using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.Hashing;
using SabreTools.Models.Logiqx;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Processors
{
///
/// Represents processing UmdImageCreator outputs
///
public sealed class UmdImageCreator : BaseProcessor
{
///
public UmdImageCreator(RedumpSystem? system, MediaType? type) : base(system, type) { }
#region BaseProcessor Implementations
///
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
{
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
// TODO: Determine if there's a UMDImageCreator version anywhere
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + "_disc.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
// Fill in the volume labels
if (GetVolumeLabels($"{basePath}_volDesc.txt", out var volLabels))
VolumeLabels = volLabels;
// Extract info based generically on MediaType
switch (Type)
{
case MediaType.UMD:
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
if (HashTool.GetStandardHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
{
// Get the Datafile information
var datafile = new Datafile
{
Game = [new Game { Rom = [new Rom { Name = string.Empty, Size = filesize.ToString(), CRC = crc32, MD5 = md5, SHA1 = sha1 }] }]
};
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
info.SizeAndChecksums!.Size = filesize;
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
}
if (GetUMDAuxInfo(basePath + "_disc.txt",
out var title,
out DiscCategory? category,
out string? serial,
out var version,
out var layer,
out long size))
{
info.CommonDiscInfo!.Title = title ?? string.Empty;
info.CommonDiscInfo.Category = category ?? DiscCategory.Games;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = serial ?? string.Empty;
info.VersionAndEditions!.Version = version ?? string.Empty;
info.SizeAndChecksums!.Size = size;
if (!string.IsNullOrEmpty(layer))
info.SizeAndChecksums.Layerbreak = long.Parse(layer ?? "-1");
}
break;
}
}
///
internal override List GetOutputFiles(string? baseDirectory, string baseFilename)
{
switch (Type)
{
case MediaType.UMD:
return [
new($"{baseFilename}.iso", OutputFileFlags.Required),
new($"{baseFilename}_disc.txt", OutputFileFlags.Required
| OutputFileFlags.Artifact
| OutputFileFlags.Zippable,
"disc"),
new($"{baseFilename}_drive.txt", OutputFileFlags.Artifact
| OutputFileFlags.Zippable,
"drive"),
new($"{baseFilename}_mainError.txt", OutputFileFlags.Required
| OutputFileFlags.Artifact
| OutputFileFlags.Zippable,
"main_error"),
new($"{baseFilename}_mainInfo.txt", OutputFileFlags.Required
| OutputFileFlags.Artifact
| OutputFileFlags.Zippable,
"main_info"),
new($"{baseFilename}_PFI.bin", OutputFileFlags.Binary
| OutputFileFlags.Zippable,
"pfi"),
new($"{baseFilename}_volDesc.txt", OutputFileFlags.Required
| OutputFileFlags.Artifact
| OutputFileFlags.Zippable,
"vol_desc"),
];
}
return [];
}
#endregion
#region Information Extraction Methods
///
/// Get the PVD from the input file, if possible
///
/// _mainInfo.txt file location
/// Newline-deliminated PVD if possible, null on error
internal static string? GetPVD(string mainInfo)
{
// If the file doesn't exist, we can't get info from it
if (string.IsNullOrEmpty(mainInfo))
return null;
if (!File.Exists(mainInfo))
return null;
try
{
// Make sure we're in the right sector
using var sr = File.OpenText(mainInfo);
while (sr.ReadLine()?.StartsWith("========== LBA[000016, 0x0000010]: Main Channel ==========") == false) ;
// Fast forward to the PVD
while (sr.ReadLine()?.StartsWith("0310") == false) ;
// Now that we're at the PVD, read each line in and concatenate
string pvd = string.Empty;
for (int i = 0; i < 6; i++)
pvd += sr.ReadLine() + "\n"; // 320-370
return pvd;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
///
/// Get the UMD auxiliary info from the outputted files, if possible
///
/// _disc.txt file location
/// True on successful extraction of info, false otherwise
internal static bool GetUMDAuxInfo(string disc,
out string? title,
out DiscCategory? category,
out string? serial,
out string? version,
out string? layer,
out long size)
{
title = null; serial = null; version = null; layer = null;
category = null;
size = -1;
// If the file doesn't exist, we can't get info from it
if (string.IsNullOrEmpty(disc))
return false;
if (!File.Exists(disc))
return false;
try
{
// Loop through everything to get the first instance of each required field
using var sr = File.OpenText(disc);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.Trim();
if (line == null)
break;
if (line.StartsWith("TITLE") && title == null)
title = line.Split(' ')[1];
else if (line.StartsWith("DISC_ID") && version == null)
serial = line.Split(' ')[1];
else if (line.StartsWith("DISC_VERSION") && version == null)
version = line.Split(' ')[1];
else if (line.StartsWith("pspUmdTypes"))
category = ProcessingTool.GetUMDCategory(line.Split(' ')[1]);
else if (line.StartsWith("L0 length"))
layer = line.Split(' ')[2];
else if (line.StartsWith("FileSize:"))
size = long.Parse(line.Split(' ')[1]);
}
// If we have a serial, format it
if (!string.IsNullOrEmpty(serial) && serial!.Length > 4)
serial = serial.Substring(0, 4) + "-" + serial.Substring(4);
// If the L0 length is the size of the full disc, there's no layerbreak
if (long.TryParse(layer, out long umdlayerValue) && umdlayerValue * 2048 == size)
layer = null;
return true;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
///
/// Get all Volume Identifiers
///
/// _volDesc.txt file location
/// Volume labels (by type), or null if none present
/// This is a copy of the code from DiscImageCreator and has extraneous checks
internal static bool GetVolumeLabels(string volDesc, out Dictionary> volLabels)
{
// If the file doesn't exist, can't get the volume labels
volLabels = [];
if (!File.Exists(volDesc))
return false;
try
{
using var sr = File.OpenText(volDesc);
var line = sr.ReadLine();
string volType = "UNKNOWN";
string label;
while (line != null)
{
// Trim the line for later use
line = line.Trim();
// ISO9660 and extensions section
if (line.StartsWith("Volume Descriptor Type: "))
{
int.TryParse(line.Substring("Volume Descriptor Type: ".Length), out int volTypeInt);
volType = volTypeInt switch
{
// 0 => "Boot Record" // Should not not contain a Volume Identifier
1 => "ISO", // ISO9660
2 => "Joliet",
// 3 => "Volume Partition Descriptor" // Should not not contain a Volume Identifier
// 255 => "???" // Should not not contain a Volume Identifier
_ => "UNKNOWN" // Should not contain a Volume Identifier
};
}
// UDF section
else if (line.StartsWith("Primary Volume Descriptor Number:"))
{
volType = "UDF";
}
// Identifier
else if (line.StartsWith("Volume Identifier: "))
{
label = line.Substring("Volume Identifier: ".Length);
// Remove leading non-printable character (unsure why DIC outputs this)
if (Convert.ToUInt32(label[0]) == 0x7F || Convert.ToUInt32(label[0]) < 0x20)
label = label.Substring(1);
// Skip if label is blank
if (label == null || label.Length <= 0)
{
volType = "UNKNOWN";
line = sr.ReadLine();
continue;
}
if (volLabels.ContainsKey(label))
volLabels[label].Add(volType);
else
volLabels.Add(label, [volType]);
// Reset volume type
volType = "UNKNOWN";
}
line = sr.ReadLine();
}
// Return true if a volume label was found
return volLabels.Count > 0;
}
catch
{
// We don't care what the exception is right now
volLabels = [];
return false;
}
}
#endregion
}
}