HNM (1)

From MultimediaWiki
Jump to navigation Jump to search

HNM 1, developed and used by Cryo Interactive, is a second generation FMV format. It operates in fixed 320x200x256 resolution.

All numbers are little endian.

File Structure

File consist of series of chunks, where all single frame related chunks (e.g. it's video and audio portion) enclosed in a "superchunks".

Each file begins with a header chunk, carrying palette and superchunk offsets table:

 word  header_size - size of this header chunk
 byte  palette[]   - palette block
 byte  padding[]   - zero or more 0xFF bytes of padding
 dword offsets[]   - 32-bit offsets of each superchunk, to the end of header chunk.
                     All offsets are relative to the first superchunk.
                     Last table entry points to the end of file.
  • It is not neccessary to use offsets table to (quickly) navigate through the file. All superchunks comes without gaps inbetween.


Header chunk followed by one or more frame superchunks. Superchunk format:

 word superchunk_size - size of the whole superchunk
   byte chunk1[]    
   ...                - zero or more typed chunks
   byte chunkN[]
   byte videoframe[]  - video chunk to the end of superchunk

As soon as videoframe chunk encountered and processed, decoder should continue to the next superchunk.


Typed chunk format:

 word chunk_tag          - TwoCC chunk id
 word chunk_size         - size of the chunk
 byte data[chunk_size-4] - chunk data of (chunk_size-4) bytes total

Palette Block

Palette block contains one or more palette ranges to update.

 byte start            - index of the first entry to update
 byte count            - number of entries to update (0 means 256)
 byte rgb[count*3]     - RGB triplets of the new palette

Start and count both set to 0xFF indicates end of the palette block. Otherwise, change specified colors and process to the next range update block.

Typed chunks

pl Chunk

New palette block. See description above.

sd Chunk

Sound data. Contains complete VOC file splitted over several blocks.

pt Chunk

Game related, ignore.

kl Chunk

Collision map? Game related, ignore.

Video Chunk

Unlike typed chunks, video chunk has no standard chunk header, thus, it's the last chunk of superchunk. It has at least following 4-byte header in bits:

  0..8 width  - width of the frame
     9 flag1  - supposedly, indicates video data presence
    10 flag2  - supposedly, indicates that the frame is not overlayed
12..14 unused - you can use these (zero) bits to distinguish this chunk from typed ones
    15 flag3  - rarely seen set on some frames, purpose not yet known
16..23 height - height of the frame
24..31 mode   - frame rendering mode
  • Each new frame may update only partial area of actual 320x200 viewport.
  • Zero frame width/height indicates that video is not changed from the previous frame, thus, no any video data follows this header.
  • Mode 0xFE renders whole unpacked frame over previous frame.
  • Mode 0xFF overlays unpacked frame with previous frame, it's pixels with color index 0 indicates transparency.

Video Decompression

If video height is non-zero, frame chunks continues with actual compressed frame data. It may be either legacy LZ-compressed raster or RLE-encoded overlay frame.

First, calculate sum of the first 6 bytes of the frame data. Depending on the calculated value, choose apropriate method.

Video Block 171

A keyframe. Unpack whole block using following algorithm (same algorithm used to decode some other game's resources as well).

First, reinterpret 6-byte header (the one you've calculated checksum of before) as follows:

 word unpacked_len - size of the unpacked data
 byte zero         - should be zero
 word packed_len   - size of the packed data, including this header
 byte salt         - adjust checksum to 171 (0xAB)


Next, perform actual decompression: Initialize bit-reader with 16-bits internal queue. When you need to take next bit from queue but it's already fully depleted, refill it with next 16-bits value from the stream. Straw queue bits from lowest to highest.

 repeat
   get next bit of the queue
   if bit is non-zero
     copy next byte of the input to the output
   else
     get next bit of the queue
     if bit is non-zero
       count = next 3 bits of the input
       offset = next 13 bits of the input minus 8192
       if count is zero
         count = next 8 bits of the input
       if count is zero
         finish the unpacking
     else
       count = next bit of the queue * 2 + next bit of the queue
       offset = next 8 bits of the input minus 256
     count += 2
     copy count bytes at (output + offset) to the output

Video Block 172

Looks like it's not used anywhere. TBD.

Video Block 173

This is an overlay block. Reinterpret block header as:

 word frame_size    - size of the final unpacked frame (in bytes)
 word codebook_size - size of the unpacked frame codebook
 byte flags         - bit 2 : use zero x/y coordinates
                      bit 6 : use color base 128, 0 otherwise
                      bit 7 : compression method (long runs)
 byte salt          - fixes up header's checksum to be 173 (0xAD)

If flags bit 2 is zero, read extra block's x and y coordinates, 16 bits each. Otherwise, assume them both set to zero.

Following decompression routines use slightly modified bit-reader. Unlike 171 decompression, now it takes bits starting from the highest one. Other rules remains the same.

After processing the header, unpack LZ-compressed codebook as follows:

 repeat while codebook_size of output not generated
   tag = next byte of the input
   if bit 7 of tag is set
     if length_code not yet encountered or already depleted
       length_code = next 8 byte of the input
       temp = top nibble of the length_code
     else
       temp = low nibble of the length_code
       assume length_code depleted
     offset = 7 bits of tag merged with lowest bit of temp
     count = top 3 bits of temp plus 2
     copy count bytes at (output - offset - 1) to the output
   else
     if tag is non-zero
       tag = tag + color base (see flags bit 6)
     output tag


Immediately after codebook rle-compressed raster begins. Decode it as follows:

 if using long runs method (see flags above)
   repeat while frame_size of output not generated
     while next bit is zero
       ouput = next byte of codebook
     pixel = next byte of codebook
     if next bit is zero
       if length_code not yet encountered or already depleted
         length_code = next byte of the input
         length = top nibble of the length_code
       else
         length = low nibble of the length_code
         assume length_code depleted
       if length is zero
         length = next byte of the input plus 16
       fill next length + 4 bytes of the output with pixel
     else
       if next bit is zero
         fill next 2 bytes of the output with pixel
       else
         if next bit is zero
           fill next 3 bytes of the output with pixel
         else
           fill next 4 bytes of the output with pixel
 else (short runs, same as above, but reordered)
   repeat while frame_size of output not generated
     while next bit is zero
       ouput = next byte of codebook
     pixel = next byte of codebook
     if next bit is zero
       fill next 2 bytes of the output with pixel
     else
       if next bit is zero
         fill next 3 bytes of the output with pixel
       else
         if next bit is zero
           fill next 4 bytes of the output with pixel
         else
           if length_code not yet encountered or already depleted
             length_code = next byte of the input
             length = top nibble of the length_code
           else
             length = low nibble of the length_code
             assume length_code depleted
           if length is zero
             length = next byte of the input plus 16
           fill next length + 4 bytes of the output with pixel

Known issues

  • Many movies from the game "Dune" do not have their own palettes and thereby reuse the current in-game palette.
  • For some unknown reasons AABBBBB.HNM file from Dune decodes incorrectly.

Games using HNM (1)