diff --git a/Nebula.Launcher/App.axaml.cs b/Nebula.Launcher/App.axaml.cs
index 885f647..1b1ea20 100644
--- a/Nebula.Launcher/App.axaml.cs
+++ b/Nebula.Launcher/App.axaml.cs
@@ -4,6 +4,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using System.Linq;
+using System.Reflection;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.ViewModels;
diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj
index a6943fb..3392f9d 100644
--- a/Nebula.Launcher/Nebula.Launcher.csproj
+++ b/Nebula.Launcher/Nebula.Launcher.csproj
@@ -27,6 +27,7 @@
+
@@ -35,6 +36,13 @@
Code
+
+
+
+
+
+
+
diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
index be2bfb5..ecd0cb8 100644
--- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
+++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
@@ -3,31 +3,106 @@ using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using Nebula.Launcher.ViewHelper;
using Nebula.Shared.Models;
+using Nebula.Shared.Services;
namespace Nebula.Launcher.ViewModels;
[ViewModelRegister(isSingleton:false)]
public partial class ServerEntryModelView : ViewModelBase
{
+ private readonly AuthService _authService = default!;
+ private readonly ContentService _contentService = default!;
+ private readonly CancellationService _cancellationService = default!;
+ private readonly DebugService _debugService = default!;
+ private readonly RunnerService _runnerService;
+
[ObservableProperty] private bool _runVisible = true;
- public ServerHubInfo ServerHubInfo { get; set; }
+ public ServerHubInfo ServerHubInfo { get; set; } = default!;
public ServerEntryModelView() : base()
{
}
- public ServerEntryModelView(IServiceProvider serviceProvider) : base(serviceProvider)
+ public ServerEntryModelView(
+ IServiceProvider serviceProvider,
+ AuthService authService,
+ ContentService contentService,
+ CancellationService cancellationService,
+ DebugService debugService,
+ RunnerService runnerService
+ ) : base(serviceProvider)
{
+ _authService = authService;
+ _contentService = contentService;
+ _cancellationService = cancellationService;
+ _debugService = debugService;
+ _runnerService = runnerService;
}
- public void RunInstance()
+ private Process? _process;
+
+ public async void RunInstance()
{
- var p = Process.Start("./Nebula.Runner", "a b c");
- p.BeginOutputReadLine();
- p.BeginErrorReadLine();
+ var authProv = _authService.SelectedAuth;
+
+ var buildInfo = await _contentService.GetBuildInfo(new RobustUrl(ServerHubInfo.Address), _cancellationService.Token);
+
+ await _runnerService.PrepareRun(buildInfo, _cancellationService.Token);
+
+ _process = Process.Start(new ProcessStartInfo()
+ {
+ FileName = "dotnet.exe",
+ Arguments = "./Nebula.Runner.dll",
+ Environment = {
+ { "ROBUST_AUTH_USERID", authProv?.UserId.ToString() } ,
+ { "ROBUST_AUTH_TOKEN", authProv?.Token.Token } ,
+ { "ROBUST_AUTH_SERVER", authProv?.AuthLoginPassword.AuthServer } ,
+ { "ROBUST_AUTH_PUBKEY", buildInfo.BuildInfo.Auth.PublicKey } ,
+ { "GAME_URL", ServerHubInfo.Address } ,
+ { "AUTH_LOGIN", authProv?.AuthLoginPassword.Login } ,
+ },
+ CreateNoWindow = true, UseShellExecute = false
+ });
+
+ if (_process is null)
+ {
+ return;
+ }
+
+ _process.OutputDataReceived += OnOutputDataReceived;
+ _process.ErrorDataReceived += OnErrorDataReceived;
+
+ _process.Exited += OnExited;
}
-
+
+ private void OnExited(object? sender, EventArgs e)
+ {
+ if (_process is null)
+ {
+ return;
+ }
+
+ _process.OutputDataReceived -= OnOutputDataReceived;
+ _process.ErrorDataReceived -= OnErrorDataReceived;
+ _process.Exited -= OnExited;
+
+ _debugService.Log("PROCESS EXIT WITH CODE " + _process.ExitCode);
+
+ _process.Dispose();
+ _process = null;
+ }
+
+ private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data != null) _debugService.Error(e.Data);
+ }
+
+ private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data != null) _debugService.Log(e.Data);
+ }
+
public void ReadLog()
{
@@ -36,6 +111,25 @@ public partial class ServerEntryModelView : ViewModelBase
public void StopInstance()
{
-
+ _process?.Close();
+ }
+
+ static string FindDotnetPath()
+ {
+ var pathEnv = Environment.GetEnvironmentVariable("PATH");
+ var paths = pathEnv?.Split(System.IO.Path.PathSeparator);
+ if (paths != null)
+ {
+ foreach (var path in paths)
+ {
+ var dotnetPath = System.IO.Path.Combine(path, "dotnet");
+ if (System.IO.File.Exists(dotnetPath))
+ {
+ return dotnetPath;
+ }
+ }
+ }
+
+ throw new Exception("Dotnet not found!");
}
}
\ No newline at end of file
diff --git a/Nebula.Runner/App.cs b/Nebula.Runner/App.cs
index 9a1688e..888cf41 100644
--- a/Nebula.Runner/App.cs
+++ b/Nebula.Runner/App.cs
@@ -1,14 +1,57 @@
using Nebula.Shared;
+using Nebula.Shared.Models;
using Nebula.Shared.Services;
+using Nebula.Shared.Utils;
+using Robust.LoaderApi;
namespace Nebula.Runner;
[ServiceRegister]
-public class App(DebugService debugService)
+public sealed class App(DebugService debugService, RunnerService runnerService, ContentService contentService) : IRedialApi
{
-
- public void Run(string[] args)
+ public async Task Run(string[] args1)
{
- debugService.Log("HELLO!!! " + string.Join(" ",args));
+ debugService.Log("HELLO!!! ");
+
+ var login = Environment.GetEnvironmentVariable("AUTH_LOGIN") ?? "Alexandra";
+ var urlraw = Environment.GetEnvironmentVariable("GAME_URL") ?? "ss14://localhost";
+
+ var url = urlraw.ToRobustUrl();
+
+ using var cancelTokenSource = new CancellationTokenSource();
+ var buildInfo = await contentService.GetBuildInfo(url, cancelTokenSource.Token);
+
+
+ var args = new List
+ {
+ // Pass username to launched client.
+ // We don't load username from client_config.toml when launched via launcher.
+ "--username", login,
+
+ // Tell game we are launcher
+ "--cvar", "launch.launcher=true"
+ };
+
+ var connectionString = url.ToString();
+ if (!string.IsNullOrEmpty(buildInfo.BuildInfo.ConnectAddress))
+ connectionString = buildInfo.BuildInfo.ConnectAddress;
+
+ // We are using the launcher. Don't show main menu etc..
+ // Note: --launcher also implied --connect.
+ // For this reason, content bundles do not set --launcher.
+ args.Add("--launcher");
+
+ args.Add("--connect-address");
+ args.Add(connectionString);
+
+ args.Add("--ss14-address");
+ args.Add(url.ToString());
+
+ await runnerService.Run(args.ToArray(), buildInfo, this, cancelTokenSource.Token);
+ }
+
+ public void Redial(Uri uri, string text = "")
+ {
+
}
}
\ No newline at end of file
diff --git a/Nebula.Runner/Nebula.Runner.csproj b/Nebula.Runner/Nebula.Runner.csproj
index 63b96af..60b4687 100644
--- a/Nebula.Runner/Nebula.Runner.csproj
+++ b/Nebula.Runner/Nebula.Runner.csproj
@@ -1,6 +1,7 @@
+ WinExe
net8.0
enable
enable
@@ -12,6 +13,6 @@
+
-
diff --git a/Nebula.Runner/Program.cs b/Nebula.Runner/Program.cs
index ad79ca3..47fb294 100644
--- a/Nebula.Runner/Program.cs
+++ b/Nebula.Runner/Program.cs
@@ -1,3 +1,4 @@
+using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Shared;
@@ -11,6 +12,7 @@ public static class Program
services.AddServices();
var serviceProvider = services.BuildServiceProvider();
- serviceProvider.GetService()!.Run(args);
+ var task = serviceProvider.GetService()!.Run(args);
+ task.Wait();
}
}
\ No newline at end of file
diff --git a/Nebula.Shared/Nebula.Shared.csproj b/Nebula.Shared/Nebula.Shared.csproj
index 8a8daa2..66ac89e 100644
--- a/Nebula.Shared/Nebula.Shared.csproj
+++ b/Nebula.Shared/Nebula.Shared.csproj
@@ -8,12 +8,10 @@
-
-
-
-
+
+ Utility.runtime.json
+
-
diff --git a/Nebula.Shared/ServiceManager.cs b/Nebula.Shared/ServiceManager.cs
index 9e1dfc9..c980b10 100644
--- a/Nebula.Shared/ServiceManager.cs
+++ b/Nebula.Shared/ServiceManager.cs
@@ -7,8 +7,17 @@ public static class ServiceExt
{
public static void AddServices(this IServiceCollection services)
{
- foreach (var (type, inference) in GetServicesWithHelpAttribute(Assembly.GetExecutingAssembly()))
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
+ AddServices(services, assembly);
+ }
+ }
+
+ public static void AddServices(this IServiceCollection services, Assembly assembly)
+ {
+ foreach (var (type, inference) in GetServicesWithHelpAttribute(assembly))
+ {
+ Console.WriteLine("[ServiceMng] ADD SERVICE " + type);
if (inference is null)
{
services.AddSingleton(type);
diff --git a/Nebula.Shared/Services/AssemblyService.cs b/Nebula.Shared/Services/AssemblyService.cs
index 432d115..cbf5035 100644
--- a/Nebula.Shared/Services/AssemblyService.cs
+++ b/Nebula.Shared/Services/AssemblyService.cs
@@ -10,20 +10,36 @@ namespace Nebula.Shared.Services;
[ServiceRegister]
public class AssemblyService
{
- private readonly Dictionary _assemblies = new();
+ private readonly List _assemblies = new();
private readonly DebugService _debugService;
public AssemblyService(DebugService debugService)
{
_debugService = debugService;
+
+ SharpZstd.Interop.ZstdImportResolver.ResolveLibrary += (name, assembly1, path) =>
+ {
+ if (name.Equals("SharpZstd.Native"))
+ {
+ _debugService.Debug("RESOLVING SHARPZSTD THINK: " + name + " " + path);
+ GetRuntimeInfo(out string platform, out string architecture, out string extension);
+ string fileName = GetDllName(platform, architecture, extension);
+
+ if (NativeLibrary.TryLoad(fileName, assembly1, path, out var nativeLibrary))
+ {
+ return nativeLibrary;
+ }
+ }
+ return IntPtr.Zero;
+ };
}
- //public IReadOnlyList Assemblies => _assemblies;
+ public IReadOnlyList Assemblies => _assemblies;
- public AssemblyApi Mount(IFileApi fileApi, string apiName = "")
+ public AssemblyApi Mount(IFileApi fileApi)
{
var asmApi = new AssemblyApi(fileApi);
- AssemblyLoadContext.Default.Resolving += (context, name) => OnAssemblyResolving(context, name, asmApi, apiName);
+ AssemblyLoadContext.Default.Resolving += (context, name) => OnAssemblyResolving(context, name, asmApi);
AssemblyLoadContext.Default.ResolvingUnmanagedDll += LoadContextOnResolvingUnmanaged;
return asmApi;
@@ -53,11 +69,6 @@ public class AssemblyService
public bool TryOpenAssembly(string name, AssemblyApi assemblyApi, [NotNullWhen(true)] out Assembly? assembly)
{
- if (_assemblies.TryGetValue(name, out assembly))
- {
- return true;
- }
-
if (!TryOpenAssemblyStream(name, assemblyApi, out var asm, out var pdb))
{
assembly = null;
@@ -66,8 +77,9 @@ public class AssemblyService
assembly = AssemblyLoadContext.Default.LoadFromStream(asm, pdb);
_debugService.Log("LOADED ASSEMBLY " + name);
-
- _assemblies.Add(name, assembly);
+
+
+ if (!_assemblies.Contains(assembly)) _assemblies.Add(assembly);
asm.Dispose();
pdb?.Dispose();
@@ -86,13 +98,27 @@ public class AssemblyService
assemblyApi.TryOpen($"{name}.pdb", out pdb);
return true;
}
+
+ private readonly HashSet _resolvingAssemblies = new HashSet();
- private Assembly? OnAssemblyResolving(AssemblyLoadContext context, AssemblyName name, AssemblyApi assemblyApi,
- string apiName)
+ private Assembly? OnAssemblyResolving(AssemblyLoadContext context, AssemblyName name, AssemblyApi assemblyApi)
{
-
- _debugService.Debug($"Resolving assembly from {apiName}: {name.Name}");
- return TryOpenAssembly(name.Name!, assemblyApi, out var assembly) ? assembly : null;
+ if (_resolvingAssemblies.Contains(name.FullName))
+ {
+ _debugService.Debug($"Already resolving {name.Name}, skipping.");
+ return null; // Prevent recursive resolution
+ }
+
+ try
+ {
+ _resolvingAssemblies.Add(name.FullName);
+ _debugService.Debug($"Resolving assembly from FileAPI: {name.Name}");
+ return TryOpenAssembly(name.Name!, assemblyApi, out var assembly) ? assembly : null;
+ }
+ finally
+ {
+ _resolvingAssemblies.Remove(name.FullName);
+ }
}
private IntPtr LoadContextOnResolvingUnmanaged(Assembly assembly, string unmanaged)
@@ -104,7 +130,66 @@ public class AssemblyService
if (NativeLibrary.TryLoad(a, out var handle))
return handle;
+
+ _debugService.Error("Loading dll error! Not found");
return IntPtr.Zero;
}
+
+ public static string GetDllName(
+ string platform,
+ string architecture,
+ string extension)
+ {
+ string name = $"SharpZstd.Native.{extension}";
+ return name;
+ }
+
+ public static void GetRuntimeInfo(
+ out string platform,
+ out string architecture,
+ out string extension)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ platform = "win";
+ extension = "dll";
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ platform = "linux";
+ extension = "so";
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ platform = "osx";
+ extension = "dylib";
+ }
+ else
+ {
+ platform = "linux";
+ extension = "so";
+ }
+
+ if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
+ {
+ architecture = "x64";
+ }
+ else if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
+ {
+ architecture = "x86";
+ }
+ else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
+ {
+ architecture = "arm";
+ }
+ else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
+ {
+ architecture = "arm64";
+ }
+ else
+ {
+ throw new PlatformNotSupportedException("Unsupported process architecture.");
+ }
+ }
}
\ No newline at end of file
diff --git a/Nebula.Shared/Services/AuthService.cs b/Nebula.Shared/Services/AuthService.cs
index 28f5987..d369413 100644
--- a/Nebula.Shared/Services/AuthService.cs
+++ b/Nebula.Shared/Services/AuthService.cs
@@ -45,6 +45,12 @@ public partial class AuthService(
SelectedAuth = null;
}
+ public void SetAuth(Guid guid, string token, string login, string authServer)
+ {
+ SelectedAuth = new CurrentAuthInfo(guid, new LoginToken(token, DateTimeOffset.Now),
+ new AuthLoginPassword(login, "", authServer));
+ }
+
public async Task EnsureToken()
{
if (SelectedAuth is null) return false;
diff --git a/Nebula.Shared/Services/EngineService.cs b/Nebula.Shared/Services/EngineService.cs
index f0fc197..c4a3f58 100644
--- a/Nebula.Shared/Services/EngineService.cs
+++ b/Nebula.Shared/Services/EngineService.cs
@@ -81,7 +81,7 @@ public sealed class EngineService
try
{
- return _assemblyService.Mount(_fileService.OpenZip(version, _fileService.EngineFileApi),$"Engine.Ensure-{version}");
+ return _assemblyService.Mount(_fileService.OpenZip(version, _fileService.EngineFileApi));
}
catch (Exception e)
{
@@ -161,7 +161,7 @@ public sealed class EngineService
try
{
- return _assemblyService.Mount(_fileService.OpenZip(fileName, _fileService.EngineFileApi),"Engine.EnsureModule");
+ return _assemblyService.Mount(_fileService.OpenZip(fileName, _fileService.EngineFileApi));
}
catch (Exception e)
{
diff --git a/Nebula.Shared/Services/RunnerService.cs b/Nebula.Shared/Services/RunnerService.cs
index a0e0ee2..751f3d1 100644
--- a/Nebula.Shared/Services/RunnerService.cs
+++ b/Nebula.Shared/Services/RunnerService.cs
@@ -10,18 +10,8 @@ public sealed class RunnerService(
ConfigurationService varService,
FileService fileService,
EngineService engineService,
- AssemblyService assemblyService,
- AuthService authService,
- PopupMessageService popupMessageService,
- CancellationService cancellationService)
- : IRedialApi
+ AssemblyService assemblyService)
{
- public async Task PrepareRun(RobustUrl url)
- {
- var buildInfo = await contentService.GetBuildInfo(url, cancellationService.Token);
- await PrepareRun(buildInfo, cancellationService.Token);
- }
-
public async Task PrepareRun(RobustBuildInfo buildInfo, CancellationToken cancellationToken)
{
debugService.Log("Prepare Content!");
@@ -67,60 +57,4 @@ public sealed class RunnerService(
await Task.Run(() => loader.Main(args), cancellationToken);
}
-
- public async Task RunGame(string urlraw)
- {
- var url = new RobustUrl(urlraw);
-
- using var cancelTokenSource = new CancellationTokenSource();
- var buildInfo = await contentService.GetBuildInfo(url, cancelTokenSource.Token);
-
- var account = authService.SelectedAuth;
- if (account is null)
- {
- popupMessageService.Popup("Error! Auth is required!");
- return;
- }
-
- if (buildInfo.BuildInfo.Auth.Mode != "Disabled")
- {
- Environment.SetEnvironmentVariable("ROBUST_AUTH_TOKEN", account.Token.Token);
- Environment.SetEnvironmentVariable("ROBUST_AUTH_USERID", account.UserId.ToString());
- Environment.SetEnvironmentVariable("ROBUST_AUTH_PUBKEY", buildInfo.BuildInfo.Auth.PublicKey);
- Environment.SetEnvironmentVariable("ROBUST_AUTH_SERVER", account.AuthLoginPassword.AuthServer);
- }
-
- var args = new List
- {
- // Pass username to launched client.
- // We don't load username from client_config.toml when launched via launcher.
- "--username", account.AuthLoginPassword.Login,
-
- // Tell game we are launcher
- "--cvar", "launch.launcher=true"
- };
-
- var connectionString = url.ToString();
- if (!string.IsNullOrEmpty(buildInfo.BuildInfo.ConnectAddress))
- connectionString = buildInfo.BuildInfo.ConnectAddress;
-
- // We are using the launcher. Don't show main menu etc..
- // Note: --launcher also implied --connect.
- // For this reason, content bundles do not set --launcher.
- args.Add("--launcher");
-
- args.Add("--connect-address");
- args.Add(connectionString);
-
- args.Add("--ss14-address");
- args.Add(url.ToString());
- debugService.Debug("Connect to " + url.ToString() + " " + account.AuthLoginPassword.AuthServer);
-
- await Run(args.ToArray(), buildInfo, this, cancelTokenSource.Token);
- }
-
- public async void Redial(Uri uri, string text = "")
- {
- //await RunGame(uri.ToString());
- }
}
\ No newline at end of file