Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff31412719 |
@@ -6,7 +6,6 @@ using Nebula.Shared;
|
|||||||
using Nebula.Shared.Models;
|
using Nebula.Shared.Models;
|
||||||
using Nebula.Shared.Services;
|
using Nebula.Shared.Services;
|
||||||
using Nebula.Shared.Utils;
|
using Nebula.Shared.Utils;
|
||||||
using Robust.LoaderApi;
|
|
||||||
|
|
||||||
namespace Nebula.Launcher.ProcessHelper;
|
namespace Nebula.Launcher.ProcessHelper;
|
||||||
|
|
||||||
@@ -22,8 +21,7 @@ public sealed class GameRunnerPreparer(IServiceProvider provider, ContentService
|
|||||||
if (engine is null)
|
if (engine is null)
|
||||||
throw new Exception("Engine version not found: " + buildInfo.BuildInfo.Build.EngineVersion);
|
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))
|
if (hashApi.TryOpen("manifest.yml", out var stream))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class ProcessRunHandler : IDisposable
|
|||||||
_processInfoTask.Wait();
|
_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;
|
if (_process is null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -62,12 +62,11 @@ public sealed partial class DecompilerService
|
|||||||
await stream.DisposeAsync();
|
await stream.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hashApi = await ContentService.EnsureItems(buildInfo, loadingHandler, cancellationToken);
|
||||||
|
|
||||||
var hashApi = await ContentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandler, cancellationToken);
|
foreach (var file in hashApi.AllFiles)
|
||||||
|
|
||||||
foreach (var (file, hash) in hashApi.Manifest)
|
|
||||||
{
|
{
|
||||||
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);
|
myTempDir.Save(Path.GetFileName(file), stream);
|
||||||
await stream.DisposeAsync();
|
await stream.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ using Nebula.Launcher.Utils;
|
|||||||
using Nebula.Launcher.ViewModels.Popup;
|
using Nebula.Launcher.ViewModels.Popup;
|
||||||
using Nebula.Launcher.Views;
|
using Nebula.Launcher.Views;
|
||||||
using Nebula.Launcher.Views.Pages;
|
using Nebula.Launcher.Views.Pages;
|
||||||
using Nebula.Shared.FileApis;
|
|
||||||
using Nebula.Shared.Models;
|
using Nebula.Shared.Models;
|
||||||
using Nebula.Shared.Services;
|
using Nebula.Shared.Services;
|
||||||
using Nebula.Shared.Utils;
|
using Nebula.Shared.Utils;
|
||||||
using Nebula.Shared.ViewHelper;
|
using Nebula.Shared.ViewHelper;
|
||||||
|
using Robust.LoaderApi;
|
||||||
|
|
||||||
namespace Nebula.Launcher.ViewModels.Pages;
|
namespace Nebula.Launcher.ViewModels.Pages;
|
||||||
|
|
||||||
@@ -199,9 +199,9 @@ public sealed class ExtContentExecutor
|
|||||||
_decompilerService = decompilerService;
|
_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")
|
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 IContentHolder Holder { get; set; } = default!;
|
||||||
public IContentEntry? Parent { get; set; }
|
public IContentEntry? Parent { get; set; }
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public string IconPath => "/Assets/svg/file.svg";
|
public string IconPath => "/Assets/svg/file.svg";
|
||||||
|
|
||||||
private RobustManifestItem _manifestItem;
|
private IFileApi _fileApi = default!;
|
||||||
private HashApi _hashApi = default!;
|
|
||||||
private ExtContentExecutor _extContentExecutor = 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;
|
Holder = holder;
|
||||||
Name = new ContentPath(manifestItem.Path).GetName();
|
Name = fileName;
|
||||||
_manifestItem = manifestItem;
|
_fileApi = api;
|
||||||
_hashApi = api;
|
|
||||||
_extContentExecutor = executor;
|
_extContentExecutor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IContentEntry? Go(ContentPath path, CancellationToken cancellationToken)
|
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;
|
return null;
|
||||||
|
|
||||||
var ext = Path.GetExtension(_manifestItem.Path);
|
var ext = Path.GetExtension(fullPath.GetName());
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_hashApi.TryOpen(_manifestItem, out var stream))
|
if (!_fileApi.TryOpen(fullPath.Path, out var stream))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
||||||
var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext);
|
var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext);
|
||||||
|
|
||||||
|
|
||||||
var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None);
|
var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||||
stream.CopyTo(sw);
|
stream.CopyTo(sw);
|
||||||
|
|
||||||
@@ -298,7 +295,7 @@ public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry
|
|||||||
|
|
||||||
public RobustUrl ServerUrl { get; private set; }
|
public RobustUrl ServerUrl { get; private set; }
|
||||||
|
|
||||||
public HashApi FileApi { get; private set; } = default!;
|
public IFileApi FileApi { get; private set; } = default!;
|
||||||
|
|
||||||
private ExtContentExecutor _contentExecutor = default!;
|
private ExtContentExecutor _contentExecutor = default!;
|
||||||
|
|
||||||
@@ -315,12 +312,12 @@ public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry
|
|||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var buildInfo = await ContentService.GetBuildInfo(serverUrl, CancellationService.Token);
|
var buildInfo = await ContentService.GetBuildInfo(serverUrl, CancellationService.Token);
|
||||||
FileApi = await ContentService.EnsureItems(buildInfo.RobustManifestInfo, loading,
|
FileApi = await ContentService.EnsureItems(buildInfo, loading,
|
||||||
CancellationService.Token);
|
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;
|
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();
|
var pathDir = path.GetDirectory();
|
||||||
BaseFolderContentEntry parent = this;
|
BaseFolderContentEntry parent = this;
|
||||||
@@ -345,8 +342,8 @@ public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry
|
|||||||
parent = folderContentEntry as BaseFolderContentEntry ?? throw new InvalidOperationException();
|
parent = folderContentEntry as BaseFolderContentEntry ?? throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var manifestContent = new ManifestContentEntry();
|
var manifestContent = new FileContentEntry();
|
||||||
manifestContent.Init(Holder, manifestItem, FileApi, _contentExecutor);
|
manifestContent.Init(Holder, FileApi, path.GetName(), _contentExecutor);
|
||||||
|
|
||||||
parent.AddChild(manifestContent);
|
parent.AddChild(manifestContent);
|
||||||
|
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ public sealed class RunnerService(
|
|||||||
if (engine is null)
|
if (engine is null)
|
||||||
throw new Exception("Engine version not found: " + buildInfo.BuildInfo.Build.EngineVersion);
|
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<ApiMount>
|
var extraMounts = new List<ApiMount>
|
||||||
{
|
{
|
||||||
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);
|
var modules = ContentManifestParser.ExtractModules(stream);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ namespace Nebula.Shared.Models;
|
|||||||
public class RobustBuildInfo
|
public class RobustBuildInfo
|
||||||
{
|
{
|
||||||
public ServerInfo BuildInfo = default!;
|
public ServerInfo BuildInfo = default!;
|
||||||
public RobustManifestInfo RobustManifestInfo;
|
public RobustManifestInfo? RobustManifestInfo;
|
||||||
|
public RobustZipContentInfo? DownloadUri;
|
||||||
public RobustUrl Url = default!;
|
public RobustUrl Url = default!;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Nebula.Shared.Models;
|
namespace Nebula.Shared.Models;
|
||||||
|
|
||||||
public record struct RobustManifestInfo(Uri ManifestUri, Uri DownloadUri, string Hash);
|
public record struct RobustManifestInfo(Uri ManifestUri, Uri DownloadUri, string Hash);
|
||||||
|
public record struct RobustZipContentInfo(Uri DownloadUri, string Hash);
|
||||||
@@ -20,7 +20,7 @@ public sealed record BuildInfo(
|
|||||||
string ManifestDownloadUrl,
|
string ManifestDownloadUrl,
|
||||||
[property: JsonPropertyName("manifest_url")]
|
[property: JsonPropertyName("manifest_url")]
|
||||||
string ManifestUrl,
|
string ManifestUrl,
|
||||||
[property: JsonPropertyName("acz")] bool Acz,
|
[property: JsonPropertyName("acz")] bool? Acz,
|
||||||
[property: JsonPropertyName("hash")] string Hash,
|
[property: JsonPropertyName("hash")] string Hash,
|
||||||
[property: JsonPropertyName("manifest_hash")]
|
[property: JsonPropertyName("manifest_hash")]
|
||||||
string ManifestHash);
|
string ManifestHash);
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Nebula.Shared.FileApis;
|
using Nebula.Shared.FileApis;
|
||||||
using Nebula.Shared.FileApis.Interfaces;
|
using Nebula.Shared.FileApis.Interfaces;
|
||||||
using Nebula.Shared.Models;
|
using Nebula.Shared.Models;
|
||||||
using Nebula.Shared.Utils;
|
using Nebula.Shared.Utils;
|
||||||
|
using Robust.LoaderApi;
|
||||||
|
|
||||||
namespace Nebula.Shared.Services;
|
namespace Nebula.Shared.Services;
|
||||||
|
|
||||||
@@ -14,6 +17,7 @@ public partial class ContentService
|
|||||||
{
|
{
|
||||||
public readonly IReadWriteFileApi ContentFileApi = fileService.CreateFileApi("content");
|
public readonly IReadWriteFileApi ContentFileApi = fileService.CreateFileApi("content");
|
||||||
public readonly IReadWriteFileApi ManifestFileApi = fileService.CreateFileApi("manifest");
|
public readonly IReadWriteFileApi ManifestFileApi = fileService.CreateFileApi("manifest");
|
||||||
|
public readonly IReadWriteFileApi ZipContentApi = fileService.CreateFileApi("zipContent");
|
||||||
|
|
||||||
public void SetServerHash(string address, string hash)
|
public void SetServerHash(string address, string hash)
|
||||||
{
|
{
|
||||||
@@ -34,7 +38,19 @@ public partial class ContentService
|
|||||||
return new HashApi(manifestItems, ContentFileApi);
|
return new HashApi(manifestItems, ContentFileApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HashApi> EnsureItems(ManifestReader manifestReader, Uri downloadUri,
|
public async Task<IFileApi> 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HashApi> EnsureItems(ManifestReader manifestReader, Uri downloadUri,
|
||||||
ILoadingHandlerFactory loadingFactory,
|
ILoadingHandlerFactory loadingFactory,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -58,7 +74,41 @@ public partial class ContentService
|
|||||||
return hashApi;
|
return hashApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HashApi> EnsureItems(RobustManifestInfo info, ILoadingHandlerFactory loadingFactory,
|
private async Task<ZipFileApi> 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<HashApi> EnsureItems(RobustManifestInfo info, ILoadingHandlerFactory loadingFactory,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.Log("Getting manifest: " + info.Hash);
|
_logger.Log("Getting manifest: " + info.Hash);
|
||||||
@@ -90,10 +140,10 @@ public partial class ContentService
|
|||||||
return await EnsureItems(manifestReader, info.DownloadUri, loadingFactory, cancellationToken);
|
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");
|
_logger.Log("Unpack manifest files");
|
||||||
var items = hashApi.Manifest.Values.ToList();
|
var items = hashApi.AllFiles.ToList();
|
||||||
loadingHandler.AppendJob(items.Count);
|
loadingHandler.AppendJob(items.Count);
|
||||||
|
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
@@ -105,13 +155,13 @@ public partial class ContentService
|
|||||||
{
|
{
|
||||||
if (hashApi.TryOpen(item, out var stream))
|
if (hashApi.TryOpen(item, out var stream))
|
||||||
{
|
{
|
||||||
_logger.Log($"Unpack {item.Hash} to: {item.Path}");
|
_logger.Log($"Unpack {item}");
|
||||||
otherApi.Save(item.Path, stream);
|
otherApi.Save(item, stream);
|
||||||
stream.Close();
|
stream.Close();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Error("Error while unpacking thinks " + item.Path);
|
_logger.Error($"Error while unpacking thinks {item}");
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingHandler.AppendResolvedJob();
|
loadingHandler.AppendResolvedJob();
|
||||||
|
|||||||
@@ -19,7 +19,14 @@ public partial class ContentService(
|
|||||||
info.Url = url;
|
info.Url = url;
|
||||||
var bi = await restService.GetAsync<ServerInfo>(url.InfoUri, cancellationToken);
|
var bi = await restService.GetAsync<ServerInfo>(url.InfoUri, cancellationToken);
|
||||||
info.BuildInfo = bi;
|
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"),
|
? new RobustManifestInfo(new RobustPath(info.Url, "manifest.txt"), new RobustPath(info.Url, "download"),
|
||||||
bi.Build.ManifestHash)
|
bi.Build.ManifestHash)
|
||||||
: new RobustManifestInfo(new Uri(info.BuildInfo.Build.ManifestUrl),
|
: new RobustManifestInfo(new Uri(info.BuildInfo.Build.ManifestUrl),
|
||||||
|
|||||||
Reference in New Issue
Block a user