mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Merge Filtering into DatTools
This commit is contained in:
341
SabreTools.DatTools/Cleaner.cs
Normal file
341
SabreTools.DatTools/Cleaner.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Core.Tools;
|
||||
using SabreTools.DatFiles;
|
||||
using SabreTools.DatItems;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
namespace SabreTools.DatTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the cleaning operations that need to be performed on a set of items, usually a DAT
|
||||
/// </summary>
|
||||
public class Cleaner
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Clean all names to WoD standards
|
||||
/// </summary>
|
||||
public bool Clean { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deduplicate items using the given method
|
||||
/// </summary>
|
||||
public DedupeType DedupeRoms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set Machine Description from Machine Name
|
||||
/// </summary>
|
||||
public bool DescriptionAsName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Keep machines that don't contain any items
|
||||
/// </summary>
|
||||
public bool KeepEmptyGames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable "One Rom, One Region (1G1R)" mode
|
||||
/// </summary>
|
||||
public bool OneGamePerRegion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ordered list of regions for "One Rom, One Region (1G1R)" mode
|
||||
/// </summary>
|
||||
public List<string>? RegionList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensure each rom is in their own game
|
||||
/// </summary>
|
||||
public bool OneRomPerGame { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remove all unicode characters
|
||||
/// </summary>
|
||||
public bool RemoveUnicode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Include root directory when determing trim sizes
|
||||
/// </summary>
|
||||
public string? Root { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remove scene dates from the beginning of machine names
|
||||
/// </summary>
|
||||
public bool SceneDateStrip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Change all machine names to "!"
|
||||
/// </summary>
|
||||
public bool Single { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Trim total machine and item name to not exceed NTFS limits
|
||||
/// </summary>
|
||||
public bool Trim { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
private readonly Logger logger = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Running
|
||||
|
||||
/// <summary>
|
||||
/// Apply cleaning methods to the DatFile
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
|
||||
/// <returns>True if cleaning was successful, false on error</returns>
|
||||
public bool ApplyCleaning(DatFile datFile, bool throwOnError = false)
|
||||
{
|
||||
InternalStopwatch watch = new("Applying cleaning steps to DAT");
|
||||
|
||||
try
|
||||
{
|
||||
// Perform item-level cleaning
|
||||
CleanDatItems(datFile);
|
||||
CleanDatItemsDB(datFile);
|
||||
|
||||
// Bucket and dedupe according to the flag
|
||||
if (DedupeRoms == DedupeType.Full)
|
||||
{
|
||||
datFile.Items.BucketBy(ItemKey.CRC, DedupeRoms);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.CRC, DedupeRoms);
|
||||
}
|
||||
else if (DedupeRoms == DedupeType.Game)
|
||||
{
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeRoms);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeRoms);
|
||||
}
|
||||
|
||||
// Process description to machine name
|
||||
if (DescriptionAsName == true)
|
||||
{
|
||||
datFile.Items.MachineDescriptionToName(throwOnError);
|
||||
datFile.ItemsDB.MachineDescriptionToName(throwOnError);
|
||||
}
|
||||
|
||||
// If we are removing scene dates, do that now
|
||||
if (SceneDateStrip == true)
|
||||
{
|
||||
datFile.Items.StripSceneDatesFromItems();
|
||||
datFile.ItemsDB.StripSceneDatesFromItems();
|
||||
}
|
||||
|
||||
// Run the one rom per game logic, if required
|
||||
if (OneGamePerRegion == true && RegionList != null)
|
||||
{
|
||||
datFile.Items.SetOneGamePerRegion(RegionList);
|
||||
datFile.ItemsDB.SetOneGamePerRegion(RegionList);
|
||||
}
|
||||
|
||||
// Run the one rom per game logic, if required
|
||||
if (OneRomPerGame == true)
|
||||
{
|
||||
datFile.Items.SetOneRomPerGame();
|
||||
datFile.ItemsDB.SetOneRomPerGame();
|
||||
}
|
||||
|
||||
// Remove all marked items
|
||||
datFile.Items.ClearMarked();
|
||||
datFile.ItemsDB.ClearMarked();
|
||||
|
||||
// We remove any blanks, if we aren't supposed to have any
|
||||
if (KeepEmptyGames == false)
|
||||
{
|
||||
datFile.Items.ClearEmpty();
|
||||
datFile.ItemsDB.ClearEmpty();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean individual items based on the current filter
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal void CleanDatItems(DatFile datFile)
|
||||
{
|
||||
List<string> keys = [.. datFile.Items.Keys];
|
||||
foreach (string key in keys)
|
||||
{
|
||||
// For every item in the current key
|
||||
var items = datFile.Items[key];
|
||||
if (items == null)
|
||||
continue;
|
||||
|
||||
foreach (DatItem item in items)
|
||||
{
|
||||
// If we have a null item, we can't clean it it
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
// Run cleaning per item
|
||||
CleanDatItem(item);
|
||||
}
|
||||
|
||||
// Assign back for caution
|
||||
datFile.Items[key] = items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean individual items based on the current filter
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal void CleanDatItemsDB(DatFile datFile)
|
||||
{
|
||||
List<string> keys = [.. datFile.ItemsDB.SortedKeys];
|
||||
foreach (string key in keys)
|
||||
{
|
||||
// For every item in the current key
|
||||
var items = datFile.ItemsDB.GetItemsForBucket(key);
|
||||
if (items == null)
|
||||
continue;
|
||||
|
||||
foreach ((long, DatItem) item in items)
|
||||
{
|
||||
// If we have a null item, we can't clean it it
|
||||
if (item.Item2 == null)
|
||||
continue;
|
||||
|
||||
// Run cleaning per item
|
||||
CleanDatItemDB(datFile.ItemsDB, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean a DatItem according to the cleaner
|
||||
/// </summary>
|
||||
/// <param name="datItem">DatItem to clean</param>
|
||||
internal void CleanDatItem(DatItem datItem)
|
||||
{
|
||||
// Get the machine associated with the item, if possible
|
||||
var machine = datItem.GetFieldValue<Machine>(DatItem.MachineKey);
|
||||
if (machine == null)
|
||||
return;
|
||||
|
||||
// Get the fields for processing
|
||||
string? machineName = machine.GetStringFieldValue(Models.Metadata.Machine.NameKey);
|
||||
string? machineDesc = machine.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey);
|
||||
string? datItemName = datItem.GetName();
|
||||
|
||||
// If we're stripping unicode characters, strip machine name and description
|
||||
if (RemoveUnicode)
|
||||
{
|
||||
machineName = TextHelper.RemoveUnicodeCharacters(machineName);
|
||||
machineDesc = TextHelper.RemoveUnicodeCharacters(machineDesc);
|
||||
datItemName = TextHelper.RemoveUnicodeCharacters(datItemName);
|
||||
}
|
||||
|
||||
// If we're in cleaning mode, sanitize machine name and description
|
||||
if (Clean)
|
||||
{
|
||||
machineName = TextHelper.NormalizeCharacters(machineName);
|
||||
machineDesc = TextHelper.NormalizeCharacters(machineDesc);
|
||||
}
|
||||
|
||||
// If we are in single game mode, rename the machine
|
||||
if (Single)
|
||||
{
|
||||
machineName = "!";
|
||||
machineDesc = "!";
|
||||
}
|
||||
|
||||
// If we are in NTFS trim mode, trim the item name
|
||||
if (Trim && datItemName != null)
|
||||
{
|
||||
// Windows max name length is 260
|
||||
int usableLength = 260 - (machineName?.Length ?? 0) - (Root?.Length ?? 0);
|
||||
if (datItemName.Length > usableLength)
|
||||
{
|
||||
string ext = Path.GetExtension(datItemName);
|
||||
datItemName = datItemName.Substring(0, usableLength - ext.Length) + ext;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the fields back, if necessary
|
||||
machine.SetFieldValue<string?>(Models.Metadata.Machine.NameKey, machineName);
|
||||
machine.SetFieldValue<string?>(Models.Metadata.Machine.DescriptionKey, machineDesc);
|
||||
datItem.SetName(datItemName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean a DatItem according to the cleaner
|
||||
/// </summary>
|
||||
/// <param name="db">ItemDictionaryDB to get machine information from</param>
|
||||
/// <param name="datItem">DatItem to clean</param>
|
||||
internal void CleanDatItemDB(ItemDictionaryDB db, (long, DatItem) datItem)
|
||||
{
|
||||
// Get the machine associated with the item, if possible
|
||||
var machine = db.GetMachineForItem(datItem.Item1);
|
||||
if (machine.Item2 == null)
|
||||
return;
|
||||
|
||||
// Get the fields for processing
|
||||
string? machineName = machine.Item2.GetStringFieldValue(Models.Metadata.Machine.NameKey);
|
||||
string? machineDesc = machine.Item2.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey);
|
||||
string? datItemName = datItem.Item2.GetName();
|
||||
|
||||
// If we're stripping unicode characters, strip machine name and description
|
||||
if (RemoveUnicode)
|
||||
{
|
||||
machineName = TextHelper.RemoveUnicodeCharacters(machineName);
|
||||
machineDesc = TextHelper.RemoveUnicodeCharacters(machineDesc);
|
||||
datItemName = TextHelper.RemoveUnicodeCharacters(datItemName);
|
||||
}
|
||||
|
||||
// If we're in cleaning mode, sanitize machine name and description
|
||||
if (Clean)
|
||||
{
|
||||
machineName = TextHelper.NormalizeCharacters(machineName);
|
||||
machineDesc = TextHelper.NormalizeCharacters(machineDesc);
|
||||
}
|
||||
|
||||
// If we are in single game mode, rename the machine
|
||||
if (Single)
|
||||
{
|
||||
machineName = "!";
|
||||
machineDesc = "!";
|
||||
}
|
||||
|
||||
// If we are in NTFS trim mode, trim the item name
|
||||
if (Trim && datItemName != null)
|
||||
{
|
||||
// Windows max name length is 260
|
||||
int usableLength = 260 - (machineName?.Length ?? 0) - (Root?.Length ?? 0);
|
||||
if (datItemName.Length > usableLength)
|
||||
{
|
||||
string ext = Path.GetExtension(datItemName);
|
||||
datItemName = datItemName.Substring(0, usableLength - ext.Length) + ext;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the fields back, if necessary
|
||||
machine.Item2.SetFieldValue<string?>(Models.Metadata.Machine.NameKey, machineName);
|
||||
machine.Item2.SetFieldValue<string?>(Models.Metadata.Machine.DescriptionKey, machineDesc);
|
||||
datItem.Item2.SetName(datItemName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
#endif
|
||||
using SabreTools.DatFiles;
|
||||
using SabreTools.DatItems;
|
||||
using SabreTools.Filtering;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
|
||||
234
SabreTools.DatTools/ExtraIni.cs
Normal file
234
SabreTools.DatTools/ExtraIni.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SabreTools.Core.Filter;
|
||||
using SabreTools.DatFiles;
|
||||
using SabreTools.DatItems;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
namespace SabreTools.DatTools
|
||||
{
|
||||
public class ExtraIni
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// List of extras to apply
|
||||
/// </summary>
|
||||
public readonly List<ExtraIniItem> Items = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
private readonly Logger logger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ExtraIni()
|
||||
{
|
||||
logger = new Logger(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Population
|
||||
|
||||
/// <summary>
|
||||
/// Populate item using field:file inputs
|
||||
/// </summary>
|
||||
/// <param name="inputs">Field and file combinations</param>
|
||||
public void PopulateFromList(List<string> inputs)
|
||||
{
|
||||
// If there are no inputs, just skip
|
||||
if (inputs == null || inputs.Count == 0)
|
||||
return;
|
||||
|
||||
InternalStopwatch watch = new("Populating extras from list");
|
||||
|
||||
foreach (string input in inputs)
|
||||
{
|
||||
// If we don't even have a possible field and file combination
|
||||
if (!input.Contains(":"))
|
||||
{
|
||||
logger.Warning($"'{input}` is not a valid INI extras string. Valid INI extras strings are of the form 'key:value'. Please refer to README.1ST or the help feature for more details.");
|
||||
return;
|
||||
}
|
||||
|
||||
string inputTrimmed = input.Trim('"', ' ', '\t');
|
||||
string fieldString = inputTrimmed.Split(':')[0].ToLowerInvariant().Trim('"', ' ', '\t');
|
||||
string fileString = inputTrimmed.Substring(fieldString.Length + 1).Trim('"', ' ', '\t');
|
||||
|
||||
var item = new ExtraIniItem(fieldString, fileString);
|
||||
if (item.Mappings.Count > 0)
|
||||
Items.Add(item);
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Running
|
||||
|
||||
/// <summary>
|
||||
/// Apply a set of Extra INIs on the DatFile
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
|
||||
/// <returns>True if the extras were applied, false on error</returns>
|
||||
public bool ApplyExtras(DatFile datFile, bool throwOnError = false)
|
||||
{
|
||||
// If we have no extras, don't attempt to apply and just return true
|
||||
if (Items == null || Items.Count == 0)
|
||||
return true;
|
||||
|
||||
var watch = new InternalStopwatch("Applying extra mappings to DAT");
|
||||
|
||||
try
|
||||
{
|
||||
// Bucket by game first
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeType.None);
|
||||
|
||||
// Create mappings based on the extra items
|
||||
var combinedMaps = CombineExtras();
|
||||
var machines = combinedMaps.Keys;
|
||||
|
||||
// Apply the mappings
|
||||
foreach (string machine in machines)
|
||||
{
|
||||
// If the key doesn't exist, continue
|
||||
if (!datFile.Items.ContainsKey(machine))
|
||||
continue;
|
||||
|
||||
// Get the list of DatItems for the machine
|
||||
var datItems = datFile.Items[machine];
|
||||
if (datItems == null)
|
||||
continue;
|
||||
|
||||
// Try to get the map values, if possible
|
||||
combinedMaps.TryGetValue(machine, out var mappings);
|
||||
|
||||
// Create a setter with the new mappings
|
||||
var setter = new Setter();
|
||||
setter.PopulateSettersFromDictionary(mappings);
|
||||
|
||||
// Loop through and set the fields accordingly
|
||||
foreach (var datItem in datItems)
|
||||
{
|
||||
setter.SetFields(datItem.GetFieldValue<Machine>(DatItem.MachineKey));
|
||||
setter.SetFields(datItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a set of Extra INIs on the DatFile
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
|
||||
/// <returns>True if the extras were applied, false on error</returns>
|
||||
public bool ApplyExtrasDB(DatFile datFile, bool throwOnError = false)
|
||||
{
|
||||
// If we have no extras, don't attempt to apply and just return true
|
||||
if (Items == null || Items.Count == 0)
|
||||
return true;
|
||||
|
||||
var watch = new InternalStopwatch("Applying extra mappings to DAT");
|
||||
|
||||
try
|
||||
{
|
||||
// Bucket by game first
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeType.None);
|
||||
|
||||
// Create mappings based on the extra items
|
||||
var combinedMaps = CombineExtras();
|
||||
var games = combinedMaps.Keys;
|
||||
|
||||
// Apply the mappings
|
||||
foreach (string game in games)
|
||||
{
|
||||
// Get the list of DatItems for the machine
|
||||
var datItems = datFile.ItemsDB.GetItemsForBucket(game);
|
||||
if (datItems == null)
|
||||
continue;
|
||||
|
||||
// Try to get the map values, if possible
|
||||
combinedMaps.TryGetValue(game, out var mappings);
|
||||
|
||||
// Create a setter with the new mappings
|
||||
var setter = new Setter();
|
||||
setter.PopulateSettersFromDictionary(mappings);
|
||||
|
||||
// Loop through and set the fields accordingly
|
||||
foreach (var datItem in datItems)
|
||||
{
|
||||
var machine = datFile.ItemsDB.GetMachineForItem(datItem.Item1);
|
||||
if (machine.Item2 != null)
|
||||
setter.SetFields(machine.Item2);
|
||||
|
||||
setter.SetFields(datItem.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine ExtraIni fields
|
||||
/// </summary>
|
||||
/// <returns>Mapping dictionary from machine name to field mapping</returns>
|
||||
private Dictionary<string, Dictionary<FilterKey, string>> CombineExtras()
|
||||
{
|
||||
var machineMap = new Dictionary<string, Dictionary<FilterKey, string>>();
|
||||
|
||||
// Loop through each of the extras
|
||||
foreach (ExtraIniItem item in Items)
|
||||
{
|
||||
foreach (var mapping in item.Mappings)
|
||||
{
|
||||
string machineName = mapping.Key;
|
||||
string value = mapping.Value;
|
||||
|
||||
if (!machineMap.ContainsKey(machineName))
|
||||
machineMap[machineName] = [];
|
||||
|
||||
machineMap[machineName][item.Key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return machineMap;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
258
SabreTools.DatTools/MergeSplit.cs
Normal file
258
SabreTools.DatTools/MergeSplit.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using SabreTools.Core.Tools;
|
||||
using SabreTools.DatFiles;
|
||||
using SabreTools.DatItems;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
namespace SabreTools.DatTools
|
||||
{
|
||||
public class MergeSplit
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Splitting mode to apply
|
||||
/// </summary>
|
||||
public MergingFlag SplitType { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
private static readonly Logger logger = new();
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Should any of these create a new DatFile in the process?
|
||||
// The reason this comes up is that doing any of the splits or merges
|
||||
// is an inherently destructive process. Making it output a new DatFile
|
||||
// might make it easier to deal with multiple internal steps. On the other
|
||||
// hand, this will increase memory usage significantly and would force the
|
||||
// existing paths to behave entirely differently
|
||||
#region Running
|
||||
|
||||
/// <summary>
|
||||
/// Apply splitting on the DatFile
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
/// <param name="useTags">True if DatFile tags override splitting, false otherwise</param>
|
||||
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
|
||||
/// <returns>True if the DatFile was split, false on error</returns>
|
||||
public bool ApplySplitting(DatFile datFile, bool useTags, bool throwOnError = false)
|
||||
{
|
||||
InternalStopwatch watch = new("Applying splitting to DAT");
|
||||
|
||||
try
|
||||
{
|
||||
// If we are using tags from the DAT, set the proper input for split type unless overridden
|
||||
if (useTags && SplitType == MergingFlag.None)
|
||||
SplitType = datFile.Header.GetStringFieldValue(Models.Metadata.Header.ForceMergingKey).AsEnumValue<MergingFlag>();
|
||||
|
||||
// Run internal splitting
|
||||
switch (SplitType)
|
||||
{
|
||||
// Standard
|
||||
case MergingFlag.None:
|
||||
// No-op
|
||||
break;
|
||||
case MergingFlag.Split:
|
||||
CreateSplitSets(datFile);
|
||||
break;
|
||||
case MergingFlag.Merged:
|
||||
CreateMergedSets(datFile);
|
||||
break;
|
||||
case MergingFlag.NonMerged:
|
||||
CreateNonMergedSets(datFile);
|
||||
break;
|
||||
|
||||
// Nonstandard
|
||||
case MergingFlag.FullMerged:
|
||||
CreateFullyMergedSets(datFile);
|
||||
break;
|
||||
case MergingFlag.DeviceNonMerged:
|
||||
CreateDeviceNonMergedSets(datFile);
|
||||
break;
|
||||
case MergingFlag.FullNonMerged:
|
||||
CreateFullyNonMergedSets(datFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use cdevice_ref tags to get full non-merged sets and remove parenting tags
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal static void CreateDeviceNonMergedSets(DatFile datFile)
|
||||
{
|
||||
logger.User("Creating device non-merged sets from the DAT");
|
||||
|
||||
// For sake of ease, the first thing we want to do is bucket by game
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
|
||||
// Now we want to loop through all of the games and set the correct information
|
||||
while (datFile.Items.AddRomsFromDevices(false, false)) ;
|
||||
while (datFile.ItemsDB.AddRomsFromDevices(false, false)) ;
|
||||
while (datFile.Items.AddRomsFromDevices(true, false)) ;
|
||||
while (datFile.ItemsDB.AddRomsFromDevices(true, false)) ;
|
||||
|
||||
// Then, remove the romof and cloneof tags so it's not picked up by the manager
|
||||
datFile.Items.RemoveTagsFromChild();
|
||||
datFile.ItemsDB.RemoveTagsFromChild();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use cloneof tags to create merged sets and remove the tags plus deduplicating if tags don't catch everything
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal static void CreateFullyMergedSets(DatFile datFile)
|
||||
{
|
||||
logger.User("Creating fully merged sets from the DAT");
|
||||
|
||||
// For sake of ease, the first thing we want to do is bucket by game
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
|
||||
// Now we want to loop through all of the games and set the correct information
|
||||
datFile.Items.AddRomsFromChildren(true, false);
|
||||
datFile.ItemsDB.AddRomsFromChildren(true, false);
|
||||
|
||||
// Now that we have looped through the cloneof tags, we loop through the romof tags
|
||||
datFile.Items.RemoveBiosRomsFromChild(false);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(false);
|
||||
datFile.Items.RemoveBiosRomsFromChild(true);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(true);
|
||||
|
||||
// Finally, remove the romof and cloneof tags so it's not picked up by the manager
|
||||
datFile.Items.RemoveTagsFromChild();
|
||||
datFile.ItemsDB.RemoveTagsFromChild();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use cloneof tags to create non-merged sets and remove the tags plus using the device_ref tags to get full sets
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal static void CreateFullyNonMergedSets(DatFile datFile)
|
||||
{
|
||||
logger.User("Creating fully non-merged sets from the DAT");
|
||||
|
||||
// For sake of ease, the first thing we want to do is bucket by game
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
|
||||
// Now we want to loop through all of the games and set the correct information
|
||||
while (datFile.Items.AddRomsFromDevices(true, true)) ;
|
||||
while (datFile.ItemsDB.AddRomsFromDevices(true, true)) ;
|
||||
datFile.Items.AddRomsFromDevices(false, true);
|
||||
datFile.ItemsDB.AddRomsFromDevices(false, true);
|
||||
datFile.Items.AddRomsFromParent();
|
||||
datFile.ItemsDB.AddRomsFromParent();
|
||||
|
||||
// Now that we have looped through the cloneof tags, we loop through the romof tags
|
||||
datFile.Items.AddRomsFromBios();
|
||||
datFile.ItemsDB.AddRomsFromBios();
|
||||
|
||||
// Then, remove the romof and cloneof tags so it's not picked up by the manager
|
||||
datFile.Items.RemoveTagsFromChild();
|
||||
datFile.ItemsDB.RemoveTagsFromChild();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use cloneof tags to create merged sets and remove the tags
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal static void CreateMergedSets(DatFile datFile)
|
||||
{
|
||||
logger.User("Creating merged sets from the DAT");
|
||||
|
||||
// For sake of ease, the first thing we want to do is bucket by game
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
|
||||
// Now we want to loop through all of the games and set the correct information
|
||||
datFile.Items.AddRomsFromChildren(true, true);
|
||||
datFile.ItemsDB.AddRomsFromChildren(true, true);
|
||||
|
||||
// Now that we have looped through the cloneof tags, we loop through the romof tags
|
||||
datFile.Items.RemoveBiosRomsFromChild(false);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(false);
|
||||
datFile.Items.RemoveBiosRomsFromChild(true);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(true);
|
||||
|
||||
// Finally, remove the romof and cloneof tags so it's not picked up by the manager
|
||||
datFile.Items.RemoveTagsFromChild();
|
||||
datFile.ItemsDB.RemoveTagsFromChild();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use cloneof tags to create non-merged sets and remove the tags
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal static void CreateNonMergedSets(DatFile datFile)
|
||||
{
|
||||
logger.User("Creating non-merged sets from the DAT");
|
||||
|
||||
// For sake of ease, the first thing we want to do is bucket by game
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
|
||||
// Now we want to loop through all of the games and set the correct information
|
||||
datFile.Items.AddRomsFromParent();
|
||||
datFile.ItemsDB.AddRomsFromParent();
|
||||
|
||||
// Now that we have looped through the cloneof tags, we loop through the romof tags
|
||||
datFile.Items.RemoveBiosRomsFromChild(false);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(false);
|
||||
datFile.Items.RemoveBiosRomsFromChild(true);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(true);
|
||||
|
||||
// Finally, remove the romof and cloneof tags so it's not picked up by the manager
|
||||
datFile.Items.RemoveTagsFromChild();
|
||||
datFile.ItemsDB.RemoveTagsFromChild();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use cloneof and romof tags to create split sets and remove the tags
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
internal static void CreateSplitSets(DatFile datFile)
|
||||
{
|
||||
logger.User("Creating split sets from the DAT");
|
||||
|
||||
// For sake of ease, the first thing we want to do is bucket by game
|
||||
datFile.Items.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
datFile.ItemsDB.BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
|
||||
|
||||
// Now we want to loop through all of the games and set the correct information
|
||||
datFile.Items.RemoveRomsFromChild();
|
||||
datFile.ItemsDB.RemoveRomsFromChild();
|
||||
|
||||
// Now that we have looped through the cloneof tags, we loop through the romof tags
|
||||
datFile.Items.RemoveBiosRomsFromChild(false);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(false);
|
||||
datFile.Items.RemoveBiosRomsFromChild(true);
|
||||
datFile.ItemsDB.RemoveBiosRomsFromChild(true);
|
||||
|
||||
// Finally, remove the romof and cloneof tags so it's not picked up by the manager
|
||||
datFile.Items.RemoveTagsFromChild();
|
||||
datFile.ItemsDB.RemoveTagsFromChild();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
139
SabreTools.DatTools/Remover.cs
Normal file
139
SabreTools.DatTools/Remover.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System.Collections.Generic;
|
||||
using SabreTools.Core.Filter;
|
||||
using SabreTools.DatFiles;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
namespace SabreTools.DatTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the removal operations that need to be performed on a set of items, usually a DAT
|
||||
/// </summary>
|
||||
public class Remover
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// List of header fields to exclude from writing
|
||||
/// </summary>
|
||||
public readonly List<string> HeaderFieldNames = [];
|
||||
|
||||
/// <summary>
|
||||
/// List of machine fields to exclude from writing
|
||||
/// </summary>
|
||||
public readonly List<string> MachineFieldNames = [];
|
||||
|
||||
/// <summary>
|
||||
/// List of fields to exclude from writing
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, List<string>> ItemFieldNames = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
protected Logger logger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public Remover()
|
||||
{
|
||||
logger = new Logger(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Population
|
||||
|
||||
/// <summary>
|
||||
/// Populate the exclusion objects using a field name
|
||||
/// </summary>
|
||||
/// <param name="field">Field name</param>
|
||||
public void PopulateExclusions(string field)
|
||||
=> PopulateExclusionsFromList([field]);
|
||||
|
||||
/// <summary>
|
||||
/// Populate the exclusion objects using a set of field names
|
||||
/// </summary>
|
||||
/// <param name="fields">List of field names</param>
|
||||
public void PopulateExclusionsFromList(List<string>? fields)
|
||||
{
|
||||
// If the list is null or empty, just return
|
||||
if (fields == null || fields.Count == 0)
|
||||
return;
|
||||
|
||||
var watch = new InternalStopwatch("Populating removals from list");
|
||||
|
||||
foreach (string field in fields)
|
||||
{
|
||||
bool removerSet = SetRemover(field);
|
||||
if (!removerSet)
|
||||
logger.Warning($"The value {field} did not match any known field names. Please check the wiki for more details on supported field names.");
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set remover from a value
|
||||
/// </summary>
|
||||
/// <param name="field">Key for the remover to be set</param>
|
||||
private bool SetRemover(string field)
|
||||
{
|
||||
// If the key is null or empty, return false
|
||||
if (string.IsNullOrEmpty(field))
|
||||
return false;
|
||||
|
||||
// Get the parser pair out of it, if possible
|
||||
try
|
||||
{
|
||||
var key = new FilterKey(field);
|
||||
switch (key.ItemName)
|
||||
{
|
||||
case Models.Metadata.MetadataFile.HeaderKey:
|
||||
HeaderFieldNames.Add(key.FieldName);
|
||||
return true;
|
||||
|
||||
case Models.Metadata.MetadataFile.MachineKey:
|
||||
MachineFieldNames.Add(key.FieldName);
|
||||
return true;
|
||||
|
||||
default:
|
||||
if (!ItemFieldNames.ContainsKey(key.ItemName))
|
||||
ItemFieldNames[key.ItemName] = [];
|
||||
|
||||
ItemFieldNames[key.ItemName].Add(key.FieldName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Running
|
||||
|
||||
/// <summary>
|
||||
/// Remove fields from a DatFile
|
||||
/// </summary>
|
||||
/// <param name="datFile">Current DatFile object to run operations on</param>
|
||||
public void ApplyRemovals(DatFile datFile)
|
||||
{
|
||||
InternalStopwatch watch = new("Applying removals to DAT");
|
||||
datFile.ApplyRemovals(HeaderFieldNames, MachineFieldNames, ItemFieldNames);
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
204
SabreTools.DatTools/Replacer.cs
Normal file
204
SabreTools.DatTools/Replacer.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SabreTools.Core.Tools;
|
||||
using SabreTools.DatItems;
|
||||
using SabreTools.DatItems.Formats;
|
||||
|
||||
namespace SabreTools.DatTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Replace fields in DatItems
|
||||
/// </summary>
|
||||
public static class Replacer
|
||||
{
|
||||
/// <summary>
|
||||
/// Replace fields with given values
|
||||
/// </summary>
|
||||
/// <param name="machine">Machine to replace fields in</param>
|
||||
/// <param name="repMachine">Machine to pull new information from</param>
|
||||
/// <param name="machineFieldNames">List of fields representing what should be updated</param>
|
||||
/// <param name="onlySame">True if descriptions should only be replaced if the game name is the same, false otherwise</param>
|
||||
public static void ReplaceFields(Machine machine, Machine repMachine, List<string> machineFieldNames, bool onlySame)
|
||||
{
|
||||
// If we have an invalid input, return
|
||||
if (machine == null || repMachine == null || machineFieldNames == null)
|
||||
return;
|
||||
|
||||
// Loop through and replace fields
|
||||
foreach (string fieldName in machineFieldNames)
|
||||
{
|
||||
// Special case for description
|
||||
if (machineFieldNames.Contains(Models.Metadata.Machine.DescriptionKey))
|
||||
{
|
||||
if (!onlySame || (onlySame && machine.GetStringFieldValue(Models.Metadata.Machine.NameKey) == machine.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)))
|
||||
machine.SetFieldValue<string?>(Models.Metadata.Machine.DescriptionKey, repMachine.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
machine.ReplaceField(repMachine, fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace fields with given values
|
||||
/// </summary>
|
||||
/// <param name="datItem">DatItem to replace fields in</param>
|
||||
/// <param name="repDatItem">DatItem to pull new information from</param>
|
||||
/// <param name="itemFieldNames">List of fields representing what should be updated</param>
|
||||
public static void ReplaceFields(DatItem datItem, DatItem repDatItem, Dictionary<string, List<string>> itemFieldNames)
|
||||
{
|
||||
// If we have an invalid input, return
|
||||
if (datItem == null || repDatItem == null || itemFieldNames == null)
|
||||
return;
|
||||
|
||||
#region Common
|
||||
|
||||
if (datItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) != repDatItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey))
|
||||
return;
|
||||
|
||||
// If there are no field names for this type or generic, return
|
||||
string? itemType = datItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue<ItemType>().AsStringValue();
|
||||
if (itemType == null || (!itemFieldNames.ContainsKey(itemType) && !itemFieldNames.ContainsKey("item")))
|
||||
return;
|
||||
|
||||
// Get the combined list of fields to remove
|
||||
var fieldNames = new List<string>();
|
||||
if (itemFieldNames.ContainsKey(itemType))
|
||||
fieldNames.AddRange(itemFieldNames[itemType]);
|
||||
if (itemFieldNames.ContainsKey("item"))
|
||||
fieldNames.AddRange(itemFieldNames["item"]);
|
||||
fieldNames = fieldNames.Distinct().ToList();
|
||||
|
||||
// If the field specifically contains Name, set it separately
|
||||
if (fieldNames.Contains(Models.Metadata.Rom.NameKey))
|
||||
datItem.SetName(repDatItem.GetName());
|
||||
|
||||
#endregion
|
||||
|
||||
#region Item-Specific
|
||||
|
||||
// Handle normal sets first
|
||||
foreach (var fieldName in fieldNames)
|
||||
{
|
||||
datItem.ReplaceField(repDatItem, fieldName);
|
||||
}
|
||||
|
||||
// TODO: Filter out hashes before here so these checks actually work
|
||||
// Handle special cases
|
||||
switch (datItem, repDatItem)
|
||||
{
|
||||
case (Disk disk, Disk repDisk): ReplaceFields(disk, repDisk, fieldNames); break;
|
||||
case (Media media, Media repMedia): ReplaceFields(media, repMedia, fieldNames); break;
|
||||
case (Rom rom, Rom repRom): ReplaceFields(rom, repRom, fieldNames); break;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace fields with given values
|
||||
/// </summary>
|
||||
/// <param name="disk">Disk to remove replace fields in</param>
|
||||
/// <param name="newItem">Disk to pull new information from</param>
|
||||
/// <param name="datItemFields">List of fields representing what should be updated</param>
|
||||
private static void ReplaceFields(Disk disk, Disk newItem, List<string> datItemFields)
|
||||
{
|
||||
if (datItemFields.Contains(Models.Metadata.Disk.MD5Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Models.Metadata.Disk.MD5Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Disk.MD5Key)))
|
||||
disk.SetFieldValue<string?>(Models.Metadata.Disk.MD5Key, newItem.GetStringFieldValue(Models.Metadata.Disk.MD5Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Disk.SHA1Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(disk.GetStringFieldValue(Models.Metadata.Disk.SHA1Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Disk.SHA1Key)))
|
||||
disk.SetFieldValue<string?>(Models.Metadata.Disk.SHA1Key, newItem.GetStringFieldValue(Models.Metadata.Disk.SHA1Key));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace fields with given values
|
||||
/// </summary>
|
||||
/// <param name="media">Media to remove replace fields in</param>
|
||||
/// <param name="newItem">Media to pull new information from</param>
|
||||
/// <param name="datItemFields">List of fields representing what should be updated</param>
|
||||
private static void ReplaceFields(Media media, Media newItem, List<string> datItemFields)
|
||||
{
|
||||
if (datItemFields.Contains(Models.Metadata.Media.MD5Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(media.GetStringFieldValue(Models.Metadata.Media.MD5Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Media.MD5Key)))
|
||||
media.SetFieldValue<string?>(Models.Metadata.Media.MD5Key, newItem.GetStringFieldValue(Models.Metadata.Media.MD5Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Media.SHA1Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(media.GetStringFieldValue(Models.Metadata.Media.SHA1Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Media.SHA1Key)))
|
||||
media.SetFieldValue<string?>(Models.Metadata.Media.SHA1Key, newItem.GetStringFieldValue(Models.Metadata.Media.SHA1Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Media.SHA256Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(media.GetStringFieldValue(Models.Metadata.Media.SHA256Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Media.SHA256Key)))
|
||||
media.SetFieldValue<string?>(Models.Metadata.Media.SHA256Key, newItem.GetStringFieldValue(Models.Metadata.Media.SHA256Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Media.SpamSumKey))
|
||||
{
|
||||
if (string.IsNullOrEmpty(media.GetStringFieldValue(Models.Metadata.Media.SpamSumKey)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Media.SpamSumKey)))
|
||||
media.SetFieldValue<string?>(Models.Metadata.Media.SpamSumKey, newItem.GetStringFieldValue(Models.Metadata.Media.SpamSumKey));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace fields with given values
|
||||
/// </summary>
|
||||
/// <param name="rom">Rom to remove replace fields in</param>
|
||||
/// <param name="newItem">Rom to pull new information from</param>
|
||||
/// <param name="datItemFields">List of fields representing what should be updated</param>
|
||||
private static void ReplaceFields(Rom rom, Rom newItem, List<string> datItemFields)
|
||||
{
|
||||
if (datItemFields.Contains(Models.Metadata.Rom.CRCKey))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Models.Metadata.Rom.CRCKey)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Rom.CRCKey)))
|
||||
rom.SetFieldValue<string?>(Models.Metadata.Rom.CRCKey, newItem.GetStringFieldValue(Models.Metadata.Rom.CRCKey));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Rom.MD5Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Models.Metadata.Rom.MD5Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Rom.MD5Key)))
|
||||
rom.SetFieldValue<string?>(Models.Metadata.Rom.MD5Key, newItem.GetStringFieldValue(Models.Metadata.Rom.MD5Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Rom.SHA1Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Models.Metadata.Rom.SHA1Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Rom.SHA1Key)))
|
||||
rom.SetFieldValue<string?>(Models.Metadata.Rom.SHA1Key, newItem.GetStringFieldValue(Models.Metadata.Rom.SHA1Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Rom.SHA256Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Models.Metadata.Rom.SHA256Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Rom.SHA256Key)))
|
||||
rom.SetFieldValue<string?>(Models.Metadata.Rom.SHA256Key, newItem.GetStringFieldValue(Models.Metadata.Rom.SHA256Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Rom.SHA384Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Models.Metadata.Rom.SHA384Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Rom.SHA384Key)))
|
||||
rom.SetFieldValue<string?>(Models.Metadata.Rom.SHA384Key, newItem.GetStringFieldValue(Models.Metadata.Rom.SHA384Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Rom.SHA512Key))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Models.Metadata.Rom.SHA512Key)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Rom.SHA512Key)))
|
||||
rom.SetFieldValue<string?>(Models.Metadata.Rom.SHA512Key, newItem.GetStringFieldValue(Models.Metadata.Rom.SHA512Key));
|
||||
}
|
||||
|
||||
if (datItemFields.Contains(Models.Metadata.Rom.SpamSumKey))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rom.GetStringFieldValue(Models.Metadata.Rom.SpamSumKey)) && !string.IsNullOrEmpty(newItem.GetStringFieldValue(Models.Metadata.Rom.SpamSumKey)))
|
||||
rom.SetFieldValue<string?>(Models.Metadata.Rom.SpamSumKey, newItem.GetStringFieldValue(Models.Metadata.Rom.SpamSumKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,15 @@
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="SabreTools.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Core\SabreTools.Core.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.DatFiles\SabreTools.DatFiles.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.DatItems\SabreTools.DatItems.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.FileTypes\SabreTools.FileTypes.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Filtering\SabreTools.Filtering.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Reports\SabreTools.Reports.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
465
SabreTools.DatTools/Setter.cs
Normal file
465
SabreTools.DatTools/Setter.cs
Normal file
@@ -0,0 +1,465 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SabreTools.Core.Filter;
|
||||
using SabreTools.Core.Tools;
|
||||
using SabreTools.DatFiles;
|
||||
using SabreTools.DatItems;
|
||||
using SabreTools.DatItems.Formats;
|
||||
using SabreTools.IO.Logging;
|
||||
|
||||
namespace SabreTools.DatTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Set fields on DatItems
|
||||
/// </summary>
|
||||
public class Setter
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Mappings to set DatHeader fields
|
||||
/// </summary>
|
||||
public Dictionary<string, string> HeaderFieldMappings { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Mappings to set Machine fields
|
||||
/// </summary>
|
||||
public Dictionary<string, string> MachineFieldMappings { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Mappings to set DatItem fields
|
||||
/// </summary>
|
||||
public Dictionary<FilterKey, string> ItemFieldMappings { get; } = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
private readonly Logger logger = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Population
|
||||
|
||||
/// <summary>
|
||||
/// Populate the setters using a field name and a value
|
||||
/// </summary>
|
||||
/// <param name="field">Field name</param>
|
||||
/// <param name="value">Field value</param>
|
||||
public void PopulateSetters(FilterKey field, string value)
|
||||
=> PopulateSettersFromList([field], [value]);
|
||||
|
||||
/// <summary>
|
||||
/// Populate the setters using a set of field names
|
||||
/// </summary>
|
||||
/// <param name="fields">List of field names</param>
|
||||
/// <param name="values">List of field values</param>
|
||||
public void PopulateSettersFromList(List<FilterKey> fields, List<string> values)
|
||||
{
|
||||
// If the list is null or empty, just return
|
||||
if (values == null || values.Count == 0)
|
||||
return;
|
||||
|
||||
var watch = new InternalStopwatch("Populating setters from list");
|
||||
|
||||
// Now we loop through and get values for everything
|
||||
for (int i = 0; i < fields.Count; i++)
|
||||
{
|
||||
FilterKey field = fields[i];
|
||||
string value = values[i];
|
||||
|
||||
if (!SetSetter(field, value))
|
||||
logger.Warning($"The value {value} did not match any known field names. Please check the wiki for more details on supported field names.");
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate the setters using a set of field names
|
||||
/// </summary>
|
||||
/// <param name="mappings">Dictionary of mappings</param>
|
||||
public void PopulateSettersFromDictionary(Dictionary<FilterKey, string>? mappings)
|
||||
{
|
||||
// If the dictionary is null or empty, just return
|
||||
if (mappings == null || mappings.Count == 0)
|
||||
return;
|
||||
|
||||
var watch = new InternalStopwatch("Populating setters from dictionary");
|
||||
|
||||
// Now we loop through and get values for everything
|
||||
foreach (var mapping in mappings)
|
||||
{
|
||||
FilterKey field = mapping.Key;
|
||||
string value = mapping.Value;
|
||||
|
||||
if (!SetSetter(field, value))
|
||||
logger.Warning($"The value {value} did not match any known field names. Please check the wiki for more details on supported field names.");
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set remover from a value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for the remover to be set</param>
|
||||
private bool SetSetter(FilterKey key, string value)
|
||||
{
|
||||
switch (key.ItemName)
|
||||
{
|
||||
case Models.Metadata.MetadataFile.HeaderKey:
|
||||
HeaderFieldMappings[key.FieldName] = value;
|
||||
return true;
|
||||
|
||||
case Models.Metadata.MetadataFile.MachineKey:
|
||||
MachineFieldMappings[key.FieldName] = value;
|
||||
return true;
|
||||
|
||||
default:
|
||||
ItemFieldMappings[key] = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="datHeader">DatHeader to set fields on</param>
|
||||
public void SetFields(DatHeader datHeader)
|
||||
{
|
||||
// If we have an invalid input, return
|
||||
if (datHeader == null || HeaderFieldMappings.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var kvp in HeaderFieldMappings)
|
||||
{
|
||||
datHeader.SetField(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="machine">Machine to set fields on</param>
|
||||
public void SetFields(Machine? machine)
|
||||
{
|
||||
// If we have an invalid input, return
|
||||
if (machine == null || MachineFieldMappings.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var kvp in MachineFieldMappings)
|
||||
{
|
||||
machine.SetField(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="datItem">DatItem to set fields on</param>
|
||||
public void SetFields(DatItem datItem)
|
||||
{
|
||||
// If we have an invalid input, return
|
||||
if (datItem == null)
|
||||
return;
|
||||
|
||||
#region Common
|
||||
|
||||
// Handle Machine fields
|
||||
if (MachineFieldMappings.Count > 0 && datItem.GetFieldValue<Machine>(DatItem.MachineKey) != null)
|
||||
SetFields(datItem.GetFieldValue<Machine>(DatItem.MachineKey)!);
|
||||
|
||||
// If there are no field names, return
|
||||
if (ItemFieldMappings == null || ItemFieldMappings.Count == 0)
|
||||
return;
|
||||
|
||||
// If there are no field names for this type or generic, return
|
||||
string? itemType = datItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue<ItemType>().AsStringValue();
|
||||
if (itemType == null || (!ItemFieldMappings.Keys.Any(kvp => kvp.ItemName == itemType) && !ItemFieldMappings.Keys.Any(kvp => kvp.ItemName == "item")))
|
||||
return;
|
||||
|
||||
// Get the combined list of fields to remove
|
||||
var fieldMappings = new Dictionary<string, string>();
|
||||
foreach (var mapping in ItemFieldMappings.Where(kvp => kvp.Key.ItemName == "item").ToDictionary(kvp => kvp.Key.FieldName, kvp => kvp.Value))
|
||||
{
|
||||
fieldMappings[mapping.Key] = mapping.Value;
|
||||
}
|
||||
foreach (var mapping in ItemFieldMappings.Where(kvp => kvp.Key.ItemName == itemType).ToDictionary(kvp => kvp.Key.FieldName, kvp => kvp.Value))
|
||||
{
|
||||
fieldMappings[mapping.Key] = mapping.Value;
|
||||
}
|
||||
|
||||
// If the field specifically contains Name, set it separately
|
||||
if (fieldMappings.Keys.Contains(Models.Metadata.Rom.NameKey))
|
||||
{
|
||||
datItem.SetName(fieldMappings[Models.Metadata.Rom.NameKey]);
|
||||
fieldMappings.Remove(Models.Metadata.Rom.NameKey);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Item-Specific
|
||||
|
||||
// Handle unnested sets first
|
||||
foreach (var kvp in fieldMappings)
|
||||
{
|
||||
datItem.SetField(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
// Handle nested sets
|
||||
switch (datItem)
|
||||
{
|
||||
case Adjuster adjuster: SetFields(adjuster); break;
|
||||
case Configuration condition: SetFields(condition); break;
|
||||
case ConfSetting confSetting: SetFields(confSetting); break;
|
||||
case Device device: SetFields(device); break;
|
||||
case DipSwitch dipSwitch: SetFields(dipSwitch); break;
|
||||
case DipValue dipValue: SetFields(dipValue); break;
|
||||
case Disk disk: SetFields(disk); break;
|
||||
case Input input: SetFields(input); break;
|
||||
case Part part: SetFields(part); break;
|
||||
case Port port: SetFields(port); break;
|
||||
case Rom rom: SetFields(rom); break;
|
||||
case Slot slot: SetFields(slot); break;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="adjuster">Adjuster to remove replace fields in</param>
|
||||
private void SetFields(Adjuster adjuster)
|
||||
{
|
||||
// Field.DatItem_Conditions does not apply here
|
||||
if (adjuster.ConditionsSpecified)
|
||||
{
|
||||
foreach (Condition subCondition in adjuster.GetFieldValue<Condition[]?>(Models.Metadata.Adjuster.ConditionKey)!)
|
||||
{
|
||||
SetFields(subCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="configuration">Configuration to remove replace fields in</param>
|
||||
private void SetFields(Configuration configuration)
|
||||
{
|
||||
if (configuration.ConditionsSpecified)
|
||||
{
|
||||
foreach (Condition subCondition in configuration.GetFieldValue<Condition[]?>(Models.Metadata.Configuration.ConditionKey)!)
|
||||
{
|
||||
SetFields(subCondition);
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.LocationsSpecified)
|
||||
{
|
||||
foreach (ConfLocation subLocation in configuration.GetFieldValue<ConfLocation[]?>(Models.Metadata.Configuration.ConfLocationKey)!)
|
||||
{
|
||||
SetFields(subLocation);
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.SettingsSpecified)
|
||||
{
|
||||
foreach (ConfSetting subSetting in configuration.GetFieldValue<ConfSetting[]?>(Models.Metadata.Configuration.ConfSettingKey)!)
|
||||
{
|
||||
SetFields(subSetting as DatItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="confSetting">ConfSetting to remove replace fields in</param>
|
||||
private void SetFields(ConfSetting confSetting)
|
||||
{
|
||||
if (confSetting.ConditionsSpecified)
|
||||
{
|
||||
foreach (Condition subCondition in confSetting.GetFieldValue<Condition[]?>(Models.Metadata.ConfSetting.ConditionKey)!)
|
||||
{
|
||||
SetFields(subCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="device">Device to remove replace fields in</param>
|
||||
private void SetFields(Device device)
|
||||
{
|
||||
if (device.ExtensionsSpecified)
|
||||
{
|
||||
foreach (Extension subExtension in device.GetFieldValue<Extension[]?>(Models.Metadata.Device.ExtensionKey)!)
|
||||
{
|
||||
SetFields(subExtension);
|
||||
}
|
||||
}
|
||||
|
||||
if (device.InstancesSpecified)
|
||||
{
|
||||
foreach (Instance subInstance in device.GetFieldValue<Instance[]?>(Models.Metadata.Device.InstanceKey)!)
|
||||
{
|
||||
SetFields(subInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="dipSwitch">DipSwitch to remove replace fields in</param>
|
||||
private void SetFields(DipSwitch dipSwitch)
|
||||
{
|
||||
if (dipSwitch.ConditionsSpecified)
|
||||
{
|
||||
foreach (Condition subCondition in dipSwitch.GetFieldValue<Condition[]?>(Models.Metadata.DipSwitch.ConditionKey)!)
|
||||
{
|
||||
SetFields(subCondition);
|
||||
}
|
||||
}
|
||||
|
||||
if (dipSwitch.LocationsSpecified)
|
||||
{
|
||||
foreach (DipLocation subLocation in dipSwitch.GetFieldValue<DipLocation[]?>(Models.Metadata.DipSwitch.DipLocationKey)!)
|
||||
{
|
||||
SetFields(subLocation);
|
||||
}
|
||||
}
|
||||
|
||||
if (dipSwitch.ValuesSpecified)
|
||||
{
|
||||
foreach (DipValue subValue in dipSwitch.GetFieldValue<DipValue[]?>(Models.Metadata.DipSwitch.DipValueKey)!)
|
||||
{
|
||||
SetFields(subValue as DatItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (!dipSwitch.PartSpecified)
|
||||
dipSwitch.SetFieldValue<Part?>(DipSwitch.PartKey, new Part());
|
||||
|
||||
SetFields((dipSwitch.GetFieldValue<Part?>(DipSwitch.PartKey) as DatItem)!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="dipValue">DipValue to remove replace fields in</param>
|
||||
private void SetFields(DipValue dipValue)
|
||||
{
|
||||
if (dipValue.ConditionsSpecified)
|
||||
{
|
||||
foreach (Condition subCondition in dipValue.GetFieldValue<Condition[]?>(Models.Metadata.DipValue.ConditionKey)!)
|
||||
{
|
||||
SetFields(subCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="disk">Disk to remove replace fields in</param>
|
||||
private void SetFields(Disk disk)
|
||||
{
|
||||
if (!disk.DiskAreaSpecified)
|
||||
disk.SetFieldValue<DiskArea?>(Disk.DiskAreaKey, new DiskArea());
|
||||
|
||||
SetFields(disk.GetFieldValue<DiskArea?>(Disk.DiskAreaKey)! as DatItem);
|
||||
|
||||
if (!disk.PartSpecified)
|
||||
disk.SetFieldValue<Part?>(Disk.PartKey, new Part());
|
||||
|
||||
SetFields(disk.GetFieldValue<Part?>(Disk.PartKey)! as DatItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="input">Input to remove replace fields in</param>
|
||||
private void SetFields(Input input)
|
||||
{
|
||||
if (input.ControlsSpecified)
|
||||
{
|
||||
foreach (Control subControl in input.GetFieldValue<Control[]?>(Models.Metadata.Input.ControlKey)!)
|
||||
{
|
||||
SetFields(subControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>s
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="part">Part to remove replace fields in</param>
|
||||
private void SetFields(Part part)
|
||||
{
|
||||
if (part.FeaturesSpecified)
|
||||
{
|
||||
foreach (PartFeature subPartFeature in part.GetFieldValue<PartFeature[]?>(Models.Metadata.Part.FeatureKey)!)
|
||||
{
|
||||
SetFields(subPartFeature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="port">Port to remove replace fields in</param>
|
||||
private void SetFields(Port port)
|
||||
{
|
||||
if (port.AnalogsSpecified)
|
||||
{
|
||||
foreach (Analog subAnalog in port.GetFieldValue<Analog[]?>(Models.Metadata.Port.AnalogKey)!)
|
||||
{
|
||||
SetFields(subAnalog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="rom">Rom to remove replace fields in</param>
|
||||
private void SetFields(Rom rom)
|
||||
{
|
||||
if (!rom.DataAreaSpecified)
|
||||
rom.SetFieldValue<DataArea?>(Rom.DataAreaKey, new DataArea());
|
||||
|
||||
SetFields(rom.GetFieldValue<DataArea?>(Rom.DataAreaKey)! as DatItem);
|
||||
|
||||
if (!rom.PartSpecified)
|
||||
rom.SetFieldValue<Part?>(Rom.PartKey, new Part());
|
||||
|
||||
SetFields(rom.GetFieldValue<Part?>(Rom.PartKey)! as DatItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set fields with given values
|
||||
/// </summary>
|
||||
/// <param name="slot">Slot to remove replace fields in</param>
|
||||
private void SetFields(Slot slot)
|
||||
{
|
||||
if (slot.SlotOptionsSpecified)
|
||||
{
|
||||
foreach (SlotOption subSlotOption in slot.GetFieldValue<SlotOption[]?>(Models.Metadata.Slot.SlotOptionKey)!)
|
||||
{
|
||||
SetFields(subSlotOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user