Tekken 4’s sound system, from the hidden driver to the music tables

The sound driver packed inside the executable, ADPCM banks, sound-effect tables, and six in-game tests read straight from sound memory

Tekken 4 (PS2) hides its sound driver inside the executable as a compressed, bit-inverted package; the project unpacks and repacks it, and the repacked driver runs identically. The banks, the sound-effect tables, the music track tables and the stage-to-music map are decoded, and music swaps, shortened tracks and replaced sound banks all work in PCSX2. Every result was read from memory: nothing has been checked by ear.

The driver hidden in the executable

On the PS2, sound runs on the I/O processor (IOP), and games normally ship their IOP modules as files on the disc. Tekken 4 doesn’t. Its sound, controller and memory-card modules are a single package inside the executable:

  • It sits at 0x2E3B00–0x300170 (116,336 bytes) and unpacks to 236,096 bytes.
  • It’s compressed with the same LZSS codec as the movesets (Tekken 4’s moveset files, decoded and edited in game). After decompression, every 32-bit word is bitwise inverted.
  • It contains six modules: sio2man, padman, mcman, mcserv, libsd, and Namco’s own sound driver, zsiopdrv.

The unpacked package appears byte for byte in the game’s memory in all four savestates checked.

To change anything in the driver, the package has to be repacked, and here the project went further than needed:

  • Namco’s encoder, recovered. Comparing every stock token with a longest-match search recovered the rule: greedy matching, lengths 3 to 32, a 2,048-byte window, and ties going to the farthest match. That reproduces the stock 116,336-byte stream byte for byte.
  • A tighter encoder for edits. Edited packages use an optimal parse instead, which comes to 113,308 bytes.
  • Test A10, in game. A repacked driver unpacks to exactly the stock bytes (0 differ), and music plays.

How the game talks to the driver

Each frame, the main CPU sends the driver a 128-byte buffer of byte commands through a remote procedure call (RPC 0x100), ended by 0xF0. The driver dispatches each command through a 193-entry table.

The driver reads DATA.BIN itself, by sector. It never writes the sound chip’s envelope (ADSR) registers at any of its 32 parameter-setting call sites, so sounds have no programmed envelope.

Banks and samples

Sound banks are headerless SPU2 ADPCM: 16-byte frames of a filter and shift byte, a flag byte, and 28 four-bit samples.

  • Proving the frames are real. In the main shared bank, 100 % of 75,272 frame headers are valid, against 0.7 % for random bytes.
  • Splitting samples. A sample ends at a frame with the end flag. A following flag-7 frame, which decodes as a one-frame square wave, is the mute-loop terminator rather than a sound.
  • Records stored raw. 155 non-empty archive records are raw (Tekken 4’s 2.57 GB archive keeps its index in the executable): the video and the audio. A control scan of the other 385 MB found no hidden ADPCM.

During a fight, sound memory holds:

AddressHolds
0x5010The common bank (205 samples)
0x12B090The stage’s bank
0x15F110Player 1’s fighter bank
0x188F90Player 2’s fighter bank (171,648 bytes each)
0x1B8000 upStreaming buffers, the music ring, and the announcer’s 512-byte buffer

Sound-effect tables

A table of 55 bank definitions at 0x31B190 maps sound ids 0–639 to samples. Each 8-byte entry gives an offset, a pitch, a volume and a class.

All 640 offsets land exactly on sample starts across 37 banks, and 32 voices caught live in savestates match their table entries’ addresses and pitches. Pitch is the SPU2 pitch register (0x1000 = 48 kHz). The most common value, 0x0555, is 15,996 Hz and covers 466 entries.

A fighter’s sound set follows a field in its row: bank definition 31 + n, voice-clip group 2 + n. This holds on all 44 rows that have a sound bank.

Music

Background music is three stream records (276, 277 and 278, with 9, 24 and 53 tracks): stereo ADPCM with a 512-byte left/right interleave.

The track tables are in the sound driver’s own data. Each 16-byte entry gives a start sector, a size and a “next track” link. The links build per-section loops, with −1 meaning stop and −2 a stinger stop. The first report said the source of the track offsets was unknown; it’s these tables.

A stage’s music comes from a 26-entry table at 0x31ABF0, through 24-byte playlist entries at 0x31ACA8.

A new song can be added without enlarging any table:

  1. The song is appended to the archive.
  2. It’s pointed at by a driver track entry that was freed because two stock tracks were byte-identical duplicates.
  3. Its playlist is reached through an out-of-range music id. The game computes the entry address as 0x31ACA8 + 24 × id with no bounds check. For id 178,956,967 that product is 2³² − 88, so the address wraps to 0x31AC50, 88 bytes before the table, and the playlist is read from there.

In game, the music voices run at 44,086 Hz and the ring buffer holds the new song’s bytes at three points in the track.

In game: experiment E4

Run on 2026-10-21 by script in PCSX2, each test predicted before booting and read from savestates afterwards:

TestChangePredictionResult
A1A stage’s music id 0 → 7Record 278, a track in 18–25Pass: track 18 playing at fight start, then 19
A10The sound driver repackedUnpacked driver identical to stockPass: 0 bytes differ, music plays
A2A music track replaced by a same-length test toneThe ring buffer holds the tone; no desynchronisationPass on structure. The pitch was not measured
A3That track shortened to 6.0 s in the driver tablePlayback runs 6.0 s ahead of A2Pass: 6.12 s ahead; the driver differs from A2 in exactly the two size bytes
A4Ten sounds in the common bank replaced by tonesSound memory holds the tone bank; pitch 0x0555Pass: two of the ids caught playing from it
A5One fighter’s sound bank replaced by tonesPlayer 2’s bank holds itPass: that fighter’s voice ids play from it

The announcer

The announcer’s lines are record 4: raw ADPCM at 22,050 Hz, each clip starting on a 2,048-byte sector, played through a 512-byte buffer. Clip 37 is an unused name call (the notes identify it as one of Combot’s). The game plays it for character index 19, which no stock select cell reaches and which is where the project put its new fighter.

A new name call was encoded to the bank’s format and written over clip 37 at the same size. In game, when the new fighter is picked, the announcer’s buffer holds bytes that exist only in the new clip.

The notes disagree on the clip table: one count gives 38 clips and clip 37 as 10,240 bytes, another 61 clips and 9,216 bytes. Neither has been tested by changing a table entry, so new lines are limited to the length of the clip they replace.

Corrections

The adversarial verification re-derived 13 of the first report’s claims with fresh code. 11 held and 2 fell, and later work corrected more:

  • Banks don’t start with a silent priming frame. Only 6 of 125 do.
  • The common bank holds 205 samples, not 391. 391 counted end flags, not samples.
  • The music is 44,086 Hz, not 48 kHz (above).
  • The track offsets come from the driver’s tables, not from an unknown source.
  • Player 1 uses bank A. That had been an open question.
  • Two fighters’ bank records were swapped in the first reports.

↑ ↓ move, Enter open Search with filters