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; }