RPK

RPK is an acronym for Rayform Package, and can be found inside the game directory with the game EXE. The RPK format is a package meant for storing the game's assets into one file. These formats can store thousands of assets in just one package which would amount to thousands of files on the file system without it. Packages can be nested within another package.

Under the hood, there is a table of entries containing information about each asset. An entry in this context is a game asset, and the words entry/asset will be used interchangeably.

Layout

The figure below depicts the layout of the RPK format.

block-beta
  columns 4
    block:header:2
      magic["magic<br/>(<span class='type'>u32</span>)"]
      table_size_bytes["table_size_bytes<br/>(<span class='type'>u32</span>)"]
    end

    table_entries["table_entries<br/>(<span class='type'>Vec&lt;TableEntry&gt;</span>)"]

    chunks["chunks<br/>(<span class='type'>Vec&lt;u8&gt;</span>)"]

    space:4

    block:entry_layout:4
      name["name<br/>(<span class='type'>[u8; 16]</span>)"]
      offset["offset<br/>(<span class='type'>u32</span>)"]
      entry_size["size<br/>(<span class='type'>u32</span>)"]
      padding["padding<br/>(<span class='type'>[u32; 2]</span>)"]
    end

    table_entries --> entry_layout

The header consists of only eight bytes.

#![allow(unused)]
fn main() {
struct RpkHeader {
  magic: u32,
  table_size_bytes: u32
}
}

magic MUST always be 0xAFBF0C01. There are in total four Exanima formats with the same magic: FDS, FLB, RML, and RPK. The layout is the same across all of them.

table_size_bytes is the total amount of bytes in the table. Each entry in the table is 32 bytes long which means this can be divided by 32 to get the total amount of entries. An entry is correlated to an asset that can be dumped into a file. As an example, in Exanima 0.9, the Apparel.rpk has a size of 54688 bytes in it's table which is 1709 when divided by 32. That is 1,709 assets just in that one RPK file.

Table Entry

Each entry has the following structure:

#![allow(unused)]
fn main() {
struct TableEntry {
  name: [u8; 16],
  offset: u32,
  size: u32,
  padding: [u32; 2],
}
}

name is NOT null-terminated and MUST be 16 bytes long. If the name is not 16 characters long, the remaining bytes will all be 0.

offset is used to find the starting position of the asset's chunk when the reader position is at the end of the table.

size indicates how big the asset's chunk is in bytes.

padding is always 0.