This page hosts a formal specification of .pak file format of Games based on Haxe Game Framework "Heaps" (e.g. Dead Cells) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
digraph {
rankdir=LR;
node [shape=plaintext];
subgraph cluster__heaps_pak {
label="HeapsPak";
graph[style=dotted];
heaps_pak__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="header_pos">0</TD><TD PORT="header_size">...</TD><TD>Header</TD><TD PORT="header_type">header</TD></TR>
</TABLE>>];
subgraph cluster__header {
label="HeapsPak::Header";
graph[style=dotted];
header__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="magic1_pos">0</TD><TD PORT="magic1_size">3</TD><TD></TD><TD PORT="magic1_type">magic1</TD></TR>
<TR><TD PORT="version_pos">3</TD><TD PORT="version_size">1</TD><TD>u1</TD><TD PORT="version_type">version</TD></TR>
<TR><TD PORT="len_header_pos">4</TD><TD PORT="len_header_size">4</TD><TD>u4le</TD><TD PORT="len_header_type">len_header</TD></TR>
<TR><TD PORT="len_data_pos">8</TD><TD PORT="len_data_size">4</TD><TD>u4le</TD><TD PORT="len_data_type">len_data</TD></TR>
<TR><TD PORT="root_entry_pos">12</TD><TD PORT="root_entry_size">(len_header - 16)</TD><TD>Entry</TD><TD PORT="root_entry_type">root_entry</TD></TR>
<TR><TD PORT="magic2_pos">...</TD><TD PORT="magic2_size">4</TD><TD></TD><TD PORT="magic2_type">magic2</TD></TR>
</TABLE>>];
subgraph cluster__entry {
label="HeapsPak::Header::Entry";
graph[style=dotted];
entry__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="len_name_pos">0</TD><TD PORT="len_name_size">1</TD><TD>u1</TD><TD PORT="len_name_type">len_name</TD></TR>
<TR><TD PORT="name_pos">1</TD><TD PORT="name_size">len_name</TD><TD>str(UTF-8)</TD><TD PORT="name_type">name</TD></TR>
<TR><TD PORT="flags_pos">...</TD><TD PORT="flags_size">1</TD><TD>Flags</TD><TD PORT="flags_type">flags</TD></TR>
<TR><TD PORT="body_pos">...</TD><TD PORT="body_size">...</TD><TD>switch (flags.is_dir)</TD><TD PORT="body_type">body</TD></TR>
</TABLE>>];
entry__seq_body_switch [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#F0F2E4">case</TD><TD BGCOLOR="#F0F2E4">type</TD></TR>
<TR><TD>true</TD><TD PORT="case0">Dir</TD></TR>
<TR><TD>false</TD><TD PORT="case1">File</TD></TR>
</TABLE>>];
subgraph cluster__flags {
label="HeapsPak::Header::Entry::Flags";
graph[style=dotted];
flags__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="unused_pos">0</TD><TD PORT="unused_size">7b</TD><TD>b7</TD><TD PORT="unused_type">unused</TD></TR>
<TR><TD PORT="is_dir_pos">0:7</TD><TD PORT="is_dir_size">1b</TD><TD>BitsType1(BigBitEndian)</TD><TD PORT="is_dir_type">is_dir</TD></TR>
</TABLE>>];
}
}
subgraph cluster__file {
label="HeapsPak::Header::File";
graph[style=dotted];
file__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="ofs_data_pos">0</TD><TD PORT="ofs_data_size">4</TD><TD>u4le</TD><TD PORT="ofs_data_type">ofs_data</TD></TR>
<TR><TD PORT="len_data_pos">4</TD><TD PORT="len_data_size">4</TD><TD>u4le</TD><TD PORT="len_data_type">len_data</TD></TR>
<TR><TD PORT="checksum_pos">8</TD><TD PORT="checksum_size">4</TD><TD></TD><TD PORT="checksum_type">checksum</TD></TR>
</TABLE>>];
file__inst__data [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="data_pos">(_root.header.len_header + ofs_data)</TD><TD PORT="data_size">len_data</TD><TD></TD><TD PORT="data_type">data</TD></TR>
</TABLE>>];
}
subgraph cluster__dir {
label="HeapsPak::Header::Dir";
graph[style=dotted];
dir__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="num_entries_pos">0</TD><TD PORT="num_entries_size">4</TD><TD>u4le</TD><TD PORT="num_entries_type">num_entries</TD></TR>
<TR><TD PORT="entries_pos">4</TD><TD PORT="entries_size">...</TD><TD>Entry</TD><TD PORT="entries_type">entries</TD></TR>
<TR><TD COLSPAN="4" PORT="entries__repeat">repeat num_entries times</TD></TR>
</TABLE>>];
}
}
}
heaps_pak__seq:header_type -> header__seq [style=bold];
header__seq:len_header_type -> header__seq:root_entry_size [color="#404040"];
header__seq:root_entry_type -> entry__seq [style=bold];
entry__seq:len_name_type -> entry__seq:name_size [color="#404040"];
entry__seq:flags_type -> flags__seq [style=bold];
entry__seq:body_type -> entry__seq_body_switch [style=bold];
entry__seq_body_switch:case0 -> dir__seq [style=bold];
entry__seq_body_switch:case1 -> file__seq [style=bold];
flags__seq:is_dir_type -> entry__seq:body_type [color="#404040"];
header__seq:len_header_type -> file__inst__data:data_pos [color="#404040"];
file__seq:ofs_data_type -> file__inst__data:data_pos [color="#404040"];
file__seq:len_data_type -> file__inst__data:data_size [color="#404040"];
dir__seq:entries_type -> entry__seq [style=bold];
dir__seq:num_entries_type -> dir__seq:entries__repeat [color="#404040"];
}