Sounds, music, images and scripts
Sounds and music
A sound (waveObj, decomp/wavesound.cpp) is MHWK, a size, WAVE, then chunks padded to even lengths: an optional Cue# (a u16 count of named positions) and Data (u16 rate, u32 sample count, u8 bits, u8 channels, u16 encoding, u16 loop count, u32 loop start and end, then samples). All 1,333 are 8-bit mono PCM at 11,025 Hz. 23 loop, forever (0xffff), over a valid range, and exactly those have a Cue#, empty; no sound lists a cue point. Odd-length data has a zero pad byte, which the header's size includes.
Music is MHWK, a size, MIDI, then a standard MIDI file's MThd and MTrk chunks padded to even lengths, with a Prg# chunk after MThd: a count, then {u16 program, u16 mask of channels} for each program the track changes to, sorted. It's derived from the track in every one of the 18. Unlike a sound's, the header's size leaves out the last chunk's pad byte. All are type 0 at 480 ticks a beat, with markers the engine looks for (Setup end, Loop end; midisound.cpp) and a track name naming the original file (ZBIsle.mff).
Images
An image (ImageHeader) has a big-endian header: width, height, bytes per row (the width rounded up to 4) and flags (all the game's are 8-bit, 2, plus 0x10 packed and 0x100 compressed), then its pixels. A bank of images (loadImageBank, decomp/view.cpp), holding a feature's frames, has the same header, as if it were one image: its width the number of images, its bytes per row that rounded up to 4, and its height the bank's size divided by that (truncated to 16 bits: MAZE2.MHK's 2 MB tBMP 11000 says 3611). The header is followed by each image's offset (the first right after the offsets, each 4-byte aligned), then the images, each with its own header. decompressImage treats the bank's header as an image's, so a compressed bank is decompressed whole. Of the 175 tBMPs, 111 are banks (109 compressed), with 10,017 images (7,714 packed), and 64 are single images (61 compressed, 34 packed, among them the scenes' 640x480 backgrounds). Rows are stored top row first.
- Compression (
lzDecompress,0x48e4df) is LZSS: flag bytes low bit first (1: a literal), matches as big-endian words (low 10 bits a position in a 1 KB ring, the rest the length less 3), the ring zeroed with writing starting 66 bytes from its end. After the header come the decompressed size, the compressed size and the ring size (0x400). Broderbund's compressor was Haruhiko Okumura'sLZSS.C(1989): a port of its encoder, binary search trees and all, reproduces all 170 compressed resources byte for byte. The trees' shape decides between equally long matches, so an encoder that merely finds longest matches doesn't.LZSS.Cdoesn't clear the lookahead past the end of a short input: in the one resource shorter than the 66-byte lookahead (TOWN.MHK'stBMP1200, 48 bytes), the original had non-zero bytes there, and any value but 0 and the input's0x32reproduces its last match. - Packing (
drawPackedPixels): each row is a u16 size and packets, a header byte whose bit 7 means a run of the next byte, else literal bytes, the count being the low bits plus 1. Broderbund's packer runs colour 0 (transparent) always, other colours from 4 pixels on, splits runs at 128 (judging what's left afresh) and ends every row with a run, however short: rules that re-pack all 365,166 packed rows exactly. - Junk. After a packed image's rows the packer left 2 bytes (in a bank, then padding to a multiple of 4), and unpacked images have row padding. 80% of those tail bytes, and 79 padding rows, aren't zero but leftovers in memory (
db6d,6db6patterns);assets/records them where they aren't zero.
Scripts
A script (SCRB, SCRS; stepped through by features.cpp, frames found by scriptFrameOffset, 0x464cbc in view.cpp) is big-endian signed words: the number of frames, for a Zoombini script the way it faces (setSnoidFacing), then the frames. A frame is its cels (at most 24 in the game's), each an image of the view's bank (0 draws nothing; its x and y are then always 0) and its x and y, then an end word: 0xff00 plus an event number (0: none) that the view's owner is told of, or 0xfe00 plus an event followed by a sound to play. All 2,501 scripts parse exactly, their frame counts matching.