NPK Format - niemasd/PyFF7 GitHub Wiki

NPK is an archive file format and is used in some file packages in Disc 4 of Final Fantasy VII's International Edition. Files are always LZSS-compressed, but they are broken into chunks . You must first decompress an LZSS-compressed NPK archive (e.g. using lzss_decompress.py) before you can play with it. This blog post might be a useful reference for the general structure of the NPK archive (and for a fun story).

Data Blocks

The NPK file is broken into multiple blocks of exactly 1,024 bytes. Each block has the following components:

4 bytes: Number of Sub-Blocks

  • This is a 4-byte unsigned integer denoting the number of remaining blocks (including this one) that are a part of the current output file
    • If this is 1, this block is the last data block of the current output file
    • If this is 0, the current output file is finished, and this block has no data

2 bytes: Compressed Size

  • This is a 2-byte unsigned integer denoting the size (in bytes) of this block's compressed data

2 bytes: Decompressed Size

  • This is a 2-byte unsigned integer denoting the size (in bytes) of this block's compressed data when it's decompressed

LZSS-Compressed Data

  • All the remaining "Compressed Size" bytes are the actual compressed data

Unpacking NPK Files

Given that I've already implemented an LZSS file decompressor (lzss_decompress.py), unpacking NPK files is easy. Below is an algorithm to do so:

current_file = empty list of bytes
for each 1024 block in the NPK file:
    num_subblocks = first 4 bytes of block (unsigned integer)
    size_compressed = next 2 bytes of block (unsigned integer)
    size_decompressed = next 2 bytes of block (unsigned integer)
    if num_subblocks != 0:
        block_data = the size_compressed blocks starting with the 9th byte (1-based counting)
        append block_data to the end of current_file
    if num_subblocks <= 1 and current_file isn't empty:
        current_file = (length of current_file as a 4-byte unsigned integer) + current_file # create and add LZSS header
        current_file_decompressed = lzss_decompress(current_file)