using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Threading.Tasks; using BinaryObjectScanner; using MPF.Frontend.ComboBoxItems; using MPF.Frontend.Tools; using SabreTools.IO; using SabreTools.IO.Extensions; using SabreTools.RedumpLib.Data; using SabreTools.RedumpLib.Data.Sections; namespace MPF.Frontend.ViewModels { public class MainViewModel : INotifyPropertyChanged { #region Fields /// /// Access to the current options /// public Options Options { get => _options; set { _options = value; OptionsLoader.SaveToConfig(_options); } } private Options _options; /// /// Indicates if SelectionChanged events can be executed /// public bool CanExecuteSelectionChanged { get; private set; } = false; /// public event PropertyChangedEventHandler? PropertyChanged; /// /// Action to process logging statements /// private Action? _logger; /// /// Display a message to a user /// /// /// T1 - Title to display to the user /// T2 - Message to display to the user /// T3 - Number of default options to display /// T4 - true for inquiry, false otherwise /// TResult - true for positive, false for negative, null for neutral /// private Func? _displayUserMessage; /// /// Detected media type, distinct from the selected one /// private MediaType? _detectedMediaType; /// /// Current dumping environment /// private DumpEnvironment? _environment; /// /// Function to process user information /// private ProcessUserInfoDelegate? _processUserInfo; #endregion #region Properties /// /// Indicates the status of the check dump menu item /// public bool AskBeforeQuit { get => _askBeforeQuit; set { _askBeforeQuit = value; TriggerPropertyChanged(nameof(AskBeforeQuit)); } } private bool _askBeforeQuit; /// /// Indicates the status of the check dump menu item /// public bool CheckDumpMenuItemEnabled { get => _checkDumpMenuItemEnabled; set { _checkDumpMenuItemEnabled = value; TriggerPropertyChanged(nameof(CheckDumpMenuItemEnabled)); } } private bool _checkDumpMenuItemEnabled; /// /// Indicates the status of the create IRD menu item /// public bool CreateIRDMenuItemEnabled { get => _createIRDMenuItemEnabled; set { _createIRDMenuItemEnabled = value; TriggerPropertyChanged(nameof(CreateIRDMenuItemEnabled)); } } private bool _createIRDMenuItemEnabled; /// /// Indicates the status of the options menu item /// public bool OptionsMenuItemEnabled { get => _optionsMenuItemEnabled; set { _optionsMenuItemEnabled = value; TriggerPropertyChanged(nameof(OptionsMenuItemEnabled)); } } private bool _optionsMenuItemEnabled; /// /// Currently selected system value /// public RedumpSystem? CurrentSystem { get => _currentSystem; set { _currentSystem = value; TriggerPropertyChanged(nameof(CurrentSystem)); } } private RedumpSystem? _currentSystem; /// /// Indicates the status of the system type combo box /// public bool SystemTypeComboBoxEnabled { get => _systemTypeComboBoxEnabled; set { _systemTypeComboBoxEnabled = value; TriggerPropertyChanged(nameof(SystemTypeComboBoxEnabled)); } } private bool _systemTypeComboBoxEnabled; /// /// Currently selected media type value /// public MediaType? CurrentMediaType { get => _currentMediaType; set { _currentMediaType = value; TriggerPropertyChanged(nameof(CurrentMediaType)); } } private MediaType? _currentMediaType; /// /// Indicates the status of the media type combo box /// public bool MediaTypeComboBoxEnabled { get => _mediaTypeComboBoxEnabled; set { _mediaTypeComboBoxEnabled = value; TriggerPropertyChanged(nameof(MediaTypeComboBoxEnabled)); } } private bool _mediaTypeComboBoxEnabled; /// /// Currently provided output path /// Not guaranteed to be a valid path /// public string OutputPath { get => _outputPath; set { _outputPath = value; TriggerPropertyChanged(nameof(OutputPath)); } } private string _outputPath; /// /// Indicates the status of the output path text box /// public bool OutputPathTextBoxEnabled { get => _outputPathTextBoxEnabled; set { _outputPathTextBoxEnabled = value; TriggerPropertyChanged(nameof(OutputPathTextBoxEnabled)); } } private bool _outputPathTextBoxEnabled; /// /// Indicates the status of the output path browse button /// public bool OutputPathBrowseButtonEnabled { get => _outputPathBrowseButtonEnabled; set { _outputPathBrowseButtonEnabled = value; TriggerPropertyChanged(nameof(OutputPathBrowseButtonEnabled)); } } private bool _outputPathBrowseButtonEnabled; /// /// Currently selected drive value /// public Drive? CurrentDrive { get => _currentDrive; set { _currentDrive = value; TriggerPropertyChanged(nameof(CurrentDrive)); } } private Drive? _currentDrive; /// /// Indicates the status of the drive combo box /// public bool DriveLetterComboBoxEnabled { get => _driveLetterComboBoxEnabled; set { _driveLetterComboBoxEnabled = value; TriggerPropertyChanged(nameof(DriveLetterComboBoxEnabled)); } } private bool _driveLetterComboBoxEnabled; /// /// Currently selected drive speed value /// public int DriveSpeed { get => _driveSpeed; set { _driveSpeed = value; TriggerPropertyChanged(nameof(DriveSpeed)); } } private int _driveSpeed; /// /// Indicates the status of the drive speed combo box /// public bool DriveSpeedComboBoxEnabled { get => _driveSpeedComboBoxEnabled; set { _driveSpeedComboBoxEnabled = value; TriggerPropertyChanged(nameof(DriveSpeedComboBoxEnabled)); } } private bool _driveSpeedComboBoxEnabled; /// /// Currently selected dumping program /// public InternalProgram CurrentProgram { get => _currentProgram; set { _currentProgram = value; TriggerPropertyChanged(nameof(CurrentProgram)); } } private InternalProgram _currentProgram; /// /// Indicates the status of the dumping program combo box /// public bool DumpingProgramComboBoxEnabled { get => _dumpingProgramComboBoxEnabled; set { _dumpingProgramComboBoxEnabled = value; TriggerPropertyChanged(nameof(DumpingProgramComboBoxEnabled)); } } private bool _dumpingProgramComboBoxEnabled; /// /// Currently provided parameters /// public string Parameters { get => _parameters; set { _parameters = value; TriggerPropertyChanged(nameof(Parameters)); } } private string _parameters; /// /// Indicates the status of the parameters text box /// public bool ParametersTextBoxEnabled { get => _parametersTextBoxEnabled; set { _parametersTextBoxEnabled = value; TriggerPropertyChanged(nameof(ParametersTextBoxEnabled)); } } private bool _parametersTextBoxEnabled; /// /// Indicates the status of the parameters check box /// public bool ParametersCheckBoxEnabled { get => _parametersCheckBoxEnabled; set { _parametersCheckBoxEnabled = value; TriggerPropertyChanged(nameof(ParametersCheckBoxEnabled)); } } private bool _parametersCheckBoxEnabled; /// /// Indicates the status of the parameters check box /// public bool EnableParametersCheckBoxEnabled { get => _enableParametersCheckBoxEnabled; set { _enableParametersCheckBoxEnabled = value; TriggerPropertyChanged(nameof(EnableParametersCheckBoxEnabled)); } } private bool _enableParametersCheckBoxEnabled; /// /// Indicates the status of the start/stop button /// public bool StartStopButtonEnabled { get => _startStopButtonEnabled; set { _startStopButtonEnabled = value; TriggerPropertyChanged(nameof(StartStopButtonEnabled)); } } private bool _startStopButtonEnabled; /// /// Current value for the start/stop dumping button /// public object StartStopButtonText { get => _startStopButtonText; set { _startStopButtonText = (value as string) ?? string.Empty; TriggerPropertyChanged(nameof(StartStopButtonText)); } } private string _startStopButtonText; /// /// Indicates the status of the media scan button /// public bool MediaScanButtonEnabled { get => _mediaScanButtonEnabled; set { _mediaScanButtonEnabled = value; TriggerPropertyChanged(nameof(MediaScanButtonEnabled)); } } private bool _mediaScanButtonEnabled; /// /// Indicates the status of the update volume label button /// public bool UpdateVolumeLabelEnabled { get => _updateVolumeLabelEnabled; set { _updateVolumeLabelEnabled = value; TriggerPropertyChanged(nameof(UpdateVolumeLabelEnabled)); } } private bool _updateVolumeLabelEnabled; /// /// Indicates the status of the copy protect scan button /// public bool CopyProtectScanButtonEnabled { get => _copyProtectScanButtonEnabled; set { _copyProtectScanButtonEnabled = value; TriggerPropertyChanged(nameof(CopyProtectScanButtonEnabled)); } } private bool _copyProtectScanButtonEnabled; /// /// Currently displayed status /// public string Status { get => _status; set { _status = value; TriggerPropertyChanged(nameof(Status)); } } private string _status; /// /// Indicates the status of the log panel /// public bool LogPanelExpanded { get => _logPanelExpanded; set { _logPanelExpanded = value; TriggerPropertyChanged(nameof(LogPanelExpanded)); } } private bool _logPanelExpanded; #endregion #region List Properties /// /// Current list of drives /// public List Drives { get => _drives; set { _drives = value; TriggerPropertyChanged(nameof(Drives)); } } private List _drives; /// /// Current list of drive speeds /// public List DriveSpeeds { get => _driveSpeeds; set { _driveSpeeds = value; TriggerPropertyChanged(nameof(DriveSpeeds)); } } private List _driveSpeeds; /// /// Current list of supported media types /// public List>? MediaTypes { get => _mediaTypes; set { _mediaTypes = value; TriggerPropertyChanged(nameof(MediaTypes)); } } private List>? _mediaTypes; /// /// Current list of supported system profiles /// public List Systems { get => _systems; set { _systems = value; TriggerPropertyChanged(nameof(Systems)); } } private List _systems; /// /// List of available internal programs /// public List> InternalPrograms { get => _internalPrograms; set { _internalPrograms = value; TriggerPropertyChanged(nameof(InternalPrograms)); } } private List> _internalPrograms; #endregion #region Strings private const string DiscNotDetectedValue = "Disc Not Detected"; private string StartDumpingValue = "Start Dumping"; private string StopDumpingValue = "Stop Dumping"; #endregion /// /// Generic constructor /// public MainViewModel() { _options = OptionsLoader.LoadFromConfig(); // Added to clear warnings, all are set externally _drives = []; _driveSpeeds = []; _internalPrograms = []; _outputPath = string.Empty; _parameters = string.Empty; _startStopButtonText = string.Empty; _status = string.Empty; _systems = []; AskBeforeQuit = false; OptionsMenuItemEnabled = true; CheckDumpMenuItemEnabled = true; CreateIRDMenuItemEnabled = true; SystemTypeComboBoxEnabled = true; MediaTypeComboBoxEnabled = true; OutputPathTextBoxEnabled = true; OutputPathBrowseButtonEnabled = true; DriveLetterComboBoxEnabled = true; DumpingProgramComboBoxEnabled = true; StartStopButtonEnabled = true; StartStopButtonText = StartDumpingValue; MediaScanButtonEnabled = true; ParametersCheckBoxEnabled = true; EnableParametersCheckBoxEnabled = true; LogPanelExpanded = _options.GUI.OpenLogWindowAtStartup; MediaTypes = []; Systems = RedumpSystemComboBoxItem.GenerateElements(); InternalPrograms = []; } /// /// Initialize the main window after loading /// public void Init( Action loggerAction, Func displayUserMessage, ProcessUserInfoDelegate processUserInfo) { // Set the callbacks _logger = loggerAction; _displayUserMessage = displayUserMessage; _processUserInfo = processUserInfo; // Finish initializing the rest of the values InitializeUIValues(removeEventHandlers: false, rebuildPrograms: true, rescanDrives: true); } #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 Population /// /// Get a complete list of active disc drives and fill the combo box /// /// TODO: Find a way for this to periodically run, or have it hook to a "drive change" event private void PopulateDrives() { // Disable other UI updates bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; DisableEventHandlers(); VerboseLogLn("Scanning for drives.."); // Always enable the media scan MediaScanButtonEnabled = true; UpdateVolumeLabelEnabled = true; // If we have a selected drive, keep track of it char? lastSelectedDrive = CurrentDrive?.Name?[0] ?? null; // Populate the list of drives and add it to the combo box Drives = Drive.CreateListOfDrives(Options.GUI.IgnoreFixedDrives); if (Drives.Count > 0) { VerboseLogLn($"Found {Drives.Count} drives: {string.Join(", ", [.. Drives.ConvertAll(d => d.Name)])}"); // Check for the last selected drive, if possible int index = -1; if (lastSelectedDrive is not null) index = Drives.FindIndex(d => d.MarkedActive && (d.Name?[0] ?? '\0') == lastSelectedDrive); // Check for active optical drives if (index == -1) index = Drives.FindIndex(d => d.MarkedActive && d.InternalDriveType == InternalDriveType.Optical); // Check for active floppy drives if (index == -1) index = Drives.FindIndex(d => d.MarkedActive && d.InternalDriveType == InternalDriveType.Floppy); // Check for any active drives if (index == -1) index = Drives.FindIndex(d => d.MarkedActive); // Set the selected index CurrentDrive = index != -1 ? Drives[index] : Drives[0]; Status = "Valid drive found!"; CopyProtectScanButtonEnabled = true; // Get the current system type DetermineSystemType(); // Only enable the start/stop if we don't have the default selected StartStopButtonEnabled = ShouldEnableDumpingButton(); } else { VerboseLogLn("Found no drives"); CurrentDrive = null; Status = "No valid drive found!"; StartStopButtonEnabled = false; CopyProtectScanButtonEnabled = false; } // Reenable event handlers, if necessary if (cachedCanExecuteSelectionChanged) EnableEventHandlers(); } /// /// Populate media type according to system type /// private void PopulateMediaType() { // Disable other UI updates bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; DisableEventHandlers(); if (CurrentSystem is not null) { var mediaTypeValues = CurrentSystem.MediaTypes(); int index = mediaTypeValues.FindIndex(m => m == CurrentMediaType); if (CurrentMediaType is not null && index == -1) VerboseLogLn($"Disc of type '{CurrentMediaType.LongName()}' found, but the current system does not support it!"); MediaTypes = mediaTypeValues.ConvertAll(m => new Element(m ?? MediaType.NONE)); MediaTypeComboBoxEnabled = MediaTypes.Count > 1; CurrentMediaType = index > -1 ? MediaTypes[index] : MediaTypes[0]; } else { MediaTypeComboBoxEnabled = false; MediaTypes = null; CurrentMediaType = null; } // Reenable event handlers, if necessary if (cachedCanExecuteSelectionChanged) EnableEventHandlers(); } /// /// Populate media type according to system type /// private void PopulateInternalPrograms() { // Disable other UI updates bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; DisableEventHandlers(); // Create a static list of supported programs, not everything #if NET5_0_OR_GREATER var ipArr = Enum.GetValues(); #else var ipArr = (InternalProgram[])Enum.GetValues(typeof(InternalProgram)); #endif ipArr = Array.FindAll(ipArr, ip => InternalProgramExists(ip)); InternalPrograms = [.. Array.ConvertAll(ipArr, ip => new Element(ip))]; // Get the current internal program InternalProgram internalProgram = Options.InternalProgram; // Select the current default dumping program if (InternalPrograms.Count == 0) { // If no programs are found, default to InternalProgram.NONE CurrentProgram = InternalProgram.NONE; } else { int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram); CurrentProgram = currentIndex > -1 ? InternalPrograms[currentIndex].Value : InternalPrograms[0].Value; } // Reenable event handlers, if necessary if (cachedCanExecuteSelectionChanged) EnableEventHandlers(); } #endregion #region UI Commands /// /// Change the currently selected dumping program /// public void ChangeDumpingProgram() { VerboseLogLn($"Changed dumping program to: {((InternalProgram?)CurrentProgram).LongName()}"); EnsureMediaInformation(); // New output name depends on new environment GetOutputNames(false); // New environment depends on new output name EnsureMediaInformation(); } /// /// Change the currently selected media type /// public void ChangeMediaType(System.Collections.IList removedItems, System.Collections.IList addedItems) { // Only change the media type if the selection and not the list has changed if ((removedItems is null || removedItems.Count == 1) && (addedItems is null || addedItems.Count == 1)) SetSupportedDriveSpeed(); GetOutputNames(false); EnsureMediaInformation(); } /// /// Change the currently selected system /// public void ChangeSystem() { VerboseLogLn($"Changed system to: {CurrentSystem.LongName()}"); PopulateMediaType(); GetOutputNames(false); EnsureMediaInformation(); } /// /// Check for available updates /// public void CheckForUpdates(out bool different, out string message, out string? url) { FrontendTool.CheckForNewVersion(out different, out message, out url); SecretLogLn(message); if (url is null) message = "An exception occurred while checking for remote versions. See the log window for more details."; } /// /// Build a dummy SubmissionInfo /// public static SubmissionInfo CreateDebugSubmissionInfo() { return new SubmissionInfo() { SchemaVersion = 1, FullyMatchedID = 3, PartiallyMatchedIDs = [0, 1, 2, 3], Added = DateTime.UtcNow, LastModified = DateTime.UtcNow, CommonDiscInfo = new CommonDiscInfoSection() { System = RedumpSystem.IBMPCcompatible, Media = DiscType.BD128, Title = "Game Title", ForeignTitleNonLatin = "Foreign Game Title", DiscNumberLetter = "1", DiscTitle = "Install Disc", Category = DiscCategory.Games, Region = Region.World, Languages = [Language.English, Language.Spanish, Language.French], LanguageSelection = [LanguageSelection.BiosSettings], Serial = "Disc Serial", Layer0MasteringRing = "L0 Mastering Ring", Layer0MasteringSID = "L0 Mastering SID", Layer0ToolstampMasteringCode = "L0 Toolstamp", Layer0MouldSID = "L0 Mould SID", Layer0AdditionalMould = "L0 Additional Mould", Layer1MasteringRing = "L1 Mastering Ring", Layer1MasteringSID = "L1 Mastering SID", Layer1ToolstampMasteringCode = "L1 Toolstamp", Layer1MouldSID = "L1 Mould SID", Layer1AdditionalMould = "L1 Additional Mould", Layer2MasteringRing = "L2 Mastering Ring", Layer2MasteringSID = "L2 Mastering SID", Layer2ToolstampMasteringCode = "L2 Toolstamp", Layer3MasteringRing = "L3 Mastering Ring", Layer3MasteringSID = "L3 Mastering SID", Layer3ToolstampMasteringCode = "L3 Toolstamp", RingWriteOffset = "+12", Barcode = "UPC Barcode", EXEDateBuildDate = "19xx-xx-xx", ErrorsCount = "0", Comments = "Comment data line 1\r\nComment data line 2", CommentsSpecialFields = new Dictionary() { [SiteCode.ISBN] = "ISBN", [SiteCode.RingNonZeroDataStart] = "+123", [SiteCode.RingPerfectAudioOffset] = "+0" }, Contents = "Special contents 1\r\nSpecial contents 2", ContentsSpecialFields = new Dictionary() { [SiteCode.PlayableDemos] = "Game Demo 1", }, }, VersionAndEditions = new VersionAndEditionsSection() { Version = "Original", VersionDatfile = "Alt", CommonEditions = ["Taikenban"], OtherEditions = "Rerelease", }, EDC = new EDCSection() { EDC = YesNo.Yes, }, ParentCloneRelationship = new ParentCloneRelationshipSection() { ParentID = "12345", RegionalParent = false, }, Extras = new ExtrasSection() { PVD = "0320 : 20 20 20 20 20 20 20 20 20 20 20 20 20 32 30 31 201\n0330 : 30 31 30 32 35 31 36 31 39 30 30 30 00 04 32 30 010251619000 .20\n0340 : 31 30 31 30 32 35 31 36 31 39 30 30 30 00 04 30 1010251619000 .0\n0350 : 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 00 000000000000000 \n0360 : 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0000000000000000\n0370 : 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 . ", DiscKey = "Disc key", DiscID = "Disc ID", PIC = "10020000444901080000200042444F01\n1101010001000000004F947F00100000\n004F947E000000000000000000000000\n00000000000000000000000000000000\n00000000000000000000000000000000\n00000000000000000000000000000000\n00000000000000000000000000000000\n00000000000000000000000000000000\n00000000", Header = "Header", BCA = "BCA", SecuritySectorRanges = "SSv1 Ranges", }, CopyProtection = new CopyProtectionSection() { AntiModchip = YesNo.Yes, LibCrypt = YesNo.No, LibCryptData = "LibCrypt data", Protection = "List of protections", SecuROMData = "SecuROM data", }, DumpersAndStatus = new DumpersAndStatusSection() { Status = DumpStatus.TwoOrMoreGreen, Dumpers = ["Dumper1", "Dumper2"], OtherDumpers = "Dumper3", }, TracksAndWriteOffsets = new TracksAndWriteOffsetsSection() { ClrMameProData = "Datfile", Cuesheet = "Cuesheet", CommonWriteOffsets = [0, 12, -12], OtherWriteOffsets = "-2", }, SizeAndChecksums = new SizeAndChecksumsSection() { Layerbreak = 0, Layerbreak2 = 1, Layerbreak3 = 2, Size = 12345, CRC32 = "CRC32", MD5 = "MD5", SHA1 = "SHA1", }, DumpingInfo = new DumpingInfoSection() { DumpingProgram = "DiscImageCreator 20500101", DumpingDate = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"), Manufacturer = "ATAPI", Model = "Optical Drive", Firmware = "1.23", ReportedDiscType = "CD-R", }, Artifacts = new Dictionary() { ["Sample Artifact"] = "Sample Data", }, }; } /// /// Toggle the Start/Stop button /// public void ToggleStartStop() { // Dump or stop the dump if ((StartStopButtonText as string) == StartDumpingValue) { StartDumping(); } else if ((StartStopButtonText as string) == StopDumpingValue) { VerboseLogLn("Canceling dumping process..."); _environment?.CancelDumping(); CopyProtectScanButtonEnabled = true; } } /// /// Update the internal options from a closed OptionsWindow /// /// Indicates if the settings were saved or not /// Options representing the new, saved values public void UpdateOptions(bool savedSettings, Options? newOptions) { // Get which options to save var optionsToSave = savedSettings ? newOptions : Options; // Ensure the first run flag is unset var continuingOptions = new Options(optionsToSave) { FirstRun = false }; Options = new Options(continuingOptions); // If settings were changed, reinitialize the UI if (savedSettings) InitializeUIValues(removeEventHandlers: true, rebuildPrograms: true, rescanDrives: true); } #endregion #region UI Functionality /// /// Performs UI value setup end to end /// /// Whether event handlers need to be removed first /// Whether the available program list should be rebuilt /// Whether drives should be rescanned or not public void InitializeUIValues(bool removeEventHandlers, bool rebuildPrograms, bool rescanDrives) { // Disable the dumping button StartStopButtonEnabled = false; // Safely check the parameters box, just in case if (!ParametersCheckBoxEnabled) { bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; DisableEventHandlers(); ParametersCheckBoxEnabled = true; if (cachedCanExecuteSelectionChanged) EnableEventHandlers(); } // Remove event handlers to ensure ordering if (removeEventHandlers) DisableEventHandlers(); // Populate the list of drives and determine the system if (rescanDrives) { Status = "Creating drive list, please wait!"; PopulateDrives(); } else { DetermineSystemType(); } // Determine current media type, if possible PopulateMediaType(); CacheCurrentDiscType(); SetCurrentDiscType(); // Set the dumping program if (rebuildPrograms) PopulateInternalPrograms(); // Set the initial environment and UI values SetSupportedDriveSpeed(); _environment = DetermineEnvironment(); GetOutputNames(true); EnsureMediaInformation(); // Enable event handlers EnableEventHandlers(); // Enable the dumping button, if necessary StartStopButtonEnabled = ShouldEnableDumpingButton(); } /// /// Performs a fast update of the output path while skipping disc checks /// /// Whether event handlers need to be removed first public void FastUpdateLabel(bool removeEventHandlers) { // Disable the dumping button StartStopButtonEnabled = false; // Safely check the parameters box, just in case if (!ParametersCheckBoxEnabled) { bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; DisableEventHandlers(); ParametersCheckBoxEnabled = true; if (cachedCanExecuteSelectionChanged) EnableEventHandlers(); } // Remove event handlers to ensure ordering if (removeEventHandlers) DisableEventHandlers(); // Refresh the drive info CurrentDrive?.RefreshDrive(); // Set the initial environment and UI values _environment = DetermineEnvironment(); GetOutputNames(true); EnsureMediaInformation(); // Enable event handlers EnableEventHandlers(); // Enable the dumping button, if necessary StartStopButtonEnabled = ShouldEnableDumpingButton(); } /// /// Enable all textbox and combobox event handlers /// private void EnableEventHandlers() { CanExecuteSelectionChanged = true; } /// /// Disable all textbox and combobox event handlers /// private void DisableEventHandlers() { CanExecuteSelectionChanged = false; } #endregion #region Logging /// /// Enqueue text to the log /// /// Text to write to the log private void Log(string text) { _logger?.Invoke(LogLevel.USER_GENERIC, text); } /// /// Enqueue text with a newline to the log /// /// Text to write to the log private void LogLn(string text) => Log(text + "\n"); /// /// Enqueue error text to the log /// /// Text to write to the log private void ErrorLog(string text) { _logger?.Invoke(LogLevel.ERROR, text); } /// /// Enqueue error text with a newline to the log /// /// Text to write to the log private void ErrorLogLn(string text) => ErrorLog(text + "\n"); /// /// Enqueue secret text to the log /// /// Text to write to the log private void SecretLog(string text) { _logger?.Invoke(LogLevel.SECRET, text); } /// /// Enqueue secret text with a newline to the log /// /// Text to write to the log private void SecretLogLn(string text) => SecretLog(text + "\n"); /// /// Enqueue success text to the log /// /// Text to write to the log private void SuccessLog(string text) { _logger?.Invoke(LogLevel.USER_SUCCESS, text); } /// /// Enqueue text with a newline to the log /// /// Text to write to the log private void SuccessLogLn(string text) => SuccessLog(text + "\n"); /// /// Enqueue verbose text to the log /// /// Text to write to the log private void VerboseLog(string text) { if (_logger is not null && Options.VerboseLogging) _logger(LogLevel.VERBOSE, text); } /// /// Enqueue verbose text with a newline to the log /// /// Text to write to the log private void VerboseLogLn(string text) { if (Options.VerboseLogging) VerboseLog(text + "\n"); } #endregion #region Helpers /// /// Cache the current disc type to internal variable /// private void CacheCurrentDiscType() { // If the selected item is invalid, we just skip if (CurrentDrive is null) return; // Get reasonable default values based on the current system var mediaTypes = CurrentSystem.MediaTypes(); MediaType? defaultMediaType = mediaTypes.Count > 0 ? mediaTypes[0] : MediaType.CDROM; if (defaultMediaType == MediaType.NONE) defaultMediaType = MediaType.CDROM; // If the drive is marked active, try to read from it if (CurrentDrive.MarkedActive) { VerboseLog($"Trying to detect media type for drive {CurrentDrive.Name} [{CurrentDrive.DriveFormat}] using size and filesystem.. "); MediaType? detectedMediaType = CurrentDrive.GetMediaType(CurrentSystem); // If we got either an error or no media, default to the current System default if (detectedMediaType is null) { VerboseLogLn($"Could not detect media type, defaulting to {defaultMediaType.LongName()}."); CurrentMediaType = defaultMediaType; } else { VerboseLogLn($"Detected {detectedMediaType.LongName()}."); _detectedMediaType = detectedMediaType; CurrentMediaType = detectedMediaType; } } // Otherwise just use the default else { VerboseLogLn($"Drive marked as empty, defaulting to {defaultMediaType.LongName()}."); CurrentMediaType = defaultMediaType; } } /// /// Create a DumpEnvironment with all current settings /// /// Filled DumpEnvironment Parent private DumpEnvironment DetermineEnvironment() { var env = new DumpEnvironment( Options, EvaluateOutputPath(OutputPath), CurrentDrive, CurrentSystem, CurrentProgram); env.SetExecutionContext(CurrentMediaType, Parameters); env.SetProcessor(); return env; } /// /// Determine and set the current system type, if allowed /// private void DetermineSystemType() { if (Drives is null || Drives.Count == 0 || CurrentDrive is null) { VerboseLogLn("Skipping system type detection because no valid drives found!"); } else if (!Options.GUI.SkipSystemDetection) { VerboseLog($"Trying to detect system for drive {CurrentDrive.Name}.. "); var currentSystem = GetRedumpSystem(CurrentDrive); if (currentSystem is not null) VerboseLogLn($"detected {currentSystem.LongName()}."); // If undetected system on inactive drive, and PC is the default system, check for potential Mac disc if (currentSystem is null && !CurrentDrive.MarkedActive && Options.Dumping.DefaultSystem == RedumpSystem.IBMPCcompatible) { try { // If disc is readable on inactive drive, assume it is a Mac disc if (PhysicalTool.GetFirstBytes(CurrentDrive, 1) is not null) { currentSystem = RedumpSystem.AppleMacintosh; VerboseLogLn($"unable to detect, defaulting to {currentSystem.LongName()}."); } } catch { } } // Fallback to default system only if drive is active if (currentSystem is null && CurrentDrive.MarkedActive) { currentSystem = Options.Dumping.DefaultSystem; VerboseLogLn($"unable to detect, defaulting to {currentSystem.LongName()}."); } if (currentSystem is not null) { int sysIndex = Systems.FindIndex(s => s == currentSystem); CurrentSystem = Systems[sysIndex]; } } else if (Options.GUI.SkipSystemDetection && Options.Dumping.DefaultSystem is not null) { var currentSystem = Options.Dumping.DefaultSystem; VerboseLogLn($"System detection disabled, defaulting to {currentSystem.LongName()}."); int sysIndex = Systems.FindIndex(s => s == currentSystem); CurrentSystem = Systems[sysIndex]; } } /// /// Disable all UI elements during dumping /// public void DisableAllUIElements() { OptionsMenuItemEnabled = false; CheckDumpMenuItemEnabled = false; CreateIRDMenuItemEnabled = false; SystemTypeComboBoxEnabled = false; MediaTypeComboBoxEnabled = false; OutputPathTextBoxEnabled = false; OutputPathBrowseButtonEnabled = false; DriveLetterComboBoxEnabled = false; DriveSpeedComboBoxEnabled = false; DumpingProgramComboBoxEnabled = false; EnableParametersCheckBoxEnabled = false; StartStopButtonText = StopDumpingValue; MediaScanButtonEnabled = false; UpdateVolumeLabelEnabled = false; CopyProtectScanButtonEnabled = false; } /// /// Enable all UI elements after dumping /// public void EnableAllUIElements() { OptionsMenuItemEnabled = true; CheckDumpMenuItemEnabled = true; CreateIRDMenuItemEnabled = true; SystemTypeComboBoxEnabled = true; MediaTypeComboBoxEnabled = true; OutputPathTextBoxEnabled = true; OutputPathBrowseButtonEnabled = true; DriveLetterComboBoxEnabled = true; DriveSpeedComboBoxEnabled = true; DumpingProgramComboBoxEnabled = true; EnableParametersCheckBoxEnabled = true; StartStopButtonText = StartDumpingValue; MediaScanButtonEnabled = true; UpdateVolumeLabelEnabled = true; CopyProtectScanButtonEnabled = true; } /// /// Ensure information is consistent with the currently selected media type /// public void EnsureMediaInformation() { // If the drive list is empty, ignore updates if (Drives.Count == 0) return; // Get the current environment information _environment = DetermineEnvironment(); // Get the status to write out ResultEventArgs result = _environment.GetSupportStatus(CurrentMediaType); if (CurrentProgram == InternalProgram.NONE) Status = "No dumping program found"; else Status = result.Message; // Enable or disable the button StartStopButtonEnabled = result == true && ShouldEnableDumpingButton(); // If we're in a type that doesn't support drive speeds DriveSpeedComboBoxEnabled = DumpEnvironment.DoesSupportDriveSpeed(CurrentMediaType); // If input params are enabled, generate the full parameters from the environment if (ParametersCheckBoxEnabled) { var generated = _environment.GetFullParameters(CurrentMediaType, DriveSpeed); if (generated is not null) Parameters = generated; } } /// /// Replaces %-delimited variables inside a path string with their values /// /// Path to be evaluated /// String with %-delimited variables evaluated public string EvaluateOutputPath(string outputPath) { string systemLong = _currentSystem.LongName() ?? "Unknown System"; if (string.IsNullOrEmpty(systemLong)) systemLong = "Unknown System"; string systemShort = _currentSystem.ShortName() ?? "unknown"; if (string.IsNullOrEmpty(systemShort)) systemShort = "unknown"; string mediaLong = _currentMediaType.LongName() ?? "Unknown Media"; if (string.IsNullOrEmpty(mediaLong)) mediaLong = "Unknown Media"; string program = _currentProgram.ToString() ?? "Unknown Program"; if (string.IsNullOrEmpty(program)) program = "Unknown Program"; string programShort = program == "DiscImageCreator" ? "DIC" : program; if (string.IsNullOrEmpty(programShort)) programShort = "Unknown Program"; string label = GetFormattedVolumeLabel(_currentDrive) ?? $"track_{DateTime.Now:yyyyMMdd-HHmm}"; if (string.IsNullOrEmpty(label)) label = $"track_{DateTime.Now:yyyyMMdd-HHmm}"; string date = DateTime.Today.ToString("yyyyMMdd"); if (string.IsNullOrEmpty(date)) date = "UNKNOWN"; string datetime = DateTime.Now.ToString("yyyyMMdd-HHmmss"); if (string.IsNullOrEmpty(datetime)) datetime = "UNKNOWN"; return outputPath .Replace("%SYSTEM%", systemLong) .Replace("%SYS%", systemShort.ToUpperInvariant()) .Replace("%sys%", systemShort) .Replace("%MEDIA%", mediaLong) .Replace("%PROGRAM%", program) .Replace("%PROG%", programShort) .Replace("%LABEL%", label) .Replace("%DATE%", date) .Replace("%DATETIME%", datetime); } /// /// Get the default output directory name from the currently selected drive /// /// Force an updated name if the drive letter changes public void GetOutputNames(bool driveChanged) { if (Drives is null || Drives.Count == 0 || CurrentDrive is null) { VerboseLogLn("Skipping output name building because no valid drives found!"); return; } // Get path pieces that are used in all branches string defaultOutputPath = Options.Dumping.DefaultOutputPath ?? "ISO"; string extension = _environment?.GetDefaultExtension(CurrentMediaType) ?? ".bin"; string label = GetFormattedVolumeLabel(CurrentDrive) ?? CurrentSystem.LongName() ?? $"track_{DateTime.Now:yyyyMMdd-HHmm}"; string defaultFilename = $"{label}{extension}"; // If no path exists, set one using default values if (string.IsNullOrEmpty(OutputPath)) { #if NET20 || NET35 OutputPath = Path.Combine(Path.Combine(defaultOutputPath, label), defaultFilename); #else OutputPath = Path.Combine(defaultOutputPath, label, defaultFilename); #endif return; } // For all other cases, separate the last path string lastPath = FrontendTool.NormalizeOutputPaths(OutputPath, false); string lastDirectory = Path.GetDirectoryName(lastPath) ?? string.Empty; string lastFilename = Path.GetFileNameWithoutExtension(lastPath); // Set the output filename, if we changed drives if (driveChanged) { // If the previous path is exactly the default path and last filename if (lastDirectory.EndsWith(Path.Combine(defaultOutputPath, lastFilename))) lastDirectory = Path.GetDirectoryName(lastDirectory) ?? string.Empty; // Create the output path if (lastDirectory == defaultOutputPath) #if NET20 || NET35 OutputPath = Path.Combine(Path.Combine(lastDirectory, label), defaultFilename); #else OutputPath = Path.Combine(lastDirectory, label, defaultFilename); #endif else OutputPath = Path.Combine(lastDirectory, defaultFilename); } // Otherwise, reset the extension of the currently set path else { lastFilename = $"{lastFilename}{extension}"; OutputPath = Path.Combine(lastDirectory, lastFilename); } } /// /// Get the current system from drive /// private static RedumpSystem? GetRedumpSystem(Drive? drive) { // If the drive does not exist, we can't do anything if (drive is null || string.IsNullOrEmpty(drive.Name)) return null; // If we can't read the files in the drive, we can only perform physical checks if (!drive.MarkedActive || !Directory.Exists(drive.Name)) { try { // Check for Panasonic 3DO - filesystem not readable on Windows RedumpSystem? detected3DOSystem = PhysicalTool.Detect3DOSystem(drive); if (detected3DOSystem is not null) { return detected3DOSystem; } // Sega Saturn / Sega Dreamcast / Sega Mega-CD / Sega-CD RedumpSystem? detectedSegaSystem = PhysicalTool.DetectSegaSystem(drive); if (detectedSegaSystem is not null) { return detectedSegaSystem; } } catch { } // Otherwise, return null return null; } // Floppies, HDDs, and removable drives are assumed if (drive.InternalDriveType != InternalDriveType.Optical) return RedumpSystem.IBMPCcompatible; // Check volume labels first RedumpSystem? systemFromLabel = FrontendTool.GetRedumpSystemFromVolumeLabel(drive.VolumeLabel); if (systemFromLabel is not null) return systemFromLabel; // Get a list of files for quicker checking #region Arcade // funworld Photo Play if (File.Exists(Path.Combine(drive.Name, "PP.INF")) && Directory.Exists(Path.Combine(drive.Name, "PPINC"))) { return RedumpSystem.funworldPhotoPlay; } // Konami Python 2 if (Directory.Exists(Path.Combine(drive.Name, "PY2.D"))) { return RedumpSystem.KonamiPython2; } #endregion #region Consoles // Bandai Playdia Quick Interactive System try { List files = [.. Directory.GetFiles(drive.Name, "*", SearchOption.TopDirectoryOnly)]; if (files.Exists(f => f.EndsWith(".AJS", StringComparison.OrdinalIgnoreCase)) && files.Exists(f => f.EndsWith(".GLB", StringComparison.OrdinalIgnoreCase))) { return RedumpSystem.BandaiPlaydiaQuickInteractiveSystem; } } catch { } // Bandai Pippin if (File.Exists(Path.Combine(drive.Name, "PippinAuthenticationFile"))) { return RedumpSystem.BandaiPippin; } // Commodore CDTV/CD32 #if NET20 || NET35 if (File.Exists(Path.Combine(Path.Combine(drive.Name, "S"), "STARTUP-SEQUENCE"))) #else if (File.Exists(Path.Combine(drive.Name, "S", "STARTUP-SEQUENCE"))) #endif { if (File.Exists(Path.Combine(drive.Name, "CDTV.TM"))) return RedumpSystem.CommodoreAmigaCDTV; else return RedumpSystem.CommodoreAmigaCD32; } // Mattel HyperScan -- TODO: May need case-insensitivity added if (File.Exists(Path.Combine(drive.Name, "hyper.exe"))) { return RedumpSystem.MattelHyperScan; } // Mattel Fisher-Price iXL #if NET20 || NET35 if (File.Exists(Path.Combine(Path.Combine(drive.Name, "iXL"), "iXLUpdater.exe"))) #else if (File.Exists(Path.Combine(drive.Name, "iXL", "iXLUpdater.exe"))) #endif { return RedumpSystem.MattelFisherPriceiXL; } // Memorex - Visual Information System if (File.Exists(Path.Combine(drive.Name, "CONTROL.TAT"))) { return RedumpSystem.MemorexVisualInformationSystem; } // Microsoft Xbox 360 try { if (Directory.Exists(Path.Combine(drive.Name, "$SystemUpdate")) && Path.Combine(drive.Name, "$SystemUpdate").SafeGetFiles().Length > 0 && drive.TotalSize <= 500_000_000) { return RedumpSystem.MicrosoftXbox360; } } catch { } // Microsoft Xbox One and Series X try { if (Directory.Exists(Path.Combine(drive.Name, "MSXC"))) { try { #if NET20 || NET35 string catalogjs = Path.Combine(drive.Name, Path.Combine("MSXC", Path.Combine("Metadata", "catalog.js"))); #else string catalogjs = Path.Combine(drive.Name, "MSXC", "Metadata", "catalog.js"); #endif if (!File.Exists(catalogjs)) return RedumpSystem.MicrosoftXboxOne; var catalog = new SabreTools.Serialization.Readers.Catalog().Deserialize(catalogjs); if (catalog is not null && catalog.Version is not null && catalog.Packages is not null) { if (!double.TryParse(catalog.Version, out double version)) return RedumpSystem.MicrosoftXboxOne; if (version < 4) return RedumpSystem.MicrosoftXboxOne; foreach (var package in catalog.Packages) { if (package.Generation != "9") return RedumpSystem.MicrosoftXboxOne; } return RedumpSystem.MicrosoftXboxSeriesXS; } } catch { return RedumpSystem.MicrosoftXboxOne; } } } catch { } // Playmaji Polymega if (File.Exists(Path.Combine(drive.Name, "Get Polymega App.url"))) { return RedumpSystem.PlaymajiPolymega; } try { // Sega Saturn / Sega Dreamcast / Sega Mega-CD / Sega-CD RedumpSystem? segaSystem = PhysicalTool.DetectSegaSystem(drive); if (segaSystem is not null) { return segaSystem; } } catch { } // Sega Dreamcast if (File.Exists(Path.Combine(drive.Name, "IP.BIN"))) { return RedumpSystem.SegaDreamcast; } // Sega Mega-CD / Sega-CD #if NET20 || NET35 if (File.Exists(Path.Combine(Path.Combine(drive.Name, "_BOOT"), "IP.BIN")) || File.Exists(Path.Combine(Path.Combine(drive.Name, "_BOOT"), "SP.BIN")) || File.Exists(Path.Combine(Path.Combine(drive.Name, "_BOOT"), "SP_AS.BIN")) || File.Exists(Path.Combine(drive.Name, "FILESYSTEM.BIN"))) #else if (File.Exists(Path.Combine(drive.Name, "_BOOT", "IP.BIN")) || File.Exists(Path.Combine(drive.Name, "_BOOT", "SP.BIN")) || File.Exists(Path.Combine(drive.Name, "_BOOT", "SP_AS.BIN")) || File.Exists(Path.Combine(drive.Name, "FILESYSTEM.BIN"))) #endif { return RedumpSystem.SegaMegaCDSegaCD; } // SNK Neo-Geo CD try { if (File.Exists(Path.Combine(drive.Name, "ABS.TXT")) || File.Exists(Path.Combine(drive.Name, "BIB.TXT")) || File.Exists(Path.Combine(drive.Name, "CPY.TXT")) || File.Exists(Path.Combine(drive.Name, "IPL.TXT"))) { return RedumpSystem.SNKNeoGeoCD; } } catch { } // Sony PlayStation and Sony PlayStation 2 string psxExePath = Path.Combine(drive.Name, "PSX.EXE"); string systemCnfPath = Path.Combine(drive.Name, "SYSTEM.CNF"); if (File.Exists(systemCnfPath)) { // Check for either BOOT or BOOT2 var systemCnf = new IniFile(systemCnfPath); if (systemCnf.ContainsKey("BOOT")) return RedumpSystem.SonyPlayStation; else if (systemCnf.ContainsKey("BOOT2")) return RedumpSystem.SonyPlayStation2; } else if (File.Exists(psxExePath)) { return RedumpSystem.SonyPlayStation; } // Sony PlayStation 3 try { if (Directory.Exists(Path.Combine(drive.Name, "PS3_GAME")) || Directory.Exists(Path.Combine(drive.Name, "PS3_UPDATE")) || File.Exists(Path.Combine(drive.Name, "PS3_DISC.SFB"))) { return RedumpSystem.SonyPlayStation3; } } catch { } // Sony PlayStation 4 #if NET20 || NET35 if (File.Exists(Path.Combine(Path.Combine(Path.Combine(drive.Name, "PS4"), "UPDATE"), "PS4UPDATE.PUP"))) #else if (File.Exists(Path.Combine(drive.Name, "PS4", "UPDATE", "PS4UPDATE.PUP"))) #endif { return RedumpSystem.SonyPlayStation4; } // Sony PlayStation 5 #if NET20 || NET35 if (File.Exists(Path.Combine(Path.Combine(Path.Combine(drive.Name, "PS5"), "UPDATE"), "PS5UPDATE.PUP"))) #else if (File.Exists(Path.Combine(drive.Name, "PS5", "UPDATE", "PS5UPDATE.PUP"))) #endif { return RedumpSystem.SonyPlayStation5; } // V.Tech V.Flash / V.Smile Pro if (File.Exists(Path.Combine(drive.Name, "0SYSTEM"))) { return RedumpSystem.VTechVFlashVSmilePro; } // VM Labs NUON #if NET20 || NET35 if (File.Exists(Path.Combine(Path.Combine(drive.Name, "NUON"), "nuon.run"))) #else if (File.Exists(Path.Combine(drive.Name, "NUON", "nuon.run"))) #endif { return RedumpSystem.VMLabsNUON; } // ZAPit Games - GameWave if (File.Exists(Path.Combine(drive.Name, "gamewave.diz"))) { return RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem; } #endregion #region Computers // Amiga CD (Do this check AFTER CD32/CDTV) if (File.Exists(Path.Combine(drive.Name, "Disk.info"))) { return RedumpSystem.CommodoreAmigaCD; } // Fujitsu FM Towns try { if (File.Exists(Path.Combine(drive.Name, "TMENU.EXP")) || File.Exists(Path.Combine(drive.Name, "TBIOS.SYS")) || File.Exists(Path.Combine(drive.Name, "TBIOS.BIN"))) { return RedumpSystem.FujitsuFMTownsseries; } } catch { } // Sharp X68000 if (File.Exists(Path.Combine(drive.Name, "COMMAND.X"))) { return RedumpSystem.SharpX68000; } #endregion #region Video Formats // BD-Video if (Directory.Exists(Path.Combine(drive.Name, "BDMV"))) { // Technically BD-Audio has this as well, but it's hard to split that out right now return RedumpSystem.BDVideo; } // DVD-Audio and DVD-Video try { if (Directory.Exists(Path.Combine(drive.Name, "AUDIO_TS")) && Path.Combine(drive.Name, "AUDIO_TS").SafeGetFiles().Length > 0) { return RedumpSystem.DVDAudio; } else if (Directory.Exists(Path.Combine(drive.Name, "VIDEO_TS")) && Path.Combine(drive.Name, "VIDEO_TS").SafeGetFiles().Length > 0) { return RedumpSystem.DVDVideo; } } catch { } // HD-DVD-Video try { if (Directory.Exists(Path.Combine(drive.Name, "HVDVD_TS")) && Path.Combine(drive.Name, "HVDVD_TS").SafeGetFiles().Length > 0) { return RedumpSystem.HDDVDVideo; } } catch { } // Photo CD try { if (Directory.Exists(Path.Combine(drive.Name, "PHOTO_CD")) && Path.Combine(drive.Name, "PHOTO_CD").SafeGetFiles().Length > 0) { return RedumpSystem.PhotoCD; } } catch { } // VCD try { if (Directory.Exists(Path.Combine(drive.Name, "VCD")) && Path.Combine(drive.Name, "VCD").SafeGetFiles().Length > 0) { return RedumpSystem.VideoCD; } } catch { } #endregion // Default return return null; } /// /// Logs the About text /// public void LogAboutText(string message) { SecretLogLn(message); } /// /// Process the current custom parameters back into UI values /// public void ProcessCustomParameters() { // Set the execution context and processor if (_environment?.SetExecutionContext(CurrentMediaType, Parameters) != true) return; if (_environment?.SetProcessor() != true) return; // Catch this in case there's an input path issue try { int driveIndex = Drives.ConvertAll(d => d.Name?[0] ?? '\0') .IndexOf(_environment.ContextInputPath?[0] ?? default); CurrentDrive = driveIndex != -1 ? Drives[driveIndex] : Drives[0]; } catch { } int driveSpeed = _environment.Speed ?? -1; if (driveSpeed > 0) DriveSpeed = driveSpeed; else _environment.Speed = DriveSpeed; // Disable change handling DisableEventHandlers(); OutputPath = FrontendTool.NormalizeOutputPaths(_environment.ContextOutputPath, false); if (MediaTypes is not null) { MediaType? mediaType = _environment.GetMediaType(); if (mediaType is not null) { int mediaTypeIndex = MediaTypes.FindIndex(m => m == mediaType); CurrentMediaType = mediaTypeIndex > -1 ? MediaTypes[mediaTypeIndex] : MediaTypes[0]; } } // Reenable change handling EnableEventHandlers(); } /// /// Scan and show copy protection for the current disc /// public async Task ScanAndShowProtection() { // Determine current environment, just in case _environment ??= DetermineEnvironment(); // If we don't have a valid drive if (CurrentDrive?.Name is null) { ErrorLogLn("No valid drive found!"); return null; } VerboseLogLn($"Scanning for copy protection in {CurrentDrive.Name}"); var tempContent = Status; Status = "Scanning for copy protection... this might take a while!"; // Disable UI elements OptionsMenuItemEnabled = false; SystemTypeComboBoxEnabled = false; MediaTypeComboBoxEnabled = false; OutputPathTextBoxEnabled = false; OutputPathBrowseButtonEnabled = false; DriveLetterComboBoxEnabled = false; DriveSpeedComboBoxEnabled = false; DumpingProgramComboBoxEnabled = false; EnableParametersCheckBoxEnabled = false; StartStopButtonEnabled = false; MediaScanButtonEnabled = false; UpdateVolumeLabelEnabled = false; CopyProtectScanButtonEnabled = false; var progress = new Progress(); progress.ProgressChanged += ProgressUpdated; try { var protections = await ProtectionTool.RunProtectionScanOnPath(CurrentDrive.Name, Options, progress); var output = ProtectionTool.FormatProtections(protections, CurrentDrive); LogLn($"Detected the following protections in {CurrentDrive.Name}:\r\n\r\n{output}"); return output; } catch (Exception ex) { ErrorLogLn($"Path could not be scanned! Exception information:\r\n\r\n{ex}"); return null; } finally { // Reset the status Status = tempContent; // Enable UI elements OptionsMenuItemEnabled = true; SystemTypeComboBoxEnabled = true; MediaTypeComboBoxEnabled = true; OutputPathTextBoxEnabled = true; OutputPathBrowseButtonEnabled = true; DriveLetterComboBoxEnabled = true; DriveSpeedComboBoxEnabled = true; DumpingProgramComboBoxEnabled = true; EnableParametersCheckBoxEnabled = true; StartStopButtonEnabled = ShouldEnableDumpingButton(); MediaScanButtonEnabled = true; UpdateVolumeLabelEnabled = true; CopyProtectScanButtonEnabled = true; } } /// /// Media label as read by Windows, formatted to avoid odd outputs /// If no volume label present, use PSX or PS2 serial if valid /// Otherwise, use "track" with current datetime as volume label /// private static string? GetFormattedVolumeLabel(Drive? drive) { // If the drive is invalid if (drive is null) return null; // If the drive is marked as inactive if (!drive.MarkedActive) return DiscNotDetectedValue; // Use internal serials where appropriate string? volumeLabel = string.IsNullOrEmpty(drive.VolumeLabel) ? null : drive.VolumeLabel!.Trim(); #pragma warning disable IDE0010 switch (GetRedumpSystem(drive)) { case RedumpSystem.SonyPlayStation: case RedumpSystem.SonyPlayStation2: string? ps12Serial = PhysicalTool.GetPlayStationSerial(drive); volumeLabel ??= ps12Serial ?? $"track_{DateTime.Now:yyyyMMdd-HHmm}"; break; case RedumpSystem.SonyPlayStation3: string? ps3Serial = PhysicalTool.GetPlayStation3Serial(drive); if (volumeLabel == "PS3VOLUME") volumeLabel = ps3Serial ?? volumeLabel; else volumeLabel ??= ps3Serial ?? $"track_{DateTime.Now:yyyyMMdd-HHmm}"; break; case RedumpSystem.SonyPlayStation4: string? ps4Serial = PhysicalTool.GetPlayStation4Serial(drive); if (volumeLabel == "PS4VOLUME") volumeLabel = ps4Serial ?? volumeLabel; else volumeLabel ??= ps4Serial ?? $"track_{DateTime.Now:yyyyMMdd-HHmm}"; break; case RedumpSystem.SonyPlayStation5: string? ps5Serial = PhysicalTool.GetPlayStation5Serial(drive); if (volumeLabel == "PS5VOLUME") volumeLabel = ps5Serial ?? volumeLabel; else volumeLabel ??= ps5Serial ?? $"track_{DateTime.Now:yyyyMMdd-HHmm}"; break; default: volumeLabel ??= $"track_{DateTime.Now:yyyyMMdd-HHmm}"; break; } #pragma warning restore IDE0010 foreach (char c in Path.GetInvalidFileNameChars()) { volumeLabel = volumeLabel?.Replace(c, '_'); } return volumeLabel; } /// /// Set the current disc type in the combo box /// private void SetCurrentDiscType() { // If we don't have any selected media types, we don't care and return if (MediaTypes is null) return; // If we have a detected media type, use that first if (_detectedMediaType is not null) { int detectedIndex = MediaTypes.FindIndex(kvp => kvp.Value == _detectedMediaType); if (detectedIndex > -1) { CurrentMediaType = _detectedMediaType; return; } } // If we have an invalid current type, we don't care and return if (CurrentMediaType is null || CurrentMediaType == MediaType.NONE) return; // Now set the selected item, if possible int index = MediaTypes.FindIndex(kvp => kvp.Value == CurrentMediaType); if (CurrentMediaType is not null && index == -1) VerboseLogLn($"Disc of type '{CurrentMediaType.LongName()}' found, but the current system does not support it!"); CurrentMediaType = index > -1 ? MediaTypes[index] : MediaTypes[0]; } /// /// Set the drive speed based on reported maximum and user-defined option /// public void SetSupportedDriveSpeed() { // Skip trying to set speeds if no drives if (Drives.Count == 0) return; // Set the drive speed list that's appropriate DriveSpeeds = InterfaceConstants.GetSpeedsForMediaType(CurrentMediaType); VerboseLogLn($"Supported media speeds: {string.Join(", ", [.. DriveSpeeds.ConvertAll(ds => ds.ToString())])}"); // Set the selected speed DriveSpeed = FrontendTool.GetDefaultSpeedForMediaType(CurrentMediaType, Options); } /// /// Determine if the dumping button should be enabled /// private bool ShouldEnableDumpingButton() { return Drives.Count > 0 && CurrentSystem is not null && CurrentMediaType is not null && ProgramSupportsMedia(); } /// /// Returns false if a given InternalProgram does not support a given MediaType /// private bool ProgramSupportsMedia() { // If the media type is not set, return false if (CurrentMediaType is null || CurrentMediaType == MediaType.NONE) return false; #pragma warning disable IDE0072 return CurrentProgram switch { // Aaru InternalProgram.Aaru when CurrentMediaType == MediaType.BluRay => true, InternalProgram.Aaru when CurrentMediaType == MediaType.CDROM => true, InternalProgram.Aaru when CurrentMediaType == MediaType.CompactFlash => true, InternalProgram.Aaru when CurrentMediaType == MediaType.DVD => true, InternalProgram.Aaru when CurrentMediaType == MediaType.GDROM => true, InternalProgram.Aaru when CurrentMediaType == MediaType.FlashDrive => true, InternalProgram.Aaru when CurrentMediaType == MediaType.FloppyDisk => true, InternalProgram.Aaru when CurrentMediaType == MediaType.HardDisk => true, InternalProgram.Aaru when CurrentMediaType == MediaType.HDDVD => true, InternalProgram.Aaru when CurrentMediaType == MediaType.NintendoGameCubeGameDisc => true, InternalProgram.Aaru when CurrentMediaType == MediaType.NintendoWiiOpticalDisc => true, InternalProgram.Aaru when CurrentMediaType == MediaType.NintendoWiiUOpticalDisc => true, InternalProgram.Aaru when CurrentMediaType == MediaType.SDCard => true, // DiscImageCreator InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.BluRay => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.CDROM => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.CompactFlash => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.DVD => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.GDROM => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.FlashDrive => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.FloppyDisk => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.HardDisk => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.HDDVD => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.NintendoGameCubeGameDisc => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.NintendoWiiOpticalDisc => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.NintendoWiiUOpticalDisc => true, InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.SDCard => true, // Dreamdump // InternalProgram.Dreamdump when CurrentMediaType == MediaType.GDROM => true, // Redumper InternalProgram.Redumper when CurrentMediaType == MediaType.BluRay => true, InternalProgram.Redumper when CurrentMediaType == MediaType.CDROM => true, InternalProgram.Redumper when CurrentMediaType == MediaType.DVD => true, InternalProgram.Redumper when CurrentMediaType == MediaType.GDROM => true, InternalProgram.Redumper when CurrentMediaType == MediaType.HDDVD => true, InternalProgram.Redumper when CurrentMediaType == MediaType.NintendoGameCubeGameDisc => true, InternalProgram.Redumper when CurrentMediaType == MediaType.NintendoWiiOpticalDisc => true, InternalProgram.Redumper when CurrentMediaType == MediaType.NintendoWiiUOpticalDisc => true, // Default _ => false, }; #pragma warning restore IDE0072 } /// /// Begin the dumping process using the given inputs /// public async void StartDumping() { // Ask user to confirm before exiting application during a dump AskBeforeQuit = true; // One last check to determine environment, just in case _environment = DetermineEnvironment(); // Force an internal drive refresh in case the user entered things manually _environment.RefreshDrive(); try { // Run pre-dumping validation checks if (!ValidateBeforeDumping()) { // Re-allow quick exiting AskBeforeQuit = false; return; } // Disable all UI elements apart from dumping button DisableAllUIElements(); // Refresh the drive, if it wasn't null _environment.RefreshDrive(); // Output to the label and log Status = "Starting dumping process... please wait!"; LogLn("Starting dumping process... please wait!"); LogLn("Look for the separate command window for more details"); // Get progress indicators var resultProgress = new Progress(); resultProgress.ProgressChanged += ProgressUpdated; var protectionProgress = new Progress(); protectionProgress.ProgressChanged += ProgressUpdated; _environment.ReportStatus += ProgressUpdated; // Run the program with the parameters ResultEventArgs result = await _environment.Run(CurrentMediaType, resultProgress); // If we didn't execute a dumping command we cannot get submission output if (!_environment.IsDumpingCommand()) { SuccessLogLn("No dumping command was run, submission information will not be gathered."); Status = "Execution complete!"; // Re-allow quick exiting AskBeforeQuit = false; // Reset all UI elements EnableAllUIElements(); return; } // Verify dump output and save it if (result == true) { result = await _environment.VerifyAndSaveDumpOutput( resultProgress: resultProgress, protectionProgress: protectionProgress, processUserInfo: _processUserInfo); if (result == false) ErrorLogLn(result.Message); } else { ErrorLogLn(result.Message); Status = "Execution failed!"; } } catch (Exception ex) { ErrorLogLn(ex.ToString()); Status = "An exception occurred!"; } finally { // Re-allow quick exiting AskBeforeQuit = false; // Reset all UI elements EnableAllUIElements(); } } /// /// Toggle the parameters input box /// public void ToggleParameters() { if (!ParametersCheckBoxEnabled) { OptionsMenuItemEnabled = false; SystemTypeComboBoxEnabled = false; MediaTypeComboBoxEnabled = false; OutputPathTextBoxEnabled = false; OutputPathBrowseButtonEnabled = false; DriveLetterComboBoxEnabled = false; DriveSpeedComboBoxEnabled = false; DumpingProgramComboBoxEnabled = false; ParametersTextBoxEnabled = true; MediaScanButtonEnabled = false; UpdateVolumeLabelEnabled = false; CopyProtectScanButtonEnabled = false; StartStopButtonEnabled = false; } else { ProcessCustomParameters(); OptionsMenuItemEnabled = true; SystemTypeComboBoxEnabled = true; MediaTypeComboBoxEnabled = true; OutputPathTextBoxEnabled = true; OutputPathBrowseButtonEnabled = true; DriveLetterComboBoxEnabled = true; DriveSpeedComboBoxEnabled = true; DumpingProgramComboBoxEnabled = true; ParametersTextBoxEnabled = false; MediaScanButtonEnabled = true; UpdateVolumeLabelEnabled = true; CopyProtectScanButtonEnabled = true; StartStopButtonEnabled = true; } } /// /// Perform validation, including user input, before attempting to start dumping /// /// True if dumping should start, false otherwise private bool ValidateBeforeDumping() { if (Parameters is null || _environment is null) return false; // Validate that we have an output path of any sort if (string.IsNullOrEmpty(_environment.OutputPath)) { if (_displayUserMessage is not null) _ = _displayUserMessage("Missing Path", "No output path was provided so dumping cannot continue.", 1, false); LogLn("Dumping aborted!"); return false; } // Validate that the user explicitly wants an inactive drive to be considered for dumping if (!_environment.DriveMarkedActive && _displayUserMessage is not null) { string message = "The currently selected drive does not appear to contain a disc! " + (!_environment!.DetectedByWindows() ? $"This is normal for {_environment.SystemName} as the discs may not be readable on Windows. " : string.Empty) + "Do you want to continue?"; bool? mbresult = _displayUserMessage("No Disc Detected", message, 2, false); if (mbresult != true) { LogLn("Dumping aborted!"); return false; } } // Pre-split the output path var outputDirectory = Path.GetDirectoryName(_environment!.OutputPath); string outputFilename = Path.GetFileName(_environment.OutputPath); // If a complete or partial dump already exists bool foundAllFiles = _environment.FoundAllFiles(CurrentMediaType, outputDirectory, outputFilename); if (foundAllFiles && _displayUserMessage is not null) { bool? mbresult = _displayUserMessage("Overwrite?", "A complete dump already exists! Are you sure you want to overwrite?", 2, true); if (mbresult != true) { LogLn("Dumping aborted!"); return false; } } else { // If a partial dump exists bool foundAnyFiles = _environment.FoundAnyFiles(CurrentMediaType, outputDirectory, outputFilename); if (foundAnyFiles && _displayUserMessage is not null) { bool? mbresult = _displayUserMessage("Overwrite?", $"A partial dump already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true); if (mbresult != true) { LogLn("Dumping aborted!"); return false; } } else { // If a complete dump exists from a different program InternalProgram? completeProgramFound = _environment.CheckForMatchingProgram(CurrentMediaType, outputDirectory, outputFilename); if (completeProgramFound is not null && _displayUserMessage is not null) { bool? mbresult = _displayUserMessage("Overwrite?", $"A complete dump from {completeProgramFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true); if (mbresult != true) { LogLn("Dumping aborted!"); return false; } } else { // If a partial dump exists from a different program InternalProgram? partialProgramFound = _environment.CheckForPartialProgram(CurrentMediaType, outputDirectory, outputFilename); if (partialProgramFound is not null && _displayUserMessage is not null) { bool? mbresult = _displayUserMessage("Overwrite?", $"A partial dump from {partialProgramFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true); if (mbresult != true) { LogLn("Dumping aborted!"); return false; } } } } } // Validate that at least some space exists // TODO: Tie this to the size of the disc, type of disc, etc. string fullPath; if (string.IsNullOrEmpty(outputDirectory)) fullPath = Path.GetFullPath(_environment.OutputPath); else fullPath = Path.GetFullPath(outputDirectory); var driveInfo = new DriveInfo(Path.GetPathRoot(fullPath) ?? string.Empty); if (driveInfo.AvailableFreeSpace < Math.Pow(2, 30) && _displayUserMessage is not null) { bool? mbresult = _displayUserMessage("Low Space", "There is less than 1gb of space left on the target drive. Are you sure you want to continue?", 2, true); if (mbresult != true) { LogLn("Dumping aborted!"); return false; } } // If nothing above fails, we want to continue return true; } /// /// Checks whether a internal program is found in its path /// /// Program to check for /// True if the program is found, false otherwise private bool InternalProgramExists(InternalProgram program) { try { #pragma warning disable IDE0072 return program switch { InternalProgram.Aaru => File.Exists(Options.Dumping.AaruPath), InternalProgram.DiscImageCreator => File.Exists(Options.Dumping.DiscImageCreatorPath), // InternalProgram.Dreamdump => File.Exists(Options.Dumping.DreamdumpPath), InternalProgram.Redumper => File.Exists(Options.Dumping.RedumperPath), _ => false, }; #pragma warning restore IDE0072 } catch { return false; } } /// /// Translates strings in MainModelView /// /// Dictionary of keys and their translated string public void TranslateStrings(Dictionary? translationStrings) { if (translationStrings is not null) { // Cache current start dumping string var oldStartDumpingValue = StartDumpingValue; // Get translated strings if (translationStrings.TryGetValue("StartDumpingButtonString", out string? startDumpingButtonString)) StartDumpingValue = startDumpingButtonString ?? StartDumpingValue; if (translationStrings.TryGetValue("StopDumpingButtonString", out string? stopDumpingValue)) StopDumpingValue = stopDumpingValue ?? StopDumpingValue; // Set button text if ((StartStopButtonText as string) == oldStartDumpingValue) StartStopButtonText = StartDumpingValue; else StartStopButtonText = StopDumpingValue; } } #endregion #region Progress Reporting /// /// Handler for Result ProgressChanged event /// private void ProgressUpdated(object? sender, StringEventArgs value) { try { LogLn(value); } catch { } } /// /// Handler for Result ProgressChanged event /// private void ProgressUpdated(object? sender, ResultEventArgs value) { var message = value.Message; // Update the label with only the first line of output #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER if (message is not null && message.Contains('\n')) #else if (message is not null && message.Contains("\n")) #endif Status = message.Split('\n')[0] + " (See log output)"; else Status = message ?? string.Empty; // Log based on success or failure if ((bool?)value is null) LogLn(message ?? string.Empty); else if (value == true) SuccessLogLn(message ?? string.Empty); else if (value == false) ErrorLogLn(message ?? string.Empty); } /// /// Handler for ProtectionProgress ProgressChanged event /// private void ProgressUpdated(object? sender, ProtectionProgress value) { string message = $"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}"; Status = message; VerboseLogLn(message); } #endregion } }