From 02606318b0dd0d753fda62f2bc59df77e8569be0 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:55:10 +0900 Subject: [PATCH] Opt-in automatic IRD creation after PS3 dump (#655) * Opt-in automatic IRD creation after PS3 dump * Add tabs before an endregion * Prevent double checking for existing files * Spin off IRD creation into new thread --- CHANGELIST.md | 1 + MPF.Core/Data/Options.cs | 14 + MPF.Core/DumpEnvironment.cs | 13 + MPF.Core/InfoTool.cs | 52 +++ MPF.Core/UI/ViewModels/CreateIRDViewModel.cs | 330 +---------------- MPF.Core/UI/ViewModels/MainViewModel.cs | 82 ++--- MPF.Core/UI/ViewModels/OptionsViewModel.cs | 13 + MPF.Core/Utilities/Tools.cs | 354 +++++++++++++++++++ MPF.UI.Core/Windows/OptionsWindow.xaml | 7 +- 9 files changed, 504 insertions(+), 362 deletions(-) diff --git a/CHANGELIST.md b/CHANGELIST.md index 803a29f8..f38a1860 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -10,6 +10,7 @@ - Make AppVeyor builds framework-dependent - Fix misattributed artifact - Update README with current build instructions +- Opt-in automatic IRD creation after PS3 dump (Deterous) ### 3.1.1 (2024-02-20) diff --git a/MPF.Core/Data/Options.cs b/MPF.Core/Data/Options.cs index f8fdcd71..c7265a83 100644 --- a/MPF.Core/Data/Options.cs +++ b/MPF.Core/Data/Options.cs @@ -500,6 +500,20 @@ namespace MPF.Core.Data set { Settings["DeleteUnnecessaryFiles"] = value.ToString(); } } + /// + /// Create a PS3 IRD file after dumping PS3 BD-ROM discs + /// Always returns false if not compiled with .NET Core 6 or newer + /// + public bool CreateIRDAfterDumping + { +#if NET6_0_OR_GREATER + get { return GetBooleanSetting(Settings, "CreateIRDAfterDumping", false); } +#else + get { return false; } +#endif + set { Settings["CreateIRDAfterDumping"] = value.ToString(); } + } + #endregion #region Skip Options diff --git a/MPF.Core/DumpEnvironment.cs b/MPF.Core/DumpEnvironment.cs index 713318cf..8f528631 100644 --- a/MPF.Core/DumpEnvironment.cs +++ b/MPF.Core/DumpEnvironment.cs @@ -424,6 +424,19 @@ namespace MPF.Core resultProgress?.Report(Result.Failure(deleteResult)); } +#if NET6_0_OR_GREATER + // Create PS3 IRD, if required + if (Options.CreateIRDAfterDumping && System == RedumpSystem.SonyPlayStation3 && Type == MediaType.BluRay) + { + resultProgress?.Report(Result.Success("Creating IRD... please wait!")); + (bool deleteSuccess, string deleteResult) = await InfoTool.WriteIRD(OutputPath, submissionInfo?.Extras?.DiscKey, submissionInfo?.Extras?.DiscID, submissionInfo?.Extras?.PIC, submissionInfo?.SizeAndChecksums?.Layerbreak, submissionInfo?.SizeAndChecksums?.CRC32); + if (deleteSuccess) + resultProgress?.Report(Result.Success(deleteResult)); + else + resultProgress?.Report(Result.Failure(deleteResult)); + } +#endif + resultProgress?.Report(Result.Success("Submission information process complete!")); return Result.Success(); } diff --git a/MPF.Core/InfoTool.cs b/MPF.Core/InfoTool.cs index 1b6bc2b4..ff7c6464 100644 --- a/MPF.Core/InfoTool.cs +++ b/MPF.Core/InfoTool.cs @@ -1495,6 +1495,58 @@ namespace MPF.Core return files; } +#if NET6_0_OR_GREATER + /// + /// Create an IRD and write it to the specified output directory with optional filename suffix + /// + /// Output folder to write to + /// Optional suffix to append to the filename + /// Output filename to use as the base path + /// True on success, false on error + public static async Task<(bool, string)> WriteIRD(string isoPath, string? discKeyString, string? discIDString, string? picString, long? layerbreak, string? crc32) + { + try + { + // Output IRD file path + string irdPath = Path.ChangeExtension(isoPath, ".ird"); + + // Parse disc key from submission info (Required) + byte[]? discKey = Tools.ParseHexKey(discKeyString); + if (discKey == null) + return (false, "Failed to create IRD: No key provided"); + + // Parse Disc ID from submission info (Optional) + byte[]? discID = Tools.ParseDiscID(discIDString); + + // Parse PIC from submission info (Optional) + byte[]? pic = Tools.ParsePIC(picString); + + // Parse CRC32 strings into ISO hash for Unique ID field (Optional) + uint? uid = Tools.ParseCRC32(crc32); + + // Ensure layerbreak value is valid (Optional) + layerbreak = Tools.ParseLayerbreak(layerbreak); + + // Create Redump-style reproducible IRD + LibIRD.ReIRD ird = await Task.Run(() => new LibIRD.ReIRD(isoPath, discKey, layerbreak, uid)); + if (pic != null) + ird.PIC = pic; + if (discID != null && ird.DiscID[15] != 0x00) + ird.DiscID = discID; + + // Write IRD to file + ird.Write(irdPath); + + return (true, "IRD created!"); + } + catch (Exception) + { + // We don't care what the error is + return (false, "Failed to create IRD"); + } + } +#endif + #endregion #region Normalization diff --git a/MPF.Core/UI/ViewModels/CreateIRDViewModel.cs b/MPF.Core/UI/ViewModels/CreateIRDViewModel.cs index 07847da5..e009e251 100644 --- a/MPF.Core/UI/ViewModels/CreateIRDViewModel.cs +++ b/MPF.Core/UI/ViewModels/CreateIRDViewModel.cs @@ -2,9 +2,6 @@ using System.ComponentModel; using System.IO; using MPF.Core.Utilities; -#if NET6_0_OR_GREATER -using LibIRD; -#endif namespace MPF.Core.UI.ViewModels { @@ -628,7 +625,7 @@ namespace MPF.Core.UI.ViewModels PICTextBoxEnabled = false; LayerbreakTextBoxEnabled = false; - if (ParseLog(LogPath, out byte[]? key, out byte[]? id, out byte[]? pic)) + if (Tools.ParseGetKeyLog(LogPath, out byte[]? key, out byte[]? id, out byte[]? pic)) { Key = key; DiscID = id; @@ -678,7 +675,7 @@ namespace MPF.Core.UI.ViewModels LogPathTextBoxEnabled = false; LogPathBrowseButtonEnabled = false; - byte[]? id = ParseDiscID(DiscIDString); + byte[]? id = Tools.ParseDiscID(DiscIDString); if (id != null) { DiscID = id; @@ -717,7 +714,7 @@ namespace MPF.Core.UI.ViewModels LogPathBrowseButtonEnabled = false; HexKeyTextBoxEnabled = false; - byte[]? key = ParseKeyFile(KeyPath); + byte[]? key = Tools.ParseKeyFile(KeyPath); if (key != null) { Key = key; @@ -761,7 +758,7 @@ namespace MPF.Core.UI.ViewModels KeyPathTextBoxEnabled = false; KeyPathBrowseButtonEnabled = false; - byte[]? key = ParseHexKey(HexKey); + byte[]? key = Tools.ParseHexKey(HexKey); if (key != null) { Key = key; @@ -801,7 +798,7 @@ namespace MPF.Core.UI.ViewModels PICTextBoxEnabled = false; LayerbreakTextBoxEnabled = false; - PIC = ParsePICFile(PICPath); + PIC = Tools.ParsePICFile(PICPath); if (PIC != null) { PICStatus = $"Using PIC from file: {Path.GetFileName(PICPath)}"; @@ -844,7 +841,7 @@ namespace MPF.Core.UI.ViewModels PICPathBrowseButtonEnabled = false; LayerbreakTextBoxEnabled = false; - PIC = ParsePIC(PICString); + PIC = Tools.ParsePIC(PICString); if (PIC != null) { PICStatus = "Using provided PIC"; @@ -884,7 +881,7 @@ namespace MPF.Core.UI.ViewModels PICPathBrowseButtonEnabled = false; PICTextBoxEnabled = false; - Layerbreak = ParseLayerbreak(LayerbreakString); + Layerbreak = Tools.ParseLayerbreak(LayerbreakString); if (Layerbreak != null) { PICStatus = $"Will generate a PIC using a Layerbreak of {Layerbreak}"; @@ -1050,8 +1047,8 @@ namespace MPF.Core.UI.ViewModels try { #if NET6_0_OR_GREATER - // Create IRD - ReIRD ird = new(InputPath, Key, Layerbreak); + // Create Redump-style reproducible IRD + LibIRD.ReIRD ird = new(InputPath, Key, Layerbreak); if (PIC != null) ird.PIC = PIC; if (DiscID != null && ird.DiscID[15] != 0x00) @@ -1071,315 +1068,6 @@ namespace MPF.Core.UI.ViewModels } } - /// - /// 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 - /// True if path to log file contains valid key, false otherwise - private static bool ParseLog(string? logPath, out byte[]? key, out byte[]? id, out byte[]? pic) - { - key = null; - id = null; - pic = null; - - if (string.IsNullOrEmpty(logPath)) - return false; - - try - { - if (!File.Exists(logPath)) - return false; - - // Protect from attempting to read from really long files - FileInfo logFile = new(logPath); - if (logFile.Length > 65536) - return false; - - // Read from .getkey.log file - using StreamReader sr = File.OpenText(logPath); - - // Determine whether GetKey was successful - string? line; - while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("get_dec_key succeeded!") == false) ; - if (line == null) - return false; - - // Look for Disc Key in log - while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_key = ") == false) ; - // If end of file reached, no key found - if (line == null) - return false; - // Get Disc Key from log - string discKeyStr = line.Substring("disc_key = ".Length); - // Validate Disc Key from log - if (discKeyStr.Length != 32) - return false; - // Convert Disc Key to byte array - key = HexStringToByteArray(discKeyStr); - if (key == null) - return false; - - // Read Disc ID - while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_id = ") == false) ; - // If end of file reached, no ID found - if (line == null) - return false; - // Get Disc ID from log - string discIDStr = line.Substring("disc_id = ".Length); - // Validate Disc ID from log - if (discIDStr.Length != 32) - return false; - // Replace X's in Disc ID with 00000001 - discIDStr = discIDStr.Substring(0, 24) + "00000001"; - // Convert Disc ID to byte array - id = HexStringToByteArray(discIDStr); - if (id == null) - return false; - - // Look for PIC in log - while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("PIC:") == false) ; - // If end of file reached, no PIC found - if (line == null) - return false; - // Get PIC from log - string discPICStr = ""; - for (int i = 0; i < 8; i++) - discPICStr += sr.ReadLine(); - if (discPICStr == null) - return false; - // Validate PIC from log - if (discPICStr.Length != 256) - return false; - // Convert PIC to byte array - pic = HexStringToByteArray(discPICStr.Substring(0, 230)); - if (pic == null) - return false; - - // Double check for warnings in .getkey.log - while ((line = sr.ReadLine()) != null) - { - string t = line.Trim(); - if (t.StartsWith("WARNING")) - return false; - else if (t.StartsWith("SUCCESS")) - return true; - } - } - catch - { - // We are not concerned with the error - return false; - } - - return true; - } - - /// - /// Validates a hexadecimal disc ID - /// - /// String representing hexadecimal disc ID - /// True if string is a valid disc ID, false otherwise - private static byte[]? ParseDiscID(string? discID) - { - if (string.IsNullOrEmpty(discID)) - return null; - - string cleandiscID = discID!.Trim().Replace("\n", string.Empty); - - if (discID!.Length != 32) - return null; - - // Censor last 4 bytes by replacing with 0x00000001 - cleandiscID = cleandiscID.Substring(0, 24) + "00000001"; - - // Convert to byte array, null if invalid hex string - byte[]? id = HexStringToByteArray(cleandiscID); - - return id; - } - - /// - /// Validates a key file to check for presence of valid PS3 key - /// - /// Path to key file - /// Output 16 byte key, null if not valid - private static byte[]? ParseKeyFile(string? keyPath) - { - if (string.IsNullOrEmpty(keyPath)) - return null; - - // Try read from key file - try - { - if (!File.Exists(keyPath)) - return null; - - // Key file must be exactly 16 bytes long - FileInfo keyFile = new(keyPath); - if (keyFile.Length != 16) - return null; - byte[] key = new byte[16]; - - // Read 16 bytes from Key file - using FileStream fs = new(keyPath, FileMode.Open, FileAccess.Read); - using BinaryReader reader = new(fs); - int numBytes = reader.Read(key, 0, 16); - if (numBytes != 16) - return null; - - return key; - } - catch - { - // Not concerned with error - return null; - } - } - - /// - /// Validates a hexadecimal key - /// - /// String representing hexadecimal key - /// Output 16 byte key, null if not valid - private static byte[]? ParseHexKey(string? hexKey) - { - if (string.IsNullOrEmpty(hexKey)) - return null; - - string cleanHexKey = hexKey!.Trim().Replace("\n", string.Empty); - - if (hexKey!.Length != 32) - return null; - - // Convert to byte array, null if invalid hex string - byte[]? key = HexStringToByteArray(cleanHexKey); - - return key; - } - - /// - /// Validates a PIC file path - /// - /// Path to PIC file - /// Output PIC byte array, null if not valid - private static byte[]? ParsePICFile(string? picPath) - { - if (string.IsNullOrEmpty(picPath)) - return null; - - // Try read from PIC file - try - { - if (!File.Exists(picPath)) - return null; - - // PIC file must be at least 115 bytes long - FileInfo picFile = new(picPath); - if (picFile.Length < 115) - return null; - byte[] pic = new byte[115]; - - // Read 115 bytes from PIC file - using FileStream fs = new(picPath, FileMode.Open, FileAccess.Read); - using BinaryReader reader = new(fs); - int numBytes = reader.Read(pic, 0, 115); - if (numBytes != 115) - return null; - - // Validate that a PIC was read by checking first 6 bytes - if (pic[0] != 0x10 || - pic[1] != 0x02 || - pic[2] != 0x00 || - pic[3] != 0x00 || - pic[4] != 0x44 || - pic[5] != 0x49) - return null; - - return pic; - } - catch - { - // Not concerned with error - return null; - } - } - - /// - /// Validates a PIC - /// - /// String representing PIC - /// Output PIC byte array, null if not valid - private static byte[]? ParsePIC(string? inputPIC) - { - if (string.IsNullOrEmpty(inputPIC)) - return null; - - string cleanPIC = inputPIC!.Trim().Replace("\n", string.Empty); - - if (cleanPIC.Length < 230) - return null; - - // Convert to byte array, null if invalid hex string - byte[]? pic = HexStringToByteArray(cleanPIC); - - return pic; - } - - /// - /// Validates a layerbreak value (in sectors) - /// - /// String representing layerbreak value - /// Output layerbreak value, null if not valid - /// True if layerbreak is valid, false otherwise - private static long? ParseLayerbreak(string? inputLayerbreak) - { - if (string.IsNullOrEmpty(inputLayerbreak)) - return null; - - if (!long.TryParse(inputLayerbreak, out long layerbreak)) - return null; - - // Check that layerbreak is positive number and smaller than largest disc size (in sectors) - if (layerbreak <= 0 || layerbreak > 24438784) - return null; - - return layerbreak; - } - - #endregion - - #region Helper Functions - - /// - /// Converts a hex string into a byte array - /// - /// Hex string - /// Converted byte array, or null if invalid hex string - private static byte[]? HexStringToByteArray(string? hexString) - { - // Valid hex string must be an even number of characters - if (string.IsNullOrEmpty(hexString) || hexString!.Length % 2 == 1) - return null; - - // Convert ASCII to byte via lookup table - int[] hexLookup = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]; - byte[] byteArray = new byte[hexString.Length / 2]; - for (int i = 0; i < hexString.Length; i += 2) - { - // Convert next two chars to ASCII value relative to '0' - int a = Char.ToUpper(hexString[i]) - '0'; - int b = Char.ToUpper(hexString[i + 1]) - '0'; - // Ensure hex string only has '0' through '9' and 'A' through 'F' (case insensitive) - if ((a < 0 || b < 0 || a > 22 || b > 22) || (a > 10 && a < 17) || (b > 10 && b < 17)) - return null; - byteArray[i / 2] = (byte)(hexLookup[a] << 4 | hexLookup[b]); - } - - return byteArray; - } - #endregion } } diff --git a/MPF.Core/UI/ViewModels/MainViewModel.cs b/MPF.Core/UI/ViewModels/MainViewModel.cs index 06feb151..371dab0a 100644 --- a/MPF.Core/UI/ViewModels/MainViewModel.cs +++ b/MPF.Core/UI/ViewModels/MainViewModel.cs @@ -1708,8 +1708,8 @@ namespace MPF.Core.UI.ViewModels _environment.Drive?.RefreshDrive(); // Output to the label and log - this.Status = "Starting dumping process... Please wait!"; - LogLn("Starting dumping process... Please wait!"); + this.Status = "Starting dumping process... please wait!"; + LogLn("Starting dumping process... please wait!"); if (this.Options.ToolsInSeparateWindow) LogLn("Look for the separate command window for more details"); else @@ -1844,49 +1844,51 @@ namespace MPF.Core.UI.ViewModels return false; } } - - // If a complete dump exists from a different program - InternalProgram? programFound = null; - if (programFound == null && _environment.InternalProgram != InternalProgram.Aaru) + else { - Modules.Aaru.Parameters parameters = new("") + // If a complete dump exists from a different program + InternalProgram? programFound = null; + if (programFound == null && _environment.InternalProgram != InternalProgram.Aaru) { - Type = _environment.Type, - System = _environment.System - }; - (bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true); - if (foundOtherFiles) - programFound = InternalProgram.Aaru; - } - if (programFound == null && _environment.InternalProgram != InternalProgram.DiscImageCreator) - { - Modules.DiscImageCreator.Parameters parameters = new("") + Modules.Aaru.Parameters parameters = new("") + { + Type = _environment.Type, + System = _environment.System + }; + (bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true); + if (foundOtherFiles) + programFound = InternalProgram.Aaru; + } + if (programFound == null && _environment.InternalProgram != InternalProgram.DiscImageCreator) { - Type = _environment.Type, - System = _environment.System - }; - (bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true); - if (foundOtherFiles) - programFound = InternalProgram.DiscImageCreator; - } - if (programFound == null && _environment.InternalProgram != InternalProgram.Redumper) - { - Modules.Redumper.Parameters parameters = new("") + Modules.DiscImageCreator.Parameters parameters = new("") + { + Type = _environment.Type, + System = _environment.System + }; + (bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true); + if (foundOtherFiles) + programFound = InternalProgram.DiscImageCreator; + } + if (programFound == null && _environment.InternalProgram != InternalProgram.Redumper) { - Type = _environment.Type, - System = _environment.System - }; - (bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true); - if (foundOtherFiles) - programFound = InternalProgram.Redumper; - } - if (programFound != null && _displayUserMessage != null) - { - bool? mbresult = _displayUserMessage("Overwrite?", $"A complete dump from {programFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true); - if (mbresult != true) + Modules.Redumper.Parameters parameters = new("") + { + Type = _environment.Type, + System = _environment.System + }; + (bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true); + if (foundOtherFiles) + programFound = InternalProgram.Redumper; + } + if (programFound != null && _displayUserMessage != null) { - LogLn("Dumping aborted!"); - return false; + bool? mbresult = _displayUserMessage("Overwrite?", $"A complete dump from {programFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true); + if (mbresult != true) + { + LogLn("Dumping aborted!"); + return false; + } } } diff --git a/MPF.Core/UI/ViewModels/OptionsViewModel.cs b/MPF.Core/UI/ViewModels/OptionsViewModel.cs index 2a97fe39..32f963f3 100644 --- a/MPF.Core/UI/ViewModels/OptionsViewModel.cs +++ b/MPF.Core/UI/ViewModels/OptionsViewModel.cs @@ -42,6 +42,19 @@ namespace MPF.Core.UI.ViewModels /// public event PropertyChangedEventHandler? PropertyChanged; + /// + /// Used to indicate whether LibIRD is supported + /// Whether compiled with .NET Core 6 or greater + /// + public static bool CreateIRDSupported + { +#if NET6_0_OR_GREATER + get { return true; } +#else + get { return false; } +#endif + } + #endregion #region Lists diff --git a/MPF.Core/Utilities/Tools.cs b/MPF.Core/Utilities/Tools.cs index 033a7d6b..3de2dbec 100644 --- a/MPF.Core/Utilities/Tools.cs +++ b/MPF.Core/Utilities/Tools.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Net; using System.Reflection; using MPF.Core.Data; @@ -71,6 +72,36 @@ namespace MPF.Core.Utilities return true; } + /// + /// Converts a hex string into a byte array + /// + /// Hex string + /// Converted byte array, or null if invalid hex string + public static byte[]? HexStringToByteArray(string? hexString) + { + // Valid hex string must be an even number of characters + if (string.IsNullOrEmpty(hexString) || hexString!.Length % 2 == 1) + return null; + + // Convert ASCII to byte via lookup table + int[] hexLookup = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]; + byte[] byteArray = new byte[hexString.Length / 2]; + for (int i = 0; i < hexString.Length; i += 2) + { + // Convert next two chars to ASCII value relative to '0' + int a = Char.ToUpper(hexString[i]) - '0'; + int b = Char.ToUpper(hexString[i + 1]) - '0'; + + // Ensure hex string only has '0' through '9' and 'A' through 'F' (case insensitive) + if ((a < 0 || b < 0 || a > 22 || b > 22) || (a > 10 && a < 17) || (b > 10 && b < 17)) + return null; + byteArray[i / 2] = (byte)(hexLookup[a] << 4 | hexLookup[b]); + } + + return byteArray; + } + #endregion #region Support @@ -261,5 +292,328 @@ namespace MPF.Core.Utilities } #endregion + + #region PlayStation 3 + + /// + /// 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 + /// 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 = null; + id = null; + pic = null; + + if (string.IsNullOrEmpty(logPath)) + return false; + + try + { + if (!File.Exists(logPath)) + return false; + + // Protect from attempting to read from really long files + FileInfo logFile = new(logPath); + if (logFile.Length > 65536) + return false; + + // Read from .getkey.log file + using StreamReader sr = File.OpenText(logPath); + + // Determine whether GetKey was successful + string? line; + while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("get_dec_key succeeded!") == false) ; + if (line == null) + return false; + + // Look for Disc Key in log + while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_key = ") == false) ; + + // If end of file reached, no key found + if (line == null) + return false; + + // Get Disc Key from log + string discKeyStr = line.Substring("disc_key = ".Length); + + // Validate Disc Key from log + if (discKeyStr.Length != 32) + return false; + + // Convert Disc Key to byte array + key = Tools.HexStringToByteArray(discKeyStr); + if (key == null) + return false; + + // Read Disc ID + while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_id = ") == false) ; + + // If end of file reached, no ID found + if (line == null) + return false; + + // Get Disc ID from log + string discIDStr = line.Substring("disc_id = ".Length); + + // Validate Disc ID from log + if (discIDStr.Length != 32) + return false; + + // Replace X's in Disc ID with 00000001 + discIDStr = discIDStr.Substring(0, 24) + "00000001"; + + // Convert Disc ID to byte array + id = Tools.HexStringToByteArray(discIDStr); + if (id == null) + return false; + + // Look for PIC in log + while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("PIC:") == false) ; + + // If end of file reached, no PIC found + if (line == null) + return false; + + // Get PIC from log + string discPICStr = ""; + for (int i = 0; i < 8; i++) + discPICStr += sr.ReadLine(); + if (discPICStr == null) + return false; + + // Validate PIC from log + if (discPICStr.Length != 256) + return false; + + // Convert PIC to byte array + pic = Tools.HexStringToByteArray(discPICStr.Substring(0, 230)); + if (pic == null) + return false; + + // Double check for warnings in .getkey.log + while ((line = sr.ReadLine()) != null) + { + string t = line.Trim(); + if (t.StartsWith("WARNING")) + return false; + else if (t.StartsWith("SUCCESS")) + return true; + } + } + catch + { + // We are not concerned with the error + return false; + } + + return true; + } + + /// + /// Validates a hexadecimal disc ID + /// + /// String representing hexadecimal disc ID + /// True if string is a valid disc ID, false otherwise + public static byte[]? ParseDiscID(string? discID) + { + if (string.IsNullOrEmpty(discID)) + return null; + + string cleandiscID = discID!.Trim().Replace("\n", string.Empty); + + if (discID!.Length != 32) + return null; + + // Censor last 4 bytes by replacing with 0x00000001 + cleandiscID = cleandiscID.Substring(0, 24) + "00000001"; + + // Convert to byte array, null if invalid hex string + byte[]? id = Tools.HexStringToByteArray(cleandiscID); + + return id; + } + + /// + /// Validates a key file to check for presence of valid PS3 key + /// + /// Path to key file + /// Output 16 byte key, null if not valid + public static byte[]? ParseKeyFile(string? keyPath) + { + if (string.IsNullOrEmpty(keyPath)) + return null; + + // Try read from key file + try + { + if (!File.Exists(keyPath)) + return null; + + // Key file must be exactly 16 bytes long + FileInfo keyFile = new(keyPath); + if (keyFile.Length != 16) + return null; + byte[] key = new byte[16]; + + // Read 16 bytes from Key file + using FileStream fs = new(keyPath, FileMode.Open, FileAccess.Read); + using BinaryReader reader = new(fs); + int numBytes = reader.Read(key, 0, 16); + if (numBytes != 16) + return null; + + return key; + } + catch + { + // Not concerned with error + return null; + } + } + + /// + /// Validates a hexadecimal key + /// + /// String representing hexadecimal key + /// Output 16 byte key, null if not valid + public static byte[]? ParseHexKey(string? hexKey) + { + if (string.IsNullOrEmpty(hexKey)) + return null; + + string cleanHexKey = hexKey!.Trim().Replace("\n", string.Empty); + + if (cleanHexKey.Length != 32) + return null; + + // Convert to byte array, null if invalid hex string + byte[]? key = Tools.HexStringToByteArray(cleanHexKey); + + return key; + } + + /// + /// Validates a PIC file path + /// + /// Path to PIC file + /// Output PIC byte array, null if not valid + public static byte[]? ParsePICFile(string? picPath) + { + if (string.IsNullOrEmpty(picPath)) + return null; + + // Try read from PIC file + try + { + if (!File.Exists(picPath)) + return null; + + // PIC file must be at least 115 bytes long + FileInfo picFile = new(picPath); + if (picFile.Length < 115) + return null; + byte[] pic = new byte[115]; + + // Read 115 bytes from PIC file + using FileStream fs = new(picPath, FileMode.Open, FileAccess.Read); + using BinaryReader reader = new(fs); + int numBytes = reader.Read(pic, 0, 115); + if (numBytes != 115) + return null; + + // Validate that a PIC was read by checking first 6 bytes + if (pic[0] != 0x10 || + pic[1] != 0x02 || + pic[2] != 0x00 || + pic[3] != 0x00 || + pic[4] != 0x44 || + pic[5] != 0x49) + return null; + + return pic; + } + catch + { + // Not concerned with error + return null; + } + } + + /// + /// Validates a PIC + /// + /// String representing PIC + /// Output PIC byte array, null if not valid + public static byte[]? ParsePIC(string? inputPIC) + { + if (string.IsNullOrEmpty(inputPIC)) + return null; + + string cleanPIC = inputPIC!.Trim().Replace("\n", string.Empty); + + if (cleanPIC.Length < 230) + return null; + + // Convert to byte array, null if invalid hex string + byte[]? pic = Tools.HexStringToByteArray(cleanPIC.Substring(0, 230)); + + return pic; + } + + /// + /// Validates a string representing a layerbreak value (in sectors) + /// + /// String representing layerbreak value + /// Output layerbreak value, null if not valid + /// True if layerbreak is valid, false otherwise + public static long? ParseLayerbreak(string? inputLayerbreak) + { + if (string.IsNullOrEmpty(inputLayerbreak)) + return null; + + if (!long.TryParse(inputLayerbreak, out long layerbreak)) + return null; + + return ParseLayerbreak(layerbreak); + } + + /// + /// Validates a layerbreak value (in sectors) + /// + /// Number representing layerbreak value + /// Output layerbreak value, null if not valid + /// True if layerbreak is valid, false otherwise + public static long? ParseLayerbreak(long? layerbreak) + { + // Check that layerbreak is positive number and smaller than largest disc size (in sectors) + if (layerbreak <= 0 || layerbreak > 24438784) + return null; + + return layerbreak; + } + + /// + /// Converts a CRC32 hash hex string into uint32 representation + /// + /// Hex string representing CRC32 hash + /// Output CRC32 value, null if not valid + /// True if CRC32 hash string is valid, false otherwise + public static uint? ParseCRC32(string? inputCRC32) + { + if (string.IsNullOrEmpty(inputCRC32)) + return null; + + byte[]? crc32 = Tools.HexStringToByteArray(inputCRC32); + + if (crc32 == null || crc32.Length != 4) + return null; + + return (uint)(0x01000000 * crc32[0] + 0x00010000 * crc32[1] + 0x00000100 * crc32[2] + 0x00000001 * crc32[3]); + } + + #endregion } } diff --git a/MPF.UI.Core/Windows/OptionsWindow.xaml b/MPF.UI.Core/Windows/OptionsWindow.xaml index 2f0dbbde..b6892efd 100644 --- a/MPF.UI.Core/Windows/OptionsWindow.xaml +++ b/MPF.UI.Core/Windows/OptionsWindow.xaml @@ -191,7 +191,7 @@ - + + +