using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace SabreTools.Core.Tools { /// /// Static utility functions used throughout the library /// public static class Utilities { /// /// Convert a byte array to a hex string /// /// Byte array to convert /// Hex string representing the byte array /// http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa public static string ByteArrayToString(byte[] bytes) { // If we get null in, we send null out if (bytes == null) return null; try { string hex = BitConverter.ToString(bytes); return hex.Replace("-", string.Empty).ToLowerInvariant(); } catch { return null; } } /// /// Convert a hex string to a byte array /// /// Hex string to convert /// Byte array represenging the hex string /// http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa public static byte[] StringToByteArray(string hex) { // If we get null in, we send null out if (hex == null) return null; 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; } } /// /// Convert .NET DateTime to MS-DOS date format /// /// .NET DateTime object to convert /// UInt32 representing the MS-DOS date /// /// Adapted from 7-zip Source Code: CPP/Windows/TimeUtils.cpp:FileTimeToDosTime /// public static long 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); } /// /// Convert MS-DOS date format to .NET DateTime /// /// UInt32 representing the MS-DOS date to convert /// .NET DateTime object representing the converted date /// /// 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)); } /// /// Get a proper romba sub path /// /// SHA-1 hash to get the path for /// Positive value representing the depth of the depot /// Subfolder path for the given hash public static string GetDepotPath(string hash, int depth) { // If the hash is null or empty, then we return null if (string.IsNullOrEmpty(hash)) return null; // If the hash isn't the right size, then we return null if (hash.Length != Constants.SHA1Length) return null; // Cap the depth between 0 and 20, for now if (depth < 0) depth = 0; else if (depth > Constants.SHA1ZeroBytes.Length) depth = Constants.SHA1ZeroBytes.Length; // Loop through and generate the subdirectory string path = string.Empty; for (int i = 0; i < depth; i++) { path += hash.Substring(i * 2, 2) + Path.DirectorySeparatorChar; } // Now append the filename path += $"{hash}.gz"; return path; } /// /// Get if the given path has a valid DAT extension /// /// Path to check /// True if the extension is valid, false otherwise public static bool HasValidDatExtension(string? path) { // If the path is null or empty, then we return false if (string.IsNullOrEmpty(path)) return false; // Get the extension from the path, if possible string ext = Path.GetExtension(path).TrimStart('.').ToLowerInvariant(); // Check against the list of known DAT extensions return ext switch { "csv" => true, "dat" => true, "json" => true, "md5" => true, "sfv" => true, "sha1" => true, "sha256" => true, "sha384" => true, "sha512" => true, "spamsum" => true, "ssv" => true, "tsv" => true, "txt" => true, "xml" => true, _ => false, }; } /// Indicates whether the specified array is null or has a length of zero /// /// The array to test /// true if the array parameter is null or has a length of zero; otherwise, false. /// https://stackoverflow.com/questions/8560106/isnullorempty-equivalent-for-array-c-sharp public static bool IsNullOrEmpty(this Array array) { return array == null || array.Length == 0; } /// /// 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()); } /// /// Returns if the first byte array starts with the second array /// /// First byte array to compare /// Second byte array to compare /// True if the input arrays should match exactly, false otherwise (default) /// True if the first byte array starts with the second, false otherwise public static bool StartsWith(this byte[] arr1, byte[] arr2, bool exact = false) { // If we have any invalid inputs, we return false if (arr1 == null || arr2 == null || arr1.Length == 0 || arr2.Length == 0 || arr2.Length > arr1.Length || (exact && arr1.Length != arr2.Length)) { return false; } // Otherwise, loop through and see for (int i = 0; i < arr2.Length; i++) { if (arr1[i] != arr2[i]) return false; } return true; } } }