diff --git a/Nebula.Launcher/LauncherConVar.cs b/Nebula.Launcher/LauncherConVar.cs index 756ad56..b056051 100644 --- a/Nebula.Launcher/LauncherConVar.cs +++ b/Nebula.Launcher/LauncherConVar.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Nebula.Launcher.Models; using Nebula.Launcher.ViewModels.Pages; using Nebula.Shared.Services; @@ -32,4 +33,6 @@ public static class LauncherConVar public static readonly ConVar CurrentLang = ConVarBuilder.Build("launcher.language", "en-US"); public static readonly ConVar ILSpyUrl = ConVarBuilder.Build("decompiler.url", "https://github.com/icsharpcode/ILSpy/releases/download/v9.0/ILSpy_binaries_9.0.0.7889-x64.zip"); + + } \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs index cbbbc1a..473fbb9 100644 --- a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs @@ -39,6 +39,11 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol public void OnBackEnter() { + if (CurrentEntry.Parent is null) + { + SetHubRoot(); + return; + } CurrentEntry.Parent?.GoCurrent(); } @@ -116,6 +121,8 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol public void SetHubRoot() { + ServerText = string.Empty; + SearchText = string.Empty; var root = ViewHelperService.GetViewModel(); root.InitHubList(this); CurrentEntry = root; diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs index d0c5328..061a26b 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs @@ -55,6 +55,7 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer [GenerateProperty] private RestService RestService { get; } = default!; [GenerateProperty] private MainViewModel MainViewModel { get; } = default!; [GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } = default!; + [GenerateProperty] private DotnetResolverService DotnetResolverService { get; } = default!; public ServerStatus Status { get; private set; } = new( @@ -210,10 +211,10 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer await RunnerService.PrepareRun(buildInfo, loadingContext, CancellationService.Token); var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); - + Process = Process.Start(new ProcessStartInfo { - FileName = "dotnet.exe", + FileName = await DotnetResolverService.EnsureDotnet(), Arguments = Path.Join(path, "Nebula.Runner.dll"), Environment = { diff --git a/Nebula.Shared/CurrentConVar.cs b/Nebula.Shared/CurrentConVar.cs index 853fe38..7287877 100644 --- a/Nebula.Shared/CurrentConVar.cs +++ b/Nebula.Shared/CurrentConVar.cs @@ -28,4 +28,11 @@ public static class CurrentConVar ConVarBuilder.Build>("engine.manifest.backup"); public static readonly ConVar ModuleManifestBackup = ConVarBuilder.Build("module.manifest.backup"); + + public static readonly ConVar> DotnetUrl = ConVarBuilder.Build>("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"} + }); } \ No newline at end of file diff --git a/Nebula.Shared/Services/DotnetResolverService.cs b/Nebula.Shared/Services/DotnetResolverService.cs new file mode 100644 index 0000000..700d44c --- /dev/null +++ b/Nebula.Shared/Services/DotnetResolverService.cs @@ -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 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 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"); + } +} \ No newline at end of file diff --git a/Nebula.UpdateResolver/DotnetStandalone.cs b/Nebula.UpdateResolver/DotnetStandalone.cs new file mode 100644 index 0000000..8510c3d --- /dev/null +++ b/Nebula.UpdateResolver/DotnetStandalone.cs @@ -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 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 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"); + } +} \ No newline at end of file diff --git a/Nebula.UpdateResolver/LogStandalone.cs b/Nebula.UpdateResolver/LogStandalone.cs new file mode 100644 index 0000000..d9c905c --- /dev/null +++ b/Nebula.UpdateResolver/LogStandalone.cs @@ -0,0 +1,21 @@ +using System; + +namespace Nebula.UpdateResolver; + +public static class LogStandalone +{ + public static Action? 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); + } +} \ No newline at end of file diff --git a/Nebula.UpdateResolver/MainWindow.axaml.cs b/Nebula.UpdateResolver/MainWindow.axaml.cs index 11d74db..768539a 100644 --- a/Nebula.UpdateResolver/MainWindow.axaml.cs +++ b/Nebula.UpdateResolver/MainWindow.axaml.cs @@ -23,6 +23,19 @@ public partial class MainWindow : Window public MainWindow() { 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(); } @@ -31,11 +44,11 @@ public partial class MainWindow : Window try { var info = await EnsureFiles(); - Log("Downloading files..."); + LogStandalone.Log("Downloading files..."); foreach (var file in info.ToDelete) { - Log("Deleting " + file.Path); + LogStandalone.Log("Deleting " + file.Path); FileApi.Remove(file.Path); } @@ -55,30 +68,21 @@ public partial class MainWindow : Window await using var stream = await response.Content.ReadAsStreamAsync(); FileApi.Save(file.Path, stream); resolved++; - Log("Saving " + file.Path, (int)(resolved / (float)count * 100f)); + LogStandalone.Log("Saving " + file.Path, (int)(resolved / (float)count * 100f)); loadedManifest.Add(file); Save(loadedManifest); } - Log("Download finished. Running launcher..."); + LogStandalone.Log("Download finished. Running launcher..."); - var process = Process.Start(new ProcessStartInfo - { - FileName = "dotnet.exe", - Arguments = Path.Join(FileApi.RootPath, "Nebula.Launcher.dll"), - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - StandardOutputEncoding = Encoding.UTF8 - }); + await DotnetStandalone.Run(Path.Join(FileApi.RootPath, "Nebula.Launcher.dll")); } catch(HttpRequestException e){ - LogError(e); - Log("Проблемы с интернет-соединением..."); + LogStandalone.LogError(e); + LogStandalone.Log("Network connection error..."); var logPath = Path.Join(RootPath,"updateResloverError.txt"); - File.WriteAllText(logPath, LogStr); + await File.WriteAllTextAsync(logPath, LogStr); Process.Start(new ProcessStartInfo(){ FileName = "notepad", Arguments = logPath @@ -86,9 +90,9 @@ public partial class MainWindow : Window } catch (Exception e) { - LogError(e); + LogStandalone.LogError(e); var logPath = Path.Join(RootPath,"updateResloverError.txt"); - File.WriteAllText(logPath, LogStr); + await File.WriteAllTextAsync(logPath, LogStr); Process.Start(new ProcessStartInfo(){ FileName = "notepad", Arguments = logPath @@ -102,7 +106,7 @@ public partial class MainWindow : Window private async Task EnsureFiles() { - Log("Ensuring launcher manifest..."); + LogStandalone.Log("Ensuring launcher manifest..."); var manifest = await RestStandalone.GetAsync( new Uri(ConfigurationStandalone.GetConfigValue(UpdateConVars.UpdateCacheUrl)! + "/manifest.json"), CancellationToken.None); @@ -110,10 +114,10 @@ public partial class MainWindow : Window var toDelete = new HashSet(); var filesExist = new HashSet(); - Log("Manifest loaded!"); + LogStandalone.Log("Manifest loaded!"); if (ConfigurationStandalone.TryGetConfigValue(UpdateConVars.CurrentLauncherManifest, out var currentManifest)) { - Log("Delta manifest loaded!"); + LogStandalone.Log("Delta manifest loaded!"); foreach (var file in currentManifest.Entries) { if (!manifest.Entries.Contains(file)) @@ -133,31 +137,11 @@ public partial class MainWindow : Window toDownload = manifest.Entries; } - Log("Saving launcher manifest..."); + LogStandalone.Log("Saving launcher manifest..."); 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 entries) { diff --git a/Nebula.UpdateResolver/UpdateCVars.cs b/Nebula.UpdateResolver/UpdateCVars.cs index 9ba415b..594ebb9 100644 --- a/Nebula.UpdateResolver/UpdateCVars.cs +++ b/Nebula.UpdateResolver/UpdateCVars.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Nebula.UpdateResolver.Configuration; namespace Nebula.UpdateResolver; @@ -8,4 +9,11 @@ public static class UpdateConVars ConVarBuilder.Build("update.url","https://durenko.tatar/nebula/manifest/"); public static readonly ConVar CurrentLauncherManifest = ConVarBuilder.Build("update.manifest"); + + public static readonly ConVar> DotnetUrl = ConVarBuilder.Build>("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"} + }); } \ No newline at end of file