.pak file format of Games based on Haxe Game Framework "Heaps" (e.g. Dead Cells): Nim 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.

Nim source code to parse .pak file format of Games based on Haxe Game Framework "Heaps" (e.g. Dead Cells)

heaps_pak.nim

import kaitai_struct_nim_runtime
import options

type
  HeapsPak* = ref object of KaitaiStruct
    `header`*: HeapsPak_Header
    `parent`*: KaitaiStruct
  HeapsPak_Header* = ref object of KaitaiStruct
    `magic1`*: seq[byte]
    `version`*: uint8
    `lenHeader`*: uint32
    `lenData`*: uint32
    `rootEntry`*: HeapsPak_Header_Entry
    `magic2`*: seq[byte]
    `parent`*: HeapsPak
    `rawRootEntry`*: seq[byte]
  HeapsPak_Header_Entry* = ref object of KaitaiStruct
    `lenName`*: uint8
    `name`*: string
    `flags`*: HeapsPak_Header_Entry_Flags
    `body`*: KaitaiStruct
    `parent`*: KaitaiStruct
  HeapsPak_Header_Entry_Flags* = ref object of KaitaiStruct
    `unused`*: uint64
    `isDir`*: bool
    `parent`*: HeapsPak_Header_Entry
  HeapsPak_Header_File* = ref object of KaitaiStruct
    `ofsData`*: uint32
    `lenData`*: uint32
    `checksum`*: seq[byte]
    `parent`*: HeapsPak_Header_Entry
    `dataInst`: seq[byte]
    `dataInstFlag`: bool
  HeapsPak_Header_Dir* = ref object of KaitaiStruct
    `numEntries`*: uint32
    `entries`*: seq[HeapsPak_Header_Entry]
    `parent`*: HeapsPak_Header_Entry

proc read*(_: typedesc[HeapsPak], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): HeapsPak
proc read*(_: typedesc[HeapsPak_Header], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak): HeapsPak_Header
proc read*(_: typedesc[HeapsPak_Header_Entry], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): HeapsPak_Header_Entry
proc read*(_: typedesc[HeapsPak_Header_Entry_Flags], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak_Header_Entry): HeapsPak_Header_Entry_Flags
proc read*(_: typedesc[HeapsPak_Header_File], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak_Header_Entry): HeapsPak_Header_File
proc read*(_: typedesc[HeapsPak_Header_Dir], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak_Header_Entry): HeapsPak_Header_Dir

proc data*(this: HeapsPak_Header_File): seq[byte]


##[
@see <a href="https://github.com/HeapsIO/heaps/blob/2bbc2b386952dfd8856c04a854bb706a52cb4b58/hxd/fmt/pak/Reader.hx">Source</a>
]##
proc read*(_: typedesc[HeapsPak], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): HeapsPak =
  template this: untyped = result
  this = new(HeapsPak)
  let root = if root == nil: cast[HeapsPak](this) else: cast[HeapsPak](root)
  this.io = io
  this.root = root
  this.parent = parent

  let headerExpr = HeapsPak_Header.read(this.io, this.root, this)
  this.header = headerExpr

proc fromFile*(_: typedesc[HeapsPak], filename: string): HeapsPak =
  HeapsPak.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[HeapsPak_Header], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak): HeapsPak_Header =
  template this: untyped = result
  this = new(HeapsPak_Header)
  let root = if root == nil: cast[HeapsPak](this) else: cast[HeapsPak](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magic1Expr = this.io.readBytes(int(3))
  this.magic1 = magic1Expr
  let versionExpr = this.io.readU1()
  this.version = versionExpr
  let lenHeaderExpr = this.io.readU4le()
  this.lenHeader = lenHeaderExpr
  let lenDataExpr = this.io.readU4le()
  this.lenData = lenDataExpr
  let rawRootEntryExpr = this.io.readBytes(int((this.lenHeader - 16)))
  this.rawRootEntry = rawRootEntryExpr
  let rawRootEntryIo = newKaitaiStream(rawRootEntryExpr)
  let rootEntryExpr = HeapsPak_Header_Entry.read(rawRootEntryIo, this.root, this)
  this.rootEntry = rootEntryExpr
  let magic2Expr = this.io.readBytes(int(4))
  this.magic2 = magic2Expr

proc fromFile*(_: typedesc[HeapsPak_Header], filename: string): HeapsPak_Header =
  HeapsPak_Header.read(newKaitaiFileStream(filename), nil, nil)


##[
@see <a href="https://github.com/HeapsIO/heaps/blob/2bbc2b386952dfd8856c04a854bb706a52cb4b58/hxd/fmt/pak/Data.hx">Source</a>
]##
proc read*(_: typedesc[HeapsPak_Header_Entry], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): HeapsPak_Header_Entry =
  template this: untyped = result
  this = new(HeapsPak_Header_Entry)
  let root = if root == nil: cast[HeapsPak](this) else: cast[HeapsPak](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenNameExpr = this.io.readU1()
  this.lenName = lenNameExpr
  let nameExpr = encode(this.io.readBytes(int(this.lenName)), "UTF-8")
  this.name = nameExpr
  let flagsExpr = HeapsPak_Header_Entry_Flags.read(this.io, this.root, this)
  this.flags = flagsExpr
  block:
    let on = this.flags.isDir
    if on == true:
      let bodyExpr = HeapsPak_Header_Dir.read(this.io, this.root, this)
      this.body = bodyExpr
    elif on == false:
      let bodyExpr = HeapsPak_Header_File.read(this.io, this.root, this)
      this.body = bodyExpr

proc fromFile*(_: typedesc[HeapsPak_Header_Entry], filename: string): HeapsPak_Header_Entry =
  HeapsPak_Header_Entry.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[HeapsPak_Header_Entry_Flags], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak_Header_Entry): HeapsPak_Header_Entry_Flags =
  template this: untyped = result
  this = new(HeapsPak_Header_Entry_Flags)
  let root = if root == nil: cast[HeapsPak](this) else: cast[HeapsPak](root)
  this.io = io
  this.root = root
  this.parent = parent

  let unusedExpr = this.io.readBitsIntBe(7)
  this.unused = unusedExpr
  let isDirExpr = this.io.readBitsIntBe(1) != 0
  this.isDir = isDirExpr

proc fromFile*(_: typedesc[HeapsPak_Header_Entry_Flags], filename: string): HeapsPak_Header_Entry_Flags =
  HeapsPak_Header_Entry_Flags.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[HeapsPak_Header_File], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak_Header_Entry): HeapsPak_Header_File =
  template this: untyped = result
  this = new(HeapsPak_Header_File)
  let root = if root == nil: cast[HeapsPak](this) else: cast[HeapsPak](root)
  this.io = io
  this.root = root
  this.parent = parent

  let ofsDataExpr = this.io.readU4le()
  this.ofsData = ofsDataExpr
  let lenDataExpr = this.io.readU4le()
  this.lenData = lenDataExpr
  let checksumExpr = this.io.readBytes(int(4))
  this.checksum = checksumExpr

proc data(this: HeapsPak_Header_File): seq[byte] = 
  if this.dataInstFlag:
    return this.dataInst
  let io = HeapsPak(this.root).io
  let pos = io.pos()
  io.seek(int((HeapsPak(this.root).header.lenHeader + this.ofsData)))
  let dataInstExpr = io.readBytes(int(this.lenData))
  this.dataInst = dataInstExpr
  io.seek(pos)
  this.dataInstFlag = true
  return this.dataInst

proc fromFile*(_: typedesc[HeapsPak_Header_File], filename: string): HeapsPak_Header_File =
  HeapsPak_Header_File.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[HeapsPak_Header_Dir], io: KaitaiStream, root: KaitaiStruct, parent: HeapsPak_Header_Entry): HeapsPak_Header_Dir =
  template this: untyped = result
  this = new(HeapsPak_Header_Dir)
  let root = if root == nil: cast[HeapsPak](this) else: cast[HeapsPak](root)
  this.io = io
  this.root = root
  this.parent = parent

  let numEntriesExpr = this.io.readU4le()
  this.numEntries = numEntriesExpr
  for i in 0 ..< int(this.numEntries):
    let it = HeapsPak_Header_Entry.read(this.io, this.root, this)
    this.entries.add(it)

proc fromFile*(_: typedesc[HeapsPak_Header_Dir], filename: string): HeapsPak_Header_Dir =
  HeapsPak_Header_Dir.read(newKaitaiFileStream(filename), nil, nil)