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 @@
-
+
+
+