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 ///