From 570bdd6180f607febf13379b79797fbd40605911 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Fri, 17 Oct 2025 14:41:17 -0600 Subject: [PATCH 1/2] Add save audio option to context menus --- FModel/Enums.cs | 3 +- FModel/MainWindow.xaml | 24 ++++++++++++++ FModel/MainWindow.xaml.cs | 13 ++++++++ FModel/ViewModels/CUE4ParseViewModel.cs | 33 +++++++++++-------- .../Commands/RightClickMenuCommand.cs | 8 +++++ FModel/ViewModels/Commands/TabCommand.cs | 8 ++++- FModel/Views/Resources/Resources.xaml | 9 +++++ FModel/Views/SearchView.xaml | 17 +++++++++- 8 files changed, 98 insertions(+), 17 deletions(-) diff --git a/FModel/Enums.cs b/FModel/Enums.cs index 59700533..f59efb9d 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -114,5 +114,6 @@ public enum EBulkType Textures = 1 << 2, Meshes = 1 << 3, Skeletons = 1 << 4, - Animations = 1 << 5 + Animations = 1 << 5, + Audio = 1 << 6 } diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 9021fa43..b91c3b96 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -376,6 +376,15 @@ + + + + + + + + + @@ -608,6 +617,21 @@ + + + + + + + + + + + + + + + diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index e1d71bc3..0c44727a 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -238,6 +238,19 @@ public partial class MainWindow } } + private async void OnFolderAudioClick(object sender, RoutedEventArgs e) + { + if (AssetsFolderName.SelectedItem is TreeItem folder) + { + await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.AudioFolder(cancellationToken, folder); }); + FLogger.Append(ELog.Information, () => + { + FLogger.Text("Successfully saved audio from ", Constants.WHITE); + FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true); + }); + } + } + private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is not TreeItem folder) return; diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 356c2d45..de60c4fe 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -583,6 +583,9 @@ public class CUE4ParseViewModel : ViewModel public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder) => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto)); + public void AudioFolder(CancellationToken cancellationToken, TreeItem folder) + => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto)); + public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None) { Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", entry.Path); @@ -594,6 +597,7 @@ public class CUE4ParseViewModel : ViewModel var updateUi = !HasFlag(bulk, EBulkType.Auto); var saveProperties = HasFlag(bulk, EBulkType.Properties); var saveTextures = HasFlag(bulk, EBulkType.Textures); + var saveAudio = HasFlag(bulk, EBulkType.Audio); switch (entry.Extension) { case "uasset": @@ -738,7 +742,7 @@ public class CUE4ParseViewModel : ViewModel var medias = WwiseProvider.ExtractBankSounds(wwise); foreach (var media in medias) { - SaveAndPlaySound(media.OutputPath, media.Extension, media.Data); + SaveAndPlaySound(media.OutputPath, media.Extension, media.Data, saveAudio); } break; @@ -753,7 +757,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); + SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data, saveAudio); break; } @@ -868,6 +872,7 @@ public class CUE4ParseViewModel : ViewModel var isNone = bulk == EBulkType.None; var updateUi = !HasFlag(bulk, EBulkType.Auto); var saveTextures = HasFlag(bulk, EBulkType.Textures); + var saveAudio = HasFlag(bulk, EBulkType.Audio); var pointer = new FPackageIndex(pkg, index + 1).ResolvedObject; if (pointer?.Object is null) return false; @@ -938,37 +943,37 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.AddImage(sourceFile.SubstringAfterLast('/'), false, bitmap, false, updateUi); return false; } - case UAkAudioEvent when isNone && pointer.Object.Value is UAkAudioEvent audioEvent: + case UAkAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEvent audioEvent: { var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent); foreach (var sound in extractedSounds) { - SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data); + SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio); } return false; } - case UFMODEvent when isNone && pointer.Object.Value is UFMODEvent fmodEvent: + case UFMODEvent when (isNone || saveAudio) && pointer.Object.Value is UFMODEvent fmodEvent: { var extractedSounds = FmodProvider.ExtractEventSounds(fmodEvent); var directory = Path.GetDirectoryName(fmodEvent.Owner?.Name) ?? "/FMOD/Desktop/"; foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } return false; } - case UFMODBank when isNone && pointer.Object.Value is UFMODBank fmodBank: + case UFMODBank when (isNone || saveAudio) && pointer.Object.Value is UFMODBank fmodBank: { var extractedSounds = FmodProvider.ExtractBankSounds(fmodBank); var directory = Path.GetDirectoryName(fmodBank.Owner?.Name) ?? "/FMOD/Desktop/"; foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } return false; } - case UAkMediaAssetData when isNone: - case USoundWave when isNone: + case UAkMediaAssetData when isNone || saveAudio: + case USoundWave when isNone || saveAudio: { var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; pointer.Object.Value.Decode(shouldDecompress, out var audioFormat, out var data); @@ -979,7 +984,7 @@ public class CUE4ParseViewModel : ViewModel return false; } - SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data); + SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio); return false; } case UWorld when isNone && UserSettings.Default.PreviewWorlds: @@ -1100,13 +1105,13 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(cpp, false, false); } - private void SaveAndPlaySound(string fullPath, string ext, byte[] data) + private void SaveAndPlaySound(string fullPath, string ext, byte[] data, bool isBulk) { if (fullPath.StartsWith('/')) fullPath = fullPath[1..]; var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory, UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}"; - if (!UserSettings.Default.IsAutoOpenSounds) + if (!UserSettings.Default.IsAutoOpenSounds || isBulk) { Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/')); using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write); @@ -1188,4 +1193,4 @@ public class CUE4ParseViewModel : ViewModel { return (a & b) == b; } -} \ No newline at end of file +} diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index fdd2fb6b..b27c388f 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -92,6 +92,14 @@ public class RightClickMenuCommand : ViewModelCommand contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi); } break; + case "Assets_Save_Audio": + foreach (var entry in entries) + { + Thread.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Audio | updateUi); + } + break; } }); } diff --git a/FModel/ViewModels/Commands/TabCommand.cs b/FModel/ViewModels/Commands/TabCommand.cs index a7911782..86a83f10 100644 --- a/FModel/ViewModels/Commands/TabCommand.cs +++ b/FModel/ViewModels/Commands/TabCommand.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System.Windows; using AdonisUI.Controls; using FModel.Framework; using FModel.Services; @@ -58,6 +58,12 @@ public class TabCommand : ViewModelCommand _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations); }); break; + case "Asset_Save_Audio": + await _threadWorkerView.Begin(cancellationToken => + { + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Audio); + }); + break; case "Open_Properties": if (tabViewModel.Header == "New Tab" || tabViewModel.Document == null) return; Helper.OpenWindow(tabViewModel.Header + " (Properties)", () => diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index fd2eea5f..6957f118 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -909,6 +909,15 @@ + + + + + + + + + diff --git a/FModel/Views/SearchView.xaml b/FModel/Views/SearchView.xaml index 5bfacacc..317a8e2a 100644 --- a/FModel/Views/SearchView.xaml +++ b/FModel/Views/SearchView.xaml @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + From de34f9c5ce51404e9cb464362220b33884080f7a Mon Sep 17 00:00:00 2001 From: Asval Date: Tue, 21 Oct 2025 17:09:49 +0200 Subject: [PATCH 2/2] removed auto open sounds --- FModel/MainWindow.xaml | 16 ---------------- FModel/MainWindow.xaml.cs | 2 +- FModel/Settings/UserSettings.cs | 7 ------- FModel/ViewModels/CUE4ParseViewModel.cs | 2 +- 4 files changed, 2 insertions(+), 25 deletions(-) diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index b91c3b96..f70fc5af 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -103,9 +103,6 @@ - - @@ -862,19 +859,6 @@ - - - - - - - diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 0c44727a..b6a957fa 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -246,7 +246,7 @@ public partial class MainWindow FLogger.Append(ELog.Information, () => { FLogger.Text("Successfully saved audio from ", Constants.WHITE); - FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true); + FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true); }); } } diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index ef577f0b..66d2aec8 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -140,13 +140,6 @@ namespace FModel.Settings set => SetProperty(ref _lastOpenedSettingTab, value); } - private bool _isAutoOpenSounds = true; - public bool IsAutoOpenSounds - { - get => _isAutoOpenSounds; - set => SetProperty(ref _isAutoOpenSounds, value); - } - private bool _isLoggerExpanded = true; public bool IsLoggerExpanded { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index de60c4fe..e6299b44 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -1111,7 +1111,7 @@ public class CUE4ParseViewModel : ViewModel var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory, UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}"; - if (!UserSettings.Default.IsAutoOpenSounds || isBulk) + if (isBulk) { Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/')); using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write);