- add: Auth service
This commit is contained in:
@@ -57,4 +57,16 @@
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="Background" Value="#00000000" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBox.AccountSelector > ListBoxItem">
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="#00000000" />
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBox.AccountSelector > ListBoxItem:selected">
|
||||
<Setter Property="Background" Value="#00000000" />
|
||||
</Style>
|
||||
</Styles>
|
||||
|
||||
@@ -17,6 +17,6 @@ public static class CurrentConVar
|
||||
"https://hub.spacestation14.com/api/servers"
|
||||
]);
|
||||
public static readonly ConVar AuthServers = ConVar.Build<string[]>("launcher.authServers", [
|
||||
"https://auth.spacestation14.com/api/auth/authenticate"
|
||||
"https://auth.spacestation14.com/api/auth"
|
||||
]);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Nebula.Launcher.Models;
|
||||
using Nebula.Launcher.Utils;
|
||||
using Robust.LoaderApi;
|
||||
|
||||
|
||||
10
Nebula.Launcher/Models/Auth/AuthenticateRequest.cs
Normal file
10
Nebula.Launcher/Models/Auth/AuthenticateRequest.cs
Normal file
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
5
Nebula.Launcher/Models/Auth/AuthenticateResponse.cs
Normal file
5
Nebula.Launcher/Models/Auth/AuthenticateResponse.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace Nebula.Launcher.Models.Auth;
|
||||
|
||||
public sealed record AuthenticateResponse(string Token, string Username, Guid UserId, DateTimeOffset ExpireTime);
|
||||
15
Nebula.Launcher/Models/Auth/LoginInfo.cs
Normal file
15
Nebula.Launcher/Models/Auth/LoginInfo.cs
Normal file
@@ -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}";
|
||||
}
|
||||
}
|
||||
15
Nebula.Launcher/Models/Auth/LoginToken.cs
Normal file
15
Nebula.Launcher/Models/Auth/LoginToken.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
12
Nebula.Launcher/Models/ContentCompressionScheme.cs
Normal file
12
Nebula.Launcher/Models/ContentCompressionScheme.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Nebula.Launcher.Models;
|
||||
|
||||
public enum ContentCompressionScheme
|
||||
{
|
||||
None = 0,
|
||||
Deflate = 1,
|
||||
|
||||
/// <summary>
|
||||
/// ZStandard compression. In the future may use SS14 specific dictionary IDs in the frame header.
|
||||
/// </summary>
|
||||
ZStd = 2
|
||||
}
|
||||
20
Nebula.Launcher/Models/MainArgs.cs
Normal file
20
Nebula.Launcher/Models/MainArgs.cs
Normal file
@@ -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<ApiMount>? apiMounts)
|
||||
{
|
||||
Args = args;
|
||||
FileApi = fileApi;
|
||||
RedialApi = redialApi;
|
||||
ApiMounts = apiMounts;
|
||||
}
|
||||
|
||||
public string[] Args { get; }
|
||||
public IFileApi FileApi { get; }
|
||||
public IRedialApi? RedialApi { get; }
|
||||
public IEnumerable<ApiMount>? ApiMounts { get; }
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
namespace Nebula.Launcher.Models;
|
||||
|
||||
public record struct RobustManifestInfo(Uri ManifestUri, Uri DownloadUri, string Hash);
|
||||
@@ -1,3 +1,3 @@
|
||||
namespace Nebula.Launcher.Utils;
|
||||
namespace Nebula.Launcher.Models;
|
||||
|
||||
public record struct RobustManifestItem(string Hash, string Path, int Id);
|
||||
@@ -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<Link> Links);
|
||||
public sealed record ServerLink(string Name, string Icon, string Url);
|
||||
public sealed record ServerInfo(string ConnectAddress, AuthInfo Auth, BuildInfo Build, string Desc, List<ServerLink> Links);
|
||||
|
||||
public sealed record Status(
|
||||
string Name,
|
||||
int Players,
|
||||
List<object> Tags,
|
||||
string Map,
|
||||
int RoundId,
|
||||
int SoftMaxPlayer,
|
||||
bool PanicBunker,
|
||||
int RunLevel,
|
||||
string Preset);
|
||||
|
||||
public enum ContentCompressionScheme
|
||||
{
|
||||
None = 0,
|
||||
Deflate = 1,
|
||||
|
||||
/// <summary>
|
||||
/// ZStandard compression. In the future may use SS14 specific dictionary IDs in the frame header.
|
||||
/// </summary>
|
||||
ZStd = 2
|
||||
}
|
||||
|
||||
public sealed record VersionInfo(
|
||||
public sealed record EngineVersionInfo(
|
||||
bool Insecure,
|
||||
[property: JsonPropertyName("redirect")]
|
||||
string? RedirectVersion,
|
||||
Dictionary<string, BuildInfo> Platforms);
|
||||
Dictionary<string, EngineBuildInfo> 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<string> InferredTags);
|
||||
public sealed record ServerHubInfo(string Address, ServerStatus StatusData, List<string> InferredTags);
|
||||
|
||||
public sealed record StatusData(
|
||||
public sealed record ServerStatus(
|
||||
string Map,
|
||||
string Name,
|
||||
List<string> 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<string, Module> Modules);
|
||||
|
||||
public sealed record Module(Dictionary<string, ModuleVersionInfo> Versions);
|
||||
|
||||
public sealed record ModuleVersionInfo(Dictionary<string, BuildInfo> Platforms);
|
||||
public sealed record ModuleVersionInfo(Dictionary<string, EngineBuildInfo> Platforms);
|
||||
@@ -6,6 +6,7 @@
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -24,14 +25,12 @@
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="libsodium" Version="1.0.20" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageReference Include="SharpZstd.Interop" Version="1.5.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Views\Controls\ServerContainerControl.axaml.cs">
|
||||
<DependentUpon>ServerContainerControl.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\Tabs\ServerListTab.axaml.cs">
|
||||
<DependentUpon>ServerListTab.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
|
||||
@@ -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<TView, TViewModel>(this IServiceCollection services)
|
||||
where TView : class
|
||||
where TViewModel : class
|
||||
{
|
||||
services.AddTransient<TViewModel>();
|
||||
services.AddTransient<TView>();
|
||||
}
|
||||
|
||||
private static IEnumerable<(Type,Type)> GetTypesWithHelpAttribute(Assembly assembly) {
|
||||
foreach(Type type in assembly.GetTypes())
|
||||
|
||||
65
Nebula.Launcher/Services/AuthService.cs
Normal file
65
Nebula.Launcher/Services/AuthService.cs
Normal file
@@ -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<bool> 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<AuthenticateResponse, AuthenticateRequest>(
|
||||
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<bool> 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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public class HubService
|
||||
|
||||
public readonly ObservableCollection<string> HubList = new();
|
||||
|
||||
private readonly Dictionary<string, List<ServerInfo>> _servers = new();
|
||||
private readonly Dictionary<string, List<ServerHubInfo>> _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<List<ServerInfo>>(new Uri(urlStr), [], CancellationToken.None);
|
||||
var servers = await _restService.GetAsyncDefault<List<ServerHubInfo>>(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<ServerInfo> Items;
|
||||
public List<ServerHubInfo> Items;
|
||||
|
||||
public HubServerChangedEventArgs(List<ServerInfo> items, HubServerChangeAction action)
|
||||
public HubServerChangedEventArgs(List<ServerHubInfo> items, HubServerChangeAction action)
|
||||
{
|
||||
Items = items;
|
||||
Action = action;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Nebula.Launcher.Models;
|
||||
|
||||
namespace Nebula.Launcher.Utils;
|
||||
|
||||
|
||||
494
Nebula.Launcher/Utils/ZStd.cs
Normal file
494
Nebula.Launcher/Utils/ZStd.cs
Normal file
@@ -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<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));
|
||||
}
|
||||
}
|
||||
@@ -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<AuthLoginPasswordModel> 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<AuthLoginPasswordModel>(a => Accounts.Remove(a));
|
||||
var onSelect = new DelegateCommand<AuthLoginPasswordModel>(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<T>
|
||||
{
|
||||
public T Value = default!;
|
||||
}
|
||||
|
||||
internal 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;
|
||||
}
|
||||
|
||||
public record AuthLoginPasswordModel(string Login, string Password, string AuthServer, ICommand OnSelect = default!, ICommand OnDelete = default!)
|
||||
: AuthLoginPassword(Login, Password, AuthServer);
|
||||
@@ -11,18 +11,22 @@ namespace Nebula.Launcher.ViewModels;
|
||||
[ViewRegister(typeof(ServerListView))]
|
||||
public partial class ServerListViewModel : ViewModelBase
|
||||
{
|
||||
public ObservableCollection<ServerInfo> ServerInfos { get; }
|
||||
public ObservableCollection<ServerHubInfo> ServerInfos { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private ServerInfo? _selectedListItem;
|
||||
|
||||
private ServerHubInfo? _selectedListItem;
|
||||
|
||||
//Design think
|
||||
public ServerListViewModel()
|
||||
{
|
||||
ServerInfos = new ObservableCollection<ServerInfo>();
|
||||
ServerInfos = new ObservableCollection<ServerHubInfo>();
|
||||
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<ServerInfo>();
|
||||
ServerInfos = new ObservableCollection<ServerHubInfo>();
|
||||
hubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<UserControl
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Nebula.Launcher.Views.Controls.PlayerContainerControl"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
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">
|
||||
<Border
|
||||
BorderThickness="0,0,1,0"
|
||||
CornerRadius="0,10,0,10"
|
||||
Margin="5,4,5,0"
|
||||
VerticalAlignment="Center">
|
||||
<Panel>
|
||||
<StackPanel Margin="10,5,5,5" Orientation="Horizontal">
|
||||
<Label Margin="0,0,2,0">Name:</Label>
|
||||
<Label>Ni Higgers</Label>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<Button CornerRadius="0,0,0,10" Padding="5">
|
||||
<Label>
|
||||
Select
|
||||
</Label>
|
||||
</Button>
|
||||
<Button
|
||||
BorderThickness="2,0,0,0"
|
||||
CornerRadius="0,10,0,0"
|
||||
Padding="5">
|
||||
<Label>
|
||||
Delete
|
||||
</Label>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<UserControl
|
||||
d:DesignHeight="100"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Nebula.Launcher.Views.Controls.ServerContainerControl"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
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">
|
||||
<Grid
|
||||
ColumnDefinitions="*,70"
|
||||
Margin="0,5,0,5"
|
||||
RowDefinitions="40,*">
|
||||
<Border
|
||||
BorderThickness="2,0,0,0"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Padding="10">
|
||||
<TextBlock x:Name="ServerNameLabel">Server name</TextBlock>
|
||||
</Border>
|
||||
<Border
|
||||
BorderThickness="2,0,0,0"
|
||||
CornerRadius="0"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Padding="5">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center">15/15</Label>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
BorderThickness="2,0,0,0"
|
||||
CornerRadius="0,0,10,10"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
MinHeight="50">
|
||||
<Border.Background>
|
||||
<ImageBrush Stretch="UniformToFill" asyncImageLoader:ImageBrushLoader.Source="https://t4.ftcdn.net/jpg/00/81/55/69/360_F_81556974_8sF8cKszJaRfBGd5sDt1RXE2QbzDtQqs.jpg" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<Panel Grid.Column="1" Grid.Row="1">
|
||||
<Border Classes="ButtonBack" CornerRadius="0,0,10,0">
|
||||
<Button
|
||||
CornerRadius="0,0,10,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Padding="0"
|
||||
VerticalAlignment="Stretch">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
Play
|
||||
</Label>
|
||||
</Button>
|
||||
</Border>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,25 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Nebula.Launcher.Views.Controls;
|
||||
|
||||
public partial class ServerContainerControl : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<string> ServerNameProperty
|
||||
= AvaloniaProperty.Register<ServerContainerControl, string>(nameof (ServerName));
|
||||
|
||||
public string ServerName
|
||||
{
|
||||
get => GetValue(ServerNameProperty);
|
||||
set
|
||||
{
|
||||
SetValue(ServerNameProperty, value);
|
||||
ServerNameLabel.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ServerContainerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -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}">
|
||||
<Design.DataContext>
|
||||
<viewModels:AccountInfoViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid ColumnDefinitions="*,1.5*" RowDefinitions="*">
|
||||
<StackPanel Grid.Column="0" Grid.Row="0">
|
||||
<Border
|
||||
@@ -23,21 +25,28 @@
|
||||
Source="/Assets/account.png" />
|
||||
<Grid ColumnDefinitions="120, 100" RowDefinitions="Auto, Auto, Auto">
|
||||
<Label Grid.Column="0" Grid.Row="0">Login:</Label>
|
||||
<TextBox Grid.Column="1" Grid.Row="0" />
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Text="{Binding CurrentLogin}"/>
|
||||
<Label Grid.Column="0" Grid.Row="1">Password:</Label>
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
PasswordChar="#"
|
||||
Text="Sas" />
|
||||
Text="{Binding CurrentPassword}" />
|
||||
<Label Grid.Column="0" Grid.Row="2">Auth server:</Label>
|
||||
<TextBox Grid.Column="1" Grid.Row="2" />
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
Text="{Binding CurrentAuthServer}"/>
|
||||
</Grid>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
Margin="5"
|
||||
Margin="5" Spacing="5"
|
||||
Orientation="Horizontal">
|
||||
<Button>Save profile</Button>
|
||||
<Button Command="{Binding OnSaveProfile}"><Label>Save profile</Label></Button>
|
||||
<Button Command="{Binding DoAuth}"><Label>Auth</Label></Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -51,10 +60,40 @@
|
||||
<Label HorizontalAlignment="Center">Profiles:</Label>
|
||||
</Border>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<controls:PlayerContainerControl />
|
||||
<controls:PlayerContainerControl />
|
||||
</StackPanel>
|
||||
<ListBox
|
||||
Background="#00000000"
|
||||
ItemsSource="{Binding Accounts}"
|
||||
Padding="0" Classes="AccountSelector">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:AuthLoginPasswordModel}">
|
||||
<Border Margin="5,5,5,0"
|
||||
CornerRadius="0,10,0,10"
|
||||
VerticalAlignment="Center">
|
||||
<Panel>
|
||||
<StackPanel Margin="10,5,5,5" Orientation="Horizontal">
|
||||
<Label>Name:</Label>
|
||||
<Label><TextBlock Text="{Binding Login}"/></Label>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<Button CornerRadius="0,0,0,10" Padding="5" Command="{Binding OnSelect}">
|
||||
<Label>
|
||||
Select
|
||||
</Label>
|
||||
</Button>
|
||||
<Button
|
||||
BorderThickness="2,0,0,0"
|
||||
CornerRadius="0,10,0,0"
|
||||
Padding="5" Command="{Binding OnDelete}">
|
||||
<Label>
|
||||
Delete
|
||||
</Label>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -11,7 +11,7 @@ public partial class AccountInfoView : UserControl
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AccountInfoView(ViewModels.AccountInfoViewModel viewModel)
|
||||
public AccountInfoView(AccountInfoViewModel viewModel)
|
||||
: this()
|
||||
{
|
||||
DataContext = viewModel;
|
||||
|
||||
@@ -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">
|
||||
|
||||
<Design.DataContext>
|
||||
<viewModels:ServerListViewModel />
|
||||
@@ -23,8 +23,57 @@
|
||||
ItemsSource="{Binding ServerInfos}"
|
||||
Padding="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type models:ServerInfo}">
|
||||
<controls:ServerContainerControl ServerName="{Binding StatusData.Name}" />
|
||||
<DataTemplate DataType="{x:Type models:ServerHubInfo}">
|
||||
<Grid
|
||||
ColumnDefinitions="*,70"
|
||||
Margin="0,5,0,5"
|
||||
RowDefinitions="40,*">
|
||||
<Border
|
||||
BorderThickness="2,0,0,0"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Padding="10">
|
||||
<TextBlock Text="{Binding StatusData.Name}"/>
|
||||
</Border>
|
||||
<Border
|
||||
BorderThickness="2,0,0,0"
|
||||
CornerRadius="0"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Padding="5">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding StatusData.Players}"/>
|
||||
<TextBlock>/</TextBlock>
|
||||
<TextBlock Text="{Binding StatusData.SoftMaxPlayers}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
BorderThickness="2,0,0,0"
|
||||
CornerRadius="0,0,10,10"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
MinHeight="50">
|
||||
<Border.Background>
|
||||
<ImageBrush Stretch="UniformToFill" asyncImageLoader:ImageBrushLoader.Source="https://t4.ftcdn.net/jpg/00/81/55/69/360_F_81556974_8sF8cKszJaRfBGd5sDt1RXE2QbzDtQqs.jpg" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<Panel Grid.Column="1" Grid.Row="1">
|
||||
<Border Classes="ButtonBack" CornerRadius="0,0,10,0">
|
||||
<Button
|
||||
CornerRadius="0,0,10,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Padding="0"
|
||||
VerticalAlignment="Stretch">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
Play
|
||||
</Label>
|
||||
</Button>
|
||||
</Border>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
Reference in New Issue
Block a user