The BNKFile class handles Wwise soundbank (.bnk) files, which are containers for multiple WEM audio files. BNK files are embedded within PCK archives and can be modified to replace individual WEMs.
BNKFile
Constructor
from src.bnk_handler import BNKFile
# Load from file
bnk = BNKFile(bnk_path="soundbank.bnk")
# Or load from bytes
bnk = BNKFile(bnk_bytes=bnk_data)
Path to BNK file to load.
BNK file data as bytes (useful when extracting from PCK).
Note: Provide either bnk_path or bnk_bytes, not both.
Methods
list_wems
Returns list of WEM IDs contained in the BNK.
wem_ids = bnk.list_wems()
List of WEM file IDs in the soundbank.
Returns: Empty list if BNK has no DATA section.
Extracts a single WEM file from the BNK.
wem_data = bnk.extract_wem(wem_id, output_path=None)
Optional path to save the WEM file. If None, returns bytes without saving.
Raises:
ValueError: If BNK has no DATA section
KeyError: If WEM ID not found in BNK
replace_wem
Replaces a WEM file inside the BNK.
bnk.replace_wem(wem_id, wem_path=None, wem_bytes=None)
Path to replacement WEM file.
Replacement WEM data as bytes.
Raises:
ValueError: If BNK has no DATA section or neither wem_path nor wem_bytes provided
KeyError: If WEM ID not found in BNK
Behavior:
- Updates WEM data in memory
- Automatically corrects DIDX offsets and sizes
- Prints confirmation with new file size
- Call
save() or get_bytes() to persist changes
save
Saves the modified BNK to a file.
Where to save the modified BNK file.
get_bytes
Returns the entire BNK as bytes (useful for embedding back into PCK).
bnk_bytes = bnk.get_bytes()
Complete BNK file as byte string.
BNK File Structure
BNK files contain several chunks:
- Contains soundbank metadata
- Always the first chunk
DIDX (Data Index)
- Index of WEM files in the DATA section
- Maps WEM ID → (offset, size)
DATA (Audio Data)
- Contains raw WEM file data
- Files are 16-byte aligned
HIRC (Hierarchy)
- Optional chunk containing sound hierarchy
- Preserved when modifying WEMs
Helper Classes
WEM
Represents a single WEM file.
wem = WEM(data)
wem_data = wem.getdata()
wem_size = len(wem)
DIDX
Manages the WEM index.
didx.wem_offsets[wem_id] # Get offset
didx.wem_sizes[wem_id] # Get size
DATA
Manages WEM data storage.
data.wem_data[wem_id] # Get WEM object
Functions
Convenience function to extract all WEMs from a BNK.
from src.bnk_handler import extract_bnk_wems
extract_bnk_wems(bnk_path, output_dir)
Directory to extract WEMs to.
Behavior:
- Extracts all WEMs as
{wem_id}.wem
- Prints progress for each file
Example Usage
from src.bnk_handler import BNKFile
from pathlib import Path
# Load BNK
bnk = BNKFile(bnk_path="2471637648.bnk")
# List contents
wem_ids = bnk.list_wems()
print(f"Found {len(wem_ids)} WEM files")
print(f"WEM IDs: {wem_ids}")
# Extract specific WEM
output_dir = Path("extracted_wems")
output_dir.mkdir(exist_ok=True)
for wem_id in wem_ids:
output_file = output_dir / f"{wem_id}.wem"
bnk.extract_wem(wem_id, output_file)
print(f"Extracted: {wem_id}.wem")
Modify BNK Soundbank
from src.bnk_handler import BNKFile
# Load original BNK
bnk = BNKFile(bnk_path="original.bnk")
# Replace multiple WEMs
replacements = {
100000: "replacement_1.wem",
100001: "replacement_2.wem",
100002: "replacement_3.wem"
}
for wem_id, wem_file in replacements.items():
try:
bnk.replace_wem(wem_id, wem_path=wem_file)
print(f"✓ Replaced WEM {wem_id}")
except KeyError:
print(f"✗ WEM {wem_id} not found in BNK")
# Save modified BNK
bnk.save("modified.bnk")
print("Modified BNK saved!")
Use with PCK Files
from src.pck_extractor import PCKExtractor
from src.bnk_handler import BNKFile
from io import BytesIO
# Extract BNK from PCK
extractor = PCKExtractor("SoundBank_SFX_1.pck")
with open("SoundBank_SFX_1.pck", 'rb') as f:
extractor.file_handle = f
extractor.parse_header()
# Find BNK by ID
bnk_id = 2471637648
bnk_info = next(
b for b in extractor.banks_info
if b['id'] == bnk_id
)
# Extract BNK data
f.seek(bnk_info['offset'])
bnk_data = f.read(bnk_info['size'])
# Modify BNK
bnk = BNKFile(bnk_bytes=bnk_data)
bnk.replace_wem(100000, wem_path="new_sound.wem")
# Get modified bytes for re-packing
modified_bnk = bnk.get_bytes()
Command-Line Usage
List WEMs in BNK
python -m src.bnk_handler list soundbank.bnk
python -m src.bnk_handler extract soundbank.bnk ./output_dir
Replace WEM in BNK
python -m src.bnk_handler replace soundbank.bnk 100000 new_sound.wem output.bnk
Technical Details
Alignment
WEM files are 16-byte aligned within the DATA section:
def align16(x):
return (16 - (x % 16)) % 16
Offset Calculation
DIDX stores block-based offsets:
if blocksize != 0:
actual_offset = offset_block * blocksize
else:
actual_offset = offset_block
Section Order
BNK chunks must appear in this order:
- BKHD (Bank Header)
- DIDX (Data Index)
- DATA (Audio Data)
- HIRC (Hierarchy, optional)
Error Handling
from src.bnk_handler import BNKFile
try:
bnk = BNKFile(bnk_path="soundbank.bnk")
bnk.replace_wem(12345, wem_path="new.wem")
bnk.save("modified.bnk")
except ValueError as e:
print(f"Invalid BNK file: {e}")
except KeyError as e:
print(f"WEM not found: {e}")
except FileNotFoundError as e:
print(f"File not found: {e}")
Notes
- BNK files must start with
BKHD magic bytes
- WEM IDs are 32-bit unsigned integers
- Modifying WEMs preserves the HIRC hierarchy
- Always keep backups of original BNK files
- Use
get_bytes() when re-embedding modified BNKs into PCKs
- The DIDX and DATA sections are automatically synchronized when replacing WEMs