diff --git a/Nebula.Launcher/Services/DecompilerService.cs b/Nebula.Launcher/Services/DecompilerService.cs index f7a7989..43902b5 100644 --- a/Nebula.Launcher/Services/DecompilerService.cs +++ b/Nebula.Launcher/Services/DecompilerService.cs @@ -15,6 +15,7 @@ using Nebula.Shared.FileApis.Interfaces; using Nebula.Shared.Models; using Nebula.Shared.Services; using Nebula.Shared.Services.Logging; +using Nebula.SharedModels; namespace Nebula.Launcher.Services; @@ -32,7 +33,7 @@ public sealed partial class DecompilerService private readonly HttpClient _httpClient = new(); private ILogger _logger; - private string FullPath => Path.Join(FileService.RootPath,$"ILSpy.{ConfigurationService.GetConfigValue(LauncherConVar.ILSpyVersion)}"); + private string FullPath => Path.Join(AppDataPath.RootPath, $"ILSpy.{ConfigurationService.GetConfigValue(LauncherConVar.ILSpyVersion)}"); private string ExecutePath => Path.Join(FullPath, "ILSpy.exe"); public async void OpenDecompiler(string arguments){ diff --git a/Nebula.Launcher/ViewModels/MainViewModel.cs b/Nebula.Launcher/ViewModels/MainViewModel.cs index 220b90a..2a290a5 100644 --- a/Nebula.Launcher/ViewModels/MainViewModel.cs +++ b/Nebula.Launcher/ViewModels/MainViewModel.cs @@ -14,6 +14,7 @@ using Nebula.Shared.Services; using Nebula.Shared.Services.Logging; using Nebula.Shared.Utils; using Nebula.Shared.ViewHelper; +using Nebula.SharedModels; namespace Nebula.Launcher.ViewModels; @@ -207,7 +208,7 @@ public partial class MainViewModel : ViewModelBase public void OpenRootPath() { - ExplorerUtils.OpenFolder(FileService.RootPath); + ExplorerUtils.OpenFolder(AppDataPath.RootPath); } public void OpenLink() diff --git a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs index 71d25f2..2674583 100644 --- a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs @@ -12,6 +12,7 @@ using Nebula.Shared; using Nebula.Shared.Configurations; using Nebula.Shared.Services; using Nebula.Shared.ViewHelper; +using Nebula.SharedModels; namespace Nebula.Launcher.ViewModels.Pages; @@ -70,12 +71,12 @@ public partial class ConfigurationViewModel : ViewModelBase public void OpenDataFolder() { - ExplorerUtils.OpenFolder(FileService.RootPath); + ExplorerUtils.OpenFolder(AppDataPath.RootPath); } public void ExportLogs() { - var logPath = Path.Join(FileService.RootPath, "log"); + var logPath = Path.Join(AppDataPath.RootPath, "log"); var path = Path.Combine(Path.GetTempPath(), "tempThink"+Path.GetRandomFileName()); Directory.CreateDirectory(path); diff --git a/Nebula.Runner/App.cs b/Nebula.Runner/App.cs index 0b0da77..ea5ac49 100644 --- a/Nebula.Runner/App.cs +++ b/Nebula.Runner/App.cs @@ -1,6 +1,5 @@ using Nebula.Runner.Services; using Nebula.Shared; -using Nebula.Shared.Models; using Nebula.Shared.Services; using Nebula.Shared.Services.Logging; using Nebula.Shared.Utils; @@ -16,6 +15,7 @@ public sealed class App(RunnerService runnerService, ContentService contentServi public void Redial(Uri uri, string text = "") { + throw new Exception($"Redial requested. Reason: {text}"); } public async Task Run(string[] args1) @@ -49,7 +49,7 @@ public sealed class App(RunnerService runnerService, ContentService contentServi args.Add("--ss14-address"); args.Add(url.ToString()); - await runnerService.Run(args.ToArray(), buildInfo, this, new ConsoleLoadingHandlerFactory(), cancelTokenSource.Token); + await runnerService.Run(args.ToArray(), buildInfo, this, new ConsoleLoadingHandlerFactory(), login, cancelTokenSource.Token); } catch (Exception e) { diff --git a/Nebula.Runner/Services/RunnerService.cs b/Nebula.Runner/Services/RunnerService.cs index e452e12..d107707 100644 --- a/Nebula.Runner/Services/RunnerService.cs +++ b/Nebula.Runner/Services/RunnerService.cs @@ -7,6 +7,7 @@ using Nebula.Shared.Models; using Nebula.Shared.Services; using Nebula.Shared.Services.Logging; using Nebula.Shared.Utils; +using Nebula.SharedModels; using Robust.LoaderApi; namespace Nebula.Runner.Services; @@ -25,8 +26,8 @@ public sealed class RunnerService( private bool MetricEnabled = false; //TODO: ADD METRIC THINKS LATER public async Task Run(string[] runArgs, RobustBuildInfo buildInfo, IRedialApi redialApi, - ILoadingHandlerFactory loadingHandler, - CancellationToken cancellationToken) + ILoadingHandlerFactory loadingHandler, string? userDataPath = null, + CancellationToken cancellationToken = default) { _logger.Log("Start Content!"); @@ -80,6 +81,12 @@ public sealed class RunnerService( metricServer = RunHelper.RunMetric(prometheusAssembly); } + if (userDataPath is not null) + { + UserDataDirPatcher.UserPath = userDataPath; + UserDataDirPatcher.ApplyPatch(reflectionService, harmonyService); + } + loadingHandler.Dispose(); await Task.Run(() => loader.Main(args), cancellationToken); @@ -112,6 +119,38 @@ public static class MetricsEnabledPatcher } } + +public static class UserDataDirPatcher +{ + public static string UserPath = "default"; + + public static void ApplyPatch(ReflectionService reflectionService, HarmonyService harmonyService) + { + var harmony = harmonyService.Instance.Harmony; + + var targetType = reflectionService.GetType("Robust.Client.Utility.UserDataDir"); + var targetMethod = targetType.GetMethod( + "GetRootUserDataDir", + BindingFlags.Static | BindingFlags.Public + ) ?? throw new Exception("target method is null"); + + var prefix = typeof(UserDataDirPatcher).GetMethod( + nameof(GetRootUserDataDirPrefix), + BindingFlags.Static | BindingFlags.NonPublic + ); + + var prefixMethod = new HarmonyMethod(prefix); + + harmony.Patch(targetMethod, prefix: prefixMethod); + } + + private static bool GetRootUserDataDirPrefix(ref string __result) + { + __result = Path.Join(AppDataPath.RootPath, "userData", UserPath); + return false; + } +} + public static class RunHelper { public static IDisposable RunMetric(Assembly prometheusAssembly) diff --git a/Nebula.Shared/Services/DebugService.cs b/Nebula.Shared/Services/DebugService.cs index 761e4f1..50b3c5e 100644 --- a/Nebula.Shared/Services/DebugService.cs +++ b/Nebula.Shared/Services/DebugService.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Reflection; using Nebula.Shared.Services.Logging; +using Nebula.SharedModels; namespace Nebula.Shared.Services; @@ -10,7 +11,7 @@ public class DebugService : IDisposable public static bool DoFileLog; private readonly string _path = - Path.Combine(FileService.RootPath, "log", Assembly.GetEntryAssembly()?.GetName().Name ?? "App"); + Path.Combine(AppDataPath.RootPath, "log", Assembly.GetEntryAssembly()?.GetName().Name ?? "App"); public DebugService() { diff --git a/Nebula.Shared/Services/DotnetResolverService.cs b/Nebula.Shared/Services/DotnetResolverService.cs index bfe81b3..669ea41 100644 --- a/Nebula.Shared/Services/DotnetResolverService.cs +++ b/Nebula.Shared/Services/DotnetResolverService.cs @@ -2,50 +2,54 @@ using System.IO.Compression; using System.Runtime.InteropServices; using System.Text; using Nebula.Shared.Utils; +using Nebula.SharedModels; namespace Nebula.Shared.Services; [ServiceRegister] public class DotnetResolverService(DebugService debugService, ConfigurationService configurationService) { - private string FullPath => - Path.Join(FileService.RootPath, $"dotnet.{configurationService.GetConfigValue(CurrentConVar.DotnetVersion)}", DotnetUrlHelper.GetRuntimeIdentifier()); - - private string ExecutePath => Path.Join(FullPath, "dotnet" + DotnetUrlHelper.GetExtension()); private readonly HttpClient _httpClient = new(); public async Task EnsureDotnet(CancellationToken cancellationToken = default) { - if (!Directory.Exists(FullPath)) - await Download(cancellationToken); + var dotnetEntry = new LauncherRuntimeInfo( + configurationService.GetConfigValue(CurrentConVar.DotnetVersion)!, + configurationService.GetConfigValue(CurrentConVar.DotnetUrl)! + ); + + if (!File.Exists(dotnetEntry.GetExecutePath())) + await Download(dotnetEntry, cancellationToken); - return ExecutePath; + return dotnetEntry.GetExecutePath(); } - private async Task Download(CancellationToken cancellationToken = default) + private async Task Download(LauncherRuntimeInfo runtimeInfo, CancellationToken cancellationToken = default) { var debugLogger = debugService.GetLogger(this); debugLogger.Log($"Downloading dotnet {DotnetUrlHelper.GetRuntimeIdentifier()}..."); - var url = DotnetUrlHelper.GetCurrentPlatformDotnetUrl( - configurationService.GetConfigValue(CurrentConVar.DotnetUrl)! - ); + var url = DotnetUrlHelper.GetCurrentPlatformDotnetUrl(runtimeInfo.DotnetRuntimes); + + var fullPath = runtimeInfo.GetFullPath(); + + UrlValidator.EnsureDomainValid(url, "microsoft.com"); using var response = await _httpClient.GetAsync(url, cancellationToken); response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); - Directory.CreateDirectory(FullPath); + Directory.CreateDirectory(fullPath); if (url.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { await using var zipArchive = new ZipArchive(stream); - await zipArchive.ExtractToDirectoryAsync(FullPath, true, cancellationToken); + await zipArchive.ExtractToDirectoryAsync(fullPath, true, cancellationToken); } else if (url.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase) || url.EndsWith(".tgz", StringComparison.OrdinalIgnoreCase)) { - TarUtils.ExtractTarGz(stream, FullPath); + TarUtils.ExtractTarGz(stream, fullPath); } else { @@ -55,36 +59,3 @@ public class DotnetResolverService(DebugService debugService, ConfigurationServi debugLogger.Log("Downloading dotnet complete."); } } - -public static class DotnetUrlHelper -{ - [Obsolete("FOR TEST USING ONLY!")] - public static string? RidOverrideTest = null; // FOR TEST PURPOSES ONLY!!! - - public static string GetExtension() - { - if (OperatingSystem.IsWindows()) return ".exe"; - return ""; - } - - public static string GetCurrentPlatformDotnetUrl(Dictionary dotnetUrl) - { - var 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(RidOverrideTest != null) return RidOverrideTest; - - 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.Shared/Services/FileService.cs b/Nebula.Shared/Services/FileService.cs index a58ace9..a4f3b09 100644 --- a/Nebula.Shared/Services/FileService.cs +++ b/Nebula.Shared/Services/FileService.cs @@ -1,8 +1,10 @@ using System.IO.Compression; +using System.Runtime.InteropServices; using Nebula.Shared.FileApis; using Nebula.Shared.FileApis.Interfaces; using Nebula.Shared.Models; using Nebula.Shared.Services.Logging; +using Nebula.SharedModels; using Robust.LoaderApi; namespace Nebula.Shared.Services; @@ -10,23 +12,20 @@ namespace Nebula.Shared.Services; [ServiceRegister] public class FileService { - public static string RootPath = Path.Join(Environment.GetFolderPath( - Environment.SpecialFolder.ApplicationData), "Datum"); - private readonly ILogger _logger; public FileService(DebugService debugService) { _logger = debugService.GetLogger(this); - if(!Directory.Exists(RootPath)) - Directory.CreateDirectory(RootPath); + if(!Directory.Exists(AppDataPath.RootPath)) + Directory.CreateDirectory(AppDataPath.RootPath); } public IReadWriteFileApi CreateFileApi(string path) { _logger.Debug($"Creating file api for {path}"); - return new FileApi(Path.Join(RootPath, path)); + return new FileApi(Path.Join(AppDataPath.RootPath, path)); } public IReadWriteFileApi EnsureTempDir(out string path) @@ -59,7 +58,7 @@ public class FileService public void RemoveAllFiles(string fileApiName,ILoadingHandler loadingHandler, CancellationToken cancellationToken) { _logger.Debug($"Deleting files from {fileApiName}"); - var path = Path.Combine(RootPath, fileApiName); + var path = Path.Combine(AppDataPath.RootPath, fileApiName); var di = new DirectoryInfo(path); @@ -89,6 +88,7 @@ public class FileService } } + public sealed class ConsoleLoadingHandlerFactory : ILoadingHandlerFactory { public ILoadingHandler CreateLoadingContext(ILoadingFormater? loadingFormater = null) diff --git a/Nebula.SharedModels/AppDataHelper.cs b/Nebula.SharedModels/AppDataHelper.cs new file mode 100644 index 0000000..a99feec --- /dev/null +++ b/Nebula.SharedModels/AppDataHelper.cs @@ -0,0 +1,124 @@ +using System.Runtime.InteropServices; + +namespace Nebula.SharedModels; + +public static class AppDataPath +{ + public static string RootPath { get; private set; } = GetAppDataPath("Datum"); + + public static void SetTestRootPath(string rootPath) + { + Console.WriteLine($"REWRITE ROOT PATH TO {rootPath}"); + RootPath = rootPath; + } + + public static string GetAppDataPath(string appName) + { + if (string.IsNullOrWhiteSpace(appName)) + throw new ArgumentException("appName cannot be null or empty.", nameof(appName)); + + string basePath; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + basePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + basePath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Personal), + "Library", + "Application Support" + ); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + basePath = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME") + ?? Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.Personal), + ".config" + ); + } + else + { + throw new PlatformNotSupportedException("Unsupported operating system."); + } + + return Path.Combine(basePath, appName); + } +} + +public static class UrlValidator +{ + public static bool IsInDomainUrl(string url, string allowedDomain) + { + if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) + return false; + + if (uri.Scheme != Uri.UriSchemeHttps) + return false; + + var host = uri.Host.ToLowerInvariant(); + return host == allowedDomain || host.EndsWith("." + allowedDomain); + } + + public static void EnsureDomainValid(string url, string allowedDomain) + { + if(!IsInDomainUrl(url, allowedDomain)) + throw new InvalidOperationException($"URL {url} is not in domain {allowedDomain}."); + } +} + +public static class DotnetUrlHelper +{ + public static string GetExtension() + { + return OperatingSystem.IsWindows() ? ".exe" : string.Empty; + } + public static string GetCurrentPlatformDotnetUrl(Dictionary dotnetUrl) + { + var 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 RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "win-x64", + Architecture.X86 => "win-x86", + Architecture.Arm64 => "win-arm64", + _ => throw new PlatformNotSupportedException($"Unsupported Windows architecture: {RuntimeInformation.ProcessArchitecture}") + }; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "linux-x64", + Architecture.X86 => "linux-x86", + Architecture.Arm => "linux-arm", + Architecture.Arm64 => "linux-arm64", + _ => throw new PlatformNotSupportedException($"Unsupported Linux architecture: {RuntimeInformation.ProcessArchitecture}") + }; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "osx-x64", + Architecture.Arm64 => "osx-arm64", + _ => throw new PlatformNotSupportedException($"Unsupported macOS architecture: {RuntimeInformation.ProcessArchitecture}") + }; + } + + throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); + } +} \ No newline at end of file diff --git a/Nebula.SharedModels/LauncherRuntimeInfo.cs b/Nebula.SharedModels/LauncherRuntimeInfo.cs index 8397a40..f5e34ac 100644 --- a/Nebula.SharedModels/LauncherRuntimeInfo.cs +++ b/Nebula.SharedModels/LauncherRuntimeInfo.cs @@ -4,4 +4,20 @@ namespace Nebula.SharedModels; public record struct LauncherRuntimeInfo( [property: JsonPropertyName("version")] string RuntimeVersion, - [property: JsonPropertyName("runtimes")] Dictionary DotnetRuntimes); \ No newline at end of file + [property: JsonPropertyName("runtimes")] Dictionary DotnetRuntimes); + +public static class LauncherManifestEntryHelper +{ + public static string GetFullPath(this LauncherRuntimeInfo runtimeInfo) + { + return Path.Join(AppDataPath.RootPath, + $"dotnet.{runtimeInfo.RuntimeVersion}", + DotnetUrlHelper.GetRuntimeIdentifier()); + } + + public static string GetExecutePath(this LauncherRuntimeInfo runtimeInfo ) + { + return Path.Join(GetFullPath(runtimeInfo), + $"dotnet{DotnetUrlHelper.GetExtension()}"); + } +} \ No newline at end of file diff --git a/Nebula.UnitTest/NebulaSharedTests/FileServiceTests.cs b/Nebula.UnitTest/NebulaSharedTests/FileServiceTests.cs index 7dc0ee9..afacdab 100644 --- a/Nebula.UnitTest/NebulaSharedTests/FileServiceTests.cs +++ b/Nebula.UnitTest/NebulaSharedTests/FileServiceTests.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using Nebula.Shared.Models; using Nebula.Shared.Services; +using Nebula.SharedModels; using Robust.LoaderApi; namespace Nebula.UnitTest.NebulaSharedTests; @@ -36,7 +37,7 @@ public class FileServiceTests : BaseSharedTest fileApi.Save("test.txt", stream); } - var expectedPath = Path.Combine(FileService.RootPath, subPath); + var expectedPath = Path.Combine(AppDataPath.RootPath, subPath); Assert.That(Directory.Exists(expectedPath), Is.True, $"Expected path to be created: {expectedPath}"); } @@ -53,7 +54,7 @@ public class FileServiceTests : BaseSharedTest [Test] public void OpenZip_ReturnsZipFileApi_WhenValid() { - var testZipPath = Path.Combine(FileService.RootPath, "test.zip"); + var testZipPath = Path.Combine(AppDataPath.RootPath, "test.zip"); using (var archive = ZipFile.Open(testZipPath, ZipArchiveMode.Create)) { var entry = archive.CreateEntry("test.txt"); @@ -93,7 +94,7 @@ public class FileServiceTests : BaseSharedTest [Test] public void RemoveAllFiles_DeletesAllFilesAndDirectories() { - var testDir = Path.Combine(FileService.RootPath, "cleanup-test"); + var testDir = Path.Combine(AppDataPath.RootPath, "cleanup-test"); Directory.CreateDirectory(testDir); File.WriteAllText(Path.Combine(testDir, "test1.txt"), "data"); diff --git a/Nebula.UnitTest/NebulaSharedTests/TarTest.cs b/Nebula.UnitTest/NebulaSharedTests/TarTest.cs index c134980..c3d1ab1 100644 --- a/Nebula.UnitTest/NebulaSharedTests/TarTest.cs +++ b/Nebula.UnitTest/NebulaSharedTests/TarTest.cs @@ -4,6 +4,7 @@ using Nebula.Shared; using Nebula.Shared.Services; using Nebula.Shared.Services.Logging; using Nebula.Shared.Utils; +using Nebula.SharedModels; namespace Nebula.UnitTest.NebulaSharedTests; @@ -31,28 +32,26 @@ public class TarTest : BaseSharedTest [Test] public async Task DownloadTarAndUnzipTest() { - DotnetUrlHelper.RidOverrideTest = "linux-x64"; - Console.WriteLine($"Downloading dotnet {DotnetUrlHelper.GetRuntimeIdentifier()}..."); - - var url = DotnetUrlHelper.GetCurrentPlatformDotnetUrl( - _configurationService.GetConfigValue(CurrentConVar.DotnetUrl)! - ); + Console.WriteLine($"Downloading dotnet linux-x64..."); + + if(!_configurationService.GetConfigValue(CurrentConVar.DotnetUrl)!.TryGetValue("linux-x64", out var url)) + throw new NullReferenceException(); using var response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(); - Directory.CreateDirectory(FileService.RootPath); + Directory.CreateDirectory(AppDataPath.RootPath); if (url.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { using var zipArchive = new ZipArchive(stream); - zipArchive.ExtractToDirectory(FileService.RootPath, true); + await zipArchive.ExtractToDirectoryAsync(AppDataPath.RootPath, true); } else if (url.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase) || url.EndsWith(".tgz", StringComparison.OrdinalIgnoreCase)) { - TarUtils.ExtractTarGz(stream, FileService.RootPath); + TarUtils.ExtractTarGz(stream, AppDataPath.RootPath); } else { diff --git a/Nebula.UnitTest/TestServiceHelper.cs b/Nebula.UnitTest/TestServiceHelper.cs index f864f8f..dc597ef 100644 --- a/Nebula.UnitTest/TestServiceHelper.cs +++ b/Nebula.UnitTest/TestServiceHelper.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Nebula.Shared; -using Nebula.Shared.Services; +using Nebula.SharedModels; namespace Nebula.UnitTest; @@ -20,19 +20,10 @@ public static class TestServiceHelper { var path = Path.Combine(Path.GetTempPath(), "tempThink"+Path.GetRandomFileName()); Directory.CreateDirectory(path); - FileService.RootPath = path; - Console.WriteLine("Change root path for file api: " + FileService.RootPath); + AppDataPath.SetTestRootPath(path); } } -public sealed class LauncherUnit : SharedUnit -{ - public LauncherUnit(IServiceProvider serviceProvider) : base(serviceProvider) - { - } -} - - public class SharedUnit { public SharedUnit(IServiceProvider serviceProvider) diff --git a/Nebula.UpdateResolver/DotnetStandalone.cs b/Nebula.UpdateResolver/DotnetStandalone.cs index 6eccadb..f4307c2 100644 --- a/Nebula.UpdateResolver/DotnetStandalone.cs +++ b/Nebula.UpdateResolver/DotnetStandalone.cs @@ -16,21 +16,13 @@ public static class DotnetStandalone { private static readonly HttpClient HttpClient = new(); - private static string GetExecutePath(LauncherRuntimeInfo runtimeInfo) - { - return Path.Join(MainWindow.RootPath, - $"dotnet.{runtimeInfo.RuntimeVersion}", - DotnetUrlHelper.GetRuntimeIdentifier(), - $"dotnet{DotnetUrlHelper.GetExtension()}"); - } - public static async Task Run(LauncherRuntimeInfo runtimeInfo, string dllPath) { await EnsureDotnet(runtimeInfo); return Process.Start(new ProcessStartInfo { - FileName = GetExecutePath(runtimeInfo), + FileName = runtimeInfo.GetExecutePath(), Arguments = dllPath, CreateNoWindow = true, UseShellExecute = false, @@ -42,7 +34,7 @@ public static class DotnetStandalone private static async Task EnsureDotnet(LauncherRuntimeInfo runtimeInfo) { - if (!File.Exists(GetExecutePath(runtimeInfo))) + if (!File.Exists(runtimeInfo.GetExecutePath())) await Download(runtimeInfo); } @@ -50,9 +42,11 @@ public static class DotnetStandalone { LogStandalone.Log($"Downloading dotnet {DotnetUrlHelper.GetRuntimeIdentifier()}..."); - var fullPath = GetExecutePath(runtimeInfo); + var fullPath = runtimeInfo.GetFullPath(); var url = DotnetUrlHelper.GetCurrentPlatformDotnetUrl(runtimeInfo.DotnetRuntimes); + + UrlValidator.EnsureDomainValid(url, "microsoft.com"); using var response = await HttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); @@ -80,58 +74,4 @@ public static class DotnetStandalone LogStandalone.Log("Downloading dotnet complete."); } -} - -public static class DotnetUrlHelper -{ - public static string GetExtension() - { - return OperatingSystem.IsWindows() ? ".exe" : string.Empty; - } - public static string GetCurrentPlatformDotnetUrl(Dictionary dotnetUrl) - { - var 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 RuntimeInformation.ProcessArchitecture switch - { - Architecture.X64 => "win-x64", - Architecture.X86 => "win-x86", - Architecture.Arm64 => "win-arm64", - _ => throw new PlatformNotSupportedException($"Unsupported Windows architecture: {RuntimeInformation.ProcessArchitecture}") - }; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return RuntimeInformation.ProcessArchitecture switch - { - Architecture.X64 => "linux-x64", - Architecture.X86 => "linux-x86", - Architecture.Arm => "linux-arm", - Architecture.Arm64 => "linux-arm64", - _ => throw new PlatformNotSupportedException($"Unsupported Linux architecture: {RuntimeInformation.ProcessArchitecture}") - }; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return RuntimeInformation.ProcessArchitecture switch - { - Architecture.X64 => "osx-x64", - Architecture.Arm64 => "osx-arm64", - _ => throw new PlatformNotSupportedException($"Unsupported macOS architecture: {RuntimeInformation.ProcessArchitecture}") - }; - } - - throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); - } } \ No newline at end of file diff --git a/Nebula.UpdateResolver/MainWindow.axaml.cs b/Nebula.UpdateResolver/MainWindow.axaml.cs index d8fdd18..54dcc00 100644 --- a/Nebula.UpdateResolver/MainWindow.axaml.cs +++ b/Nebula.UpdateResolver/MainWindow.axaml.cs @@ -15,11 +15,10 @@ namespace Nebula.UpdateResolver; public partial class MainWindow : Window { - public static readonly string RootPath = Path.Join(Environment.GetFolderPath( - Environment.SpecialFolder.ApplicationData), "Datum"); + public static readonly string RootPath = AppDataPath.GetAppDataPath("Datum"); - private readonly HttpClient _httpClient = new HttpClient(); - public readonly FileApi FileApi = new FileApi(Path.Join(RootPath,"app")); + private readonly HttpClient _httpClient = new(); + private readonly FileApi _fileApi = new(Path.Join(RootPath, "app")); private string _logStr = ""; public MainWindow() @@ -64,7 +63,7 @@ public partial class MainWindow : Window foreach (var file in info.ToDelete) { LogStandalone.Log("Deleting " + file.Path); - FileApi.Remove(file.Path); + _fileApi.Remove(file.Path); } var loadedManifest = info.FilesExist; @@ -81,7 +80,7 @@ public partial class MainWindow : Window response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(); - FileApi.Save(file.Path, stream); + _fileApi.Save(file.Path, stream); resolved++; LogStandalone.Log("Saving " + file.Path, (int)(resolved / (float)count * 100f)); @@ -91,7 +90,7 @@ public partial class MainWindow : Window LogStandalone.Log("Download finished. Running launcher..."); - await DotnetStandalone.Run(manifest.RuntimeInfo, Path.Join(FileApi.RootPath, "Nebula.Launcher.dll")); + await DotnetStandalone.Run(manifest.RuntimeInfo, Path.Join(_fileApi.RootPath, "Nebula.Launcher.dll")); } catch(HttpRequestException e){ LogStandalone.LogError(e); @@ -182,14 +181,13 @@ public partial class MainWindow : Window private LauncherManifestEntry EnsurePath(LauncherManifestEntry entry) { - if(!PathValidator.IsSafePath(FileApi.RootPath, entry.Path)) + if(!PathValidator.IsSafePath(_fileApi.RootPath, entry.Path)) throw new ArgumentException("Path contains invalid characters. Manifest hash: " + entry.Hash); return entry; } } - public static class PathValidator { public static bool IsSafePath(string baseDirectory, string relativePath) diff --git a/Nebula.sln.DotSettings.user b/Nebula.sln.DotSettings.user index bc97dff..bff5f4e 100644 --- a/Nebula.sln.DotSettings.user +++ b/Nebula.sln.DotSettings.user @@ -101,23 +101,12 @@ </AssemblyExplorer> 735691F8-949C-4476-B9E4-5DF6FF8D3D0B db4927dd-2e12-48a7-9a84-2b7e3e31b9c8 - <SessionState ContinuousTestingMode="0" IsActive="True" Name="RestServiceTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="CryptographicTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.RestServiceTests</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.PopupMessageServiceTests.PopupMessageServiceTest</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.PopupMessageServiceTests.DisposeTest</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.FileServiceTests.CreateFileApi_CreatesCorrectPath</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.FileServiceTests.EnsureTempDir_CreatesDirectoryAndReturnsApi</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.FileServiceTests.OpenZip_ReturnsZipFileApi_WhenValid</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.FileServiceTests.RemoveAllFiles_DeletesAllFilesAndDirectories</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.FileServiceTests.OpenZip_ThrowsException_WhenFileApiFails</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.EngineServiceTests.GetVersionInfo_ReturnsCorrectBuildInfo</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.EngineServiceTests.TryGetVersionInfo_ReturnsTrue</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.ConfigurationServiceTests.WriteConVarTest</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.ConfigurationServiceTests.WriteArrayConvarTest</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.ConfigurationServiceTests</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.CryptographicTest.EncryptDecrypt</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.CryptographicTest</TestId> - <TestId>NUnit3x::735691F8-949C-4476-B9E4-5DF6FF8D3D0B::net9.0::Nebula.UnitTest.NebulaSharedTests.TarTest.Download</TestId> + <TestId>NUnit3x::7430875B-ABAA-D4E0-C34F-0797C4762C66::net10.0::Nebula.UnitTest.NebulaSharedTests.CryptographicTest</TestId> </TestAncestor> -</SessionState> \ No newline at end of file +</SessionState> + + + + \ No newline at end of file