From 63b2c81229eaa1cdd83ace20b7e6153622c1edff Mon Sep 17 00:00:00 2001 From: TomatechGames Date: Mon, 25 May 2026 00:03:49 +0100 Subject: [PATCH 1/7] Convert to Class Library Converts the BanjoBotAssets project to a Class Library, and adds a BanjoBotAssets.Console project to replicate the old behaviour. Moves appsettings.json to the Console project to preserve preferences --- .../BanjoBotAssets.Console.csproj | 38 ++++++++++++++++ BanjoBotAssets.Console/Program.cs | 43 ++++++++++++++++++ .../appsettings.json | 0 BanjoBotAssets.sln | 10 ++++- BanjoBotAssets/BanjoBotAssets.csproj | 2 +- BanjoBotAssets/BanjoEntryPoint.cs | 44 +++++++++++++++++++ BanjoBotAssets/Program.cs | 8 ++++ 7 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 BanjoBotAssets.Console/BanjoBotAssets.Console.csproj create mode 100644 BanjoBotAssets.Console/Program.cs rename {BanjoBotAssets => BanjoBotAssets.Console}/appsettings.json (100%) create mode 100644 BanjoBotAssets/BanjoEntryPoint.cs diff --git a/BanjoBotAssets.Console/BanjoBotAssets.Console.csproj b/BanjoBotAssets.Console/BanjoBotAssets.Console.csproj new file mode 100644 index 0000000..1b040cb --- /dev/null +++ b/BanjoBotAssets.Console/BanjoBotAssets.Console.csproj @@ -0,0 +1,38 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/BanjoBotAssets.Console/Program.cs b/BanjoBotAssets.Console/Program.cs new file mode 100644 index 0000000..759cbf1 --- /dev/null +++ b/BanjoBotAssets.Console/Program.cs @@ -0,0 +1,43 @@ +/* Copyright 2026 Tara "Dino" Cassatt + * + * This file is part of BanjoBotAssets. + * + * BanjoBotAssets is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BanjoBotAssets is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BanjoBotAssets. If not, see . + */ + + +using BanjoBotAssets; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Reflection; + + +await Host.CreateDefaultBuilder(args) +#if DEBUG + .UseEnvironment("Development") +#endif + .UseContentRoot(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException("Null content root")) + .ConfigureLogging(logging => + { + logging + .ClearProviders() + .AddSimpleConsole(console => console.SingleLine = true); + }) + .ConfigureServices(services => + { + services.AddBanjoServices(); + }) + .RunConsoleAsync(o => o.SuppressStatusMessages = true); + +return Environment.ExitCode; \ No newline at end of file diff --git a/BanjoBotAssets/appsettings.json b/BanjoBotAssets.Console/appsettings.json similarity index 100% rename from BanjoBotAssets/appsettings.json rename to BanjoBotAssets.Console/appsettings.json diff --git a/BanjoBotAssets.sln b/BanjoBotAssets.sln index c101549..bcb5e2a 100644 --- a/BanjoBotAssets.sln +++ b/BanjoBotAssets.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31919.166 +# Visual Studio Version 18 +VisualStudioVersion = 18.5.11801.241 oobstable MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BanjoBotAssets", "BanjoBotAssets\BanjoBotAssets.csproj", "{BE443DAB-C3C2-4505-8DE0-0FBAD609481A}" EndProject @@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BanjoBotAssets.SourceGenera EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BanjoBotAssets.SourceGenerators.Tests", "BanjoBotAssets.SourceGenerators.Tests\BanjoBotAssets.SourceGenerators.Tests.csproj", "{A786A866-CE3F-49D4-A54A-CC6488B732E3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BanjoBotAssets.Console", "BanjoBotAssets.Console\BanjoBotAssets.Console.csproj", "{7E3756BA-9BBF-461D-8ED9-3A0D43251039}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,10 @@ Global {A786A866-CE3F-49D4-A54A-CC6488B732E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {A786A866-CE3F-49D4-A54A-CC6488B732E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {A786A866-CE3F-49D4-A54A-CC6488B732E3}.Release|Any CPU.Build.0 = Release|Any CPU + {7E3756BA-9BBF-461D-8ED9-3A0D43251039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E3756BA-9BBF-461D-8ED9-3A0D43251039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E3756BA-9BBF-461D-8ED9-3A0D43251039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E3756BA-9BBF-461D-8ED9-3A0D43251039}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BanjoBotAssets/BanjoBotAssets.csproj b/BanjoBotAssets/BanjoBotAssets.csproj index 6750ce7..2943689 100644 --- a/BanjoBotAssets/BanjoBotAssets.csproj +++ b/BanjoBotAssets/BanjoBotAssets.csproj @@ -1,7 +1,7 @@  - Exe + Library net8.0 enable enable diff --git a/BanjoBotAssets/BanjoEntryPoint.cs b/BanjoBotAssets/BanjoEntryPoint.cs new file mode 100644 index 0000000..db922fb --- /dev/null +++ b/BanjoBotAssets/BanjoEntryPoint.cs @@ -0,0 +1,44 @@ +/* Copyright 2026 Tara "Dino" Cassatt + * + * This file is part of BanjoBotAssets. + * + * BanjoBotAssets is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BanjoBotAssets is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BanjoBotAssets. If not, see . + */ +using BanjoBotAssets.Config; + +namespace BanjoBotAssets +{ + public static class BanjoEntryPoint + { + public static IServiceCollection AddBanjoServices(this IServiceCollection services) + { + // TODO: export per-difficulty stat clamp tables (GameDifficultyGrowthBounds, CombatStatClampsPerTheater) + // TODO: export collection book categories and recruitment/research/voucher options (CollectionBookSlots) + + services + .AddHostedService() + .AddHttpClient() + .AddAesProviders() + .AddMappingsProviders() + .AddGameFileProvider() + .AddAssetExporters(); + + services + .AddOptions() + .Configure((scope, config) => config.Bind(scope)); + + return services; + } + } +} diff --git a/BanjoBotAssets/Program.cs b/BanjoBotAssets/Program.cs index f5727e3..e34c311 100644 --- a/BanjoBotAssets/Program.cs +++ b/BanjoBotAssets/Program.cs @@ -23,6 +23,12 @@ [assembly: InternalsVisibleTo("BanjoBotAssets.SourceGenerators.Tests")] +namespace BanjoBotAssets; + +internal class OldProgram +{ + internal async Task Main(string[] args) + { // TODO: export per-difficulty stat clamp tables (GameDifficultyGrowthBounds, CombatStatClampsPerTheater) // TODO: export collection book categories and recruitment/research/voucher options (CollectionBookSlots) @@ -54,3 +60,5 @@ await Host.CreateDefaultBuilder(args) .RunConsoleAsync(o => o.SuppressStatusMessages = true); return Environment.ExitCode; + } +} From c3401be91e65527b4e3b001c4ae30ff0850c8be3 Mon Sep 17 00:00:00 2001 From: TomatechGames Date: Mon, 25 May 2026 01:01:50 +0100 Subject: [PATCH 2/7] Update CUE4Parse Also replaces defunct FortniteCentral URLs with equivelant fortniteapi.com URLs Additionally, this will require a PR in CUE4Parse-FortniteTypes to be merged (reimplements a helper method we had in our fork of CUE4Parse) https://github.com/BanjoByTheBay/CUE4Parse-FortniteTypes/pull/2 --- BanjoBotAssets.Console/appsettings.json | 4 ++-- BanjoBotAssets/AssetExportService.cs | 12 ++++++++---- BanjoBotAssets/BanjoBotAssets.csproj | 2 +- BanjoBotAssets/CachingFileProvider.cs | 12 ++++++------ .../ServiceCollectionExtensions.AppConfig.cs | 6 +++--- BanjoBotAssets/Exporters/BaseExporter.cs | 2 +- BanjoBotAssets/Exporters/DifficultyExporter.cs | 2 +- .../Exporters/ExpeditionCriteriaExporter.cs | 2 +- BanjoBotAssets/Exporters/ExportProgress.cs | 1 + .../Exporters/Groups/GroupExporter.cs | 2 +- .../Exporters/Groups/HeroExporter.cs | 4 ++-- .../Exporters/Groups/SchematicExporter.cs | 4 ++-- BanjoBotAssets/Exporters/ItemRatingExporter.cs | 4 ++-- BanjoBotAssets/Exporters/QuestMapExporter.cs | 2 +- .../Exporters/UObjects/AbilityExporter.cs | 1 + .../Exporters/UObjects/UObjectExporter.cs | 15 ++++++++++++--- .../Exporters/VenturesSeasonExporter.cs | 18 ++++++++++++------ .../PostExporters/ImageFilesPostExporter.cs | 10 ++++++---- external/CUE4Parse | 2 +- 19 files changed, 64 insertions(+), 41 deletions(-) diff --git a/BanjoBotAssets.Console/appsettings.json b/BanjoBotAssets.Console/appsettings.json index ad1916d..6247b5b 100644 --- a/BanjoBotAssets.Console/appsettings.json +++ b/BanjoBotAssets.Console/appsettings.json @@ -9,11 +9,11 @@ "ELanguage": "" }, "AesOptions": { - "AesApiUri": "https://fortnitecentral.genxgames.gg/api/v1/aes", + "AesApiUri": "https://api.fortniteapi.com/v1/aes", "LocalFilePath": "aes.json" }, "MappingsOptions": { - "MappingsApiUri": "https://fortnitecentral.genxgames.gg/api/v1/mappings", + "MappingsApiUri": "https://api.fortniteapi.com/v1/mappings", "LocalFilePath": "mappings.usmap" }, "ExportedAssets": { diff --git a/BanjoBotAssets/AssetExportService.cs b/BanjoBotAssets/AssetExportService.cs index f9f0735..a73b2ea 100644 --- a/BanjoBotAssets/AssetExportService.cs +++ b/BanjoBotAssets/AssetExportService.cs @@ -181,11 +181,11 @@ private async Task InitializeOodleAsync() { logger.LogInformation(Resources.Status_LocatingOodle); - await OodleHelper.DownloadOodleDllAsync(OodleHelper.OODLE_DLL_NAME); + await OodleHelper.DownloadOodleDllAsync(); logger.LogInformation(Resources.Status_InitializingOodle); - OodleHelper.Initialize(OodleHelper.OODLE_DLL_NAME); + OodleHelper.Initialize(); } private void LoadVirtualPaths() @@ -198,7 +198,7 @@ private async Task LoadMappingsAsync(CancellationToken cancellationToken) { logger.LogInformation(Resources.Status_LoadingMappings); - if (provider.InternalGameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) + if (provider.ProjectName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) { provider.MappingsContainer = typeMappingsProviderFactory.Create(); } @@ -351,8 +351,12 @@ private void OfferFileListToExporters() private void LoadLocalization(CancellationToken cancellationToken) { + //PostMount is intended to be used to validate encryption keys, but it now also prepares localization dictionary (for some reason) + //might be better off at the end of DecryptGameFilesAsync + provider.PostMount(); + logger.LogInformation(Resources.Status_LoadingLocalization, languageProvider.Language.ToString()); - provider.LoadLocalization(languageProvider.Language, cancellationToken); + provider.ChangeCulture(provider.GetLanguageCode(languageProvider.Language)); } } } diff --git a/BanjoBotAssets/BanjoBotAssets.csproj b/BanjoBotAssets/BanjoBotAssets.csproj index 2943689..aea723c 100644 --- a/BanjoBotAssets/BanjoBotAssets.csproj +++ b/BanjoBotAssets/BanjoBotAssets.csproj @@ -54,7 +54,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/BanjoBotAssets/CachingFileProvider.cs b/BanjoBotAssets/CachingFileProvider.cs index 52f8672..381c145 100644 --- a/BanjoBotAssets/CachingFileProvider.cs +++ b/BanjoBotAssets/CachingFileProvider.cs @@ -98,13 +98,13 @@ private void WriteToAssetLog(string line) } } - public override Task LoadPackageAsync(GameFile file) + public override IPackage LoadPackage(GameFile file) { Interlocked.Increment(ref cacheRequests); - return cache.Cache.GetOrAddAsync( + return cache.Cache.GetOrAdd( file.Path, - async _ => + _ => { Interlocked.Increment(ref cacheMisses); @@ -112,12 +112,12 @@ public override Task LoadPackageAsync(GameFile file) logger.LogDebug(Resources.Status_CacheMiss, file.Path, file.Size); WriteToAssetLog(file.Path); - return await base.LoadPackageAsync(file); + return base.LoadPackage(file); }, new MemoryCacheEntryOptions { Size = file.Size }); } - public override Task TryLoadPackageAsync(GameFile file) + public override Task LoadPackageAsync(GameFile file) { Interlocked.Increment(ref cacheRequests); @@ -131,7 +131,7 @@ public override Task LoadPackageAsync(GameFile file) logger.LogDebug(Resources.Status_CacheMiss, file.Path, file.Size); WriteToAssetLog(file.Path); - return await base.TryLoadPackageAsync(file); + return await base.LoadPackageAsync(file); }, new MemoryCacheEntryOptions { Size = file.Size }); } diff --git a/BanjoBotAssets/Config/ServiceCollectionExtensions.AppConfig.cs b/BanjoBotAssets/Config/ServiceCollectionExtensions.AppConfig.cs index c0d49a4..f3ea3cb 100644 --- a/BanjoBotAssets/Config/ServiceCollectionExtensions.AppConfig.cs +++ b/BanjoBotAssets/Config/ServiceCollectionExtensions.AppConfig.cs @@ -45,7 +45,7 @@ public static IServiceCollection AddAesProviders(this IServiceCollection service .AddOptions() .Configure((options, config) => { - options.AesApiUri = "https://fortnitecentral.genxgames.gg/api/v1/aes"; + options.AesApiUri = "https://api.fortniteapi.com/v1/aes"; options.LocalFilePath = "aes.json"; config.GetSection(nameof(AesOptions)).Bind(options); }); @@ -152,7 +152,7 @@ public static IServiceCollection AddGameFileProvider(this IServiceCollection ser directory: gameDirectory, searchOption: SearchOption.TopDirectoryOnly, isCaseInsensitive: true, - versions: new VersionContainer(EGame.GAME_UE5_4), + versions: new VersionContainer(EGame.GAME_UE5_6), assetLogPath: perfOptions.Value.AssetLogPath); provider.Initialize(); @@ -192,7 +192,7 @@ public static IServiceCollection AddMappingsProviders(this IServiceCollection se services.AddOptions() .Configure((options, config) => { - options.MappingsApiUri = "https://fortnitecentral.genxgames.gg/api/v1/mappings"; + options.MappingsApiUri = "https://api.fortniteapi.com/v1/mappings"; options.LocalFilePath = "mappings.usmap"; config.GetSection(nameof(MappingsOptions)).Bind(options); }); diff --git a/BanjoBotAssets/Exporters/BaseExporter.cs b/BanjoBotAssets/Exporters/BaseExporter.cs index c109ebd..7967949 100644 --- a/BanjoBotAssets/Exporters/BaseExporter.cs +++ b/BanjoBotAssets/Exporters/BaseExporter.cs @@ -56,7 +56,7 @@ public void CountAssetLoaded() var file = provider[path]; CountAssetLoaded(); - return await provider.LoadObjectAsync(file.PathWithoutExtension); + return await provider.SafeLoadPackageObjectAsync(file.PathWithoutExtension); } public abstract Task ExportAssetsAsync(IProgress progress, IAssetOutput output, CancellationToken cancellationToken); diff --git a/BanjoBotAssets/Exporters/DifficultyExporter.cs b/BanjoBotAssets/Exporters/DifficultyExporter.cs index ae1f550..2481154 100644 --- a/BanjoBotAssets/Exporters/DifficultyExporter.cs +++ b/BanjoBotAssets/Exporters/DifficultyExporter.cs @@ -32,7 +32,7 @@ public override async Task ExportAssetsAsync(IProgress progress, var file = provider[growthBoundsPath]; Interlocked.Increment(ref assetsLoaded); - var dataTable = await provider.LoadObjectAsync(file.PathWithoutExtension); + var dataTable = await provider.SafeLoadPackageObjectAsync(file.PathWithoutExtension); if (dataTable == null) { diff --git a/BanjoBotAssets/Exporters/ExpeditionCriteriaExporter.cs b/BanjoBotAssets/Exporters/ExpeditionCriteriaExporter.cs index b9059ca..306796d 100644 --- a/BanjoBotAssets/Exporters/ExpeditionCriteriaExporter.cs +++ b/BanjoBotAssets/Exporters/ExpeditionCriteriaExporter.cs @@ -17,7 +17,7 @@ public override async Task ExportAssetsAsync(IProgress progress, var file = provider[criteriaPath]; Interlocked.Increment(ref assetsLoaded); - var dataTable = await provider.LoadObjectAsync(file.PathWithoutExtension); + var dataTable = await provider.SafeLoadPackageObjectAsync(file.PathWithoutExtension); if (dataTable == null) { diff --git a/BanjoBotAssets/Exporters/ExportProgress.cs b/BanjoBotAssets/Exporters/ExportProgress.cs index 79927b3..e740601 100644 --- a/BanjoBotAssets/Exporters/ExportProgress.cs +++ b/BanjoBotAssets/Exporters/ExportProgress.cs @@ -19,6 +19,7 @@ namespace BanjoBotAssets.Exporters { internal sealed record ExportProgress { + public string? ExportType { get; init; } public int TotalSteps { get; init; } public int CompletedSteps { get; init; } public required string CurrentItem { get; init; } diff --git a/BanjoBotAssets/Exporters/Groups/GroupExporter.cs b/BanjoBotAssets/Exporters/Groups/GroupExporter.cs index 6b60060..390287d 100644 --- a/BanjoBotAssets/Exporters/Groups/GroupExporter.cs +++ b/BanjoBotAssets/Exporters/Groups/GroupExporter.cs @@ -128,7 +128,7 @@ await Parallel.ForEachAsync(assetsToProcess, opts, async (grouping, _) => Report(progress, file.PathWithoutExtension); - var asset = await provider.LoadObjectAsync(file.PathWithoutExtension); + var asset = await provider.SafeLoadPackageObjectAsync(file.PathWithoutExtension); if (asset == null) { diff --git a/BanjoBotAssets/Exporters/Groups/HeroExporter.cs b/BanjoBotAssets/Exporters/Groups/HeroExporter.cs index 56ec7b8..a8a1281 100644 --- a/BanjoBotAssets/Exporters/Groups/HeroExporter.cs +++ b/BanjoBotAssets/Exporters/Groups/HeroExporter.cs @@ -165,8 +165,8 @@ public override async Task ExportAssetsAsync(IProgress progress, { if (itemToQuestPath != null && questRewardsPath != null) { - var itemToQuestTask = provider.LoadObjectAsync(provider.Files[itemToQuestPath].PathWithoutExtension); - var questRewardsTask = provider.LoadObjectAsync(provider.Files[questRewardsPath].PathWithoutExtension); + var itemToQuestTask = provider.LoadPackageObjectAsync(provider.Files[itemToQuestPath].PathWithoutExtension); + var questRewardsTask = provider.LoadPackageObjectAsync(provider.Files[questRewardsPath].PathWithoutExtension); var itemToQuestTable = await itemToQuestTask; var questRewardsTable = await questRewardsTask; diff --git a/BanjoBotAssets/Exporters/Groups/SchematicExporter.cs b/BanjoBotAssets/Exporters/Groups/SchematicExporter.cs index bd1781c..a8feaae 100644 --- a/BanjoBotAssets/Exporters/Groups/SchematicExporter.cs +++ b/BanjoBotAssets/Exporters/Groups/SchematicExporter.cs @@ -195,7 +195,7 @@ public override async Task ExportAssetsAsync(IProgress progress, } var widOrTidFile = provider[widOrTidPath]; Interlocked.Increment(ref assetsLoaded); - return await provider.LoadObjectAsync(widOrTidFile.PathWithoutExtension); + return await provider.SafeLoadPackageObjectAsync(widOrTidFile.PathWithoutExtension); } protected override async Task ExtractCommonFieldsAsync(UObject asset, IGrouping grouping) @@ -347,7 +347,7 @@ private static (string category, string subType) CategoryAndSubTypeFromTags(FGam return await cachedAmmoTypesFromPaths.GetOrAdd(ammoDataPath.AssetPathName.Text, static async (path, provider) => { - var asset = await provider.LoadObjectAsync(path); + var asset = await provider.SafeLoadPackageObjectAsync(path); if (asset.ItemName?.Text is string str) { var i = str.IndexOf(':'); diff --git a/BanjoBotAssets/Exporters/ItemRatingExporter.cs b/BanjoBotAssets/Exporters/ItemRatingExporter.cs index 4ad2feb..db7a16c 100644 --- a/BanjoBotAssets/Exporters/ItemRatingExporter.cs +++ b/BanjoBotAssets/Exporters/ItemRatingExporter.cs @@ -47,7 +47,7 @@ private async Task ExportDefaultItemRatingsAsync(IAssetOutput output) var file = provider[baseItemRatingPath]; Interlocked.Increment(ref assetsLoaded); - var curveTable = await provider.LoadObjectAsync(file.PathWithoutExtension); + var curveTable = await provider.SafeLoadPackageObjectAsync(file.PathWithoutExtension); if (curveTable == null) { @@ -71,7 +71,7 @@ private async Task ExportSurvivorItemRatingsAsync(IAssetOutput output) var file = provider[survivorItemRatingPath]; Interlocked.Increment(ref assetsLoaded); - var curveTable = await provider.LoadObjectAsync(file.PathWithoutExtension); + var curveTable = await provider.SafeLoadPackageObjectAsync(file.PathWithoutExtension); if (curveTable == null) { diff --git a/BanjoBotAssets/Exporters/QuestMapExporter.cs b/BanjoBotAssets/Exporters/QuestMapExporter.cs index 0f6150f..d22ba4c 100644 --- a/BanjoBotAssets/Exporters/QuestMapExporter.cs +++ b/BanjoBotAssets/Exporters/QuestMapExporter.cs @@ -29,7 +29,7 @@ public override async Task ExportAssetsAsync(IProgress progress, var file = provider[assetPaths[0]]; Interlocked.Increment(ref assetsLoaded); - var mapData = await provider.LoadObjectAsync(file.PathWithoutExtension); + var mapData = await provider.SafeLoadPackageObjectAsync(file.PathWithoutExtension); cancellationToken.ThrowIfCancellationRequested(); diff --git a/BanjoBotAssets/Exporters/UObjects/AbilityExporter.cs b/BanjoBotAssets/Exporters/UObjects/AbilityExporter.cs index 17057af..70afb24 100644 --- a/BanjoBotAssets/Exporters/UObjects/AbilityExporter.cs +++ b/BanjoBotAssets/Exporters/UObjects/AbilityExporter.cs @@ -18,6 +18,7 @@ using BanjoBotAssets.UExports; using CUE4Parse.FN.Structs.GA; using CUE4Parse.UE4.Objects.Engine; +using CUE4Parse.Utilities; using System.Data; namespace BanjoBotAssets.Exporters.UObjects diff --git a/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs b/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs index cdcd3b7..c5b90a1 100644 --- a/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs +++ b/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs @@ -17,6 +17,7 @@ */ using CUE4Parse.FN.Enums.FortniteGame; using System.Collections.Concurrent; +using CUE4Parse.Utilities; namespace BanjoBotAssets.Exporters.UObjects { @@ -85,15 +86,23 @@ await Parallel.ForEachAsync(assetsToProcess, opts, async (path, _) => TAsset? uobject; if (IgnoreLoadFailures) { - var pkg = await provider.TryLoadPackageAsync(file); + IPackage pkg; + try + { + pkg = await provider.LoadPackageAsync(file); + } + catch + { + return; + } cancellationToken.ThrowIfCancellationRequested(); - if (pkg?.GetExportOrNull(file.NameWithoutExtension, StringComparison.OrdinalIgnoreCase) is TAsset asset) + if (pkg.GetExportOrNull(file.NameWithoutExtension, StringComparison.OrdinalIgnoreCase) is TAsset asset) { uobject = asset; } - else if (pkg?.GetExportOrNull(file.NameWithoutExtension + "_C", StringComparison.OrdinalIgnoreCase) is TAsset assetC) + else if (pkg.GetExportOrNull(file.NameWithoutExtension + "_C", StringComparison.OrdinalIgnoreCase) is TAsset assetC) { uobject = assetC; } diff --git a/BanjoBotAssets/Exporters/VenturesSeasonExporter.cs b/BanjoBotAssets/Exporters/VenturesSeasonExporter.cs index c639677..5a43da8 100644 --- a/BanjoBotAssets/Exporters/VenturesSeasonExporter.cs +++ b/BanjoBotAssets/Exporters/VenturesSeasonExporter.cs @@ -47,17 +47,19 @@ public override async Task ExportAssetsAsync(IProgress progress, return; } - var levelRewardsTask = provider.LoadObjectAsync(provider[levelRewardsPath].PathWithoutExtension); - var pastLevelRewardsTask = provider.LoadObjectAsync(provider[pastLevelRewardsPath].PathWithoutExtension); - var defaultGameDataTask = provider.LoadObjectAsync(provider[defaultGameDataPath].PathWithoutExtension); + var levelRewardsTask = provider.SafeLoadPackageObjectAsync(provider[levelRewardsPath].PathWithoutExtension); + var pastLevelRewardsTask = provider.SafeLoadPackageObjectAsync(provider[pastLevelRewardsPath].PathWithoutExtension); + var defaultGameDataTask = provider.SafeLoadPackageObjectAsync(provider[defaultGameDataPath].PathWithoutExtension); ExportLevelRewards(await levelRewardsTask, output, cancellationToken); ExportPastLevelRewards(await pastLevelRewardsTask, output, cancellationToken); ExportPastLevelXPRequirements(await defaultGameDataTask, output, cancellationToken); } - private static void ExportPastLevelXPRequirements(UObject defaultGameData, IAssetOutput output, CancellationToken cancellationToken) + private static void ExportPastLevelXPRequirements(UObject? defaultGameData, IAssetOutput output, CancellationToken cancellationToken) { + if (defaultGameData is null) + return; cancellationToken.ThrowIfCancellationRequested(); var map = defaultGameData.Get("PhoenixEventOverlevelXPPerLevel"); @@ -70,8 +72,10 @@ private static void ExportPastLevelXPRequirements(UObject defaultGameData, IAsse } } - private static void ExportLevelRewards(UDataTable levelRewardsTable, IAssetOutput output, CancellationToken cancellationToken = default) + private static void ExportLevelRewards(UDataTable? levelRewardsTable, IAssetOutput output, CancellationToken cancellationToken = default) { + if (levelRewardsTable is null) + return; int level = 1; foreach (var (key, row) in levelRewardsTable.RowMap) @@ -111,8 +115,10 @@ private static void ExportLevelRewards(UDataTable levelRewardsTable, IAssetOutpu } } - private static void ExportPastLevelRewards(UDataTable pastLevelRewardsTable, IAssetOutput output, CancellationToken cancellationToken = default) + private static void ExportPastLevelRewards(UDataTable? pastLevelRewardsTable, IAssetOutput output, CancellationToken cancellationToken = default) { + if (pastLevelRewardsTable is null) + return; int pastLevel = 1; foreach (var (key, row) in pastLevelRewardsTable.RowMap) diff --git a/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs b/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs index 34277f8..0782903 100644 --- a/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs +++ b/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs @@ -70,19 +70,21 @@ public async Task ProcessExportsAsync(ExportedAssets exportedAssets, IList(imagePath); - using var bitmap = asset.Decode(); + var asset = await provider.SafeLoadPackageObjectAsync(imagePath); + var bitmap = asset?.Decode(); if (bitmap == null) { logger.LogError(Resources.Error_CannotDecodeTexture, imagePath); continue; } - await using var stream = new FileStream(exportedPath, FileMode.Create, FileAccess.Write); - if (!bitmap.Encode(stream, SkiaSharp.SKEncodedImageFormat.Png, 100)) + var bytes = bitmap.Encode(ETextureFormat.Png, false, out var ext); + if (ext != "png") { logger.LogError(Resources.Error_CannotEncodeTexture, imagePath); } + await using var stream = new FileStream(exportedPath, FileMode.Create, FileAccess.Write); + stream.Write(bytes, 0, bytes.Length); filesWritten++; } } diff --git a/external/CUE4Parse b/external/CUE4Parse index 0566f26..ecc4878 160000 --- a/external/CUE4Parse +++ b/external/CUE4Parse @@ -1 +1 @@ -Subproject commit 0566f26df1a8fab6e7980fc8dc498487953d9378 +Subproject commit ecc4878950336126f125af0747190edf474b2a21 From c1c9c592e1333d726f72ab92f41002d4117ba5e1 Mon Sep 17 00:00:00 2001 From: TomatechGames Date: Mon, 25 May 2026 02:08:30 +0100 Subject: [PATCH 3/7] Add Reporter Interfaces Adds IExportStageReporter and IExportProgressReporter. When implementations are injected as services, AssetExportService will use them to report the current ExportStage (a new enum introduced to correlate with each step of AssetExportService.RunAsync), and any ExportProgress objects emitted by exporters. ExportProgress also has an optional (could become required) string to represent the type of exporter the progress object is coming from. Currently this is only used in UObjectExporter and BlueprintExporter. (Not GroupExporter, since TBH that should've been deprecated months ago in favour of UObjectExporter) Added example implementations of both reporters in BanjoBotAssets.Console --- BanjoBotAssets.Console/Program.cs | 27 +++++++++++++++-- BanjoBotAssets/AssetExportService.cs | 30 +++++++++++++++++-- .../Exporters/Blueprints/BlueprintExporter.cs | 1 + BanjoBotAssets/Exporters/ExportProgress.cs | 2 +- .../Exporters/UObjects/UObjectExporter.cs | 1 + .../Reporters/IExportProgressReporter.cs | 14 +++++++++ .../Reporters/IExportStageReporter.cs | 14 +++++++++ 7 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 BanjoBotAssets/Reporters/IExportProgressReporter.cs create mode 100644 BanjoBotAssets/Reporters/IExportStageReporter.cs diff --git a/BanjoBotAssets.Console/Program.cs b/BanjoBotAssets.Console/Program.cs index 759cbf1..4539e89 100644 --- a/BanjoBotAssets.Console/Program.cs +++ b/BanjoBotAssets.Console/Program.cs @@ -18,6 +18,8 @@ using BanjoBotAssets; +using BanjoBotAssets.Exporters; +using BanjoBotAssets.Reporters; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Reflection; @@ -36,8 +38,29 @@ await Host.CreateDefaultBuilder(args) }) .ConfigureServices(services => { - services.AddBanjoServices(); + services + .AddBanjoServices(); + + services.AddSingleton(); + services.AddSingleton(); }) .RunConsoleAsync(o => o.SuppressStatusMessages = true); -return Environment.ExitCode; \ No newline at end of file +return Environment.ExitCode; + +class ExampleStageReporter : IExportStageReporter +{ + public void Report(ExportStage stage) + { + Console.WriteLine($"Reporting Stage: {stage}"); + } +} + +class ExampleProgressReporter : IExportProgressReporter +{ + public void Report(ExportProgress progress) + { + if (progress.ExportType is not null) + Console.WriteLine($"Reporting Progress for {progress.ExportType}: {progress.CompletedSteps}/{progress.TotalSteps}"); + } +} \ No newline at end of file diff --git a/BanjoBotAssets/AssetExportService.cs b/BanjoBotAssets/AssetExportService.cs index a73b2ea..f3d4331 100644 --- a/BanjoBotAssets/AssetExportService.cs +++ b/BanjoBotAssets/AssetExportService.cs @@ -20,6 +20,7 @@ using BanjoBotAssets.Config; using BanjoBotAssets.Exporters; using BanjoBotAssets.PostExporters; +using BanjoBotAssets.Reporters; using CUE4Parse.Compression; using CUE4Parse.Encryption.Aes; using CUE4Parse.UE4.Objects.Core.Misc; @@ -29,6 +30,19 @@ namespace BanjoBotAssets { + public enum ExportStage + { + DecryptingGameFiles, + InitialisingOodle, + LoadingVirtualPaths, + LoadingMappings, + LoadingLocalisation, + PreparingInterestingAssets, + RunningExporters, + RunningPostExporters, + GeneratingArtifacts, + } + internal sealed partial class AssetExportService( ILogger logger, IHostApplicationLifetime lifetime, @@ -40,7 +54,9 @@ internal sealed partial class AssetExportService( ITypeMappingsProviderFactory typeMappingsProviderFactory, IOptions scopeOptions, IEnumerable allPostExporters, - LanguageProvider languageProvider) : BackgroundService + LanguageProvider languageProvider, + IExportProgressReporter? progressReporter = null, + IExportStageReporter? stageReporter = null) : BackgroundService { private readonly List exportersToRun = MakeExportersToRun(allExporters, scopeOptions); private readonly ConcurrentDictionary failedAssets = new(); @@ -92,36 +108,46 @@ private async Task RunAsync(CancellationToken cancellationToken) // by the time this method is called, the CUE4Parse file provider has already been created, // and the game files have been located but not decrypted. we need to supply the AES keys, // from cache or from an external API. + stageReporter?.Report(ExportStage.DecryptingGameFiles); await DecryptGameFilesAsync(cancellationToken); // download the oodle library if needed, and initialize it + stageReporter?.Report(ExportStage.InitialisingOodle); await InitializeOodleAsync(); // load virtual paths + stageReporter?.Report(ExportStage.LoadingVirtualPaths); LoadVirtualPaths(); // load the type mappings CUE4Parse uses to parse UE structures + stageReporter?.Report(ExportStage.LoadingMappings); await LoadMappingsAsync(cancellationToken); // load localized resources + stageReporter?.Report(ExportStage.LoadingLocalisation); LoadLocalization(cancellationToken); // register the export classes used to expose UE structures as strongly-typed C# objects RegisterExportTypes(); // feed the file list to each exporter so they can record the paths they're interested in + // todo: make this async? would otherwise cause hanging in a GUI app + stageReporter?.Report(ExportStage.PreparingInterestingAssets); OfferFileListToExporters(); // run exporters and collect their intermediate results + stageReporter?.Report(ExportStage.RunningExporters); var (exportedAssets, exportedRecipes, stats1) = await RunSelectedExportersAsync(cancellationToken); // run post-exporters to refine the intermediate results + stageReporter?.Report(ExportStage.RunningPostExporters); var stats2 = await RunSelectedPostExportersAsync(exportedAssets, exportedRecipes, cancellationToken); // report assets loaded and time elapsed ReportAssetLoadingStats(stats1 + stats2); // generate output artifacts + stageReporter?.Report(ExportStage.GeneratingArtifacts); await GenerateSelectedArtifactsAsync(exportedAssets, exportedRecipes, cancellationToken); // report cache stats @@ -312,7 +338,7 @@ private void HandleProgressReport(ExportProgress progress) } } - // TODO: do something more with progress reports + progressReporter?.Report(progress); } private void ReportFailedAssets() diff --git a/BanjoBotAssets/Exporters/Blueprints/BlueprintExporter.cs b/BanjoBotAssets/Exporters/Blueprints/BlueprintExporter.cs index 71b4cb4..bb375e3 100644 --- a/BanjoBotAssets/Exporters/Blueprints/BlueprintExporter.cs +++ b/BanjoBotAssets/Exporters/Blueprints/BlueprintExporter.cs @@ -38,6 +38,7 @@ private void Report(IProgress progress, string current) { progress.Report(new ExportProgress { + ExportType = $"Blueprint.{Type}", TotalSteps = numToProcess, CompletedSteps = processedSoFar, AssetsLoaded = assetsLoaded, diff --git a/BanjoBotAssets/Exporters/ExportProgress.cs b/BanjoBotAssets/Exporters/ExportProgress.cs index e740601..7e036a8 100644 --- a/BanjoBotAssets/Exporters/ExportProgress.cs +++ b/BanjoBotAssets/Exporters/ExportProgress.cs @@ -17,7 +17,7 @@ */ namespace BanjoBotAssets.Exporters { - internal sealed record ExportProgress + public sealed record ExportProgress { public string? ExportType { get; init; } public int TotalSteps { get; init; } diff --git a/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs b/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs index c5b90a1..af05a62 100644 --- a/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs +++ b/BanjoBotAssets/Exporters/UObjects/UObjectExporter.cs @@ -51,6 +51,7 @@ private void Report(IProgress progress, string current) { progress.Report(new ExportProgress { + ExportType = $"UObject.{Type}", TotalSteps = numToProcess, CompletedSteps = processedSoFar, AssetsLoaded = assetsLoaded, diff --git a/BanjoBotAssets/Reporters/IExportProgressReporter.cs b/BanjoBotAssets/Reporters/IExportProgressReporter.cs new file mode 100644 index 0000000..ffefacb --- /dev/null +++ b/BanjoBotAssets/Reporters/IExportProgressReporter.cs @@ -0,0 +1,14 @@ +using BanjoBotAssets.Exporters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BanjoBotAssets.Reporters +{ + public interface IExportProgressReporter + { + public void Report(ExportProgress progress); + } +} diff --git a/BanjoBotAssets/Reporters/IExportStageReporter.cs b/BanjoBotAssets/Reporters/IExportStageReporter.cs new file mode 100644 index 0000000..0489847 --- /dev/null +++ b/BanjoBotAssets/Reporters/IExportStageReporter.cs @@ -0,0 +1,14 @@ +using BanjoBotAssets.Exporters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BanjoBotAssets.Reporters +{ + public interface IExportStageReporter + { + public void Report(ExportStage stage); + } +} From e5af9f6132ed6da965671cd73b0461476a3c48e6 Mon Sep 17 00:00:00 2001 From: TomatechGames Date: Mon, 25 May 2026 02:36:23 +0100 Subject: [PATCH 4/7] Disable Example reporters by default --- BanjoBotAssets.Console/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BanjoBotAssets.Console/Program.cs b/BanjoBotAssets.Console/Program.cs index 4539e89..8f5258b 100644 --- a/BanjoBotAssets.Console/Program.cs +++ b/BanjoBotAssets.Console/Program.cs @@ -41,8 +41,8 @@ await Host.CreateDefaultBuilder(args) services .AddBanjoServices(); - services.AddSingleton(); - services.AddSingleton(); + //services.AddSingleton(); + //services.AddSingleton(); }) .RunConsoleAsync(o => o.SuppressStatusMessages = true); From 7c5e3464aae1c9291f12d8b8eb3c6ddf0d596bbc Mon Sep 17 00:00:00 2001 From: TomatechGames Date: Mon, 25 May 2026 02:36:40 +0100 Subject: [PATCH 5/7] Fix Image Export Exception --- BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs b/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs index 0782903..120c253 100644 --- a/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs +++ b/BanjoBotAssets/PostExporters/ImageFilesPostExporter.cs @@ -18,6 +18,7 @@ using BanjoBotAssets.Config; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse_Conversion.Textures; +using CUE4Parse_Conversion.Textures.BC; using Microsoft.Extensions.Options; namespace BanjoBotAssets.PostExporters @@ -44,6 +45,8 @@ public async Task ProcessExportsAsync(ExportedAssets exportedAssets, IList Date: Mon, 25 May 2026 15:57:07 +0100 Subject: [PATCH 6/7] Add License Headers to Reporter interface files --- .../Reporters/IExportProgressReporter.cs | 19 ++++++++++++++++++- .../Reporters/IExportStageReporter.cs | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/BanjoBotAssets/Reporters/IExportProgressReporter.cs b/BanjoBotAssets/Reporters/IExportProgressReporter.cs index ffefacb..483ec37 100644 --- a/BanjoBotAssets/Reporters/IExportProgressReporter.cs +++ b/BanjoBotAssets/Reporters/IExportProgressReporter.cs @@ -1,4 +1,21 @@ -using BanjoBotAssets.Exporters; +/* Copyright 2026 Tara "Dino" Cassatt + * + * This file is part of BanjoBotAssets. + * + * BanjoBotAssets is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BanjoBotAssets is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BanjoBotAssets. If not, see . + */ +using BanjoBotAssets.Exporters; using System; using System.Collections.Generic; using System.Linq; diff --git a/BanjoBotAssets/Reporters/IExportStageReporter.cs b/BanjoBotAssets/Reporters/IExportStageReporter.cs index 0489847..687c98b 100644 --- a/BanjoBotAssets/Reporters/IExportStageReporter.cs +++ b/BanjoBotAssets/Reporters/IExportStageReporter.cs @@ -1,4 +1,21 @@ -using BanjoBotAssets.Exporters; +/* Copyright 2026 Tara "Dino" Cassatt + * + * This file is part of BanjoBotAssets. + * + * BanjoBotAssets is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BanjoBotAssets is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BanjoBotAssets. If not, see . + */ +using BanjoBotAssets.Exporters; using System; using System.Collections.Generic; using System.Linq; From 2b4bfa05b5307ad591a4f4643361d6158c901c9b Mon Sep 17 00:00:00 2001 From: TomatechGames Date: Tue, 26 May 2026 03:12:29 +0100 Subject: [PATCH 7/7] Update CUE4Parse-FortniteTypes --- external/CUE4Parse-FortniteTypes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/CUE4Parse-FortniteTypes b/external/CUE4Parse-FortniteTypes index 424118b..72fbc06 160000 --- a/external/CUE4Parse-FortniteTypes +++ b/external/CUE4Parse-FortniteTypes @@ -1 +1 @@ -Subproject commit 424118b4072aaccf64dbb3f6e801860c5945b3a1 +Subproject commit 72fbc06ef0cc31c7ac8778f9c94c435272410426