using System; using System.ComponentModel; using System.IO; using MPF.Core.Utilities; #if NET6_0_OR_GREATER using LibIRD; #endif namespace MPF.Core.UI.ViewModels { /// /// Constructor /// public class CreateIRDViewModel : INotifyPropertyChanged { #region Fields /// /// Access to the current options /// public Data.Options Options { get => _options; } private readonly Data.Options _options; /// /// Indicates if SelectionChanged events can be executed /// public bool CanExecuteSelectionChanged { get; private set; } = false; /// public event PropertyChangedEventHandler? PropertyChanged; #endregion #region Properties /// /// Indicates the status of the cancel button /// public bool CancelButtonEnabled { get => _cancelButtonEnabled; set { _cancelButtonEnabled = value; TriggerPropertyChanged(nameof(CancelButtonEnabled)); } } private bool _cancelButtonEnabled; /// /// Indicates the status of the check dump button /// public bool CreateIRDButtonEnabled { get => _createIRDButtonEnabled; set { _createIRDButtonEnabled = value; TriggerPropertyChanged(nameof(CreateIRDButtonEnabled)); } } private bool _createIRDButtonEnabled; /// /// Current Create IRD status message /// public string CreateIRDStatus { get => _createIRDStatus; set { _createIRDStatus = value; TriggerPropertyChanged(nameof(CreateIRDStatus)); } } private string _createIRDStatus; /// /// Currently provided Disc ID /// public byte[]? DiscID { get => _discID; set { _discID = value; TriggerPropertyChanged(nameof(DiscID)); } } private byte[]? _discID; /// /// Current disc ID status message /// public string DiscIDStatus { get => _discIDStatus; set { _discIDStatus = value; TriggerPropertyChanged(nameof(DiscIDStatus)); } } private string _discIDStatus; /// /// Currently provided Disc ID string /// public string? DiscIDString { get => _discIDString; set { _discIDString = value; TriggerPropertyChanged(nameof(DiscIDString)); } } private string? _discIDString; /// /// Indicates the status of the disc ID text box /// public bool DiscIDTextBoxEnabled { get => _discIDTextBoxEnabled; set { _discIDTextBoxEnabled = value; TriggerPropertyChanged(nameof(DiscIDTextBoxEnabled)); } } private bool _discIDTextBoxEnabled; /// /// Currently provided hexadecimal key /// public string? HexKey { get => _hexKey; set { _hexKey = value; TriggerPropertyChanged(nameof(HexKey)); } } private string? _hexKey; /// /// Indicates the status of the hex key text box /// public bool HexKeyTextBoxEnabled { get => _hexKeyTextBoxEnabled; set { _hexKeyTextBoxEnabled = value; TriggerPropertyChanged(nameof(HexKeyTextBoxEnabled)); } } private bool _hexKeyTextBoxEnabled; /// /// Currently provided input path /// public string? InputPath { get => _inputPath; set { _inputPath = value; TriggerPropertyChanged(nameof(InputPath)); } } private string? _inputPath; /// /// Indicates the status of the input path browse button /// public bool InputPathBrowseButtonEnabled { get => _inputPathBrowseButtonEnabled; set { _inputPathBrowseButtonEnabled = value; TriggerPropertyChanged(nameof(InputPathBrowseButtonEnabled)); } } private bool _inputPathBrowseButtonEnabled; /// /// Indicates the status of the input path text box /// public bool InputPathTextBoxEnabled { get => _inputPathTextBoxEnabled; set { _inputPathTextBoxEnabled = value; TriggerPropertyChanged(nameof(InputPathTextBoxEnabled)); } } private bool _inputPathTextBoxEnabled; /// /// Currently provided key /// public byte[]? Key { get => _key; set { _key = value; TriggerPropertyChanged(nameof(Key)); } } private byte[]? _key; /// /// Currently provided key path /// public string? KeyPath { get => _keyPath; set { _keyPath = value; TriggerPropertyChanged(nameof(KeyPath)); } } private string? _keyPath; /// /// Indicates the status of the key path browse button /// public bool KeyPathBrowseButtonEnabled { get => _keyPathBrowseButtonEnabled; set { _keyPathBrowseButtonEnabled = value; TriggerPropertyChanged(nameof(KeyPathBrowseButtonEnabled)); } } private bool _keyPathBrowseButtonEnabled; /// /// Indicates the status of the key path text box /// public bool KeyPathTextBoxEnabled { get => _keyPathTextBoxEnabled; set { _keyPathTextBoxEnabled = value; TriggerPropertyChanged(nameof(KeyPathTextBoxEnabled)); } } private bool _keyPathTextBoxEnabled; /// /// Current key status message /// public string KeyStatus { get => _keyStatus; set { _keyStatus = value; TriggerPropertyChanged(nameof(KeyStatus)); } } private string _keyStatus; /// /// Currently provided layerbreak /// public long? Layerbreak { get => _layerbreak; set { _layerbreak = value; TriggerPropertyChanged(nameof(Layerbreak)); } } private long? _layerbreak; /// /// Currently provided layerbreak string /// public string? LayerbreakString { get => _layerbreakString; set { _layerbreakString = value; TriggerPropertyChanged(nameof(LayerbreakString)); } } private string? _layerbreakString; /// /// Indicates the status of the layerbreak text box /// public bool LayerbreakTextBoxEnabled { get => _layerbreakTextBoxEnabled; set { _layerbreakTextBoxEnabled = value; TriggerPropertyChanged(nameof(LayerbreakTextBoxEnabled)); } } private bool _layerbreakTextBoxEnabled; /// /// Currently provided .getkey.log path /// public string? LogPath { get => _logPath; set { _logPath = value; TriggerPropertyChanged(nameof(LogPath)); } } private string? _logPath; /// /// Indicates the status of the .getkey.log path browse button /// public bool LogPathBrowseButtonEnabled { get => _logPathBrowseButtonEnabled; set { _logPathBrowseButtonEnabled = value; TriggerPropertyChanged(nameof(LogPathBrowseButtonEnabled)); } } private bool _logPathBrowseButtonEnabled; /// /// Indicates whether a .getkey.log path is not provided /// public bool LogPathNotProvided { get => _logPathNotProvided; set { _logPathNotProvided = value; TriggerPropertyChanged(nameof(LogPathNotProvided)); } } private bool _logPathNotProvided; /// /// Indicates the status of the .getkey.log path text box /// public bool LogPathTextBoxEnabled { get => _logPathTextBoxEnabled; set { _logPathTextBoxEnabled = value; TriggerPropertyChanged(nameof(LogPathTextBoxEnabled)); } } private bool _logPathTextBoxEnabled; /// /// Currently provided PIC file path /// public string? PICPath { get => _picPath; set { _picPath = value; TriggerPropertyChanged(nameof(PICPath)); } } private string? _picPath; /// /// Indicates the status of the PIC file path browse button /// public bool PICPathBrowseButtonEnabled { get => _picPathBrowseButtonEnabled; set { _picPathBrowseButtonEnabled = value; TriggerPropertyChanged(nameof(PICPathBrowseButtonEnabled)); } } private bool _picPathBrowseButtonEnabled; /// /// Indicates the status of the PIC file path text box /// public bool PICPathTextBoxEnabled { get => _picPathTextBoxEnabled; set { _picPathTextBoxEnabled = value; TriggerPropertyChanged(nameof(PICPathTextBoxEnabled)); } } private bool _picPathTextBoxEnabled; /// /// Currently provided PIC /// public byte[]? PIC { get => _pic; set { _pic = value; TriggerPropertyChanged(nameof(PIC)); } } private byte[]? _pic; /// /// Current PIC status message /// public string PICStatus { get => _picStatus; set { _picStatus = value; TriggerPropertyChanged(nameof(PICStatus)); } } private string _picStatus; /// /// Currently provided PIC string /// public string? PICString { get => _picString; set { _picString = value; TriggerPropertyChanged(nameof(PICString)); } } private string? _picString; /// /// Indicates the status of the PIC text box /// public bool PICTextBoxEnabled { get => _picTextBoxEnabled; set { _picTextBoxEnabled = value; TriggerPropertyChanged(nameof(PICTextBoxEnabled)); } } private bool _picTextBoxEnabled; #endregion /// /// Constructor for pure view model /// public CreateIRDViewModel() { _options = OptionsLoader.LoadFromConfig(); _inputPath = string.Empty; _logPath = string.Empty; _keyPath = string.Empty; _hexKey = string.Empty; _key = null; //_keyStatus = "Will attempt to pull Encryption Key from redump.org"; _keyStatus = "Cannot create an IRD without a key"; _discID = null; _discIDString = string.Empty; _discIDStatus = "Unknown Disc ID, generating ID using Region: NONE"; _picPath = string.Empty; _layerbreakString = string.Empty; _picString = string.Empty; _pic = null; _picStatus = "Will generate a PIC assuming a Layerbreak of 12219392"; _createIRDStatus = "Please provide an ISO"; _inputPathTextBoxEnabled = true; _inputPathBrowseButtonEnabled = true; _logPathTextBoxEnabled = true; _logPathNotProvided = true; _logPathBrowseButtonEnabled = true; _discIDTextBoxEnabled = true; _keyPathTextBoxEnabled = true; _keyPathBrowseButtonEnabled = true; _hexKeyTextBoxEnabled = true; _picPathTextBoxEnabled = true; _picPathBrowseButtonEnabled = true; _picTextBoxEnabled = true; _layerbreakTextBoxEnabled = true; _createIRDButtonEnabled = false; _cancelButtonEnabled = true; EnableEventHandlers(); } #region Property Updates /// /// Trigger a property changed event /// private void TriggerPropertyChanged(string propertyName) { // Disable event handlers temporarily bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; DisableEventHandlers(); // If the property change event is initialized PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); // Reenable event handlers, if necessary if (cachedCanExecuteSelectionChanged) EnableEventHandlers(); } #endregion #region UI Commands /// /// Determine if the Create IRD button should be enabled /// private bool ShouldEnableCreateIRDButton() { if(string.IsNullOrEmpty(InputPath) || !File.Exists(InputPath)) { CreateIRDStatus = "Please provide an ISO"; return false; } if (string.IsNullOrEmpty(LogPath) && string.IsNullOrEmpty(HexKey) && string.IsNullOrEmpty(KeyPath)) { CreateIRDStatus = "Please provide a GetKey log or Disc Key"; return false; } CreateIRDStatus = "Ready to create IRD"; return true; } /// /// Determine if the Log Path TextBox and Browse Button should be enabled /// /// private bool ShouldEnableLogPath() { return string.IsNullOrEmpty(LogPath) && string.IsNullOrEmpty(HexKey) && string.IsNullOrEmpty(KeyPath) && string.IsNullOrEmpty(DiscIDString) && string.IsNullOrEmpty(PICString) && string.IsNullOrEmpty(PICPath) && Layerbreak == null; } /// /// Change the currently selected input path /// public void ChangeInputPath() { CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } /// /// Change the currently selected .getkey.log path /// public void ChangeLogPath() { if (string.IsNullOrEmpty(LogPath)) { // No .getkey.log file provided: Reset Key and PIC sections LogPathNotProvided = true; Key = null; //KeyStatus = "Will attempt to pull Encryption Key from redump.org"; // Use this when redump key pulling is implemented KeyStatus = "Cannot create an IRD without a key"; KeyPathTextBoxEnabled = true; KeyPathBrowseButtonEnabled = true; HexKeyTextBoxEnabled = true; DiscID = null; DiscIDStatus = "Unknown Disc ID, using Region: NONE"; DiscIDTextBoxEnabled = true; PIC = null; PICStatus = "Will generate a PIC assuming a Layerbreak of 12219392"; PICPathTextBoxEnabled = true; PICPathBrowseButtonEnabled = true; PICTextBoxEnabled = true; LayerbreakTextBoxEnabled = true; //CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); // Use this when redump key pulling is implemented CreateIRDStatus = "Please provide a GetKey log or Disc Key"; CreateIRDButtonEnabled = false; return; } // A .getkey.log path is provided: Disable Key and PIC sections LogPathNotProvided = false; KeyPathTextBoxEnabled = false; KeyPathBrowseButtonEnabled = false; HexKeyTextBoxEnabled = false; DiscIDTextBoxEnabled = false; PICPathTextBoxEnabled = false; PICPathBrowseButtonEnabled = false; PICTextBoxEnabled = false; LayerbreakTextBoxEnabled = false; if (ParseLog(LogPath, out byte[]? key, out byte[]? id, out byte[]? pic)) { Key = key; DiscID = id; PIC = pic; KeyStatus = $"Using key from file: {Path.GetFileName(LogPath)}"; DiscIDStatus = $"Using ID from file: {Path.GetFileName(LogPath)}"; PICStatus = $"Using PIC from file: {Path.GetFileName(LogPath)}"; CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } else { Key = null; DiscID = null; PIC = null; CreateIRDStatus = "Please provide a valid GetKey log file path"; if (File.Exists(LogPath)) { KeyStatus = "ERROR: Invalid *.getkey.log file"; DiscIDStatus = "ERROR: Invalid *.getkey.log file"; PICStatus = "ERROR: Invalid *.getkey.log file"; } else { KeyStatus = "ERROR: Invalid *.getkey.log path"; DiscIDStatus = "ERROR: Invalid *.getkey.log path"; PICStatus = "ERROR: Invalid *.getkey.log path"; } CreateIRDButtonEnabled = false; } } /// /// Change the currently selected disc ID /// public void ChangeDiscID() { if (string.IsNullOrEmpty(DiscIDString)) { DiscID = null; DiscIDStatus = "Unknown Disc ID, generating ID using Region: NONE"; LogPathTextBoxEnabled = ShouldEnableLogPath(); LogPathBrowseButtonEnabled = ShouldEnableLogPath(); CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); return; } LogPathTextBoxEnabled = false; LogPathBrowseButtonEnabled = false; byte[]? id = ParseDiscID(DiscIDString); if (id != null) { DiscID = id; DiscIDStatus = $"Using provided ID: {DiscIDString}"; CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } else { DiscID = null; DiscIDStatus = "ERROR: Invalid Disc ID"; CreateIRDStatus = "Please provide a valid Disc ID"; CreateIRDButtonEnabled = false; } } /// /// Change the currently selected key file path /// public void ChangeKeyPath() { if (string.IsNullOrEmpty(KeyPath)) { Key = null; //KeyStatus = "Will attempt to pull Encryption Key from redump.org"; // Use this when redump key pulling is implemented KeyStatus = "Cannot create an IRD without a key"; LogPathTextBoxEnabled = ShouldEnableLogPath(); LogPathBrowseButtonEnabled = ShouldEnableLogPath(); HexKeyTextBoxEnabled = true; //CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); // Use this when redump key pulling is implemented CreateIRDButtonEnabled = false; CreateIRDStatus = "Please provide a GetKey log or Disc Key"; // Remove this when redump key pulling is implemented return; } LogPathTextBoxEnabled = false; LogPathBrowseButtonEnabled = false; HexKeyTextBoxEnabled = false; byte[]? key = ParseKeyFile(KeyPath); if (key != null) { Key = key; KeyStatus = $"Using key from file: {Path.GetFileName(KeyPath)}"; CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } else { Key = null; if (File.Exists(KeyPath)) KeyStatus = "ERROR: Invalid *.key file"; else KeyStatus = "ERROR: Invalid *.key path"; CreateIRDButtonEnabled = false; CreateIRDStatus = "Please provide a valid key file path"; } } /// /// Change the currently selected hexadecimal key /// public void ChangeKey() { if (string.IsNullOrEmpty(HexKey)) { Key = null; //KeyStatus = "Will attempt to pull Encryption Key from redump.org"; // Use this when redump key pulling is implemented KeyStatus = "Cannot create an IRD without a key"; LogPathTextBoxEnabled = ShouldEnableLogPath(); LogPathBrowseButtonEnabled = ShouldEnableLogPath(); KeyPathTextBoxEnabled = true; KeyPathBrowseButtonEnabled = true; //CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); // Use this when redump key pulling is implemented CreateIRDButtonEnabled = false; CreateIRDStatus = "Please provide a GetKey log or Disc Key"; // Remove this when redump key pulling is implemented return; } LogPathTextBoxEnabled = false; LogPathBrowseButtonEnabled = false; KeyPathTextBoxEnabled = false; KeyPathBrowseButtonEnabled = false; byte[]? key = ParseHexKey(HexKey); if (key != null) { Key = key; KeyStatus = $"Using provided Key: {HexKey}"; CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } else { Key = null; KeyStatus = "ERROR: Invalid Key"; CreateIRDButtonEnabled = false; CreateIRDStatus = "Please provide a valid key"; } } /// /// Change the currently selected PIC file path /// public void ChangePICPath() { Layerbreak = null; if (string.IsNullOrEmpty(PICPath)) { PIC = null; PICStatus = "Will generate a PIC assuming a Layerbreak of 12219392"; PICTextBoxEnabled = true; LayerbreakTextBoxEnabled = true; LogPathTextBoxEnabled = ShouldEnableLogPath(); LogPathBrowseButtonEnabled = ShouldEnableLogPath(); CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); return; } LogPathTextBoxEnabled = false; LogPathBrowseButtonEnabled = false; PICTextBoxEnabled = false; LayerbreakTextBoxEnabled = false; PIC = ParsePICFile(PICPath); if (PIC != null) { PICStatus = $"Using PIC from file: {Path.GetFileName(PICPath)}"; CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } else { if (File.Exists(PICPath)) PICStatus = "ERROR: Invalid PIC file"; else PICStatus = "ERROR: Invalid PIC path"; CreateIRDButtonEnabled = false; CreateIRDStatus = "Please provide a valid PIC"; } } /// /// Change the currently selected PIC /// public void ChangePIC() { Layerbreak = null; if (string.IsNullOrEmpty(PICString)) { PIC = null; PICStatus = "Will generate a PIC assuming a Layerbreak of 12219392"; PICPathTextBoxEnabled = true; PICPathBrowseButtonEnabled = true; LayerbreakTextBoxEnabled = true; LogPathTextBoxEnabled = ShouldEnableLogPath(); LogPathBrowseButtonEnabled = ShouldEnableLogPath(); CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); return; } LogPathTextBoxEnabled = false; LogPathBrowseButtonEnabled = false; PICPathTextBoxEnabled = false; PICPathBrowseButtonEnabled = false; LayerbreakTextBoxEnabled = false; PIC = ParsePIC(PICString); if (PIC != null) { PICStatus = "Using provided PIC"; CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } else { PICStatus = "ERROR: Invalid PIC"; CreateIRDButtonEnabled = false; CreateIRDStatus = "Please provide a valid PIC"; } } /// /// Change the currently selected layerbreak /// public void ChangeLayerbreak() { PIC = null; if (string.IsNullOrEmpty(LayerbreakString)) { Layerbreak = null; PICStatus = "Will generate a PIC assuming a Layerbreak of 12219392"; PICPathTextBoxEnabled = true; PICPathBrowseButtonEnabled = true; PICTextBoxEnabled = true; LogPathTextBoxEnabled = ShouldEnableLogPath(); LogPathBrowseButtonEnabled = ShouldEnableLogPath(); CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); return; } LogPathTextBoxEnabled = false; LogPathBrowseButtonEnabled = false; PICPathTextBoxEnabled = false; PICPathBrowseButtonEnabled = false; PICTextBoxEnabled = false; Layerbreak = ParseLayerbreak(LayerbreakString); if (Layerbreak != null) { PICStatus = $"Will generate a PIC using a Layerbreak of {Layerbreak}"; CreateIRDButtonEnabled = ShouldEnableCreateIRDButton(); } else { PICStatus = "ERROR: Invalid Layerbreak"; CreateIRDButtonEnabled = false; CreateIRDStatus = "Please provide a valid Layerbreak value"; } } #endregion #region UI Functionality /// /// Enable all textbox and combobox event handlers /// private void EnableEventHandlers() { CanExecuteSelectionChanged = true; } /// /// Disable all textbox and combobox event handlers /// private void DisableEventHandlers() { CanExecuteSelectionChanged = false; } /// /// Resets all UI fields /// public void ResetFields() { InputPath = string.Empty; LogPath = string.Empty; KeyPath = string.Empty; HexKey = string.Empty; Key = null; //_keyStatus = "Will attempt to pull Encryption Key from redump.org"; KeyStatus = "Cannot create an IRD without a key"; DiscID = null; DiscIDString = string.Empty; DiscIDStatus = "Unknown Disc ID, generating ID using Region: NONE"; PICPath = string.Empty; LayerbreakString = string.Empty; PICString = string.Empty; PIC = null; PICStatus = "Will generate a PIC assuming a Layerbreak of 12219392"; CreateIRDStatus = "Please provide an ISO"; InputPathTextBoxEnabled = true; InputPathBrowseButtonEnabled = true; LogPathTextBoxEnabled = true; LogPathNotProvided = true; LogPathBrowseButtonEnabled = true; DiscIDTextBoxEnabled = true; KeyPathTextBoxEnabled = true; KeyPathBrowseButtonEnabled = true; HexKeyTextBoxEnabled = true; PICPathTextBoxEnabled = true; PICPathBrowseButtonEnabled = true; PICTextBoxEnabled = true; LayerbreakTextBoxEnabled = true; CreateIRDButtonEnabled = false; CancelButtonEnabled = true; } /// /// Disables all UI fields and returns a list of all their previous states /// /// public bool[] DisableUIFields() { bool[] oldValues = [ InputPathTextBoxEnabled, InputPathBrowseButtonEnabled, LogPathTextBoxEnabled, LogPathNotProvided, LogPathBrowseButtonEnabled, DiscIDTextBoxEnabled, KeyPathTextBoxEnabled, KeyPathBrowseButtonEnabled, HexKeyTextBoxEnabled, PICPathTextBoxEnabled, PICPathBrowseButtonEnabled, PICTextBoxEnabled, LayerbreakTextBoxEnabled, CreateIRDButtonEnabled, CancelButtonEnabled, ]; InputPathTextBoxEnabled = false; InputPathBrowseButtonEnabled = false; LogPathTextBoxEnabled = false; LogPathNotProvided = false; LogPathBrowseButtonEnabled = false; DiscIDTextBoxEnabled = false; KeyPathTextBoxEnabled = false; KeyPathBrowseButtonEnabled = false; HexKeyTextBoxEnabled = false; PICPathTextBoxEnabled = false; PICPathBrowseButtonEnabled = false; PICTextBoxEnabled = false; LayerbreakTextBoxEnabled = false; CreateIRDButtonEnabled = false; CancelButtonEnabled = false; return oldValues; } /// /// Re-enables all UI fields to their previous states /// /// public void ReenableUIFields(bool[] oldValues) { InputPathTextBoxEnabled = oldValues[0]; InputPathBrowseButtonEnabled = oldValues[1]; LogPathTextBoxEnabled = oldValues[2]; LogPathNotProvided = oldValues[3]; LogPathBrowseButtonEnabled = oldValues[4]; DiscIDTextBoxEnabled = oldValues[5]; KeyPathTextBoxEnabled = oldValues[6]; KeyPathBrowseButtonEnabled = oldValues[7]; HexKeyTextBoxEnabled = oldValues[8]; PICPathTextBoxEnabled = oldValues[9]; PICPathBrowseButtonEnabled = oldValues[10]; PICTextBoxEnabled = oldValues[11]; LayerbreakTextBoxEnabled = oldValues[12]; CreateIRDButtonEnabled = oldValues[13]; CancelButtonEnabled = oldValues[14]; } #endregion #region LibIRD /// /// Performs LibIRD functionality /// /// An error message if failed, otherwise string.Empty/null public string? CreateIRD(string outputPath) { if (string.IsNullOrEmpty(InputPath)) return "Invalid ISO path."; if (!File.Exists(InputPath!.Trim('"'))) return $"{InputPath!.Trim('"')} is not a valid ISO path."; // TODO: Implement pulling key from redump.org if (Key == null) return "Pulling key from redump.org is currently not implemented."; try { #if NET6_0_OR_GREATER // Create IRD ReIRD ird = new(InputPath, Key, Layerbreak); if (PIC != null) ird.PIC = PIC; if (DiscID != null && ird.DiscID[15] != 0x00) ird.DiscID = DiscID; ird.Write(outputPath); CreateIRDStatus = "IRD Created Successfully"; return string.Empty; #else return "LibIRD requires .NET Core 6 or greater."; #endif } catch (Exception e) { // Failed to create IRD, return error message CreateIRDStatus = "Failed to create IRD"; return e.Message; } } /// /// 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 } }