Files
MPF/MPF.Processors/ProcessingTool.cs
2024-05-28 00:13:17 -04:00

342 lines
12 KiB
C#

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using SabreTools.Models.Logiqx;
using SabreTools.Models.PIC;
using SabreTools.RedumpLib.Data;
namespace MPF.Processors
{
/// <summary>
/// Includes processing-specific utility functionality
/// </summary>
public static class ProcessingTool
{
#region Information Extraction
/// <summary>
/// Generate the proper datfile from the input Datafile, if possible
/// </summary>
/// <param name="datafile">.dat file location</param>
/// <returns>Relevant pieces of the datfile, null on error</returns>
public static string? GenerateDatfile(Datafile? datafile)
{
// If we don't have a valid datafile, we can't do anything
if (datafile?.Game == null || datafile.Game.Length == 0)
return null;
var roms = datafile.Game[0].Rom;
if (roms == null || roms.Length == 0)
return null;
// Otherwise, reconstruct the hash data with only the required info
try
{
string datString = string.Empty;
for (int i = 0; i < roms.Length; i++)
{
var rom = roms[i];
datString += $"<rom name=\"{rom.Name}\" size=\"{rom.Size}\" crc=\"{rom.CRC}\" md5=\"{rom.MD5}\" sha1=\"{rom.SHA1}\" />\n";
}
return datString.TrimEnd('\n');
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the Base64 representation of a string
/// </summary>
/// <param name="content">String content to encode</param>
/// <returns>Base64-encoded contents, if possible</returns>
public static string? GetBase64(string? content)
{
if (string.IsNullOrEmpty(content))
return null;
byte[] temp = Encoding.UTF8.GetBytes(content);
return Convert.ToBase64String(temp);
}
/// <summary>
/// Get Datafile from a standard DAT
/// </summary>
/// <param name="dat">Path to the DAT file to parse</param>
/// <returns>Filled Datafile on success, null on error</returns>
public static Datafile? GetDatafile(string? dat)
{
// If there's no path, we can't read the file
if (string.IsNullOrEmpty(dat))
return null;
// If the file doesn't exist, we can't read it
if (!File.Exists(dat))
return null;
try
{
// Open and read in the XML file
XmlReader xtr = XmlReader.Create(dat, new XmlReaderSettings
{
CheckCharacters = false,
#if NET40_OR_GREATER || NETCOREAPP
DtdProcessing = DtdProcessing.Ignore,
#endif
IgnoreComments = true,
IgnoreWhitespace = true,
ValidationFlags = XmlSchemaValidationFlags.None,
ValidationType = ValidationType.None,
});
// If the reader is null for some reason, we can't do anything
if (xtr == null)
return null;
var serializer = new XmlSerializer(typeof(Datafile));
return serializer.Deserialize(xtr) as Datafile;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Gets disc information from a PIC file
/// </summary>
/// <param name="pic">Path to a PIC.bin file</param>
/// <returns>Filled DiscInformation on success, null on error</returns>
/// <remarks>This omits the emergency brake information, if it exists</remarks>
public static DiscInformation? GetDiscInformation(string pic)
{
try
{
return SabreTools.Serialization.Deserializers.PIC.DeserializeFile(pic);
}
catch
{
// We don't care what the error was
return null;
}
}
/// <summary>
/// Get the last modified date from a file path, if possible
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <returns>Filled DateTime on success, null on failure</returns>
public static DateTime? GetFileModifiedDate(string? filename, bool fallback = false)
{
if (string.IsNullOrEmpty(filename))
return fallback ? (DateTime?)DateTime.UtcNow : null;
else if (!File.Exists(filename))
return fallback ? (DateTime?)DateTime.UtcNow : null;
var fi = new FileInfo(filename);
return fi.LastWriteTimeUtc;
}
/// <summary>
/// Get the full lines from the input file, if possible
/// </summary>
/// <param name="filename">file location</param>
/// <param name="binary">True if should read as binary, false otherwise (default)</param>
/// <returns>Full text of the file, null on error</returns>
public static string? GetFullFile(string filename, bool binary = false)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(filename))
return null;
// If we're reading as binary
if (binary)
{
byte[] bytes = File.ReadAllBytes(filename);
return BitConverter.ToString(bytes).Replace("-", string.Empty);
}
return File.ReadAllText(filename);
}
/// <summary>
/// Get the split values for ISO-based media
/// </summary>
/// <param name="datafile">Datafile represenging the hash data</param>
/// <returns>True if extraction was successful, false otherwise</returns>
public static bool GetISOHashValues(Datafile? datafile, out long size, out string? crc32, out string? md5, out string? sha1)
{
size = -1; crc32 = null; md5 = null; sha1 = null;
if (datafile?.Game == null || datafile.Game.Length == 0)
return false;
var roms = datafile.Game[0].Rom;
if (roms == null || roms.Length == 0)
return false;
var rom = roms[0];
_ = Int64.TryParse(rom.Size, out size);
crc32 = rom.CRC;
md5 = rom.MD5;
sha1 = rom.SHA1;
return true;
}
/// <summary>
/// Get the split values for ISO-based media
/// </summary>
/// <param name="hashData">String representing the combined hash data</param>
/// <returns>True if extraction was successful, false otherwise</returns>
public static bool GetISOHashValues(string? hashData, out long size, out string? crc32, out string? md5, out string? sha1)
{
size = -1; crc32 = null; md5 = null; sha1 = null;
if (string.IsNullOrEmpty(hashData))
return false;
var hashreg = new Regex(@"<rom name="".*?"" size=""(.*?)"" crc=""(.*?)"" md5=""(.*?)"" sha1=""(.*?)""", RegexOptions.Compiled);
Match m = hashreg.Match(hashData);
if (m.Success)
{
_ = Int64.TryParse(m.Groups[1].Value, out size);
crc32 = m.Groups[2].Value;
md5 = m.Groups[3].Value;
sha1 = m.Groups[4].Value;
return true;
}
else
{
return false;
}
}
/// <summary>
/// Get the layerbreak info associated from the disc information
/// </summary>
/// <param name="di">Disc information containing unformatted data</param>
/// <returns>True if layerbreak info was set, false otherwise</returns>
public static bool GetLayerbreaks(DiscInformation? di, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3)
{
// Set the default values
layerbreak1 = null; layerbreak2 = null; layerbreak3 = null;
// If we don't have valid disc information, we can't do anything
if (di?.Units == null || di.Units.Length <= 1)
return false;
static int ReadFromArrayBigEndian(byte[]? bytes, int offset)
{
if (bytes == null)
return default;
#if NET20 || NET35 || NET40
byte[] rev = new byte[0x04];
Array.Copy(bytes, offset, rev, 0, 0x04);
#else
var span = new ReadOnlySpan<byte>(bytes, offset, 0x04);
byte[] rev = span.ToArray();
#endif
Array.Reverse(rev);
return BitConverter.ToInt32(rev, 0);
}
// Layerbreak 1 (2+ layers)
if (di.Units.Length >= 2)
{
long offset = ReadFromArrayBigEndian(di.Units[0]?.Body?.FormatDependentContents, 0x0C);
long value = ReadFromArrayBigEndian(di.Units[0]?.Body?.FormatDependentContents, 0x10);
layerbreak1 = value - offset + 2;
}
// Layerbreak 2 (3+ layers)
if (di.Units.Length >= 3)
{
long offset = ReadFromArrayBigEndian(di.Units[1]?.Body?.FormatDependentContents, 0x0C);
long value = ReadFromArrayBigEndian(di.Units[1]?.Body?.FormatDependentContents, 0x10);
layerbreak2 = layerbreak1 + value - offset + 2;
}
// Layerbreak 3 (4 layers)
if (di.Units.Length >= 4)
{
long offset = ReadFromArrayBigEndian(di.Units[2]?.Body?.FormatDependentContents, 0x0C);
long value = ReadFromArrayBigEndian(di.Units[2]?.Body?.FormatDependentContents, 0x10);
layerbreak3 = layerbreak2 + value - offset + 2;
}
return true;
}
/// <summary>
/// Get the PIC identifier from the first disc information unit, if possible
/// </summary>
/// <param name="di">Disc information containing the data</param>
/// <returns>String representing the PIC identifier, null on error</returns>
public static string? GetPICIdentifier(DiscInformation? di)
{
// If we don't have valid disc information, we can't do anything
if (di?.Units == null || di.Units.Length <= 1)
return null;
// We assume the identifier is consistent across all units
return di.Units[0]?.Body?.DiscTypeIdentifier;
}
#endregion
#region Category Extraction
/// <summary>
/// Determine the category based on the UMDImageCreator string
/// </summary>
/// <param name="region">String representing the category</param>
/// <returns>Category, if possible</returns>
public static DiscCategory? GetUMDCategory(string? category)
{
return category switch
{
"GAME" => DiscCategory.Games,
"VIDEO" => DiscCategory.Video,
"AUDIO" => DiscCategory.Audio,
_ => null,
};
}
#endregion
#region Region Extraction
/// <summary>
/// Determine the region based on the XGD serial character
/// </summary>
/// <param name="region">Character denoting the region</param>
/// <returns>Region, if possible</returns>
public static Region? GetXGDRegion(char? region)
{
return region switch
{
'W' => (Region?)Region.World,
'A' => (Region?)Region.UnitedStatesOfAmerica,
'J' => (Region?)Region.JapanAsia,
'E' => (Region?)Region.Europe,
'K' => (Region?)Region.USAJapan,
'L' => (Region?)Region.USAEurope,
'H' => (Region?)Region.JapanEurope,
_ => null,
};
}
#endregion
}
}