using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; 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 #if NET20 || NET35 || NET40 /// /// Wrapper event args class for old .NET /// public class StringEventArgs : EventArgs { public string? Value { get; set; } } /// /// Geneeic way of reporting a message /// /// String value to report public EventHandler? ReportStatus; #else /// /// Geneeic way of reporting a message /// /// String value to report public EventHandler? ReportStatus; #endif #endregion #region Generic Dumping Information /// /// Base command to run /// public string? BaseCommand { get; set; } /// /// Set of flags to pass to the executable /// protected Dictionary flags = []; protected internal IEnumerable Keys => flags.Keys; /// /// Safe access to currently set flags /// public bool? this[string key] { get { if (flags.TryGetValue(key, out bool? val)) return val; return null; } set { flags[key] = value; } } /// /// Process to track external program /// private Process? process; /// /// All found volume labels and their corresponding file systems /// public Dictionary>? VolumeLabels; #endregion #region Virtual Dumping Information /// /// Command to flag support mappings /// public Dictionary>? CommandSupport => GetCommandSupport(); /// /// Input path for operations /// public virtual string? InputPath => null; /// /// Output path for operations /// /// String representing the path, null on error public virtual string? OutputPath => null; /// /// Get the processing speed from the implementation /// public virtual int? Speed { get; set; } = null; #endregion #region Metadata /// /// Path to the executable /// public string? ExecutablePath { get; set; } /// /// 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 public BaseParameters(string? parameters) { // 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 public BaseParameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options) { 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 public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, Options options, string basePath, Drive? drive, bool includeArtifacts); #endregion #region Virtual Methods /// /// Get all commands mapped to the supported flags /// /// Mappings from command to supported flags public virtual Dictionary>? GetCommandSupport() => null; /// /// Blindly generate a parameter string based on the inputs /// /// Parameter string for invocation, null on error public virtual string? GenerateParameters() => null; /// /// Get the default extension for a given media type /// /// MediaType value to check /// String representing the media type, null on error public virtual string? GetDefaultExtension(MediaType? mediaType) => null; /// /// Generate a list of all deleteable files generated /// /// Base filename and path to use for checking /// List of all deleteable file paths, empty otherwise public virtual List GetDeleteableFilePaths(string basePath) => new(); /// /// Generate a list of all log files generated /// /// Base filename and path to use for checking /// List of all log file paths, empty otherwise public virtual List GetLogFilePaths(string basePath) => new(); /// /// 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.TryGetValue(this.BaseCommand, out var supported)) return false; return supported.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 protected virtual void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Options options) { } /// /// Scan a possible parameter string and populate whatever possible /// /// String possibly representing parameters /// True if the parameters were set correctly, false otherwise protected virtual bool ValidateAndSetParameters(string? parameters) => !string.IsNullOrEmpty(parameters); #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) { #if NET40 Logging.OutputToLog(process.StandardOutput, this, ReportStatus); Logging.OutputToLog(process.StandardError, this, ReportStatus); #else _ = Logging.OutputToLog(process.StandardOutput, this, ReportStatus); _ = Logging.OutputToLog(process.StandardError, this, ReportStatus); #endif } 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 protected static string? GetBase64(string? content) { 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 protected static string? GetFullFile(string filename, bool binary = false) { // 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 protected bool ProcessFlagParameter(List parts, string? shortFlagString, string longFlagString, ref int i) { 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 protected bool ProcessBooleanParameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) { 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> protected sbyte? ProcessInt8Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) { 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]); if (sbyte.TryParse(value, out sbyte sByteValue)) return (sbyte)(sByteValue * factor); string hexValue = RemoveHexIdentifier(value); if (sbyte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out sbyte sByteHexValue)) return (sbyte)(sByteHexValue * factor); return null; } 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); if (sbyte.TryParse(value, out sbyte sByteValue)) return (sbyte)(sByteValue * factor); string hexValue = RemoveHexIdentifier(value); if (sbyte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out sbyte sByteHexValue)) return (sbyte)(sByteHexValue * factor); return null; } 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> protected short? ProcessInt16Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) { 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]); if (short.TryParse(value, out short shortValue)) return (short)(shortValue * factor); string hexValue = RemoveHexIdentifier(value); if (short.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out short shortHexValue)) return (short)(shortHexValue * factor); return null; } 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); if (short.TryParse(value, out short shortValue)) return (short)(shortValue * factor); string hexValue = RemoveHexIdentifier(value); if (short.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out short shortHexValue)) return (short)(shortHexValue * factor); return null; } 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> protected int? ProcessInt32Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) { 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]); if (int.TryParse(value, out int intValue)) return (int)(intValue * factor); string hexValue = RemoveHexIdentifier(value); if (int.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int intHexValue)) return (int)(intHexValue * factor); return null; } 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); if (int.TryParse(value, out int intValue)) return (int)(intValue * factor); string hexValue = RemoveHexIdentifier(value); if (int.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int intHexValue)) return (int)(intHexValue * factor); return null; } 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> protected long? ProcessInt64Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) { 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]); if (long.TryParse(value, out long longValue)) return (long)(longValue * factor); string hexValue = RemoveHexIdentifier(value); if (long.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long longHexValue)) return (long)(longHexValue * factor); return null; } 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); if (long.TryParse(value, out long longValue)) return (long)(longValue * factor); string hexValue = RemoveHexIdentifier(value); if (long.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long longHexValue)) return (long)(longHexValue * factor); return null; } 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 protected string? ProcessStringParameter(List parts, string flagString, ref int i, bool missingAllowed = false) => 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 protected string? ProcessStringParameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) { 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.IsNullOrEmpty(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> protected byte? ProcessUInt8Parameter(List parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false) { 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]); if (byte.TryParse(value, out byte byteValue)) return (byte)(byteValue * factor); string hexValue = RemoveHexIdentifier(value); if (byte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out byte byteHexValue)) return (byte)(byteHexValue * factor); return null; } 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); if (byte.TryParse(value, out byte byteValue)) return (byte)(byteValue * factor); string hexValue = RemoveHexIdentifier(value); if (byte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out byte byteHexValue)) return (byte)(byteHexValue * factor); return null; } return Byte.MinValue; } /// /// Get the 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); } /// /// Removes a leading 0x if it exists, case insensitive /// /// String with removed leading 0x /// private static string RemoveHexIdentifier(string value) { if (value.Length <= 2) return value; if (value[0] != '0') return value; if (value[1] != 'x' && value[1] != 'X') return value; return value.Substring(2); } #endregion #region Methods to Move /// /// Get the hex contents of the PIC file /// /// Path to the PIC.bin file associated with the dump /// Number of characters to trim the PIC to, if -1, ignored /// PIC data as a hex string if possible, null on error /// https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters protected static string? GetPIC(string picPath, int trimLength = -1) { // If the file doesn't exist, we can't get the info if (!File.Exists(picPath)) return null; try { var hex = GetFullFile(picPath, true); if (hex == null) return null; if (trimLength > -1) hex = hex.Substring(0, trimLength); return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled); } catch { // We don't care what the error was right now return null; } } #endregion } }