diff --git a/.idea/.idea.Nebula/.idea/.name b/.idea/.idea.Nebula/.idea/.name deleted file mode 100644 index c622c56..0000000 --- a/.idea/.idea.Nebula/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Nebula \ No newline at end of file diff --git a/Nebula.Launcher/Assets/Style.axaml b/Nebula.Launcher/Assets/Style.axaml index cafaaa9..af8a340 100644 --- a/Nebula.Launcher/Assets/Style.axaml +++ b/Nebula.Launcher/Assets/Style.axaml @@ -57,4 +57,16 @@ + + + + diff --git a/Nebula.Launcher/CurrentConVar.cs b/Nebula.Launcher/CurrentConVar.cs index 4dde8f3..dff0042 100644 --- a/Nebula.Launcher/CurrentConVar.cs +++ b/Nebula.Launcher/CurrentConVar.cs @@ -17,6 +17,6 @@ public static class CurrentConVar "https://hub.spacestation14.com/api/servers" ]); public static readonly ConVar AuthServers = ConVar.Build("launcher.authServers", [ - "https://auth.spacestation14.com/api/auth/authenticate" + "https://auth.spacestation14.com/api/auth" ]); } \ No newline at end of file diff --git a/Nebula.Launcher/FileApis/HashApi.cs b/Nebula.Launcher/FileApis/HashApi.cs index 4a049f9..e5ae500 100644 --- a/Nebula.Launcher/FileApis/HashApi.cs +++ b/Nebula.Launcher/FileApis/HashApi.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using Nebula.Launcher.Models; using Nebula.Launcher.Utils; using Robust.LoaderApi; diff --git a/Nebula.Launcher/Models/Auth/AuthenticateRequest.cs b/Nebula.Launcher/Models/Auth/AuthenticateRequest.cs new file mode 100644 index 0000000..565663e --- /dev/null +++ b/Nebula.Launcher/Models/Auth/AuthenticateRequest.cs @@ -0,0 +1,10 @@ +using System; + +namespace Nebula.Launcher.Models.Auth; + +public sealed record AuthenticateRequest(string? Username, Guid? UserId, string Password, string? TfaCode = null) +{ + public AuthenticateRequest(string username, string password) : this(username, null, password) + { + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Models/Auth/AuthenticateResponse.cs b/Nebula.Launcher/Models/Auth/AuthenticateResponse.cs new file mode 100644 index 0000000..9894b57 --- /dev/null +++ b/Nebula.Launcher/Models/Auth/AuthenticateResponse.cs @@ -0,0 +1,5 @@ +using System; + +namespace Nebula.Launcher.Models.Auth; + +public sealed record AuthenticateResponse(string Token, string Username, Guid UserId, DateTimeOffset ExpireTime); \ No newline at end of file diff --git a/Nebula.Launcher/Models/Auth/LoginInfo.cs b/Nebula.Launcher/Models/Auth/LoginInfo.cs new file mode 100644 index 0000000..d861086 --- /dev/null +++ b/Nebula.Launcher/Models/Auth/LoginInfo.cs @@ -0,0 +1,15 @@ +using System; + +namespace Nebula.Launcher.Models.Auth; + +public class LoginInfo +{ + public Guid UserId { get; set; } + public string Username { get; set; } = default!; + public LoginToken Token { get; set; } + + public override string ToString() + { + return $"{Username}/{UserId}"; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Models/Auth/LoginToken.cs b/Nebula.Launcher/Models/Auth/LoginToken.cs new file mode 100644 index 0000000..4d4c3b6 --- /dev/null +++ b/Nebula.Launcher/Models/Auth/LoginToken.cs @@ -0,0 +1,15 @@ +using System; + +namespace Nebula.Launcher.Models.Auth; + +public readonly struct LoginToken +{ + public readonly string Token; + public readonly DateTimeOffset ExpireTime; + + public LoginToken(string token, DateTimeOffset expireTime) + { + Token = token; + ExpireTime = expireTime; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Models/ContentCompressionScheme.cs b/Nebula.Launcher/Models/ContentCompressionScheme.cs new file mode 100644 index 0000000..22fd911 --- /dev/null +++ b/Nebula.Launcher/Models/ContentCompressionScheme.cs @@ -0,0 +1,12 @@ +namespace Nebula.Launcher.Models; + +public enum ContentCompressionScheme +{ + None = 0, + Deflate = 1, + + /// + /// ZStandard compression. In the future may use SS14 specific dictionary IDs in the frame header. + /// + ZStd = 2 +} \ No newline at end of file diff --git a/Nebula.Launcher/Models/MainArgs.cs b/Nebula.Launcher/Models/MainArgs.cs new file mode 100644 index 0000000..845f283 --- /dev/null +++ b/Nebula.Launcher/Models/MainArgs.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Robust.LoaderApi; + +namespace Nebula.Launcher.Models; + +public sealed class MainArgs : IMainArgs +{ + public MainArgs(string[] args, IFileApi fileApi, IRedialApi? redialApi, IEnumerable? apiMounts) + { + Args = args; + FileApi = fileApi; + RedialApi = redialApi; + ApiMounts = apiMounts; + } + + public string[] Args { get; } + public IFileApi FileApi { get; } + public IRedialApi? RedialApi { get; } + public IEnumerable? ApiMounts { get; } +} \ No newline at end of file diff --git a/Nebula.Launcher/Models/RobustManifestInfo.cs b/Nebula.Launcher/Models/RobustManifestInfo.cs index dd97d17..1cb7b12 100644 --- a/Nebula.Launcher/Models/RobustManifestInfo.cs +++ b/Nebula.Launcher/Models/RobustManifestInfo.cs @@ -1,5 +1,5 @@ using System; -namespace Nebula.Launcher.Utils; +namespace Nebula.Launcher.Models; public record struct RobustManifestInfo(Uri ManifestUri, Uri DownloadUri, string Hash); \ No newline at end of file diff --git a/Nebula.Launcher/Models/RobustManifestItem.cs b/Nebula.Launcher/Models/RobustManifestItem.cs index 0cf9dda..bcde69f 100644 --- a/Nebula.Launcher/Models/RobustManifestItem.cs +++ b/Nebula.Launcher/Models/RobustManifestItem.cs @@ -1,3 +1,3 @@ -namespace Nebula.Launcher.Utils; +namespace Nebula.Launcher.Models; public record struct RobustManifestItem(string Hash, string Path, int Id); \ No newline at end of file diff --git a/Nebula.Launcher/Models/RobustServerEntry.cs b/Nebula.Launcher/Models/RobustServerEntry.cs index 0c2254b..ccad506 100644 --- a/Nebula.Launcher/Models/RobustServerEntry.cs +++ b/Nebula.Launcher/Models/RobustServerEntry.cs @@ -4,9 +4,9 @@ using System.Text.Json.Serialization; namespace Nebula.Launcher.Models; -public sealed record Auth(string Mode, string PublicKey); +public sealed record AuthInfo(string Mode, string PublicKey); -public sealed record Build( +public sealed record BuildInfo( string EngineVersion, string ForkId, string Version, @@ -16,38 +16,16 @@ public sealed record Build( string Hash, string ManifestHash); -public sealed record Link(string Name, string Icon, string Url); -public sealed record Info(string ConnectAddress, Auth Auth, Build Build, string Desc, List Links); +public sealed record ServerLink(string Name, string Icon, string Url); +public sealed record ServerInfo(string ConnectAddress, AuthInfo Auth, BuildInfo Build, string Desc, List Links); -public sealed record Status( - string Name, - int Players, - List Tags, - string Map, - int RoundId, - int SoftMaxPlayer, - bool PanicBunker, - int RunLevel, - string Preset); - -public enum ContentCompressionScheme -{ - None = 0, - Deflate = 1, - - /// - /// ZStandard compression. In the future may use SS14 specific dictionary IDs in the frame header. - /// - ZStd = 2 -} - -public sealed record VersionInfo( +public sealed record EngineVersionInfo( bool Insecure, [property: JsonPropertyName("redirect")] string? RedirectVersion, - Dictionary Platforms); + Dictionary Platforms); -public sealed class BuildInfo +public sealed class EngineBuildInfo { [JsonInclude] [JsonPropertyName("sha256")] public string Sha256 = default!; @@ -59,9 +37,9 @@ public sealed class BuildInfo public string Url = default!; } -public sealed record ServerInfo(string Address, StatusData StatusData, List InferredTags); +public sealed record ServerHubInfo(string Address, ServerStatus StatusData, List InferredTags); -public sealed record StatusData( +public sealed record ServerStatus( string Map, string Name, List Tags, @@ -70,11 +48,11 @@ public sealed record StatusData( int RoundId, int RunLevel, bool PanicBunker, - DateTime RoundStartTime, - int SoftMaxPlayer); + DateTime? RoundStartTime, + int SoftMaxPlayers); public sealed record ModulesInfo(Dictionary Modules); public sealed record Module(Dictionary Versions); -public sealed record ModuleVersionInfo(Dictionary Platforms); \ No newline at end of file +public sealed record ModuleVersionInfo(Dictionary Platforms); \ No newline at end of file diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj index 74bdbaf..1dffa58 100644 --- a/Nebula.Launcher/Nebula.Launcher.csproj +++ b/Nebula.Launcher/Nebula.Launcher.csproj @@ -6,6 +6,7 @@ true app.manifest true + true @@ -24,14 +25,12 @@ All + + - - ServerContainerControl.axaml - Code - ServerListTab.axaml Code diff --git a/Nebula.Launcher/ServiceCollectionExtensions.cs b/Nebula.Launcher/ServiceCollectionExtensions.cs index aab6f00..723723e 100644 --- a/Nebula.Launcher/ServiceCollectionExtensions.cs +++ b/Nebula.Launcher/ServiceCollectionExtensions.cs @@ -44,7 +44,7 @@ public static class ServiceCollectionExtensions foreach (var (viewModel, view) in GetTypesWithHelpAttribute(Assembly.GetExecutingAssembly())) { - services.AddTransient(viewModel); + services.AddSingleton(viewModel); services.AddTransient(view); } @@ -60,14 +60,6 @@ public static class ServiceCollectionExtensions } } } - - private static void AddView(this IServiceCollection services) - where TView : class - where TViewModel : class - { - services.AddTransient(); - services.AddTransient(); - } private static IEnumerable<(Type,Type)> GetTypesWithHelpAttribute(Assembly assembly) { foreach(Type type in assembly.GetTypes()) diff --git a/Nebula.Launcher/Services/AuthService.cs b/Nebula.Launcher/Services/AuthService.cs new file mode 100644 index 0000000..b4f8ca4 --- /dev/null +++ b/Nebula.Launcher/Services/AuthService.cs @@ -0,0 +1,65 @@ +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using Nebula.Launcher.Models.Auth; + +namespace Nebula.Launcher.Services; + +[ServiceRegister] +public class AuthService +{ + private readonly HttpClient _httpClient = new(); + private readonly RestService _restService; + private readonly DebugService _debugService; + + public CurrentAuthInfo? SelectedAuth; + + public AuthService(RestService restService, DebugService debugService) + { + _restService = restService; + _debugService = debugService; + } + + public async Task Auth(AuthLoginPassword authLoginPassword) + { + var authServer = authLoginPassword.AuthServer; + var login = authLoginPassword.Login; + var password = authLoginPassword.Password; + + _debugService.Debug($"Auth to {authServer}/authenticate {login}"); + + var authUrl = new Uri($"{authServer}/authenticate"); + + var result = + await _restService.PostAsync( + new AuthenticateRequest(login, password), authUrl, CancellationToken.None); + _debugService.Debug("RESULT " + result.Value); + if (result.Value is null) return false; + + SelectedAuth = new CurrentAuthInfo(result.Value.UserId, result.Value.Username, + new LoginToken(result.Value.Token, result.Value.ExpireTime), authServer); + + return true; + } + + public async Task EnsureToken() + { + if (SelectedAuth is null) return false; + + var authUrl = new Uri($"{SelectedAuth.AuthServer}/ping"); + + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, authUrl); + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", SelectedAuth.Token.Token); + using var resp = await _httpClient.SendAsync(requestMessage); + + if (!resp.IsSuccessStatusCode) SelectedAuth = null; + + return resp.IsSuccessStatusCode; + } +} + +public sealed record CurrentAuthInfo(Guid UserId, string Username, LoginToken Token, string AuthServer); +public record AuthLoginPassword(string Login, string Password, string AuthServer); diff --git a/Nebula.Launcher/Services/ConfigurationService.cs b/Nebula.Launcher/Services/ConfigurationService.cs index 308e3f4..3880f43 100644 --- a/Nebula.Launcher/Services/ConfigurationService.cs +++ b/Nebula.Launcher/Services/ConfigurationService.cs @@ -54,7 +54,7 @@ public class ConfigurationService return value != null; } - public void SetValue(ConVar conVar, object value) + public void SetConfigValue(ConVar conVar, object value) { if(conVar.Type != value.GetType()) return; diff --git a/Nebula.Launcher/Services/FileService.cs b/Nebula.Launcher/Services/FileService.cs index 0408f12..ffd83a0 100644 --- a/Nebula.Launcher/Services/FileService.cs +++ b/Nebula.Launcher/Services/FileService.cs @@ -5,6 +5,7 @@ using System.IO.Compression; using System.Runtime.InteropServices; using Nebula.Launcher.FileApis; using Nebula.Launcher.FileApis.Interfaces; +using Nebula.Launcher.Models; using Nebula.Launcher.Utils; using Robust.LoaderApi; diff --git a/Nebula.Launcher/Services/HubService.cs b/Nebula.Launcher/Services/HubService.cs index 0a78a8b..661531c 100644 --- a/Nebula.Launcher/Services/HubService.cs +++ b/Nebula.Launcher/Services/HubService.cs @@ -16,7 +16,7 @@ public class HubService public readonly ObservableCollection HubList = new(); - private readonly Dictionary> _servers = new(); + private readonly Dictionary> _servers = new(); public HubService(ConfigurationService configurationService, RestService restService) @@ -37,7 +37,7 @@ public class HubService foreach (var hubUri in e.NewItems) { var urlStr = (string)hubUri; - var servers = await _restService.GetAsyncDefault>(new Uri(urlStr), [], CancellationToken.None); + var servers = await _restService.GetAsyncDefault>(new Uri(urlStr), [], CancellationToken.None); _servers[urlStr] = servers; HubServerChangedEventArgs?.Invoke(new HubServerChangedEventArgs(servers, HubServerChangeAction.Add)); } @@ -61,9 +61,9 @@ public class HubService public class HubServerChangedEventArgs : EventArgs { public HubServerChangeAction Action; - public List Items; + public List Items; - public HubServerChangedEventArgs(List items, HubServerChangeAction action) + public HubServerChangedEventArgs(List items, HubServerChangeAction action) { Items = items; Action = action; diff --git a/Nebula.Launcher/Utils/Manifest.cs b/Nebula.Launcher/Utils/Manifest.cs index 0b581eb..5f6d9eb 100644 --- a/Nebula.Launcher/Utils/Manifest.cs +++ b/Nebula.Launcher/Utils/Manifest.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; +using Nebula.Launcher.Models; namespace Nebula.Launcher.Utils; diff --git a/Nebula.Launcher/Utils/ZStd.cs b/Nebula.Launcher/Utils/ZStd.cs new file mode 100644 index 0000000..4b49da6 --- /dev/null +++ b/Nebula.Launcher/Utils/ZStd.cs @@ -0,0 +1,494 @@ +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 destination, Span 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 destination, Span 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.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.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 buf = stackalloc byte[1]; + return Read(buf) == 0 ? -1 : buf[0]; + } + + public override unsafe int Read(Span 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 ReadAsync( + Memory 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 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.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 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.Shared.Return(_buffer); + } + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(ZStdCompressStream)); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs b/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs index 5d0f9fd..7ead05f 100644 --- a/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs +++ b/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs @@ -1,13 +1,117 @@ using System; +using System.Collections.ObjectModel; +using System.Windows.Input; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Nebula.Launcher.Services; using Nebula.Launcher.ViewHelper; using Nebula.Launcher.Views.Pages; namespace Nebula.Launcher.ViewModels; [ViewRegister(typeof(AccountInfoView))] -public class AccountInfoViewModel : ViewModelBase +public partial class AccountInfoViewModel : ViewModelBase { - public AccountInfoViewModel(IServiceProvider serviceProvider) : base(serviceProvider) + private readonly AuthService _authService; + + public ObservableCollection Accounts { get; } = new(); + + [ObservableProperty] + private string _currentLogin = String.Empty; + + [ObservableProperty] + private string _currentPassword = String.Empty; + + [ObservableProperty] + private string _currentAuthServer = String.Empty; + + [ObservableProperty] private bool _pageEnabled = true; + + public AuthLoginPassword CurrentALP => new AuthLoginPassword(CurrentLogin, CurrentPassword, CurrentAuthServer); + + //Design think + public AccountInfoViewModel() { + AddAccount(new AuthLoginPassword("Binka","12341","")); + } + + //Real think + public AccountInfoViewModel(IServiceProvider serviceProvider, AuthService authService) : base(serviceProvider) + { + _authService = authService; + } + + public void AuthByALP(AuthLoginPassword authLoginPassword) + { + CurrentLogin = authLoginPassword.Login; + CurrentPassword = authLoginPassword.Password; + CurrentAuthServer = authLoginPassword.AuthServer; + + DoAuth(); + } + + public async void DoAuth() + { + PageEnabled = false; + if(await _authService.Auth(CurrentALP)) + Console.WriteLine("Hello, " + _authService.SelectedAuth!.Username); + else + Console.WriteLine("Shit!"); + PageEnabled = true; + } + + private void AddAccount(AuthLoginPassword authLoginPassword) + { + var onDelete = new DelegateCommand(a => Accounts.Remove(a)); + var onSelect = new DelegateCommand(AuthByALP); + + var alpm = new AuthLoginPasswordModel( + authLoginPassword.Login, + authLoginPassword.Password, + authLoginPassword.AuthServer, + onSelect, + onDelete); + + onDelete.TRef.Value = alpm; + onSelect.TRef.Value = alpm; + + Accounts.Add(alpm); + } + + [RelayCommand] + public void OnSaveProfile() + { + AddAccount(CurrentALP); } } + +internal class Ref +{ + public T Value = default!; +} + +internal class DelegateCommand : ICommand +{ + private readonly Action _func; + public readonly Ref TRef = new(); + + public DelegateCommand(Action func) + { + _func = func; + } + + public bool CanExecute(object? parameter) + { + return true; + } + + public void Execute(object? parameter) + { + _func(TRef.Value); + } + + public event EventHandler? CanExecuteChanged; +} + +public record AuthLoginPasswordModel(string Login, string Password, string AuthServer, ICommand OnSelect = default!, ICommand OnDelete = default!) + : AuthLoginPassword(Login, Password, AuthServer); \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/ServerListViewModel.cs b/Nebula.Launcher/ViewModels/ServerListViewModel.cs index ffc586d..0b36be7 100644 --- a/Nebula.Launcher/ViewModels/ServerListViewModel.cs +++ b/Nebula.Launcher/ViewModels/ServerListViewModel.cs @@ -11,18 +11,22 @@ namespace Nebula.Launcher.ViewModels; [ViewRegister(typeof(ServerListView))] public partial class ServerListViewModel : ViewModelBase { - public ObservableCollection ServerInfos { get; } + public ObservableCollection ServerInfos { get; } [ObservableProperty] - private ServerInfo? _selectedListItem; - + private ServerHubInfo? _selectedListItem; + + //Design think public ServerListViewModel() { - ServerInfos = new ObservableCollection(); + ServerInfos = new ObservableCollection(); + ServerInfos.Add(new ServerHubInfo("ss14://localhost",new ServerStatus("","TestCraft", [], "super", 12,55,1,false,DateTime.Now, 20),[])); } + + //real think public ServerListViewModel(IServiceProvider serviceProvider, HubService hubService) : base(serviceProvider) { - ServerInfos = new ObservableCollection(); + ServerInfos = new ObservableCollection(); hubService.HubServerChangedEventArgs += HubServerChangedEventArgs; } diff --git a/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml b/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml deleted file mode 100644 index 8c966d3..0000000 --- a/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml.cs b/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml.cs deleted file mode 100644 index 24ecb51..0000000 --- a/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace Nebula.Launcher.Views.Controls; - -public partial class PlayerContainerControl : UserControl -{ - public PlayerContainerControl() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml b/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml deleted file mode 100644 index 54d0ef5..0000000 --- a/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml +++ /dev/null @@ -1,57 +0,0 @@ - - - - Server name - - - - - - - - - - - - - - - - - - diff --git a/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml.cs b/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml.cs deleted file mode 100644 index 4d8fdc8..0000000 --- a/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Avalonia; -using Avalonia.Controls; - -namespace Nebula.Launcher.Views.Controls; - -public partial class ServerContainerControl : UserControl -{ - public static readonly StyledProperty ServerNameProperty - = AvaloniaProperty.Register(nameof (ServerName)); - - public string ServerName - { - get => GetValue(ServerNameProperty); - set - { - SetValue(ServerNameProperty, value); - ServerNameLabel.Text = value; - } - } - - public ServerContainerControl() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml index 5da79ee..d9747d1 100644 --- a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml +++ b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml @@ -5,11 +5,13 @@ x:Class="Nebula.Launcher.Views.Pages.AccountInfoView" x:DataType="viewModels:AccountInfoViewModel" xmlns="https://github.com/avaloniaui" - xmlns:controls="clr-namespace:Nebula.Launcher.Views.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"> + xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels" IsEnabled="{Binding PageEnabled}"> + + + - + + Text="{Binding CurrentPassword}" /> - + - + + @@ -51,10 +60,40 @@ - - - - + + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml.cs b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml.cs index 64289c3..007be59 100644 --- a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml.cs +++ b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml.cs @@ -11,7 +11,7 @@ public partial class AccountInfoView : UserControl InitializeComponent(); } - public AccountInfoView(ViewModels.AccountInfoViewModel viewModel) + public AccountInfoView(AccountInfoViewModel viewModel) : this() { DataContext = viewModel; diff --git a/Nebula.Launcher/Views/Pages/ServerListView.axaml b/Nebula.Launcher/Views/Pages/ServerListView.axaml index 31c0e18..30402fb 100644 --- a/Nebula.Launcher/Views/Pages/ServerListView.axaml +++ b/Nebula.Launcher/Views/Pages/ServerListView.axaml @@ -5,12 +5,12 @@ x:Class="Nebula.Launcher.Views.Pages.ServerListView" x:DataType="viewModels:ServerListViewModel" xmlns="https://github.com/avaloniaui" - xmlns:controls="clr-namespace:Nebula.Launcher.Views.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="clr-namespace:Nebula.Launcher.Models" xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"> @@ -23,8 +23,57 @@ ItemsSource="{Binding ServerInfos}" Padding="0"> - - + + + + + + + + + / + + + + + + + + + + + + + + + +