#nullable enable using System; using System.IO; using System.Text; using System.Linq; using System.Collections.Generic; using Mono.Cecil; using System.Runtime.CompilerServices; using System.Threading; using System.Diagnostics.CodeAnalysis; using Version = Hive.Versioning.Version; #if NET3 using File = Net3_Proxy.File; #endif namespace IPA.Utilities { /// /// A class providing static utility functions that in any other language would just *exist*. /// public static class Utils { /// /// Converts a hex string to a byte array. /// /// the hex stream /// the corresponding byte array public static byte[] StringToByteArray(string hex) { if (hex is null) throw new ArgumentNullException(nameof(hex)); 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; } /// /// Converts a byte array to a hex string. /// /// the byte array /// the hex form of the array public static string ByteArrayToString(byte[] ba) { if (ba is null) throw new ArgumentNullException(nameof(ba)); var hex = new StringBuilder(ba.Length * 2); foreach (byte b in ba) _ = hex.AppendFormat("{0:x2}", b); return hex.ToString(); } // Copyright (c) 2008-2013 Hafthor Stefansson // Distributed under the MIT/X11 software license // Ref: http://www.opensource.org/licenses/mit-license.php. // From: https://stackoverflow.com/a/8808245/3117125 /// /// Uses unsafe code to compare 2 byte arrays quickly. /// /// array 1 /// array 2 /// whether or not they are byte-for-byte equal public static unsafe bool UnsafeCompare(byte[] a1, byte[] a2) { if (a1 == a2) return true; if (a1 == null || a2 == null || a1.Length != a2.Length) return false; fixed (byte* p1 = a1, p2 = a2) { byte* x1 = p1, x2 = p2; int l = a1.Length; for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8) if (*(long*)x1 != *(long*)x2) return false; if ((l & 4) != 0) { if (*(int*)x1 != *(int*)x2) return false; x1 += 4; x2 += 4; } if ((l & 2) != 0) { if (*(short*)x1 != *(short*)x2) return false; x1 += 2; x2 += 2; } if ((l & 1) != 0) if (*x1 != *x2) return false; return true; } } /// /// Gets a path relative to the provided folder. /// /// the file to relativize /// the source folder /// a path to get from to public static string GetRelativePath(string file, string folder) { if (file is null) throw new ArgumentNullException(nameof(file)); if (folder is null) throw new ArgumentNullException(nameof(folder)); var pathUri = new Uri(file); // Folders must end in a slash if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { folder += Path.DirectorySeparatorChar; } var folderUri = new Uri(folder); return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar)); } /// /// Copies all files from to . /// /// the source directory /// the destination directory /// the filename of the file to append together /// a delegate called when there is an error copying. Return true to keep going. public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string appendFileName = "", Func? onCopyException = null) { if (source is null) throw new ArgumentNullException(nameof(source)); if (target is null) throw new ArgumentNullException(nameof(target)); if (source.FullName.ToUpperInvariant() == target.FullName.ToUpperInvariant()) { return; } // Check if the target directory exists, if not, create it. if (Directory.Exists(target.FullName) == false) { _ = Directory.CreateDirectory(target.FullName); } // Copy each file into it's new directory. foreach (var fi in source.GetFiles()) { try { if (fi.Name == appendFileName) File.AppendAllLines(Path.Combine(target.ToString(), fi.Name), File.ReadAllLines(fi.FullName)); else _ = fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true); } catch (Exception e) { var keepOn = onCopyException?.Invoke(e, fi); if (!keepOn.Unwrap()) throw; } } // Copy each subdirectory using recursion. foreach (var diSourceSubDir in source.GetDirectories()) { var nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name); CopyAll(diSourceSubDir, nextTargetSubDir, appendFileName, onCopyException); } } /// /// Whether you can safely use without Mono throwing a fit. /// /// if you can use safely, otherwise public static bool CanUseDateTimeNowSafely { get; private set; } = true; private static bool DateTimeSafetyUnknown = true; /// /// Returns if supported, otherwise . /// /// the current if supported, otherwise some indeterminant increasing value. public static DateTime CurrentTime() { if (DateTimeSafetyUnknown) { var time = DateTime.MinValue; try { time = DateTime.Now; } catch (TimeZoneNotFoundException) { // Mono did a fucky wucky and we need to avoid this call CanUseDateTimeNowSafely = false; } DateTimeSafetyUnknown = false; return time; } else { if (CanUseDateTimeNowSafely) return DateTime.Now; else return DateTime.UtcNow; // return MinValue as a fallback } } /// /// Compares a pair of s ignoring both the prerelease and build fields. /// /// the left value /// the right value /// < 0 if l is less than r, 0 if they are equal in the numeric portion, or > 0 if l is greater than r [Obsolete("Use Hive.Versioning.Version overload instead.")] public static int VersionCompareNoPrerelease(SemVer.Version l, SemVer.Version r) => VersionCompareNoPrerelease(l?.UnderlyingVersion!, r?.UnderlyingVersion!); /// /// Compares a pair of s ignoring both the prerelease and build fields. /// /// the left value /// the right value /// < 0 if l is less than r, 0 if they are equal in the numeric portion, or > 0 if l is greater than r public static int VersionCompareNoPrerelease(Version l, Version r) { if (l is null) throw new ArgumentNullException(nameof(l)); if (r is null) throw new ArgumentNullException(nameof(r)); var cmpVal = l.Major.CompareTo(r.Major); if (cmpVal != 0) return cmpVal; cmpVal = l.Minor.CompareTo(r.Minor); if (cmpVal != 0) return cmpVal; cmpVal = l.Patch.CompareTo(r.Patch); return cmpVal; } /// /// An object used to manage scope guards. /// /// /// /// using var _ = new Utils.ScopeGuardObject(() => RunOnScopeExit(value)); /// /// /// [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "This type needs to be public to avoid allocations")] [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "This type is never supposed to be compared")] public struct ScopeGuardObject : IDisposable { private readonly Action action; /// /// Creates a new scope guard that will invoke when disposed. /// /// the action to run on dispose public ScopeGuardObject(Action action) => this.action = action; void IDisposable.Dispose() => action?.Invoke(); } /// /// Creates a scope guard for a given . /// /// the to run on dispose /// a that will run on disposal /// /// /// using var _ = Utils.ScopeGuard(() => RunOnScopeExit(value)); /// /// public static ScopeGuardObject ScopeGuard(Action action) => new(action); /// /// Deconstructs a as its key and value. /// /// The type of the key. /// The type of the value. /// The to deconstruct. /// The key in . /// The value in . public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) => (key, value) = (kvp.Key, kvp.Value); internal static bool HasInterface(this TypeDefinition? type, string interfaceFullName) { return (type?.Interfaces?.Any(i => i.InterfaceType.FullName == interfaceFullName) ?? false) || (type?.Interfaces?.Any(t => HasInterface(t?.InterfaceType?.Resolve(), interfaceFullName)) ?? false); } #if NET4 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static IEnumerable StrJP(this IEnumerable a) => a; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static IEnumerable StrJP(this IEnumerable a) => a.Select(o => $"{o}" /* safer than .ToString() */); #endif #if NET3 internal static string[] StrJP(this IEnumerable a) => a.ToArray(); internal static string[] StrJP(this IEnumerable a) => a.Select(o => $"{o}" /* safer than .ToString() */).ToArray(); #endif } }