using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Text.RegularExpressions; using MPF.Core.Data; using MPF.Core.Utilities; using SabreTools.RedumpLib.Data; namespace MPF.Core.Modules { public abstract class BaseParameters { #region Event Handlers /// /// Geneeic way of reporting a message /// /// String value to report #if NET48 public EventHandler ReportStatus; #else public EventHandler? ReportStatus; #endif #endregion #region Generic Dumping Information /// /// Base command to run /// #if NET48 public string BaseCommand { get; set; } #else public string? BaseCommand { get; set; } #endif /// /// Set of flags to pass to the executable /// #if NET48 protected Dictionary flags = new Dictionary(); #else protected Dictionary flags = new(); #endif protected internal IEnumerable Keys => flags.Keys; /// /// Safe access to currently set flags /// public bool? this[string key] { get { if (flags.ContainsKey(key)) return flags[key]; return null; } set { flags[key] = value; } } /// /// Process to track external program /// #if NET48 private Process process; #else private Process? process; #endif #endregion #region Virtual Dumping Information /// /// Command to flag support mappings /// #if NET48 public Dictionary> CommandSupport => GetCommandSupport(); #else public Dictionary>? CommandSupport => GetCommandSupport(); #endif /// /// Input path for operations /// #if NET48 public virtual string InputPath => null; #else public virtual string? InputPath => null; #endif /// /// Output path for operations /// /// String representing the path, null on error #if NET48 public virtual string OutputPath => null; #else public virtual string? OutputPath => null; #endif /// /// Get the processing speed from the implementation /// public virtual int? Speed { get; set; } = null; #endregion #region Metadata /// /// Path to the executable /// #if NET48 public string ExecutablePath { get; set; } #else public string? ExecutablePath { get; set; } #endif /// /// Program that this set of parameters represents /// public virtual InternalProgram InternalProgram { get; } /// /// Currently represented system /// public RedumpSystem? System { get; set; } /// /// Currently represented media type /// public MediaType? Type { get; set; } #endregion /// /// Populate a Parameters object from a param string /// /// String possibly representing a set of parameters #if NET48 public BaseParameters(string parameters) #else public BaseParameters(string? parameters) #endif { // If any parameters are not valid, wipe out everything if (!ValidateAndSetParameters(parameters)) ResetValues(); } /// /// Generate parameters based on a set of known inputs /// /// RedumpSystem value to use /// MediaType value to use /// Drive path to use /// Filename to use /// Drive speed to use /// Options object containing all settings that may be used for setting parameters #if NET48 public BaseParameters(RedumpSystem? system, MediaType? type, string drivePath, string filename, int? driveSpeed, Options options) #else public BaseParameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options) #endif { this.System = system; this.Type = type; SetDefaultParameters(drivePath, filename, driveSpeed, options); } #region Abstract Methods /// /// Validate if all required output files exist /// /// Base filename and path to use for checking /// True if this is a check done before a dump, false if done after /// Tuple of true if all required files exist, false otherwise and a list representing missing files public abstract (bool, List) CheckAllOutputFilesExist(string basePath, bool preCheck); /// /// Generate a SubmissionInfo for the output files /// /// Base submission info to fill in specifics for /// Options object representing user-defined options /// Base filename and path to use for checking /// Drive representing the disc to get information from /// True to include output files as encoded artifacts, false otherwise #if NET48 public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, Options options, string basePath, Drive drive, bool includeArtifacts); #else public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, Options options, string basePath, Drive? drive, bool includeArtifacts); #endif #endregion #region Virtual Methods /// /// Get all commands mapped to the supported flags /// /// Mappings from command to supported flags #if NET48 public virtual Dictionary> GetCommandSupport() => null; #else public virtual Dictionary>? GetCommandSupport() => null; #endif /// /// Blindly generate a parameter string based on the inputs /// /// Parameter string for invocation, null on error #if NET48 public virtual string GenerateParameters() => null; #else public virtual string? GenerateParameters() => null; #endif /// /// Get the default extension for a given media type /// /// MediaType value to check /// String representing the media type, null on error #if NET48 public virtual string GetDefaultExtension(MediaType? mediaType) => null; #else public virtual string? GetDefaultExtension(MediaType? mediaType) => null; #endif /// /// Generate a list of all deleteable files generated /// /// Base filename and path to use for checking /// List of all deleteable file paths, empty otherwise #if NET48 public virtual List GetDeleteableFilePaths(string basePath) => new List(); #else public virtual List GetDeleteableFilePaths(string basePath) => new(); #endif /// /// Generate a list of all log files generated /// /// Base filename and path to use for checking /// List of all log file paths, empty otherwise #if NET48 public virtual List GetLogFilePaths(string basePath) => new List(); #else public virtual List GetLogFilePaths(string basePath) => new(); #endif /// /// Get the MediaType from the current set of parameters /// /// MediaType value if successful, null on error public virtual MediaType? GetMediaType() => null; /// /// Gets if the current command is considered a dumping command or not /// /// True if it's a dumping command, false otherwise public virtual bool IsDumpingCommand() => true; /// /// Gets if the flag is supported by the current command /// /// Flag value to check /// True if the flag value is supported, false otherwise public virtual bool IsFlagSupported(string flag) { if (CommandSupport == null) return false; if (this.BaseCommand == null) return false; if (!CommandSupport.ContainsKey(this.BaseCommand)) return false; return CommandSupport[this.BaseCommand].Contains(flag); } /// /// Returns if the current Parameter object is valid /// /// public bool IsValid() => GenerateParameters() != null; /// /// Reset all special variables to have default values /// protected virtual void ResetValues() { } /// /// Set default parameters for a given system and media type /// /// Drive path to use /// Filename to use /// Drive speed to use /// Options object containing all settings that may be used for setting parameters #if NET48 protected virtual void SetDefaultParameters(string drivePath, string filename, int? driveSpeed, Options options) { } #else protected virtual void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Options options) { } #endif /// /// Scan a possible parameter string and populate whatever possible /// /// String possibly representing parameters /// True if the parameters were set correctly, false otherwise #if NET48 protected virtual bool ValidateAndSetParameters(string parameters) => !string.IsNullOrWhiteSpace(parameters); #else protected virtual bool ValidateAndSetParameters(string? parameters) => !string.IsNullOrWhiteSpace(parameters); #endif #endregion #region Execution /// /// Run internal program /// /// True to show in separate window, false otherwise public void ExecuteInternalProgram(bool separateWindow) { // Create the start info var startInfo = new ProcessStartInfo() { FileName = ExecutablePath, Arguments = GenerateParameters() ?? "", CreateNoWindow = !separateWindow, UseShellExecute = separateWindow, RedirectStandardOutput = !separateWindow, RedirectStandardError = !separateWindow, }; // Create the new process process = new Process() { StartInfo = startInfo }; // Start the process process.Start(); // Start processing tasks, if necessary if (!separateWindow) { _ = Logging.OutputToLog(process.StandardOutput, this, ReportStatus); _ = Logging.OutputToLog(process.StandardError, this, ReportStatus); } process.WaitForExit(); process.Close(); } /// /// Cancel an in-progress dumping process /// public void KillInternalProgram() { try { while (process != null && !process.HasExited) { process.Kill(); } } catch { } } #endregion #region Parameter Parsing /// /// Returns whether or not the selected item exists /// /// List of parameters to check against /// Current index /// True if the next item exists, false otherwise protected static bool DoesExist(List parameters, int index) => index < parameters.Count; /// /// Get the Base64 representation of a string /// /// String content to encode /// Base64-encoded contents, if possible #if NET48 protected static string GetBase64(string content) #else protected static string? GetBase64(string? content) #endif { if (string.IsNullOrEmpty(content)) return null; byte[] temp = Encoding.UTF8.GetBytes(content); return Convert.ToBase64String(temp); } /// /// Get the full lines from the input file, if possible /// /// file location /// True if should read as binary, false otherwise (default) /// Full text of the file, null on error #if NET48 protected static string GetFullFile(string filename, bool binary = false) #else protected static string? GetFullFile(string filename, bool binary = false) #endif { // If the file doesn't exist, we can't get info from it if (!File.Exists(filename)) return null; // If we're reading as binary if (binary) { byte[] bytes = File.ReadAllBytes(filename); return BitConverter.ToString(bytes).Replace("-", string.Empty); } return File.ReadAllText(filename); } /// /// Returns whether a string is a valid drive letter /// /// String value to check /// True if it's a valid drive letter, false otherwise protected static bool IsValidDriveLetter(string parameter) => Regex.IsMatch(parameter, @"^[A-Z]:?\\?$"); /// /// Returns whether a string is a valid bool /// /// String value to check /// True if it's a valid bool, false otherwise protected static bool IsValidBool(string parameter) => bool.TryParse(parameter, out bool _); /// /// Returns whether a string is a valid byte /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid byte, false otherwise protected static bool IsValidInt8(string parameter, sbyte lowerBound = -1, sbyte upperBound = -1) { (string value, long _) = ExtractFactorFromValue(parameter); if (!sbyte.TryParse(value, out sbyte temp)) return false; else if (lowerBound != -1 && temp < lowerBound) return false; else if (upperBound != -1 && temp > upperBound) return false; return true; } /// /// Returns whether a string is a valid Int16 /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid Int16, false otherwise protected static bool IsValidInt16(string parameter, short lowerBound = -1, short upperBound = -1) { (string value, long _) = ExtractFactorFromValue(parameter); if (!short.TryParse(value, out short temp)) return false; else if (lowerBound != -1 && temp < lowerBound) return false; else if (upperBound != -1 && temp > upperBound) return false; return true; } /// /// Returns whether a string is a valid Int32 /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid Int32, false otherwise protected static bool IsValidInt32(string parameter, int lowerBound = -1, int upperBound = -1) { (string value, long _) = ExtractFactorFromValue(parameter); if (!int.TryParse(value, out int temp)) return false; else if (lowerBound != -1 && temp < lowerBound) return false; else if (upperBound != -1 && temp > upperBound) return false; return true; } /// /// Returns whether a string is a valid Int64 /// /// String value to check /// Lower bound (>=) /// Upper bound (<=) /// True if it's a valid Int64, false otherwise protected static bool IsValidInt64(string parameter, long lowerBound = -1, long upperBound = -1) { (string value, long _) = ExtractFactorFromValue(parameter); if (!long.TryParse(value, out long temp)) return false; else if (lowerBound != -1 && temp < lowerBound) return false; else if (upperBound != -1 && temp > upperBound) return false; return true; } /// /// Process a flag parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if the parameter was processed successfully or skipped, false otherwise protected bool ProcessFlagParameter(List parts, string flagString, ref int i) => ProcessFlagParameter(parts, null, flagString, ref i); /// /// Process a flag parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if the parameter was processed successfully or skipped, false otherwise #if NET48 protected bool ProcessFlagParameter(List parts, string shortFlagString, string longFlagString, ref int i) #else protected bool ProcessFlagParameter(List parts, string? shortFlagString, string longFlagString, ref int i) #endif { if (parts == null) return false; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) return false; this[longFlagString] = true; } return true; } /// /// Process a boolean parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// True if the parameter was processed successfully or skipped, false otherwise protected bool ProcessBooleanParameter(List parts, string flagString, ref int i, bool missingAllowed = false) => ProcessBooleanParameter(parts, null, flagString, ref i, missingAllowed); /// /// Process a boolean parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// True if the parameter was processed successfully or skipped, false otherwise #if NET48 protected bool ProcessBooleanParameter(List parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #else protected bool ProcessBooleanParameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #endif { if (parts == null) return false; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) { return false; } else if (!DoesExist(parts, i + 1)) { if (missingAllowed) { this[longFlagString] = true; return true; } else { return false; } } else if (IsFlagSupported(parts[i + 1])) { if (missingAllowed) { this[longFlagString] = true; return true; } else { return false; } } else if (!IsValidBool(parts[i + 1])) { if (missingAllowed) { this[longFlagString] = true; return true; } else { return false; } } this[longFlagString] = bool.Parse(parts[i + 1]); i++; } return true; } /// /// Process a sbyte parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// SByte value if success, SByte.MinValue if skipped, null on error/returns> protected sbyte? ProcessInt8Parameter(List parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt8Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an sbyte parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// SByte value if success, SByte.MinValue if skipped, null on error/returns> #if NET48 protected sbyte? ProcessInt8Parameter(List parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #else protected sbyte? ProcessInt8Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #endif { if (parts == null) return null; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) { return null; } else if (!DoesExist(parts, i + 1)) { if (missingAllowed) this[longFlagString] = true; return null; } else if (IsFlagSupported(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } else if (!IsValidInt8(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } this[longFlagString] = true; i++; (string value, long factor) = ExtractFactorFromValue(parts[i]); return (sbyte)(sbyte.Parse(value) * factor); } else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "=")) { if (!IsFlagSupported(longFlagString)) return null; string[] commandParts = parts[i].Split('='); if (commandParts.Length != 2) return null; string valuePart = commandParts[1]; this[longFlagString] = true; (string value, long factor) = ExtractFactorFromValue(valuePart); return (sbyte)(sbyte.Parse(value) * factor); } return SByte.MinValue; } /// /// Process an Int16 parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Int16 value if success, Int16.MinValue if skipped, null on error/returns> protected short? ProcessInt16Parameter(List parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt16Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an Int16 parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Int16 value if success, Int16.MinValue if skipped, null on error/returns> #if NET48 protected short? ProcessInt16Parameter(List parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #else protected short? ProcessInt16Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #endif { if (parts == null) return null; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) { return null; } else if (!DoesExist(parts, i + 1)) { if (missingAllowed) this[longFlagString] = true; return null; } else if (IsFlagSupported(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } else if (!IsValidInt16(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } this[longFlagString] = true; i++; (string value, long factor) = ExtractFactorFromValue(parts[i]); return (short)(short.Parse(value) * factor); } else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "=")) { if (!IsFlagSupported(longFlagString)) return null; string[] commandParts = parts[i].Split('='); if (commandParts.Length != 2) return null; string valuePart = commandParts[1]; this[longFlagString] = true; (string value, long factor) = ExtractFactorFromValue(valuePart); return (short)(short.Parse(value) * factor); } return Int16.MinValue; } /// /// Process an Int32 parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Int32 value if success, Int32.MinValue if skipped, null on error/returns> protected int? ProcessInt32Parameter(List parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt32Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an Int32 parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Int32 value if success, Int32.MinValue if skipped, null on error/returns> #if NET48 protected int? ProcessInt32Parameter(List parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #else protected int? ProcessInt32Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #endif { if (parts == null) return null; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) { return null; } else if (!DoesExist(parts, i + 1)) { if (missingAllowed) this[longFlagString] = true; return null; } else if (IsFlagSupported(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } else if (!IsValidInt32(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } this[longFlagString] = true; i++; (string value, long factor) = ExtractFactorFromValue(parts[i]); return (int)(int.Parse(value) * factor); } else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "=")) { if (!IsFlagSupported(longFlagString)) return null; string[] commandParts = parts[i].Split('='); if (commandParts.Length != 2) return null; string valuePart = commandParts[1]; this[longFlagString] = true; (string value, long factor) = ExtractFactorFromValue(valuePart); return (int)(int.Parse(value) * factor); } return Int32.MinValue; } /// /// Process an Int64 parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Int64 value if success, Int64.MinValue if skipped, null on error/returns> protected long? ProcessInt64Parameter(List parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt64Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an Int64 parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Int64 value if success, Int64.MinValue if skipped, null on error/returns> #if NET48 protected long? ProcessInt64Parameter(List parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #else protected long? ProcessInt64Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #endif { if (parts == null) return null; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) { return null; } else if (!DoesExist(parts, i + 1)) { if (missingAllowed) this[longFlagString] = true; return null; } else if (IsFlagSupported(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } else if (!IsValidInt64(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } this[longFlagString] = true; i++; (string value, long factor) = ExtractFactorFromValue(parts[i]); return long.Parse(value) * factor; } else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "=")) { if (!IsFlagSupported(longFlagString)) return null; string[] commandParts = parts[i].Split('='); if (commandParts.Length != 2) return null; string valuePart = commandParts[1]; this[longFlagString] = true; (string value, long factor) = ExtractFactorFromValue(valuePart); return long.Parse(value) * factor; } return Int64.MinValue; } /// /// Process an string parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// String value if possible, string.Empty on missing, null on error #if NET48 protected string ProcessStringParameter(List parts, string flagString, ref int i, bool missingAllowed = false) #else protected string? ProcessStringParameter(List parts, string flagString, ref int i, bool missingAllowed = false) #endif => ProcessStringParameter(parts, null, flagString, ref i, missingAllowed); /// /// Process a string parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// String value if possible, string.Empty on missing, null on error #if NET48 protected string ProcessStringParameter(List parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #else protected string? ProcessStringParameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #endif { if (parts == null) return null; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) { return null; } else if (!DoesExist(parts, i + 1)) { if (missingAllowed) this[longFlagString] = true; return null; } else if (IsFlagSupported(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } else if (string.IsNullOrWhiteSpace(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } this[longFlagString] = true; i++; return parts[i].Trim('"'); } else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "=")) { if (!IsFlagSupported(longFlagString)) return null; string[] commandParts = parts[i].Split('='); if (commandParts.Length != 2) return null; string valuePart = commandParts[1]; this[longFlagString] = true; return valuePart.Trim('"'); } return string.Empty; } /// /// Process a byte parameter /// /// List of parts to be referenced /// Flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Byte value if success, Byte.MinValue if skipped, null on error/returns> protected byte? ProcessUInt8Parameter(List parts, string flagString, ref int i, bool missingAllowed = false) => ProcessUInt8Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process a byte parameter /// /// List of parts to be referenced /// Short flag string, if available /// Long flag string, if available /// Reference to the position in the parts /// True if missing values are allowed, false otherwise /// Byte value if success, Byte.MinValue if skipped, null on error/returns> #if NET48 protected byte? ProcessUInt8Parameter(List parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #else protected byte? ProcessUInt8Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) #endif { if (parts == null) return null; if (parts[i] == shortFlagString || parts[i] == longFlagString) { if (!IsFlagSupported(longFlagString)) { return null; } else if (!DoesExist(parts, i + 1)) { if (missingAllowed) this[longFlagString] = true; return null; } else if (IsFlagSupported(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } else if (!IsValidInt8(parts[i + 1])) { if (missingAllowed) this[longFlagString] = true; return null; } this[longFlagString] = true; i++; (string value, long factor) = ExtractFactorFromValue(parts[i]); return (byte)(byte.Parse(value) * factor); } else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "=")) { if (!IsFlagSupported(longFlagString)) return null; string[] commandParts = parts[i].Split('='); if (commandParts.Length != 2) return null; string valuePart = commandParts[1]; this[longFlagString] = true; (string value, long factor) = ExtractFactorFromValue(valuePart); return (byte)(byte.Parse(value) * factor); } return Byte.MinValue; } /// /// Get yhe trimmed value and multiplication factor from a value /// /// String value to treat as suffixed number /// Trimmed value and multiplication factor private static (string trimmed, long factor) ExtractFactorFromValue(string value) { value = value.Trim('"'); long factor = 1; // Characters if (value.EndsWith("c", StringComparison.Ordinal)) { factor = 1; value = value.TrimEnd('c'); } // Words else if (value.EndsWith("w", StringComparison.Ordinal)) { factor = 2; value = value.TrimEnd('w'); } // Double Words else if (value.EndsWith("d", StringComparison.Ordinal)) { factor = 4; value = value.TrimEnd('d'); } // Quad Words else if (value.EndsWith("q", StringComparison.Ordinal)) { factor = 8; value = value.TrimEnd('q'); } // Kilobytes else if (value.EndsWith("k", StringComparison.Ordinal)) { factor = 1024; value = value.TrimEnd('k'); } // Megabytes else if (value.EndsWith("M", StringComparison.Ordinal)) { factor = 1024 * 1024; value = value.TrimEnd('M'); } // Gigabytes else if (value.EndsWith("G", StringComparison.Ordinal)) { factor = 1024 * 1024 * 1024; value = value.TrimEnd('G'); } return (value, factor); } #endregion } }