diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs
index 25e82893..fa41c1fa 100644
--- a/FModel/MainWindow.xaml.cs
+++ b/FModel/MainWindow.xaml.cs
@@ -118,6 +118,7 @@ public partial class MainWindow
await Task.WhenAll(
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
+ _applicationView.CUE4Parse.VerifyCloudArchives(),
_applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitDetex(),
ApplicationViewModel.InitVgmStream(),
diff --git a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs
index 598f070c..d9fd084f 100644
--- a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs
+++ b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs
@@ -30,6 +30,11 @@ public class ManifestInfoDilly
[J] public string DownloadUrl { get; private set; }
}
+public class CloudContent
+{
+ public string ManifestPath { get; private set; }
+}
+
public class Donator
{
[J] public string Username { get; private set; }
diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs
index fd589621..0a2891dd 100644
--- a/FModel/ViewModels/CUE4ParseViewModel.cs
+++ b/FModel/ViewModels/CUE4ParseViewModel.cs
@@ -75,6 +75,7 @@ using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
+using FModel.ViewModels.ApiEndpoints.Models;
using FModel.Views;
using FModel.Views.Resources.Controls;
using FModel.Views.Snooper;
@@ -88,6 +89,7 @@ using Svg.Skia;
using UE4Config.Parsing;
using Application = System.Windows.Application;
using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid;
+using Version = System.Version;
namespace FModel.ViewModels;
@@ -372,6 +374,21 @@ public class CUE4ParseViewModel : ViewModel
}
}
+ private void RegisterArchivesFromManifest(DefaultFileProvider provider, FBuildPatchAppManifest manifest)
+ {
+ var archiveFiles = manifest.Files.Where(x =>
+ _fnLiveRegex.IsMatch(x.FileName) &&
+ (x.FileName.EndsWith(".pak", StringComparison.OrdinalIgnoreCase) ||
+ x.FileName.EndsWith(".utoc", StringComparison.OrdinalIgnoreCase))).ToList();
+
+ Parallel.ForEach(archiveFiles, fileManifest =>
+ {
+ provider.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream()],
+ it => new FRandomAccessStreamArchive(it, manifest.FindFile(it)!.GetStream(), provider.Versions));
+ });
+ }
+
+
///
/// load virtual files system from GameDirectory
///
@@ -551,6 +568,47 @@ public class CUE4ParseViewModel : ViewModel
});
}
+ public Task VerifyCloudArchives()
+ {
+ if (Provider is not DefaultFileProvider p || !Provider.ProjectName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase))
+ return Task.CompletedTask;
+
+ var cloudContentPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\cloudcontent.json");
+ if (!File.Exists(cloudContentPath))
+ return Task.CompletedTask;
+
+ return Task.Run(async () =>
+ {
+ var startTs = Stopwatch.GetTimestamp();
+
+ var cloudContent = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(cloudContentPath));
+ if (cloudContent is null || string.IsNullOrEmpty(cloudContent.ManifestPath))
+ return;
+
+ var manifestBytes = await _chunkClient.GetByteArrayAsync("https://egdownload.fastly-edge.com/" + cloudContent.ManifestPath);
+
+ var manifestOptions = new ManifestParseOptions
+ {
+ ChunkCacheDirectory = CacheManager.ChunksDirectory,
+ ManifestCacheDirectory = CacheManager.ManifestsDirectory,
+ ChunkBaseUrl = "https://egdownload.fastly-edge.com/Builds/Fortnite/CloudDir/",
+ Decompressor = Compression.Decompressor,
+ Client = _chunkClient,
+ CacheChunksAsIs = false
+ };
+
+ var contentManifest = FBuildPatchAppManifest.Deserialize(manifestBytes, manifestOptions);
+
+ RegisterArchivesFromManifest(p, contentManifest);
+
+ var cloudCount = await Provider.MountAsync();
+ var elapsedTime = Stopwatch.GetElapsedTime(startTs);
+
+ FLogger.Append(ELog.Information, () =>
+ FLogger.Text($"{cloudCount} cloud archive{(cloudCount > 1 ? "s" : "")} streamed via epicgames.com in {elapsedTime.TotalMilliseconds:F1}ms", Constants.WHITE, true));
+ });
+ }
+
public int LocalizedResourcesCount { get; set; }
public bool LocalResourcesDone { get; set; }
public bool HotfixedResourcesDone { get; set; }