Skip to content

Commit fd4eb0a

Browse files
authored
Borderlands 3 audio (#649)
1 parent 7fab3e2 commit fd4eb0a

8 files changed

Lines changed: 65 additions & 23 deletions

File tree

FModel/Enums.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,5 +157,5 @@ public enum EAssetCategory : uint
157157
AudioEvent = Media + 5,
158158
Particle = AssetCategoryExtensions.CategoryBase + (9 << 16),
159159
GameSpecific = AssetCategoryExtensions.CategoryBase + (10 << 16),
160-
Borderlands4 = GameSpecific + 1,
160+
Borderlands = GameSpecific + 1,
161161
}

FModel/ViewModels/CUE4ParseViewModel.cs

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using CUE4Parse.GameTypes.KRD.Assets.Exports;
2727
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
2828
using CUE4Parse.GameTypes.Borderlands4.Wwise;
29+
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
2930
using CUE4Parse.MappingsProvider;
3031
using CUE4Parse.UE4.AssetRegistry;
3132
using CUE4Parse.UE4.Assets;
@@ -792,7 +793,7 @@ public void Extract(CancellationToken cancellationToken, GameFile entry, bool ad
792793
var directory = Path.GetDirectoryName(entry.Path) ?? "/FMOD/Desktop/";
793794
foreach (var sound in extractedSounds)
794795
{
795-
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
796+
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
796797
}
797798

798799
break;
@@ -807,7 +808,7 @@ public void Extract(CancellationToken cancellationToken, GameFile entry, bool ad
807808
var medias = WwiseProvider.ExtractBankSounds(wwise);
808809
foreach (var media in medias)
809810
{
810-
SaveAndPlaySound(media.OutputPath, media.Extension, media.Data, saveAudio, updateUi);
811+
SaveAndPlaySound(cancellationToken, media.OutputPath, media.Extension, media.Data, saveAudio, updateUi);
811812
}
812813

813814
break;
@@ -823,7 +824,7 @@ public void Extract(CancellationToken cancellationToken, GameFile entry, bool ad
823824
var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name);
824825
foreach (var sound in extractedSounds)
825826
{
826-
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
827+
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
827828
}
828829

829830
break;
@@ -839,7 +840,7 @@ public void Extract(CancellationToken cancellationToken, GameFile entry, bool ad
839840
var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name);
840841
foreach (var sound in extractedSounds)
841842
{
842-
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
843+
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
843844
}
844845

845846
break;
@@ -854,7 +855,7 @@ public void Extract(CancellationToken cancellationToken, GameFile entry, bool ad
854855
// todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif":
855856
{
856857
var data = Provider.SaveAsset(entry);
857-
SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data, saveAudio, updateUi);
858+
SaveAndPlaySound(cancellationToken, entry.PathWithoutExtension, entry.Extension, data, saveAudio, updateUi);
858859

859860
break;
860861
}
@@ -1114,15 +1115,24 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
11141115
case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource:
11151116
{
11161117
var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath);
1117-
SaveAndPlaySound(audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio, updateUi);
1118+
SaveAndPlaySound(cancellationToken, audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio, updateUi);
1119+
return false;
1120+
}
1121+
case UAkAudioBank when (isNone || saveAudio) && pointer.Object.Value is UAkAudioBank soundBank:
1122+
{
1123+
var extractedSounds = WwiseProvider.ExtractBankSounds(soundBank);
1124+
foreach (var sound in extractedSounds)
1125+
{
1126+
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
1127+
}
11181128
return false;
11191129
}
11201130
case UAkAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEvent audioEvent:
11211131
{
11221132
var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent);
11231133
foreach (var sound in extractedSounds)
11241134
{
1125-
SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
1135+
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
11261136
}
11271137
return false;
11281138
}
@@ -1132,7 +1142,7 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
11321142
var directory = Path.GetDirectoryName(fmodEvent.Owner?.Name) ?? "/FMOD/Desktop/";
11331143
foreach (var sound in extractedSounds)
11341144
{
1135-
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
1145+
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
11361146
}
11371147
return false;
11381148
}
@@ -1142,7 +1152,7 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
11421152
var directory = Path.GetDirectoryName(fmodBank.Owner?.Name) ?? "/FMOD/Desktop/";
11431153
foreach (var sound in extractedSounds)
11441154
{
1145-
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
1155+
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
11461156
}
11471157
return false;
11481158
}
@@ -1161,7 +1171,7 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
11611171
directory = Path.GetDirectoryName(atomObject.Owner.Provider.FixPath(directory));
11621172
foreach (var sound in extractedSounds)
11631173
{
1164-
SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
1174+
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
11651175
}
11661176
return false;
11671177
}
@@ -1181,7 +1191,7 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
11811191
return false;
11821192
}
11831193

1184-
SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio, updateUi);
1194+
SaveAndPlaySound(cancellationToken, TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio, updateUi);
11851195
return false;
11861196
}
11871197
case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset:
@@ -1192,7 +1202,7 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
11921202
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
11931203
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
11941204

1195-
SaveAndPlaySound(audioName, audioFormat, data, saveAudio, updateUi);
1205+
SaveAndPlaySound(cancellationToken, audioName, audioFormat, data, saveAudio, updateUi);
11961206
}
11971207
return false;
11981208
}
@@ -1208,12 +1218,22 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
12081218
var audioName = akMediaAsset.MediaName ?? $"{akAudioEventData.Outer.Name} ({akMediaAsset.ID})";
12091219
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
12101220

1211-
SaveAndPlaySound(audioName, audioFormat, data, saveAudio, updateUi);
1221+
SaveAndPlaySound(cancellationToken, audioName, audioFormat, data, saveAudio, updateUi);
12121222
}
12131223
}
12141224
}
12151225
return false;
12161226
}
1227+
// Borderlands 3
1228+
case UDialogPerformanceData when (isNone || saveAudio) && pointer.Object.Value is UDialogPerformanceData dialogPerformanceData:
1229+
{
1230+
var extractedSounds = WwiseProvider.ExtractDialogBorderlands3(dialogPerformanceData);
1231+
foreach (var sound in extractedSounds)
1232+
{
1233+
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
1234+
}
1235+
return false;
1236+
}
12171237
// Borderlands 4
12181238
case UFaceFXAnimSet when (isNone || saveAudio) && pointer.Object.Value is UFaceFXAnimSet faceFXAnimSet:
12191239
{
@@ -1225,7 +1245,7 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
12251245
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(faceFXAnimData.ID.Name, false);
12261246
foreach (var sound in extractedSounds)
12271247
{
1228-
SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
1248+
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
12291249
}
12301250
}
12311251

@@ -1239,7 +1259,7 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat
12391259
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(eventName, useSoundTag);
12401260
foreach (var sound in extractedSounds)
12411261
{
1242-
SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
1262+
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
12431263
}
12441264
}
12451265

@@ -1379,14 +1399,15 @@ public void Decompile(GameFile entry)
13791399
TabControl.SelectedTab.SetDocumentText(cpp, false, false);
13801400
}
13811401

1382-
private void SaveAndPlaySound(string fullPath, string ext, byte[] data, bool isBulk, bool updateUi)
1402+
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool isBulk, bool updateUi)
13831403
{
13841404
if (fullPath.StartsWith('/')) fullPath = fullPath[1..];
13851405
var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory,
13861406
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}";
13871407

13881408
if (isBulk)
13891409
{
1410+
cancellationToken.ThrowIfCancellationRequested();
13901411
Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/'));
13911412
using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write);
13921413
using (var writer = new BinaryWriter(stream))
@@ -1398,7 +1419,18 @@ private void SaveAndPlaySound(string fullPath, string ext, byte[] data, bool isB
13981419
if (UserSettings.Default.ConvertAudioOnBulkExport)
13991420
{
14001421
AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath);
1401-
savedAudioPath = wavFilePath;
1422+
if (!string.IsNullOrEmpty(wavFilePath))
1423+
{
1424+
savedAudioPath = wavFilePath;
1425+
}
1426+
else if (updateUi)
1427+
{
1428+
FLogger.Append(ELog.Error, () =>
1429+
{
1430+
FLogger.Text("Failed to convert audio to WAV format, aborting extraction.", Constants.WHITE, true);
1431+
});
1432+
return;
1433+
}
14021434
}
14031435

14041436
Log.Information("Successfully saved {FilePath}", savedAudioPath);

FModel/ViewModels/GameFileViewModel.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Windows.Media.Imaging;
88

99
using CUE4Parse.FileProvider.Objects;
10+
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
1011
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
1112
using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets;
1213
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
@@ -257,8 +258,9 @@ or USoundAtomCueSheet or UAkAudioType or UExternalSource or UExternalSourceBank
257258
UNiagaraSystem or UNiagaraScriptBase or UParticleSystem => (EAssetCategory.Particle, EBulkType.None),
258259

259260
// Game specific assets below
260-
UGbxGraphAsset => (EAssetCategory.Borderlands4, EBulkType.Audio), // Borderlands 4
261-
UFaceFXAnimSet when _applicationView.CUE4Parse?.Provider.Versions.Game is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands4, EBulkType.Audio), // Borderlands 4
261+
UBorderlandsDialogObject => (EAssetCategory.Borderlands, EBulkType.None), // Borderlands 3;
262+
UGbxGraphAsset or UDialogScriptData or UDialogPerformanceData => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; Borderlands 3;
263+
UFaceFXAnimSet when _applicationView.CUE4Parse?.Provider.Versions.Game is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4;
262264

263265
_ => (EAssetCategory.All, EBulkType.None),
264266
};
@@ -427,6 +429,11 @@ private Task ResolveByExtensionAsync(EResolveCompute resolve)
427429
bitmap.Dispose();
428430
});
429431
}
432+
// Game specific extensions below
433+
case "ace": // Borderlands 3
434+
case "ncs": // Borderlands 4
435+
AssetCategory = EAssetCategory.Borderlands;
436+
break;
430437
default:
431438
AssetCategory = EAssetCategory.All; // just so it sets resolved
432439
break;

FModel/Views/Resources/Colors.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
<SolidColorBrush x:Key="AudioBrush" Color="MediumSeaGreen" />
2727
<SolidColorBrush x:Key="SoundBankBrush" Color="DarkSeaGreen" />
28-
<SolidColorBrush x:Key="AudioEventBrush" Color="DarkTurquoise" />
28+
<SolidColorBrush x:Key="AudioEventBrush" Color="LightSeaGreen" />
2929

3030
<SolidColorBrush x:Key="VideoBrush" Color="IndianRed" />
3131
<SolidColorBrush x:Key="DataTableBrush" Color="SteelBlue" />
@@ -47,6 +47,7 @@
4747
<SolidColorBrush x:Key="CssBrush" Color="MediumPurple" />
4848
<SolidColorBrush x:Key="GitBrush" Color="Coral" />
4949
<SolidColorBrush x:Key="CsvBrush" Color="ForestGreen" />
50+
<SolidColorBrush x:Key="AIBrush" Color="LightGray" />
5051

5152
<!-- For specific games -->
5253
<SolidColorBrush x:Key="BorderlandsBrush" Color="Yellow"></SolidColorBrush>

FModel/Views/Resources/Converters/FileToGeometryConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
8989

9090
EAssetCategory.ByteCode => ("CodeIcon", "CodeBrush"),
9191

92-
EAssetCategory.Borderlands4 => ("BorderlandsIcon", "BorderlandsBrush"),
92+
EAssetCategory.Borderlands => ("BorderlandsIcon", "BorderlandsBrush"),
9393

9494
_ => ("AssetIcon", "NeutralBrush")
9595
};

FModel/Views/Resources/Converters/FolderToGeometryConverter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
1919

2020
var (geometry, brush) = folderName switch
2121
{
22+
"ai" => ("AIIcon", "AIBrush"),
2223
"textures" or "texture" or "ui" or "icons" or "umgassets" or "hud" or "hdri" or "tex" => ("TextureIconAlt", "TextureBrush"),
2324
"config" or "tags" => ("ConfigIcon", "ConfigBrush"),
2425
"audio" or "wwiseaudio" or "wwise" or "fmod" or "sound" or "sounds" or "cue" => ("AudioIconAlt", "AudioBrush"),

FModel/Views/Resources/Icons.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
<Geometry x:Key="JavaScriptIcon">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</Geometry>
9898
<Geometry x:Key="CssIcon">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</Geometry>
9999
<Geometry x:Key="CsvIcon">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</Geometry>
100+
<Geometry x:Key="AIIcon">M12,2A2,2 0 0,1 14,4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0 0,1 21,14H22A1,1 0 0,1 23,15V18A1,1 0 0,1 22,19H21V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V19H2A1,1 0 0,1 1,18V15A1,1 0 0,1 2,14H3A7,7 0 0,1 10,7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0 0,1 12,2M7.5,13A2.5,2.5 0 0,0 5,15.5A2.5,2.5 0 0,0 7.5,18A2.5,2.5 0 0,0 10,15.5A2.5,2.5 0 0,0 7.5,13M16.5,13A2.5,2.5 0 0,0 14,15.5A2.5,2.5 0 0,0 16.5,18A2.5,2.5 0 0,0 19,15.5A2.5,2.5 0 0,0 16.5,13Z</Geometry>
100101

101102
<!-- For specific games-->
102103
<Geometry x:Key="BorderlandsIcon">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</Geometry>

0 commit comments

Comments
 (0)