.pak file format of Games based on Haxe Game Framework "Heaps" (e.g. Dead Cells): C++98/STL parsing library

Application

Games based on Haxe Game Framework "Heaps" (e.g. Dead Cells)

File extension

pak

KS implementation details

License: MIT

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.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.pak", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    heaps_pak_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.header() // => get header

C++98/STL source code to parse .pak file format of Games based on Haxe Game Framework "Heaps" (e.g. Dead Cells)

heaps_pak.h

#ifndef HEAPS_PAK_H_
#define HEAPS_PAK_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * \sa https://github.com/HeapsIO/heaps/blob/2bbc2b386952dfd8856c04a854bb706a52cb4b58/hxd/fmt/pak/Reader.hx Source
 */

class heaps_pak_t : public kaitai::kstruct {

public:
    class header_t;

    heaps_pak_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, heaps_pak_t* p__root = 0);

private:
    void _read();
    void _clean_up();

public:
    ~heaps_pak_t();

    class header_t : public kaitai::kstruct {

    public:
        class entry_t;
        class file_t;
        class dir_t;

        header_t(kaitai::kstream* p__io, heaps_pak_t* p__parent = 0, heaps_pak_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

        /**
         * \sa https://github.com/HeapsIO/heaps/blob/2bbc2b386952dfd8856c04a854bb706a52cb4b58/hxd/fmt/pak/Data.hx Source
         */

        class entry_t : public kaitai::kstruct {

        public:
            class flags_t;

            entry_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, heaps_pak_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~entry_t();

            class flags_t : public kaitai::kstruct {

            public:

                flags_t(kaitai::kstream* p__io, heaps_pak_t::header_t::entry_t* p__parent = 0, heaps_pak_t* p__root = 0);

            private:
                void _read();
                void _clean_up();

            public:
                ~flags_t();

            private:
                uint64_t m_unused;
                bool m_is_dir;
                heaps_pak_t* m__root;
                heaps_pak_t::header_t::entry_t* m__parent;

            public:
                uint64_t unused() const { return m_unused; }
                bool is_dir() const { return m_is_dir; }
                heaps_pak_t* _root() const { return m__root; }
                heaps_pak_t::header_t::entry_t* _parent() const { return m__parent; }
            };

        private:
            uint8_t m_len_name;
            std::string m_name;
            flags_t* m_flags;
            kaitai::kstruct* m_body;
            bool n_body;

        public:
            bool _is_null_body() { body(); return n_body; };

        private:
            heaps_pak_t* m__root;
            kaitai::kstruct* m__parent;

        public:
            uint8_t len_name() const { return m_len_name; }
            std::string name() const { return m_name; }
            flags_t* flags() const { return m_flags; }
            kaitai::kstruct* body() const { return m_body; }
            heaps_pak_t* _root() const { return m__root; }
            kaitai::kstruct* _parent() const { return m__parent; }
        };

        class file_t : public kaitai::kstruct {

        public:

            file_t(kaitai::kstream* p__io, heaps_pak_t::header_t::entry_t* p__parent = 0, heaps_pak_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~file_t();

        private:
            bool f_data;
            std::string m_data;

        public:
            std::string data();

        private:
            uint32_t m_ofs_data;
            uint32_t m_len_data;
            std::string m_checksum;
            heaps_pak_t* m__root;
            heaps_pak_t::header_t::entry_t* m__parent;

        public:
            uint32_t ofs_data() const { return m_ofs_data; }
            uint32_t len_data() const { return m_len_data; }
            std::string checksum() const { return m_checksum; }
            heaps_pak_t* _root() const { return m__root; }
            heaps_pak_t::header_t::entry_t* _parent() const { return m__parent; }
        };

        class dir_t : public kaitai::kstruct {

        public:

            dir_t(kaitai::kstream* p__io, heaps_pak_t::header_t::entry_t* p__parent = 0, heaps_pak_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~dir_t();

        private:
            uint32_t m_num_entries;
            std::vector<entry_t*>* m_entries;
            heaps_pak_t* m__root;
            heaps_pak_t::header_t::entry_t* m__parent;

        public:
            uint32_t num_entries() const { return m_num_entries; }
            std::vector<entry_t*>* entries() const { return m_entries; }
            heaps_pak_t* _root() const { return m__root; }
            heaps_pak_t::header_t::entry_t* _parent() const { return m__parent; }
        };

    private:
        std::string m_magic1;
        uint8_t m_version;
        uint32_t m_len_header;
        uint32_t m_len_data;
        entry_t* m_root_entry;
        std::string m_magic2;
        heaps_pak_t* m__root;
        heaps_pak_t* m__parent;
        std::string m__raw_root_entry;
        kaitai::kstream* m__io__raw_root_entry;

    public:
        std::string magic1() const { return m_magic1; }
        uint8_t version() const { return m_version; }
        uint32_t len_header() const { return m_len_header; }
        uint32_t len_data() const { return m_len_data; }
        entry_t* root_entry() const { return m_root_entry; }
        std::string magic2() const { return m_magic2; }
        heaps_pak_t* _root() const { return m__root; }
        heaps_pak_t* _parent() const { return m__parent; }
        std::string _raw_root_entry() const { return m__raw_root_entry; }
        kaitai::kstream* _io__raw_root_entry() const { return m__io__raw_root_entry; }
    };

private:
    header_t* m_header;
    heaps_pak_t* m__root;
    kaitai::kstruct* m__parent;

public:
    header_t* header() const { return m_header; }
    heaps_pak_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // HEAPS_PAK_H_

heaps_pak.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "heaps_pak.h"
#include "kaitai/exceptions.h"

heaps_pak_t::heaps_pak_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, heaps_pak_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_header = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void heaps_pak_t::_read() {
    m_header = new header_t(m__io, this, m__root);
}

heaps_pak_t::~heaps_pak_t() {
    _clean_up();
}

void heaps_pak_t::_clean_up() {
    if (m_header) {
        delete m_header; m_header = 0;
    }
}

heaps_pak_t::header_t::header_t(kaitai::kstream* p__io, heaps_pak_t* p__parent, heaps_pak_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_root_entry = 0;
    m__io__raw_root_entry = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void heaps_pak_t::header_t::_read() {
    m_magic1 = m__io->read_bytes(3);
    if (!(magic1() == std::string("\x50\x41\x4B", 3))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x50\x41\x4B", 3), magic1(), _io(), std::string("/types/header/seq/0"));
    }
    m_version = m__io->read_u1();
    m_len_header = m__io->read_u4le();
    m_len_data = m__io->read_u4le();
    m__raw_root_entry = m__io->read_bytes((len_header() - 16));
    m__io__raw_root_entry = new kaitai::kstream(m__raw_root_entry);
    m_root_entry = new entry_t(m__io__raw_root_entry, this, m__root);
    m_magic2 = m__io->read_bytes(4);
    if (!(magic2() == std::string("\x44\x41\x54\x41", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x44\x41\x54\x41", 4), magic2(), _io(), std::string("/types/header/seq/5"));
    }
}

heaps_pak_t::header_t::~header_t() {
    _clean_up();
}

void heaps_pak_t::header_t::_clean_up() {
    if (m__io__raw_root_entry) {
        delete m__io__raw_root_entry; m__io__raw_root_entry = 0;
    }
    if (m_root_entry) {
        delete m_root_entry; m_root_entry = 0;
    }
}

heaps_pak_t::header_t::entry_t::entry_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, heaps_pak_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void heaps_pak_t::header_t::entry_t::_read() {
    m_len_name = m__io->read_u1();
    m_name = kaitai::kstream::bytes_to_str(m__io->read_bytes(len_name()), std::string("UTF-8"));
    m_flags = new flags_t(m__io, this, m__root);
    n_body = true;
    {
        bool on = flags()->is_dir();
        if (on == true) {
            n_body = false;
            m_body = new dir_t(m__io, this, m__root);
        }
        else if (on == false) {
            n_body = false;
            m_body = new file_t(m__io, this, m__root);
        }
    }
}

heaps_pak_t::header_t::entry_t::~entry_t() {
    _clean_up();
}

void heaps_pak_t::header_t::entry_t::_clean_up() {
    if (m_flags) {
        delete m_flags; m_flags = 0;
    }
    if (!n_body) {
        if (m_body) {
            delete m_body; m_body = 0;
        }
    }
}

heaps_pak_t::header_t::entry_t::flags_t::flags_t(kaitai::kstream* p__io, heaps_pak_t::header_t::entry_t* p__parent, heaps_pak_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void heaps_pak_t::header_t::entry_t::flags_t::_read() {
    m_unused = m__io->read_bits_int_be(7);
    m_is_dir = m__io->read_bits_int_be(1);
}

heaps_pak_t::header_t::entry_t::flags_t::~flags_t() {
    _clean_up();
}

void heaps_pak_t::header_t::entry_t::flags_t::_clean_up() {
}

heaps_pak_t::header_t::file_t::file_t(kaitai::kstream* p__io, heaps_pak_t::header_t::entry_t* p__parent, heaps_pak_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_data = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void heaps_pak_t::header_t::file_t::_read() {
    m_ofs_data = m__io->read_u4le();
    m_len_data = m__io->read_u4le();
    m_checksum = m__io->read_bytes(4);
}

heaps_pak_t::header_t::file_t::~file_t() {
    _clean_up();
}

void heaps_pak_t::header_t::file_t::_clean_up() {
    if (f_data) {
    }
}

std::string heaps_pak_t::header_t::file_t::data() {
    if (f_data)
        return m_data;
    kaitai::kstream *io = _root()->_io();
    std::streampos _pos = io->pos();
    io->seek((_root()->header()->len_header() + ofs_data()));
    m_data = io->read_bytes(len_data());
    io->seek(_pos);
    f_data = true;
    return m_data;
}

heaps_pak_t::header_t::dir_t::dir_t(kaitai::kstream* p__io, heaps_pak_t::header_t::entry_t* p__parent, heaps_pak_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_entries = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void heaps_pak_t::header_t::dir_t::_read() {
    m_num_entries = m__io->read_u4le();
    m_entries = new std::vector<entry_t*>();
    const int l_entries = num_entries();
    for (int i = 0; i < l_entries; i++) {
        m_entries->push_back(new entry_t(m__io, this, m__root));
    }
}

heaps_pak_t::header_t::dir_t::~dir_t() {
    _clean_up();
}

void heaps_pak_t::header_t::dir_t::_clean_up() {
    if (m_entries) {
        for (std::vector<entry_t*>::iterator it = m_entries->begin(); it != m_entries->end(); ++it) {
            delete *it;
        }
        delete m_entries; m_entries = 0;
    }
}