Files
SabreTools/SabreTools.DatFiles/Formats/Hashfile.cs

478 lines
18 KiB
C#
Raw Normal View History

using System;
2022-11-03 16:46:53 -07:00
using System.Collections.Generic;
using System.IO;
using System.Text;
2020-12-08 13:23:59 -08:00
using SabreTools.Core;
2020-12-08 15:15:41 -08:00
using SabreTools.DatItems;
2021-02-02 10:23:43 -08:00
using SabreTools.DatItems.Formats;
2020-12-07 15:08:57 -08:00
using SabreTools.IO;
2020-12-09 23:11:10 -08:00
using SabreTools.IO.Writers;
namespace SabreTools.DatFiles.Formats
{
2019-01-11 13:43:15 -08:00
/// <summary>
/// Represents parsing and writing of a hashfile such as an SFV, MD5, or SHA-1 file
/// </summary>
internal class Hashfile : DatFile
{
// Private instance variables specific to Hashfile DATs
private readonly Hash _hash;
2019-01-11 13:43:15 -08:00
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
/// <param name="hash">Type of hash that is associated with this DAT</param>
public Hashfile(DatFile datFile, Hash hash)
: base(datFile)
2019-01-11 13:43:15 -08:00
{
_hash = hash;
}
/// <inheritdoc/>
public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false)
2019-01-11 13:43:15 -08:00
{
// Open a file reader
2020-12-10 22:16:53 -08:00
Encoding enc = filename.GetEncoding();
2023-04-17 13:22:35 -04:00
StreamReader sr = new StreamReader(System.IO.File.OpenRead(filename), enc);
2019-01-11 13:43:15 -08:00
while (!sr.EndOfStream)
{
try
{
string line = sr.ReadLine();
2019-01-11 13:43:15 -08:00
// Split the line and get the name and hash
string[] split = line.Split(' ');
string name = string.Empty;
string hash = string.Empty;
2019-01-11 13:43:15 -08:00
2020-12-20 14:20:03 -08:00
// If we have CRC, then it's an SFV file and the name is first
if (_hash.HasFlag(Hash.CRC))
{
2023-03-30 19:35:41 -04:00
name = string.Join(" ", split[..^1]).Replace("*", String.Empty).Trim();
hash = split[^1];
}
// Otherwise, the name is second
else
{
2023-03-30 19:35:41 -04:00
name = string.Join(" ", split[1..]).Replace("*", String.Empty).Trim();
hash = split[0];
}
2019-01-11 13:43:15 -08:00
// If the name contains a path, use that path as the machine
string machine = Path.GetFileNameWithoutExtension(filename);
if (name.Contains('/'))
{
split = name.Split('/');
machine = split[0];
name = name.Substring(machine.Length + 1);
}
else if (name.Contains('\\'))
{
split = name.Split('\\');
machine = split[0];
name = name.Substring(machine.Length + 1);
}
Rom rom = new Rom
{
Name = name,
Size = null,
CRC = (_hash.HasFlag(Hash.CRC) ? hash : null),
MD5 = (_hash.HasFlag(Hash.MD5) ? hash : null),
SHA1 = (_hash.HasFlag(Hash.SHA1) ? hash : null),
SHA256 = (_hash.HasFlag(Hash.SHA256) ? hash : null),
SHA384 = (_hash.HasFlag(Hash.SHA384) ? hash : null),
SHA512 = (_hash.HasFlag(Hash.SHA512) ? hash : null),
SpamSum = (_hash.HasFlag(Hash.SpamSum) ? hash : null),
ItemStatus = ItemStatus.None,
2019-01-11 13:43:15 -08:00
Machine = new Machine
{
Name = machine,
},
2019-01-11 13:43:15 -08:00
Source = new Source
{
Index = indexId,
Name = filename,
},
};
2019-01-11 13:43:15 -08:00
// Now process and add the rom
ParseAddHelper(rom, statsOnly);
}
catch (Exception ex) when (!throwOnError)
{
string message = $"'{filename}' - There was an error parsing at position {sr.BaseStream.Position}";
logger.Error(ex, message);
}
2019-01-11 13:43:15 -08:00
}
sr.Dispose();
}
2020-09-18 17:12:31 -07:00
/// <inheritdoc/>
protected override ItemType[] GetSupportedTypes()
{
return new ItemType[] { ItemType.Disk, ItemType.Media, ItemType.Rom };
}
/// <inheritdoc/>
2022-11-03 16:46:53 -07:00
protected override List<DatItemField> GetMissingRequiredFields(DatItem datItem)
{
2022-11-03 16:46:53 -07:00
List<DatItemField> missingFields = new List<DatItemField>();
2022-11-03 16:46:53 -07:00
// Check item name
if (string.IsNullOrWhiteSpace(datItem.GetName()))
missingFields.Add(DatItemField.Name);
// Check hash linked to specific Hashfile type
switch (_hash)
{
2022-11-03 16:46:53 -07:00
case Hash.CRC:
switch (datItem.ItemType)
{
case ItemType.Rom:
if (!string.IsNullOrEmpty((datItem as Rom)?.CRC))
missingFields.Add(DatItemField.CRC);
break;
default:
missingFields.Add(DatItemField.CRC);
break;
}
break;
case Hash.MD5:
switch (datItem.ItemType)
{
case ItemType.Disk:
if (!string.IsNullOrEmpty((datItem as Disk)?.MD5))
missingFields.Add(DatItemField.MD5);
break;
case ItemType.Media:
if (!string.IsNullOrEmpty((datItem as Media)?.MD5))
missingFields.Add(DatItemField.MD5);
break;
case ItemType.Rom:
if (!string.IsNullOrEmpty((datItem as Rom)?.MD5))
missingFields.Add(DatItemField.MD5);
break;
default:
missingFields.Add(DatItemField.MD5);
break;
}
break;
case Hash.SHA1:
switch (datItem.ItemType)
{
case ItemType.Disk:
if (!string.IsNullOrEmpty((datItem as Disk)?.SHA1))
missingFields.Add(DatItemField.SHA1);
break;
case ItemType.Media:
if (!string.IsNullOrEmpty((datItem as Media)?.SHA1))
missingFields.Add(DatItemField.SHA1);
break;
case ItemType.Rom:
if (!string.IsNullOrEmpty((datItem as Rom)?.SHA1))
missingFields.Add(DatItemField.SHA1);
break;
default:
missingFields.Add(DatItemField.SHA1);
break;
}
break;
case Hash.SHA256:
switch (datItem.ItemType)
{
case ItemType.Media:
if (!string.IsNullOrEmpty((datItem as Media)?.SHA256))
missingFields.Add(DatItemField.SHA256);
break;
case ItemType.Rom:
if (!string.IsNullOrEmpty((datItem as Rom)?.SHA256))
missingFields.Add(DatItemField.SHA256);
break;
default:
missingFields.Add(DatItemField.SHA256);
break;
}
break;
case Hash.SHA384:
switch (datItem.ItemType)
{
case ItemType.Rom:
if (!string.IsNullOrEmpty((datItem as Rom)?.SHA384))
missingFields.Add(DatItemField.SHA384);
break;
default:
missingFields.Add(DatItemField.SHA384);
break;
}
break;
case Hash.SHA512:
switch (datItem.ItemType)
{
case ItemType.Rom:
if (!string.IsNullOrEmpty((datItem as Rom)?.SHA512))
missingFields.Add(DatItemField.SHA512);
break;
default:
missingFields.Add(DatItemField.SHA512);
break;
}
break;
case Hash.SpamSum:
switch (datItem.ItemType)
{
case ItemType.Media:
if (!string.IsNullOrEmpty((datItem as Media)?.SpamSum))
missingFields.Add(DatItemField.SpamSum);
break;
case ItemType.Rom:
if (!string.IsNullOrEmpty((datItem as Rom)?.SpamSum))
missingFields.Add(DatItemField.SpamSum);
break;
default:
missingFields.Add(DatItemField.SpamSum);
break;
}
break;
}
return missingFields;
}
/// <inheritdoc/>
public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false)
2019-01-11 13:43:15 -08:00
{
try
{
2021-02-03 11:22:09 -08:00
logger.User($"Writing to '{outfile}'...");
2023-04-17 13:22:35 -04:00
FileStream fs = System.IO.File.Create(outfile);
2019-01-11 13:43:15 -08:00
// If we get back null for some reason, just log and return
if (fs == null)
{
logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
2019-01-11 13:43:15 -08:00
return false;
}
SeparatedValueWriter svw = new SeparatedValueWriter(fs, new UTF8Encoding(false))
{
Quotes = false,
Separator = ' ',
VerifyFieldCount = true
};
2019-01-11 13:43:15 -08:00
2020-07-26 21:00:30 -07:00
// Use a sorted list of games to output
2020-07-26 22:34:45 -07:00
foreach (string key in Items.SortedKeys)
2019-01-11 13:43:15 -08:00
{
ConcurrentList<DatItem> datItems = Items[key];
2019-01-11 13:43:15 -08:00
2020-09-25 20:25:29 -07:00
// If this machine doesn't contain any writable items, skip
if (!ContainsWritable(datItems))
continue;
2019-01-11 13:43:15 -08:00
// Resolve the names in the block
2020-08-28 15:06:07 -07:00
datItems = DatItem.ResolveNames(datItems);
2019-01-11 13:43:15 -08:00
2020-08-28 15:06:07 -07:00
for (int index = 0; index < datItems.Count; index++)
2019-01-11 13:43:15 -08:00
{
2020-08-28 15:06:07 -07:00
DatItem datItem = datItems[index];
// Check for a "null" item
datItem = ProcessNullifiedItem(datItem);
// Write out the item if we're not ignoring
if (!ShouldIgnore(datItem, ignoreblanks))
WriteDatItem(svw, datItem);
2019-01-11 13:43:15 -08:00
}
}
2021-02-03 11:22:09 -08:00
logger.User($"'{outfile}' written!{Environment.NewLine}");
svw.Dispose();
2019-01-11 13:43:15 -08:00
fs.Dispose();
}
catch (Exception ex) when (!throwOnError)
2019-01-11 13:43:15 -08:00
{
logger.Error(ex);
2019-01-11 13:43:15 -08:00
return false;
}
return true;
}
/// <summary>
/// Write out DatItem using the supplied SeparatedValueWriter
2019-01-11 13:43:15 -08:00
/// </summary>
/// <param name="svw">SeparatedValueWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
private void WriteDatItem(SeparatedValueWriter svw, DatItem datItem)
2019-01-11 13:43:15 -08:00
{
// Build the state
string[] fields = new string[2];
// Get the name field
string name = string.Empty;
switch (datItem.ItemType)
2019-01-11 13:43:15 -08:00
{
case ItemType.Disk:
var disk = datItem as Disk;
if (Header.GameName)
name = $"{disk.Machine.Name}{Path.DirectorySeparatorChar}";
2020-08-23 22:23:55 -07:00
name += disk.Name;
break;
2020-08-23 22:23:55 -07:00
case ItemType.Media:
var media = datItem as Media;
if (Header.GameName)
name = $"{media.Machine.Name}{Path.DirectorySeparatorChar}";
2020-08-23 22:23:55 -07:00
name += media.Name;
break;
case ItemType.Rom:
var rom = datItem as Rom;
if (Header.GameName)
name = $"{rom.Machine.Name}{Path.DirectorySeparatorChar}";
name += rom.Name;
break;
}
2020-08-23 22:23:55 -07:00
// Get the hash field and set final fields
switch (_hash)
{
case Hash.CRC:
switch (datItem.ItemType)
{
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = name;
fields[1] = rom.CRC;
break;
}
2020-08-23 22:23:55 -07:00
break;
case Hash.MD5:
switch (datItem.ItemType)
{
case ItemType.Disk:
var disk = datItem as Disk;
fields[0] = disk.MD5;
fields[1] = name;
break;
case ItemType.Media:
var media = datItem as Media;
fields[0] = media.MD5;
fields[1] = name;
break;
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = rom.MD5;
fields[1] = name;
break;
}
break;
2020-08-23 22:23:55 -07:00
case Hash.SHA1:
switch (datItem.ItemType)
{
case ItemType.Disk:
var disk = datItem as Disk;
fields[0] = disk.SHA1;
fields[1] = name;
break;
case ItemType.Media:
var media = datItem as Media;
fields[0] = media.SHA1;
fields[1] = name;
break;
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = rom.SHA1;
fields[1] = name;
break;
}
2019-01-11 13:43:15 -08:00
break;
case Hash.SHA256:
switch (datItem.ItemType)
{
case ItemType.Media:
var media = datItem as Media;
fields[0] = media.SHA256;
fields[1] = name;
break;
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = rom.SHA256;
fields[1] = name;
break;
}
2020-09-15 12:12:13 -07:00
break;
case Hash.SHA384:
switch (datItem.ItemType)
{
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = rom.SHA384;
fields[1] = name;
break;
}
break;
case Hash.SHA512:
switch (datItem.ItemType)
{
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = rom.SHA512;
fields[1] = name;
break;
}
break;
case Hash.SpamSum:
switch (datItem.ItemType)
{
case ItemType.Media:
var media = datItem as Media;
fields[0] = media.SpamSum;
fields[1] = name;
break;
case ItemType.Rom:
var rom = datItem as Rom;
fields[0] = rom.SpamSum;
fields[1] = name;
break;
}
break;
2019-01-11 13:43:15 -08:00
}
// If we had at least one field filled in
if (!string.IsNullOrEmpty(fields[0]) || !string.IsNullOrEmpty(fields[1]))
svw.WriteValues(fields);
svw.Flush();
2019-01-11 13:43:15 -08:00
}
}
}