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 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; #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? 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 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) { this.System = system; this.Type = 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 protected 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 protected 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 protected static string? GetStringSetting(Dictionary settings, string key, string? defaultValue) { if (settings.ContainsKey(key)) return settings[key]; else return defaultValue; } #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; /// /// 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 (this.BaseCommand == null) return false; if (!CommandSupport.TryGetValue(this.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 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; 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 /// /// 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 } }