Skip to content

tools/ue_audio_extract: UE3 XMA2 audio extraction, re-encode and repack pipeline - #48

Open
reido601hz-lgtm wants to merge 1 commit into
OpenPortroid:mainfrom
reido601hz-lgtm:contrib/ue-audio-extraction
Open

reido601hz-lgtm wants to merge 1 commit into
OpenPortroid:mainfrom
reido601hz-lgtm:contrib/ue-audio-extraction

Conversation

@reido601hz-lgtm

Copy link
Copy Markdown

UE3/XMA2 audio package tooling (extraction, re-encode and repack)

This adds a standalone tools/ue_audio_extract/ folder that lets modders
(un / the project) pull the compiled Xbox-360 voice data out of the
assets/AO2Game/CookedXenon/*.xxx packages, and splice replacement audio
back in place — without touching package offsets file-wide.

What it contains

  • lzodec.cpp — LZXP/LZO chunk decompressor. UE3 packages in this game
    are LZO chunk compressed (CompressionFlags=2). UELib's Deserialize
    returns early ("We can't continue without decompressing"), leaving names,
    imports and exports empty. This decoder is the LZO1X implementation from
    this project's own font path (aot/src/cyrillic_fonts.cpp).
  • ExtractManual.cs / chunk_dump.cs — C# dumpers on Eliot.UELib with
    one fix: UELib's UBulkData.LoadData seeks to the storage offsets as read
    by its stream layer, which translates them through the compressed-chunk
    map and yields wrong buffers in this build. The tools read the bytes
    directly at the deserialized StorageOffset.
  • ue_package.py — chunk table reader and plain-package assembler
    (assemble_package).
  • ue_audio_extract.py — per-package CLI: .xxx → per-wave .xma2 +
    vgmstream TXTH stubs.
  • xma2_reencode.py — WAV → XMA2 packets (via external xma2enc
    CLI, birabittoh/xma2enc, WMA-Pro bitstream + XMA2 packet layer),
    per-wave bulk-header rebuild (dataSize, numSamples, packet count and the
    per-packet cumulative seek table), budget-fitted splicing.
  • README.md — the reversed-engineered format notes: chunk layout,
    the 56+4N bulk header, and the sample-rate finding (UE3 Xbox VO runs
    at 24000 Hz mono).

Validation performed

  • Inventory over the 140 *_LOC_int.xxx packages: 9753 SoundNodeWave
    exports
    extracted and decoded to WAV (XMA2 via vgmstream TXTH,
    RMS ≈ 5–7k, pitch ≈ 114 Hz for Rios @24000).
  • 902 mission lines cloned to pt-BR (cross-lingual clone from each wave's
    own English reference, 24000 Hz) — pilot of 5 approved by the author.
  • Repack round-trip of a pilot package: re-encode → rebuild header →
    splice into the original bulk budget → UELib parses identically (49
    SoundNodeWave exports, same offsets) → the rebuilt package boots in
    armyoftworecomp.exe with no XMA/audio errors in the log.

The user-visible bit (full re-dub distribution) is a separate workstream
(pilot in a private mod layer, not this PR).

What this is NOT

  • No game content is included (assets and generated code remain on the
    user's side, per repo convention).
  • No renderer/audio-runtime changes — pure tooling.

…k pipeline

Adds a standalone toolchain for Army of Two (Xbox 360 UE3) voice data:
- lzodec: LZXP/LZO block decompressor (LZO1X, verbatim from the port's own
  cyrillic_fonts path) for compressed LOC packages where UELib stops after
  the summary.
- ue_package.py + C# dumpers (chunk table, manual bulk reader) for the
  package layouts discovered while investigating.
- ue_audio_extract.py: package -> per-wave XMA2 payload + vgmstream TXTH.
- xma2_reencode.py: WAV -> XMA2 (external xma2enc), per-wave bulk header
  rebuild (dataSize/numSamples/packet seek table), budget-fitted splicing
  into the wave's original StorageSize so file size/offsets are unchanged.

Format notes (README): compressed chunk layout, the 56+4N bulk header
with its seek table, and the 24000 Hz mono sample rate observed in the
game's dumps. Validated: 9753 waves extracted/decoded, 902 mission lines
cloned to pt-BR (pilot of 5 approved), repack boots without XMA errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant