You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
12 KiB

  1. #nullable enable
  2. using System;
  3. using System.IO;
  4. using System.Text;
  5. using System.Linq;
  6. using System.Collections.Generic;
  7. using Mono.Cecil;
  8. using System.Runtime.CompilerServices;
  9. using System.Threading;
  10. using System.Diagnostics.CodeAnalysis;
  11. #if NET3
  12. using File = Net3_Proxy.File;
  13. #endif
  14. namespace IPA.Utilities
  15. {
  16. /// <summary>
  17. /// A class providing static utility functions that in any other language would just *exist*.
  18. /// </summary>
  19. public static class Utils
  20. {
  21. /// <summary>
  22. /// Converts a hex string to a byte array.
  23. /// </summary>
  24. /// <param name="hex">the hex stream</param>
  25. /// <returns>the corresponding byte array</returns>
  26. public static byte[] StringToByteArray(string hex)
  27. {
  28. if (hex is null)
  29. throw new ArgumentNullException(nameof(hex));
  30. int numberChars = hex.Length;
  31. byte[] bytes = new byte[numberChars / 2];
  32. for (int i = 0; i < numberChars; i += 2)
  33. bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
  34. return bytes;
  35. }
  36. /// <summary>
  37. /// Converts a byte array to a hex string.
  38. /// </summary>
  39. /// <param name="ba">the byte array</param>
  40. /// <returns>the hex form of the array</returns>
  41. public static string ByteArrayToString(byte[] ba)
  42. {
  43. if (ba is null)
  44. throw new ArgumentNullException(nameof(ba));
  45. var hex = new StringBuilder(ba.Length * 2);
  46. foreach (byte b in ba)
  47. _ = hex.AppendFormat("{0:x2}", b);
  48. return hex.ToString();
  49. }
  50. // Copyright (c) 2008-2013 Hafthor Stefansson
  51. // Distributed under the MIT/X11 software license
  52. // Ref: http://www.opensource.org/licenses/mit-license.php.
  53. // From: https://stackoverflow.com/a/8808245/3117125
  54. /// <summary>
  55. /// Uses unsafe code to compare 2 byte arrays quickly.
  56. /// </summary>
  57. /// <param name="a1">array 1</param>
  58. /// <param name="a2">array 2</param>
  59. /// <returns>whether or not they are byte-for-byte equal</returns>
  60. public static unsafe bool UnsafeCompare(byte[] a1, byte[] a2)
  61. {
  62. if (a1 == a2) return true;
  63. if (a1 == null || a2 == null || a1.Length != a2.Length)
  64. return false;
  65. fixed (byte* p1 = a1, p2 = a2)
  66. {
  67. byte* x1 = p1, x2 = p2;
  68. int l = a1.Length;
  69. for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
  70. if (*(long*)x1 != *(long*)x2) return false;
  71. if ((l & 4) != 0) { if (*(int*)x1 != *(int*)x2) return false; x1 += 4; x2 += 4; }
  72. if ((l & 2) != 0) { if (*(short*)x1 != *(short*)x2) return false; x1 += 2; x2 += 2; }
  73. if ((l & 1) != 0) if (*x1 != *x2) return false;
  74. return true;
  75. }
  76. }
  77. /// <summary>
  78. /// Gets a path relative to the provided folder.
  79. /// </summary>
  80. /// <param name="file">the file to relativize</param>
  81. /// <param name="folder">the source folder</param>
  82. /// <returns>a path to get from <paramref name="folder"/> to <paramref name="file"/></returns>
  83. public static string GetRelativePath(string file, string folder)
  84. {
  85. if (file is null)
  86. throw new ArgumentNullException(nameof(file));
  87. if (folder is null)
  88. throw new ArgumentNullException(nameof(folder));
  89. var pathUri = new Uri(file);
  90. // Folders must end in a slash
  91. if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
  92. {
  93. folder += Path.DirectorySeparatorChar;
  94. }
  95. var folderUri = new Uri(folder);
  96. return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
  97. }
  98. /// <summary>
  99. /// Copies all files from <paramref name="source"/> to <paramref name="target"/>.
  100. /// </summary>
  101. /// <param name="source">the source directory</param>
  102. /// <param name="target">the destination directory</param>
  103. /// <param name="appendFileName">the filename of the file to append together</param>
  104. /// <param name="onCopyException">a delegate called when there is an error copying. Return true to keep going.</param>
  105. public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string appendFileName = "",
  106. Func<Exception, FileInfo, bool>? onCopyException = null)
  107. {
  108. if (source is null)
  109. throw new ArgumentNullException(nameof(source));
  110. if (target is null)
  111. throw new ArgumentNullException(nameof(target));
  112. if (source.FullName.ToUpperInvariant() == target.FullName.ToUpperInvariant())
  113. {
  114. return;
  115. }
  116. // Check if the target directory exists, if not, create it.
  117. if (Directory.Exists(target.FullName) == false)
  118. {
  119. _ = Directory.CreateDirectory(target.FullName);
  120. }
  121. // Copy each file into it's new directory.
  122. foreach (var fi in source.GetFiles())
  123. {
  124. try
  125. {
  126. if (fi.Name == appendFileName)
  127. File.AppendAllLines(Path.Combine(target.ToString(), fi.Name), File.ReadAllLines(fi.FullName));
  128. else
  129. _ = fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
  130. }
  131. catch (Exception e)
  132. {
  133. var keepOn = onCopyException?.Invoke(e, fi);
  134. if (!keepOn.Unwrap())
  135. throw;
  136. }
  137. }
  138. // Copy each subdirectory using recursion.
  139. foreach (var diSourceSubDir in source.GetDirectories())
  140. {
  141. var nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name);
  142. CopyAll(diSourceSubDir, nextTargetSubDir, appendFileName, onCopyException);
  143. }
  144. }
  145. /// <summary>
  146. /// Whether you can safely use <see cref="DateTime.Now"/> without Mono throwing a fit.
  147. /// </summary>
  148. /// <value><see langword="true"/> if you can use <see cref="DateTime.Now"/> safely, <see langword="false"/> otherwise</value>
  149. public static bool CanUseDateTimeNowSafely { get; private set; } = true;
  150. private static bool DateTimeSafetyUnknown = true;
  151. /// <summary>
  152. /// Returns <see cref="DateTime.Now"/> if supported, otherwise <see cref="DateTime.UtcNow"/>.
  153. /// </summary>
  154. /// <returns>the current <see cref="DateTime"/> if supported, otherwise some indeterminant increasing value.</returns>
  155. public static DateTime CurrentTime()
  156. {
  157. if (DateTimeSafetyUnknown)
  158. {
  159. var time = DateTime.MinValue;
  160. try
  161. {
  162. time = DateTime.Now;
  163. }
  164. catch (TimeZoneNotFoundException)
  165. { // Mono did a fucky wucky and we need to avoid this call
  166. CanUseDateTimeNowSafely = false;
  167. }
  168. DateTimeSafetyUnknown = false;
  169. return time;
  170. }
  171. else
  172. {
  173. if (CanUseDateTimeNowSafely) return DateTime.Now;
  174. else return DateTime.UtcNow; // return MinValue as a fallback
  175. }
  176. }
  177. /// <summary>
  178. /// Compares a pair of <see cref="SemVer.Version"/>s ignoring both the prerelease and build fields.
  179. /// </summary>
  180. /// <param name="l">the left value</param>
  181. /// <param name="r">the right value</param>
  182. /// <returns>&lt; 0 if l is less than r, 0 if they are equal in the numeric portion, or &gt; 0 if l is greater than r</returns>
  183. public static int VersionCompareNoPrerelease(SemVer.Version l, SemVer.Version r)
  184. {
  185. if (l is null) throw new ArgumentNullException(nameof(l));
  186. if (r is null) throw new ArgumentNullException(nameof(r));
  187. var cmpVal = l.Major - r.Major;
  188. if (cmpVal != 0) return cmpVal;
  189. cmpVal = l.Minor - r.Minor;
  190. if (cmpVal != 0) return cmpVal;
  191. cmpVal = l.Patch - r.Patch;
  192. return cmpVal;
  193. }
  194. /// <summary>
  195. /// An object used to manage scope guards.
  196. /// </summary>
  197. /// <example>
  198. /// <code>
  199. /// using var _ = new Utils.ScopeGuardObject(() => RunOnScopeExit(value));
  200. /// </code>
  201. /// </example>
  202. /// <seealso cref="ScopeGuard(Action)"/>
  203. [SuppressMessage("Design", "CA1034:Nested types should not be visible",
  204. Justification = "This type needs to be public to avoid allocations")]
  205. [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types",
  206. Justification = "This type is never supposed to be compared")]
  207. public struct ScopeGuardObject : IDisposable
  208. {
  209. private readonly Action action;
  210. /// <summary>
  211. /// Creates a new scope guard that will invoke <paramref name="action"/> when disposed.
  212. /// </summary>
  213. /// <param name="action">the action to run on dispose</param>
  214. public ScopeGuardObject(Action action)
  215. => this.action = action;
  216. void IDisposable.Dispose()
  217. => action?.Invoke();
  218. }
  219. /// <summary>
  220. /// Creates a scope guard for a given <see cref="Action"/>.
  221. /// </summary>
  222. /// <param name="action">the <see cref="Action"/> to run on dispose</param>
  223. /// <returns>a <see cref="ScopeGuardObject"/> that will run <paramref name="action"/> on disposal</returns>
  224. /// <example>
  225. /// <code>
  226. /// using var _ = Utils.ScopeGuard(() => RunOnScopeExit(value));
  227. /// </code>
  228. /// </example>
  229. public static ScopeGuardObject ScopeGuard(Action action)
  230. => new(action);
  231. /// <summary>
  232. /// Deconstructs a <see cref="KeyValuePair{TKey, TValue}"/> as its key and value.
  233. /// </summary>
  234. /// <typeparam name="TKey">The type of the key.</typeparam>
  235. /// <typeparam name="TValue">The type of the value.</typeparam>
  236. /// <param name="kvp">The <see cref="KeyValuePair{TKey, TValue}"/> to deconstruct.</param>
  237. /// <param name="key">The key in <paramref name="kvp"/>.</param>
  238. /// <param name="value">The value in <paramref name="kvp"/>.</param>
  239. public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
  240. => (key, value) = (kvp.Key, kvp.Value);
  241. internal static bool HasInterface(this TypeDefinition? type, string interfaceFullName)
  242. {
  243. return (type?.Interfaces?.Any(i => i.InterfaceType.FullName == interfaceFullName) ?? false)
  244. || (type?.Interfaces?.Any(t => HasInterface(t?.InterfaceType?.Resolve(), interfaceFullName)) ?? false);
  245. }
  246. #if NET4
  247. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  248. internal static IEnumerable<string> StrJP(this IEnumerable<string> a) => a;
  249. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  250. internal static IEnumerable<string> StrJP<T>(this IEnumerable<T> a) => a.Select(o => $"{o}" /* safer than .ToString() */);
  251. #endif
  252. #if NET3
  253. internal static string[] StrJP(this IEnumerable<string> a) => a.ToArray();
  254. internal static string[] StrJP<T>(this IEnumerable<T> a) => a.Select(o => $"{o}" /* safer than .ToString() */).ToArray();
  255. #endif
  256. }
  257. }