mirror of
https://github.com/SabreTools/MPF.git
synced 2026-07-02 17:24:48 +00:00
* Abstract out non-UI code to separate DLL This change seems rather large, but it's mostly just moving anything that is not directly driving the UI to its own, separate library. This will make it easier at a later date for any UI improvements or changes as well as making the code much more discrete. One TODO has been added as a result of this change. * Remove MessageBox from library This change removes the last UI-driven elements from the library. This now makes the library (in theory) UI-agnostic and removes the TODO. * Options is more UI-related * Fix Nuget references in csproj * Add Winforms UI This is a clone of the current WPF UI but implemented in Windows Forms. WPF is not currently supported by Mono for Linux and macOS, so this can provide an alternative to those users who want to run on those systems instead. This also adds a second artifact for the Winforms build.
1653 lines
70 KiB
C#
1653 lines
70 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using DICUI.Data;
|
|
|
|
namespace DICUI.Utilities
|
|
{
|
|
/// <summary>
|
|
/// Represents information for a single drive
|
|
/// </summary>
|
|
public class Drive
|
|
{
|
|
public char Letter { get; private set; }
|
|
public bool IsFloppy { get; private set; }
|
|
public string VolumeLabel { get; private set; }
|
|
public bool MarkedActive { get; private set; }
|
|
|
|
private Drive(char letter, string volumeLabel, bool isFloppy, bool markedActive)
|
|
{
|
|
this.Letter = letter;
|
|
this.IsFloppy = isFloppy;
|
|
this.VolumeLabel = volumeLabel;
|
|
this.MarkedActive = markedActive;
|
|
}
|
|
|
|
public static Drive Floppy(char letter) => new Drive(letter, null, true, true);
|
|
public static Drive Optical(char letter, string volumeLabel, bool active) => new Drive(letter, volumeLabel, false, active);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents the state of all settings to be used during dumping
|
|
/// </summary>
|
|
public class DumpEnvironment
|
|
{
|
|
// Tool paths
|
|
public string DICPath;
|
|
public string SubdumpPath;
|
|
|
|
// Output paths
|
|
public string OutputDirectory;
|
|
public string OutputFilename;
|
|
|
|
// UI information
|
|
public Drive Drive;
|
|
public KnownSystem? System;
|
|
public MediaType? Type;
|
|
public bool IsFloppy { get => Drive.IsFloppy; }
|
|
public Parameters DICParameters;
|
|
|
|
// extra DIC arguments
|
|
public bool QuietMode;
|
|
public bool ParanoidMode;
|
|
public bool ScanForProtection;
|
|
public int RereadAmountC2;
|
|
|
|
// External process information
|
|
private Process dicProcess;
|
|
|
|
#region Public Functionality
|
|
|
|
/// <summary>
|
|
/// Cancel an in-progress dumping process
|
|
/// </summary>
|
|
public void CancelDumping()
|
|
{
|
|
try
|
|
{
|
|
if (dicProcess != null && !dicProcess.HasExited)
|
|
dicProcess.Kill();
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Eject the disc using DIC
|
|
/// </summary>
|
|
public async void EjectDisc()
|
|
{
|
|
// Validate that the required program exists
|
|
if (!File.Exists(DICPath))
|
|
return;
|
|
|
|
CancelDumping();
|
|
|
|
// Validate we're not trying to eject a floppy disk
|
|
if (IsFloppy)
|
|
return;
|
|
|
|
Process childProcess;
|
|
await Task.Run(() =>
|
|
{
|
|
childProcess = new Process()
|
|
{
|
|
StartInfo = new ProcessStartInfo()
|
|
{
|
|
FileName = DICPath,
|
|
Arguments = DICCommandStrings.Eject + " " + Drive.Letter,
|
|
CreateNoWindow = true,
|
|
UseShellExecute = false,
|
|
RedirectStandardInput = true,
|
|
RedirectStandardOutput = true,
|
|
},
|
|
};
|
|
childProcess.Start();
|
|
childProcess.WaitForExit(1000);
|
|
|
|
// Just in case, we want to push a button 5 times to clear any errors
|
|
for (int i = 0; i < 5; i++)
|
|
childProcess.StandardInput.WriteLine("Y");
|
|
|
|
childProcess.Dispose();
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets if the current drive has the latest firmware
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<bool> DriveHasLatestFimrware()
|
|
{
|
|
// Validate that the required program exists
|
|
if (!File.Exists(DICPath))
|
|
return false;
|
|
|
|
// Use the drive speed command as a quick test
|
|
Process childProcess;
|
|
string output = await Task.Run(() =>
|
|
{
|
|
childProcess = new Process()
|
|
{
|
|
StartInfo = new ProcessStartInfo()
|
|
{
|
|
FileName = DICPath,
|
|
Arguments = DICCommandStrings.DriveSpeed + " " + Drive.Letter,
|
|
CreateNoWindow = true,
|
|
UseShellExecute = false,
|
|
RedirectStandardInput = true,
|
|
RedirectStandardOutput = true,
|
|
},
|
|
};
|
|
childProcess.Start();
|
|
childProcess.WaitForExit(1000);
|
|
|
|
// Just in case, we want to push a button 5 times to clear any errors
|
|
for (int i = 0; i < 5; i++)
|
|
childProcess.StandardInput.WriteLine("Y");
|
|
|
|
string stdout = childProcess.StandardOutput.ReadToEnd();
|
|
childProcess.Dispose();
|
|
return stdout;
|
|
});
|
|
|
|
// If we get the firmware message
|
|
if (output.Contains("[ERROR] This drive isn't latest firmware. Please update."))
|
|
return false;
|
|
|
|
// Otherwise, we know the firmware's good
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the full parameter string for DIC
|
|
/// </summary>
|
|
/// <param name="driveSpeed">Nullable int representing the drive speed</param>
|
|
/// <returns>String representing the params, null on error</returns>
|
|
public string GetFullParameters(int? driveSpeed)
|
|
{
|
|
// Populate with the correct params for inputs (if we're not on the default option)
|
|
if (System != KnownSystem.NONE && Type != MediaType.NONE)
|
|
{
|
|
// If drive letter is invalid, skip this
|
|
if (Drive == null)
|
|
return null;
|
|
|
|
FixOutputPaths();
|
|
|
|
// Set the proper parameters
|
|
DICParameters = new Parameters(System, Type, Drive.Letter, Path.Combine(OutputDirectory, OutputFilename), driveSpeed, ParanoidMode, RereadAmountC2);
|
|
if (QuietMode)
|
|
DICParameters[DICFlag.DisableBeep] = true;
|
|
|
|
// Generate and return the param string
|
|
return DICParameters.GenerateParameters();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute a complete dump workflow
|
|
/// </summary>
|
|
public async Task<Result> StartDumping(IProgress<Result> progress)
|
|
{
|
|
Result result = IsValidForDump();
|
|
|
|
// Execute DIC and external tools, if needed
|
|
if (Validators.GetSupportStatus(System, Type)
|
|
&& !result.Message.Contains("not supported") // Completely unsupported media
|
|
&& !result.Message.Contains("submission info")) // Submission info-only media
|
|
{
|
|
// If the environment is invalid, return
|
|
if (!result)
|
|
return result;
|
|
|
|
progress?.Report(Result.Success("Executing DiscImageCreator... please wait!"));
|
|
await Task.Run(() => ExecuteDiskImageCreator());
|
|
progress?.Report(Result.Success("DiscImageCreator has finished!"));
|
|
|
|
// Execute additional tools
|
|
progress?.Report(Result.Success("Running any additional tools... please wait!"));
|
|
result = await Task.Run(() => ExecuteAdditionalToolsAfterDIC());
|
|
progress?.Report(result);
|
|
}
|
|
|
|
// Verify dump output and save it
|
|
progress?.Report(Result.Success("Gathering submission information... please wait!"));
|
|
result = await Task.Run(() => VerifyAndSaveDumpOutput(progress));
|
|
progress?.Report(Result.Success("All submission information gathered!"));
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public for Testing Purposes
|
|
|
|
/// <summary>
|
|
/// Fix output paths to strip out any invalid characters
|
|
/// </summary>
|
|
public void FixOutputPaths()
|
|
{
|
|
try
|
|
{
|
|
// Cache if we had a directory separator or not
|
|
bool endedWithDirectorySeparator = OutputDirectory.EndsWith(Path.DirectorySeparatorChar.ToString());
|
|
|
|
// Normalize the path so the below filters work better
|
|
string combinedPath = Path.Combine(OutputDirectory, OutputFilename);
|
|
OutputDirectory = Path.GetDirectoryName(combinedPath);
|
|
OutputFilename = Path.GetFileName(combinedPath);
|
|
|
|
// If we have either a blank filename or path, just exit
|
|
if (String.IsNullOrWhiteSpace(OutputFilename) || String.IsNullOrWhiteSpace(OutputDirectory))
|
|
return;
|
|
|
|
// Take care of extra path characters
|
|
OutputDirectory = new StringBuilder(OutputDirectory.Replace('.', '_').Replace('&', '_'))
|
|
.Replace(':', '_', 0, OutputDirectory.LastIndexOf(':') == -1 ? 0 : OutputDirectory.LastIndexOf(':')).ToString();
|
|
OutputFilename = new StringBuilder(OutputFilename.Replace('&', '_'))
|
|
.Replace('.', '_', 0, OutputFilename.LastIndexOf('.') == -1 ? 0 : OutputFilename.LastIndexOf('.')).ToString();
|
|
|
|
// Sanitize everything else
|
|
foreach (char c in Path.GetInvalidPathChars())
|
|
OutputDirectory = OutputDirectory.Replace(c, '_');
|
|
foreach (char c in Path.GetInvalidFileNameChars())
|
|
OutputFilename = OutputFilename.Replace(c, '_');
|
|
|
|
// If we had a directory separator at the end before, add it again
|
|
if (endedWithDirectorySeparator)
|
|
OutputDirectory += Path.DirectorySeparatorChar;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error was
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the parameters are valid
|
|
/// </summary>
|
|
/// <returns>True if the configuration is valid, false otherwise</returns>
|
|
public bool ParametersValid()
|
|
{
|
|
return DICParameters.IsValid() && !(IsFloppy ^ Type == MediaType.FloppyDisk);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that all required output files have been created
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool FoundAllFiles()
|
|
{
|
|
// First, sanitized the output filename to strip off any potential extension
|
|
string outputFilename = Path.GetFileNameWithoutExtension(OutputFilename);
|
|
|
|
// Now ensure that all required files exist
|
|
string combinedBase = Path.Combine(OutputDirectory, outputFilename);
|
|
switch (Type)
|
|
{
|
|
case MediaType.CDROM:
|
|
case MediaType.GDROM: // TODO: Verify GD-ROM outputs this
|
|
return File.Exists(combinedBase + ".c2")
|
|
&& File.Exists(combinedBase + ".ccd")
|
|
&& File.Exists(combinedBase + ".cue")
|
|
&& File.Exists(combinedBase + ".dat")
|
|
&& File.Exists(combinedBase + ".img")
|
|
&& File.Exists(combinedBase + ".img_EdcEcc.txt")
|
|
&& File.Exists(combinedBase + ".scm")
|
|
&& File.Exists(combinedBase + ".sub")
|
|
&& File.Exists(combinedBase + "_c2Error.txt")
|
|
&& File.Exists(combinedBase + "_cmd.txt")
|
|
&& File.Exists(combinedBase + "_disc.txt")
|
|
&& File.Exists(combinedBase + "_drive.txt")
|
|
&& File.Exists(combinedBase + "_img.cue")
|
|
&& File.Exists(combinedBase + "_mainError.txt")
|
|
&& File.Exists(combinedBase + "_mainInfo.txt")
|
|
&& File.Exists(combinedBase + "_subError.txt")
|
|
&& File.Exists(combinedBase + "_subInfo.txt")
|
|
// && File.Exists(combinedBase + "_subIntention.txt")
|
|
&& (File.Exists(combinedBase + "_subReadable.txt") || File.Exists(combinedBase + "_sub.txt"))
|
|
&& File.Exists(combinedBase + "_volDesc.txt");
|
|
case MediaType.DVD:
|
|
case MediaType.HDDVD:
|
|
case MediaType.BluRay:
|
|
case MediaType.NintendoGameCube:
|
|
case MediaType.NintendoWiiOpticalDisc:
|
|
return File.Exists(combinedBase + ".dat")
|
|
&& File.Exists(combinedBase + "_cmd.txt")
|
|
&& File.Exists(combinedBase + "_disc.txt")
|
|
&& File.Exists(combinedBase + "_drive.txt")
|
|
&& File.Exists(combinedBase + "_mainError.txt")
|
|
&& File.Exists(combinedBase + "_mainInfo.txt")
|
|
&& File.Exists(combinedBase + "_volDesc.txt");
|
|
case MediaType.FloppyDisk:
|
|
return File.Exists(combinedBase + ".dat")
|
|
&& File.Exists(combinedBase + "_cmd.txt")
|
|
&& File.Exists(combinedBase + "_disc.txt");
|
|
case MediaType.UMD:
|
|
return File.Exists(combinedBase + "_disc.txt")
|
|
|| File.Exists(combinedBase + "_mainError.txt")
|
|
|| File.Exists(combinedBase + "_mainInfo.txt")
|
|
|| File.Exists(combinedBase + "_volDesc.txt");
|
|
default:
|
|
// Non-dumping commands will usually produce no output, so this is irrelevant
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Helpers
|
|
|
|
/// <summary>
|
|
/// Run any additional tools given a DumpEnvironment
|
|
/// </summary>
|
|
/// <returns>Result instance with the outcome</returns>
|
|
private Result ExecuteAdditionalToolsAfterDIC()
|
|
{
|
|
// Special cases
|
|
switch (System)
|
|
{
|
|
case KnownSystem.SegaSaturn:
|
|
if (!File.Exists(SubdumpPath))
|
|
return Result.Failure("Error! Could not find subdump!");
|
|
|
|
ExecuteSubdump();
|
|
return Result.Success("subdump has finished!");
|
|
}
|
|
|
|
return Result.Success("No external tools needed!");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run DiscImageCreator
|
|
/// </summary>
|
|
private void ExecuteDiskImageCreator()
|
|
{
|
|
dicProcess = new Process()
|
|
{
|
|
StartInfo = new ProcessStartInfo()
|
|
{
|
|
FileName = DICPath,
|
|
Arguments = DICParameters.GenerateParameters() ?? "",
|
|
},
|
|
};
|
|
dicProcess.Start();
|
|
dicProcess.WaitForExit();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute subdump for a (potential) Sega Saturn dump
|
|
/// </summary>
|
|
private async void ExecuteSubdump()
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
Process childProcess = new Process()
|
|
{
|
|
StartInfo = new ProcessStartInfo()
|
|
{
|
|
FileName = SubdumpPath,
|
|
Arguments = "-i " + Drive.Letter + ": -f " + Path.Combine(OutputDirectory, Path.GetFileNameWithoutExtension(OutputFilename) + "_subdump.sub") + "-mode 6 -rereadnum 25 -fix 2",
|
|
},
|
|
};
|
|
childProcess.Start();
|
|
childProcess.WaitForExit();
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract all of the possible information from a given input combination
|
|
/// </summary>
|
|
/// <param name="driveLetter">Drive letter to check</param>
|
|
/// <returns>Dictionary containing mapped output values, null on error</returns>
|
|
/// <remarks>TODO: Make sure that all special formats are accounted for</remarks>
|
|
private Dictionary<string, string> ExtractOutputInformation(IProgress<Result> progress)
|
|
{
|
|
// Ensure the current disc combination should exist
|
|
if (!Validators.GetValidMediaTypes(System).Contains(Type))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Sanitize the output filename to strip off any potential extension
|
|
string outputFilename = Path.GetFileNameWithoutExtension(OutputFilename);
|
|
|
|
// Check that all of the relevant files are there
|
|
if (!FoundAllFiles())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Create the output dictionary with all user-inputted values by default
|
|
string combinedBase = Path.Combine(OutputDirectory, outputFilename);
|
|
Dictionary<string, string> mappings = new Dictionary<string, string>
|
|
{
|
|
{ Template.TitleField, Template.RequiredValue },
|
|
{ Template.DiscNumberField, Template.OptionalValue },
|
|
{ Template.DiscTitleField, Template.OptionalValue },
|
|
{ Template.SystemField, System.Name() },
|
|
{ Template.MediaTypeField, Type.Name() },
|
|
{ Template.CategoryField, "Games" },
|
|
{ Template.RegionField, "World (CHANGE THIS)" },
|
|
{ Template.LanguagesField, "Klingon (CHANGE THIS)" },
|
|
{ Template.DiscSerialField, Template.RequiredIfExistsValue },
|
|
{ Template.BarcodeField, Template.OptionalValue},
|
|
{ Template.CommentsField, Template.OptionalValue },
|
|
{ Template.ContentsField, Template.OptionalValue },
|
|
{ Template.VersionField, Template.RequiredIfExistsValue },
|
|
{ Template.EditionField, "Original (VERIFY THIS)" },
|
|
{ Template.DATField, GetDatfile(combinedBase + ".dat") },
|
|
};
|
|
|
|
// Now we want to do a check by MediaType and extract all required info
|
|
switch (Type)
|
|
{
|
|
case MediaType.CDROM:
|
|
case MediaType.GDROM: // TODO: Verify GD-ROM outputs this
|
|
mappings[Template.MasteringRingField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.MasteringSIDField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.MouldSIDField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.AdditionalMouldField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.ToolstampField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.PVDField] = GetPVD(combinedBase + "_mainInfo.txt") ?? "";
|
|
mappings[Template.ErrorCountField] = GetErrorCount(combinedBase + ".img_EdcEcc.txt").ToString();
|
|
mappings[Template.CuesheetField] = GetFullFile(combinedBase + ".cue") ?? "";
|
|
mappings[Template.WriteOffsetField] = GetWriteOffset(combinedBase + "_disc.txt") ?? "";
|
|
|
|
// System-specific options
|
|
switch (System)
|
|
{
|
|
case KnownSystem.AppleMacintosh:
|
|
case KnownSystem.EnhancedCD:
|
|
case KnownSystem.EnhancedDVD:
|
|
case KnownSystem.EnhancedBD:
|
|
case KnownSystem.IBMPCCompatible:
|
|
case KnownSystem.RainbowDisc:
|
|
mappings[Template.ISBNField] = Template.OptionalValue;
|
|
|
|
progress?.Report(Result.Success("Running copy protection scan... this might take a while!"));
|
|
mappings[Template.CopyProtectionField] = GetCopyProtection();
|
|
progress?.Report(Result.Success("Copy protection scan complete!"));
|
|
|
|
if (File.Exists(combinedBase + "_subIntention.txt"))
|
|
{
|
|
FileInfo fi = new FileInfo(combinedBase + "_subIntention.txt");
|
|
if (fi.Length > 0)
|
|
{
|
|
mappings[Template.SubIntentionField] = GetFullFile(combinedBase + "_subIntention.txt") ?? "";
|
|
}
|
|
}
|
|
|
|
break;
|
|
case KnownSystem.SegaSaturn:
|
|
mappings[Template.SaturnHeaderField] = GetSaturnHeader(combinedBase + "_mainInfo.txt") ?? "";
|
|
if (GetSaturnBuildInfo(mappings[Template.SaturnHeaderField], out string serial, out string version, out string buildDate))
|
|
{
|
|
mappings[Template.DiscSerialField] = serial ?? "";
|
|
mappings[Template.VersionField] = version ?? "";
|
|
mappings[Template.SaturnBuildDateField] = buildDate ?? "";
|
|
}
|
|
|
|
break;
|
|
case KnownSystem.SonyPlayStation:
|
|
mappings[Template.PlaystationEXEDateField] = GetPlayStationEXEDate(Drive.Letter) ?? "";
|
|
mappings[Template.PlayStationEDCField] = GetMissingEDCCount(combinedBase + ".img_EdcEcc.txt") > 0 ? "No" : "Yes";
|
|
mappings[Template.PlayStationAntiModchipField] = GetAntiModchipDetected(combinedBase + "_disc.txt") ? "Yes" : "No";
|
|
mappings[Template.PlayStationLibCryptField] = "No";
|
|
if (File.Exists(combinedBase + "_subIntention.txt"))
|
|
{
|
|
FileInfo fi = new FileInfo(combinedBase + "_subIntention.txt");
|
|
if (fi.Length > 0)
|
|
{
|
|
mappings[Template.PlayStationLibCryptField] = "Yes";
|
|
mappings[Template.SubIntentionField] = GetFullFile(combinedBase + "_subIntention.txt") ?? "";
|
|
}
|
|
}
|
|
|
|
break;
|
|
case KnownSystem.SonyPlayStation2:
|
|
mappings[Template.PlaystationEXEDateField] = GetPlayStationEXEDate(Drive.Letter) ?? "";
|
|
mappings[Template.VersionField] = GetPlayStation2Version(Drive.Letter) ?? "";
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case MediaType.DVD:
|
|
case MediaType.HDDVD:
|
|
case MediaType.BluRay:
|
|
bool isXbox = (System == KnownSystem.MicrosoftXBOX || System == KnownSystem.MicrosoftXBOX360);
|
|
string layerbreak = GetLayerbreak(combinedBase + "_disc.txt", isXbox) ?? "";
|
|
|
|
// If we have a single-layer disc
|
|
if (String.IsNullOrWhiteSpace(layerbreak))
|
|
{
|
|
switch (Type)
|
|
{
|
|
case MediaType.DVD:
|
|
mappings[Template.MediaTypeField] += "-5";
|
|
break;
|
|
case MediaType.BluRay:
|
|
mappings[Template.MediaTypeField] += "-25";
|
|
break;
|
|
}
|
|
mappings[Template.MasteringRingField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.MasteringSIDField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.MouldSIDField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.AdditionalMouldField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.ToolstampField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.PVDField] = GetPVD(combinedBase + "_mainInfo.txt") ?? "";
|
|
}
|
|
// If we have a dual-layer disc
|
|
else
|
|
{
|
|
switch (Type)
|
|
{
|
|
case MediaType.DVD:
|
|
mappings[Template.MediaTypeField] += "-9";
|
|
break;
|
|
case MediaType.BluRay:
|
|
mappings[Template.MediaTypeField] += "-50";
|
|
break;
|
|
}
|
|
mappings["Outer " + Template.MasteringRingField] = Template.RequiredIfExistsValue;
|
|
mappings["Inner " + Template.MasteringRingField] = Template.RequiredIfExistsValue;
|
|
mappings["Outer " + Template.MasteringSIDField] = Template.RequiredIfExistsValue;
|
|
mappings["Inner " + Template.MasteringSIDField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.MouldSIDField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.AdditionalMouldField] = Template.RequiredIfExistsValue;
|
|
mappings["Outer " + Template.ToolstampField] = Template.RequiredIfExistsValue;
|
|
mappings["Inner " + Template.ToolstampField] = Template.RequiredIfExistsValue;
|
|
mappings[Template.PVDField] = GetPVD(combinedBase + "_mainInfo.txt") ?? "";
|
|
mappings[Template.LayerbreakField] = layerbreak;
|
|
}
|
|
|
|
// System-specific options
|
|
switch (System)
|
|
{
|
|
case KnownSystem.AppleMacintosh:
|
|
case KnownSystem.EnhancedCD:
|
|
case KnownSystem.EnhancedDVD:
|
|
case KnownSystem.EnhancedBD:
|
|
case KnownSystem.IBMPCCompatible:
|
|
case KnownSystem.RainbowDisc:
|
|
mappings[Template.ISBNField] = Template.OptionalValue;
|
|
|
|
progress?.Report(Result.Success("Running copy protection scan... this might take a while!"));
|
|
mappings[Template.CopyProtectionField] = GetCopyProtection();
|
|
progress?.Report(Result.Success("Copy protection scan complete!"));
|
|
|
|
if (File.Exists(combinedBase + "_subIntention.txt"))
|
|
{
|
|
FileInfo fi = new FileInfo(combinedBase + "_subIntention.txt");
|
|
if (fi.Length > 0)
|
|
{
|
|
mappings[Template.SubIntentionField] = GetFullFile(combinedBase + "_subIntention.txt") ?? "";
|
|
}
|
|
}
|
|
|
|
break;
|
|
case KnownSystem.DVDVideo:
|
|
mappings[Template.CopyProtectionField] = GetDVDProtection(combinedBase + "_CSSKey.txt", combinedBase + "_disc.txt") ?? "";
|
|
break;
|
|
case KnownSystem.MicrosoftXBOX:
|
|
case KnownSystem.MicrosoftXBOX360:
|
|
if (GetXBOXAuxInfo(combinedBase + "_disc.txt", out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver))
|
|
{
|
|
mappings[Template.XBOXDMIHash] = dmihash ?? "";
|
|
mappings[Template.XBOXPFIHash] = pfihash ?? "";
|
|
mappings[Template.XBOXSSHash] = sshash ?? "";
|
|
mappings[Template.XBOXSSVersion] = ssver ?? "";
|
|
mappings[Template.XBOXSSRanges] = ss ?? "";
|
|
}
|
|
|
|
break;
|
|
case KnownSystem.SonyPlayStation2:
|
|
mappings[Template.PlaystationEXEDateField] = GetPlayStationEXEDate(Drive.Letter) ?? "";
|
|
mappings[Template.VersionField] = GetPlayStation2Version(Drive.Letter) ?? "";
|
|
break;
|
|
case KnownSystem.SonyPlayStation4:
|
|
mappings[Template.PlayStation4PICField] = GetPlayStation4PIC(Path.Combine(OutputDirectory, "PIC.bin")) ?? "";
|
|
mappings[Template.VersionField] = GetPlayStation4Version(Drive.Letter) ?? "";
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case MediaType.UMD:
|
|
mappings[Template.PVDField] = GetPVD(combinedBase + "_mainInfo.txt") ?? "";
|
|
mappings[Template.DATField] = Template.RequiredValue + " [Not automatically generated for UMD]";
|
|
if (GetUMDAuxInfo(combinedBase + "_disc.txt", out string title, out string umdversion, out string umdlayer))
|
|
{
|
|
mappings[Template.TitleField] = title ?? "";
|
|
mappings[Template.VersionField] = umdversion ?? "";
|
|
if (!String.IsNullOrWhiteSpace(umdlayer))
|
|
mappings[Template.LayerbreakField] = umdlayer ?? "";
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return mappings;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Format the output data in a human readable way, separating each printed line into a new item in the list
|
|
/// </summary>
|
|
/// <param name="info">Information dictionary that should contain normalized values</param>
|
|
/// <returns>List of strings representing each line of an output file, null on error</returns>
|
|
/// <remarks>TODO: Get full list of customizable stuff for other systems</remarks>
|
|
private List<string> FormatOutputData(Dictionary<string, string> info)
|
|
{
|
|
// Check to see if the inputs are valid
|
|
if (info == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
List<string> output = new List<string>();
|
|
|
|
output.Add(Template.TitleField + ": " + info[Template.TitleField]);
|
|
output.Add(Template.DiscNumberField + ": " + info[Template.DiscNumberField]);
|
|
output.Add(Template.DiscTitleField + ": " + info[Template.DiscTitleField]);
|
|
output.Add(Template.SystemField + ": " + info[Template.SystemField]);
|
|
output.Add(Template.MediaTypeField + ": " + info[Template.MediaTypeField]);
|
|
output.Add(Template.CategoryField + ": " + info[Template.CategoryField]);
|
|
output.Add(Template.RegionField + ": " + info[Template.RegionField]);
|
|
output.Add(Template.LanguagesField + ": " + info[Template.LanguagesField]);
|
|
output.Add(Template.DiscSerialField + ": " + info[Template.DiscSerialField]);
|
|
switch (System)
|
|
{
|
|
case KnownSystem.SegaSaturn:
|
|
output.Add(Template.SaturnBuildDateField + ": " + info[Template.SaturnBuildDateField]);
|
|
break;
|
|
case KnownSystem.SonyPlayStation:
|
|
case KnownSystem.SonyPlayStation2:
|
|
output.Add(Template.PlaystationEXEDateField + ": " + info[Template.PlaystationEXEDateField]);
|
|
break;
|
|
}
|
|
output.Add("Ringcode Information:");
|
|
switch (Type)
|
|
{
|
|
case MediaType.CDROM:
|
|
case MediaType.GDROM:
|
|
case MediaType.DVD:
|
|
case MediaType.HDDVD:
|
|
case MediaType.BluRay:
|
|
// If we have a dual-layer disc
|
|
if (info.ContainsKey(Template.LayerbreakField))
|
|
{
|
|
output.Add("\tOuter " + Template.MasteringRingField + ": " + info["Outer " + Template.MasteringRingField]);
|
|
output.Add("\tInner " + Template.MasteringRingField + ": " + info["Inner " + Template.MasteringRingField]);
|
|
output.Add("\tOuter " + Template.MasteringSIDField + ": " + info["Outer " + Template.MasteringSIDField]);
|
|
output.Add("\tInner " + Template.MasteringSIDField + ": " + info["Inner " + Template.MasteringSIDField]);
|
|
output.Add("\t" + Template.MouldSIDField + ": " + info[Template.MouldSIDField]);
|
|
output.Add("\t" + Template.AdditionalMouldField + ": " + info[Template.AdditionalMouldField]);
|
|
output.Add("\tOuter " + Template.ToolstampField + ": " + info["Outer " + Template.ToolstampField]);
|
|
output.Add("\tInner " + Template.ToolstampField + ": " + info["Inner " + Template.ToolstampField]);
|
|
}
|
|
// If we have a single-layer disc
|
|
else
|
|
{
|
|
output.Add("\t" + Template.MasteringRingField + ": " + info[Template.MasteringRingField]);
|
|
output.Add("\t" + Template.MasteringSIDField + ": " + info[Template.MasteringSIDField]);
|
|
output.Add("\t" + Template.MouldSIDField + ": " + info[Template.MouldSIDField]);
|
|
output.Add("\t" + Template.AdditionalMouldField + ": " + info[Template.AdditionalMouldField]);
|
|
output.Add("\t" + Template.ToolstampField + ": " + info[Template.ToolstampField]);
|
|
}
|
|
break;
|
|
}
|
|
output.Add(Template.BarcodeField + ": " + info[Template.BarcodeField]);
|
|
switch (System)
|
|
{
|
|
case KnownSystem.AppleMacintosh:
|
|
case KnownSystem.EnhancedCD:
|
|
case KnownSystem.EnhancedDVD:
|
|
case KnownSystem.EnhancedBD:
|
|
case KnownSystem.IBMPCCompatible:
|
|
case KnownSystem.RainbowDisc:
|
|
output.Add(Template.ISBNField + ": " + info[Template.ISBNField]);
|
|
break;
|
|
}
|
|
switch (Type)
|
|
{
|
|
case MediaType.CDROM:
|
|
case MediaType.GDROM:
|
|
output.Add(Template.ErrorCountField + ": " + info[Template.ErrorCountField]);
|
|
break;
|
|
}
|
|
output.Add(Template.CommentsField + ": " + info[Template.CommentsField]);
|
|
output.Add(Template.ContentsField + ": " + info[Template.ContentsField]);
|
|
output.Add(Template.VersionField + ": " + info[Template.VersionField]);
|
|
output.Add(Template.EditionField + ": " + info[Template.EditionField]);
|
|
switch (System)
|
|
{
|
|
case KnownSystem.SegaSaturn:
|
|
output.Add(Template.SaturnHeaderField + ":"); output.Add("");
|
|
output.AddRange(info[Template.SaturnHeaderField].Split('\n')); output.Add("");
|
|
break;
|
|
case KnownSystem.SonyPlayStation:
|
|
output.Add(Template.PlayStationEDCField + ": " + info[Template.PlayStationEDCField]);
|
|
output.Add(Template.PlayStationAntiModchipField + ": " + info[Template.PlayStationAntiModchipField]);
|
|
output.Add(Template.PlayStationLibCryptField + ": " + info[Template.PlayStationLibCryptField]);
|
|
break;
|
|
}
|
|
switch (Type)
|
|
{
|
|
case MediaType.DVD:
|
|
case MediaType.BluRay:
|
|
case MediaType.UMD:
|
|
// If we have a dual-layer disc
|
|
if (info.ContainsKey(Template.LayerbreakField))
|
|
{
|
|
output.Add(Template.LayerbreakField + ": " + info[Template.LayerbreakField]);
|
|
}
|
|
break;
|
|
}
|
|
output.Add(Template.PVDField + ":"); output.Add("");
|
|
output.AddRange(info[Template.PVDField].Split('\n'));
|
|
switch (System)
|
|
{
|
|
case KnownSystem.AppleMacintosh:
|
|
case KnownSystem.EnhancedCD:
|
|
case KnownSystem.EnhancedDVD:
|
|
case KnownSystem.EnhancedBD:
|
|
case KnownSystem.IBMPCCompatible:
|
|
case KnownSystem.RainbowDisc:
|
|
output.Add(Template.CopyProtectionField + ": " + info[Template.CopyProtectionField]); output.Add("");
|
|
break;
|
|
case KnownSystem.DVDVideo:
|
|
output.Add(Template.CopyProtectionField + ":"); output.Add("");
|
|
output.AddRange(info[Template.CopyProtectionField].Split('\n'));
|
|
break;
|
|
case KnownSystem.MicrosoftXBOX:
|
|
case KnownSystem.MicrosoftXBOX360:
|
|
if (Type == MediaType.DVD)
|
|
{
|
|
output.Add(Template.XBOXDMIHash + ": " + info[Template.XBOXDMIHash]);
|
|
output.Add(Template.XBOXPFIHash + ": " + info[Template.XBOXPFIHash]);
|
|
output.Add(Template.XBOXSSHash + ": " + info[Template.XBOXSSHash]); output.Add("");
|
|
output.Add(Template.XBOXSSVersion + ": " + info[Template.XBOXSSVersion]);
|
|
output.Add(Template.XBOXSSRanges + ":"); output.Add("");
|
|
output.AddRange(info[Template.XBOXSSRanges].Split('\n'));
|
|
}
|
|
break;
|
|
case KnownSystem.SonyPlayStation4:
|
|
output.Add(Template.PlayStation4PICField + ":"); output.Add("");
|
|
output.AddRange(info[Template.PlayStation4PICField].Split('\n'));
|
|
break;
|
|
}
|
|
if (info.ContainsKey(Template.SubIntentionField))
|
|
{
|
|
output.Add(Template.SubIntentionField + ":"); output.Add("");
|
|
output.AddRange(info[Template.SubIntentionField].Split('\n')); output.Add("");
|
|
}
|
|
switch (Type)
|
|
{
|
|
case MediaType.CDROM:
|
|
case MediaType.GDROM:
|
|
output.Add(Template.CuesheetField + ":"); output.Add("");
|
|
output.AddRange(info[Template.CuesheetField].Split('\n')); output.Add("");
|
|
output.Add(Template.WriteOffsetField + ": " + info[Template.WriteOffsetField]); output.Add("");
|
|
break;
|
|
}
|
|
if (info.ContainsKey(Template.DATField))
|
|
{
|
|
output.Add(Template.DATField + ":"); output.Add("");
|
|
output.AddRange(info[Template.DATField].Split('\n'));
|
|
}
|
|
|
|
return output;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error is
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the existance of an anti-modchip string from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="disc">_disc.txt file location</param>
|
|
/// <returns>Antimodchip existance if possible, false on error</returns>
|
|
private bool GetAntiModchipDetected(string disc)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(disc))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(disc))
|
|
{
|
|
try
|
|
{
|
|
// Check for either antimod string
|
|
string line = sr.ReadLine().Trim();
|
|
while (!sr.EndOfStream)
|
|
{
|
|
if (line.StartsWith("Detected anti-mod string"))
|
|
{
|
|
return true;
|
|
}
|
|
else if (line.StartsWith("No anti-mod string"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
line = sr.ReadLine().Trim();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current copy protection scheme, if possible
|
|
/// </summary>
|
|
/// <returns>Copy protection scheme if possible, null on error</returns>
|
|
private string GetCopyProtection()
|
|
{
|
|
if (ScanForProtection)
|
|
return Task.Run(() => Validators.RunProtectionScanOnPath(Drive.Letter + ":\\")).GetAwaiter().GetResult();
|
|
return "(CHECK WITH PROTECTIONID)";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the proper datfile from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="dat">.dat file location</param>
|
|
/// <returns>Relevant pieces of the datfile, null on error</returns>
|
|
private string GetDatfile(string dat)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(dat))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(dat))
|
|
{
|
|
try
|
|
{
|
|
// Make sure this file is a .dat
|
|
if (sr.ReadLine() != "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
|
|
{
|
|
return null;
|
|
}
|
|
if (sr.ReadLine() != "<!DOCTYPE datafile PUBLIC \"-//Logiqx//DTD ROM Management Datafile//EN\" \"http://www.logiqx.com/Dats/datafile.dtd\">")
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Fast forward to the rom lines
|
|
while (!sr.ReadLine().TrimStart().StartsWith("<game")) ;
|
|
sr.ReadLine(); // <category>Games</category>
|
|
sr.ReadLine(); // <description>Plextor</description>
|
|
|
|
// Now that we're at the relevant entries, read each line in and concatenate
|
|
string pvd = "", line = sr.ReadLine().Trim();
|
|
while (line.StartsWith("<rom"))
|
|
{
|
|
pvd += line + "\n";
|
|
line = sr.ReadLine().Trim();
|
|
}
|
|
|
|
return pvd.TrimEnd('\n');
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the DVD protection information, if possible
|
|
/// </summary>
|
|
/// <param name="cssKey">_CSSKey.txt file location</param>
|
|
/// <param name="disc">_disc.txt file location</param>
|
|
/// <returns>Formatted string representing the DVD protection, null on error</returns>
|
|
private string GetDVDProtection(string cssKey, string disc)
|
|
{
|
|
// If one of the files doesn't exist, we can't get info from them
|
|
if (!File.Exists(disc))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Setup all of the individual pieces
|
|
string region = null, rceProtection = null, copyrightProtectionSystemType = null, encryptedDiscKey = null, playerKey = null, decryptedDiscKey = null;
|
|
|
|
// Get everything from _disc.txt first
|
|
using (StreamReader sr = File.OpenText(disc))
|
|
{
|
|
try
|
|
{
|
|
// Fast forward to the copyright information
|
|
while (!sr.ReadLine().Trim().StartsWith("========== CopyrightInformation =========="));
|
|
|
|
// Now read until we hit the manufacturing information
|
|
string line = sr.ReadLine().Trim();
|
|
while (!line.StartsWith("========== ManufacturingInformation =========="))
|
|
{
|
|
if (line.StartsWith("CopyrightProtectionType"))
|
|
copyrightProtectionSystemType = line.Substring("CopyrightProtectionType: ".Length);
|
|
else if (line.StartsWith("RegionManagementInformation"))
|
|
region = line.Substring("RegionManagementInformation: ".Length);
|
|
|
|
line = sr.ReadLine().Trim();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// Get everything from _CSSKey.txt next, if it exists
|
|
if (File.Exists(cssKey))
|
|
{
|
|
using (StreamReader sr = File.OpenText(cssKey))
|
|
{
|
|
try
|
|
{
|
|
// Read until the end
|
|
while (!sr.EndOfStream)
|
|
{
|
|
string line = sr.ReadLine().Trim();
|
|
|
|
if (line.StartsWith("[001]"))
|
|
encryptedDiscKey = line.Substring("[001]: ".Length);
|
|
else if (line.StartsWith("PlayerKey"))
|
|
playerKey = line.Substring("PlayerKey[1]: ".Length);
|
|
else if (line.StartsWith("DecryptedDiscKey"))
|
|
decryptedDiscKey = line.Substring("DecryptedDiscKey[020]: ".Length);
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
// Now we format everything we can
|
|
string protection = "";
|
|
if (!String.IsNullOrEmpty(region))
|
|
protection += $"Region: {region}\n";
|
|
if (!String.IsNullOrEmpty(rceProtection))
|
|
protection += $"RCE Protection: {rceProtection}\n";
|
|
if (!String.IsNullOrEmpty(copyrightProtectionSystemType))
|
|
protection += $"Copyright Protection System Type: {copyrightProtectionSystemType}\n";
|
|
if (!String.IsNullOrEmpty(encryptedDiscKey))
|
|
protection += $"Encrypted Disc Key: {encryptedDiscKey}\n";
|
|
if (!String.IsNullOrEmpty(playerKey))
|
|
protection += $"Player Key: {playerKey}\n";
|
|
if (!String.IsNullOrEmpty(decryptedDiscKey))
|
|
protection += $"Decrypted Disc Key: {decryptedDiscKey}\n";
|
|
|
|
return protection;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the detected error count from the input files, if possible
|
|
/// </summary>
|
|
/// <param name="edcecc">.img_EdcEcc.txt file location</param>
|
|
/// <returns>Error count if possible, -1 on error</returns>
|
|
private long GetErrorCount(string edcecc)
|
|
{
|
|
// TODO: Better usage of _mainInfo and _c2Error for uncorrectable errors
|
|
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(edcecc))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// First line of defense is the EdcEcc error file
|
|
using (StreamReader sr = File.OpenText(edcecc))
|
|
{
|
|
try
|
|
{
|
|
// Read in the error count whenever we find it
|
|
while (!sr.EndOfStream)
|
|
{
|
|
string line = sr.ReadLine().Trim();
|
|
|
|
if (line.StartsWith("[NO ERROR]"))
|
|
return 0;
|
|
else if (line.StartsWith("Total errors"))
|
|
{
|
|
if (Int64.TryParse(line.Substring("Total errors: ".Length).Trim(), out long te))
|
|
return te;
|
|
else
|
|
return Int64.MinValue;
|
|
}
|
|
}
|
|
|
|
// If we haven't found anything, return -1
|
|
return -1;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return Int64.MaxValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the full lines from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="filename">file location</param>
|
|
/// <returns>Full text of the file, null on error</returns>
|
|
private string GetFullFile(string filename)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(filename))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return string.Join("\n", File.ReadAllLines(filename));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the layerbreak from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="disc">_disc.txt file location</param>
|
|
/// <param name="ignoreFirst">True if the first sector length is to be ignored, false otherwise</param>
|
|
/// <returns>Layerbreak if possible, null on error</returns>
|
|
private string GetLayerbreak(string disc, bool ignoreFirst)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(disc))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(disc))
|
|
{
|
|
try
|
|
{
|
|
// Fast forward to the layerbreak
|
|
string line = sr.ReadLine();
|
|
while (line != null)
|
|
{
|
|
// We definitely found a single-layer disc
|
|
if (line.Contains("NumberOfLayers: Single Layer"))
|
|
return null;
|
|
else if (line.Trim().StartsWith("========== SectorLength =========="))
|
|
{
|
|
// Skip the first one and unset the flag
|
|
if (ignoreFirst)
|
|
ignoreFirst = false;
|
|
else
|
|
break;
|
|
}
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
// Now that we're at the layerbreak line, attempt to get the decimal version
|
|
return sr.ReadLine().Trim().Split(' ')[1];
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the detected missing EDC count from the input files, if possible
|
|
/// </summary>
|
|
/// <param name="edcecc">.img_EdcEcc.txt file location</param>
|
|
/// <returns>Missing EDC count if possible, -1 on error</returns>
|
|
private long GetMissingEDCCount(string edcecc)
|
|
{
|
|
// If one of the files doesn't exist, we can't get info from them
|
|
if (!File.Exists(edcecc))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// First line of defense is the EdcEcc error file
|
|
using (StreamReader sr = File.OpenText(edcecc))
|
|
{
|
|
try
|
|
{
|
|
// Fast forward to the PVD
|
|
string line = sr.ReadLine();
|
|
while (!line.StartsWith("[INFO] Number of sector(s) where EDC doesn't exist: "))
|
|
{
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
return Int64.Parse(line.Remove(0, "[INFO] Number of sector(s) where EDC doesn't exist: ".Length).Trim());
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the PVD from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="mainInfo">_mainInfo.txt file location</param>
|
|
/// <returns>Newline-deliminated PVD if possible, null on error</returns>
|
|
private string GetPVD(string mainInfo)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(mainInfo))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(mainInfo))
|
|
{
|
|
try
|
|
{
|
|
// Make sure we're in the right sector
|
|
while (!sr.ReadLine().StartsWith("========== LBA[000016, 0x00010]: Main Channel =========="));
|
|
|
|
// Fast forward to the PVD
|
|
while (!sr.ReadLine().StartsWith("0310"));
|
|
|
|
// Now that we're at the PVD, read each line in and concatenate
|
|
string pvd = "";
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the EXE date from a PlayStation disc, if possible
|
|
/// </summary>
|
|
/// <param name="driveLetter">Drive letter to use to check</param>
|
|
/// <returns>EXE date in "yyyy-mm-dd" format if possible, null on error</returns>
|
|
private string GetPlayStationEXEDate(char driveLetter)
|
|
{
|
|
// If the folder no longer exists, we can't do this part
|
|
string drivePath = driveLetter + ":\\";
|
|
if (!Directory.Exists(drivePath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// If we can't find SYSTEM.CNF, we don't have a PlayStation disc
|
|
string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF");
|
|
if (!File.Exists(systemCnfPath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Let's try reading SYSTEM.CNF to find the "BOOT" value
|
|
string exeName = null;
|
|
try
|
|
{
|
|
using (StreamReader sr = File.OpenText(systemCnfPath))
|
|
{
|
|
// Not assuming proper ordering, just in case
|
|
string line = sr.ReadLine();
|
|
while (!line.StartsWith("BOOT"))
|
|
{
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
// Once it finds the "BOOT" line, extract the name
|
|
exeName = Regex.Match(line, @"BOOT.? = cdrom.?:\\(.*?);.*").Groups[1].Value;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error was
|
|
return null;
|
|
}
|
|
|
|
// Now that we have the EXE name, try to get the fileinfo for it
|
|
string exePath = Path.Combine(drivePath, exeName);
|
|
if (!File.Exists(exePath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
FileInfo fi = new FileInfo(exePath);
|
|
return fi.LastWriteTimeUtc.ToString("yyyy-MM-dd");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the version from a PlayStation 2 disc, if possible
|
|
/// </summary>
|
|
/// <param name="driveLetter">Drive letter to use to check</param>
|
|
/// <returns>Game version if possible, null on error</returns>
|
|
private string GetPlayStation2Version(char driveLetter)
|
|
{
|
|
// If the folder no longer exists, we can't do this part
|
|
string drivePath = driveLetter + ":\\";
|
|
if (!Directory.Exists(drivePath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// If we can't find SYSTEM.CNF, we don't have a PlayStation 2 disc
|
|
string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF");
|
|
if (!File.Exists(systemCnfPath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Let's try reading SYSTEM.CNF to find the "VER" value
|
|
try
|
|
{
|
|
using (StreamReader sr = File.OpenText(systemCnfPath))
|
|
{
|
|
// Not assuming proper ordering, just in case
|
|
string line = sr.ReadLine();
|
|
while (!line.StartsWith("VER"))
|
|
{
|
|
line = sr.ReadLine();
|
|
}
|
|
|
|
// Once it finds the "VER" line, extract the version
|
|
return Regex.Match(line, @"VER\s*=\s*(.*)").Groups[1].Value;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error was
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the hex contents of the PIC file
|
|
/// </summary>
|
|
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
|
|
/// <returns>PIC data as a hex string if possible, null on error</returns>
|
|
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
|
|
private string GetPlayStation4PIC(string picPath)
|
|
{
|
|
// If the file doesn't exist, we can't get the info
|
|
if (!File.Exists(picPath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
using (BinaryReader br = new BinaryReader(File.OpenRead(picPath)))
|
|
{
|
|
string hex = BitConverter.ToString(br.ReadBytes(140)).Replace("-", string.Empty);
|
|
return Regex.Replace(hex, ".{32}", "$0\n");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error was right now
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the version from a PlayStation 4 disc, if possible
|
|
/// </summary>
|
|
/// <param name="driveLetter">Drive letter to use to check</param>
|
|
/// <returns>Game version if possible, null on error</returns>
|
|
private string GetPlayStation4Version(char driveLetter)
|
|
{
|
|
// If the folder no longer exists, we can't do this part
|
|
string drivePath = driveLetter + ":\\";
|
|
if (!Directory.Exists(drivePath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// If we can't find param.sfo, we don't have a PlayStation 4 disc
|
|
string paramSfoPath = Path.Combine(drivePath, "bd", "param.sfo");
|
|
if (!File.Exists(paramSfoPath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Let's try reading param.sfo to find the version at the end of the file
|
|
try
|
|
{
|
|
using (BinaryReader br = new BinaryReader(File.OpenRead(paramSfoPath)))
|
|
{
|
|
br.BaseStream.Seek(0x9A4, SeekOrigin.Begin);
|
|
return new string(br.ReadChars(5));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error was
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the header from a Saturn disc, if possible
|
|
/// </summary>
|
|
/// <param name="mainInfo">_mainInfo.txt file location</param>
|
|
/// <returns>Header as a byte array if possible, null on error</returns>
|
|
private string GetSaturnHeader(string mainInfo)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(mainInfo))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(mainInfo))
|
|
{
|
|
try
|
|
{
|
|
// Make sure we're in the right sector
|
|
while (!sr.ReadLine().StartsWith("========== LBA[000000, 0000000]: Main Channel ==========")) ;
|
|
|
|
// Fast forward to the header
|
|
while (!sr.ReadLine().Trim().StartsWith("+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F")) ;
|
|
|
|
// Now that we're at the Header, read each line in and concatenate
|
|
string header = "";
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
header += sr.ReadLine() + "\n"; // 0000-00F0
|
|
}
|
|
|
|
return header;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the build info from a Saturn disc, if possible
|
|
/// </summary>
|
|
/// <<param name="saturnHeader">String representing a formatter variant of the Saturn header</param>
|
|
/// <returns>True on successful extraction of info, false otherwise</returns>
|
|
private bool GetSaturnBuildInfo(string saturnHeader, out string serial, out string version, out string date)
|
|
{
|
|
serial = null; version = null; date = null;
|
|
|
|
// If the input header is null, we can't do a thing
|
|
if (String.IsNullOrWhiteSpace(saturnHeader))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Now read it in cutting it into lines for easier parsing
|
|
try
|
|
{
|
|
string[] header = saturnHeader.Split('\n');
|
|
string serialVersionLine = header[2].Substring(58);
|
|
string dateLine = header[3].Substring(58);
|
|
serial = serialVersionLine.Substring(0, 8);
|
|
version = serialVersionLine.Substring(10, 6);
|
|
date = dateLine.Substring(0, 8);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error is
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the UMD auxiliary info from the outputted files, if possible
|
|
/// </summary>
|
|
/// <param name="disc">_disc.txt file location</param>
|
|
/// <returns>True on successful extraction of info, false otherwise</returns>
|
|
private bool GetUMDAuxInfo(string disc, out string title, out string umdversion, out string umdlayer)
|
|
{
|
|
title = null; umdversion = null; umdlayer = null;
|
|
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(disc))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(disc))
|
|
{
|
|
try
|
|
{
|
|
// Loop through everything to get the first instance of each required field
|
|
string line = string.Empty;
|
|
while (!sr.EndOfStream)
|
|
{
|
|
line = sr.ReadLine().Trim();
|
|
|
|
if (line.StartsWith("TITLE") && title == null)
|
|
title = line.Substring("TITLE: ".Length);
|
|
else if (line.StartsWith("version") && umdversion == null)
|
|
umdversion = line.Substring("version: ".Length);
|
|
else if (line.StartsWith("L0 length"))
|
|
umdlayer = line.Split(' ')[2];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the XBOX/360 auxiliary info from the outputted files, if possible
|
|
/// </summary>
|
|
/// <param name="disc">_disc.txt file location</param>
|
|
/// <returns>True on successful extraction of info, false otherwise</returns>
|
|
private bool GetXBOXAuxInfo(string disc, out string dmihash, out string pfihash, out string sshash, out string ss, out string ssver)
|
|
{
|
|
dmihash = null; pfihash = null; sshash = null; ss = null; ssver = null;
|
|
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(disc))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(disc))
|
|
{
|
|
try
|
|
{
|
|
// Fast forward to the Security Sector version and read it
|
|
while (!sr.ReadLine().Trim().StartsWith("CPR_MAI Key"));
|
|
ssver = sr.ReadLine().Trim().Split(' ')[4]; // "Version of challenge table: <VER>"
|
|
|
|
// Fast forward to the Security Sector Ranges
|
|
while (!sr.ReadLine().Trim().StartsWith("Number of security sector ranges:"));
|
|
|
|
// Now that we're at the ranges, read each line in and concatenate
|
|
Regex layerRegex = new Regex(@"Layer [01].*, startLBA-endLBA:\s*(\d+)-\s*(\d+)");
|
|
string line = sr.ReadLine().Trim();
|
|
while (!line.StartsWith("========== Unlock 2 state(wxripper) =========="))
|
|
{
|
|
// If we have a recognized line format, parse it
|
|
if (line.StartsWith("Layer "))
|
|
{
|
|
var match = layerRegex.Match(line);
|
|
ss += $"{match.Groups[1]}-{match.Groups[2]}\n";
|
|
}
|
|
|
|
line = sr.ReadLine().Trim();
|
|
}
|
|
|
|
// Fast forward to the aux hashes
|
|
while (!line.StartsWith("<rom"))
|
|
{
|
|
line = sr.ReadLine().Trim();
|
|
}
|
|
|
|
// Read in the hashes to the proper parts
|
|
while (line.StartsWith("<rom"))
|
|
{
|
|
if (line.Contains("SS.bin"))
|
|
sshash = line;
|
|
else if (line.Contains("PFI.bin"))
|
|
pfihash = line;
|
|
else if (line.Contains("DMI.bin"))
|
|
dmihash = line;
|
|
|
|
if (sr.EndOfStream)
|
|
break;
|
|
|
|
line = sr.ReadLine().Trim();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the write offset from the input file, if possible
|
|
/// </summary>
|
|
/// <param name="disc">_disc.txt file location</param>
|
|
/// <returns>Sample write offset if possible, null on error</returns>
|
|
private string GetWriteOffset(string disc)
|
|
{
|
|
// If the file doesn't exist, we can't get info from it
|
|
if (!File.Exists(disc))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using (StreamReader sr = File.OpenText(disc))
|
|
{
|
|
try
|
|
{
|
|
// Fast forward to the offsets
|
|
while (!sr.ReadLine().Trim().StartsWith("========== Offset")) ;
|
|
sr.ReadLine(); // Combined Offset
|
|
sr.ReadLine(); // Drive Offset
|
|
sr.ReadLine(); // Separator line
|
|
|
|
// Now that we're at the offsets, attempt to get the sample offset
|
|
return sr.ReadLine().Split(' ').LastOrDefault();
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the exception is right now
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate the current environment is ready for a dump
|
|
/// </summary>
|
|
/// <returns>Result instance with the outcome</returns>
|
|
private Result IsValidForDump()
|
|
{
|
|
// Validate that everything is good
|
|
if (!ParametersValid())
|
|
return Result.Failure("Error! Current configuration is not supported!");
|
|
|
|
FixOutputPaths();
|
|
|
|
// Validate that the required program exists
|
|
if (!File.Exists(DICPath))
|
|
return Result.Failure("Error! Could not find DiscImageCreator!");
|
|
|
|
return Result.Success();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the current environment has a complete dump and create submission info is possible
|
|
/// </summary>
|
|
/// <returns>Result instance with the outcome</returns>
|
|
private Result VerifyAndSaveDumpOutput(IProgress<Result> progress)
|
|
{
|
|
// Check to make sure that the output had all the correct files
|
|
if (!FoundAllFiles())
|
|
return Result.Failure("Error! Please check output directory as dump may be incomplete!");
|
|
|
|
progress?.Report(Result.Success("Extracting output information from output files..."));
|
|
Dictionary<string, string> templateValues = ExtractOutputInformation(progress);
|
|
progress?.Report(Result.Success("Extracting information complete!"));
|
|
|
|
progress?.Report(Result.Success("Formatting extracted information..."));
|
|
List<string> formattedValues = FormatOutputData(templateValues);
|
|
progress?.Report(Result.Success("Formatting complete!"));
|
|
|
|
progress?.Report(Result.Success("Writing information to !submissionInfo.txt..."));
|
|
bool success = WriteOutputData(formattedValues);
|
|
progress?.Report(Result.Success("Writing complete!"));
|
|
|
|
return Result.Success();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the data to the output folder
|
|
/// </summary>
|
|
/// <param name="lines">Preformatted list of lines to write out to the file</param>
|
|
/// <returns>True on success, false on error</returns>
|
|
private bool WriteOutputData(List<string> lines)
|
|
{
|
|
// Check to see if the inputs are valid
|
|
if (lines == null)
|
|
return false;
|
|
|
|
// Now write out to a generic file
|
|
try
|
|
{
|
|
using (StreamWriter sw = new StreamWriter(File.Open(Path.Combine(OutputDirectory, "!submissionInfo.txt"), FileMode.Create, FileAccess.Write)))
|
|
{
|
|
foreach (string line in lines)
|
|
sw.WriteLine(line);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// We don't care what the error is right now
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|