Update WwiseProvider for FDeferredByteData, counting failed exports

Co-authored-by: Masusder <59669685+Masusder@users.noreply.github.com>
This commit is contained in:
LongerWarrior 2026-03-22 18:43:58 +02:00
parent fd4051b163
commit 8e66371fca
6 changed files with 59 additions and 48 deletions

@ -1 +1 @@
Subproject commit dc0f8183f6551f9b5bccc8aecee65807362ef297
Subproject commit df263631dbb96469f8f0316d74f4904c51f439ed

View File

@ -454,13 +454,6 @@ namespace FModel.Settings
set => SetProperty(ref _cameraMode, value);
}
private int _wwiseMaxBnkPrefetch;
public int WwiseMaxBnkPrefetch
{
get => _wwiseMaxBnkPrefetch;
set => SetProperty(ref _wwiseMaxBnkPrefetch, value);
}
private int _previewMaxTextureSize = 1024;
public int PreviewMaxTextureSize
{

View File

@ -163,6 +163,7 @@ public class CUE4ParseViewModel : ViewModel
public ConcurrentBag<string> UnknownExtensions = [];
public int ExportedCount;
public int FailedExportCount;
public CUE4ParseViewModel()
{
@ -324,7 +325,7 @@ public class CUE4ParseViewModel : ViewModel
}
Provider.Initialize();
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory, UserSettings.Default.WwiseMaxBnkPrefetch));
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory));
_fmodProviderLazy = new Lazy<FModProvider>(() => new FModProvider(Provider, UserSettings.Default.GameDirectory));
_criWareProviderLazy = new Lazy<CriWareProvider>(() => new CriWareProvider(Provider, UserSettings.Default.GameDirectory));
Log.Information($"{Provider.Versions.Game} ({Provider.Versions.Platform}) | Archives: x{Provider.UnloadedVfs.Count} | AES: x{Provider.RequiredKeys.Count} | Loose Files: x{Provider.Files.Count}");
@ -601,7 +602,7 @@ public class CUE4ParseViewModel : ViewModel
}
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder, EBulkType bulk)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk));
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk));
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs));
@ -807,13 +808,13 @@ public class CUE4ParseViewModel : ViewModel
case "pck":
{
var archive = entry.CreateReader();
var wwise = new WwiseReader(archive);
var wwise = new WwiseReader(archive, new WwiseGameFileSource(entry));
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
var medias = WwiseProvider.ExtractBankSounds(wwise);
foreach (var media in medias)
{
SaveAndPlaySound(cancellationToken, media.OutputPath, media.Extension, media.Data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, media.OutputPath, media.Extension, media.Data?.GetData() ?? [], saveAudio, updateUi);
}
break;
@ -1120,7 +1121,8 @@ public class CUE4ParseViewModel : ViewModel
case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource:
{
var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath);
SaveAndPlaySound(cancellationToken, audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio, updateUi);
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
SaveAndPlaySound(cancellationToken, outputPath, "wem", externalSource.Data?.WemFile?.GetData() ?? [], saveAudio, updateUi);
return false;
}
case UAkAudioBank when (isNone || saveAudio) && pointer.Object.Value is UAkAudioBank soundBank:
@ -1128,7 +1130,7 @@ public class CUE4ParseViewModel : ViewModel
var extractedSounds = WwiseProvider.ExtractBankSounds(soundBank);
foreach (var sound in extractedSounds)
{
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
}
return false;
}
@ -1137,7 +1139,7 @@ public class CUE4ParseViewModel : ViewModel
var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent);
foreach (var sound in extractedSounds)
{
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
}
return false;
}
@ -1202,12 +1204,13 @@ public class CUE4ParseViewModel : ViewModel
case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset:
{
var audioName = akMediaAsset.MediaName ?? akMediaAsset.Name;
if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true)
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
if (akMediaAsset.CurrentMediaAssetData?.ResolvedObject?.Object?.Value is UAkMediaAssetData akMediaAssetData)
{
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
SaveAndPlaySound(cancellationToken, audioName, audioFormat, data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
}
return false;
}
@ -1216,14 +1219,15 @@ public class CUE4ParseViewModel : ViewModel
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
foreach (var mediaIndex in akAudioEventData.MediaList)
{
if (mediaIndex.TryLoad<UAkMediaAsset>(out var akMediaAsset))
if (mediaIndex.ResolvedObject?.Object?.Value is UAkMediaAsset akMediaAsset)
{
if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true)
if (akMediaAsset.CurrentMediaAssetData?.ResolvedObject?.Object?.Value is UAkMediaAssetData akMediaAssetData)
{
var audioName = akMediaAsset.MediaName ?? $"{akAudioEventData.Outer.Name} ({akMediaAsset.ID})";
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
SaveAndPlaySound(cancellationToken, audioName, audioFormat, data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
}
}
}
@ -1235,7 +1239,7 @@ public class CUE4ParseViewModel : ViewModel
var extractedSounds = WwiseProvider.ExtractDialogBorderlands3(dialogPerformanceData);
foreach (var sound in extractedSounds)
{
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
}
return false;
}
@ -1245,12 +1249,13 @@ public class CUE4ParseViewModel : ViewModel
if (Provider.Versions.Game is not EGame.GAME_Borderlands4)
return false;
var ownerDirectory = WwiseProvider.GetOwnerDirectory(faceFXAnimSet);
foreach (var faceFXAnimData in faceFXAnimSet.FaceFXAnimDataList)
{
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(faceFXAnimData.ID.Name, false);
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(ownerDirectory, faceFXAnimData.ID.Name, false);
foreach (var sound in extractedSounds)
{
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
}
}
@ -1259,12 +1264,13 @@ public class CUE4ParseViewModel : ViewModel
// Borderlands 4
case UGbxGraphAsset when (isNone || saveAudio) && pointer.Object.Value is UGbxGraphAsset gbxGraphAsset:
{
var ownerDirectory = WwiseProvider.GetOwnerDirectory(gbxGraphAsset);
foreach (var (eventName, useSoundTag) in GbxAudioUtil.GetAndClearEvents())
{
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(eventName, useSoundTag);
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(ownerDirectory, eventName, useSoundTag);
foreach (var sound in extractedSounds)
{
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
}
}
@ -1404,28 +1410,33 @@ public class CUE4ParseViewModel : ViewModel
TabControl.SelectedTab.SetDocumentText(cpp, false, false);
}
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool isBulk, bool updateUi)
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool saveAudio, bool updateUi)
{
if (fullPath.StartsWith('/')) fullPath = fullPath[1..];
var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory,
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}";
if (isBulk)
if (saveAudio)
{
cancellationToken.ThrowIfCancellationRequested();
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();
}
var directory = Path.GetDirectoryName(savedAudioPath);
Directory.CreateDirectory(directory);
bool conversionSuccess = true;
if (UserSettings.Default.ConvertAudioOnBulkExport)
{
conversionSuccess = AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath);
if (conversionSuccess) savedAudioPath = wavFilePath;
if (AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath))
savedAudioPath = wavFilePath;
else
{
Interlocked.Increment(ref FailedExportCount);
return;
}
}
else
{
using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write);
stream.Write(data);
}
Interlocked.Increment(ref ExportedCount);
@ -1475,6 +1486,7 @@ public class CUE4ParseViewModel : ViewModel
}
else
{
Interlocked.Increment(ref FailedExportCount);
Log.Error("{FileName} could not be saved", export.Name);
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true));
}
@ -1509,6 +1521,7 @@ public class CUE4ParseViewModel : ViewModel
}
else
{
Interlocked.Increment(ref FailedExportCount);
Log.Error("{FileName} could not be exported", entry.Name);
if (updateUi)
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true));

View File

@ -74,6 +74,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
};
Interlocked.Exchange(ref contextViewModel.CUE4Parse.ExportedCount, 0);
Interlocked.Exchange(ref contextViewModel.CUE4Parse.FailedExportCount, 0);
await _threadWorkerView.Begin(cancellationToken =>
{
if (action is EAction.Show)
@ -167,15 +168,24 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
FLogger.Link(directory, Path.Exists(path) ? path : basePath, true);
});
}
else
else if (contextViewModel.CUE4Parse.FailedExportCount == 0)
{
// Not an error because folder simply might not contain type of asset user is trying to save
FLogger.Append(ELog.Warning, () =>
{
FLogger.Text($"Failed to export any {fileType} from {directory}", Constants.WHITE, true);
FLogger.Text($"Failed to find any {fileType} in {directory}", Constants.WHITE, true);
});
}
if (contextViewModel.CUE4Parse.FailedExportCount > 0)
{
FLogger.Append(ELog.Error, () =>
{
FLogger.Text($"Failed to export {contextViewModel.CUE4Parse.FailedExportCount} {fileType} from {directory}", Constants.WHITE, true);
});
}
Interlocked.Exchange(ref contextViewModel.CUE4Parse.ExportedCount, 0);
Interlocked.Exchange(ref contextViewModel.CUE4Parse.FailedExportCount, 0);
}
}

View File

@ -376,8 +376,7 @@ public class TabItem : ViewModel
public void SaveImage() => SaveImage(SelectedImage, true);
private void SaveImage(TabImage image, bool updateUi)
{
if (image == null)
return;
if (image is null) return;
var path = Path.Combine(UserSettings.Default.TextureDirectory, UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", image.ExportName).Replace('\\', '/');
@ -394,6 +393,7 @@ public class TabItem : ViewModel
private void SaveImage(TabImage image, string path)
{
if (image.ImageBuffer is null) return;
using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
}
@ -427,6 +427,7 @@ public class TabItem : ViewModel
}
else
{
Interlocked.Increment(ref ApplicationService.ApplicationView.CUE4Parse.FailedExportCount);
Log.Error("{FileName} could not be saved", fileName);
if (updateUi)
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileName}'", Constants.WHITE, true));

View File

@ -43,7 +43,6 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -249,19 +248,14 @@
Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="20" Grid.Column="0" Text="Max Wwise Bank (.BNK) Prefetch" VerticalAlignment="Center" Margin="0 0 0 5" />
<Slider Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="5" TickPlacement="None" Minimum="0" Maximum="2048" Ticks="0,8,32,128,256,512,1024,2048"
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
Value="{Binding WwiseMaxBnkPrefetch, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
<TextBlock Grid.Row="21"
<TextBlock Grid.Row="20"
Grid.Column="0"
Text="CRIWARE Decryption Key"
VerticalAlignment="Center"
Margin="0 0 0 10" />
<TextBox x:Name="CriwareKeyBox"
Grid.Row="21"
Grid.Row="20"
Grid.Column="2"
Grid.ColumnSpan="5"
Margin="0 5 0 10"