Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2f910fc
BinInterp: Parse WwiseBank action HIRC type
henbagle Feb 6, 2025
9dc3f55
BinInterp: Fix a bunch of WwiseBank issues
henbagle May 14, 2025
72ef107
BinInterp: Add out var overloads for basic MakeNode methods
henbagle May 14, 2025
d7bafcf
BinInterp: Add go to referenced offset in bin interp treeview
henbagle May 15, 2025
a25cf99
BinInterp: Break up BinaryInterpreterScans.cs into several more files
henbagle May 15, 2025
56b8286
BinInterp: Refactor node creation methods into BinaryNodeFactory stat…
henbagle May 18, 2025
f8790c4
Update Wwiser, BinInterp: Scan STMG chunk, Hirc State item on some ve…
henbagle Jun 8, 2025
b22ecba
Correct parse of HircState ParameterId
henbagle Jun 9, 2025
c8b41c7
Implement Wwiser.NET into LEC, Soundpanel
henbagle Jun 23, 2025
a9aafe8
Soundpanel work better, less code
henbagle Jun 23, 2025
abf84a7
HIRC hex editor working without HIRCNotableItems
henbagle Jun 24, 2025
604955f
Remove old wwisebank parsing code from WwiseBankScans
henbagle Jun 24, 2025
899955d
Merge branch 'refactor-bininterpscans' into WwiserMerge
henbagle Jun 24, 2025
b8e0f7f
Soundpanel: Show BinInterp scan of HIRC Item in hex editor
henbagle Jun 24, 2025
27cb78f
Soundpanel HIRC Item Editor: Remove bottom status bar
henbagle Jul 5, 2025
e0cb5e0
WwiseGraphEditor ported to Wwiser.NET
henbagle Jul 10, 2025
d1d1dc6
Remove old WwiseBank ObjectBinary, only Wwiser
henbagle Jul 10, 2025
47d5ea6
Remove WwiseParser submodule
henbagle Jul 11, 2025
fe40436
Merge branch 'Beta' into WwiserMerge
henbagle Aug 19, 2025
47d295c
Fix build?
henbagle Aug 19, 2025
c3729fb
BinInterp: Parse WwiseBank ENVS, misc fixes. Handle ObjectBinary seri…
henbagle Dec 2, 2025
5e63894
BinInterp: Several more Wwise fixes, update Wwiser.NET submodule
henbagle Dec 20, 2025
410f6e4
Fix bank scans, update wwiser submodule
henbagle Jan 20, 2026
bfcd6d8
Merge branch 'Beta' into WwiserMerge
henbagle Jan 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
[submodule "LegendaryExplorer/submodules/WwiseTools"]
path = LegendaryExplorer/submodules/WwiseTools
url = https://github.com/ME3Tweaks/WwiseTools.git
[submodule "LegendaryExplorer/submodules/WwiseParser"]
path = LegendaryExplorer/submodules/WwiseParser
url = https://github.com/ME3Tweaks/WwiseParser.git
[submodule "LegendaryExplorer/submodules/Wwiser.NET"]
path = LegendaryExplorer/submodules/Wwiser.NET
url = https://github.com/ME3Tweaks/Wwiser.NET
2 changes: 0 additions & 2 deletions LegendaryExplorer/LegendaryExplorer.sln
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaveFormRendererLib", "Wave
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WwiseTools_DotNet6", "submodules\WwiseTools\WwiseTools_DotNet6\WwiseTools_DotNet6.csproj", "{BF251A45-7189-4E2C-AA08-0308F0E309A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WwiseParserLib", "submodules\WwiseParser\WwiseParserLib\WwiseParserLib.csproj", "{DA7BCC8E-034B-4AA6-BBB4-BAA6CB4F6FB2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "submodules", "submodules", "{947C0083-A3FF-4CEC-912C-BB65A11C6EEC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wwiser.NET", "Wwiser.NET", "{1F845EFA-0145-46AD-86FE-84C7DAAA5842}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@
<ProjectReference Include="..\..\SharedProjects\Be.Windows.Forms.HexBox\Be.Windows.Forms.HexBox.csproj" />
<ProjectReference Include="..\..\SharedProjects\HexConverterWPF\HexConverterWPF.csproj" />
<ProjectReference Include="..\LegendaryExplorerCore\LegendaryExplorerCore.csproj" />
<ProjectReference Include="..\submodules\WwiseParser\WwiseParserLib\WwiseParserLib.csproj" />
<ProjectReference Include="..\submodules\Wwiser.NET\ME3Tweaks.Wwiser\ME3Tweaks.Wwiser.csproj" />
<ProjectReference Include="..\submodules\Wwiser.NET\BinarySerializer\BinarySerializer\BinarySerializer.csproj" />
<ProjectReference Include="..\submodules\WwiseTools\WwiseTools_DotNet6\WwiseTools_DotNet6.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using ME3Tweaks.Wwiser.Model.Hierarchy;
using ME3Tweaks.Wwiser.Model.Hierarchy.Enums;
using AudioStreamHelper = LegendaryExplorer.UnrealExtensions.AudioStreamHelper;
using HIRCDisplayObject = LegendaryExplorer.UserControls.ExportLoaderControls.HIRCDisplayObject;
using HIRCDisplayObject = LegendaryExplorer.UserControls.ExportLoaderControls.Soundpanel.HIRCDisplayObject;

namespace LegendaryExplorer.SharedUI.Converters
{
Expand Down Expand Up @@ -36,14 +38,11 @@ public class HIRCMediaFetchTypeConverter : IValueConverter
// parameter is allowed class type for visibility
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
uint i = (uint)value;
return i switch
if (value is HircItemContainer { Type.Value: HircType.Sound, Item: Sound snd})
{
0 => $"Embedded",
1 => $"Streamed",
2 => $"Streamed with prefetch",
_ => $"Unknown playback fetch type: {value}"
};
return Enum.GetName(typeof(StreamType.StreamTypeInner), snd.BankSourceData.StreamType.Value);
}
return "No media";
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
Expand All @@ -55,10 +54,14 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu
[ValueConversion(typeof(byte), typeof(string))]
public class HIRCObjectTypeConverter : IValueConverter
{
// parameter is allowed class type for visibility
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return AudioStreamHelper.GetHircObjTypeString((byte)value);
if (value is HircItemContainer { Type: { } hircType })
{
return Enum.GetName(typeof(HircType), hircType.Value);
}

return "";
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
Expand All @@ -70,14 +73,11 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu
[ValueConversion(typeof(int), typeof(Visibility))]
public class HIRCObjectTypeVisibilityConverter : IValueConverter
{
// parameter is allowed class type for visibility
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null && value != null)
if (parameter is HircType parameterType && value is HIRCDisplayObject hdo)
{
int iparameter = int.Parse((string)parameter);
HIRCDisplayObject ho = (HIRCDisplayObject)value;
return iparameter == ho.ObjType ? Visibility.Visible : Visibility.Collapsed;
return parameterType == hdo.Item.Type.Value ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
xmlns:exportLoaderControls="clr-namespace:LegendaryExplorer.UserControls.ExportLoaderControls"
xmlns:assetDbFilters="clr-namespace:LegendaryExplorer.Tools.AssetDatabase.Filters"
xmlns:materialEditor="clr-namespace:LegendaryExplorer.UserControls.ExportLoaderControls.MaterialEditor"
xmlns:soundpanel="clr-namespace:LegendaryExplorer.UserControls.ExportLoaderControls.Soundpanel"
Loaded="AssetDB_Loaded"
Closing="AssetDB_Closing"
AllowDrop="True"
Expand Down Expand Up @@ -1074,7 +1075,7 @@
</TabControl>
</StackPanel>
<ToggleButton x:Name="btn_LinePlaybackToggle" Content="Toggle Line Playback" Margin="5" Click="btn_LinePlaybackToggle_Click" IsChecked="False" MaxHeight="30" ClickMode="Release" DockPanel.Dock="Bottom"/>
<exportLoaderControls:Soundpanel x:Name="SoundpanelWPF_ADB" PlayBackOnlyMode="True" />
<soundpanel:Soundpanel x:Name="SoundpanelWPF_ADB" PlayBackOnlyMode="True" />
</DockPanel>
</Grid>
</TabItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
xmlns:exportLoaderControls="clr-namespace:LegendaryExplorer.UserControls.ExportLoaderControls"
xmlns:dialogueEditor="clr-namespace:LegendaryExplorer.DialogueEditor"
xmlns:sharedUi="clr-namespace:LegendaryExplorer.SharedUI"
xmlns:soundpanel="clr-namespace:LegendaryExplorer.UserControls.ExportLoaderControls.Soundpanel"
mc:Ignorable="d"
Loaded="DialogueEditorWPF_Loaded"
Closing="DialogueEditorWPF_Closing"
Expand Down Expand Up @@ -1395,11 +1396,11 @@
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" x:Name="SoundpanelFemaleControl" Margin="4,0">
<TextBlock>Female</TextBlock>
<exportLoaderControls:Soundpanel x:Name="SoundpanelWPF_F" MiniPlayerMode="True" />
<soundpanel:Soundpanel x:Name="SoundpanelWPF_F" MiniPlayerMode="True" />
</StackPanel>
<StackPanel Grid.Row="1" x:Name="SoundpanelMaleControl" Margin="4,0">
<TextBlock>Male</TextBlock>
<exportLoaderControls:Soundpanel x:Name="SoundpanelWPF_M" MiniPlayerMode="True" />
<soundpanel:Soundpanel x:Name="SoundpanelWPF_M" MiniPlayerMode="True" />
</StackPanel>
</Grid>
</TabItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace LegendaryExplorer.Tools.PackageDumper
{
public class TreeNode
{
public BinaryInterpreterWPF.NodeType Tag { get; set; }
public NodeType Tag { get; set; }
public string Text { get; set; }
public string Name { get; set; }

Expand Down Expand Up @@ -80,25 +80,25 @@ public void PrintPretty(string indent, StreamWriter str, bool last)
indent += "| ";
}
str.Write(Text);
if (Children.Count > 1000 && Tag == BinaryInterpreterWPF.NodeType.ArrayProperty)
if (Children.Count > 1000 && Tag == NodeType.ArrayProperty)
{
str.Write($" > 1000, ({Children.Count}) suppressed.");
return;
}

if (Tag == BinaryInterpreterWPF.NodeType.ArrayProperty && (Text.Contains("LookupTable") || Text.Contains("CompressedTrackOffsets")))
if (Tag == NodeType.ArrayProperty && (Text.Contains("LookupTable") || Text.Contains("CompressedTrackOffsets")))
{
str.Write(" - suppressed by data dumper.");
return;
}
for (int i = 0; i < Children.Count; i++)
{
if (Children[i].Tag == BinaryInterpreterWPF.NodeType.None)
if (Children[i].Tag == NodeType.None)
{
continue;
}
str.Write("\n");
Children[i].PrintPretty(indent, str, i == Children.Count - 1 || (i == Children.Count - 2 && Children[Children.Count - 1].Tag == BinaryInterpreterWPF.NodeType.None));
Children[i].PrintPretty(indent, str, i == Children.Count - 1 || (i == Children.Count - 2 && Children[Children.Count - 1].Tag == NodeType.None));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,5 +372,46 @@ public static void ReplaceAudioFromWwiseEncodedFile(string filePath = null, Expo
}
}
}

public static void DumpAllWwiseBanks(PackageEditorWindow pe)
{
var games = new[] { MEGame.ME2, MEGame.ME3, MEGame.LE2, MEGame.LE3 };
var outputFolder = Path.Combine(AppDirectories.ObjectDatabasesFolder, "WwiseBankExport");
Task.Run(() =>
{
pe.SetBusy($"Exporting Wwise Banks");
foreach (var game in games)
{
var outputGameFolder = Path.Combine(outputFolder, game.ToString());
Directory.CreateDirectory(outputGameFolder);
var found = new HashSet<string>();
var allPackages = MELoadedFiles.GetFilesLoadedInGame(game).ToList();
int numDone = 0;
foreach (var f in allPackages)
{
pe.BusyText = $"Processing file {++numDone}/{allPackages.Count} in {game}";
using var package = MEPackageHandler.OpenMEPackage(f.Value);

foreach (var exp in package.Exports.Where(x => x.ClassName == "WwiseBank" && !x.IsDefaultObject))
{
if (found.Contains(exp.MemoryFullPath)) continue;
found.Add(exp.MemoryFullPath);
var fileName = exp.MemoryFullPath + ".bnk";

var data = new MemoryStream(exp.GetBinaryData());
var binSkip = game.IsGame3() ? 0x10 : 0x18;
data.Skip(binSkip);

using FileStream fs = new FileStream(Path.Combine(outputGameFolder, fileName), FileMode.Create);
data.CopyToEx(fs, (int)data.Length - binSkip);
}
}
}
}).ContinueWithOnUIThread(_ =>
{
Process.Start(outputFolder);
pe.EndBusy();
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Numerics;
using System.Reactive;
using System.Windows;
using ME3Tweaks.Wwiser.Model.Action;
using static LegendaryExplorer.Misc.ExperimentsTools.PackageAutomations;
using static LegendaryExplorer.Misc.ExperimentsTools.SequenceAutomations;
using static LegendaryExplorer.Misc.ExperimentsTools.SharedMethods;
Expand Down Expand Up @@ -1934,30 +1935,32 @@ public static void UpdateAudioIDs_EXPERIMENTAL(ExportEntry wwiseBankEntry, strin

(uint oldBankID, uint newBankID) = UpdateID_EXPERIMENTAL(wwiseBankEntry, null, newWwiseBankName);

WwiseBankParsed wwiseBank = wwiseBankEntry.GetBinaryData<WwiseBankParsed>();
var wwiserBinary = wwiseBankEntry.GetBinaryData<WwiseBank>();
var wwiseBank = wwiserBinary.Bank;
// Update the bank id
wwiseBank.ID = newBankID;
wwiseBank.BKHD.SoundBankId = newBankID;

idPairs.Add(oldBankID, newBankID);

// Update referenced banks kvp that reference the old bank name
IEnumerable<KeyValuePair<uint, string>> updatedBanks = wwiseBank.ReferencedBanks
.Select(referencedBank =>
if (wwiseBank.STID != null)
foreach (var refBank in wwiseBank.STID.BankIdToFilename)
{
if (referencedBank.Value.Equals(oldBankName, StringComparison.OrdinalIgnoreCase))
if (refBank.FileName.Equals(oldBankName, StringComparison.OrdinalIgnoreCase))
{
return new KeyValuePair<uint, string>(newBankID, newWwiseBankName);
refBank.BankId = newBankID;
}
return referencedBank;
});
wwiseBank.ReferencedBanks = new(updatedBanks);
}

// Gather all the IDs we don't know about yet
foreach (WwiseBankParsed.HIRCObject hirc in wwiseBank.HIRCObjects.Values)
if (wwiseBank.HIRC != null)
{
if (hirc.ID != 0 && !idPairs.ContainsKey(hirc.ID))
foreach (var hirc in wwiseBank.HIRC.Items)
{
idPairs.Add(hirc.ID, GenerateRandomID(random));
if (hirc.Item.Id != 0 && !idPairs.ContainsKey(hirc.Item.Id))
{
idPairs.Add(hirc.Item.Id, GenerateRandomID(random));
}
}
}

Expand Down Expand Up @@ -2001,6 +2004,8 @@ public static void UpdateAudioIDs_EXPERIMENTAL(ExportEntry wwiseBankEntry, strin
// }
// }
//}

wwiseBankEntry.WriteBinary(wwiserBinary);

string bankBinaryAsString = Convert.ToHexString(wwiseBankEntry.GetBinaryData());

Expand Down Expand Up @@ -2031,62 +2036,62 @@ public static void UpdateAudioIDs_LEGACY(ExportEntry wwiseBankEntry, string newW

(uint oldBankID, uint newBankID) = UpdateID_LEGACY(wwiseBankEntry, newWwiseBankName);

var wwiseBank = wwiseBankEntry.GetBinaryData<WwiseBankParsed>();
var wwiseBankBin = wwiseBankEntry.GetBinaryData<WwiseBank>();
var wwiseBank = wwiseBankBin.Bank;

// Update the bank id
wwiseBank.ID = newBankID;
wwiseBank.BKHD.SoundBankId = newBankID;

// Update referenced banks kvp that reference the old bank name
IEnumerable<KeyValuePair<uint, string>> updatedBanks = wwiseBank.ReferencedBanks
.Select(referencedBank =>
if (wwiseBank.STID != null)
foreach (var refBank in wwiseBank.STID.BankIdToFilename)
{
if (referencedBank.Value.Equals(oldBankName, StringComparison.OrdinalIgnoreCase))
if (refBank.FileName.Equals(oldBankName, StringComparison.OrdinalIgnoreCase))
{
return new KeyValuePair<uint, string>(newBankID, newWwiseBankName);
refBank.BankId = newBankID;
}
return referencedBank;
});
wwiseBank.ReferencedBanks = new(updatedBanks);
}

// DISABLED: Update references to old wwiseEvents' hashes, which are the ID of Event HIRCs.
// DISABLED: Update references to old wwiseStreams' hashes, which are in the unknown bytes of Sound HIRCs.
// Update references to old bank hash, which I'm certain is at the end of Event Action HIRCs,
// but we check in all of them just in case.
byte[] bankIDArr = BitConverter.GetBytes(oldBankID);
byte[] newBankIDArr = BitConverter.GetBytes(newBankID);
foreach (WwiseBankParsed.HIRCObject hirc in wwiseBank.HIRCObjects.Values)
{
//if (hirc.Type == HIRCType.Event) // References a WwiseEvent
//{
// if (wwiseEventIDs.TryGetValue(hirc.ID, out uint newEventID))
// {
// hirc.ID = newEventID;
// }
//}
//else if (hirc.Type == HIRCType.SoundSXFSoundVoice) // References a WwiseStream
//{
// // 4 bytes ID is located at the start after 14 bytes
// Span<byte> streamIDSpan = hirc.unparsed.AsSpan(5..9);
// uint streamIDUInt = BitConverter.ToUInt32(streamIDSpan);
// if (wwiseStreamIDs.TryGetValue(streamIDUInt, out uint newStreamIDUInt))
// {
// byte[] newStreamIDArr = BitConverter.GetBytes(newStreamIDUInt);
// newStreamIDArr.CopyTo(streamIDSpan);
// }
//}

// Check for bank ID in all HIRCs, even though I'm almost certain it only appears
// in Event Actions
if (hirc.unparsed != null && hirc.unparsed.Length >= 4) // Only replace if not null and at least width of hash
{
Span<byte> bankIDSpan = hirc.unparsed.AsSpan(^4..);
if (bankIDSpan.SequenceEqual(bankIDArr))
if(wwiseBank.HIRC != null)
foreach (var hircC in wwiseBank.HIRC.Items)
{
//if (hirc.Type == HIRCType.Event) // References a WwiseEvent
//{
// if (wwiseEventIDs.TryGetValue(hirc.ID, out uint newEventID))
// {
// hirc.ID = newEventID;
// }
//}
//else if (hirc.Type == HIRCType.SoundSXFSoundVoice) // References a WwiseStream
//{
// // 4 bytes ID is located at the start after 14 bytes
// Span<byte> streamIDSpan = hirc.unparsed.AsSpan(5..9);
// uint streamIDUInt = BitConverter.ToUInt32(streamIDSpan);
// if (wwiseStreamIDs.TryGetValue(streamIDUInt, out uint newStreamIDUInt))
// {
// byte[] newStreamIDArr = BitConverter.GetBytes(newStreamIDUInt);
// newStreamIDArr.CopyTo(streamIDSpan);
// }
//}

// Check for bank ID in all HIRCs, even though I'm almost certain it only appears
// in Event Actions - Updated 6/19/25 by HenBagle to just update Play action?
if (hircC.Item is ME3Tweaks.Wwiser.Model.Hierarchy.Action action)
{
newBankIDArr.CopyTo(bankIDSpan);
if(action.BankData.BankId == oldBankID)
{
action.BankData.BankId = newBankID;
}
}
}
}

wwiseBankEntry.WriteBinary(wwiseBank);
wwiseBankEntry.WriteBinary(wwiseBankBin);
}

/// <summary>
Expand Down
Loading
Loading