diff --git a/MPF.Core/Converters/EnumConverter.cs b/MPF.Core/Converters/EnumConverter.cs
index 11032b5cb..ca3ac7bcb 100644
--- a/MPF.Core/Converters/EnumConverter.cs
+++ b/MPF.Core/Converters/EnumConverter.cs
@@ -101,6 +101,7 @@ public static string LongName(this InternalProgram? prog)
InternalProgram.CleanRip => "CleanRip",
InternalProgram.DCDumper => "DCDumper",
+ InternalProgram.PS3CFW => "PS3 CFW",
InternalProgram.UmdImageCreator => "UmdImageCreator",
#endregion
@@ -141,6 +142,11 @@ public static InternalProgram ToInternalProgram(string? internalProgram)
"dc"
or "dcd"
or "dcdumper" => InternalProgram.DCDumper,
+ "ps3cfw"
+ or "ps3"
+ or "getkey"
+ or "managunz"
+ or "multiman" => InternalProgram.PS3CFW,
"uic"
or "umd"
or "umdcreator"
diff --git a/MPF.Core/Data/Enumerations.cs b/MPF.Core/Data/Enumerations.cs
index e8d2c0d3b..fd4ed2b4d 100644
--- a/MPF.Core/Data/Enumerations.cs
+++ b/MPF.Core/Data/Enumerations.cs
@@ -46,6 +46,7 @@ public enum InternalProgram
// Verification support only
CleanRip,
DCDumper,
+ PS3CFW,
UmdImageCreator,
}
diff --git a/MPF.Core/DumpEnvironment.cs b/MPF.Core/DumpEnvironment.cs
index 8f5286311..d81e01287 100644
--- a/MPF.Core/DumpEnvironment.cs
+++ b/MPF.Core/DumpEnvironment.cs
@@ -148,6 +148,7 @@ public void SetParameters(string? parameters)
// Verification support only
InternalProgram.CleanRip => new Modules.CleanRip.Parameters(parameters) { ExecutablePath = null },
InternalProgram.DCDumper => null, // TODO: Create correct parameter type when supported
+ InternalProgram.PS3CFW => new Modules.PS3CFW.Parameters(parameters) { ExecutablePath = null },
InternalProgram.UmdImageCreator => new Modules.UmdImageCreator.Parameters(parameters) { ExecutablePath = null },
// If no dumping program found, set to null
diff --git a/MPF.Core/Modules/PS3CFW/Parameters.cs b/MPF.Core/Modules/PS3CFW/Parameters.cs
new file mode 100644
index 000000000..928261b4a
--- /dev/null
+++ b/MPF.Core/Modules/PS3CFW/Parameters.cs
@@ -0,0 +1,292 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using MPF.Core.Converters;
+using MPF.Core.Data;
+using SabreTools.RedumpLib;
+using SabreTools.RedumpLib.Data;
+
+namespace MPF.Core.Modules.PS3CFW
+{
+ ///
+ /// Represents a generic set of PlayStation 3 Custom Firmware parameters
+ ///
+ public class Parameters : BaseParameters
+ {
+ #region Metadata
+
+ ///
+ public override InternalProgram InternalProgram => InternalProgram.PS3CFW;
+
+ #endregion
+
+ ///
+ public Parameters(string? parameters) : base(parameters) { }
+
+ ///
+ public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
+ : base(system, type, drivePath, filename, driveSpeed, options)
+ {
+ }
+
+ #region BaseParameters Implementations
+
+ ///
+ public override (bool, List) CheckAllOutputFilesExist(string basePath, bool preCheck)
+ {
+ var missingFiles = new List();
+
+ if (this.Type != MediaType.BluRay || this.System != RedumpSystem.SonyPlayStation3)
+ {
+ missingFiles.Add("Media and system combination not supported for PS3 CFW");
+ }
+ else
+ {
+ string? getKeyBasePath = GetCFWBasePath(basePath);
+ if (!File.Exists($"{getKeyBasePath}.getkey.log"))
+ missingFiles.Add($"{getKeyBasePath}.getkey.log");
+ if (!File.Exists($"{getKeyBasePath}.disc.pic"))
+ missingFiles.Add($"{getKeyBasePath}.disc.pic");
+ }
+
+ return (missingFiles.Count == 0, missingFiles);
+ }
+
+ ///
+ public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
+ {
+ // Ensure that required sections exist
+ info = Builder.EnsureAllSections(info);
+
+ info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
+
+ // Get the Datafile information
+ Datafile? datafile = GeneratePS3CFWDatafile(basePath + ".iso");
+
+ // Fill in the hash data
+ info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
+
+ // Get the individual hash data, as per internal
+ if (InfoTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
+ {
+ info.SizeAndChecksums!.Size = size;
+ info.SizeAndChecksums.CRC32 = crc32;
+ info.SizeAndChecksums.MD5 = md5;
+ info.SizeAndChecksums.SHA1 = sha1;
+ }
+
+ // Get the PVD from the ISO
+ if (GetPVD(basePath + ".iso", out string? pvd))
+ info.Extras!.PVD = pvd;
+
+ // Try get the serial, version, and firmware version if a drive is provided
+ if (drive != null)
+ {
+ info.VersionAndEditions!.Version = InfoTool.GetPlayStation3Version(drive?.Name) ?? string.Empty;
+ info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation3Serial(drive?.Name) ?? string.Empty;
+ string? firmwareVersion = InfoTool.GetPlayStation3FirmwareVersion(drive?.Name);
+ if (firmwareVersion != null)
+ info.CommonDiscInfo!.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
+ }
+
+ // Try to determine the name of the GetKey file(s)
+ string? getKeyBasePath = GetCFWBasePath(basePath);
+
+ // If GenerateSubmissionInfo is run, .getkey.log existence should already be checked
+ if (!File.Exists(getKeyBasePath + ".getkey.log"))
+ return;
+
+ // Get dumping date from GetKey log date
+ info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate(getKeyBasePath + ".getkey.log")?.ToString("yyyy-MM-dd HH:mm:ss");
+
+ // TODO: Put info about abnormal PIC info beyond 132 bytes in comments?
+ if (File.Exists(getKeyBasePath + ".disc.pic"))
+ info.Extras!.PIC = GetPIC(getKeyBasePath + ".disc.pic", 264);
+
+ // Parse Disc Key, Disc ID, and PIC from the .getkey.log file
+ if (Utilities.Tools.ParseGetKeyLog(getKeyBasePath + ".getkey.log", out string? key, out string? id, out string? pic))
+ {
+ if (key != null)
+ info.Extras!.DiscKey = key.ToUpperInvariant();
+ if (id != null)
+ info.Extras!.DiscID = id.ToUpperInvariant().Substring(0, 24) + "XXXXXXXX";
+ if (string.IsNullOrEmpty(info.Extras!.PIC) && !string.IsNullOrEmpty(pic))
+ {
+ pic = Regex.Replace(pic, ".{32}", "$0\n");
+ info.Extras.PIC = pic;
+ }
+ }
+
+ // Fill in any artifacts that exist, Base64-encoded, if we need to
+ if (includeArtifacts)
+ {
+ info.Artifacts ??= [];
+
+ if (File.Exists(getKeyBasePath + ".disc.pic"))
+ info.Artifacts["discpic"] = GetBase64(GetFullFile(getKeyBasePath + ".disc.pic", binary: true)) ?? string.Empty;
+ if (File.Exists(getKeyBasePath + ".getkey.log"))
+ info.Artifacts["getkeylog"] = GetBase64(GetFullFile(getKeyBasePath + ".getkey.log")) ?? string.Empty;
+ }
+ }
+
+ ///
+ public override List GetLogFilePaths(string basePath)
+ {
+ var logFiles = new List();
+ string? getKeyBasePath = GetCFWBasePath(basePath);
+
+ if (this.System != RedumpSystem.SonyPlayStation3)
+ return logFiles;
+
+ switch (this.Type)
+ {
+ case MediaType.BluRay:
+ if (File.Exists($"{getKeyBasePath}.getkey.log"))
+ logFiles.Add($"{getKeyBasePath}.getkey.log");
+ if (File.Exists($"{getKeyBasePath}.disc.pic"))
+ logFiles.Add($"{getKeyBasePath}.disc.pic");
+
+ break;
+ }
+
+ return logFiles;
+ }
+
+ #endregion
+
+ #region Information Extraction Methods
+
+ ///
+ /// Get a formatted datfile from the PS3 CFW output, if possible
+ ///
+ /// Path to ISO file
+ ///
+ private static Datafile? GeneratePS3CFWDatafile(string iso)
+ {
+ // If the ISO file doesn't exist, we can't get info from it
+ if (!File.Exists(iso))
+ return null;
+
+ try
+ {
+ if (Hashing.Hasher.GetFileHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
+ {
+ return new Datafile
+ {
+ Games = [new Game { Roms = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), Crc = crc, Md5 = md5, Sha1 = sha1, }] }]
+ };
+ }
+ return null;
+ }
+ catch
+ {
+ // We don't care what the exception is right now
+ return null;
+ }
+ }
+
+ // TODO: Don't hardcode 0x8320 and move this function to BaseParameters
+ ///
+ /// Get a isobuster-formatted PVD from a PS3 ISO, if possible
+ ///
+ /// Path to ISO file
+ /// Formatted PVD string, otherwise null
+ /// True if PVD was successfully parsed, otherwise false
+ private static bool GetPVD(string isoPath, out string? pvd)
+ {
+ pvd = null;
+ try
+ {
+ // Get PVD bytes from ISO file
+ var buf = new byte[96];
+ using (FileStream iso = File.OpenRead(isoPath))
+ {
+ iso.Seek(0x8320, SeekOrigin.Begin);
+
+ int offset = 0;
+ while (offset < 96)
+ {
+ int read = iso.Read(buf, offset, buf.Length - offset);
+ if (read == 0)
+ throw new EndOfStreamException();
+ offset += read;
+ }
+ }
+
+ // Format PVD to isobuster standard
+ char[] pvdCharArray = new char[96];
+ for (int i = 0; i < 96; i++)
+ {
+ if (buf[i] >= 0x20 && buf[i] <= 0x7E)
+ pvdCharArray[i] = (char)buf[i];
+ else
+ pvdCharArray[i] = '.';
+ }
+ string pvdASCII = new string(pvdCharArray, 0, 96);
+ pvd = string.Empty;
+ for (int i = 0; i < 96; i += 16)
+ {
+ pvd += $"{(0x0320+i):X4} : {buf[i]:X2} {buf[i+1]:X2} {buf[i+2]:X2} {buf[i+3]:X2} {buf[i+4]:X2} {buf[i+5]:X2} {buf[i+6]:X2} {buf[i+7]:X2} " +
+ $"{buf[i+8]:X2} {buf[i+9]:X2} {buf[i+10]:X2} {buf[i+11]:X2} {buf[i+12]:X2} {buf[i+13]:X2} {buf[i+14]:X2} {buf[i+15]:X2} {pvdASCII.Substring(i, 16)}\n";
+ }
+
+ return true;
+ }
+ catch
+ {
+ // We don't care what the error is
+ return false;
+ }
+ }
+
+ ///
+ /// Get a formatted datfile from the PS3 CFW output, if possible
+ ///
+ /// Path to ISO file
+ /// Formatted datfile, null if not valid
+ private static string? GetPS3CFWDatfile(string iso)
+ {
+ // If the files don't exist, we can't get info from it
+ if (!File.Exists(iso))
+ return null;
+
+ try
+ {
+ if (Hashing.Hasher.GetFileHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
+ return $"";
+ return null;
+ }
+ catch
+ {
+ // We don't care what the exception is right now
+ return null;
+ }
+ }
+
+ #endregion
+
+ #region Helper Functions
+
+ ///
+ /// Estimate the base filename of the .getkey.log file associated with the dump
+ ///
+ /// Path to ISO file
+ /// Base filename, null if not found
+ private string? GetCFWBasePath(string iso)
+ {
+ string? dir = Path.GetDirectoryName(iso);
+ dir ??= ".";
+
+ string[] files = Directory.GetFiles(dir, "*.getkey.log");
+
+ if (files.Length != 1)
+ return null;
+
+ return files[0].Substring(0, files[0].Length - 11);
+ }
+
+ #endregion
+ }
+}
diff --git a/MPF.Core/SubmissionInfoTool.cs b/MPF.Core/SubmissionInfoTool.cs
index 27eb9e704..d8cb393f0 100644
--- a/MPF.Core/SubmissionInfoTool.cs
+++ b/MPF.Core/SubmissionInfoTool.cs
@@ -453,7 +453,7 @@ internal static class SubmissionInfoTool
case RedumpSystem.SonyPlayStation3:
info.Extras!.DiscKey ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
- info.Extras.DiscID = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
+ info.Extras.DiscID ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.TomyKissSite:
diff --git a/MPF.Core/UI/ViewModels/CheckDumpViewModel.cs b/MPF.Core/UI/ViewModels/CheckDumpViewModel.cs
index 734d5b6a3..563ba1010 100644
--- a/MPF.Core/UI/ViewModels/CheckDumpViewModel.cs
+++ b/MPF.Core/UI/ViewModels/CheckDumpViewModel.cs
@@ -354,7 +354,7 @@ private void PopulateInternalPrograms()
InternalProgram internalProgram = this.Options.InternalProgram;
// Create a static list of supported Check programs, not everything
- var internalPrograms = new List { InternalProgram.Redumper, InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.CleanRip, InternalProgram.UmdImageCreator };
+ var internalPrograms = new List { InternalProgram.Redumper, InternalProgram.Aaru, InternalProgram.DiscImageCreator, InternalProgram.CleanRip, InternalProgram.PS3CFW, InternalProgram.UmdImageCreator };
InternalPrograms = internalPrograms.Select(ip => new Element(ip)).ToList();
// Select the current default dumping program
diff --git a/MPF.Core/Utilities/Tools.cs b/MPF.Core/Utilities/Tools.cs
index 3de2dbeca..441da9fa4 100644
--- a/MPF.Core/Utilities/Tools.cs
+++ b/MPF.Core/Utilities/Tools.cs
@@ -293,19 +293,19 @@ private static (string? tag, string? url) GetRemoteVersionAndUrl()
#endregion
- #region PlayStation 3
+ #region PlayStation 3 specific tools
///
/// Validates a getkey log to check for presence of valid PS3 key
///
/// Path to getkey log file
- /// Output 16 byte key, null if not valid
+ /// Output key string, null if not valid
+ /// Output disc ID string, null if not valid
+ /// Output PIC string, null if not valid
/// True if path to log file contains valid key, false otherwise
- public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]? id, out byte[]? pic)
+ public static bool ParseGetKeyLog(string? logPath, out string? key, out string? id, out string? pic)
{
- key = null;
- id = null;
- pic = null;
+ key = id = pic = null;
if (string.IsNullOrEmpty(logPath))
return false;
@@ -344,7 +344,7 @@ public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]?
return false;
// Convert Disc Key to byte array
- key = Tools.HexStringToByteArray(discKeyStr);
+ key = discKeyStr;
if (key == null)
return false;
@@ -366,7 +366,7 @@ public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]?
discIDStr = discIDStr.Substring(0, 24) + "00000001";
// Convert Disc ID to byte array
- id = Tools.HexStringToByteArray(discIDStr);
+ id = discIDStr;
if (id == null)
return false;
@@ -379,17 +379,17 @@ public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]?
// Get PIC from log
string discPICStr = "";
- for (int i = 0; i < 8; i++)
+ for (int i = 0; i < 9; i++)
discPICStr += sr.ReadLine();
if (discPICStr == null)
return false;
// Validate PIC from log
- if (discPICStr.Length != 256)
+ if (discPICStr.Length != 264)
return false;
// Convert PIC to byte array
- pic = Tools.HexStringToByteArray(discPICStr.Substring(0, 230));
+ pic = discPICStr;
if (pic == null)
return false;
@@ -412,6 +412,33 @@ public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]?
return true;
}
+ ///
+ /// Validates a getkey log to check for presence of valid PS3 key
+ ///
+ /// Path to getkey log file
+ /// Output 16 byte disc key, null if not valid
+ /// Output 16 byte disc ID, null if not valid
+ /// Output 230 byte PIC, null if not valid
+ /// True if path to log file contains valid key, false otherwise
+ public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]? id, out byte[]? pic)
+ {
+ key = id = pic = null;
+ if (ParseGetKeyLog(logPath, out string? keyString, out string? idString, out string? picString))
+ {
+ if (string.IsNullOrEmpty(keyString) || string.IsNullOrEmpty(idString) || string.IsNullOrEmpty(picString) || picString!.Length < 230)
+ return false;
+
+ key = Tools.HexStringToByteArray(keyString);
+ id = Tools.HexStringToByteArray(idString);
+ pic = Tools.HexStringToByteArray(picString.Substring(0, 230));
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
///
/// Validates a hexadecimal disc ID
///