// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.Text; namespace dnlib.DotNet { /// /// Compares byte arrays /// sealed class ByteArrayEqualityComparer : IEqualityComparer { /// /// Default instance /// public static readonly ByteArrayEqualityComparer Instance = new ByteArrayEqualityComparer(); /// public bool Equals(byte[] x, byte[] y) => Utils.Equals(x, y); /// public int GetHashCode(byte[] obj) => Utils.GetHashCode(obj); } static class Utils { /// /// Convert a byte[] to a /// /// All bytes /// true if output should be in upper case hex /// as a hex string internal static string ToHex(byte[] bytes, bool upper) { if (bytes is null) return ""; var chars = new char[bytes.Length * 2]; for (int i = 0, j = 0; i < bytes.Length; i++) { byte b = bytes[i]; chars[j++] = ToHexChar(b >> 4, upper); chars[j++] = ToHexChar(b & 0x0F, upper); } return new string(chars); } static char ToHexChar(int val, bool upper) { if (0 <= val && val <= 9) return (char)(val + (int)'0'); return (char)(val - 10 + (upper ? (int)'A' : (int)'a')); } /// /// Converts a hex string to a byte[] /// /// A string with an even number of hex characters /// converted to a byte[] or null /// if is invalid internal static byte[] ParseBytes(string hexString) { try { if (hexString.Length % 2 != 0) return null; var bytes = new byte[hexString.Length / 2]; for (int i = 0; i < hexString.Length; i += 2) { int upper = TryParseHexChar(hexString[i]); int lower = TryParseHexChar(hexString[i + 1]); if (upper < 0 || lower < 0) return null; bytes[i / 2] = (byte)((upper << 4) | lower); } return bytes; } catch { return null; } } /// /// Converts a character to a hex digit /// /// Hex character /// 0x00-0x0F if successful, -1 if is not /// a valid hex digit static int TryParseHexChar(char c) { if ('0' <= c && c <= '9') return (ushort)c - (ushort)'0'; if ('a' <= c && c <= 'f') return 10 + (ushort)c - (ushort)'a'; if ('A' <= c && c <= 'F') return 10 + (ushort)c - (ushort)'A'; return -1; } /// /// Compares two byte arrays /// /// Byte array #1 /// Byte array #2 /// < 0 if a < b, 0 if a == b, > 0 if a > b internal static int CompareTo(byte[] a, byte[] b) { if (a == b) return 0; if (a is null) return -1; if (b is null) return 1; int count = Math.Min(a.Length, b.Length); for (int i = 0; i < count; i++) { var ai = a[i]; var bi = b[i]; if (ai < bi) return -1; if (ai > bi) return 1; } return a.Length.CompareTo(b.Length); } /// /// Checks whether two byte arrays are equal /// /// First /// Second /// true if same, false otherwise internal static bool Equals(byte[] a, byte[] b) { if (a == b) return true; if (a is null || b is null) return false; if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) return false; } return true; } /// /// Gets the hash code of a byte array /// /// Byte array /// The hash code internal static int GetHashCode(byte[] a) { if (a is null || a.Length == 0) return 0; int count = Math.Min(a.Length / 2, 20); if (count == 0) count = 1; uint hash = 0; for (int i = 0, j = a.Length - 1; i < count; i++, j--) { hash ^= a[i] | ((uint)a[j] << 8); hash = (hash << 13) | (hash >> 19); } return (int)hash; } /// /// Compares two versions /// /// This differs from if the build /// and/or revision numbers haven't been initialized or if one of the args is null. /// /// Version #1 or null to be treated as v0.0.0.0 /// Version #2 or null to be treated as v0.0.0.0 /// < 0 if a < b, 0 if a == b, > 0 if a > b internal static int CompareTo(Version a, Version b) { if (a is null) a = new Version(); if (b is null) b = new Version(); if (a.Major != b.Major) return a.Major.CompareTo(b.Major); if (a.Minor != b.Minor) return a.Minor.CompareTo(b.Minor); if (GetDefaultVersionValue(a.Build) != GetDefaultVersionValue(b.Build)) return GetDefaultVersionValue(a.Build).CompareTo(GetDefaultVersionValue(b.Build)); return GetDefaultVersionValue(a.Revision).CompareTo(GetDefaultVersionValue(b.Revision)); } /// /// Checks whether two versions are the same /// /// This differs from if the build /// and/or revision numbers haven't been initialized or if one of the args is null. /// /// Version #1 or null to be treated as v0.0.0.0 /// Version #2 or null to be treated as v0.0.0.0 /// true if same, false otherwise internal static bool Equals(Version a, Version b) => CompareTo(a, b) == 0; /// /// Creates a new instance with no undefined version values (eg. /// the build and revision values won't be -1). /// /// A instance /// A new instance internal static Version CreateVersionWithNoUndefinedValues(Version a) { if (a is null) return new Version(0, 0, 0, 0); return new Version(a.Major, a.Minor, GetDefaultVersionValue(a.Build), GetDefaultVersionValue(a.Revision)); } static int GetDefaultVersionValue(int val) => val == -1 ? 0 : val; /// /// Parses a version string /// /// Version string /// A new or null if /// is an invalid version internal static Version ParseVersion(string versionString) { try { return Utils.CreateVersionWithNoUndefinedValues(new Version(versionString)); } catch { return null; } } /// /// Compares two locales (cultures) /// /// First /// Second /// < 0 if a < b, 0 if a == b, > 0 if a > b internal static int LocaleCompareTo(UTF8String a, UTF8String b) => GetCanonicalLocale(a).CompareTo(GetCanonicalLocale(b)); /// /// Compares two locales (cultures) /// /// First /// Second /// true if same, false otherwise internal static bool LocaleEquals(UTF8String a, UTF8String b) => LocaleCompareTo(a, b) == 0; /// /// Compares two locales (cultures) /// /// First /// Second /// < 0 if a < b, 0 if a == b, > 0 if a > b internal static int LocaleCompareTo(UTF8String a, string b) => GetCanonicalLocale(a).CompareTo(GetCanonicalLocale(b)); /// /// Compares two locales (cultures) /// /// First /// Second /// true if same, false otherwise internal static bool LocaleEquals(UTF8String a, string b) => LocaleCompareTo(a, b) == 0; /// /// Gets the hash code of a locale /// /// Value /// The hash code internal static int GetHashCodeLocale(UTF8String a) => GetCanonicalLocale(a).GetHashCode(); static string GetCanonicalLocale(UTF8String locale) => GetCanonicalLocale(UTF8String.ToSystemStringOrEmpty(locale)); static string GetCanonicalLocale(string locale) { var s = locale.ToUpperInvariant(); if (s == "NEUTRAL") s = string.Empty; return s; } /// /// Align up /// /// Value /// Alignment public static uint AlignUp(uint v, uint alignment) => (v + alignment - 1) & ~(alignment - 1); /// /// Align up /// /// Value /// Alignment public static int AlignUp(int v, uint alignment) => (int)AlignUp((uint)v, alignment); /// /// Rounds up the provided number to the next power of 2 /// /// The number to round public static uint RoundToNextPowerOfTwo(uint num) { num--; num |= num >> 1; num |= num >> 2; num |= num >> 4; num |= num >> 8; num |= num >> 16; return num + 1; } } }