Skip to content

CB8 — FMV Container (.CB8)

Multiplexed audio/video container for full-motion video. Used for intros, cutscenes, and per-aircraft presentation clips. Each .CB8 is paired with a .11K audio file of the same stem (for playback outside the container). Found in FA_4C.LIB, FA_4D.LIB, FA_10.LIB, FA_10B.LIB, FA_11.LIB, and FA_11B.LIB.

Tools

fx

fx cb8 info   <file.CB8>                       # header and chunk summary
fx cb8 frames <file.CB8> [-o output_dir]       # decode every frame to PGM (indices)
fx cb8 unpack <file.CB8> [-o output_dir]       # decode every frame to colour PNG
fx cb8 repack <orig.CB8> <png_dir> [-o out]    # rebuild the movie around edited frames

unpack renders through each frame's embedded palette. repack re-encodes the video (each PNG must use ≤ 256 distinct colours; the per-frame palette is rebuilt) while the DRBC header, every audio chunk, the stream order, and the VooM timing carry over from the original verbatim — the decode→edit→repack loop is pixel-exact (#95).

Other Tools

  • GIMP — free, cross-platform; batch script (File → Script-Fu) useful for processing many frames
  • Paint.NET — free, Windows
  • Photoshop $ — industry standard; Image Processor script for batch frame edits
  • Affinity Photo $ — one-time purchase alternative to Photoshop

File Layout

All multi-byte integers are little-endian.

The file begins with a 64-byte DRBC header, followed by a sequence of variable-length typed chunks packed back-to-back. Each chunk starts with a 4-byte ASCII type tag and a 4-byte total size (including the tag and size field).

Offset Size Description
0x00 64 DRBC file header
0x40 var Typed chunks: MRFA, MRFI, VooM (see below)

Chunk structure:

Offset Size Type Description
+0x00 4 char[4] ASCII type tag (MRFA, MRFI, or VooM)
+0x04 4 u32 Total size of this chunk in bytes (including these 8 bytes)
+0x08 var Chunk payload

DRBC File Header (64 bytes)

Offset Size Type Description
0x00 4 char[4] Magic DRBC — the fourth generation of the format: InitCobra explicitly rejects ARBC, BRBC, and CRBC as too old (confirmed)
0x04 4 u32 Flags: bit 0 = audio interleaved (MRFA chunks present), bit 1 = pixel-doubled playback (confirmed from InitCobra)
0x08 2 u16 Audio timing divisor P (observed: 150)
0x0A 2 u16 Audio rate term Q (observed: 22050); samples per MRFA chunk = Q × 50 / P = 7350 (confirmed: InitCobra primes two chunks of exactly this size)
0x0C 4 u32 Format version — the engine requires < 0x67 (observed: 0x65)
0x10 2 u16 Reserved — read into the 64-byte header buffer but never referenced by InitCobra (observed: 0x0000 or 0x0080)
0x12 46 u8[46] 0xFF padding

The engine reads the file sequentially — header, two priming MRFA chunks (when flag bit 0 is set; the first sample of each is forced to 0x80 silence), the VooM index, then frames — and never checks the MRFI/MRFA/VooM tags: the index is trusted for every seek (confirmed; the tags exist for the file format, not the player).

Chunk Type: MRFA — Audio Block

Raw PCM audio data. The chunk payload contains uncompressed 8-bit unsigned PCM samples at 11025 Hz (matching the .11K convention). Silence is 0x80.

Offset Size Type Description
+0x00 4 char[4] Magic MRFA
+0x04 4 u32 Chunk size (observed: 7374)
+0x08 4 u32 Audio-format descriptor — not consumed by the player (observed: 128)
+0x0C 4 u32 Audio-format descriptor — not consumed (observed: 0)
+0x10 4 u32 Audio-format descriptor, consistent with bits-per-sample (observed: 8) — not consumed
+0x14 4 u32 Audio-format descriptor, consistent with channel count (observed: 1 = mono) — not consumed
+0x18 7350 u8[] Raw 8-bit unsigned PCM samples at 11025 Hz

The four dwords at +0x08+0x17 are read past by InitCobra but never consumed: the playback audio format is fixed (8-bit / 11025 Hz / mono) and the per-chunk sample count comes from the DRBC P/Q timing pair, not this header — consistent with the doc's note that the chunk metadata "exist[s] for the file format, not the player."

7350 samples ÷ 11025 Hz = 666.7 ms = exactly 10 video frames at 15 fps.

Chunk Type: MRFI — Video Key Frame

One self-contained, vector-quantized key frame (confirmed — engine trace

95, DecodeSVGA8Frame at 0x456EC0; the DecodeFrame dispatcher at

0x442370 has no inter decoder for the 8-bit submode, so nothing carries over between frames). Every frame brings its own palette and codebooks.

An earlier revision of this section documented a delta/skip-map model with block data at +0x18; that model was wrong — the region at +0x18 is the palette — and it never decoded real frames. The layout below is read from the engine and validated by a pixel-exact decoder.

Offset Size Type Description
+0x00 4 char[4] Magic MRFI (unchecked by the player)
+0x04 4 u32 Chunk size, padded to a 4-byte multiple
+0x08 1 u8 Frame kind: 0 = key (the only kind used by CB8)
+0x09 1 u8 Submode: 5 = 8-bit paletted (6 = the 15/16/24-bit VDO path)
+0x0A 2 u16 A — detail-book entries for pixel rows < X
+0x0C 2 u16 B — detail-book entries for pixel rows >= X
+0x0E 2 u16 C — single-book entries
+0x10 2 u16 S — mode-bitmap bytes (((cells + 31) / 32) × 4; 600 for 320×240)
+0x12 2 u16 D — palette bytes (768)
+0x14 2 u16 X — detail-book switch row in pixels (0xFFFF = never)
+0x16 2 u16 Padding (0)
+0x18 D u8[] Palette: 256 × 3 bytes of 6-bit VGA RGB — every frame carries its own
(A+B)×4 u8[] Detail book: 2×2-pixel entries (4 palette indices, row-major); entries 0..A-1 serve rows < X, A..A+B-1 rows >= X
C×4 u8[] Single book: 2×2-pixel entries expanded to 4×4 at decode
S u8[] Mode bitmap: one bit per 4×4 cell, row-major, continuous across rows; consumed as u32-LE words MSB-first
var u8[] Index stream (to end of chunk, zero-padded to the 4-byte boundary)

Cell grid. The frame divides into 4×4-pixel cells, row-major (80 × 60 = 4,800 for 320×240). For each cell, its mode bit selects the coding:

  • Bit 0 — single (CopySB8): one index byte into the single book. The 4-byte entry (a, b, c, d) is a 2×2 block expanded to 4×4 — plainly, each value pixel-doubled into its quadrant (ExpandDB), or with a dither when the display path enables it (EDB, below).
  • Bit 1 — detail (CopyDB8): four index bytes selecting detail-book entries placed as the TL, TR, BL, BR 2×2 quadrants; each 4-byte entry is one 2×2 block (row 0: bytes 0–1, row 1: bytes 2–3). Rows >= X index the second book half (the B entries).

Decode algorithm:

parse A,B,C,S,D,X; locate palette/detail/single/bitmap/index regions
cell = 0
for cy in 0..cell_rows-1:
    book = detail[0..A)        if cy*4 <  X or X == 0xFFFF
         = detail[A..A+B)      otherwise
    for cx in 0..cell_cols-1:
        word = u32le(bitmap[(cell/32)*4 ..])       // MSB-first bit order
        bit  = (word >> (31 - cell%32)) & 1
        if bit == 0:  expand single[next_index()] into the 4x4 cell
        else:         place book[next_index()] at TL, TR, BL, BR (4 indices)
        cell += 1

EDB dither (display-time option). When the SVGA path enables dithering, each single-book value v expands not by plain doubling but as a 2×2 checkerboard of v and a partner p: the neighbour index (v−1 or v+1, within the same 128-entry palette half, guarded at 0/0x7F/0x80/0xFF) whose RGB — looked up in this frame's palette — is nearest to v's with squared distance ≤ 8; p = v when neither qualifies. The expanded rows are (a, pa, b, pb) / (pa, a, pb, b) and likewise for (c, d). fx decodes with the plain expansion (deterministic and exactly invertible); the dither is a player-side rendering variant, not stream data.

Chunk Type: VooM — Video Index / Key Frame

Serves as both a video key frame marker and an A/V index table. The payload begins with a 12-byte header describing the video stream, followed by a flat array of 16-byte index entries (one per MRFI frame).

Offset Size Type Description
+0x00 4 char[4] Magic VooM
+0x04 4 u32 Chunk size
+0x08 4 u32 Video width in pixels (observed: 320)
+0x0C 4 u32 Video height in pixels (observed: 240)
+0x10 4 u32 Audio sync rate = samples_per_frame × fps (observed: 6000 = 400 × 15)
+0x14 16×N Index entries (N = (chunk_size − 20) / 16)

Index entry (16 bytes each):

Offset Size Type Description
+0x00 4 u32 Absolute file offset of this MRFI chunk
+0x04 4 u32 Byte size of this MRFI chunk
+0x08 4 u32 Cumulative audio sample count at this frame (frame_index × samples_per_frame)
+0x0C 4 u32 Audio samples per frame (constant: 400)

Typical Chunk Sequence

DRBC header
MRFA  — priming audio block (first sample forced to 0x80 silence)
MRFA  — second priming block
VooM  — A/V index (N entries)
MRFI  — key frame 0
MRFI  — key frame 1
...   — MRFA blocks interleaved roughly every 10 frames
MRFI  — key frame N-1

Palette and Colour

Every MRFI frame embeds its own 768-byte palette (256 × 3 bytes of 6-bit VGA RGB at payload offset +0x18) — resolved by the #95 engine trace. No external palette exists or is needed: decoding is fully self-contained, and palettes can change per frame (the movies fade by animating them).

PALETTE.PAL never applies to CB8 (its low indices are magenta placeholders); the earlier greyscale-fallback advice is obsolete now that the in-band palette decodes true colour.

File Inventory

File Video Audio Source LIB
ATF.CB8 VooM 320×240 .11K (external) FA_4C.LIB
C_INTRO.CB8 VooM 320×240 .11K (external) FA_4C.LIB
JANELOGO.CB8 466 MRFI frames MRFA blocks (11025 Hz) FA_4C.LIB
B2_D.CB8 MRFI delta frames MRFA blocks (11025 Hz) FA_10.LIB

JANELOGO.CB8 (6,496,064 bytes): VooM at offset 14812 with 466 index entries (chunk_size 7476 = 20 + 466×16). Frame 0 offset: 22288, duration: ~31.1 s @ 15 fps.

Engine Notes

Provenance: the Cobra framework

CB8's player is "Cobra", EA's in-house movie framework — not licensed middleware (confirmed, #95 engine trace). The evidence: the decoder is first-party C++ compiled into the game executable (InitCobra, SetupCobra, PlayCobra, DecodeFrame — MSVC-mangled names from EA's own FA.SMS symbols), it shares engine-internal structs (GlobalData, MovieContext, FrameHeader), reads movies through the engine's own LIB resource layer rather than a middleware I/O callback, and renders through the engine's VGA banking (SetVESABank, DrawAcrossBank). The magic's generation lineage — InitCobra rejects ARBC, BRBC, and CRBC as too old before accepting DRBC, plus an internal version gate (< 0x67) — is a format evolving privately across the USNF-line titles this anthology collects; licensed FMV of the era (RAD's Smacker, and Bink from 1999) shipped as self-contained libraries with their own containers. "CB8" reads as CodeBook, 8-bit; the same Cobra dispatcher also serves the 15/16/24-bit submode used by the VDO hi-color movies, so Cobra is the umbrella for both. The name's origin is unrecorded in the binary — an internal codename (inferred).

Open Questions

1. Playback palette recovery — resolved

Resolved by the #95 engine trace: the palette is embedded per frame (768 bytes at MRFI +0x18); nothing engine-internal or external exists. DecodeSVGA8Frame reads it from the frame, and EDB's dither computes neighbour distances against it.

Status: resolved — re-static (#95)

2. Unknown header fields — resolved (#54)

The static header unknowns are closed against the InitCobra (0x46ae10) decompile: the loader reads the 64-byte DRBC header but references only the magic, flags, audio-timing pair (P/Q), and version gate — the +0x10 field is read into the buffer and never used (reserved). The four MRFA payload dwords at +0x08..+0x17 are likewise read past but not consumed; the audio format is fixed and per-chunk size is DRBC-driven, so they are file-format descriptors, not decode inputs (see the DRBC / MRFA header tables above).

The one remaining item is not a file field: the palette-half selector EDB consults (GlobalData+0xc1b0 bit 0) is a runtime playback / double-buffer detail, best confirmed on the bench.

Status: resolved static; palette-half re-tagged re-gameplay (#56)

Formats: 11K — the paired external audio (and the MRFA sample format); PAL — why PALETTE.PAL does not apply here; LIB — the six carrier archives.