- add: Auth service
This commit is contained in:
1
.idea/.idea.Nebula/.idea/.name
generated
1
.idea/.idea.Nebula/.idea/.name
generated
@@ -1 +0,0 @@
|
|||||||
Nebula
|
|
||||||
@@ -57,4 +57,16 @@
|
|||||||
<Setter Property="Padding" Value="8" />
|
<Setter Property="Padding" Value="8" />
|
||||||
<Setter Property="Background" Value="#00000000" />
|
<Setter Property="Background" Value="#00000000" />
|
||||||
</Style>
|
</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>
|
</Styles>
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ public static class CurrentConVar
|
|||||||
"https://hub.spacestation14.com/api/servers"
|
"https://hub.spacestation14.com/api/servers"
|
||||||
]);
|
]);
|
||||||
public static readonly ConVar AuthServers = ConVar.Build<string[]>("launcher.authServers", [
|
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.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Nebula.Launcher.Models;
|
||||||
using Nebula.Launcher.Utils;
|
using Nebula.Launcher.Utils;
|
||||||
using Robust.LoaderApi;
|
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;
|
using System;
|
||||||
|
|
||||||
namespace Nebula.Launcher.Utils;
|
namespace Nebula.Launcher.Models;
|
||||||
|
|
||||||
public record struct RobustManifestInfo(Uri ManifestUri, Uri DownloadUri, string Hash);
|
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);
|
public record struct RobustManifestItem(string Hash, string Path, int Id);
|
||||||
@@ -4,9 +4,9 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace Nebula.Launcher.Models;
|
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 EngineVersion,
|
||||||
string ForkId,
|
string ForkId,
|
||||||
string Version,
|
string Version,
|
||||||
@@ -16,38 +16,16 @@ public sealed record Build(
|
|||||||
string Hash,
|
string Hash,
|
||||||
string ManifestHash);
|
string ManifestHash);
|
||||||
|
|
||||||
public sealed record Link(string Name, string Icon, string Url);
|
public sealed record ServerLink(string Name, string Icon, string Url);
|
||||||
public sealed record Info(string ConnectAddress, Auth Auth, Build Build, string Desc, List<Link> Links);
|
public sealed record ServerInfo(string ConnectAddress, AuthInfo Auth, BuildInfo Build, string Desc, List<ServerLink> Links);
|
||||||
|
|
||||||
public sealed record Status(
|
public sealed record EngineVersionInfo(
|
||||||
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(
|
|
||||||
bool Insecure,
|
bool Insecure,
|
||||||
[property: JsonPropertyName("redirect")]
|
[property: JsonPropertyName("redirect")]
|
||||||
string? RedirectVersion,
|
string? RedirectVersion,
|
||||||
Dictionary<string, BuildInfo> Platforms);
|
Dictionary<string, EngineBuildInfo> Platforms);
|
||||||
|
|
||||||
public sealed class BuildInfo
|
public sealed class EngineBuildInfo
|
||||||
{
|
{
|
||||||
[JsonInclude] [JsonPropertyName("sha256")]
|
[JsonInclude] [JsonPropertyName("sha256")]
|
||||||
public string Sha256 = default!;
|
public string Sha256 = default!;
|
||||||
@@ -59,9 +37,9 @@ public sealed class BuildInfo
|
|||||||
public string Url = default!;
|
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 Map,
|
||||||
string Name,
|
string Name,
|
||||||
List<string> Tags,
|
List<string> Tags,
|
||||||
@@ -70,11 +48,11 @@ public sealed record StatusData(
|
|||||||
int RoundId,
|
int RoundId,
|
||||||
int RunLevel,
|
int RunLevel,
|
||||||
bool PanicBunker,
|
bool PanicBunker,
|
||||||
DateTime RoundStartTime,
|
DateTime? RoundStartTime,
|
||||||
int SoftMaxPlayer);
|
int SoftMaxPlayers);
|
||||||
|
|
||||||
public sealed record ModulesInfo(Dictionary<string, Module> Modules);
|
public sealed record ModulesInfo(Dictionary<string, Module> Modules);
|
||||||
|
|
||||||
public sealed record Module(Dictionary<string, ModuleVersionInfo> Versions);
|
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>
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -24,14 +25,12 @@
|
|||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
<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="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||||
|
<PackageReference Include="SharpZstd.Interop" Version="1.5.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Views\Controls\ServerContainerControl.axaml.cs">
|
|
||||||
<DependentUpon>ServerContainerControl.axaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Views\Tabs\ServerListTab.axaml.cs">
|
<Compile Update="Views\Tabs\ServerListTab.axaml.cs">
|
||||||
<DependentUpon>ServerListTab.axaml</DependentUpon>
|
<DependentUpon>ServerListTab.axaml</DependentUpon>
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
foreach (var (viewModel, view) in GetTypesWithHelpAttribute(Assembly.GetExecutingAssembly()))
|
foreach (var (viewModel, view) in GetTypesWithHelpAttribute(Assembly.GetExecutingAssembly()))
|
||||||
{
|
{
|
||||||
services.AddTransient(viewModel);
|
services.AddSingleton(viewModel);
|
||||||
services.AddTransient(view);
|
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) {
|
private static IEnumerable<(Type,Type)> GetTypesWithHelpAttribute(Assembly assembly) {
|
||||||
foreach(Type type in assembly.GetTypes())
|
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;
|
return value != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetValue(ConVar conVar, object value)
|
public void SetConfigValue(ConVar conVar, object value)
|
||||||
{
|
{
|
||||||
if(conVar.Type != value.GetType())
|
if(conVar.Type != value.GetType())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.IO.Compression;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Nebula.Launcher.FileApis;
|
using Nebula.Launcher.FileApis;
|
||||||
using Nebula.Launcher.FileApis.Interfaces;
|
using Nebula.Launcher.FileApis.Interfaces;
|
||||||
|
using Nebula.Launcher.Models;
|
||||||
using Nebula.Launcher.Utils;
|
using Nebula.Launcher.Utils;
|
||||||
using Robust.LoaderApi;
|
using Robust.LoaderApi;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class HubService
|
|||||||
|
|
||||||
public readonly ObservableCollection<string> HubList = new();
|
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)
|
public HubService(ConfigurationService configurationService, RestService restService)
|
||||||
@@ -37,7 +37,7 @@ public class HubService
|
|||||||
foreach (var hubUri in e.NewItems)
|
foreach (var hubUri in e.NewItems)
|
||||||
{
|
{
|
||||||
var urlStr = (string)hubUri;
|
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;
|
_servers[urlStr] = servers;
|
||||||
HubServerChangedEventArgs?.Invoke(new HubServerChangedEventArgs(servers, HubServerChangeAction.Add));
|
HubServerChangedEventArgs?.Invoke(new HubServerChangedEventArgs(servers, HubServerChangeAction.Add));
|
||||||
}
|
}
|
||||||
@@ -61,9 +61,9 @@ public class HubService
|
|||||||
public class HubServerChangedEventArgs : EventArgs
|
public class HubServerChangedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public HubServerChangeAction Action;
|
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;
|
Items = items;
|
||||||
Action = action;
|
Action = action;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Nebula.Launcher.Models;
|
||||||
|
|
||||||
namespace Nebula.Launcher.Utils;
|
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;
|
||||||
|
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.ViewHelper;
|
||||||
using Nebula.Launcher.Views.Pages;
|
using Nebula.Launcher.Views.Pages;
|
||||||
|
|
||||||
namespace Nebula.Launcher.ViewModels;
|
namespace Nebula.Launcher.ViewModels;
|
||||||
|
|
||||||
[ViewRegister(typeof(AccountInfoView))]
|
[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))]
|
[ViewRegister(typeof(ServerListView))]
|
||||||
public partial class ServerListViewModel : ViewModelBase
|
public partial class ServerListViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public ObservableCollection<ServerInfo> ServerInfos { get; }
|
public ObservableCollection<ServerHubInfo> ServerInfos { get; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ServerInfo? _selectedListItem;
|
private ServerHubInfo? _selectedListItem;
|
||||||
|
|
||||||
|
//Design think
|
||||||
public ServerListViewModel()
|
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)
|
public ServerListViewModel(IServiceProvider serviceProvider, HubService hubService) : base(serviceProvider)
|
||||||
{
|
{
|
||||||
ServerInfos = new ObservableCollection<ServerInfo>();
|
ServerInfos = new ObservableCollection<ServerHubInfo>();
|
||||||
hubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
|
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:Class="Nebula.Launcher.Views.Pages.AccountInfoView"
|
||||||
x:DataType="viewModels:AccountInfoViewModel"
|
x:DataType="viewModels:AccountInfoViewModel"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:controls="clr-namespace:Nebula.Launcher.Views.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
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="*">
|
<Grid ColumnDefinitions="*,1.5*" RowDefinitions="*">
|
||||||
<StackPanel Grid.Column="0" Grid.Row="0">
|
<StackPanel Grid.Column="0" Grid.Row="0">
|
||||||
<Border
|
<Border
|
||||||
@@ -23,21 +25,28 @@
|
|||||||
Source="/Assets/account.png" />
|
Source="/Assets/account.png" />
|
||||||
<Grid ColumnDefinitions="120, 100" RowDefinitions="Auto, Auto, Auto">
|
<Grid ColumnDefinitions="120, 100" RowDefinitions="Auto, Auto, Auto">
|
||||||
<Label Grid.Column="0" Grid.Row="0">Login:</Label>
|
<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>
|
<Label Grid.Column="0" Grid.Row="1">Password:</Label>
|
||||||
<TextBox
|
<TextBox
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
PasswordChar="#"
|
PasswordChar="#"
|
||||||
Text="Sas" />
|
Text="{Binding CurrentPassword}" />
|
||||||
<Label Grid.Column="0" Grid.Row="2">Auth server:</Label>
|
<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>
|
</Grid>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Margin="5"
|
Margin="5" Spacing="5"
|
||||||
Orientation="Horizontal">
|
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>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -51,10 +60,40 @@
|
|||||||
<Label HorizontalAlignment="Center">Profiles:</Label>
|
<Label HorizontalAlignment="Center">Profiles:</Label>
|
||||||
</Border>
|
</Border>
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel>
|
<ListBox
|
||||||
<controls:PlayerContainerControl />
|
Background="#00000000"
|
||||||
<controls:PlayerContainerControl />
|
ItemsSource="{Binding Accounts}"
|
||||||
</StackPanel>
|
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>
|
</ScrollViewer>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public partial class AccountInfoView : UserControl
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountInfoView(ViewModels.AccountInfoViewModel viewModel)
|
public AccountInfoView(AccountInfoViewModel viewModel)
|
||||||
: this()
|
: this()
|
||||||
{
|
{
|
||||||
DataContext = viewModel;
|
DataContext = viewModel;
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
x:Class="Nebula.Launcher.Views.Pages.ServerListView"
|
x:Class="Nebula.Launcher.Views.Pages.ServerListView"
|
||||||
x:DataType="viewModels:ServerListViewModel"
|
x:DataType="viewModels:ServerListViewModel"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:controls="clr-namespace:Nebula.Launcher.Views.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:models="clr-namespace:Nebula.Launcher.Models"
|
xmlns:models="clr-namespace:Nebula.Launcher.Models"
|
||||||
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
|
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>
|
<Design.DataContext>
|
||||||
<viewModels:ServerListViewModel />
|
<viewModels:ServerListViewModel />
|
||||||
@@ -23,8 +23,57 @@
|
|||||||
ItemsSource="{Binding ServerInfos}"
|
ItemsSource="{Binding ServerInfos}"
|
||||||
Padding="0">
|
Padding="0">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate DataType="{x:Type models:ServerInfo}">
|
<DataTemplate DataType="{x:Type models:ServerHubInfo}">
|
||||||
<controls:ServerContainerControl ServerName="{Binding StatusData.Name}" />
|
<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>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
|
|||||||
Reference in New Issue
Block a user