wabbajack/Wabbajack.Common/Utils.cs

599 lines
19 KiB
C#
Raw Normal View History

2019-09-14 04:35:42 +00:00
using System;
2019-07-21 04:40:54 +00:00
using System.Collections.Generic;
using System.Diagnostics;
2019-07-21 04:40:54 +00:00
using System.IO;
using System.Linq;
2019-09-23 21:37:10 +00:00
using System.Net.Configuration;
2019-07-23 04:27:26 +00:00
using System.Net.Http;
using System.Reflection;
2019-07-21 04:40:54 +00:00
using System.Security.Cryptography;
using System.Text;
2019-08-02 23:04:04 +00:00
using System.Threading;
2019-07-21 04:40:54 +00:00
using System.Threading.Tasks;
2019-09-14 04:35:42 +00:00
using ICSharpCode.SharpZipLib.BZip2;
using IniParser;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
2019-10-16 21:36:14 +00:00
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
2019-08-29 22:49:48 +00:00
using File = Alphaleonis.Win32.Filesystem.File;
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
using Path = Alphaleonis.Win32.Filesystem.Path;
2019-07-21 04:40:54 +00:00
namespace Wabbajack.Common
{
public static class Utils
{
public static string LogFile { get; private set; }
static Utils()
{
var program_name = Assembly.GetEntryAssembly()?.Location ?? "Wabbajack";
LogFile = program_name + ".log";
_startTime = DateTime.Now;
if (LogFile.FileExists())
File.Delete(LogFile);
}
private static Action<string> _loggerFn;
private static Action<string, int> _statusFn;
2019-07-21 04:40:54 +00:00
2019-09-24 15:26:44 +00:00
private static readonly string[] Suffix = {"B", "KB", "MB", "GB", "TB", "PB", "EB"}; // Longs run out around EB
2019-09-14 04:35:42 +00:00
public static void SetLoggerFn(Action<string> f)
{
_loggerFn = f;
}
public static void SetStatusFn(Action<string, int> f)
{
_statusFn = f;
}
private static object _lock = new object();
private static DateTime _startTime;
public static void Log(string msg)
{
lock (_lock)
{
msg = $"{(DateTime.Now - _startTime).TotalSeconds:0.##} - {msg}";
File.AppendAllText(LogFile, msg + "\r\n");
}
_loggerFn?.Invoke(msg);
}
2019-10-12 19:15:19 +00:00
public static void LogToFile(string msg)
{
lock (_lock)
{
msg = $"{(DateTime.Now - _startTime).TotalSeconds:0.##} - {msg}";
File.AppendAllText(LogFile, msg + "\r\n");
}
}
public static void Status(string msg, int progress = 0)
{
_statusFn?.Invoke(msg, progress);
}
2019-09-14 04:35:42 +00:00
2019-07-21 04:40:54 +00:00
/// <summary>
2019-09-14 04:35:42 +00:00
/// MurMur3 hashes the file pointed to by this string
2019-07-21 04:40:54 +00:00
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static string FileSHA256(this string file)
{
var sha = new SHA256Managed();
using (var o = new CryptoStream(Stream.Null, sha, CryptoStreamMode.Write))
{
using (var i = File.OpenRead(file))
2019-09-14 04:35:42 +00:00
{
i.CopyToWithStatus(new FileInfo(file).Length, o, $"Hashing {Path.GetFileName(file)}");
2019-09-14 04:35:42 +00:00
}
2019-07-21 04:40:54 +00:00
}
2019-09-14 04:35:42 +00:00
return sha.Hash.ToBase64();
2019-07-21 04:40:54 +00:00
}
2019-08-25 23:52:03 +00:00
public static void CopyToWithStatus(this Stream istream, long maxSize, Stream ostream, string status)
{
var buffer = new byte[1024 * 64];
if (maxSize == 0) maxSize = 1;
long total_read = 0;
while (true)
{
2019-09-14 04:35:42 +00:00
var read = istream.Read(buffer, 0, buffer.Length);
2019-08-25 23:52:03 +00:00
if (read == 0) break;
total_read += read;
ostream.Write(buffer, 0, read);
2019-09-14 04:35:42 +00:00
Status(status, (int) (total_read * 100 / maxSize));
2019-08-25 23:52:03 +00:00
}
}
2019-07-26 20:59:14 +00:00
public static string SHA256(this byte[] data)
{
return new SHA256Managed().ComputeHash(data).ToBase64();
}
2019-07-21 04:40:54 +00:00
/// <summary>
2019-09-14 04:35:42 +00:00
/// Returns a Base64 encoding of these bytes
2019-07-21 04:40:54 +00:00
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string ToBase64(this byte[] data)
{
return Convert.ToBase64String(data);
}
public static string ToHEX(this byte[] bytes)
{
2019-09-14 04:35:42 +00:00
var builder = new StringBuilder();
for (var i = 0; i < bytes.Length; i++) builder.Append(bytes[i].ToString("x2"));
return builder.ToString();
}
2019-07-23 04:27:26 +00:00
/// <summary>
2019-09-14 04:35:42 +00:00
/// Returns data from a base64 stream
2019-07-23 04:27:26 +00:00
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static byte[] FromBase64(this string data)
{
return Convert.FromBase64String(data);
}
2019-07-21 04:40:54 +00:00
/// <summary>
2019-09-14 04:35:42 +00:00
/// Executes the action for every item in coll
2019-07-21 04:40:54 +00:00
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="coll"></param>
/// <param name="f"></param>
public static void Do<T>(this IEnumerable<T> coll, Action<T> f)
{
foreach (var i in coll) f(i);
}
2019-08-25 23:52:03 +00:00
public static void DoIndexed<T>(this IEnumerable<T> coll, Action<int, T> f)
{
2019-09-14 04:35:42 +00:00
var idx = 0;
2019-08-25 23:52:03 +00:00
foreach (var i in coll)
{
f(idx, i);
idx += 1;
}
}
2019-07-21 04:40:54 +00:00
/// <summary>
2019-09-14 04:35:42 +00:00
/// Loads INI data from the given filename and returns a dynamic type that
/// can use . operators to navigate the INI.
2019-07-21 04:40:54 +00:00
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static dynamic LoadIniFile(this string file)
{
return new DynamicIniData(new FileIniDataParser().ReadFile(file));
}
2019-10-12 18:03:45 +00:00
/// <summary>
/// Loads a INI from the given string
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static dynamic LoadIniString(this string file)
{
return new DynamicIniData(new FileIniDataParser().ReadData(new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(file)))));
}
2019-07-21 04:40:54 +00:00
public static void ToJSON<T>(this T obj, string filename)
{
2019-09-14 04:35:42 +00:00
File.WriteAllText(filename,
JsonConvert.SerializeObject(obj, Formatting.Indented,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}));
2019-07-21 04:40:54 +00:00
}
public static void ToBSON<T>(this T obj, string filename)
{
2019-09-14 04:35:42 +00:00
using (var fo = File.OpenWrite(filename))
using (var br = new BsonDataWriter(fo))
{
fo.SetLength(0);
2019-09-14 04:35:42 +00:00
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{TypeNameHandling = TypeNameHandling.Auto});
serializer.Serialize(br, obj);
}
}
public static ulong ToMilliseconds(this DateTime date)
{
2019-09-14 04:35:42 +00:00
return (ulong) (date - new DateTime(1970, 1, 1)).TotalMilliseconds;
}
2019-07-31 03:59:19 +00:00
public static string ToJSON<T>(this T obj)
{
2019-09-14 04:35:42 +00:00
return JsonConvert.SerializeObject(obj, Formatting.Indented,
2019-10-12 18:03:45 +00:00
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All});
2019-07-31 03:59:19 +00:00
}
2019-07-21 04:40:54 +00:00
public static T FromJSON<T>(this string filename)
{
2019-09-14 04:35:42 +00:00
return JsonConvert.DeserializeObject<T>(File.ReadAllText(filename),
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
2019-07-31 03:59:19 +00:00
}
public static T FromBSON<T>(this string filename, bool root_is_array = false)
{
using (var fo = File.OpenRead(filename))
2019-09-14 04:35:42 +00:00
using (var br = new BsonDataReader(fo, root_is_array, DateTimeKind.Local))
{
2019-09-14 04:35:42 +00:00
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{TypeNameHandling = TypeNameHandling.Auto});
return serializer.Deserialize<T>(br);
}
}
2019-07-31 03:59:19 +00:00
public static T FromJSONString<T>(this string data)
{
2019-09-14 04:35:42 +00:00
return JsonConvert.DeserializeObject<T>(data,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
2019-07-21 04:40:54 +00:00
}
2019-09-14 04:35:42 +00:00
2019-07-23 04:27:26 +00:00
public static T FromJSON<T>(this Stream data)
{
var s = Encoding.UTF8.GetString(data.ReadAll());
return JsonConvert.DeserializeObject<T>(s,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
2019-07-23 04:27:26 +00:00
}
2019-07-21 04:40:54 +00:00
public static bool FileExists(this string filename)
{
return File.Exists(filename);
}
public static string RelativeTo(this string file, string folder)
{
return file.Substring(folder.Length + 1);
}
2019-07-21 22:47:17 +00:00
/// <summary>
2019-09-14 04:35:42 +00:00
/// Returns the string compressed via BZip2
2019-07-21 22:47:17 +00:00
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static byte[] BZip2String(this string data)
{
using (var os = new MemoryStream())
{
using (var bz = new BZip2OutputStream(os))
{
using (var bw = new BinaryWriter(bz))
2019-09-14 04:35:42 +00:00
{
2019-07-21 22:47:17 +00:00
bw.Write(data);
2019-09-14 04:35:42 +00:00
}
2019-07-21 22:47:17 +00:00
}
2019-09-14 04:35:42 +00:00
2019-07-21 22:47:17 +00:00
return os.ToArray();
}
}
public static void BZip2ExtractToFile(this Stream src, string dest)
{
using (var os = File.OpenWrite(dest))
{
os.SetLength(0);
using (var bz = new BZip2InputStream(src))
bz.CopyTo(os);
}
}
2019-07-21 22:47:17 +00:00
/// <summary>
2019-09-14 04:35:42 +00:00
/// Returns the string compressed via BZip2
2019-07-21 22:47:17 +00:00
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string BZip2String(this byte[] data)
{
using (var s = new MemoryStream(data))
{
using (var bz = new BZip2InputStream(s))
{
using (var bw = new BinaryReader(bz))
2019-09-14 04:35:42 +00:00
{
2019-07-21 22:47:17 +00:00
return bw.ReadString();
2019-09-14 04:35:42 +00:00
}
2019-07-21 22:47:17 +00:00
}
}
}
2019-07-22 03:36:25 +00:00
public static byte[] ReadAll(this Stream ins)
{
using (var ms = new MemoryStream())
{
ins.CopyTo(ms);
return ms.ToArray();
}
}
2019-07-22 22:17:46 +00:00
public static List<TR> PMap<TI, TR>(this IEnumerable<TI> coll, Func<TI, TR> f)
{
2019-08-02 23:04:04 +00:00
var colllst = coll.ToList();
Interlocked.Add(ref WorkQueue.MaxQueueSize, colllst.Count);
//WorkQueue.CurrentQueueSize = 0;
2019-08-02 23:04:04 +00:00
2019-09-14 04:35:42 +00:00
var remaining_tasks = colllst.Count;
2019-08-10 15:21:50 +00:00
2019-07-22 22:17:46 +00:00
var tasks = coll.Select(i =>
{
2019-09-14 04:35:42 +00:00
var tc = new TaskCompletionSource<TR>();
2019-07-22 22:17:46 +00:00
WorkQueue.QueueTask(() =>
{
try
{
tc.SetResult(f(i));
}
catch (Exception ex)
{
tc.SetException(ex);
}
2019-09-14 04:35:42 +00:00
2019-08-02 23:04:04 +00:00
Interlocked.Increment(ref WorkQueue.CurrentQueueSize);
2019-08-10 15:21:50 +00:00
Interlocked.Decrement(ref remaining_tasks);
2019-08-02 23:04:04 +00:00
WorkQueue.ReportNow();
2019-07-22 22:17:46 +00:00
});
return tc.Task;
}).ToList();
2019-08-10 15:21:50 +00:00
// To avoid thread starvation, we'll start to help out in the work queue
if (WorkQueue.WorkerThread)
2019-09-14 04:35:42 +00:00
while (remaining_tasks > 0)
if (WorkQueue.Queue.TryTake(out var a, 500))
a();
2019-08-10 15:21:50 +00:00
if (WorkQueue.CurrentQueueSize == WorkQueue.MaxQueueSize)
{
WorkQueue.MaxQueueSize = 0;
WorkQueue.MaxQueueSize = 0;
}
2019-07-22 22:17:46 +00:00
return tasks.Select(t =>
{
t.Wait();
2019-07-26 20:59:14 +00:00
if (t.IsFaulted)
throw t.Exception;
2019-07-22 22:17:46 +00:00
return t.Result;
}).ToList();
}
public static void PMap<TI>(this IEnumerable<TI> coll, Action<TI> f)
{
2019-09-14 04:35:42 +00:00
coll.PMap(i =>
2019-07-22 22:17:46 +00:00
{
2019-08-10 15:21:50 +00:00
f(i);
return false;
});
2019-07-22 22:17:46 +00:00
}
2019-09-14 04:35:42 +00:00
public static void DoProgress<T>(this IEnumerable<T> coll, string msg, Action<T> f)
2019-08-29 22:49:48 +00:00
{
var lst = coll.ToList();
lst.DoIndexed((idx, i) =>
{
Status(msg, idx * 100 / lst.Count);
f(i);
});
}
public static void OnQueue(Action f)
{
new List<bool>().Do(_ => f());
}
2019-07-23 04:27:26 +00:00
public static HttpResponseMessage GetSync(this HttpClient client, string url)
{
var result = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
result.Wait();
return result.Result;
}
2019-09-14 04:35:42 +00:00
2019-07-23 04:27:26 +00:00
public static string GetStringSync(this HttpClient client, string url)
{
var result = client.GetStringAsync(url);
result.Wait();
return result.Result;
}
public static Stream GetStreamSync(this HttpClient client, string url)
{
var result = client.GetStreamAsync(url);
result.Wait();
return result.Result;
}
public static Stream PostStreamSync(this HttpClient client, string url, HttpContent content)
{
var result = client.PostAsync(url, content);
result.Wait();
var stream = result.Result.Content.ReadAsStreamAsync();
stream.Wait();
return stream.Result;
}
public static string ExceptionToString(this Exception ex)
{
2019-09-14 04:35:42 +00:00
var sb = new StringBuilder();
while (ex != null)
{
sb.AppendLine(ex.Message);
var st = new StackTrace(ex, true);
foreach (var frame in st.GetFrames())
2019-09-14 04:35:42 +00:00
sb.AppendLine(
$"{frame.GetFileName()}:{frame.GetMethod().Name}:{frame.GetFileLineNumber()}:{frame.GetFileColumnNumber()}");
ex = ex.InnerException;
}
return sb.ToString();
}
public static void CrashDump(Exception e)
{
File.WriteAllText($"{DateTime.Now.ToString("yyyyMMddTHHmmss_crash_log.txt")}", ExceptionToString(e));
}
public static IEnumerable<T> DistinctBy<T, V>(this IEnumerable<T> vs, Func<T, V> select)
{
2019-09-14 04:35:42 +00:00
var set = new HashSet<V>();
foreach (var v in vs)
{
var key = select(v);
if (set.Contains(key)) continue;
yield return v;
}
}
public static T Last<T>(this T[] a)
{
if (a == null || a.Length == 0)
throw new InvalidDataException("null or empty array");
return a[a.Length - 1];
}
public static V GetOrDefault<K, V>(this IDictionary<K, V> dict, K key)
{
2019-09-14 04:35:42 +00:00
if (dict.TryGetValue(key, out var v)) return v;
return default;
}
public static string ToFileSizeString(this long byteCount)
{
if (byteCount == 0)
return "0" + Suffix[0];
2019-09-14 04:35:42 +00:00
var bytes = Math.Abs(byteCount);
var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
var num = Math.Round(bytes / Math.Pow(1024, place), 1);
return Math.Sign(byteCount) * num + Suffix[place];
}
public static string ToFileSizeString(this int byteCount)
{
return ToFileSizeString((long)byteCount);
}
public static void CreatePatch(byte[] a, byte[] b, Stream output)
{
var data_a = a.SHA256().FromBase64().ToHEX();
var data_b = b.SHA256().FromBase64().ToHEX();
var cache_file = Path.Combine("patch_cache", $"{data_a}_{data_b}.patch");
if (!Directory.Exists("patch_cache"))
Directory.CreateDirectory("patch_cache");
while (true)
{
if (File.Exists(cache_file))
{
using (var f = File.OpenRead(cache_file))
{
f.CopyTo(output);
}
}
else
{
var tmp_name = Path.Combine("patch_cache", Guid.NewGuid() + ".tmp");
using (var f = File.OpenWrite(tmp_name))
{
BSDiff.Create(a, b, f);
}
File.Move(tmp_name, cache_file);
continue;
}
break;
}
}
public static void TryGetPatch(string foundHash, string fileHash, out byte[] ePatch)
{
2019-09-14 04:35:42 +00:00
var patch_name = Path.Combine("patch_cache",
$"{foundHash.FromBase64().ToHEX()}_{fileHash.FromBase64().ToHEX()}.patch");
ePatch = File.Exists(patch_name) ? File.ReadAllBytes(patch_name) : null;
}
2019-09-23 21:37:10 +00:00
public static void Warning(string s)
{
Log($"WARNING: {s}");
}
public static TV GetOrDefault<TK, TV>(this Dictionary<TK, TV> dict, TK key)
{
return dict.TryGetValue(key, out var result) ? result : default;
}
public static IEnumerable<T> ButLast<T>(this IEnumerable<T> coll)
{
var lst = coll.ToList();
return lst.Take(lst.Count() - 1);
}
2019-09-23 21:37:10 +00:00
public static byte[] ConcatArrays(this IEnumerable<byte[]> arrays)
{
var outarr = new byte[arrays.Sum(a => a.Length)];
int offset = 0;
foreach (var arr in arrays)
{
Array.Copy(arr, 0, outarr, offset, arr.Length);
offset += arr.Length;
}
return outarr;
}
2019-10-12 18:03:45 +00:00
/// <summary>
/// Roundtrips the value throught the JSON routines
/// </summary>
/// <typeparam name="TV"></typeparam>
/// <typeparam name="TR"></typeparam>
/// <param name="tv"></param>
/// <returns></returns>
public static T ViaJSON<T>(this T tv)
{
return tv.ToJSON().FromJSONString<T>();
}
public static void Error(string msg)
{
Log(msg);
throw new Exception(msg);
}
2019-10-16 03:17:27 +00:00
public static Stream GetResourceStream(string name)
{
return (from assembly in AppDomain.CurrentDomain.GetAssemblies()
where !assembly.IsDynamic
from rname in assembly.GetManifestResourceNames()
where rname == name
select assembly.GetManifestResourceStream(name)).First();
}
2019-10-16 21:36:14 +00:00
public static T FromYaml<T>(this Stream s)
{
var d = new DeserializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
.Build();
return d.Deserialize<T>(new StreamReader(s));
}
public static T FromYaml<T>(this string s)
{
var d = new DeserializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
.Build();
return d.Deserialize<T>(new StreamReader(s));
}
2019-07-21 04:40:54 +00:00
}
2019-09-14 04:35:42 +00:00
}