- Extension: hnm
- Company: CRYO Interactive Entertainment
HNM6 is the latest variant of HNM video format by Cryo. Unlike its previous versions, it has Hi-color video support.
File consist of a main header, followed by frame chunks. Each frame chunk consist of individual audio and video chunks. All numbers are little-endian.
u8 sig -- file signature, "HNM6" u8 reserved -- usually 0 u8 audioflags -- nonzero value indicates file has APC sound (not authoritative) u8 bpp u16 width u16 height u32 filesize u16 frames -- number of frames u16 reserved2 u32 reserved3 -- usually 0, but sometimes "V107", "V108" - version? u16 speed -- playback speed in fps, may be zero (?assume 15 fps then?) u16 maxbuffer -- number of frame buffers used u32 maxchunk -- max frame chunk size u8 note u8 copyright -- "-Copyright CRYO-"
Each frame chunk begins with u32 chunk size (including this size field), then followed by frame's individual chunks:
u32 chunksize -- chunk size including this field, excluding padding u16 chunkid -- TWOCC chunk id u16 reserved u8 data u8 padding -- pads chunk to 4-byte boundary
APC Audio. See CRYO APC
Video frame, WARP format.
Video frame, normal format.
At least one known game (Riverworld) uses slightly different HNM6 container format, most likely due to different sound encoding. This format is just a small enhancement of the original, but it's not backward compatible.
Standard HNM6 header continues with additional audio description header:
u32 freq u32 bits u32 channels u32 ? u32 ? u8 copyright -- "HNMS 1.1 by SARRET Hubert\0\0\0" u32 ? -- some initial audio samples? u32 ? u32 ? u32 ?
Each frame chunk followed by an audio chunk, however, this audio chunk size is not counted by frame's chunk size field. Frame chunk format:
u32 sig -- "SOUN" u32 chunksize -- including this preamble u8 data
The video compression relies on a concept of Key-blocks, encoded with JPEG-like algorithm and motion blocks, derived from previously drawn blocks. The frame is encoded in 8x8 or smaller block. The Key-blocks are always 8x8 and directly encoded, while motion blocks heavily rely on D&C approach and various block transformation techniques. Generally, this codec is quite similar to 4XM codec and Mobiclip codecs, because it's written by the same people.
There are two variants of HNM6 video codec.
- WARP codec. Prototype codec, simplified version. Used in a very little number of games.
- Normal (yes, that's how it's called internally) codec. Fully-featured version, widely used.
Both version has identical bitstream layout. Frame data begins this the following header:
s32 quality -- JPEG quality index, negative value indicates that the frame is a keyframe u32 bitbuffer -- offset of bitbuffer u32 motionbuffer -- offset of motion vector buffer u32 shortmotionbuffer -- offset of short motion vector buffer u32 jpegbuffer -- offset of jpeg data buffer u32 jpegend -- offset of jpeg data buffer end
All offsets a relative to this header.
- quality is a standard JPEG quality value (0..100%). Negative value is used to specify a keyframe (for Normal codec only.)
- bitbuffer points to frame's VLC-encoded macroblocks. This kind of data is accessed by a bit-reader with 32-bit internal queue, MSB bit comes out first.
- motion buffer points to array of u16 motion vectors, used to copy blocks from various areas of the frame
- short motion buffer is used for accessing the blocks near the current macroblock. Each entry of buffer is 12 bits long, accessed in LSB order.
- jpeg buffer points to array of encoded JPEG macroblocks
JPEG Blocks decoding
JPEG blocks are encoded in YUV 4:4:4 format. The requantization, IDCT and colorspace conversion steps are identical to standard JPEG. Standard zigzag (0, 1, 8, 16, 9, ...) and quantization tables (16, 11, 10, 16, 24, ... ; 17, 18, 24, 47, 99, ...) used as well.
The only custom feature used is the coefficients encoding. Instead of Huffman coding they are stored using RLE scheme.
Macroblock's jpeg data is accessed by 4-bit nibbles, lower nibble of each byte first. Also, the following primitives are used:
- half - half of a nibble, upper part first, then lower (i.e. read a nibble, process upper part of it upon first use, lower part upon second)
- s2 - signed two-bit 2's compliment value of a half, no zero point (i.e. 0b00 -> 1, 0b01 -> 2, 0b10 -> -2, 0b11 -> -1)
- s4 - signed four-bit 2's compliment value of a nibble, no zero point
- s44 - signed eight-bit value of two nibbles, with zero point (this is basically (signextend(nibble1) << 4) | nibble2)
For each plane of 8x8 coefficients, first entry is s44, then read nibbles and fill the rest of coefficients as follows until the whole plane is complete. Repeat for each Y, U and V plane.
0 - fill remainder of plane with zeros 1 - 0, 0, 0, 0 2 - 0, 1 3 - 0, -1 4 - 0, 0, 1 5 - 0, 0, -1 6 - 0, 0, 0, 1 7 - 0, 0, 0, -1 8 - zeros = half, tail = half fill (zeros+1) coefficients with 0 fill (tail+2) coefficients with s2 9 - zeros = half, tail = half fill (zeros+1) coefficients with 0 fill (tail+1) coefficients with s4 10 - s2, s2 11 - s4 12 - s4, s4 13 - s4, s4, s4 14 - 0 15 - s44
While you can use stock JPEG routines to decode Key-blocks, if you want to produce bitexact output you need to use very specific code to do so.