using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Web; using SabreTools.Library.Data; using SabreTools.Library.DatFiles; #if MONO using System.IO; #else using Alphaleonis.Win32.Filesystem; using BinaryReader = System.IO.BinaryReader; using FileStream = System.IO.FileStream; #endif namespace SabreTools.Library.Tools { /// /// Include character normalization and replacement mappings /// public static class Style { #region DAT Cleaning /// /// Clean a game (or rom) name to the WoD standard /// /// Name of the game to be cleaned /// The cleaned name public static string CleanGameName(string game) { ///Run the name through the filters to make sure that it's correct game = NormalizeChars(game); game = RussianToLatin(game); game = SearchPattern(game); game = new Regex(@"(([[(].*[\)\]] )?([^([]+))").Match(game).Groups[1].Value; game = game.TrimStart().TrimEnd(); return game; } /// /// Clean a game (or rom) name to the WoD standard /// /// Array representing the path to be cleaned /// The cleaned name public static string CleanGameName(string[] game) { game[game.Length - 1] = CleanGameName(game[game.Length - 1]); string outgame = String.Join(Path.DirectorySeparatorChar.ToString(), game); outgame = outgame.TrimStart().TrimEnd(); return outgame; } /// /// Clean a hash string and pad to the correct size /// /// Hash string to sanitize /// Amount of characters to pad to /// Cleaned string public static string CleanHashData(string hash, int padding) { // If we have a known blank hash, return blank if (string.IsNullOrEmpty(hash) || hash == "-" || hash == "_") { return ""; } // Check to see if it's a "hex" hash hash = hash.Trim().Replace("0x", ""); // If we have a blank hash now, return blank if (string.IsNullOrEmpty(hash)) { return ""; } // If the hash shorter than the required length, pad it if (hash.Length < padding) { hash = hash.PadLeft(padding, '0'); } // If the hash is longer than the required length, it's invalid else if (hash.Length > padding) { return ""; } // Now normalize the hash hash = hash.ToLowerInvariant(); // Otherwise, make sure that every character is a proper match for (int i = 0; i < hash.Length; i++) { if ((hash[i] < '0' || hash[i] > '9') && (hash[i] < 'a' || hash[i] > 'f')) { hash = ""; break; } } return hash; } /// /// Clean a hash string from a Listrom DAT /// /// Hash string to sanitize /// Cleaned string public static string CleanListromHashData(string hash) { if (hash.StartsWith("CRC")) { return hash.Substring(4, 8).ToLowerInvariant(); } else if (hash.StartsWith("SHA1")) { return hash.Substring(5, 40).ToLowerInvariant(); } return hash; } /// /// Generate a proper outfile name based on a DAT and output directory /// /// Output directory /// DAT information /// True if we ignore existing files (default), false otherwise /// Dictionary of output formats mapped to file names public static Dictionary CreateOutfileNames(string outDir, DatFile datdata, bool overwrite = true) { // Create the output dictionary Dictionary outfileNames = new Dictionary(); // Double check the outDir for the end delim if (!outDir.EndsWith(Path.DirectorySeparatorChar.ToString())) { outDir += Path.DirectorySeparatorChar; } // Get the extensions from the output type // AttractMode if ((datdata.DatFormat & DatFormat.AttractMode) != 0) { outfileNames.Add(DatFormat.AttractMode, CreateOutfileNamesHelper(outDir, ".txt", datdata, overwrite)); } // ClrMamePro if ((datdata.DatFormat & DatFormat.ClrMamePro) != 0) { outfileNames.Add(DatFormat.ClrMamePro, CreateOutfileNamesHelper(outDir, ".dat", datdata, overwrite)); }; // CSV if ((datdata.DatFormat & DatFormat.CSV) != 0) { outfileNames.Add(DatFormat.CSV, CreateOutfileNamesHelper(outDir, ".csv", datdata, overwrite)); }; // DOSCenter if ((datdata.DatFormat & DatFormat.DOSCenter) != 0 && (datdata.DatFormat & DatFormat.ClrMamePro) == 0 && (datdata.DatFormat & DatFormat.RomCenter) == 0) { outfileNames.Add(DatFormat.DOSCenter, CreateOutfileNamesHelper(outDir, ".dat", datdata, overwrite)); }; if ((datdata.DatFormat & DatFormat.DOSCenter) != 0 && ((datdata.DatFormat & DatFormat.ClrMamePro) != 0 || (datdata.DatFormat & DatFormat.RomCenter) != 0)) { outfileNames.Add(DatFormat.DOSCenter, CreateOutfileNamesHelper(outDir, ".dc.dat", datdata, overwrite)); } //MAME Listroms if ((datdata.DatFormat & DatFormat.Listroms) != 0 && (datdata.DatFormat & DatFormat.AttractMode) == 0) { outfileNames.Add(DatFormat.Listroms, CreateOutfileNamesHelper(outDir, ".txt", datdata, overwrite)); } if ((datdata.DatFormat & DatFormat.Listroms) != 0 && (datdata.DatFormat & DatFormat.AttractMode) != 0) { outfileNames.Add(DatFormat.Listroms, CreateOutfileNamesHelper(outDir, ".lr.txt", datdata, overwrite)); } // Logiqx XML if ((datdata.DatFormat & DatFormat.Logiqx) != 0) { outfileNames.Add(DatFormat.Logiqx, CreateOutfileNamesHelper(outDir, ".xml", datdata, overwrite)); } // Missfile if ((datdata.DatFormat & DatFormat.MissFile) != 0 && (datdata.DatFormat & DatFormat.AttractMode) == 0) { outfileNames.Add(DatFormat.MissFile, CreateOutfileNamesHelper(outDir, ".txt", datdata, overwrite)); } if ((datdata.DatFormat & DatFormat.MissFile) != 0 && (datdata.DatFormat & DatFormat.AttractMode) != 0) { outfileNames.Add(DatFormat.MissFile, CreateOutfileNamesHelper(outDir, ".miss.txt", datdata, overwrite)); } // OfflineList if (((datdata.DatFormat & DatFormat.OfflineList) != 0) && (datdata.DatFormat & DatFormat.Logiqx) == 0 && (datdata.DatFormat & DatFormat.SabreDat) == 0 && (datdata.DatFormat & DatFormat.SoftwareList) == 0) { outfileNames.Add(DatFormat.OfflineList, CreateOutfileNamesHelper(outDir, ".xml", datdata, overwrite)); } if (((datdata.DatFormat & DatFormat.OfflineList) != 0 && ((datdata.DatFormat & DatFormat.Logiqx) != 0 || (datdata.DatFormat & DatFormat.SabreDat) != 0 || (datdata.DatFormat & DatFormat.SoftwareList) != 0))) { outfileNames.Add(DatFormat.OfflineList, CreateOutfileNamesHelper(outDir, ".ol.xml", datdata, overwrite)); } // Redump MD5 if ((datdata.DatFormat & DatFormat.RedumpMD5) != 0) { outfileNames.Add(DatFormat.RedumpMD5, CreateOutfileNamesHelper(outDir, ".md5", datdata, overwrite)); }; // Redump SFV if ((datdata.DatFormat & DatFormat.RedumpSFV) != 0) { outfileNames.Add(DatFormat.RedumpSFV, CreateOutfileNamesHelper(outDir, ".sfv", datdata, overwrite)); }; // Redump SHA-1 if ((datdata.DatFormat & DatFormat.RedumpSHA1) != 0) { outfileNames.Add(DatFormat.RedumpSHA1, CreateOutfileNamesHelper(outDir, ".sha1", datdata, overwrite)); }; // Redump SHA-256 if ((datdata.DatFormat & DatFormat.RedumpSHA256) != 0) { outfileNames.Add(DatFormat.RedumpSHA256, CreateOutfileNamesHelper(outDir, ".sha256", datdata, overwrite)); }; // RomCenter if ((datdata.DatFormat & DatFormat.RomCenter) != 0 && (datdata.DatFormat & DatFormat.ClrMamePro) == 0) { outfileNames.Add(DatFormat.RomCenter, CreateOutfileNamesHelper(outDir, ".dat", datdata, overwrite)); }; if ((datdata.DatFormat & DatFormat.RomCenter) != 0 && (datdata.DatFormat & DatFormat.ClrMamePro) != 0) { outfileNames.Add(DatFormat.RomCenter, CreateOutfileNamesHelper(outDir, ".rc.dat", datdata, overwrite)); }; // SabreDAT if ((datdata.DatFormat & DatFormat.SabreDat) != 0 && (datdata.DatFormat & DatFormat.Logiqx) == 0) { outfileNames.Add(DatFormat.SabreDat, CreateOutfileNamesHelper(outDir, ".xml", datdata, overwrite)); }; if ((datdata.DatFormat & DatFormat.SabreDat) != 0 && (datdata.DatFormat & DatFormat.Logiqx) != 0) { outfileNames.Add(DatFormat.SabreDat, CreateOutfileNamesHelper(outDir, ".sd.xml", datdata, overwrite)); }; // Software List if ((datdata.DatFormat & DatFormat.SoftwareList) != 0 && (datdata.DatFormat & DatFormat.Logiqx) == 0 && (datdata.DatFormat & DatFormat.SabreDat) == 0) { outfileNames.Add(DatFormat.SoftwareList, CreateOutfileNamesHelper(outDir, ".xml", datdata, overwrite)); } if ((datdata.DatFormat & DatFormat.SoftwareList) != 0 && ((datdata.DatFormat & DatFormat.Logiqx) != 0 || (datdata.DatFormat & DatFormat.SabreDat) != 0)) { outfileNames.Add(DatFormat.SoftwareList, CreateOutfileNamesHelper(outDir, ".sl.xml", datdata, overwrite)); } // TSV if ((datdata.DatFormat & DatFormat.TSV) != 0) { outfileNames.Add(DatFormat.TSV, CreateOutfileNamesHelper(outDir, ".tsv", datdata, overwrite)); }; return outfileNames; } /// /// Help generating the outfile name /// /// Output directory /// Extension to use for the file /// DAT information /// True if we ignore existing files, false otherwise /// String containing the new filename private static string CreateOutfileNamesHelper(string outDir, string extension, DatFile datdata, bool overwrite) { string filename = (String.IsNullOrEmpty(datdata.FileName) ? datdata.Description : datdata.FileName); string outfile = outDir + filename + extension; outfile = (outfile.Contains(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString()) ? outfile.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString()) : outfile); if (!overwrite) { int i = 1; while (File.Exists(outfile)) { outfile = outDir + filename + "_" + i + extension; outfile = (outfile.Contains(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString()) ? outfile.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString()) : outfile); i++; } } return outfile; } /// /// Get the proper extension for the stat output format /// /// Output path to use /// StatDatFormat to get the extension for /// Name of the input file to use /// Dictionary of output formats mapped to file names public static Dictionary CreateOutStatsNames(string outDir, StatReportFormat statDatFormat, string reportName, bool overwrite = true) { Dictionary output = new Dictionary(); // First try to create the output directory if we need to if (!Directory.Exists(outDir)) { Directory.CreateDirectory(outDir); } // Double check the outDir for the end delim if (!outDir.EndsWith(Path.DirectorySeparatorChar.ToString())) { outDir += Path.DirectorySeparatorChar; } // For each output format, get the appropriate stream writer if ((statDatFormat & StatReportFormat.None) != 0) { output.Add(StatReportFormat.None, CreateOutStatsNamesHelper(outDir, ".txt", reportName, overwrite)); } if ((statDatFormat & StatReportFormat.CSV) != 0) { output.Add(StatReportFormat.CSV, CreateOutStatsNamesHelper(outDir, ".csv", reportName, overwrite)); } if ((statDatFormat & StatReportFormat.HTML) != 0) { output.Add(StatReportFormat.HTML, CreateOutStatsNamesHelper(outDir, ".html", reportName, overwrite)); } if ((statDatFormat & StatReportFormat.TSV) != 0) { output.Add(StatReportFormat.TSV, CreateOutStatsNamesHelper(outDir, ".tsv", reportName, overwrite)); } return output; } /// /// Help generating the outstats name /// /// Output directory /// Extension to use for the file /// Name of the input file to use /// True if we ignore existing files, false otherwise /// String containing the new filename private static string CreateOutStatsNamesHelper(string outDir, string extension, string reportName, bool overwrite) { string outfile = outDir + reportName + extension; outfile = (outfile.Contains(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString()) ? outfile.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString()) : outfile); if (!overwrite) { int i = 1; while (File.Exists(outfile)) { outfile = outDir + reportName + "_" + i + extension; outfile = (outfile.Contains(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString()) ? outfile.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString()) : outfile); i++; } } return outfile; } #endregion #region Extensions /// /// Reads the specified number of bytes from the stream, starting from a specified point in the byte array. /// /// The buffer to read data into. /// The starting point in the buffer at which to begin reading into the buffer. /// The number of bytes to read. /// The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, or it might be zero if the end of the stream is reached. public static int ReadReverse(this BinaryReader reader, byte[] buffer, int index, int count) { int retval = reader.Read(buffer, index, count); buffer = buffer.Reverse().ToArray(); return retval; } /// /// Reads the specified number of characters from the stream, starting from a specified point in the character array. /// /// The buffer to read data into. /// The starting point in the buffer at which to begin reading into the buffer. /// The number of characters to read. /// The total number of characters read into the buffer. This might be less than the number of characters requested if that many characters are not currently available, or it might be zero if the end of the stream is reached. public static int ReadReverse(this BinaryReader reader, char[] buffer, int index, int count) { int retval = reader.Read(buffer, index, count); buffer = buffer.Reverse().ToArray(); return retval; } /// /// Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes. /// /// The number of bytes to read. This value must be 0 or a non-negative number or an exception will occur. /// A byte array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached. public static byte[] ReadBytesReverse(this BinaryReader reader, int count) { byte[] retval = reader.ReadBytes(count); retval = retval.Reverse().ToArray(); return retval; } /// /// Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes. /// /// A decimal value read from the current stream. public static decimal ReadDecimalReverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(16); retval = retval.Reverse().ToArray(); int i1 = BitConverter.ToInt32(retval, 0); int i2 = BitConverter.ToInt32(retval, 4); int i3 = BitConverter.ToInt32(retval, 8); int i4 = BitConverter.ToInt32(retval, 12); return new decimal(new int[] { i1, i2, i3, i4 }); } /// /// eads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes. /// /// An 8-byte floating point value read from the current stream. public static double ReadDoubleReverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(8); retval = retval.Reverse().ToArray(); return BitConverter.ToDouble(retval, 0); } /// /// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes. /// /// A 2-byte signed integer read from the current stream. public static short ReadInt16Reverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(2); retval = retval.Reverse().ToArray(); return BitConverter.ToInt16(retval, 0); } /// /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes. /// /// A 4-byte signed integer read from the current stream. public static int ReadInt32Reverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(4); retval = retval.Reverse().ToArray(); return BitConverter.ToInt32(retval, 0); } /// /// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes. /// /// An 8-byte signed integer read from the current stream. public static long ReadInt64Reverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(8); retval = retval.Reverse().ToArray(); return BitConverter.ToInt64(retval, 0); } /// /// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes. /// /// A 4-byte floating point value read from the current stream. public static float ReadSingleReverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(4); retval = retval.Reverse().ToArray(); return BitConverter.ToSingle(retval, 0); } /// /// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by two bytes. /// /// This API is not CLS-compliant. /// /// A 2-byte unsigned integer read from this stream. public static ushort ReadUInt16Reverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(2); retval = retval.Reverse().ToArray(); return BitConverter.ToUInt16(retval, 0); } /// /// Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes. /// /// This API is not CLS-compliant. /// /// A 4-byte unsigned integer read from this stream. public static uint ReadUInt32Reverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(4); retval = retval.Reverse().ToArray(); return BitConverter.ToUInt32(retval, 0); } /// /// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes. /// /// This API is not CLS-compliant. /// /// An 8-byte unsigned integer read from this stream. public static ulong ReadUInt64Reverse(this BinaryReader reader) { byte[] retval = reader.ReadBytes(8); retval = retval.Reverse().ToArray(); return BitConverter.ToUInt64(retval, 0); } #endregion #region String Manipulation /// /// Compare strings as numeric /// /// First string to compare /// Second string to compare /// -1 if s1 comes before s2, 0 if s1 and s2 are equal, 1 if s1 comes after s2 /// I want to be able to handle paths properly with no issue, can I do a recursive call based on separated by path separator? public static int CompareNumeric(string s1, string s2) { // Save the orginal strings, for later comparison string s1orig = s1; string s2orig = s2; // We want to normalize the strings, so we set both to lower case s1 = s1.ToLowerInvariant(); s2 = s2.ToLowerInvariant(); // If the strings are the same exactly, return if (s1 == s2) { return s1orig.CompareTo(s2orig); } // If one is null, then say that's less than if (s1 == null) { return -1; } if (s2 == null) { return 1; } // Now split into path parts after converting AltDirSeparator to DirSeparator s1 = s1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); s2 = s2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); string[] s1parts = s1.Split(Path.DirectorySeparatorChar); string[] s2parts = s2.Split(Path.DirectorySeparatorChar); // Then compare each part in turn for (int j = 0; j < s1parts.Length && j < s2parts.Length; j++) { int compared = CompareNumericPart(s1parts[j], s2parts[j]); if (compared != 0) { return compared; } } // If we got out here, then it looped through at least one of the strings if (s1parts.Length > s2parts.Length) { return 1; } if (s1parts.Length < s2parts.Length) { return -1; } return s1orig.CompareTo(s2orig); } /// /// Helper for CompareNumeric /// /// First string to compare /// Second string to compare /// -1 if s1 comes before s2, 0 if s1 and s2 are equal, 1 if s1 comes after s2 private static int CompareNumericPart(string s1, string s2) { // Otherwise, loop through until we have an answer for (int i = 0; i < s1.Length && i < s2.Length; i++) { int s1c = s1[i]; int s2c = s2[i]; // If the characters are the same, continue if (s1c == s2c) { continue; } // If they're different, check which one was larger if (s1c > s2c) { return 1; } if (s1c < s2c) { return -1; } } // If we got out here, then it looped through at least one of the strings if (s1.Length > s2.Length) { return 1; } if (s1.Length < s2.Length) { return -1; } return 0; } /// /// Convert all characters that are not considered XML-safe /// /// Input string to clean /// Cleaned string public static string ConvertXMLUnsafeCharacters(string s) { return new String(s.Select(c => (c == 0x9 || c == 0xA || c == 0xD || (c >= 0x20 && c <= 0xD77F) || (c >= 0xE000 && c <= 0xFFFD) || (c >= 0x10000 && c <= 0x10FFFF) ? c : HttpUtility.HtmlEncode(c)[0])) .ToArray()); } /// /// Get a proper romba sub path /// /// SHA-1 hash to get the path for /// Subfolder path for the given hash public static string GetRombaPath(string hash) { // If the hash isn't the right size, then we return null if (hash.Length != Constants.SHA1Length) // TODO: When updating to SHA-256, this needs to update to Constants.SHA256Length { return null; } return Path.Combine(hash.Substring(0, 2), hash.Substring(2, 2), hash.Substring(4, 2), hash.Substring(6, 2), hash + ".gz"); } /// /// Get the multiplier to be used with the size given /// /// String with possible size with extension /// Tuple of multiplier to use on final size and fixed size string public static long GetSizeFromString(string sizestring) { // Make sure the string is in lower case sizestring = sizestring.ToLowerInvariant(); // Get any trailing size identifiers long multiplier = 1; if (sizestring.EndsWith("k") || sizestring.EndsWith("kb")) { multiplier = Constants.KiloByte; } else if (sizestring.EndsWith("ki") || sizestring.EndsWith("kib")) { multiplier = Constants.KibiByte; } else if (sizestring.EndsWith("m") || sizestring.EndsWith("mb")) { multiplier = Constants.MegaByte; } else if (sizestring.EndsWith("mi") || sizestring.EndsWith("mib")) { multiplier = Constants.MibiByte; } else if (sizestring.EndsWith("g") || sizestring.EndsWith("gb")) { multiplier = Constants.GigaByte; } else if (sizestring.EndsWith("gi") || sizestring.EndsWith("gib")) { multiplier = Constants.GibiByte; } else if (sizestring.EndsWith("t") || sizestring.EndsWith("tb")) { multiplier = Constants.TeraByte; } else if (sizestring.EndsWith("ti") || sizestring.EndsWith("tib")) { multiplier = Constants.TibiByte; } else if (sizestring.EndsWith("p") || sizestring.EndsWith("pb")) { multiplier = Constants.PetaByte; } else if (sizestring.EndsWith("pi") || sizestring.EndsWith("pib")) { multiplier = Constants.PibiByte; } // Remove any trailing identifiers sizestring = sizestring.TrimEnd(new char[] { 'k', 'm', 'g', 't', 'p', 'i', 'b', ' ' }); // Now try to get the size from the string if (!Int64.TryParse(sizestring, out long size)) { size = -1; } else { size *= multiplier; } return size; } /// /// Get if a string contains Unicode characters /// /// Input string to test /// True if the string contains at least one Unicode character, false otherwise public static bool IsUnicode(string s) { return (s.Any(c => c > 255)); } /// /// Remove all chars that are considered path unsafe /// /// Input string to clean /// Cleaned string public static string RemovePathUnsafeCharacters(string s) { List invalidPath = Path.GetInvalidPathChars().ToList(); return new string(s.Where(c => !invalidPath.Contains(c)).ToArray()); } /// /// Remove all unicode-specific chars from a string /// /// Input string to clean /// Cleaned string public static string RemoveUnicodeCharacters(string s) { return new string(s.Where(c => c <= 255).ToArray()); } /// /// Split a line as if it were a CMP rom line /// /// Line to split /// Line split /// Uses code from http://stackoverflow.com/questions/554013/regular-expression-to-split-on-spaces-unless-in-quotes public static string[] SplitLineAsCMP(string s) { // Get the opening and closing brace locations int openParenLoc = s.IndexOf('('); int closeParenLoc = s.LastIndexOf(')'); // Now remove anything outside of those braces, including the braces s = s.Substring(openParenLoc + 1, closeParenLoc - openParenLoc - 1); s = s.Trim(); // Now we get each string, divided up as cleanly as possible string[] matches = Regex //.Matches(s, @"([^\s]*""[^""]+""[^\s]*)|[^""]?\w+[^""]?") .Matches(s, @"[^\s""]+|""[^""]*""") .Cast() .Select(m => m.Groups[0].Value) .ToArray(); return matches; } #endregion #region System.IO.Path Replacements /// /// Replacement for System.IO.Path.GetDirectoryName /// /// Path to get directory name out of /// Directory name from path /// public static string GetDirectoryName(string s) { if (s == null) { return ""; } if (s.Contains("/")) { string[] tempkey = s.Split('/'); return String.Join("/", tempkey.Take(tempkey.Length - 1)); } else if (s.Contains("\\")) { string[] tempkey = s.Split('\\'); return String.Join("\\", tempkey.Take(tempkey.Length - 1)); } return ""; } /// /// Replacement for System.IO.Path.GetFileName /// /// Path to get file name out of /// File name from path /// public static string GetFileName(string s) { if (s == null) { return ""; } if (s.Contains("/")) { string[] tempkey = s.Split('/'); return tempkey.Last(); } else if (s.Contains("\\")) { string[] tempkey = s.Split('\\'); return tempkey.Last(); } return s; } /// /// Replacement for System.IO.Path.GetFileNameWithoutExtension /// /// Path to get file name out of /// File name without extension from path /// public static string GetFileNameWithoutExtension(string s) { s = GetFileName(s); string[] tempkey = s.Split('.'); if (tempkey.Count() == 1) { return s; } return String.Join(".", tempkey.Take(tempkey.Length - 1)); } #endregion #region WoD-based String Cleaning /// /// Replace accented characters /// /// String to be parsed /// String with characters replaced public static string NormalizeChars(string input) { string[,] charmap = { { "Á", "A" }, { "á", "a" }, { "À", "A" }, { "à", "a" }, { "Â", "A" }, { "â", "a" }, { "Ä", "Ae" }, { "ä", "ae" }, { "Ã", "A" }, { "ã", "a" }, { "Å", "A" }, { "å", "a" }, { "Æ", "Ae" }, { "æ", "ae" }, { "Ç", "C" }, { "ç", "c" }, { "Ð", "D" }, { "ð", "d" }, { "É", "E" }, { "é", "e" }, { "È", "E" }, { "è", "e" }, { "Ê", "E" }, { "ê", "e" }, { "Ë", "E" }, { "ë", "e" }, { "ƒ", "f" }, { "Í", "I" }, { "í", "i" }, { "Ì", "I" }, { "ì", "i" }, { "Î", "I" }, { "î", "i" }, { "Ï", "I" }, { "ï", "i" }, { "Ñ", "N" }, { "ñ", "n" }, { "Ó", "O" }, { "ó", "o" }, { "Ò", "O" }, { "ò", "o" }, { "Ô", "O" }, { "ô", "o" }, { "Ö", "Oe" }, { "ö", "oe" }, { "Õ", "O" }, { "õ", "o" }, { "Ø", "O" }, { "ø", "o" }, { "Š", "S" }, { "š", "s" }, { "ß", "ss" }, { "Þ", "B" }, { "þ", "b" }, { "Ú", "U" }, { "ú", "u" }, { "Ù", "U" }, { "ù", "u" }, { "Û", "U" }, { "û", "u" }, { "Ü", "Ue" }, { "ü", "ue" }, { "ÿ", "y" }, { "Ý", "Y" }, { "ý", "y" }, { "Ž", "Z" }, { "ž", "z" }, }; for (int i = 0; i < charmap.GetLength(0); i++) { input = input.Replace(charmap[i, 0], charmap[i, 1]); } return input; } /// /// Convert Cyrillic lettering to Latin lettering /// /// String to be parsed /// String with characters replaced public static string RussianToLatin(string input) { string[,] charmap = { { "А", "A" }, { "Б", "B" }, { "В", "V" }, { "Г", "G" }, { "Д", "D" }, { "Е", "E" }, { "Ё", "Yo" }, { "Ж", "Zh" }, { "З", "Z" }, { "И", "I" }, { "Й", "J" }, { "К", "K" }, { "Л", "L" }, { "М", "M" }, { "Н", "N" }, { "О", "O" }, { "П", "P" }, { "Р", "R" }, { "С", "S" }, { "Т", "T" }, { "У", "U" }, { "Ф", "f" }, { "Х", "Kh" }, { "Ц", "Ts" }, { "Ч", "Ch" }, { "Ш", "Sh" }, { "Щ", "Sch" }, { "Ъ", "" }, { "Ы", "y" }, { "Ь", "" }, { "Э", "e" }, { "Ю", "yu" }, { "Я", "ya" }, { "а", "a" }, { "б", "b" }, { "в", "v" }, { "г", "g" }, { "д", "d" }, { "е", "e" }, { "ё", "yo" }, { "ж", "zh" }, { "з", "z" }, { "и", "i" }, { "й", "j" }, { "к", "k" }, { "л", "l" }, { "м", "m" }, { "н", "n" }, { "о", "o" }, { "п", "p" }, { "р", "r" }, { "с", "s" }, { "т", "t" }, { "у", "u" }, { "ф", "f" }, { "х", "kh" }, { "ц", "ts" }, { "ч", "ch" }, { "ш", "sh" }, { "щ", "sch" }, { "ъ", "" }, { "ы", "y" }, { "ь", "" }, { "э", "e" }, { "ю", "yu" }, { "я", "ya" }, }; for (int i = 0; i < charmap.GetLength(0); i++) { input = input.Replace(charmap[i, 0], charmap[i, 1]); } return input; } /// /// Replace special characters and patterns /// /// String to be parsed /// String with characters replaced public static string SearchPattern(string input) { string[,] charmap = { { @"~", " - " }, { @"_", " " }, { @":", " " }, { @">", ")" }, { @"<", "(" }, { @"\|", "-" }, { "\"", "'" }, { @"\*", "." }, { @"\\", "-" }, { @"/", "-" }, { @"\?", " " }, { @"\(([^)(]*)\(([^)]*)\)([^)(]*)\)", " " }, { @"\(([^)]+)\)", " " }, { @"\[([^]]+)\]", " " }, { @"\{([^}]+)\}", " " }, { @"(ZZZJUNK|ZZZ-UNK-|ZZZ-UNK |zzz unknow |zzz unk |Copy of |[.][a-z]{3}[.][a-z]{3}[.]|[.][a-z]{3}[.])", " " }, { @" (r|rev|v|ver)\s*[\d\.]+[^\s]*", " " }, { @"(( )|(\A))(\d{6}|\d{8})(( )|(\Z))", " " }, { @"(( )|(\A))(\d{1,2})-(\d{1,2})-(\d{4}|\d{2})", " " }, { @"(( )|(\A))(\d{4}|\d{2})-(\d{1,2})-(\d{1,2})", " " }, { @"[-]+", "-" }, { @"\A\s*\)", " " }, { @"\A\s*(,|-)", " " }, { @"\s+", " " }, { @"\s+,", "," }, { @"\s*(,|-)\s*\Z", " " }, }; for (int i = 0; i < charmap.GetLength(0); i++) { input = Regex.Replace(input, charmap[i, 0], charmap[i, 1]); } return input; } #endregion #region Externally sourced methods /// /// Returns the human-readable file size for an arbitrary, 64-bit file size /// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB" /// /// /// Human-readable file size /// http://www.somacon.com/p576.php public static string GetBytesReadable(long input) { // Get absolute value long absolute_i = (input < 0 ? -input : input); // Determine the suffix and readable value string suffix; double readable; if (absolute_i >= 0x1000000000000000) // Exabyte { suffix = "EB"; readable = (input >> 50); } else if (absolute_i >= 0x4000000000000) // Petabyte { suffix = "PB"; readable = (input >> 40); } else if (absolute_i >= 0x10000000000) // Terabyte { suffix = "TB"; readable = (input >> 30); } else if (absolute_i >= 0x40000000) // Gigabyte { suffix = "GB"; readable = (input >> 20); } else if (absolute_i >= 0x100000) // Megabyte { suffix = "MB"; readable = (input >> 10); } else if (absolute_i >= 0x400) // Kilobyte { suffix = "KB"; readable = input; } else { return input.ToString("0 B"); // Byte } // Divide by 1024 to get fractional value readable = (readable / 1024); // Return formatted number with suffix return readable.ToString("0.### ") + suffix; } /// /// http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa /// public static string ByteArrayToString(byte[] bytes) { try { string hex = BitConverter.ToString(bytes); return hex.Replace("-", string.Empty).ToLowerInvariant(); } catch { return null; } } /// /// http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa /// public static byte[] StringToByteArray(string hex) { try { int NumberChars = hex.Length; byte[] bytes = new byte[NumberChars / 2]; for (int i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); return bytes; } catch { return null; } } /// /// http://stackoverflow.com/questions/5613279/c-sharp-hex-to-ascii /// public static string ConvertHexToAscii(string hexString) { if (hexString.Contains("-")) { hexString = hexString.Replace("-", ""); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < hexString.Length; i += 2) { String hs = hexString.Substring(i, 2); sb.Append(Convert.ToChar(Convert.ToUInt32(hs, 16))); } return sb.ToString(); } /// /// http://stackoverflow.com/questions/15920741/convert-from-string-ascii-to-string-hex /// public static string ConvertAsciiToHex(string asciiString) { string hexOutput = ""; foreach (char _eachChar in asciiString.ToCharArray()) { // Get the integral value of the character. int value = Convert.ToInt32(_eachChar); // Convert the decimal value to a hexadecimal value in string form. hexOutput += String.Format("{0:X2}", value).Remove(0, 2); // to make output as your eg // hexOutput +=" "+ String.Format("{0:X}", value); } return hexOutput; } /// /// Adapted from 7-zip Source Code: CPP/Windows/TimeUtils.cpp:FileTimeToDosTime /// public static uint ConvertDateTimeToMsDosTimeFormat(DateTime dateTime) { uint year = (uint)((dateTime.Year - 1980) % 128); uint mon = (uint)dateTime.Month; uint day = (uint)dateTime.Day; uint hour = (uint)dateTime.Hour; uint min = (uint)dateTime.Minute; uint sec = (uint)dateTime.Second; return (year << 25) | (mon << 21) | (day << 16) | (hour << 11) | (min << 5) | (sec >> 1); } /// /// Adapted from 7-zip Source Code: CPP/Windows/TimeUtils.cpp:DosTimeToFileTime /// public static DateTime ConvertMsDosTimeFormatToDateTime(uint msDosDateTime) { return new DateTime((int)(1980 + (msDosDateTime >> 25)), (int)((msDosDateTime >> 21) & 0xF), (int)((msDosDateTime >> 16) & 0x1F), (int)((msDosDateTime >> 11) & 0x1F), (int)((msDosDateTime >> 5) & 0x3F), (int)((msDosDateTime & 0x1F) * 2)); } /// /// Determines a text file's encoding by analyzing its byte order mark (BOM). /// Defaults to ASCII when detection of the text file's endianness fails. /// http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding /// /// The text file to analyze. /// The detected encoding. public static Encoding GetEncoding(string filename) { // Read the BOM var bom = new byte[4]; FileStream file = FileTools.TryOpenRead(filename); file.Read(bom, 0, 4); file.Dispose(); // Analyze the BOM if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7; if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8; if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32; return Encoding.Default; } /// /// http://stackoverflow.com/questions/1600962/displaying-the-build-date /// public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null) { var filePath = assembly.Location; const int c_PeHeaderOffset = 60; const int c_LinkerTimestampOffset = 8; var buffer = new byte[2048]; using (var stream = FileTools.TryOpenRead(filePath)) stream.Read(buffer, 0, 2048); var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset); var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset); var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); var linkTimeUtc = epoch.AddSeconds(secondsSince1970); var tz = target ?? TimeZoneInfo.Local; var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz); return localTime; } /// /// Indicates whether the specified array is null or has a length of zero. /// https://stackoverflow.com/questions/8560106/isnullorempty-equivalent-for-array-c-sharp /// /// The array to test. /// true if the array parameter is null or has a length of zero; otherwise, false. public static bool IsNullOrEmpty(this Array array) { return (array == null || array.Length == 0); } #endregion } }