using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Threading.Tasks; using BinaryObjectScanner; using MPF.Frontend.ComboBoxItems; using MPF.Frontend.Tools; using SabreTools.IO; using SabreTools.RedumpLib.Data; namespace MPF.Frontend.ViewModels { public class MainViewModel : INotifyPropertyChanged { #region Fields /// /// Access to the current options /// public Frontend.Options Options { get => _options; set { _options = value; OptionsLoader.SaveToConfig(_options); } } private Frontend.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 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 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 Constants private const string DiscNotDetectedValue = "Disc Not Detected"; private const string StartDumpingValue = "Start Dumping"; private const 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 = []; OptionsMenuItemEnabled = true; CheckDumpMenuItemEnabled = true; CreateIRDMenuItemEnabled = true; SystemTypeComboBoxEnabled = true; MediaTypeComboBoxEnabled = true; OutputPathTextBoxEnabled = true; OutputPathBrowseButtonEnabled = true; DriveLetterComboBoxEnabled = true; DumpingProgramComboBoxEnabled = true; StartStopButtonEnabled = true; StartStopButtonText = StartDumpingValue; MediaScanButtonEnabled = true; EnableParametersCheckBoxEnabled = true; LogPanelExpanded = _options.OpenLogWindowAtStartup; MediaTypes = []; Systems = RedumpSystemComboBoxItem.GenerateElements().ToList(); 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, 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 this.MediaScanButtonEnabled = true; this.UpdateVolumeLabelEnabled = true; // If we have a selected drive, keep track of it char? lastSelectedDrive = this.CurrentDrive?.Name?[0] ?? null; // Populate the list of drives and add it to the combo box Drives = Drive.CreateListOfDrives(this.Options.IgnoreFixedDrives); if (Drives.Count > 0) { VerboseLogLn($"Found {Drives.Count} drives: {string.Join(", ", Drives.Select(d => d.Name).ToArray())}"); // Check for the last selected drive, if possible int index = -1; if (lastSelectedDrive != 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]); this.Status = "Valid drive found! Choose your Media Type"; this.CopyProtectScanButtonEnabled = true; // Get the current system type if (index != -1) DetermineSystemType(); // Only enable the start/stop if we don't have the default selected this.StartStopButtonEnabled = ShouldEnableDumpingButton(); } else { VerboseLogLn("Found no drives"); this.CurrentDrive = null; this.Status = "No valid drive found!"; this.StartStopButtonEnabled = false; this.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 (this.CurrentSystem != null) { var mediaTypeValues = this.CurrentSystem.MediaTypes(); int index = mediaTypeValues.FindIndex(m => m == this.CurrentMediaType); if (this.CurrentMediaType != null && index == -1) VerboseLogLn($"Disc of type '{CurrentMediaType.LongName()}' found, but the current system does not support it!"); MediaTypes = mediaTypeValues.Select(m => new Element(m ?? MediaType.NONE)).ToList(); this.MediaTypeComboBoxEnabled = MediaTypes.Count > 1; this.CurrentMediaType = (index > -1 ? MediaTypes[index] : MediaTypes[0]); } else { this.MediaTypeComboBoxEnabled = false; this.MediaTypes = null; this.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 InternalPrograms = Enum.GetValues(typeof(InternalProgram)).Cast().Where(ip => InternalProgramExists(ip)).Select(ip => new Element(ip)).ToList(); // Get the current internal program InternalProgram internalProgram = this.Options.InternalProgram; // Select the current default dumping program if (InternalPrograms.Count == 0) { // If no programs are found, default to InternalProgram.NONE this.CurrentProgram = InternalProgram.NONE; } else { int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram); this.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?)this.CurrentProgram).LongName()}"); EnsureDiscInformation(); // New output name depends on new environment GetOutputNames(false); // New environment depends on new output name EnsureDiscInformation(); } /// /// 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 == null || removedItems.Count == 1) && (addedItems == null || addedItems.Count == 1)) SetSupportedDriveSpeed(); GetOutputNames(false); EnsureDiscInformation(); } /// /// Change the currently selected system /// public void ChangeSystem() { VerboseLogLn($"Changed system to: {this.CurrentSystem.LongName()}"); PopulateMediaType(); GetOutputNames(false); EnsureDiscInformation(); } /// /// 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 == null) message = "An exception occurred while checking for versions, please try again later. See the log window for more details."; } /// /// Build the about text /// /// public string CreateAboutText() { string aboutText = $"Media Preservation Frontend (MPF)" + $"{Environment.NewLine}" + $"{Environment.NewLine}A community preservation frontend developed in C#." + $"{Environment.NewLine}Supports Redumper, Aaru, and DiscImageCreator." + $"{Environment.NewLine}Originally created to help the Redump project." + $"{Environment.NewLine}" + $"{Environment.NewLine}Thanks to everyone who has supported this project!" + $"{Environment.NewLine}" + $"{Environment.NewLine}Version {FrontendTool.GetCurrentVersion()}"; SecretLogLn(aboutText); return aboutText; } /// /// 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 = SabreTools.RedumpLib.Data.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", }, 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 = "PVD with a stupidly long line and nothing else but a little more\nPVD with a stupidly long line and nothing else but a little more\nPVD with a stupidly long line and nothing else but a little more\nPVD with a stupidly long line and nothing else but a little more\nPVD with a stupidly long line and nothing else but a little more\nPVD with a stupidly long line and nothing else but a little more\n", DiscKey = "Disc key", DiscID = "Disc ID", PIC = "PIC", 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 (this.StartStopButtonText as string == StartDumpingValue) { StartDumping(); } else if (this.StartStopButtonText as string == StopDumpingValue) { VerboseLogLn("Canceling dumping process..."); _environment?.CancelDumping(); this.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, Frontend.Options? newOptions) { // Get which options to save var optionsToSave = savedSettings ? newOptions : Options; // Ensure the first run flag is unset var continuingOptions = new Frontend.Options(optionsToSave) { FirstRun = false }; this.Options = continuingOptions; // If settings were changed, reinitialize the UI if (savedSettings) InitializeUIValues(removeEventHandlers: true, rescanDrives: true); } #endregion #region UI Functionality /// /// Performs UI value setup end to end /// /// Whether event handlers need to be removed first /// Whether drives should be rescanned or not public void InitializeUIValues(bool removeEventHandlers, bool rescanDrives) { // Disable the dumping button StartStopButtonEnabled = false; // Safely uncheck the parameters box, just in case if (ParametersCheckBoxEnabled == true) { bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; DisableEventHandlers(); ParametersCheckBoxEnabled = false; 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 PopulateInternalPrograms(); // Set the initial environment and UI values SetSupportedDriveSpeed(); _environment = DetermineEnvironment(); GetOutputNames(true); EnsureDiscInformation(); // 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 this.StartStopButtonEnabled = false; // Safely uncheck the parameters box, just in case if (this.ParametersCheckBoxEnabled == true) { bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged; this.DisableEventHandlers(); this.ParametersCheckBoxEnabled = false; if (cachedCanExecuteSelectionChanged) EnableEventHandlers(); } // Remove event handlers to ensure ordering if (removeEventHandlers) DisableEventHandlers(); // Refresh the drive info this.CurrentDrive?.RefreshDrive(); // Set the initial environment and UI values _environment = DetermineEnvironment(); GetOutputNames(true); EnsureDiscInformation(); // Enable event handlers EnableEventHandlers(); // Enable the dumping button, if necessary this.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, 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 verbose text to the log /// /// Text to write to the log private void VerboseLog(string text) { if (_logger != 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 (this.CurrentDrive == null) return; // Get reasonable default values based on the current system MediaType? defaultMediaType = this.CurrentSystem.MediaTypes().FirstOrDefault() ?? MediaType.CDROM; if (defaultMediaType == MediaType.NONE) defaultMediaType = MediaType.CDROM; // If we're skipping detection, set the default value if (this.Options.SkipMediaTypeDetection) { VerboseLogLn($"Media type detection disabled, defaulting to {defaultMediaType.LongName()}."); CurrentMediaType = defaultMediaType; } // If the drive is marked active, try to read from it else if (this.CurrentDrive.MarkedActive) { VerboseLog($"Trying to detect media type for drive {this.CurrentDrive.Name} [{this.CurrentDrive.DriveFormat}] using size and filesystem.. "); MediaType? detectedMediaType = this.CurrentDrive.GetMediaType(this.CurrentSystem); // If we got either an error or no media, default to the current System default if (detectedMediaType == null) { VerboseLogLn($"Could not detect media type, defaulting to {defaultMediaType.LongName()}."); CurrentMediaType = defaultMediaType; } else { VerboseLogLn($"Detected {detectedMediaType.LongName()}."); _detectedMediaType = detectedMediaType; CurrentMediaType = detectedMediaType; } } // All other cases, 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 this.Parent private DumpEnvironment DetermineEnvironment() { return new DumpEnvironment( this.Options, EvaluateOutputPath(this.OutputPath), this.CurrentDrive, this.CurrentSystem, this.CurrentMediaType, this.CurrentProgram, this.Parameters); } /// /// Determine and set the current system type, if allowed /// private void DetermineSystemType() { if (Drives == null || Drives.Count == 0 || this.CurrentDrive == null) { VerboseLogLn("Skipping system type detection because no valid drives found!"); } else if (this.CurrentDrive?.MarkedActive != true) { VerboseLogLn("Skipping system type detection because drive not marked as active!"); } else if (!this.Options.SkipSystemDetection) { VerboseLog($"Trying to detect system for drive {this.CurrentDrive.Name}.. "); var currentSystem = GetRedumpSystem(CurrentDrive, Options.DefaultSystem) ?? Options.DefaultSystem; VerboseLogLn(currentSystem == null ? "unable to detect." : ($"detected {currentSystem.LongName()}.")); if (currentSystem != null) { int sysIndex = Systems.FindIndex(s => s == currentSystem); this.CurrentSystem = Systems[sysIndex]; } } else if (this.Options.SkipSystemDetection && this.Options.DefaultSystem != null) { var currentSystem = this.Options.DefaultSystem; VerboseLogLn($"System detection disabled, setting to default of {currentSystem.LongName()}."); int sysIndex = Systems.FindIndex(s => s == currentSystem); this.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 disc type /// public void EnsureDiscInformation() { // Get the current environment information _environment = DetermineEnvironment(); // Get the status to write out ResultEventArgs result = _environment.GetSupportStatus(); if (this.CurrentProgram == InternalProgram.NONE) this.Status = "No dumping program found"; else this.Status = result.Message; // Enable or disable the button this.StartStopButtonEnabled = result && ShouldEnableDumpingButton(); // If we're in a type that doesn't support drive speeds this.DriveSpeedComboBoxEnabled = _environment.DoesSupportDriveSpeed(); // If input params are not enabled, generate the full parameters from the environment if (!this.ParametersCheckBoxEnabled) { var generated = _environment.GetFullParameters(this.DriveSpeed); if (generated != null) this.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 = this._currentSystem.LongName() ?? "Unknown System"; if (string.IsNullOrEmpty(systemLong)) systemLong = "Unknown System"; string systemShort = this._currentSystem.ShortName() ?? "unknown"; if (string.IsNullOrEmpty(systemShort)) systemShort = "unknown"; string mediaLong = this._currentMediaType.LongName() ?? "Unknown Media"; if (string.IsNullOrEmpty(mediaLong)) mediaLong = "Unknown Media"; string program = this._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"; if (string.IsNullOrEmpty(label)) label = "track"; 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) .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 == null || Drives.Count == 0 || this.CurrentDrive == null) { VerboseLogLn("Skipping output name building because no valid drives found!"); return; } // Get the extension for the file for the next two statements var extension = _environment?.GetDefaultExtension(this.CurrentMediaType); // Set the output filename, if it's not already if (string.IsNullOrEmpty(this.OutputPath)) { var label = GetFormattedVolumeLabel(CurrentDrive) ?? this.CurrentSystem.LongName(); var directory = this.Options.DefaultOutputPath; string filename = $"{label}{extension ?? ".bin"}"; // If the path ends with the label already if (directory != null && label != null && directory.EndsWith(label, StringComparison.OrdinalIgnoreCase)) directory = Path.GetDirectoryName(directory); if (directory != null && label != null) #if NET20 || NET35 this.OutputPath = Path.Combine(Path.Combine(directory, label), filename); #else this.OutputPath = Path.Combine(directory, label, filename); #endif else this.OutputPath = filename; } // Set the output filename, if we changed drives else if (driveChanged) { var label = GetFormattedVolumeLabel(CurrentDrive) ?? this.CurrentSystem.LongName(); string oldPath = FrontendTool.NormalizeOutputPaths(this.OutputPath, false); string oldFilename = Path.GetFileNameWithoutExtension(oldPath); var directory = Path.GetDirectoryName(oldPath); string filename = $"{label}{extension ?? ".bin"}"; // If the previous path included the label if (directory != null && directory.EndsWith(oldFilename, StringComparison.OrdinalIgnoreCase)) directory = Path.GetDirectoryName(directory); // If the path ends with the label already if (directory != null && label != null && directory.EndsWith(label, StringComparison.OrdinalIgnoreCase)) directory = Path.GetDirectoryName(directory); if (directory != null && label != null) #if NET20 || NET35 this.OutputPath = Path.Combine(Path.Combine(directory, label), filename); #else this.OutputPath = Path.Combine(directory, label, filename); #endif else this.OutputPath = filename; } // Otherwise, reset the extension of the currently set path else { string oldPath = FrontendTool.NormalizeOutputPaths(this.OutputPath, false); string filename = Path.GetFileNameWithoutExtension(oldPath); var directory = Path.GetDirectoryName(oldPath); filename = $"{filename}{extension ?? ".bin"}"; if (directory != null) this.OutputPath = Path.Combine(directory, filename); else this.OutputPath = filename; } } /// /// Get the current system from drive /// /// /// private static RedumpSystem? GetRedumpSystem(Drive? drive, RedumpSystem? defaultValue) { // If the drive does not exist, we can't do anything if (drive == null) return defaultValue; // If we can't read the media in that drive, we can't do anything if (string.IsNullOrEmpty(drive.Name) || !Directory.Exists(drive.Name)) return defaultValue; // We're going to assume for floppies, HDDs, and removable drives if (drive.InternalDriveType != InternalDriveType.Optical) return RedumpSystem.IBMPCcompatible; // Check volume labels first RedumpSystem? systemFromLabel = FrontendTool.GetRedumpSystemFromVolumeLabel(drive.VolumeLabel); if (systemFromLabel != 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 { #if NET20 || NET35 List files = [.. Directory.GetFiles(drive.Name, "*", SearchOption.TopDirectoryOnly)]; #else List files = Directory.EnumerateFiles(drive.Name, "*", SearchOption.TopDirectoryOnly).ToList(); #endif if (files.Any(f => f.EndsWith(".AJS", StringComparison.OrdinalIgnoreCase)) && files.Any(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; } // Microsoft Xbox 360 try { if (Directory.Exists(Path.Combine(drive.Name, "$SystemUpdate")) #if NET20 || NET35 && Directory.GetFiles(Path.Combine(drive.Name, "$SystemUpdate")).Any() #else && Directory.EnumerateFiles(Path.Combine(drive.Name, "$SystemUpdate")).Any() #endif && 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; SabreTools.Models.Xbox.Catalog? catalog = SabreTools.Serialization.Deserializers.Catalog.DeserializeFile(catalogjs); if (catalog != null && catalog.Version != null && catalog.Packages != 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 { } // 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; } // 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 // There are more possible paths that could be checked. // There are some entries that can be found on most PS4 discs: // "/app/GAME_SERIAL/app.pkg" // "/bd/param.sfo" // "/license/rif" // There are also extra files that can be found on some discs: // "/patch/GAME_SERIAL/patch.pkg" can be found in Redump entry 66816. // Originally on disc as "/patch/CUSA11302/patch.pkg". // Is used as an on-disc update for the base game app without needing to get update from the internet. // "/addcont/GAME_SERIAL/CONTENT_ID/ac.pkg" can be found in Redump entry 97619. // Originally on disc as "/addcont/CUSA00288/FFXIVEXPS400001A/ac.pkg". #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; } // V.Tech V.Flash / V.Smile Pro if (File.Exists(Path.Combine(drive.Name, "0SYSTEM"))) { return RedumpSystem.VTechVFlashVSmilePro; } #endregion #region Computers // 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")) #if NET20 || NET35 && Directory.GetFiles(Path.Combine(drive.Name, "AUDIO_TS")).Any()) #else && Directory.EnumerateFiles(Path.Combine(drive.Name, "AUDIO_TS")).Any()) #endif { return RedumpSystem.DVDAudio; } else if (Directory.Exists(Path.Combine(drive.Name, "VIDEO_TS")) #if NET20 || NET35 && Directory.GetFiles(Path.Combine(drive.Name, "VIDEO_TS")).Any()) #else && Directory.EnumerateFiles(Path.Combine(drive.Name, "VIDEO_TS")).Any()) #endif { return RedumpSystem.DVDVideo; } } catch { } // HD-DVD-Video try { if (Directory.Exists(Path.Combine(drive.Name, "HVDVD_TS")) #if NET20 || NET35 && Directory.GetFiles(Path.Combine(drive.Name, "HVDVD_TS")).Any()) #else && Directory.EnumerateFiles(Path.Combine(drive.Name, "HVDVD_TS")).Any()) #endif { return RedumpSystem.HDDVDVideo; } } catch { } // Photo CD try { if (Directory.Exists(Path.Combine(drive.Name, "PHOTO_CD")) #if NET20 || NET35 && Directory.GetFiles(Path.Combine(drive.Name, "PHOTO_CD")).Any()) #else && Directory.EnumerateFiles(Path.Combine(drive.Name, "PHOTO_CD")).Any()) #endif { return RedumpSystem.PhotoCD; } } catch { } // VCD try { if (Directory.Exists(Path.Combine(drive.Name, "VCD")) #if NET20 || NET35 && Directory.GetFiles(Path.Combine(drive.Name, "drive.VCD")).Any()) #else && Directory.EnumerateFiles(Path.Combine(drive.Name, "VCD")).Any()) #endif { return RedumpSystem.VideoCD; } } catch { } #endregion // Default return return defaultValue; } /// /// Process the current custom parameters back into UI values /// public void ProcessCustomParameters() { // Set the execution context and processor if (_environment?.SetExecutionContext(this.Parameters) != true) return; if (_environment?.SetProcessor() != true) return; // Catch this in case there's an input path issue try { int driveIndex = Drives.Select(d => d.Name?[0] ?? '\0').ToList().IndexOf(_environment.ContextInputPath?[0] ?? default); this.CurrentDrive = (driveIndex != -1 ? Drives[driveIndex] : Drives[0]); } catch { } int driveSpeed = _environment.Speed ?? -1; if (driveSpeed > 0) this.DriveSpeed = driveSpeed; else _environment.Speed = this.DriveSpeed; // Disable change handling DisableEventHandlers(); this.OutputPath = FrontendTool.NormalizeOutputPaths(_environment.ContextOutputPath, false); if (MediaTypes != null) { MediaType? mediaType = _environment.GetMediaType(); if (mediaType != null) { int mediaTypeIndex = MediaTypes.FindIndex(m => m == mediaType); this.CurrentMediaType = (mediaTypeIndex > -1 ? MediaTypes[mediaTypeIndex] : MediaTypes[0]); } } // Reenable change handling EnableEventHandlers(); } /// /// Scan and show copy protection for the current disc /// #if NET40 public string? ScanAndShowProtection() #else public async Task ScanAndShowProtection() #endif { // Determine current environment, just in case _environment ??= DetermineEnvironment(); // If we don't have a valid drive if (this.CurrentDrive?.Name == null) { ErrorLogLn("No valid drive found!"); return null; } VerboseLogLn($"Scanning for copy protection in {this.CurrentDrive.Name}"); var tempContent = this.Status; this.Status = "Scanning for copy protection... this might take a while!"; this.StartStopButtonEnabled = false; this.MediaScanButtonEnabled = false; this.UpdateVolumeLabelEnabled = false; this.CopyProtectScanButtonEnabled = false; var progress = new Progress(); progress.ProgressChanged += ProgressUpdated; try { #if NET40 var protectionTask = ProtectionTool.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress); protectionTask.Wait(); var protections = protectionTask.Result; #else var protections = await ProtectionTool.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress); #endif var output = ProtectionTool.FormatProtections(protections); LogLn($"Detected the following protections in {this.CurrentDrive.Name}:\r\n\r\n{output}"); this.Status = tempContent; this.StartStopButtonEnabled = ShouldEnableDumpingButton(); this.MediaScanButtonEnabled = true; this.UpdateVolumeLabelEnabled = true; this.CopyProtectScanButtonEnabled = true; return output; } catch (Exception ex) { ErrorLogLn($"Path could not be scanned! Exception information:\r\n\r\n{ex}"); return null; } } /// /// 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" as volume label /// private static string? GetFormattedVolumeLabel(Drive? drive) { if (drive == null) return null; string? volumeLabel = DiscNotDetectedValue; if (!drive.MarkedActive) return volumeLabel; if (!string.IsNullOrEmpty(drive.VolumeLabel)) { volumeLabel = drive.VolumeLabel; } else { // No Volume Label found, fallback to something sensible switch (GetRedumpSystem(drive, null)) { case RedumpSystem.SonyPlayStation: case RedumpSystem.SonyPlayStation2: string? serial = PhysicalTool.GetPlayStationSerial(drive); volumeLabel = serial ?? "track"; break; default: volumeLabel = "track"; break; } } 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 == null) return; // If we have a detected media type, use that first if (_detectedMediaType != 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 == null || CurrentMediaType == MediaType.NONE) return; // Now set the selected item, if possible int index = MediaTypes.FindIndex(kvp => kvp.Value == CurrentMediaType); if (this.CurrentMediaType != null && index == -1) VerboseLogLn($"Disc of type '{CurrentMediaType.LongName()}' found, but the current system does not support it!"); this.CurrentMediaType = (index > -1 ? MediaTypes[index] : MediaTypes[0]); } /// /// Set the drive speed based on reported maximum and user-defined option /// public void SetSupportedDriveSpeed() { // Set the drive speed list that's appropriate this.DriveSpeeds = (List)InterfaceConstants.GetSpeedsForMediaType(CurrentMediaType); VerboseLogLn($"Supported media speeds: {string.Join(", ", this.DriveSpeeds.Select(ds => ds.ToString()).ToArray())}"); // Set the selected speed int speed = FrontendTool.GetDefaultSpeedForMediaType(CurrentMediaType, Options); VerboseLogLn($"Setting drive speed to: {speed}"); this.DriveSpeed = speed; } /// /// Determine if the dumping button should be enabled /// private bool ShouldEnableDumpingButton() { return Drives != null && Drives.Count > 0 && this.CurrentSystem != null && this.CurrentMediaType != 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 == null || CurrentMediaType == MediaType.NONE) return false; 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.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.SDCard => 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, // Default _ => false, }; } /// /// Begin the dumping process using the given inputs /// public async void StartDumping() { // 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(); // If still in custom parameter mode, check that users meant to continue or not if (this.ParametersCheckBoxEnabled == true && _displayUserMessage != null) { bool? result = _displayUserMessage("Custom Changes", "It looks like you have custom parameters that have not been saved. Would you like to apply those changes before starting to dump?", 3, true); if (result == true) { this.ParametersCheckBoxEnabled = false; ProcessCustomParameters(); } else if (result == null) { return; } // If false, then we continue with the current known environment } // Run path adjustments for DiscImageCreator -- Disabled until further notice //Env.AdjustPathsForDiscImageCreator(); try { // Run pre-dumping validation checks if (!ValidateBeforeDumping()) 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 this.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(resultProgress); // If we didn't execute a dumping command we cannot get submission output if (!_environment.IsDumpingCommand()) { LogLn("No dumping command was run, submission information will not be gathered."); this.Status = "Execution complete!"; // Reset all UI elements EnableAllUIElements(); return; } // Verify dump output and save it if (result) { result = await _environment.VerifyAndSaveDumpOutput(resultProgress, protectionProgress, _processUserInfo); } else { ErrorLogLn(result.Message); this.Status = "Execution failed!"; } } catch (Exception ex) { ErrorLogLn(ex.ToString()); this.Status = "An exception occurred!"; } finally { // Reset all UI elements EnableAllUIElements(); } } /// /// Toggle the parameters input box /// public void ToggleParameters() { if (ParametersCheckBoxEnabled == true) { SystemTypeComboBoxEnabled = false; MediaTypeComboBoxEnabled = false; OutputPathTextBoxEnabled = false; OutputPathBrowseButtonEnabled = false; MediaScanButtonEnabled = false; UpdateVolumeLabelEnabled = false; CopyProtectScanButtonEnabled = false; } else { ProcessCustomParameters(); SystemTypeComboBoxEnabled = true; MediaTypeComboBoxEnabled = true; OutputPathTextBoxEnabled = true; OutputPathBrowseButtonEnabled = true; MediaScanButtonEnabled = true; UpdateVolumeLabelEnabled = true; CopyProtectScanButtonEnabled = true; } } /// /// Perform validation, including user input, before attempting to start dumping /// /// True if dumping should start, false otherwise private bool ValidateBeforeDumping() { if (Parameters == null || _environment == null) return false; // Validate that we have an output path of any sort if (string.IsNullOrEmpty(_environment.OutputPath)) { if (_displayUserMessage != 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 != 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 dump already exists bool foundFiles = _environment.FoundAllFiles(outputDirectory, outputFilename); if (foundFiles && _displayUserMessage != 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 complete dump exists from a different program InternalProgram? programFound = _environment.CheckForMatchingProgram(outputDirectory, outputFilename); if (programFound != null && _displayUserMessage != null) { bool? mbresult = _displayUserMessage("Overwrite?", $"A complete dump from {programFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true); if (mbresult != true) { 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 != 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 { return program switch { InternalProgram.Redumper => File.Exists(this.Options.RedumperPath), InternalProgram.Aaru => File.Exists(this.Options.AaruPath), InternalProgram.DiscImageCreator => File.Exists(this.Options.DiscImageCreatorPath), _ => false, }; } catch { return false; } } #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 (message != null && message.Contains('\n')) this.Status = value?.Message?.Split('\n')[0] + " (See log output)"; else this.Status = value?.Message ?? string.Empty; // Log based on success or failure if (value != null && value) VerboseLogLn(message ?? string.Empty); else if (value != null && !value) 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}"; this.Status = message; VerboseLogLn(message); } #endregion } }