From 40996973ebb61977265451dff8cd111e3cabbcfc Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Tue, 3 Feb 2026 00:35:13 +0200 Subject: [PATCH 1/8] Code Vein 2, Infinity Nikki update Ashes of Creation dbc extraction --- CUE4Parse | 2 +- FModel/ViewModels/CUE4ParseViewModel.cs | 44 +++++++++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 68655624..4935ec79 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 6865562475cb0661843438c4a20bd8748ba9c5e2 +Subproject commit 4935ec79a1d2c801b0735d84cb42322475231359 diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 45e0dad5..b4db2ad5 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -20,6 +20,7 @@ using CUE4Parse.FileProvider; using CUE4Parse.FileProvider.Objects; using CUE4Parse.FileProvider.Vfs; using CUE4Parse.GameTypes.Aion2.Objects; +using CUE4Parse.GameTypes.AoC.Objects; using CUE4Parse.GameTypes.AshEchoes.FileProvider; using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise; using CUE4Parse.GameTypes.KRD.Assets.Exports; @@ -657,11 +658,16 @@ public class CUE4ParseViewModel : ViewModel break; } - case "dat" when Provider.ProjectName.Equals("Aion2", StringComparison.OrdinalIgnoreCase): - { - ProcessAion2DatFile(entry, updateUi, saveProperties); - break; - } + case "dat" when Provider.Versions.Game is EGame.GAME_Aion2: + { + ProcessAion2DatFile(entry, updateUi, saveProperties); + break; + } + case "dbc" when Provider.Versions.Game is EGame.GAME_AshesOfCreation: + { + ProcessCacheDBFile(entry, updateUi, saveProperties); + break; + } case "upluginmanifest": case "code-workspace": case "projectstore": @@ -690,6 +696,7 @@ public class CUE4ParseViewModel : ViewModel case "usda": case "ocio": case "data" when Provider.ProjectName is "OakGame": + case "scss": case "ini": case "txt": case "log": @@ -705,6 +712,7 @@ public class CUE4ParseViewModel : ViewModel case "css": case "csv": case "pem": + case "tsv": case "tps": case "tgc": // State of Decay 2 case "cpp": @@ -969,6 +977,30 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(datfile, Formatting.Indented), saveProperties, updateUi); } } + + // Ashhes of Creation + void ProcessCacheDBFile(GameFile entry, bool updateUi, bool saveProperties) + { + var data = entry.Read(); + var dbc = new FAoCDBCReader(data, Provider.MappingsForGame, Provider.Versions); + for (var i = 0; i < dbc.Chunks.Length; i++) + { + if (!dbc.TryReadChunk(i, out var category, out var files)) + { + Log.Warning("Couldn't read {i} chuck in AoC CacheDB", i); + continue; + } + var fileName = Path.ChangeExtension(category, ".json"); + var directory = Path.Combine(UserSettings.Default.PropertiesDirectory, + UserSettings.Default.KeepDirectoryStructure ? entry.Directory : "", entry.Name, fileName).Replace('\\', '/'); + + Directory.CreateDirectory(directory.SubstringBeforeLast('/')); + + File.WriteAllText(directory, JsonConvert.SerializeObject(files, Formatting.Indented)); + } + + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(dbc, Formatting.Indented), saveProperties, updateUi); + } } public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType) @@ -1076,7 +1108,7 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.AddImage(sourceFile.SubstringAfterLast('/'), false, bitmap, false, updateUi); return false; } - // The Dark Pictures Anthology: House of Ashes + // The Dark Pictures Anthology case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource: { var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath); From 508dcb4c7a87f849cce3aa960692ef25d72dce10 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Sun, 8 Feb 2026 12:49:50 +0100 Subject: [PATCH 2/8] Wwise media assets + .adx fallback --- CUE4Parse | 2 +- FModel/ViewModels/AudioPlayerViewModel.cs | 10 +++++++ FModel/ViewModels/CUE4ParseViewModel.cs | 36 ++++++++++++++++++++++- FModel/ViewModels/GameFileViewModel.cs | 6 ++-- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 4935ec79..ad9836e6 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 4935ec79a1d2c801b0735d84cb42322475231359 +Subproject commit ad9836e6bc106bedfd525d1a63c46e297f42388c diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index 9ffada58..edd717b4 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -617,6 +617,16 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable _ => throw new NotSupportedException() }; + if (wavData.Length is 0) + { + if (TryConvert(out var wavFilePathFallback)) + { + var newAudioFallback = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePathFallback)); + Replace(newAudioFallback); + return true; + } + } + string wavFilePath = Path.Combine( UserSettings.Default.AudioDirectory, SelectedAudioFile.FilePath.TrimStart('/')); diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index b4db2ad5..77910234 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -1108,7 +1108,7 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.AddImage(sourceFile.SubstringAfterLast('/'), false, bitmap, false, updateUi); return false; } - // The Dark Pictures Anthology + // Supermassive Games (for example - The Dark Pictures Anthology: House of Ashes etc.) case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource: { var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath); @@ -1166,6 +1166,10 @@ public class CUE4ParseViewModel : ViewModel case UAkMediaAssetData when isNone || saveAudio: case USoundWave when isNone || saveAudio: { + // If UAkMediaAsset exists in the same package it should be used to handle the audio instead (because it contains actual audio name) + if (pointer.Object.Value is UAkMediaAssetData dataObj && dataObj.Outer is UAkMediaAsset) + return false; + var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; pointer.Object.Value.Decode(shouldDecompress, out var audioFormat, out var data); var hasAf = !string.IsNullOrEmpty(audioFormat); @@ -1178,6 +1182,36 @@ public class CUE4ParseViewModel : ViewModel SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio); return false; } + case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset: + { + var audioName = akMediaAsset.MediaName; + if (akMediaAsset.CurrentMediaAssetData?.TryLoad(out var akMediaAssetData) is true) + { + var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed; + akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data); + + SaveAndPlaySound(audioName, audioFormat, data, saveAudio); + } + return false; + } + case UAkAudioEventData when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEventData akAudioEventData: + { + var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed; + foreach (var mediaIndex in akAudioEventData.MediaList) + { + if (mediaIndex.TryLoad(out var akMediaAsset)) + { + if (akMediaAsset.CurrentMediaAssetData?.TryLoad(out var akMediaAssetData) is true) + { + var audioName = akMediaAsset.MediaName ?? $"{akAudioEventData.Outer.Name} ({akMediaAsset.ID})"; + akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data); + + SaveAndPlaySound(audioName, audioFormat, data, saveAudio); + } + } + } + return false; + } case UWorld when isNone && UserSettings.Default.PreviewWorlds: case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch { diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index 3e97b075..f7c6d30d 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -235,12 +235,14 @@ public class GameFileViewModel(GameFile asset) : ViewModel UObjectRedirector => (EAssetCategory.ObjectRedirector, EBulkType.None), UPhysicalMaterial => (EAssetCategory.PhysicalMaterial, EBulkType.None), - USoundAtomCue or UAkAudioEvent or USoundCue or UFMODEvent => (EAssetCategory.AudioEvent, EBulkType.Audio), + USoundAtomCue or UAkAudioEvent or USoundCue or UFMODEvent + or UAkAssetData or UAkAssetPlatformData => (EAssetCategory.AudioEvent, EBulkType.Audio), UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank => (EAssetCategory.SoundBank, EBulkType.Audio), UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomCueSheet - or USoundAtomCueSheet or UAkAudioType or UExternalSource or UExternalSourceBank => (EAssetCategory.Audio, EBulkType.Audio), + or USoundAtomCueSheet or UAkAudioType or UExternalSource or UExternalSourceBank + or UAkMediaAsset => (EAssetCategory.Audio, EBulkType.Audio), UFileMediaSource => (EAssetCategory.Video, EBulkType.None), UFont or UFontFace or USMGLocaleFontUMG => (EAssetCategory.Font, EBulkType.None), From 87fe6ed09dc197f2904cc62cc04ead6b11f6cd6d Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Tue, 10 Feb 2026 01:10:24 +0200 Subject: [PATCH 3/8] bump cue4parse --- CUE4Parse | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CUE4Parse b/CUE4Parse index ad9836e6..9596944a 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit ad9836e6bc106bedfd525d1a63c46e297f42388c +Subproject commit 9596944ac2e491ffe29c0c961a27e4fa6b07fb88 From dee4ecab9c74f8718f470b5d1e02d9a3d04a5cfc Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 11 Feb 2026 23:57:59 +1100 Subject: [PATCH 4/8] Prevents ImGui assert from AddFontFromFileTTF --- FModel/Framework/ImGuiController.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/FModel/Framework/ImGuiController.cs b/FModel/Framework/ImGuiController.cs index 9181b4fb..b62fde21 100644 --- a/FModel/Framework/ImGuiController.cs +++ b/FModel/Framework/ImGuiController.cs @@ -66,9 +66,19 @@ public class ImGuiController : IDisposable var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini")); io.NativePtr->IniFilename = (byte*)iniFileNamePtr; } - FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale); - FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale); - FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale); + + // If not found, Fallback to default ImGui Font + var normalPath = @"C:\Windows\Fonts\segoeui.ttf"; + var boldPath = @"C:\Windows\Fonts\segoeuib.ttf"; + var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf"; + + if (File.Exists(normalPath)) + FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale); + if (File.Exists(boldPath)) + FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale); + if (File.Exists(semiBoldPath)) + FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale); + io.Fonts.AddFontDefault(); io.Fonts.Build(); // Build font atlas From 40ed646dcdd2e6b0c69c441941030a0dfb07d7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=85=E1=B4=8A=CA=9F=E1=B4=8F=CA=803x=E1=B4=A2?= =?UTF-8?q?=E1=B4=8F?= <89230676+djlorenzouasset@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:03:50 +0100 Subject: [PATCH 5/8] Fix HTML tags in some Quests DisplayName. (#640) * Fix HTML tags in some Quests DisplayName. * New Backups & Hotfixes API * fix backups being null --------- Co-authored-by: GhostScissors <79089473+GhostScissors@users.noreply.github.com> --- FModel/Creator/Bases/FN/BaseQuest.cs | 3 ++- FModel/ViewModels/ApiEndpointViewModel.cs | 4 ++-- ...ralApiEndpoint.cs => DillyApiEndpoints.cs} | 22 ++++++++++++++++--- .../ApiEndpoints/FModelApiEndpoint.cs | 14 ------------ .../ApiEndpoints/Models/FModelResponse.cs | 4 +--- FModel/ViewModels/BackupManagerViewModel.cs | 6 ++--- FModel/ViewModels/CUE4ParseViewModel.cs | 2 +- 7 files changed, 28 insertions(+), 27 deletions(-) rename FModel/ViewModels/ApiEndpoints/{FortniteCentralApiEndpoint.cs => DillyApiEndpoints.cs} (52%) diff --git a/FModel/Creator/Bases/FN/BaseQuest.cs b/FModel/Creator/Bases/FN/BaseQuest.cs index ef3159aa..6d2e9b87 100644 --- a/FModel/Creator/Bases/FN/BaseQuest.cs +++ b/FModel/Creator/Bases/FN/BaseQuest.cs @@ -242,7 +242,8 @@ public class BaseQuest : BaseIcon { _informationPaint.TextSize = 25; _informationPaint.Typeface = Utils.Typefaces.Bundle; - Utils.DrawMultilineText(c, DisplayName, Width - padding, 0, SKTextAlign.Left, + + Utils.DrawMultilineText(c, Utils.RemoveHtmlTags(DisplayName).Replace(" ", " "), Width - padding, 0, SKTextAlign.Left, new SKRect(x, y + padding, maxX, Height - padding * 1.5f), _informationPaint, out _); } diff --git a/FModel/ViewModels/ApiEndpointViewModel.cs b/FModel/ViewModels/ApiEndpointViewModel.cs index 6276567a..249f450b 100644 --- a/FModel/ViewModels/ApiEndpointViewModel.cs +++ b/FModel/ViewModels/ApiEndpointViewModel.cs @@ -17,7 +17,7 @@ public class ApiEndpointViewModel public FortniteApiEndpoint FortniteApi { get; } public ValorantApiEndpoint ValorantApi { get; } - public FortniteCentralApiEndpoint CentralApi { get; } + public DillyApiEndpoint DillyApi { get; } public EpicApiEndpoint EpicApi { get; } public FModelApiEndpoint FModelApi { get; } public GitHubApiEndpoint GitHubApi { get; } @@ -27,7 +27,7 @@ public class ApiEndpointViewModel { FortniteApi = new FortniteApiEndpoint(_client); ValorantApi = new ValorantApiEndpoint(_client); - CentralApi = new FortniteCentralApiEndpoint(_client); + DillyApi = new DillyApiEndpoint(_client); EpicApi = new EpicApiEndpoint(_client); FModelApi = new FModelApiEndpoint(_client); GitHubApi = new GitHubApiEndpoint(_client); diff --git a/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs similarity index 52% rename from FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs rename to FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs index 0bb587bb..926061bc 100644 --- a/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs @@ -2,18 +2,34 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using FModel.Framework; +using FModel.ViewModels.ApiEndpoints.Models; using RestSharp; using Serilog; namespace FModel.ViewModels.ApiEndpoints; -public class FortniteCentralApiEndpoint : AbstractApiProvider +public class DillyApiEndpoint : AbstractApiProvider { - public FortniteCentralApiEndpoint(RestClient client) : base(client) { } + private Backup[] _backups; + + public DillyApiEndpoint(RestClient client) : base(client) { } + + public async Task GetBackupsAsync(CancellationToken token) + { + var request = new FRestRequest($"https://export-service-new.dillyapis.com/v1/backups"); + 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.Data; + } + + public Backup[] GetBackups(CancellationToken token) + { + return _backups ??= GetBackupsAsync(token).GetAwaiter().GetResult(); + } public async Task>> GetHotfixesAsync(CancellationToken token, string language = "en") { - var request = new FRestRequest("https://fortnitecentral.genxgames.gg/api/v1/hotfixes") + var request = new FRestRequest("https://api.fortniteapi.com/v1/cloudstorage/hotfixes") { Interceptors = [_interceptor] }; diff --git a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs index c12679bd..db2882ed 100644 --- a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs @@ -26,7 +26,6 @@ public class FModelApiEndpoint : AbstractApiProvider private News _news; private Info _infos; private Donator[] _donators; - private Backup[] _backups; private Game _game; private readonly IDictionary _communityDesigns = new Dictionary(); private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; @@ -60,19 +59,6 @@ public class FModelApiEndpoint : AbstractApiProvider return _donators ??= GetDonatorsAsync().GetAwaiter().GetResult(); } - public async Task GetBackupsAsync(CancellationToken token, string gameName) - { - var request = new FRestRequest($"https://api.fmodel.app/v1/backups/{gameName}"); - 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.Data; - } - - public Backup[] GetBackups(CancellationToken token, string gameName) - { - return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult(); - } - public async Task GetGamesAsync(CancellationToken token, string gameName) { var request = new FRestRequest($"https://api.fmodel.app/v1/games/{gameName}"); diff --git a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs index 6c9b8dc4..67c6f6a4 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs @@ -19,10 +19,8 @@ public class News [DebuggerDisplay("{" + nameof(FileName) + "}")] public class Backup { - [J] public string GameName { get; private set; } [J] public string FileName { get; private set; } - [J] public string DownloadUrl { get; private set; } - [J] public long FileSize { get; private set; } + [J] public string Url { get; private set; } } public class Donator diff --git a/FModel/ViewModels/BackupManagerViewModel.cs b/FModel/ViewModels/BackupManagerViewModel.cs index 0010b761..ce2c0356 100644 --- a/FModel/ViewModels/BackupManagerViewModel.cs +++ b/FModel/ViewModels/BackupManagerViewModel.cs @@ -48,13 +48,13 @@ public class BackupManagerViewModel : ViewModel { await _threadWorkerView.Begin(cancellationToken => { - var backups = _apiEndpointView.FModelApi.GetBackups(cancellationToken, _gameName); + var backups = _apiEndpointView.DillyApi.GetBackups(cancellationToken); if (backups == null) return; Application.Current.Dispatcher.Invoke(() => { foreach (var backup in backups) Backups.Add(backup); - SelectedBackup = Backups.LastOrDefault(); + SelectedBackup = Backups.FirstOrDefault(); }); }); } @@ -93,7 +93,7 @@ public class BackupManagerViewModel : ViewModel await _threadWorkerView.Begin(_ => { var fullPath = Path.Combine(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), SelectedBackup.FileName); - _apiEndpointView.DownloadFile(SelectedBackup.DownloadUrl, fullPath); + _apiEndpointView.DownloadFile(SelectedBackup.Url, fullPath); SaveCheck(fullPath, SelectedBackup.FileName, "downloaded", "download"); }); } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 77910234..b2ad6705 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -527,7 +527,7 @@ public class CUE4ParseViewModel : ViewModel if (!Provider.ProjectName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask; return Task.Run(() => { - var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(CancellationToken.None, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); + var hotfixes = ApplicationService.ApiEndpointView.DillyApi.GetHotfixes(CancellationToken.None, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); if (hotfixes == null) return; Provider.Internationalization.Override(hotfixes); From 6b3a4ae785dd44bff6a640eead98a81356865748 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:05:52 +0100 Subject: [PATCH 6/8] Borderlands 4 - GbxAudio support (#645) --- CUE4Parse | 2 +- FModel/Enums.cs | 2 ++ FModel/ViewModels/CUE4ParseViewModel.cs | 33 +++++++++++++++++++ FModel/ViewModels/GameFileViewModel.cs | 12 +++++++ FModel/Views/Resources/Colors.xaml | 8 +++++ .../Converters/FileToGeometryConverter.cs | 7 ++++ FModel/Views/Resources/Icons.xaml | 8 +++++ 7 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CUE4Parse b/CUE4Parse index 9596944a..1774159e 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 9596944ac2e491ffe29c0c961a27e4fa6b07fb88 +Subproject commit 1774159e9c85f4b42623cbd6e920b99c3b60b9f1 diff --git a/FModel/Enums.cs b/FModel/Enums.cs index c2f21f6e..8abb2e60 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -156,4 +156,6 @@ public enum EAssetCategory : uint SoundBank = Media + 4, AudioEvent = Media + 5, Particle = AssetCategoryExtensions.CategoryBase + (9 << 16), + GameSpecific = AssetCategoryExtensions.CategoryBase + (10 << 16), + Borderlands4 = GameSpecific + 1, } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index b2ad6705..56f5cdff 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -24,6 +24,8 @@ using CUE4Parse.GameTypes.AoC.Objects; using CUE4Parse.GameTypes.AshEchoes.FileProvider; using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise; using CUE4Parse.GameTypes.KRD.Assets.Exports; +using CUE4Parse.GameTypes.Borderlands4.Assets.Exports; +using CUE4Parse.GameTypes.Borderlands4.Wwise; using CUE4Parse.MappingsProvider; using CUE4Parse.UE4.AssetRegistry; using CUE4Parse.UE4.Assets; @@ -1212,6 +1214,37 @@ public class CUE4ParseViewModel : ViewModel } return false; } + // Borderlands 4 + case UFaceFXAnimSet when (isNone || saveAudio) && pointer.Object.Value is UFaceFXAnimSet faceFXAnimSet: + { + if (Provider.Versions.Game is not EGame.GAME_Borderlands4) + return false; + + foreach (var faceFXAnimData in faceFXAnimSet.FaceFXAnimDataList) + { + var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(faceFXAnimData.ID.Name, false); + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio); + } + } + + return false; + } + // Borderlands 4 + case UGbxGraphAsset when (isNone || saveAudio) && pointer.Object.Value is UGbxGraphAsset gbxGraphAsset: + { + foreach (var (eventName, useSoundTag) in GbxAudioUtil.GetAndClearEvents()) + { + var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(eventName, useSoundTag); + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio); + } + } + + return false; + } case UWorld when isNone && UserSettings.Default.PreviewWorlds: case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch { diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index f7c6d30d..8ae82022 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -7,6 +7,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using CUE4Parse.FileProvider.Objects; +using CUE4Parse.GameTypes.Borderlands4.Assets.Exports; using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets; using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise; using CUE4Parse.GameTypes.SMG.UE4.Assets.Objects; @@ -42,6 +43,7 @@ using CUE4Parse.UE4.Objects.PhysicsEngine; using CUE4Parse.UE4.Objects.RigVM; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Objects.UObject.Editor; +using CUE4Parse.UE4.Versions; using CUE4Parse.Utils; using CUE4Parse_Conversion.Textures; @@ -249,6 +251,10 @@ public class GameFileViewModel(GameFile asset) : ViewModel UNiagaraSystem or UNiagaraScriptBase or UParticleSystem => (EAssetCategory.Particle, EBulkType.None), + // Game specific assets below + UGbxGraphAsset => (EAssetCategory.Borderlands4, EBulkType.Audio), // Borderlands 4 + UFaceFXAnimSet when _applicationView.CUE4Parse?.Provider.Versions.Game is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands4, EBulkType.Audio), // Borderlands 4 + _ => (EAssetCategory.All, EBulkType.None), }; @@ -333,6 +339,12 @@ public class GameFileViewModel(GameFile asset) : ViewModel case "log": case "pem": case "xml": + case "gitignore": + case "html": + case "css": + case "js": + case "data": + case "csv": AssetCategory = EAssetCategory.Data; break; case "ushaderbytecode": diff --git a/FModel/Views/Resources/Colors.xaml b/FModel/Views/Resources/Colors.xaml index e2753c4d..cf0f2e86 100644 --- a/FModel/Views/Resources/Colors.xaml +++ b/FModel/Views/Resources/Colors.xaml @@ -42,4 +42,12 @@ + + + + + + + + diff --git a/FModel/Views/Resources/Converters/FileToGeometryConverter.cs b/FModel/Views/Resources/Converters/FileToGeometryConverter.cs index 437eefe1..e8a605d9 100644 --- a/FModel/Views/Resources/Converters/FileToGeometryConverter.cs +++ b/FModel/Views/Resources/Converters/FileToGeometryConverter.cs @@ -79,11 +79,18 @@ public class FileToGeometryConverter : IMultiValueConverter "function" => ("FunctionIcon", "NeutralBrush"), "bin" => ("DataTableIcon", "BinaryBrush"), "xml" => ("XmlIcon", "JsonXmlBrush"), + "gitignore" => ("GitIcon", "GitBrush"), + "html" => ("HtmlIcon", "HtmlBrush"), + "js" => ("JavaScriptIcon", "JavaScriptBrush"), + "css" => ("CssIcon", "CssBrush"), + "csv" => ("CsvIcon", "CsvBrush"), _ => ("DataTableIcon", "NeutralBrush") }, EAssetCategory.ByteCode => ("CodeIcon", "CodeBrush"), + EAssetCategory.Borderlands4 => ("BorderlandsIcon", "BorderlandsBrush"), + _ => ("AssetIcon", "NeutralBrush") }; diff --git a/FModel/Views/Resources/Icons.xaml b/FModel/Views/Resources/Icons.xaml index 93b90cc7..909cefb7 100644 --- a/FModel/Views/Resources/Icons.xaml +++ b/FModel/Views/Resources/Icons.xaml @@ -92,4 +92,12 @@ M4,3C2.89,3 2,3.89 2,5V15A2,2 0 0,0 4,17H12V22L15,19L18,22V17H20A2,2 0 0,0 22,15V8L22,6V5A2,2 0 0,0 20,3H16V3H4M12,5L15,7L18,5V8.5L21,10L18,11.5V15L15,13L12,15V11.5L9,10L12,8.5V5M4,5H9V7H4V5M4,9H7V11H4V9M4,13H9V15H4V13Z M289.718,1208.22 L283.795,1202.28 C283.404,1201.89 282.768,1201.89 282.376,1202.28 C281.984,1202.68 282,1203.35 282,1204 L282,1207 L266,1207 L266,1204 C266,1203.35 266.016,1202.68 265.624,1202.28 C265.232,1201.89 264.597,1201.89 264.205,1202.28 L258.282,1208.22 C258.073,1208.43 257.983,1208.71 257.998,1208.98 C257.983,1209.26 258.073,1209.54 258.282,1209.75 L264.205,1215.69 C264.597,1216.08 265.232,1216.08 265.624,1215.69 C266.016,1215.29 266,1214.39 266,1214 L266,1211 L282,1211 L282,1214 C282,1214.65 281.984,1215.29 282.376,1215.69 C282.768,1216.08 283.404,1216.08 283.795,1215.69 L289.718,1209.75 C289.927,1209.54 290.017,1209.26 290.002,1208.98 C290.017,1208.71 289.927,1208.43 289.718,1208.22 M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6.12,15.5L9.86,19.24L11.28,17.83L8.95,15.5L11.28,13.17L9.86,11.76L6.12,15.5M17.28,15.5L13.54,11.76L12.12,13.17L14.45,15.5L12.12,17.83L13.54,19.24L17.28,15.5Z + M2.6,10.59L8.38,4.8L10.07,6.5C9.83,7.35 10.22,8.28 11,8.73V14.27C10.4,14.61 10,15.26 10,16A2,2 0 0,0 12,18A2,2 0 0,0 14,16C14,15.26 13.6,14.61 13,14.27V9.41L15.07,11.5C15,11.65 15,11.82 15,12A2,2 0 0,0 17,14A2,2 0 0,0 19,12A2,2 0 0,0 17,10C16.82,10 16.65,10 16.5,10.07L13.93,7.5C14.19,6.57 13.71,5.55 12.78,5.16C12.35,5 11.9,4.96 11.5,5.07L9.8,3.38L10.59,2.6C11.37,1.81 12.63,1.81 13.41,2.6L21.4,10.59C22.19,11.37 22.19,12.63 21.4,13.41L13.41,21.4C12.63,22.19 11.37,22.19 10.59,21.4L2.6,13.41C1.81,12.63 1.81,11.37 2.6,10.59Z + M12,17.56L16.07,16.43L16.62,10.33H9.38L9.2,8.3H16.8L17,6.31H7L7.56,12.32H14.45L14.22,14.9L12,15.5L9.78,14.9L9.64,13.24H7.64L7.93,16.43L12,17.56M4.07,3H19.93L18.5,19.2L12,21L5.5,19.2L4.07,3Z + M3,3H21V21H3V3M7.73,18.04C8.13,18.89 8.92,19.59 10.27,19.59C11.77,19.59 12.8,18.79 12.8,17.04V11.26H11.1V17C11.1,17.86 10.75,18.08 10.2,18.08C9.62,18.08 9.38,17.68 9.11,17.21L7.73,18.04M13.71,17.86C14.21,18.84 15.22,19.59 16.8,19.59C18.4,19.59 19.6,18.76 19.6,17.23C19.6,15.82 18.79,15.19 17.35,14.57L16.93,14.39C16.2,14.08 15.89,13.87 15.89,13.37C15.89,12.96 16.2,12.64 16.7,12.64C17.18,12.64 17.5,12.85 17.79,13.37L19.1,12.5C18.55,11.54 17.77,11.17 16.7,11.17C15.19,11.17 14.22,12.13 14.22,13.4C14.22,14.78 15.03,15.43 16.25,15.95L16.67,16.13C17.45,16.47 17.91,16.68 17.91,17.26C17.91,17.74 17.46,18.09 16.76,18.09C15.93,18.09 15.45,17.66 15.09,17.06L13.71,17.86Z + M5,3L4.35,6.34H17.94L17.5,8.5H3.92L3.26,11.83H16.85L16.09,15.64L10.61,17.45L5.86,15.64L6.19,14H2.85L2.06,18L9.91,21L18.96,18L20.16,11.97L20.4,10.76L21.94,3H5Z + M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2M15 16L13 20H10L12 16H9V11H15V16M13 9V3.5L18.5 9H13Z + + + M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6 ZM12,11A3,3 0 1,0 12,17A3,3 0 0,0 12,11 ZM12,12.5L14,16H13L12,14.5L11,16H10L12,12.5Z From 1c48a27f8ebb3771ed12d240d8f14edef6871754 Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Tue, 17 Feb 2026 01:51:22 +0200 Subject: [PATCH 7/8] InitOodle update, High on Life 2 and Borderlands 4 Audio support --- CUE4Parse | 2 +- FModel/ViewModels/ApplicationViewModel.cs | 24 ++++--------------- .../Resources/Controls/AvalonEditor.xaml.cs | 2 +- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 1774159e..a451f8e3 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 1774159e9c85f4b42623cbd6e920b99c3b60b9f1 +Subproject commit a451f8e3140c806da8b438e9b44dafe4c1c0a37b diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index 0c8839b2..7822aa70 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -268,31 +268,15 @@ public class ApplicationViewModel : ViewModel public static async Task InitOodle() { - if (File.Exists(OodleHelper.OODLE_DLL_NAME_OLD)) - { - try - { - File.Delete(OodleHelper.OODLE_DLL_NAME_OLD); - } - catch { /* ignored */} - } - - var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME_OLD); + var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_NAME_OLD); if (!File.Exists(oodlePath)) { - oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME); - } - - if (!File.Exists(oodlePath)) - { - if (!await OodleHelper.DownloadOodleDllAsync(oodlePath)) - { - FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Oodle", Constants.WHITE, true)); - return; - } + oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_NAME_CURRENT); } OodleHelper.Initialize(oodlePath); + if (OodleHelper.Instance is null) + FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Oodle", Constants.WHITE, true)); } public static async Task InitZlib() diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index db81c600..9aebc30c 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -27,7 +27,7 @@ public partial class AvalonEditor private readonly Dictionary> _savedCarets = new(); private NavigationList _caretsOffsets { - get => MyAvalonEditor.Document != null + get => MyAvalonEditor.Document != null && MyAvalonEditor.Document.FileName != null ? _savedCarets.GetOrAdd(MyAvalonEditor.Document.FileName, () => new NavigationList()) : new NavigationList(); } From 500fa59f852ff95f5e80edc807862fe49c0cb1bd Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:40:15 +0100 Subject: [PATCH 8/8] Option for bulk audio conversion (#646) --- CUE4Parse | 2 +- FModel/Settings/UserSettings.cs | 7 +++ FModel/ViewModels/AudioPlayerViewModel.cs | 2 +- FModel/ViewModels/CUE4ParseViewModel.cs | 59 ++++++++++++------- FModel/ViewModels/Commands/MenuCommand.cs | 2 +- FModel/ViewModels/GameFileViewModel.cs | 12 +++- .../Controls/Rtb/CustomRichTextBox.cs | 6 ++ FModel/Views/SettingsView.xaml | 36 ++++++++--- 8 files changed, 91 insertions(+), 35 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index a451f8e3..1861416d 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit a451f8e3140c806da8b438e9b44dafe4c1c0a37b +Subproject commit 1861416d2ab765cde91eec6f46305fc316dfae6e diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index d3d83f3b..f6a77b19 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -266,6 +266,13 @@ namespace FModel.Settings set => SetProperty(ref _readShaderMaps, value); } + private bool _convertAudioOnBulkExport; + public bool ConvertAudioOnBulkExport + { + get => _convertAudioOnBulkExport; + set => SetProperty(ref _convertAudioOnBulkExport, value); + } + private IDictionary _perDirectory = new Dictionary(); public IDictionary PerDirectory { diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index edd717b4..d12b2e7b 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -655,7 +655,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable } private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath); - private bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath) + public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath) { wavFilePath = string.Empty; var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe"); diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 56f5cdff..18e74bab 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -780,7 +780,7 @@ public class CUE4ParseViewModel : ViewModel case "bank": { var archive = entry.CreateReader(); - if (!FModProvider.TryLoadBank(archive, entry.NameWithoutExtension, out var fmodReader)) + if (!FmodProvider.TryLoadBank(archive, entry.NameWithoutExtension, out var fmodReader)) { Log.Error($"Failed to load FMOD bank {entry.Path}"); break; @@ -792,7 +792,7 @@ public class CUE4ParseViewModel : ViewModel var directory = Path.GetDirectoryName(entry.Path) ?? "/FMOD/Desktop/"; foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi); } break; @@ -807,7 +807,7 @@ public class CUE4ParseViewModel : ViewModel var medias = WwiseProvider.ExtractBankSounds(wwise); foreach (var media in medias) { - SaveAndPlaySound(media.OutputPath, media.Extension, media.Data, saveAudio); + SaveAndPlaySound(media.OutputPath, media.Extension, media.Data, saveAudio, updateUi); } break; @@ -823,7 +823,7 @@ public class CUE4ParseViewModel : ViewModel var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi); } break; @@ -839,7 +839,7 @@ public class CUE4ParseViewModel : ViewModel var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name); foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi); } break; @@ -854,7 +854,7 @@ public class CUE4ParseViewModel : ViewModel // todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif": { var data = Provider.SaveAsset(entry); - SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data, saveAudio); + SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data, saveAudio, updateUi); break; } @@ -1114,7 +1114,7 @@ public class CUE4ParseViewModel : ViewModel case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource: { var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath); - SaveAndPlaySound(audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio); + SaveAndPlaySound(audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio, updateUi); return false; } case UAkAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEvent audioEvent: @@ -1122,7 +1122,7 @@ public class CUE4ParseViewModel : ViewModel var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent); foreach (var sound in extractedSounds) { - SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi); } return false; } @@ -1132,7 +1132,7 @@ public class CUE4ParseViewModel : ViewModel var directory = Path.GetDirectoryName(fmodEvent.Owner?.Name) ?? "/FMOD/Desktop/"; foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi); } return false; } @@ -1142,7 +1142,7 @@ public class CUE4ParseViewModel : ViewModel var directory = Path.GetDirectoryName(fmodBank.Owner?.Name) ?? "/FMOD/Desktop/"; foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi); } return false; } @@ -1161,7 +1161,7 @@ public class CUE4ParseViewModel : ViewModel directory = Path.GetDirectoryName(atomObject.Owner.Provider.FixPath(directory)); foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi); } return false; } @@ -1181,7 +1181,7 @@ public class CUE4ParseViewModel : ViewModel return false; } - SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio); + SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio, updateUi); return false; } case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset: @@ -1192,7 +1192,7 @@ public class CUE4ParseViewModel : ViewModel var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed; akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data); - SaveAndPlaySound(audioName, audioFormat, data, saveAudio); + SaveAndPlaySound(audioName, audioFormat, data, saveAudio, updateUi); } return false; } @@ -1208,7 +1208,7 @@ public class CUE4ParseViewModel : ViewModel var audioName = akMediaAsset.MediaName ?? $"{akAudioEventData.Outer.Name} ({akMediaAsset.ID})"; akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data); - SaveAndPlaySound(audioName, audioFormat, data, saveAudio); + SaveAndPlaySound(audioName, audioFormat, data, saveAudio, updateUi); } } } @@ -1225,7 +1225,7 @@ public class CUE4ParseViewModel : ViewModel var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(faceFXAnimData.ID.Name, false); foreach (var sound in extractedSounds) { - SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi); } } @@ -1239,7 +1239,7 @@ public class CUE4ParseViewModel : ViewModel var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(eventName, useSoundTag); foreach (var sound in extractedSounds) { - SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi); } } @@ -1379,7 +1379,7 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(cpp, false, false); } - private void SaveAndPlaySound(string fullPath, string ext, byte[] data, bool isBulk) + private void SaveAndPlaySound(string fullPath, string ext, byte[] data, bool isBulk, bool updateUi) { if (fullPath.StartsWith('/')) fullPath = fullPath[1..]; var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory, @@ -1389,9 +1389,28 @@ public class CUE4ParseViewModel : ViewModel { Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/')); using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write); - using var writer = new BinaryWriter(stream); - writer.Write(data); - writer.Flush(); + using (var writer = new BinaryWriter(stream)) + { + writer.Write(data); + writer.Flush(); + } + + if (UserSettings.Default.ConvertAudioOnBulkExport) + { + AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath); + savedAudioPath = wavFilePath; + } + + Log.Information("Successfully saved {FilePath}", savedAudioPath); + if (updateUi) + { + FLogger.Append(ELog.Information, () => + { + FLogger.Text("Successfully saved ", Constants.WHITE); + FLogger.Link(Path.GetFileName(savedAudioPath), savedAudioPath, true); + }); + } + return; } diff --git a/FModel/ViewModels/Commands/MenuCommand.cs b/FModel/ViewModels/Commands/MenuCommand.cs index 1c3e7dbb..fe223f88 100644 --- a/FModel/ViewModels/Commands/MenuCommand.cs +++ b/FModel/ViewModels/Commands/MenuCommand.cs @@ -65,7 +65,7 @@ public class MenuCommand : ViewModelCommand Process.Start(new ProcessStartInfo { FileName = Constants.DISCORD_LINK, UseShellExecute = true }); break; case "ToolBox_Clear_Logs": - FLogger.Logger.Text = string.Empty; + FLogger.ClearLogs(); break; case "ToolBox_Open_Output_Directory": Process.Start(new ProcessStartInfo { FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true }); diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index 8ae82022..afd53544 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -21,6 +21,7 @@ using CUE4Parse.UE4.Assets.Exports.CustomizableObject; using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Exports.Engine.Font; using CUE4Parse.UE4.Assets.Exports.Fmod; +using CUE4Parse.UE4.Assets.Exports.FMod; using CUE4Parse.UE4.Assets.Exports.Foliage; using CUE4Parse.UE4.Assets.Exports.Internationalization; using CUE4Parse.UE4.Assets.Exports.LevelSequence; @@ -240,6 +241,10 @@ public class GameFileViewModel(GameFile asset) : ViewModel USoundAtomCue or UAkAudioEvent or USoundCue or UFMODEvent or UAkAssetData or UAkAssetPlatformData => (EAssetCategory.AudioEvent, EBulkType.Audio), + UFMODBankLookup => (EAssetCategory.Data, EBulkType.None), + + UFMODBus or UFMODSnapshot or UFMODSnapshotReverb or UFMODVCA => (EAssetCategory.Audio, EBulkType.None), + UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank => (EAssetCategory.SoundBank, EBulkType.Audio), UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomCueSheet @@ -320,7 +325,8 @@ public class GameFileViewModel(GameFile asset) : ViewModel private Task ResolveByExtensionAsync(EResolveCompute resolve) { Resolved |= EResolveCompute.Preview; - switch (Asset.Extension) + var lowercaseExtension = Asset.Extension.ToLowerInvariant(); + switch (lowercaseExtension) { case "uproject": case "uefnproject": @@ -393,7 +399,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel stream.Position = 0; SKBitmap bitmap; - if (Asset.Extension == "svg") + if (lowercaseExtension == "svg") { var svg = new SKSvg(); svg.Load(stream); @@ -415,7 +421,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel bitmap = SKBitmap.Decode(stream); } - using var image = bitmap.Encode(Asset.Extension == "jpg" ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100); + using var image = bitmap.Encode(lowercaseExtension == "jpg" ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100); SetPreviewImage(image); bitmap.Dispose(); diff --git a/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs b/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs index c480a4bf..8b995c01 100644 --- a/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs +++ b/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs @@ -155,6 +155,12 @@ public class FLogger : ITextFormatter { new TextRange(document.ContentStart, document.ContentEnd).Text = text; } + + public static void ClearLogs() + { + Logger.Document.Blocks.Clear(); + _previous = 0; + } } public class CustomRichTextBox : RichTextBox diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index 3869882d..450f97a3 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -43,6 +43,7 @@ + @@ -157,7 +158,8 @@ + IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 0" + Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> @@ -207,7 +209,8 @@ + IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}" + Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> + IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10" + Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> + IsChecked="{Binding ReadShaderMaps, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10" + Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> + IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10" + Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> - - + + + + -