- add: DotNet runtime download

This commit is contained in:
2025-06-15 13:48:56 +03:00
parent 30a526a746
commit 22a6b3157d
9 changed files with 236 additions and 46 deletions

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Nebula.Launcher.Models; using Nebula.Launcher.Models;
using Nebula.Launcher.ViewModels.Pages; using Nebula.Launcher.ViewModels.Pages;
using Nebula.Shared.Services; using Nebula.Shared.Services;
@@ -32,4 +33,6 @@ public static class LauncherConVar
public static readonly ConVar<string> CurrentLang = ConVarBuilder.Build<string>("launcher.language", "en-US"); public static readonly ConVar<string> CurrentLang = ConVarBuilder.Build<string>("launcher.language", "en-US");
public static readonly ConVar<string> ILSpyUrl = ConVarBuilder.Build<string>("decompiler.url", public static readonly ConVar<string> ILSpyUrl = ConVarBuilder.Build<string>("decompiler.url",
"https://github.com/icsharpcode/ILSpy/releases/download/v9.0/ILSpy_binaries_9.0.0.7889-x64.zip"); "https://github.com/icsharpcode/ILSpy/releases/download/v9.0/ILSpy_binaries_9.0.0.7889-x64.zip");
} }

View File

@@ -39,6 +39,11 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol
public void OnBackEnter() public void OnBackEnter()
{ {
if (CurrentEntry.Parent is null)
{
SetHubRoot();
return;
}
CurrentEntry.Parent?.GoCurrent(); CurrentEntry.Parent?.GoCurrent();
} }
@@ -116,6 +121,8 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol
public void SetHubRoot() public void SetHubRoot()
{ {
ServerText = string.Empty;
SearchText = string.Empty;
var root = ViewHelperService.GetViewModel<ServerListContentEntry>(); var root = ViewHelperService.GetViewModel<ServerListContentEntry>();
root.InitHubList(this); root.InitHubList(this);
CurrentEntry = root; CurrentEntry = root;

View File

@@ -55,6 +55,7 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer
[GenerateProperty] private RestService RestService { get; } = default!; [GenerateProperty] private RestService RestService { get; } = default!;
[GenerateProperty] private MainViewModel MainViewModel { get; } = default!; [GenerateProperty] private MainViewModel MainViewModel { get; } = default!;
[GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } = default!; [GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } = default!;
[GenerateProperty] private DotnetResolverService DotnetResolverService { get; } = default!;
public ServerStatus Status { get; private set; } = public ServerStatus Status { get; private set; } =
new( new(
@@ -210,10 +211,10 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer
await RunnerService.PrepareRun(buildInfo, loadingContext, CancellationService.Token); await RunnerService.PrepareRun(buildInfo, loadingContext, CancellationService.Token);
var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
Process = Process.Start(new ProcessStartInfo Process = Process.Start(new ProcessStartInfo
{ {
FileName = "dotnet.exe", FileName = await DotnetResolverService.EnsureDotnet(),
Arguments = Path.Join(path, "Nebula.Runner.dll"), Arguments = Path.Join(path, "Nebula.Runner.dll"),
Environment = Environment =
{ {

View File

@@ -28,4 +28,11 @@ public static class CurrentConVar
ConVarBuilder.Build<Dictionary<string, EngineVersionInfo>>("engine.manifest.backup"); ConVarBuilder.Build<Dictionary<string, EngineVersionInfo>>("engine.manifest.backup");
public static readonly ConVar<ModulesInfo> ModuleManifestBackup = public static readonly ConVar<ModulesInfo> ModuleManifestBackup =
ConVarBuilder.Build<ModulesInfo>("module.manifest.backup"); ConVarBuilder.Build<ModulesInfo>("module.manifest.backup");
public static readonly ConVar<Dictionary<string,string>> DotnetUrl = ConVarBuilder.Build<Dictionary<string,string>>("dotnet.url",
new(){
{"win-x64", "https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-win-x64.zip"},
{"win-x86", "https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-win-x86.zip"},
{"linux-x64", "https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-linux-x64.tar.gz"}
});
} }

View File

@@ -0,0 +1,70 @@
using System.Diagnostics;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
namespace Nebula.Shared.Services;
[ServiceRegister]
public class DotnetResolverService(DebugService debugService, ConfigurationService configurationService)
{
private HttpClient _httpClient = new HttpClient();
private static readonly string FullPath = Path.Join(FileService.RootPath, "dotnet", DotnetUrlHelper.GetRuntimeIdentifier());
private static readonly string ExecutePath = Path.Join(FullPath, "dotnet" + DotnetUrlHelper.GetExtension());
public async Task<string> EnsureDotnet(){
if(!Directory.Exists(FullPath))
await Download();
return ExecutePath;
}
private async Task Download(){
debugService.GetLogger("DotnetResolver").Log($"Downloading dotnet {DotnetUrlHelper.GetRuntimeIdentifier()}...");
var ridExt =
DotnetUrlHelper.GetCurrentPlatformDotnetUrl(configurationService.GetConfigValue(CurrentConVar.DotnetUrl)!);
using var response = await _httpClient.GetAsync(ridExt);
using var zipArchive = new ZipArchive(await response.Content.ReadAsStreamAsync());
Directory.CreateDirectory(FullPath);
zipArchive.ExtractToDirectory(FullPath);
debugService.GetLogger("DotnetResolver").Log($"Downloading dotnet complete.");
}
}
public static class DotnetUrlHelper
{
public static string GetExtension()
{
if (OperatingSystem.IsWindows()) return ".exe";
return "";
}
public static string GetCurrentPlatformDotnetUrl(Dictionary<string, string> dotnetUrl)
{
string? rid = GetRuntimeIdentifier();
if (dotnetUrl.TryGetValue(rid, out var url))
{
return url;
}
throw new PlatformNotSupportedException($"No download URL available for the current platform: {rid}");
}
public static string GetRuntimeIdentifier()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Environment.Is64BitProcess ? "win-x64" : "win-x86";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "linux-x64";
}
throw new PlatformNotSupportedException("Unsupported operating system");
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Nebula.UpdateResolver.Configuration;
namespace Nebula.UpdateResolver;
public static class DotnetStandalone
{
private static readonly HttpClient HttpClient = new HttpClient();
private static readonly string FullPath = Path.Join(MainWindow.RootPath, "dotnet", DotnetUrlHelper.GetRuntimeIdentifier());
private static readonly string ExecutePath = Path.Join(FullPath, "dotnet" + DotnetUrlHelper.GetExtension());
public static async Task<Process?> Run(string dllPath)
{
await EnsureDotnet();
return Process.Start(new ProcessStartInfo
{
FileName = ExecutePath,
Arguments = dllPath,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8
});
}
private static async Task EnsureDotnet(){
if(!Directory.Exists(FullPath))
await Download();
}
private static async Task Download(){
LogStandalone.Log($"Downloading dotnet {DotnetUrlHelper.GetRuntimeIdentifier()}...");
var ridExt =
DotnetUrlHelper.GetCurrentPlatformDotnetUrl(ConfigurationStandalone.GetConfigValue(UpdateConVars.DotnetUrl)!);
using var response = await HttpClient.GetAsync(ridExt);
using var zipArchive = new ZipArchive(await response.Content.ReadAsStreamAsync());
Directory.CreateDirectory(FullPath);
zipArchive.ExtractToDirectory(FullPath);
LogStandalone.Log($"Downloading dotnet complete.");
}
}
public static class DotnetUrlHelper
{
public static string GetExtension()
{
if (OperatingSystem.IsWindows()) return ".exe";
return "";
}
public static string GetCurrentPlatformDotnetUrl(Dictionary<string, string> dotnetUrl)
{
string? rid = GetRuntimeIdentifier();
if (dotnetUrl.TryGetValue(rid, out var url))
{
return url;
}
throw new PlatformNotSupportedException($"No download URL available for the current platform: {rid}");
}
public static string GetRuntimeIdentifier()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Environment.Is64BitProcess ? "win-x64" : "win-x86";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "linux-x64";
}
throw new PlatformNotSupportedException("Unsupported operating system");
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace Nebula.UpdateResolver;
public static class LogStandalone
{
public static Action<string, int>? OnLog;
public static void LogError(Exception e){
Log($"{e.GetType().Name}: "+ e.Message);
Log(e.StackTrace);
if(e.InnerException != null)
LogError(e.InnerException);
}
public static void Log(string? message, int percentage = 0)
{
if(message is null) return;
OnLog?.Invoke(message, percentage);
}
}

View File

@@ -23,6 +23,19 @@ public partial class MainWindow : Window
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
LogStandalone.OnLog += (message, percentage) =>
{
ProgressLabel.Content = message;
if (percentage == 0)
PercentLabel.Content = "";
else
PercentLabel.Content = percentage + "%";
var messageOut =
$"[{DateTime.Now.ToUniversalTime():yyyy-MM-dd HH:mm:ss}]: {message} {PercentLabel.Content}";
Console.WriteLine(messageOut);
LogStr += messageOut + "\n";
};
Start(); Start();
} }
@@ -31,11 +44,11 @@ public partial class MainWindow : Window
try try
{ {
var info = await EnsureFiles(); var info = await EnsureFiles();
Log("Downloading files..."); LogStandalone.Log("Downloading files...");
foreach (var file in info.ToDelete) foreach (var file in info.ToDelete)
{ {
Log("Deleting " + file.Path); LogStandalone.Log("Deleting " + file.Path);
FileApi.Remove(file.Path); FileApi.Remove(file.Path);
} }
@@ -55,30 +68,21 @@ public partial class MainWindow : Window
await using var stream = await response.Content.ReadAsStreamAsync(); await using var stream = await response.Content.ReadAsStreamAsync();
FileApi.Save(file.Path, stream); FileApi.Save(file.Path, stream);
resolved++; resolved++;
Log("Saving " + file.Path, (int)(resolved / (float)count * 100f)); LogStandalone.Log("Saving " + file.Path, (int)(resolved / (float)count * 100f));
loadedManifest.Add(file); loadedManifest.Add(file);
Save(loadedManifest); Save(loadedManifest);
} }
Log("Download finished. Running launcher..."); LogStandalone.Log("Download finished. Running launcher...");
var process = Process.Start(new ProcessStartInfo await DotnetStandalone.Run(Path.Join(FileApi.RootPath, "Nebula.Launcher.dll"));
{
FileName = "dotnet.exe",
Arguments = Path.Join(FileApi.RootPath, "Nebula.Launcher.dll"),
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8
});
} }
catch(HttpRequestException e){ catch(HttpRequestException e){
LogError(e); LogStandalone.LogError(e);
Log("Проблемы с интернет-соединением..."); LogStandalone.Log("Network connection error...");
var logPath = Path.Join(RootPath,"updateResloverError.txt"); var logPath = Path.Join(RootPath,"updateResloverError.txt");
File.WriteAllText(logPath, LogStr); await File.WriteAllTextAsync(logPath, LogStr);
Process.Start(new ProcessStartInfo(){ Process.Start(new ProcessStartInfo(){
FileName = "notepad", FileName = "notepad",
Arguments = logPath Arguments = logPath
@@ -86,9 +90,9 @@ public partial class MainWindow : Window
} }
catch (Exception e) catch (Exception e)
{ {
LogError(e); LogStandalone.LogError(e);
var logPath = Path.Join(RootPath,"updateResloverError.txt"); var logPath = Path.Join(RootPath,"updateResloverError.txt");
File.WriteAllText(logPath, LogStr); await File.WriteAllTextAsync(logPath, LogStr);
Process.Start(new ProcessStartInfo(){ Process.Start(new ProcessStartInfo(){
FileName = "notepad", FileName = "notepad",
Arguments = logPath Arguments = logPath
@@ -102,7 +106,7 @@ public partial class MainWindow : Window
private async Task<ManifestEnsureInfo> EnsureFiles() private async Task<ManifestEnsureInfo> EnsureFiles()
{ {
Log("Ensuring launcher manifest..."); LogStandalone.Log("Ensuring launcher manifest...");
var manifest = await RestStandalone.GetAsync<LauncherManifest>( var manifest = await RestStandalone.GetAsync<LauncherManifest>(
new Uri(ConfigurationStandalone.GetConfigValue(UpdateConVars.UpdateCacheUrl)! + "/manifest.json"), CancellationToken.None); new Uri(ConfigurationStandalone.GetConfigValue(UpdateConVars.UpdateCacheUrl)! + "/manifest.json"), CancellationToken.None);
@@ -110,10 +114,10 @@ public partial class MainWindow : Window
var toDelete = new HashSet<LauncherManifestEntry>(); var toDelete = new HashSet<LauncherManifestEntry>();
var filesExist = new HashSet<LauncherManifestEntry>(); var filesExist = new HashSet<LauncherManifestEntry>();
Log("Manifest loaded!"); LogStandalone.Log("Manifest loaded!");
if (ConfigurationStandalone.TryGetConfigValue(UpdateConVars.CurrentLauncherManifest, out var currentManifest)) if (ConfigurationStandalone.TryGetConfigValue(UpdateConVars.CurrentLauncherManifest, out var currentManifest))
{ {
Log("Delta manifest loaded!"); LogStandalone.Log("Delta manifest loaded!");
foreach (var file in currentManifest.Entries) foreach (var file in currentManifest.Entries)
{ {
if (!manifest.Entries.Contains(file)) if (!manifest.Entries.Contains(file))
@@ -133,31 +137,11 @@ public partial class MainWindow : Window
toDownload = manifest.Entries; toDownload = manifest.Entries;
} }
Log("Saving launcher manifest..."); LogStandalone.Log("Saving launcher manifest...");
return new ManifestEnsureInfo(toDownload, toDelete, filesExist); return new ManifestEnsureInfo(toDownload, toDelete, filesExist);
} }
private void LogError(Exception e){
Log($"{e.GetType().Name}: "+ e.Message);
Log(e.StackTrace);
if(e.InnerException != null)
LogError(e.InnerException);
}
private void Log(string? message, int percentage = 0)
{
if(message is null) return;
ProgressLabel.Content = message;
if (percentage == 0)
PercentLabel.Content = "";
else
PercentLabel.Content = percentage + "%";
var messageOut = $"[{DateTime.Now.ToUniversalTime():yyyy-MM-dd HH:mm:ss}]: {message} {PercentLabel.Content}";
Console.WriteLine(messageOut);
LogStr += messageOut + "\n";
}
private void Save(HashSet<LauncherManifestEntry> entries) private void Save(HashSet<LauncherManifestEntry> entries)
{ {

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Nebula.UpdateResolver.Configuration; using Nebula.UpdateResolver.Configuration;
namespace Nebula.UpdateResolver; namespace Nebula.UpdateResolver;
@@ -8,4 +9,11 @@ public static class UpdateConVars
ConVarBuilder.Build<string>("update.url","https://durenko.tatar/nebula/manifest/"); ConVarBuilder.Build<string>("update.url","https://durenko.tatar/nebula/manifest/");
public static readonly ConVar<LauncherManifest> CurrentLauncherManifest = public static readonly ConVar<LauncherManifest> CurrentLauncherManifest =
ConVarBuilder.Build<LauncherManifest>("update.manifest"); ConVarBuilder.Build<LauncherManifest>("update.manifest");
public static readonly ConVar<Dictionary<string,string>> DotnetUrl = ConVarBuilder.Build<Dictionary<string,string>>("dotnet.url",
new(){
{"win-x64", "https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-win-x64.zip"},
{"win-x86", "https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-win-x86.zip"},
{"linux-x64", "https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-linux-x64.tar.gz"}
});
} }