mirror of
https://github.com/SabreTools/MPF.git
synced 2026-07-02 17:24:48 +00:00
342 lines
12 KiB
C#
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
|
|
}
|
|
} |