diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index b3e00a8d..70f2f7ae 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -3,8 +3,6 @@ name: FModel QA Builder on: push: branches: [ dev ] - pull_request: - branches: [ dev ] jobs: build: diff --git a/CUE4Parse b/CUE4Parse index 3ff8c179..5107809d 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 3ff8c179dfbe817f22e9672dab9c2901a58b9db7 +Subproject commit 5107809d35e3c43121c717b3b3aa732aa08e77fb diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 9cc995ef..0d0ccca8 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -69,8 +69,7 @@ private async void OnLoaded(object sender, RoutedEventArgs e) #endif await Task.WhenAll( _applicationView.CUE4Parse.VerifyConsoleVariables(), - _applicationView.CUE4Parse.VerifyVirtualCache(), - _applicationView.CUE4Parse.VerifyContentBuildManifest(), + _applicationView.CUE4Parse.VerifyOnDemandArchives(), _applicationView.CUE4Parse.InitMappings(), _applicationView.InitImGuiSettings(newOrUpdated), _applicationView.InitVgmStream(), diff --git a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs index e09390a2..386762ce 100644 --- a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs @@ -14,20 +14,12 @@ public class EpicApiEndpoint : AbstractApiProvider private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token"; private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE="; private const string _APP_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live"; - private const string _CBM_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/Windows/5cb97847cee34581afdbc445400e2f77/FortniteContentBuilds"; public EpicApiEndpoint(RestClient client) : base(client) { } public async Task GetManifestAsync(CancellationToken token) { - if (await IsExpired().ConfigureAwait(false)) - { - var auth = await GetAuthAsync(token).ConfigureAwait(false); - if (auth != null) - { - UserSettings.Default.LastAuthResponse = auth; - } - } + await VerifyAuth(token).ConfigureAwait(false); var request = new FRestRequest(_APP_URL); request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}"); @@ -36,7 +28,12 @@ public async Task GetManifestAsync(CancellationToken token) return response.IsSuccessful ? new ManifestInfo(response.Content) : null; } - public async Task GetContentBuildManifestAsync(CancellationToken token, string label) + public ManifestInfo GetManifest(CancellationToken token) + { + return GetManifestAsync(token).GetAwaiter().GetResult(); + } + + public async Task VerifyAuth(CancellationToken token) { if (await IsExpired().ConfigureAwait(false)) { @@ -46,23 +43,6 @@ public async Task GetContentBuildManifestAsync(Cancell UserSettings.Default.LastAuthResponse = auth; } } - - var request = new FRestRequest(_CBM_URL); - request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}"); - request.AddQueryParameter("label", label); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); - return response.IsSuccessful ? new ContentBuildManifestInfo(response.Content) : null; - } - - public ManifestInfo GetManifest(CancellationToken token) - { - return GetManifestAsync(token).GetAwaiter().GetResult(); - } - - public ContentBuildManifestInfo GetContentBuildManifest(CancellationToken token, string label) - { - return GetContentBuildManifestAsync(token, label).GetAwaiter().GetResult(); } private async Task GetAuthAsync(CancellationToken token) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 473ad1fb..21de8fec 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http.Headers; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -31,7 +32,6 @@ using CUE4Parse.UE4.Wwise; using CUE4Parse_Conversion; using CUE4Parse_Conversion.Sounds; -using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Objects.Core.Serialization; using EpicManifestParser.Objects; using FModel.Creator; @@ -121,7 +121,7 @@ public Snooper SnooperViewer public AssetsFolderViewModel AssetsFolder { get; } public SearchViewModel SearchVm { get; } public TabControlViewModel TabControl { get; } - public ConfigIni BuildInfo { get; } + public ConfigIni IoStoreOnDemand { get; } public CUE4ParseViewModel() { @@ -175,7 +175,7 @@ public CUE4ParseViewModel() AssetsFolder = new AssetsFolderViewModel(); SearchVm = new SearchViewModel(); TabControl = new TabControlViewModel(); - BuildInfo = new ConfigIni(nameof(BuildInfo)); + IoStoreOnDemand = new ConfigIni(nameof(IoStoreOnDemand)); } public async Task Initialize() @@ -216,9 +216,9 @@ await _threadWorkerView.Begin(cancellationToken => foreach (var fileManifest in manifest.FileManifests) { - if (fileManifest.Name.Equals("Cloud/BuildInfo.ini", StringComparison.OrdinalIgnoreCase)) + if (fileManifest.Name.Equals("Cloud/IoStoreOnDemand.ini", StringComparison.OrdinalIgnoreCase)) { - BuildInfo.Read(new StreamReader(fileManifest.GetStream())); + IoStoreOnDemand.Read(new StreamReader(fileManifest.GetStream())); continue; } if (!_fnLive.IsMatch(fileManifest.Name)) continue; @@ -252,8 +252,8 @@ await _threadWorkerView.Begin(cancellationToken => break; case DefaultFileProvider: - var buildInfoPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\BuildInfo.ini"); - if (File.Exists(buildInfoPath)) BuildInfo.Read(new StringReader(File.ReadAllText(buildInfoPath))); + var ioStoreOnDemandPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\IoStoreOnDemand.ini"); + if (File.Exists(ioStoreOnDemandPath)) IoStoreOnDemand.Read(new StringReader(File.ReadAllText(ioStoreOnDemandPath))); break; } @@ -294,7 +294,7 @@ public void LoadVfs(CancellationToken token, IEnumerable aesKeys) if (Provider.MountedVfs.FirstOrDefault(x => x.Name == file.Name) is not { } vfs) { if (Provider.UnloadedVfs.FirstOrDefault(x => x.Name == file.Name) is IoStoreReader store) - file.FileCount = (int) store.Info.TocEntryCount - 1; + file.FileCount = (int) store.TocResource.Header.TocEntryCount - 1; continue; } @@ -436,87 +436,35 @@ public Task VerifyConsoleVariables() }); } - private int _vfcCount { get; set; } - public Task VerifyVirtualCache() - { - if (Provider is StreamedFileProvider { LiveGame: "FortniteLive" } || _vfcCount > 0) - return Task.CompletedTask; - - return Task.Run(() => - { - _vfcCount = Provider.LoadVirtualCache(); - if (_vfcCount > 0) - FLogger.Append(ELog.Information, - () => FLogger.Text($"{_vfcCount} cached packages loaded", Constants.WHITE, true)); - }); - } - - public Task VerifyContentBuildManifest() + public Task VerifyOnDemandArchives() { + // only local fortnite if (Provider is not DefaultFileProvider || !Provider.InternalGameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) return Task.CompletedTask; + // scuffed but working var persistentDownloadDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FortniteGame/Saved/PersistentDownloadDir"); - var vfcMetadata = Path.Combine(persistentDownloadDir, "VFC", "vfc.meta"); - if (!File.Exists(vfcMetadata)) + var iasFileInfo = new FileInfo(Path.Combine(persistentDownloadDir, "ias", "ias.cache.0")); + if (!iasFileInfo.Exists || iasFileInfo.Length == 0) return Task.CompletedTask; - // load if local fortnite with ondemand disabled - // VFC folder is created at launch if ondemand - // VFC folder is deleted at launch if not ondemand anymore - return Task.Run(() => + return Task.Run(async () => { var inst = new List(); - BuildInfo.FindPropertyInstructions("Content", "Label", inst); + IoStoreOnDemand.FindPropertyInstructions("Endpoint", "TocPath", inst); if (inst.Count <= 0) return; - var manifestInfo = _apiEndpointView.EpicApi.GetContentBuildManifest(default, inst[0].Value); - var manifestDir = new DirectoryInfo(Path.Combine(persistentDownloadDir, "ManifestCache")); - var manifestPath = Path.Combine(manifestDir.FullName, manifestInfo?.FileName ?? ""); - - byte[] manifestData; - if (File.Exists(manifestPath)) - { - manifestData = File.ReadAllBytes(manifestPath); - } - else if (manifestInfo != null) - { - manifestData = manifestInfo.DownloadManifestData(); - File.WriteAllBytes(manifestPath, manifestData); - } - else if (manifestDir.Exists && manifestDir.GetFiles("*.manifest") is { Length: > 0} cachedManifests) - { - manifestData = File.ReadAllBytes(cachedManifests[0].FullName); - } - else return; + var ioStoreOnDemandPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud", inst[0].Value.SubstringAfterLast("/").SubstringBefore("\"")); + if (!File.Exists(ioStoreOnDemandPath)) return; - var manifest = new Manifest(manifestData, new ManifestOptions + await _apiEndpointView.EpicApi.VerifyAuth(default); + await Provider.RegisterVfs(new IoChunkToc(ioStoreOnDemandPath), new IoStoreOnDemandOptions { - ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/Content/CloudDir/ChunksV4/", UriKind.Absolute), - ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")) + ChunkBaseUri = new Uri("https://download.epicgames.com/ias/fortnite/", UriKind.Absolute), + ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")), + Authorization = new AuthenticationHeaderValue("Bearer", UserSettings.Default.LastAuthResponse.AccessToken) }); - - var onDemandFiles = new Dictionary(); - foreach (var fileManifest in manifest.FileManifests) - { - if (Provider.Files.TryGetValue(fileManifest.Name, out _)) continue; - - var onDemandFile = new StreamedGameFile(fileManifest.Name, fileManifest.GetStream(), Provider.Versions); - if (Provider.IsCaseInsensitive) onDemandFiles[onDemandFile.Path.ToLowerInvariant()] = onDemandFile; - else onDemandFiles[onDemandFile.Path] = onDemandFile; - } - - (Provider.Files as FileProviderDictionary)?.AddFiles(onDemandFiles); - if (onDemandFiles.Count > 0) - FLogger.Append(ELog.Information, - () => FLogger.Text($"{onDemandFiles.Count} streamed packages loaded", Constants.WHITE, true)); -#if DEBUG - - var missing = manifest.FileManifests.Count - onDemandFiles.Count; - if (missing > 0) - FLogger.Append(ELog.Debug, - () => FLogger.Text($"{missing} packages were already loaded by regular archives", Constants.WHITE, true)); -#endif + await Provider.MountAsync(); }); }