using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; using SabreTools.RedumpLib.Data; namespace MPF.ExecutionContexts { public abstract class BaseExecutionContext { #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 List 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; #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; } /// /// Currently represented system /// public RedumpSystem? RedumpSystem { get; set; } /// /// Currently represented media type /// public MediaType? MediaType { get; set; } #endregion /// /// Populate a Parameters object from a param string /// /// String possibly representing a set of parameters public BaseExecutionContext(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 /// Dictionary object containing all settings that may be used for setting parameters public BaseExecutionContext(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Dictionary options) { RedumpSystem = system; MediaType = type; SetDefaultParameters(drivePath, filename, driveSpeed, options); } #region Abstract Methods /// /// Get all commands mapped to the supported flags /// /// Mappings from command to supported flags public abstract Dictionary>? GetCommandSupport(); /// /// Blindly generate a parameter string based on the inputs /// /// Parameter string for invocation, null on error public abstract string? GenerateParameters(); /// /// Get the default extension for a given media type /// /// MediaType value to check /// String representing the media type, null on error public abstract string? GetDefaultExtension(MediaType? mediaType); /// /// Get the MediaType from the current set of parameters /// /// MediaType value if successful, null on error public abstract MediaType? GetMediaType(); /// /// Gets if the current command is considered a dumping command or not /// /// True if it's a dumping command, false otherwise public abstract bool IsDumpingCommand(); /// /// Returns if the current Parameter object is valid /// /// public bool IsValid() => GenerateParameters() != null; /// /// Reset all special variables to have default values /// protected abstract void ResetValues(); /// /// Set default parameters for a given system and media type /// /// Drive path to use /// Filename to use /// Drive speed to use /// Dictionary containing all settings that may be used for setting parameters protected abstract void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Dictionary options); /// /// Scan a possible parameter string and populate whatever possible /// /// String possibly representing parameters /// True if the parameters were set correctly, false otherwise protected abstract bool ValidateAndSetParameters(string? parameters); #endregion #region Execution /// /// Run internal program /// public void ExecuteInternalProgram() { // Create the start info var startInfo = new ProcessStartInfo() { FileName = ExecutablePath!, Arguments = GenerateParameters() ?? "", CreateNoWindow = false, UseShellExecute = true, RedirectStandardOutput = false, RedirectStandardError = false, }; // Create the new process process = new Process() { StartInfo = startInfo }; // Start the process process.Start(); process.WaitForExit(); process.Close(); } /// /// Cancel an in-progress dumping process /// public void KillInternalProgram() { try { while (process != null && !process.HasExited) { process.Kill(); } } catch { } } #endregion #region Option Processing /// /// Get a Boolean setting from a settings, dictionary /// /// Dictionary representing the settings /// Setting key to get a value for /// Default value to return if no value is found /// Setting value if possible, default value otherwise internal static bool GetBooleanSetting(Dictionary settings, string key, bool defaultValue) { if (settings.ContainsKey(key)) { if (bool.TryParse(settings[key], out bool value)) return value; else return defaultValue; } else { return defaultValue; } } /// /// Get an Int32 setting from a settings, dictionary /// /// Dictionary representing the settings /// Setting key to get a value for /// Default value to return if no value is found /// Setting value if possible, default value otherwise internal static int GetInt32Setting(Dictionary settings, string key, int defaultValue) { if (settings.ContainsKey(key)) { if (int.TryParse(settings[key], out int value)) return value; else return defaultValue; } else { return defaultValue; } } /// /// Get a String setting from a settings, dictionary /// /// Dictionary representing the settings /// Setting key to get a value for /// Default value to return if no value is found /// Setting value if possible, default value otherwise internal static string? GetStringSetting(Dictionary settings, string key, string? defaultValue) { if (settings.ContainsKey(key)) return settings[key]; else return defaultValue; } #endregion #region Parameter Parsing /// /// Split a parameters string into a list while taking quotes into account /// internal static string[] SplitParameterString(string parameters) { // Ensure the parameter string is trimmed parameters = parameters.Trim(); // Split the string using Regex var matches = Regex.Matches(parameters, @"([a-zA-Z0-9\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled); // Get just the values from the matches var matchArr = new Match[matches.Count]; matches.CopyTo(matchArr, 0); return Array.ConvertAll(matchArr, m => m.Value); } /// /// Returns whether or not the selected item exists /// /// Parts array to be referenced /// Current index /// True if the next item exists, false otherwise internal static bool DoesExist(string[] parts, int index) => index >= 0 && index < parts.Length; /// /// Gets if the flag is supported by the current command /// /// Flag value to check /// True if the flag value is supported, false otherwise protected bool IsFlagSupported(string flag) { if (CommandSupport == null) return false; if (BaseCommand == null) return false; if (!CommandSupport.TryGetValue(BaseCommand, out var supported)) return false; return supported.Contains(flag); } /// /// Returns whether a string is a valid bool /// /// String value to check /// True if it's a valid bool, false otherwise internal 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 internal static bool IsValidInt8(string parameter, sbyte? lowerBound = null, sbyte? upperBound = null) { string value = ExtractFactorFromValue(parameter, out _); if (!sbyte.TryParse(value, out sbyte temp)) return false; else if (lowerBound != null && temp < lowerBound) return false; else if (upperBound != null && 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 internal static bool IsValidInt16(string parameter, short? lowerBound = null, short? upperBound = null) { string value = ExtractFactorFromValue(parameter, out _); if (!short.TryParse(value, out short temp)) return false; else if (lowerBound != null && temp < lowerBound) return false; else if (upperBound != null && 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 internal static bool IsValidInt32(string parameter, int? lowerBound = null, int? upperBound = null) { string value = ExtractFactorFromValue(parameter, out _); if (!int.TryParse(value, out int temp)) return false; else if (lowerBound != null && temp < lowerBound) return false; else if (upperBound != null && 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 internal static bool IsValidInt64(string parameter, long? lowerBound = null, long? upperBound = null) { string value = ExtractFactorFromValue(parameter, out _); if (!long.TryParse(value, out long temp)) return false; else if (lowerBound != null && temp < lowerBound) return false; else if (upperBound != null && temp > upperBound) return false; return true; } /// /// Process a flag parameter /// /// Parts array 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(string[] parts, string flagString, ref int i) => ProcessFlagParameter(parts, null, flagString, ref i); /// /// Process a flag parameter /// /// Parts array 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(string[] 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 /// /// Parts array 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(string[] parts, string flagString, ref int i, bool missingAllowed = false) => ProcessBooleanParameter(parts, null, flagString, ref i, missingAllowed); /// /// Process a boolean parameter /// /// Parts array 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(string[] 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 /// /// Parts array 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(string[] parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt8Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an sbyte parameter /// /// Parts array 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(string[] 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 = ExtractFactorFromValue(parts[i], out long factor); 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 = ExtractFactorFromValue(valuePart, out long factor); 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 /// /// Parts array 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(string[] parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt16Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an Int16 parameter /// /// Parts array 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(string[] 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 = ExtractFactorFromValue(parts[i], out long factor); 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 = ExtractFactorFromValue(valuePart, out long factor); 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 /// /// Parts array 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(string[] parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt32Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an Int32 parameter /// /// Parts array 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(string[] 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 = ExtractFactorFromValue(parts[i], out long factor); 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 = ExtractFactorFromValue(valuePart, out long factor); 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 int.MinValue; } /// /// Process an Int64 parameter /// /// Parts array 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(string[] parts, string flagString, ref int i, bool missingAllowed = false) => ProcessInt64Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process an Int64 parameter /// /// Parts array 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(string[] 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 = ExtractFactorFromValue(parts[i], out long factor); 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 = ExtractFactorFromValue(valuePart, out long factor); 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 long.MinValue; } /// /// Process an string parameter /// /// Parts array 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(string[] parts, string flagString, ref int i, bool missingAllowed = false) => ProcessStringParameter(parts, null, flagString, ref i, missingAllowed); /// /// Process a string parameter /// /// Parts array 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(string[] 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; int loc = parts[i].IndexOf('='); string valuePart = parts[i].Substring(loc + 1); this[longFlagString] = true; return valuePart.Trim('"'); } return string.Empty; } /// /// Process a byte parameter /// /// Parts array 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(string[] parts, string flagString, ref int i, bool missingAllowed = false) => ProcessUInt8Parameter(parts, null, flagString, ref i, missingAllowed); /// /// Process a byte parameter /// /// Parts array 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(string[] 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 = ExtractFactorFromValue(parts[i], out long factor); 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 = ExtractFactorFromValue(valuePart, out long factor); 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 internal static string ExtractFactorFromValue(string value, out long factor) { value = value.Trim('"'); 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; } /// /// Removes a leading 0x if it exists, case insensitive /// /// String with removed leading 0x /// internal 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 } }