- tweak: refactor funny
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public sealed class BandwidthStream : Stream
|
||||
{
|
||||
private const int NumSeconds = 8;
|
||||
private const int BucketDivisor = 2;
|
||||
private const int BucketsPerSecond = 2 << BucketDivisor;
|
||||
|
||||
// TotalBuckets MUST be power of two!
|
||||
private const int TotalBuckets = NumSeconds * BucketsPerSecond;
|
||||
private readonly Stream _baseStream;
|
||||
private readonly long[] _buckets;
|
||||
|
||||
private readonly Stopwatch _stopwatch;
|
||||
|
||||
private long _bucketIndex;
|
||||
|
||||
public BandwidthStream(Stream baseStream)
|
||||
{
|
||||
_stopwatch = Stopwatch.StartNew();
|
||||
_baseStream = baseStream;
|
||||
_buckets = new long[TotalBuckets];
|
||||
}
|
||||
|
||||
public override bool CanRead => _baseStream.CanRead;
|
||||
|
||||
public override bool CanSeek => _baseStream.CanSeek;
|
||||
|
||||
public override bool CanWrite => _baseStream.CanWrite;
|
||||
|
||||
public override long Length => _baseStream.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _baseStream.Position;
|
||||
set => _baseStream.Position = value;
|
||||
}
|
||||
|
||||
private void TrackBandwidth(long value)
|
||||
{
|
||||
const int bucketMask = TotalBuckets - 1;
|
||||
|
||||
var bucketIdx = CurBucketIdx();
|
||||
|
||||
// Increment to bucket idx, clearing along the way.
|
||||
if (bucketIdx != _bucketIndex)
|
||||
{
|
||||
var diff = bucketIdx - _bucketIndex;
|
||||
if (diff > TotalBuckets)
|
||||
for (var i = _bucketIndex; i < bucketIdx; i++)
|
||||
_buckets[i & bucketMask] = 0;
|
||||
else
|
||||
// We managed to skip so much time the whole buffer is empty.
|
||||
Array.Clear(_buckets);
|
||||
|
||||
_bucketIndex = bucketIdx;
|
||||
}
|
||||
|
||||
// Write value.
|
||||
_buckets[bucketIdx & bucketMask] += value;
|
||||
}
|
||||
|
||||
private long CurBucketIdx()
|
||||
{
|
||||
var elapsed = _stopwatch.Elapsed.TotalSeconds;
|
||||
return (long)(elapsed / BucketsPerSecond);
|
||||
}
|
||||
|
||||
public long CalcCurrentAvg()
|
||||
{
|
||||
var sum = 0L;
|
||||
|
||||
for (var i = 0; i < TotalBuckets; i++) sum += _buckets[i];
|
||||
|
||||
return sum >> BucketDivisor;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return _baseStream.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
_baseStream.Dispose();
|
||||
}
|
||||
|
||||
public override ValueTask DisposeAsync()
|
||||
{
|
||||
return _baseStream.DisposeAsync();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var read = _baseStream.Read(buffer, offset, count);
|
||||
TrackBandwidth(read);
|
||||
return read;
|
||||
}
|
||||
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var read = await base.ReadAsync(buffer, cancellationToken);
|
||||
TrackBandwidth(read);
|
||||
return read;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return _baseStream.Seek(offset, origin);
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
_baseStream.SetLength(value);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
TrackBandwidth(count);
|
||||
}
|
||||
|
||||
public override async ValueTask WriteAsync(
|
||||
ReadOnlyMemory<byte> buffer,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _baseStream.WriteAsync(buffer, cancellationToken);
|
||||
TrackBandwidth(buffer.Length);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using Nebula.Launcher.ViewModels;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public class DelegateCommand<T> : ICommand
|
||||
{
|
||||
private readonly Action<T> _func;
|
||||
public readonly Ref<T> TRef = new();
|
||||
|
||||
public DelegateCommand(Action<T> func)
|
||||
{
|
||||
_func = func;
|
||||
}
|
||||
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
_func(TRef.Value);
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public static class Helper
|
||||
{
|
||||
public static readonly JsonSerializerOptions JsonWebOptions = new(JsonSerializerDefaults.Web);
|
||||
|
||||
public static void OpenBrowser(string url)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Process.Start("xdg-open", url);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
Process.Start("open", url);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<T> AsJson<T>(this HttpContent content) where T : notnull
|
||||
{
|
||||
var str = await content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(str, JsonWebOptions) ??
|
||||
throw new JsonException("AsJson: did not expect null response");
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Nebula.Launcher.Models;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public class ManifestReader : StreamReader
|
||||
{
|
||||
public const int BufferSize = 128;
|
||||
|
||||
public ManifestReader(Stream stream) : base(stream)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(Stream stream, bool detectEncodingFromByteOrderMarks) : base(stream,
|
||||
detectEncodingFromByteOrderMarks)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(Stream stream, Encoding encoding) : base(stream, encoding)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks) : base(stream,
|
||||
encoding, detectEncodingFromByteOrderMarks)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) :
|
||||
base(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(Stream stream, Encoding? encoding = null, bool detectEncodingFromByteOrderMarks = true,
|
||||
int bufferSize = -1, bool leaveOpen = false) : base(stream, encoding, detectEncodingFromByteOrderMarks,
|
||||
bufferSize, leaveOpen)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(string path) : base(path)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(string path, bool detectEncodingFromByteOrderMarks) : base(path,
|
||||
detectEncodingFromByteOrderMarks)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(string path, FileStreamOptions options) : base(path, options)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(string path, Encoding encoding) : base(path, encoding)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks) : base(path, encoding,
|
||||
detectEncodingFromByteOrderMarks)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) : base(
|
||||
path, encoding, detectEncodingFromByteOrderMarks, bufferSize)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public ManifestReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks,
|
||||
FileStreamOptions options) : base(path, encoding, detectEncodingFromByteOrderMarks, options)
|
||||
{
|
||||
ReadManifestVersion();
|
||||
}
|
||||
|
||||
public string ManifestVersion { get; private set; } = "";
|
||||
public int CurrentId { get; private set; }
|
||||
|
||||
private void ReadManifestVersion()
|
||||
{
|
||||
ManifestVersion = ReadLine();
|
||||
}
|
||||
|
||||
public RobustManifestItem? ReadItem()
|
||||
{
|
||||
var line = ReadLine();
|
||||
if (line == null) return null;
|
||||
var splited = line.Split(" ");
|
||||
return new RobustManifestItem(splited[0], line.Substring(splited[0].Length + 1), CurrentId++);
|
||||
}
|
||||
|
||||
public bool TryReadItem([NotNullWhen(true)] out RobustManifestItem? item)
|
||||
{
|
||||
item = ReadItem();
|
||||
return item != null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
CurrentId = 0;
|
||||
}
|
||||
|
||||
public new void DiscardBufferedData()
|
||||
{
|
||||
base.DiscardBufferedData();
|
||||
CurrentId = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public class Ref<T>
|
||||
{
|
||||
public T Value = default!;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public static class RidUtility
|
||||
{
|
||||
public static string? FindBestRid(ICollection<string> runtimes, string? currentRid = null)
|
||||
{
|
||||
var catalog = LoadRidCatalog();
|
||||
|
||||
if (currentRid == null)
|
||||
{
|
||||
var reportedRid = RuntimeInformation.RuntimeIdentifier;
|
||||
if (!catalog.Runtimes.ContainsKey(reportedRid))
|
||||
{
|
||||
currentRid = GuessRid();
|
||||
Console.WriteLine(".NET reported unknown RID: {0}, guessing: {1}", reportedRid, currentRid);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRid = reportedRid;
|
||||
}
|
||||
}
|
||||
|
||||
// Breadth-first search.
|
||||
var q = new Queue<string>();
|
||||
if (!catalog.Runtimes.TryGetValue(currentRid, out var root))
|
||||
// RID doesn't exist in catalog???
|
||||
return null;
|
||||
|
||||
root.Discovered = true;
|
||||
q.Enqueue(currentRid);
|
||||
|
||||
while (q.TryDequeue(out var v))
|
||||
{
|
||||
if (runtimes.Contains(v)) return v;
|
||||
|
||||
foreach (var w in catalog.Runtimes[v].Imports)
|
||||
{
|
||||
var r = catalog.Runtimes[w];
|
||||
if (!r.Discovered)
|
||||
{
|
||||
q.Enqueue(w);
|
||||
r.Discovered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GuessRid()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.X86 => "linux-x86",
|
||||
Architecture.X64 => "linux-x64",
|
||||
Architecture.Arm => "linux-arm",
|
||||
Architecture.Arm64 => "linux-arm64",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.X64 => "freebsd-x64",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.X86 => "win-x86",
|
||||
Architecture.X64 => "win-x64",
|
||||
Architecture.Arm => "win-arm",
|
||||
Architecture.Arm64 => "win-arm64",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
return RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.X64 => "osx-x64",
|
||||
Architecture.Arm64 => "osx-arm64",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static RidCatalog LoadRidCatalog()
|
||||
{
|
||||
using var stream = typeof(RidCatalog).Assembly.GetManifestResourceStream("Utility.runtime.json")!;
|
||||
var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
|
||||
return JsonSerializer.Deserialize<RidCatalog>(ms.GetBuffer().AsSpan(0, (int)ms.Length))!;
|
||||
}
|
||||
|
||||
#pragma warning disable 649
|
||||
private sealed class RidCatalog
|
||||
{
|
||||
[JsonInclude] [JsonPropertyName("runtimes")]
|
||||
public Dictionary<string, Runtime> Runtimes = default!;
|
||||
|
||||
public class Runtime
|
||||
{
|
||||
public bool Discovered;
|
||||
|
||||
[JsonInclude] [JsonPropertyName("#import")]
|
||||
public string[] Imports = default!;
|
||||
}
|
||||
}
|
||||
#pragma warning restore 649
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public static class StreamHelper
|
||||
{
|
||||
public static async ValueTask<byte[]> ReadExactAsync(this Stream stream, int amount, CancellationToken? cancel)
|
||||
{
|
||||
var data = new byte[amount];
|
||||
await ReadExactAsync(stream, data, cancel);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static async ValueTask ReadExactAsync(this Stream stream, Memory<byte> into, CancellationToken? cancel)
|
||||
{
|
||||
while (into.Length > 0)
|
||||
{
|
||||
var read = await stream.ReadAsync(into);
|
||||
|
||||
// Check EOF.
|
||||
if (read == 0)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
into = into[read..];
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task CopyAmountToAsync(
|
||||
this Stream stream,
|
||||
Stream to,
|
||||
int amount,
|
||||
int bufferSize,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
|
||||
while (amount > 0)
|
||||
{
|
||||
Memory<byte> readInto = buffer;
|
||||
if (amount < readInto.Length)
|
||||
readInto = readInto[..amount];
|
||||
|
||||
var read = await stream.ReadAsync(readInto, cancel);
|
||||
if (read == 0)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
amount -= read;
|
||||
|
||||
readInto = readInto[..read];
|
||||
|
||||
await to.WriteAsync(readInto, cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Web;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public static class UriHelper
|
||||
{
|
||||
public const string SchemeSs14 = "ss14";
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public const string SchemeSs14s = "ss14s";
|
||||
|
||||
/// <summary>
|
||||
/// Parses an <c>ss14://</c> or <c>ss14s://</c> URI,
|
||||
/// defaulting to <c>ss14://</c> if no scheme is specified.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri ParseSs14Uri(string address)
|
||||
{
|
||||
if (!TryParseSs14Uri(address, out var uri)) throw new FormatException("Not a valid SS14 URI");
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public static bool TryParseSs14Uri(string address, [NotNullWhen(true)] out Uri? uri)
|
||||
{
|
||||
if (!address.Contains("://")) address = "ss14://" + address;
|
||||
|
||||
if (!Uri.TryCreate(address, UriKind.Absolute, out uri)) return false;
|
||||
|
||||
if (uri.Scheme != SchemeSs14 && uri.Scheme != SchemeSs14s) return false;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(uri.Host))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>http://</c> or <c>https://</c> API address for a server address.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri GetServerApiAddress(Uri serverAddress)
|
||||
{
|
||||
var dataScheme = serverAddress.Scheme switch
|
||||
{
|
||||
"ss14" => Uri.UriSchemeHttp,
|
||||
"ss14s" => Uri.UriSchemeHttps,
|
||||
_ => throw new ArgumentException($"Wrong URI scheme: {serverAddress.Scheme}")
|
||||
};
|
||||
|
||||
var builder = new UriBuilder(serverAddress)
|
||||
{
|
||||
Scheme = dataScheme
|
||||
};
|
||||
|
||||
// No port specified.
|
||||
// Default port for ss14:// is 1212, for ss14s:// it's 443 (HTTPS)
|
||||
if (serverAddress.IsDefaultPort && serverAddress.Scheme == SchemeSs14) builder.Port = 1212;
|
||||
|
||||
if (!builder.Path.EndsWith('/')) builder.Path += "/";
|
||||
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>/status</c> HTTP address for a server address.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri GetServerStatusAddress(string serverAddress)
|
||||
{
|
||||
return GetServerStatusAddress(ParseSs14Uri(serverAddress));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>/status</c> HTTP address for an ss14 uri.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri GetServerStatusAddress(Uri serverAddress)
|
||||
{
|
||||
return new Uri(GetServerApiAddress(serverAddress), "status");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>/info</c> HTTP address for a server address.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri GetServerInfoAddress(string serverAddress)
|
||||
{
|
||||
return GetServerInfoAddress(ParseSs14Uri(serverAddress));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>/info</c> HTTP address for an ss14 uri.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri GetServerInfoAddress(Uri serverAddress)
|
||||
{
|
||||
return new Uri(GetServerApiAddress(serverAddress), "info");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>/client.zip</c> HTTP address for a server address.
|
||||
/// This is not necessarily the actual client ZIP address.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri GetServerSelfhostedClientZipAddress(string serverAddress)
|
||||
{
|
||||
return GetServerSelfhostedClientZipAddress(ParseSs14Uri(serverAddress));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <c>/client.zip</c> HTTP address for an ss14 uri.
|
||||
/// This is not necessarily the actual client ZIP address.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static Uri GetServerSelfhostedClientZipAddress(Uri serverAddress)
|
||||
{
|
||||
return new Uri(GetServerApiAddress(serverAddress), "client.zip");
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public static Uri AddParameter(this Uri url, string paramName, string paramValue)
|
||||
{
|
||||
var uriBuilder = new UriBuilder(url);
|
||||
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
query[paramName] = paramValue;
|
||||
uriBuilder.Query = query.ToString();
|
||||
|
||||
return uriBuilder.Uri;
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpZstd.Interop;
|
||||
using static SharpZstd.Interop.Zstd;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
public static class ZStd
|
||||
{
|
||||
public static int CompressBound(int length)
|
||||
{
|
||||
return (int)ZSTD_compressBound((nuint)length);
|
||||
}
|
||||
|
||||
[ModuleInitializer]
|
||||
public static void InitZStd()
|
||||
{
|
||||
return;
|
||||
NativeLibrary.SetDllImportResolver(
|
||||
typeof(Zstd).Assembly,
|
||||
ResolveZstd
|
||||
);
|
||||
}
|
||||
|
||||
private static IntPtr ResolveZstd(string name, Assembly assembly, DllImportSearchPath? path)
|
||||
{
|
||||
if (name == "zstd" && OperatingSystem.IsLinux())
|
||||
{
|
||||
if (NativeLibrary.TryLoad("zstd.so", assembly, path, out var handle))
|
||||
return handle;
|
||||
|
||||
// Try some extra paths too worst case.
|
||||
if (NativeLibrary.TryLoad("libzstd.so.1", assembly, path, out handle))
|
||||
return handle;
|
||||
|
||||
if (NativeLibrary.TryLoad("libzstd.so", assembly, path, out handle))
|
||||
return handle;
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed unsafe class ZStdCCtx : IDisposable
|
||||
{
|
||||
public ZStdCCtx()
|
||||
{
|
||||
Context = ZSTD_createCCtx();
|
||||
}
|
||||
|
||||
public ZSTD_CCtx* Context { get; private set; }
|
||||
|
||||
private bool Disposed => Context == null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Disposed)
|
||||
return;
|
||||
|
||||
ZSTD_freeCCtx(Context);
|
||||
Context = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void SetParameter(ZSTD_cParameter parameter, int value)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
ZSTD_CCtx_setParameter(Context, parameter, value);
|
||||
}
|
||||
|
||||
public int Compress(Span<byte> destination, Span<byte> source, int compressionLevel = ZSTD_CLEVEL_DEFAULT)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
fixed (byte* dst = destination)
|
||||
fixed (byte* src = source)
|
||||
{
|
||||
var ret = ZSTD_compressCCtx(
|
||||
Context,
|
||||
dst, (nuint)destination.Length,
|
||||
src, (nuint)source.Length,
|
||||
compressionLevel);
|
||||
|
||||
ZStdException.ThrowIfError(ret);
|
||||
return (int)ret;
|
||||
}
|
||||
}
|
||||
|
||||
~ZStdCCtx()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(nameof(ZStdCCtx));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed unsafe class ZStdDCtx : IDisposable
|
||||
{
|
||||
public ZStdDCtx()
|
||||
{
|
||||
Context = ZSTD_createDCtx();
|
||||
}
|
||||
|
||||
public ZSTD_DCtx* Context { get; private set; }
|
||||
|
||||
private bool Disposed => Context == null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Disposed)
|
||||
return;
|
||||
|
||||
ZSTD_freeDCtx(Context);
|
||||
Context = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void SetParameter(ZSTD_dParameter parameter, int value)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
ZSTD_DCtx_setParameter(Context, parameter, value);
|
||||
}
|
||||
|
||||
public int Decompress(Span<byte> destination, Span<byte> source)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
fixed (byte* dst = destination)
|
||||
fixed (byte* src = source)
|
||||
{
|
||||
var ret = ZSTD_decompressDCtx(Context, dst, (nuint)destination.Length, src, (nuint)source.Length);
|
||||
|
||||
ZStdException.ThrowIfError(ret);
|
||||
return (int)ret;
|
||||
}
|
||||
}
|
||||
|
||||
~ZStdDCtx()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException(nameof(ZStdDCtx));
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ZStdException : Exception
|
||||
{
|
||||
public ZStdException()
|
||||
{
|
||||
}
|
||||
|
||||
public ZStdException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ZStdException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
public static unsafe ZStdException FromCode(nuint code)
|
||||
{
|
||||
return new ZStdException(Marshal.PtrToStringUTF8((IntPtr)ZSTD_getErrorName(code))!);
|
||||
}
|
||||
|
||||
public static void ThrowIfError(nuint code)
|
||||
{
|
||||
if (ZSTD_isError(code) != 0)
|
||||
throw FromCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ZStdDecompressStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private readonly byte[] _buffer;
|
||||
private readonly unsafe ZSTD_DCtx* _ctx;
|
||||
private readonly bool _ownStream;
|
||||
private int _bufferPos;
|
||||
private int _bufferSize;
|
||||
private bool _disposed;
|
||||
|
||||
public unsafe ZStdDecompressStream(Stream baseStream, bool ownStream = true)
|
||||
{
|
||||
_baseStream = baseStream;
|
||||
_ownStream = ownStream;
|
||||
_ctx = ZSTD_createDCtx();
|
||||
_buffer = ArrayPool<byte>.Shared.Rent((int)ZSTD_DStreamInSize());
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override unsafe void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
ZSTD_freeDCtx(_ctx);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (_ownStream)
|
||||
_baseStream.Dispose();
|
||||
|
||||
ArrayPool<byte>.Shared.Return(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return Read(buffer.AsSpan(offset, count));
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[1];
|
||||
return Read(buf) == 0 ? -1 : buf[0];
|
||||
}
|
||||
|
||||
public override unsafe int Read(Span<byte> buffer)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
do
|
||||
{
|
||||
if (_bufferSize == 0 || _bufferPos == _bufferSize)
|
||||
{
|
||||
_bufferPos = 0;
|
||||
_bufferSize = _baseStream.Read(_buffer);
|
||||
|
||||
if (_bufferSize == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
fixed (byte* inputPtr = _buffer)
|
||||
fixed (byte* outputPtr = buffer)
|
||||
{
|
||||
var outputBuf = new ZSTD_outBuffer { dst = outputPtr, pos = 0, size = (nuint)buffer.Length };
|
||||
var inputBuf = new ZSTD_inBuffer { src = inputPtr, pos = (nuint)_bufferPos, size = (nuint)_bufferSize };
|
||||
var ret = ZSTD_decompressStream(_ctx, &outputBuf, &inputBuf);
|
||||
|
||||
_bufferPos = (int)inputBuf.pos;
|
||||
ZStdException.ThrowIfError(ret);
|
||||
|
||||
if (outputBuf.pos > 0)
|
||||
return (int)outputBuf.pos;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public override async ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
do
|
||||
{
|
||||
if (_bufferSize == 0 || _bufferPos == _bufferSize)
|
||||
{
|
||||
_bufferPos = 0;
|
||||
_bufferSize = await _baseStream.ReadAsync(_buffer, cancellationToken);
|
||||
|
||||
if (_bufferSize == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
var ret = DecompressChunk(this, buffer.Span);
|
||||
if (ret > 0)
|
||||
return (int)ret;
|
||||
} while (true);
|
||||
|
||||
static unsafe nuint DecompressChunk(ZStdDecompressStream stream, Span<byte> buffer)
|
||||
{
|
||||
fixed (byte* inputPtr = stream._buffer)
|
||||
fixed (byte* outputPtr = buffer)
|
||||
{
|
||||
ZSTD_outBuffer outputBuf = default;
|
||||
outputBuf.dst = outputPtr;
|
||||
outputBuf.pos = 0;
|
||||
outputBuf.size = (nuint)buffer.Length;
|
||||
ZSTD_inBuffer inputBuf = default;
|
||||
inputBuf.src = inputPtr;
|
||||
inputBuf.pos = (nuint)stream._bufferPos;
|
||||
inputBuf.size = (nuint)stream._bufferSize;
|
||||
|
||||
var ret = ZSTD_decompressStream(stream._ctx, &outputBuf, &inputBuf);
|
||||
|
||||
stream._bufferPos = (int)inputBuf.pos;
|
||||
ZStdException.ThrowIfError(ret);
|
||||
|
||||
return outputBuf.pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(ZStdDecompressStream));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ZStdCompressStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private readonly byte[] _buffer;
|
||||
private readonly unsafe ZSTD_CCtx* _ctx;
|
||||
private readonly bool _ownStream;
|
||||
private int _bufferPos;
|
||||
private bool _disposed;
|
||||
|
||||
public unsafe ZStdCompressStream(Stream baseStream, bool ownStream = true)
|
||||
{
|
||||
_ctx = ZSTD_createCCtx();
|
||||
_baseStream = baseStream;
|
||||
_ownStream = ownStream;
|
||||
_buffer = ArrayPool<byte>.Shared.Rent((int)ZSTD_CStreamOutSize());
|
||||
}
|
||||
|
||||
public override bool CanRead => false;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => true;
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
FlushInternal(ZSTD_EndDirective.ZSTD_e_flush);
|
||||
}
|
||||
|
||||
public void FlushEnd()
|
||||
{
|
||||
FlushInternal(ZSTD_EndDirective.ZSTD_e_end);
|
||||
}
|
||||
|
||||
private unsafe void FlushInternal(ZSTD_EndDirective directive)
|
||||
{
|
||||
fixed (byte* outPtr = _buffer)
|
||||
{
|
||||
ZSTD_outBuffer outBuf = default;
|
||||
outBuf.size = (nuint)_buffer.Length;
|
||||
outBuf.pos = (nuint)_bufferPos;
|
||||
outBuf.dst = outPtr;
|
||||
|
||||
ZSTD_inBuffer inBuf = default;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var err = ZSTD_compressStream2(_ctx, &outBuf, &inBuf, directive);
|
||||
ZStdException.ThrowIfError(err);
|
||||
_bufferPos = (int)outBuf.pos;
|
||||
|
||||
_baseStream.Write(_buffer.AsSpan(0, (int)outBuf.pos));
|
||||
_bufferPos = 0;
|
||||
outBuf.pos = 0;
|
||||
|
||||
if (err == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Write(buffer.AsSpan(offset, count));
|
||||
}
|
||||
|
||||
public override unsafe void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
fixed (byte* outPtr = _buffer)
|
||||
fixed (byte* inPtr = buffer)
|
||||
{
|
||||
ZSTD_outBuffer outBuf = default;
|
||||
outBuf.size = (nuint)_buffer.Length;
|
||||
outBuf.pos = (nuint)_bufferPos;
|
||||
outBuf.dst = outPtr;
|
||||
|
||||
ZSTD_inBuffer inBuf = default;
|
||||
inBuf.pos = 0;
|
||||
inBuf.size = (nuint)buffer.Length;
|
||||
inBuf.src = inPtr;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var err = ZSTD_compressStream2(_ctx, &outBuf, &inBuf, ZSTD_EndDirective.ZSTD_e_continue);
|
||||
ZStdException.ThrowIfError(err);
|
||||
_bufferPos = (int)outBuf.pos;
|
||||
|
||||
if (inBuf.pos >= inBuf.size)
|
||||
break;
|
||||
|
||||
// Not all input data consumed. Flush output buffer and continue.
|
||||
_baseStream.Write(_buffer.AsSpan(0, (int)outBuf.pos));
|
||||
_bufferPos = 0;
|
||||
outBuf.pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override unsafe void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
ZSTD_freeCCtx(_ctx);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (_ownStream)
|
||||
_baseStream.Dispose();
|
||||
|
||||
ArrayPool<byte>.Shared.Return(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(ZStdCompressStream));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user