diff --git a/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs b/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs index 59700eb..49f3f14 100644 --- a/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs +++ b/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs @@ -6,7 +6,6 @@ using Nebula.Shared; using Nebula.Shared.Models; using Nebula.Shared.Services; using Nebula.Shared.Utils; -using Robust.LoaderApi; namespace Nebula.Launcher.ProcessHelper; @@ -22,8 +21,7 @@ public sealed class GameRunnerPreparer(IServiceProvider provider, ContentService if (engine is null) throw new Exception("Engine version not found: " + buildInfo.BuildInfo.Build.EngineVersion); - var hashApi = await contentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandlerFactory, cancellationToken); - + var hashApi = await contentService.EnsureItems(buildInfo, loadingHandlerFactory, cancellationToken); if (hashApi.TryOpen("manifest.yml", out var stream)) { diff --git a/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs b/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs index 7a1eda3..9629f24 100644 --- a/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs +++ b/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs @@ -57,7 +57,7 @@ public class ProcessRunHandler : IDisposable _processInfoTask.Wait(); } - _process = Process.Start(_processInfo!); + _process = Process.Start(_processInfo ?? throw new Exception("Process info is null, please try again.")); if (_process is null) return; diff --git a/Nebula.Launcher/Services/DecompilerService.cs b/Nebula.Launcher/Services/DecompilerService.cs index 5877b2b..f7a7989 100644 --- a/Nebula.Launcher/Services/DecompilerService.cs +++ b/Nebula.Launcher/Services/DecompilerService.cs @@ -61,13 +61,12 @@ public sealed partial class DecompilerService myTempDir.Save(file, stream); await stream.DisposeAsync(); } - - - var hashApi = await ContentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandler, cancellationToken); - foreach (var (file, hash) in hashApi.Manifest) + var hashApi = await ContentService.EnsureItems(buildInfo, loadingHandler, cancellationToken); + + foreach (var file in hashApi.AllFiles) { - if(!file.Contains(".dll") || !hashApi.TryOpen(hash, out var stream)) continue; + if(!file.Contains(".dll") || !hashApi.TryOpen(file, out var stream)) continue; myTempDir.Save(Path.GetFileName(file), stream); await stream.DisposeAsync(); } diff --git a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs index 53e66f3..0bc7274 100644 --- a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs @@ -15,11 +15,11 @@ using Nebula.Launcher.Utils; using Nebula.Launcher.ViewModels.Popup; using Nebula.Launcher.Views; using Nebula.Launcher.Views.Pages; -using Nebula.Shared.FileApis; using Nebula.Shared.Models; using Nebula.Shared.Services; using Nebula.Shared.Utils; using Nebula.Shared.ViewHelper; +using Robust.LoaderApi; namespace Nebula.Launcher.ViewModels.Pages; @@ -199,9 +199,9 @@ public sealed class ExtContentExecutor _decompilerService = decompilerService; } - public bool TryExecute(RobustManifestItem manifestItem, CancellationToken cancellationToken) + public bool TryExecute(IFileApi api, ContentPath path, CancellationToken cancellationToken) { - var ext = Path.GetExtension(manifestItem.Path); + var ext = Path.GetExtension(path.GetName()); if (ext == ".dll") { @@ -214,42 +214,39 @@ public sealed class ExtContentExecutor } -public sealed partial class ManifestContentEntry : IContentEntry +public sealed partial class FileContentEntry : IContentEntry { public IContentHolder Holder { get; set; } = default!; public IContentEntry? Parent { get; set; } public string? Name { get; set; } public string IconPath => "/Assets/svg/file.svg"; - private RobustManifestItem _manifestItem; - private HashApi _hashApi = default!; + private IFileApi _fileApi = default!; private ExtContentExecutor _extContentExecutor = default!; - public void Init(IContentHolder holder, RobustManifestItem manifestItem, HashApi api, ExtContentExecutor executor) + public void Init(IContentHolder holder, IFileApi api, string fileName, ExtContentExecutor executor) { Holder = holder; - Name = new ContentPath(manifestItem.Path).GetName(); - _manifestItem = manifestItem; - _hashApi = api; + Name = fileName; + _fileApi = api; _extContentExecutor = executor; } public IContentEntry? Go(ContentPath path, CancellationToken cancellationToken) { - if (_extContentExecutor.TryExecute(_manifestItem, cancellationToken)) + var fullPath = ((IContentEntry)this).FullPath; + if (_extContentExecutor.TryExecute(_fileApi, fullPath, cancellationToken)) return null; - var ext = Path.GetExtension(_manifestItem.Path); + var ext = Path.GetExtension(fullPath.GetName()); try { - if (!_hashApi.TryOpen(_manifestItem, out var stream)) + if (!_fileApi.TryOpen(fullPath.Path, out var stream)) return null; - var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext); - var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None); stream.CopyTo(sw); @@ -298,7 +295,7 @@ public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry public RobustUrl ServerUrl { get; private set; } - public HashApi FileApi { get; private set; } = default!; + public IFileApi FileApi { get; private set; } = default!; private ExtContentExecutor _contentExecutor = default!; @@ -315,12 +312,12 @@ public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry Task.Run(async () => { var buildInfo = await ContentService.GetBuildInfo(serverUrl, CancellationService.Token); - FileApi = await ContentService.EnsureItems(buildInfo.RobustManifestInfo, loading, + FileApi = await ContentService.EnsureItems(buildInfo, loading, CancellationService.Token); - foreach (var (path, item) in FileApi.Manifest) + foreach (var path in FileApi.AllFiles) { - CreateContent(new ContentPath(path), item); + CreateContent(new ContentPath(path)); } IsLoading = false; @@ -328,7 +325,7 @@ public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry }); } - public ManifestContentEntry CreateContent(ContentPath path, RobustManifestItem manifestItem) + public FileContentEntry CreateContent(ContentPath path) { var pathDir = path.GetDirectory(); BaseFolderContentEntry parent = this; @@ -345,8 +342,8 @@ public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry parent = folderContentEntry as BaseFolderContentEntry ?? throw new InvalidOperationException(); } - var manifestContent = new ManifestContentEntry(); - manifestContent.Init(Holder, manifestItem, FileApi, _contentExecutor); + var manifestContent = new FileContentEntry(); + manifestContent.Init(Holder, FileApi, path.GetName(), _contentExecutor); parent.AddChild(manifestContent); diff --git a/Nebula.Runner/Services/RunnerService.cs b/Nebula.Runner/Services/RunnerService.cs index cb9b2d9..e452e12 100644 --- a/Nebula.Runner/Services/RunnerService.cs +++ b/Nebula.Runner/Services/RunnerService.cs @@ -35,14 +35,14 @@ public sealed class RunnerService( if (engine is null) throw new Exception("Engine version not found: " + buildInfo.BuildInfo.Build.EngineVersion); - var hashApi = await contentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandler, cancellationToken); + var fileApi = await contentService.EnsureItems(buildInfo, loadingHandler, cancellationToken); var extraMounts = new List { - new(hashApi, "/") + new(fileApi, "/") }; - if (hashApi.TryOpen("manifest.yml", out var stream)) + if (fileApi.TryOpen("manifest.yml", out var stream)) { var modules = ContentManifestParser.ExtractModules(stream); diff --git a/Nebula.Shared/Models/RobustBuildInfo.cs b/Nebula.Shared/Models/RobustBuildInfo.cs index 90549ab..5d8f8e8 100644 --- a/Nebula.Shared/Models/RobustBuildInfo.cs +++ b/Nebula.Shared/Models/RobustBuildInfo.cs @@ -3,6 +3,7 @@ namespace Nebula.Shared.Models; public class RobustBuildInfo { public ServerInfo BuildInfo = default!; - public RobustManifestInfo RobustManifestInfo; + public RobustManifestInfo? RobustManifestInfo; + public RobustZipContentInfo? DownloadUri; public RobustUrl Url = default!; } \ No newline at end of file diff --git a/Nebula.Shared/Models/RobustManifestInfo.cs b/Nebula.Shared/Models/RobustManifestInfo.cs index 69ff327..e391843 100644 --- a/Nebula.Shared/Models/RobustManifestInfo.cs +++ b/Nebula.Shared/Models/RobustManifestInfo.cs @@ -1,3 +1,4 @@ namespace Nebula.Shared.Models; -public record struct RobustManifestInfo(Uri ManifestUri, Uri DownloadUri, string Hash); \ No newline at end of file +public record struct RobustManifestInfo(Uri ManifestUri, Uri DownloadUri, string Hash); +public record struct RobustZipContentInfo(Uri DownloadUri, string Hash); \ No newline at end of file diff --git a/Nebula.Shared/Models/RobustServerEntry.cs b/Nebula.Shared/Models/RobustServerEntry.cs index 4a307f1..08dd6ab 100644 --- a/Nebula.Shared/Models/RobustServerEntry.cs +++ b/Nebula.Shared/Models/RobustServerEntry.cs @@ -20,7 +20,7 @@ public sealed record BuildInfo( string ManifestDownloadUrl, [property: JsonPropertyName("manifest_url")] string ManifestUrl, - [property: JsonPropertyName("acz")] bool Acz, + [property: JsonPropertyName("acz")] bool? Acz, [property: JsonPropertyName("hash")] string Hash, [property: JsonPropertyName("manifest_hash")] string ManifestHash); diff --git a/Nebula.Shared/Services/ContentService.Download.cs b/Nebula.Shared/Services/ContentService.Download.cs index 53e9764..991cb44 100644 --- a/Nebula.Shared/Services/ContentService.Download.cs +++ b/Nebula.Shared/Services/ContentService.Download.cs @@ -1,12 +1,15 @@ using System.Buffers.Binary; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO.Compression; using System.Net.Http.Headers; using System.Numerics; using Nebula.Shared.FileApis; using Nebula.Shared.FileApis.Interfaces; using Nebula.Shared.Models; using Nebula.Shared.Utils; +using Robust.LoaderApi; namespace Nebula.Shared.Services; @@ -14,6 +17,7 @@ public partial class ContentService { public readonly IReadWriteFileApi ContentFileApi = fileService.CreateFileApi("content"); public readonly IReadWriteFileApi ManifestFileApi = fileService.CreateFileApi("manifest"); + public readonly IReadWriteFileApi ZipContentApi = fileService.CreateFileApi("zipContent"); public void SetServerHash(string address, string hash) { @@ -33,8 +37,20 @@ public partial class ContentService { return new HashApi(manifestItems, ContentFileApi); } + + public async Task EnsureItems(RobustBuildInfo info, ILoadingHandlerFactory loadingFactory, + CancellationToken cancellationToken) + { + if (info.RobustManifestInfo.HasValue) + return await EnsureItems(info.RobustManifestInfo.Value, loadingFactory, cancellationToken); + + if (info.DownloadUri.HasValue) + return await EnsureItems(info.DownloadUri.Value, loadingFactory, cancellationToken); + + throw new InvalidOperationException("DownloadUri is null"); + } - public async Task EnsureItems(ManifestReader manifestReader, Uri downloadUri, + private async Task EnsureItems(ManifestReader manifestReader, Uri downloadUri, ILoadingHandlerFactory loadingFactory, CancellationToken cancellationToken) { @@ -58,7 +74,41 @@ public partial class ContentService return hashApi; } - public async Task EnsureItems(RobustManifestInfo info, ILoadingHandlerFactory loadingFactory, + private async Task EnsureItems(RobustZipContentInfo info, ILoadingHandlerFactory loadingFactory, CancellationToken cancellationToken) + { + if (TryFromFile(ZipContentApi, info.Hash, out var zipFile)) + return zipFile; + + var loadingHandler = loadingFactory.CreateLoadingContext(new FileLoadingFormater()); + + var response = await _http.GetAsync(info.DownloadUri, cancellationToken); + response.EnsureSuccessStatusCode(); + + loadingHandler.SetLoadingMessage("Downloading zip content"); + loadingHandler.SetJobsCount(response.Content.Headers.ContentLength ?? 0); + await using var streamContent = await response.Content.ReadAsStreamAsync(cancellationToken); + ZipContentApi.Save(info.Hash, streamContent, loadingHandler); + loadingHandler.Dispose(); + + if (TryFromFile(ZipContentApi, info.Hash, out zipFile)) + return zipFile; + + ZipContentApi.Remove(info.Hash); + throw new Exception("Failed to load zip file"); + } + + private bool TryFromFile(IFileApi fileApi, string path, [NotNullWhen(true)] out ZipFileApi? zipFileApi) + { + zipFileApi = null; + if(!fileApi.TryOpen(path, out var zipContent)) + return false; + + var zip = new ZipArchive(zipContent); + zipFileApi = new ZipFileApi(zip, null); + return true; + } + + private async Task EnsureItems(RobustManifestInfo info, ILoadingHandlerFactory loadingFactory, CancellationToken cancellationToken) { _logger.Log("Getting manifest: " + info.Hash); @@ -90,10 +140,10 @@ public partial class ContentService return await EnsureItems(manifestReader, info.DownloadUri, loadingFactory, cancellationToken); } - public void Unpack(HashApi hashApi, IWriteFileApi otherApi, ILoadingHandler loadingHandler) + public void Unpack(IFileApi hashApi, IWriteFileApi otherApi, ILoadingHandler loadingHandler) { _logger.Log("Unpack manifest files"); - var items = hashApi.Manifest.Values.ToList(); + var items = hashApi.AllFiles.ToList(); loadingHandler.AppendJob(items.Count); var options = new ParallelOptions @@ -105,13 +155,13 @@ public partial class ContentService { if (hashApi.TryOpen(item, out var stream)) { - _logger.Log($"Unpack {item.Hash} to: {item.Path}"); - otherApi.Save(item.Path, stream); + _logger.Log($"Unpack {item}"); + otherApi.Save(item, stream); stream.Close(); } else { - _logger.Error("Error while unpacking thinks " + item.Path); + _logger.Error($"Error while unpacking thinks {item}"); } loadingHandler.AppendResolvedJob(); diff --git a/Nebula.Shared/Services/ContentService.cs b/Nebula.Shared/Services/ContentService.cs index 6f3332a..5e4369e 100644 --- a/Nebula.Shared/Services/ContentService.cs +++ b/Nebula.Shared/Services/ContentService.cs @@ -19,7 +19,14 @@ public partial class ContentService( info.Url = url; var bi = await restService.GetAsync(url.InfoUri, cancellationToken); info.BuildInfo = bi; - info.RobustManifestInfo = info.BuildInfo.Build.Acz + + if (info.BuildInfo.Build.Acz is null) + { + info.DownloadUri = new RobustZipContentInfo(new Uri(info.BuildInfo.Build.DownloadUrl), info.BuildInfo.Build.Hash); + return info; + } + + info.RobustManifestInfo = info.BuildInfo.Build.Acz.Value ? new RobustManifestInfo(new RobustPath(info.Url, "manifest.txt"), new RobustPath(info.Url, "download"), bi.Build.ManifestHash) : new RobustManifestInfo(new Uri(info.BuildInfo.Build.ManifestUrl),