diff --git a/SabreTools.Helper/SabreTools.Helper.csproj b/SabreTools.Helper/SabreTools.Helper.csproj index c94fd167..ab31812a 100644 --- a/SabreTools.Helper/SabreTools.Helper.csproj +++ b/SabreTools.Helper/SabreTools.Helper.csproj @@ -118,7 +118,9 @@ + + diff --git a/SabreTools.Helper/Tools/DatToolsHash.cs b/SabreTools.Helper/Tools/DatToolsHash.cs index 821accf3..2022e4b4 100644 --- a/SabreTools.Helper/Tools/DatToolsHash.cs +++ b/SabreTools.Helper/Tools/DatToolsHash.cs @@ -1428,7 +1428,7 @@ namespace SabreTools.Helper List hashes = dict[key]; if (mergeroms) { - hashes = RomTools.Merge(hashes, logger); + hashes = RomToolsHash.Merge(hashes, logger); } foreach (HashData hash in hashes) @@ -1505,7 +1505,7 @@ namespace SabreTools.Helper List newhashes = hashes; if (mergeroms) { - newhashes = RomTools.Merge(newhashes, logger); + newhashes = RomToolsHash.Merge(newhashes, logger); } foreach (HashData hash in newhashes) diff --git a/SabreTools.Helper/Tools/FileTools.cs b/SabreTools.Helper/Tools/FileTools.cs index 7460488f..a58f6fb1 100644 --- a/SabreTools.Helper/Tools/FileTools.cs +++ b/SabreTools.Helper/Tools/FileTools.cs @@ -1093,291 +1093,5 @@ namespace SabreTools.Helper } #endregion - - #region Hash-to-Dat functions - - // All things in this region are direct ports and do not take advantage of the multiple rom per hash that comes with the new system - - /// - /// Copy a file to an output archive - /// - /// Input filename to be moved - /// Output directory to build to - /// RomData representing the new information - /// This uses the new system that is not implemented anywhere yet - public static void WriteToArchive(string input, string output, RomData rom) - { - string archiveFileName = Path.Combine(output, rom.Machine + ".zip"); - - ZipArchive outarchive = null; - try - { - if (!File.Exists(archiveFileName)) - { - outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Create); - } - else - { - outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Update); - } - - if (File.Exists(input)) - { - if (outarchive.Mode == ZipArchiveMode.Create || outarchive.GetEntry(rom.Name) == null) - { - outarchive.CreateEntryFromFile(input, rom.Name, CompressionLevel.Optimal); - } - } - else if (Directory.Exists(input)) - { - foreach (string file in Directory.EnumerateFiles(input, "*", SearchOption.AllDirectories)) - { - if (outarchive.Mode == ZipArchiveMode.Create || outarchive.GetEntry(file) == null) - { - outarchive.CreateEntryFromFile(file, file, CompressionLevel.Optimal); - } - } - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - finally - { - outarchive?.Dispose(); - } - } - - /// - /// Copy a file to an output archive using SharpCompress - /// - /// Input filename to be moved - /// Output directory to build to - /// RomData representing the new information - /// This uses the new system that is not implemented anywhere yet - public static void WriteToManagedArchive(string input, string output, RomData rom) - { - string archiveFileName = Path.Combine(output, rom.Machine + ".zip"); - - // Delete an empty file first - if (File.Exists(archiveFileName) && new FileInfo(archiveFileName).Length == 0) - { - File.Delete(archiveFileName); - } - - // Get if the file should be written out - bool newfile = File.Exists(archiveFileName) && new FileInfo(archiveFileName).Length != 0; - - using (SharpCompress.Archive.Zip.ZipArchive archive = (newfile - ? ArchiveFactory.Open(archiveFileName, Options.LookForHeader) as SharpCompress.Archive.Zip.ZipArchive - : ArchiveFactory.Create(ArchiveType.Zip) as SharpCompress.Archive.Zip.ZipArchive)) - { - try - { - if (File.Exists(input)) - { - archive.AddEntry(rom.Name, input); - } - else if (Directory.Exists(input)) - { - archive.AddAllFromDirectory(input, "*", SearchOption.AllDirectories); - } - - archive.SaveTo(archiveFileName + ".tmp", CompressionType.Deflate); - } - catch (Exception) - { - // Don't log archive write errors - } - } - - if (File.Exists(archiveFileName + ".tmp")) - { - File.Delete(archiveFileName); - File.Move(archiveFileName + ".tmp", archiveFileName); - } - } - - /// - /// Generate a list of HashData objects from the header values in an archive - /// - /// Input file to get data from - /// Logger object for file and console output - /// List of HashData objects representing the found data - /// This uses the new system that is not implemented anywhere yet - public static List GetArchiveFileHashes(string input, Logger logger) - { - List hashes = new List(); - string gamename = Path.GetFileNameWithoutExtension(input); - - // First get the archive type - ArchiveType? at = GetCurrentArchiveType(input, logger); - - // If we got back null, then it's not an archive, so we we return - if (at == null) - { - return hashes; - } - - // If we got back GZip, try to get TGZ info first - else if (at == ArchiveType.GZip) - { - HashData possibleTgz = GetTorrentGZFileHash(input, logger); - - // If it was, then add it to the outputs and continue - if (possibleTgz.Size != -1) - { - hashes.Add(possibleTgz); - return hashes; - } - } - - IReader reader = null; - try - { - logger.Log("Found archive of type: " + at); - long size = 0; - byte[] crc = null; - - // If we have a gzip file, get the crc directly - if (at == ArchiveType.GZip) - { - // Get the CRC and size from the file - using (BinaryReader br = new BinaryReader(File.OpenRead(input))) - { - br.BaseStream.Seek(-8, SeekOrigin.End); - crc = br.ReadBytes(4).Reverse().ToArray(); - byte[] headersize = br.ReadBytes(4); - size = BitConverter.ToInt32(headersize.Reverse().ToArray(), 0); - } - } - - reader = ReaderFactory.Open(File.OpenRead(input)); - - if (at != ArchiveType.Tar) - { - while (reader.MoveToNextEntry()) - { - if (reader.Entry != null && !reader.Entry.IsDirectory) - { - logger.Log("Entry found: '" + reader.Entry.Key + "': " - + (size == 0 ? reader.Entry.Size : size) + ", " - + (crc == null ? BitConverter.GetBytes(reader.Entry.Crc) : crc)); - - RomData temprom = new RomData - { - Type = ItemType.Rom, - Name = reader.Entry.Key, - Machine = new MachineData - { - Name = gamename, - Description = gamename, - }, - }; - HashData temphash = new HashData - { - Size = (size == 0 ? reader.Entry.Size : size), - CRC = (crc == null ? BitConverter.GetBytes(reader.Entry.Crc) : crc), - MD5 = null, - SHA1 = null, - Roms = new List(), - }; - temphash.Roms.Add(temprom); - hashes.Add(temphash); - } - } - } - } - catch (Exception ex) - { - logger.Error(ex.ToString()); - } - finally - { - reader?.Dispose(); - } - - return hashes; - } - - /// - /// Retrieve file information for a single torrent GZ file - /// - /// Filename to get information from - /// Logger object for file and console output - /// Populated HashData object if success, empty one on error - /// This uses the new system that is not implemented anywhere yet - public static HashData GetTorrentGZFileHash(string input, Logger logger) - { - string datum = Path.GetFileName(input).ToLowerInvariant(); - string sha1 = Path.GetFileNameWithoutExtension(input).ToLowerInvariant(); - long filesize = new FileInfo(input).Length; - - // Check if the name is the right length - if (!Regex.IsMatch(datum, @"^[0-9a-f]{40}\.gz")) - { - logger.Warning("Non SHA-1 filename found, skipping: '" + datum + "'"); - return new HashData(); - } - - // Check if the file is at least the minimum length - if (filesize < 40 /* bytes */) - { - logger.Warning("Possibly corrupt file '" + input + "' with size " + Style.GetBytesReadable(filesize)); - return new HashData(); - } - - // Get the Romba-specific header data - byte[] header; // Get preamble header for checking - byte[] headermd5; // MD5 - byte[] headercrc; // CRC - byte[] headersz; // Int64 size - using (BinaryReader br = new BinaryReader(File.OpenRead(input))) - { - header = br.ReadBytes(12); - headermd5 = br.ReadBytes(16); - headercrc = br.ReadBytes(4); - headersz = br.ReadBytes(8); - } - - // If the header is not correct, return a blank rom - bool correct = true; - for (int i = 0; i < header.Length; i++) - { - correct &= (header[i] == Constants.TorrentGZHeader[i]); - } - if (!correct) - { - return new HashData(); - } - - // Now convert the size and get the right position - long extractedsize = (long)BitConverter.ToUInt64(headersz.Reverse().ToArray(), 0); - - RomData temprom = new RomData - { - Type = ItemType.Rom, - Name = sha1, - Machine = new MachineData - { - Name = sha1, - Description = sha1, - }, - }; - HashData temphash = new HashData - { - Size = extractedsize, - CRC = headercrc, - MD5 = headermd5, - SHA1 = Style.StringToByteArray(sha1), - Roms = new List(), - }; - temphash.Roms.Add(temprom); - - return temphash; - } - - #endregion } } diff --git a/SabreTools.Helper/Tools/FileToolsHash.cs b/SabreTools.Helper/Tools/FileToolsHash.cs new file mode 100644 index 00000000..efe9de6b --- /dev/null +++ b/SabreTools.Helper/Tools/FileToolsHash.cs @@ -0,0 +1,305 @@ +using SharpCompress.Archive; +using SharpCompress.Common; +using SharpCompress.Reader; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.RegularExpressions; + +namespace SabreTools.Helper +{ + public class FileToolsHash + { + #region Archive Writing + + // All things in this region are direct ports and do not take advantage of the multiple rom per hash that comes with the new system + + /// + /// Copy a file to an output archive + /// + /// Input filename to be moved + /// Output directory to build to + /// RomData representing the new information + /// This uses the new system that is not implemented anywhere yet + public static void WriteToArchive(string input, string output, RomData rom) + { + string archiveFileName = Path.Combine(output, rom.Machine + ".zip"); + + ZipArchive outarchive = null; + try + { + if (!File.Exists(archiveFileName)) + { + outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Create); + } + else + { + outarchive = ZipFile.Open(archiveFileName, ZipArchiveMode.Update); + } + + if (File.Exists(input)) + { + if (outarchive.Mode == ZipArchiveMode.Create || outarchive.GetEntry(rom.Name) == null) + { + outarchive.CreateEntryFromFile(input, rom.Name, CompressionLevel.Optimal); + } + } + else if (Directory.Exists(input)) + { + foreach (string file in Directory.EnumerateFiles(input, "*", SearchOption.AllDirectories)) + { + if (outarchive.Mode == ZipArchiveMode.Create || outarchive.GetEntry(file) == null) + { + outarchive.CreateEntryFromFile(file, file, CompressionLevel.Optimal); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + finally + { + outarchive?.Dispose(); + } + } + + /// + /// Copy a file to an output archive using SharpCompress + /// + /// Input filename to be moved + /// Output directory to build to + /// RomData representing the new information + /// This uses the new system that is not implemented anywhere yet + public static void WriteToManagedArchive(string input, string output, RomData rom) + { + string archiveFileName = Path.Combine(output, rom.Machine + ".zip"); + + // Delete an empty file first + if (File.Exists(archiveFileName) && new FileInfo(archiveFileName).Length == 0) + { + File.Delete(archiveFileName); + } + + // Get if the file should be written out + bool newfile = File.Exists(archiveFileName) && new FileInfo(archiveFileName).Length != 0; + + using (SharpCompress.Archive.Zip.ZipArchive archive = (newfile + ? ArchiveFactory.Open(archiveFileName, Options.LookForHeader) as SharpCompress.Archive.Zip.ZipArchive + : ArchiveFactory.Create(ArchiveType.Zip) as SharpCompress.Archive.Zip.ZipArchive)) + { + try + { + if (File.Exists(input)) + { + archive.AddEntry(rom.Name, input); + } + else if (Directory.Exists(input)) + { + archive.AddAllFromDirectory(input, "*", SearchOption.AllDirectories); + } + + archive.SaveTo(archiveFileName + ".tmp", CompressionType.Deflate); + } + catch (Exception) + { + // Don't log archive write errors + } + } + + if (File.Exists(archiveFileName + ".tmp")) + { + File.Delete(archiveFileName); + File.Move(archiveFileName + ".tmp", archiveFileName); + } + } + + #endregion + + #region File Information + + /// + /// Generate a list of HashData objects from the header values in an archive + /// + /// Input file to get data from + /// Logger object for file and console output + /// List of HashData objects representing the found data + /// This uses the new system that is not implemented anywhere yet + public static List GetArchiveFileHashes(string input, Logger logger) + { + List hashes = new List(); + string gamename = Path.GetFileNameWithoutExtension(input); + + // First get the archive type + ArchiveType? at = FileTools.GetCurrentArchiveType(input, logger); + + // If we got back null, then it's not an archive, so we we return + if (at == null) + { + return hashes; + } + + // If we got back GZip, try to get TGZ info first + else if (at == ArchiveType.GZip) + { + HashData possibleTgz = GetTorrentGZFileHash(input, logger); + + // If it was, then add it to the outputs and continue + if (possibleTgz.Size != -1) + { + hashes.Add(possibleTgz); + return hashes; + } + } + + IReader reader = null; + try + { + logger.Log("Found archive of type: " + at); + long size = 0; + byte[] crc = null; + + // If we have a gzip file, get the crc directly + if (at == ArchiveType.GZip) + { + // Get the CRC and size from the file + using (BinaryReader br = new BinaryReader(File.OpenRead(input))) + { + br.BaseStream.Seek(-8, SeekOrigin.End); + crc = br.ReadBytes(4).Reverse().ToArray(); + byte[] headersize = br.ReadBytes(4); + size = BitConverter.ToInt32(headersize.Reverse().ToArray(), 0); + } + } + + reader = ReaderFactory.Open(File.OpenRead(input)); + + if (at != ArchiveType.Tar) + { + while (reader.MoveToNextEntry()) + { + if (reader.Entry != null && !reader.Entry.IsDirectory) + { + logger.Log("Entry found: '" + reader.Entry.Key + "': " + + (size == 0 ? reader.Entry.Size : size) + ", " + + (crc == null ? BitConverter.GetBytes(reader.Entry.Crc) : crc)); + + RomData temprom = new RomData + { + Type = ItemType.Rom, + Name = reader.Entry.Key, + Machine = new MachineData + { + Name = gamename, + Description = gamename, + }, + }; + HashData temphash = new HashData + { + Size = (size == 0 ? reader.Entry.Size : size), + CRC = (crc == null ? BitConverter.GetBytes(reader.Entry.Crc) : crc), + MD5 = null, + SHA1 = null, + Roms = new List(), + }; + temphash.Roms.Add(temprom); + hashes.Add(temphash); + } + } + } + } + catch (Exception ex) + { + logger.Error(ex.ToString()); + } + finally + { + reader?.Dispose(); + } + + return hashes; + } + + /// + /// Retrieve file information for a single torrent GZ file + /// + /// Filename to get information from + /// Logger object for file and console output + /// Populated HashData object if success, empty one on error + /// This uses the new system that is not implemented anywhere yet + public static HashData GetTorrentGZFileHash(string input, Logger logger) + { + string datum = Path.GetFileName(input).ToLowerInvariant(); + string sha1 = Path.GetFileNameWithoutExtension(input).ToLowerInvariant(); + long filesize = new FileInfo(input).Length; + + // Check if the name is the right length + if (!Regex.IsMatch(datum, @"^[0-9a-f]{40}\.gz")) + { + logger.Warning("Non SHA-1 filename found, skipping: '" + datum + "'"); + return new HashData(); + } + + // Check if the file is at least the minimum length + if (filesize < 40 /* bytes */) + { + logger.Warning("Possibly corrupt file '" + input + "' with size " + Style.GetBytesReadable(filesize)); + return new HashData(); + } + + // Get the Romba-specific header data + byte[] header; // Get preamble header for checking + byte[] headermd5; // MD5 + byte[] headercrc; // CRC + byte[] headersz; // Int64 size + using (BinaryReader br = new BinaryReader(File.OpenRead(input))) + { + header = br.ReadBytes(12); + headermd5 = br.ReadBytes(16); + headercrc = br.ReadBytes(4); + headersz = br.ReadBytes(8); + } + + // If the header is not correct, return a blank rom + bool correct = true; + for (int i = 0; i < header.Length; i++) + { + correct &= (header[i] == Constants.TorrentGZHeader[i]); + } + if (!correct) + { + return new HashData(); + } + + // Now convert the size and get the right position + long extractedsize = (long)BitConverter.ToUInt64(headersz.Reverse().ToArray(), 0); + + RomData temprom = new RomData + { + Type = ItemType.Rom, + Name = sha1, + Machine = new MachineData + { + Name = sha1, + Description = sha1, + }, + }; + HashData temphash = new HashData + { + Size = extractedsize, + CRC = headercrc, + MD5 = headermd5, + SHA1 = Style.StringToByteArray(sha1), + Roms = new List(), + }; + temphash.Roms.Add(temprom); + + return temphash; + } + + #endregion + } +} diff --git a/SabreTools.Helper/Tools/RomTools.cs b/SabreTools.Helper/Tools/RomTools.cs index f495a079..4f15ebca 100644 --- a/SabreTools.Helper/Tools/RomTools.cs +++ b/SabreTools.Helper/Tools/RomTools.cs @@ -245,223 +245,5 @@ namespace SabreTools.Helper } #endregion - - #region HashData-based sorting and merging - - /// - /// Merge an arbitrary set of ROMs based on the supplied information - /// - /// List of HashData objects representing the roms to be merged - /// Logger object for console and/or file output - /// A List of HashData objects representing the merged roms - public static List Merge(List inhashes, Logger logger) - { - // Create output list - List outroms = new List(); - - // Check for null or blank roms first - if (inhashes == null || inhashes.Count == 0) - { - return outroms; - } - - // Then deduplicate them by checking to see if data matches previous saved roms - foreach (HashData hash in inhashes) - { - // If it's a nodump, add and skip - if (hash.Roms[0].Nodump) - { - outroms.Add(hash); - continue; - } - - // If it's the first rom in the list, don't touch it - if (outroms.Count != 0) - { - // Check if the rom is a duplicate - DupeType dupetype = DupeType.None; - HashData savedHash = new HashData(); - int pos = -1; - for (int i = 0; i < outroms.Count; i++) - { - HashData lastrom = outroms[i]; - RomData savedRom = savedHash.Roms[0]; - MachineData savedMachine = savedRom.Machine; - - // Get the duplicate status - dupetype = GetDuplicateStatus(hash, lastrom, logger); - - // If it's a duplicate, skip adding it to the output but add any missing information - if (dupetype != DupeType.None) - { - savedHash = lastrom; - pos = i; - - savedHash.CRC = (savedHash.CRC == null && hash.CRC != null ? hash.CRC : savedHash.CRC); - savedHash.MD5 = (savedHash.MD5 == null && hash.MD5 != null ? hash.MD5 : savedHash.MD5); - savedHash.SHA1 = (savedHash.SHA1 == null && hash.SHA1 != null ? hash.SHA1 : savedHash.SHA1); - savedRom.DupeType = dupetype; - - // If the current system has a lower ID than the previous, set the system accordingly - if (hash.Roms[0].Machine.SystemID < savedMachine.SystemID) - { - savedMachine.SystemID = hash.Roms[0].Machine.SystemID; - savedMachine.System = hash.Roms[0].Machine.System; - savedMachine.Name = hash.Roms[0].Machine.Name; - savedRom.Name = hash.Roms[0].Name; - } - - // If the current source has a lower ID than the previous, set the source accordingly - if (hash.Roms[0].Machine.SourceID < savedMachine.SourceID) - { - savedMachine.SourceID = hash.Roms[0].Machine.SourceID; - savedMachine.Source = hash.Roms[0].Machine.Source; - savedMachine.Name = hash.Roms[0].Machine.Name; - savedRom.Name = hash.Roms[0].Name; - } - - break; - } - } - - // If no duplicate is found, add it to the list - if (dupetype == DupeType.None) - { - outroms.Add(hash); - } - // Otherwise, if a new rom information is found, add that - else - { - outroms.RemoveAt(pos); - outroms.Insert(pos, savedHash); - } - } - else - { - outroms.Add(hash); - } - } - - // Then return the result - return outroms; - } - - /* - /// - /// List all duplicates found in a DAT based on a rom - /// - /// Hash to use as a base - /// DAT to match against - /// Logger object for console and/or file output - /// True to remove matched roms from the input, false otherwise (default) - /// List of matched HashData objects - public static List GetDuplicates(HashData lastrom, DatData datdata, Logger logger, bool remove = false) - { - List output = new List(); - - // Check for an empty rom list first - if (datdata.Hashes == null || datdata.Hashes.Count == 0) - { - return output; - } - - // Try to find duplicates - for (int i = 0; i < datdata.Hashes.Count; i++) - { - - List roms = datdata.Files[key]; - List left = new List(); - foreach (Rom rom in roms) - { - if (IsDuplicate(rom, lastrom, logger)) - { - output.Add(rom); - } - else - { - left.Add(rom); - } - } - - // If we're in removal mode, replace the list with the new one - if (remove) - { - datdata.Files[key] = left; - } - } - - return output; - } - */ - - /// - /// Determine if a file is a duplicate using partial matching logic - /// - /// Hash to check for duplicate status - /// Hash to use as a baseline - /// Logger object for console and/or file output - /// True if the hashes are duplicates, false otherwise - public static bool IsDuplicate(HashData hash, int hashIndex, HashData lastHash, int lastHashIndex, Logger logger) - { - bool dupefound = hash.Equals(lastHash); - - // More wonderful SHA-1 logging that has to be done - if (hash.SHA1 == lastHash.SHA1 && hash.Size != lastHash.Size) - { - logger.User("SHA-1 mismatch - Hash: " + hash.SHA1); - } - - return dupefound; - } - - /// - /// Return the duplicate status of two hashes - /// - /// Current hash to check - /// Last hash to check against - /// Logger object for console and/or file output - /// The DupeType corresponding to the relationship between the two - public static DupeType GetDuplicateStatus(HashData hash, HashData lasthash, Logger logger) - { - DupeType output = DupeType.None; - - /* - // If we don't have a duplicate at all, return none - if (!IsDuplicate(hash, lasthash, logger)) - { - return output; - } - */ - - // If the duplicate is external already or should be, set it - if (lasthash.Roms[0].DupeType >= DupeType.ExternalHash || lasthash.Roms[0].Machine.SystemID != hash.Roms[0].Machine.SystemID || lasthash.Roms[0].Machine.SourceID != hash.Roms[0].Machine.SourceID) - { - if (lasthash.Roms[0].Machine.Name == hash.Roms[0].Machine.Name && lasthash.Roms[0].Name == hash.Roms[0].Name) - { - output = DupeType.ExternalAll; - } - else - { - output = DupeType.ExternalHash; - } - } - - // Otherwise, it's considered an internal dupe - else - { - if (lasthash.Roms[0].Machine.Name == hash.Roms[0].Machine.Name && lasthash.Roms[0].Name == hash.Roms[0].Name) - { - output = DupeType.InternalAll; - } - else - { - output = DupeType.InternalHash; - } - } - - return output; - } - - #endregion } } diff --git a/SabreTools.Helper/Tools/RomToolsHash.cs b/SabreTools.Helper/Tools/RomToolsHash.cs new file mode 100644 index 00000000..632295c2 --- /dev/null +++ b/SabreTools.Helper/Tools/RomToolsHash.cs @@ -0,0 +1,225 @@ +using System.Collections.Generic; + +namespace SabreTools.Helper +{ + public class RomToolsHash + { + #region HashData-based sorting and merging + + /// + /// Merge an arbitrary set of ROMs based on the supplied information + /// + /// List of HashData objects representing the roms to be merged + /// Logger object for console and/or file output + /// A List of HashData objects representing the merged roms + public static List Merge(List inhashes, Logger logger) + { + // Create output list + List outroms = new List(); + + // Check for null or blank roms first + if (inhashes == null || inhashes.Count == 0) + { + return outroms; + } + + // Then deduplicate them by checking to see if data matches previous saved roms + foreach (HashData hash in inhashes) + { + // If it's a nodump, add and skip + if (hash.Roms[0].Nodump) + { + outroms.Add(hash); + continue; + } + + // If it's the first rom in the list, don't touch it + if (outroms.Count != 0) + { + // Check if the rom is a duplicate + DupeType dupetype = DupeType.None; + HashData savedHash = new HashData(); + int pos = -1; + for (int i = 0; i < outroms.Count; i++) + { + HashData lastrom = outroms[i]; + RomData savedRom = savedHash.Roms[0]; + MachineData savedMachine = savedRom.Machine; + + // Get the duplicate status + dupetype = GetDuplicateStatus(hash, lastrom, logger); + + // If it's a duplicate, skip adding it to the output but add any missing information + if (dupetype != DupeType.None) + { + savedHash = lastrom; + pos = i; + + savedHash.CRC = (savedHash.CRC == null && hash.CRC != null ? hash.CRC : savedHash.CRC); + savedHash.MD5 = (savedHash.MD5 == null && hash.MD5 != null ? hash.MD5 : savedHash.MD5); + savedHash.SHA1 = (savedHash.SHA1 == null && hash.SHA1 != null ? hash.SHA1 : savedHash.SHA1); + savedRom.DupeType = dupetype; + + // If the current system has a lower ID than the previous, set the system accordingly + if (hash.Roms[0].Machine.SystemID < savedMachine.SystemID) + { + savedMachine.SystemID = hash.Roms[0].Machine.SystemID; + savedMachine.System = hash.Roms[0].Machine.System; + savedMachine.Name = hash.Roms[0].Machine.Name; + savedRom.Name = hash.Roms[0].Name; + } + + // If the current source has a lower ID than the previous, set the source accordingly + if (hash.Roms[0].Machine.SourceID < savedMachine.SourceID) + { + savedMachine.SourceID = hash.Roms[0].Machine.SourceID; + savedMachine.Source = hash.Roms[0].Machine.Source; + savedMachine.Name = hash.Roms[0].Machine.Name; + savedRom.Name = hash.Roms[0].Name; + } + + break; + } + } + + // If no duplicate is found, add it to the list + if (dupetype == DupeType.None) + { + outroms.Add(hash); + } + // Otherwise, if a new rom information is found, add that + else + { + outroms.RemoveAt(pos); + outroms.Insert(pos, savedHash); + } + } + else + { + outroms.Add(hash); + } + } + + // Then return the result + return outroms; + } + + /* + /// + /// List all duplicates found in a DAT based on a rom + /// + /// Hash to use as a base + /// DAT to match against + /// Logger object for console and/or file output + /// True to remove matched roms from the input, false otherwise (default) + /// List of matched HashData objects + public static List GetDuplicates(HashData lastrom, DatData datdata, Logger logger, bool remove = false) + { + List output = new List(); + + // Check for an empty rom list first + if (datdata.Hashes == null || datdata.Hashes.Count == 0) + { + return output; + } + + // Try to find duplicates + for (int i = 0; i < datdata.Hashes.Count; i++) + { + + List roms = datdata.Files[key]; + List left = new List(); + foreach (Rom rom in roms) + { + if (IsDuplicate(rom, lastrom, logger)) + { + output.Add(rom); + } + else + { + left.Add(rom); + } + } + + // If we're in removal mode, replace the list with the new one + if (remove) + { + datdata.Files[key] = left; + } + } + + return output; + } + */ + + /// + /// Determine if a file is a duplicate using partial matching logic + /// + /// Hash to check for duplicate status + /// Hash to use as a baseline + /// Logger object for console and/or file output + /// True if the hashes are duplicates, false otherwise + public static bool IsDuplicate(HashData hash, int hashIndex, HashData lastHash, int lastHashIndex, Logger logger) + { + bool dupefound = hash.Equals(lastHash); + + // More wonderful SHA-1 logging that has to be done + if (hash.SHA1 == lastHash.SHA1 && hash.Size != lastHash.Size) + { + logger.User("SHA-1 mismatch - Hash: " + hash.SHA1); + } + + return dupefound; + } + + /// + /// Return the duplicate status of two hashes + /// + /// Current hash to check + /// Last hash to check against + /// Logger object for console and/or file output + /// The DupeType corresponding to the relationship between the two + public static DupeType GetDuplicateStatus(HashData hash, HashData lasthash, Logger logger) + { + DupeType output = DupeType.None; + + /* + // If we don't have a duplicate at all, return none + if (!IsDuplicate(hash, lasthash, logger)) + { + return output; + } + */ + + // If the duplicate is external already or should be, set it + if (lasthash.Roms[0].DupeType >= DupeType.ExternalHash || lasthash.Roms[0].Machine.SystemID != hash.Roms[0].Machine.SystemID || lasthash.Roms[0].Machine.SourceID != hash.Roms[0].Machine.SourceID) + { + if (lasthash.Roms[0].Machine.Name == hash.Roms[0].Machine.Name && lasthash.Roms[0].Name == hash.Roms[0].Name) + { + output = DupeType.ExternalAll; + } + else + { + output = DupeType.ExternalHash; + } + } + + // Otherwise, it's considered an internal dupe + else + { + if (lasthash.Roms[0].Machine.Name == hash.Roms[0].Machine.Name && lasthash.Roms[0].Name == hash.Roms[0].Name) + { + output = DupeType.InternalAll; + } + else + { + output = DupeType.InternalHash; + } + } + + return output; + } + + #endregion + } +}