.pak file format of Dune 2 game engine: Nim parsing library

Application

Dune 2 game engine

File extension

pak

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.8

This page hosts a formal specification of .pak file format of Dune 2 game engine 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 Dune 2 game engine

dune_2_pak.nim

import kaitai_struct_nim_runtime
import options

type
  Dune2Pak* = ref object of KaitaiStruct
    `dir`*: Dune2Pak_Files
    `parent`*: KaitaiStruct
    `rawDir`*: seq[byte]
    `dirSizeInst`: uint32
    `dirSizeInstFlag`: bool
  Dune2Pak_Files* = ref object of KaitaiStruct
    `files`*: seq[Dune2Pak_File]
    `parent`*: Dune2Pak
  Dune2Pak_File* = ref object of KaitaiStruct
    `ofs`*: uint32
    `fileName`*: string
    `idx`*: uint32
    `parent`*: Dune2Pak_Files
    `nextOfs0Inst`: uint32
    `nextOfs0InstFlag`: bool
    `nextOfsInst`: int
    `nextOfsInstFlag`: bool
    `bodyInst`: seq[byte]
    `bodyInstFlag`: bool

proc read*(_: typedesc[Dune2Pak], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Dune2Pak
proc read*(_: typedesc[Dune2Pak_Files], io: KaitaiStream, root: KaitaiStruct, parent: Dune2Pak): Dune2Pak_Files
proc read*(_: typedesc[Dune2Pak_File], io: KaitaiStream, root: KaitaiStruct, parent: Dune2Pak_Files, idx: any): Dune2Pak_File

proc dirSize*(this: Dune2Pak): uint32
proc nextOfs0*(this: Dune2Pak_File): uint32
proc nextOfs*(this: Dune2Pak_File): int
proc body*(this: Dune2Pak_File): seq[byte]


##[
@see <a href="https://moddingwiki.shikadi.net/wiki/PAK_Format_(Westwood)">Source</a>
]##
proc read*(_: typedesc[Dune2Pak], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Dune2Pak =
  template this: untyped = result
  this = new(Dune2Pak)
  let root = if root == nil: cast[Dune2Pak](this) else: cast[Dune2Pak](root)
  this.io = io
  this.root = root
  this.parent = parent

  let rawDirExpr = this.io.readBytes(int(this.dirSize))
  this.rawDir = rawDirExpr
  let rawDirIo = newKaitaiStream(rawDirExpr)
  let dirExpr = Dune2Pak_Files.read(rawDirIo, this.root, this)
  this.dir = dirExpr

proc dirSize(this: Dune2Pak): uint32 = 
  if this.dirSizeInstFlag:
    return this.dirSizeInst
  let pos = this.io.pos()
  this.io.seek(int(0))
  let dirSizeInstExpr = this.io.readU4le()
  this.dirSizeInst = dirSizeInstExpr
  this.io.seek(pos)
  this.dirSizeInstFlag = true
  return this.dirSizeInst

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

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

  block:
    var i: int
    while not this.io.isEof:
      let it = Dune2Pak_File.read(this.io, this.root, this, i)
      this.files.add(it)
      inc i

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

proc read*(_: typedesc[Dune2Pak_File], io: KaitaiStream, root: KaitaiStruct, parent: Dune2Pak_Files, idx: any): Dune2Pak_File =
  template this: untyped = result
  this = new(Dune2Pak_File)
  let root = if root == nil: cast[Dune2Pak](this) else: cast[Dune2Pak](root)
  this.io = io
  this.root = root
  this.parent = parent
  let idxExpr = uint32(idx)
  this.idx = idxExpr

  let ofsExpr = this.io.readU4le()
  this.ofs = ofsExpr
  if this.ofs != 0:
    let fileNameExpr = encode(this.io.readBytesTerm(0, false, true, true), "ASCII")
    this.fileName = fileNameExpr

proc nextOfs0(this: Dune2Pak_File): uint32 = 
  if this.nextOfs0InstFlag:
    return this.nextOfs0Inst
  if this.ofs != 0:
    let nextOfs0InstExpr = uint32(Dune2Pak(this.root).dir.files[(this.idx + 1)].ofs)
    this.nextOfs0Inst = nextOfs0InstExpr
  this.nextOfs0InstFlag = true
  return this.nextOfs0Inst

proc nextOfs(this: Dune2Pak_File): int = 
  if this.nextOfsInstFlag:
    return this.nextOfsInst
  if this.ofs != 0:
    let nextOfsInstExpr = int((if this.nextOfs0 == 0: Dune2Pak(this.root).io.size else: this.nextOfs0))
    this.nextOfsInst = nextOfsInstExpr
  this.nextOfsInstFlag = true
  return this.nextOfsInst

proc body(this: Dune2Pak_File): seq[byte] = 
  if this.bodyInstFlag:
    return this.bodyInst
  if this.ofs != 0:
    let io = Dune2Pak(this.root).io
    let pos = io.pos()
    io.seek(int(this.ofs))
    let bodyInstExpr = io.readBytes(int((this.nextOfs - this.ofs)))
    this.bodyInst = bodyInstExpr
    io.seek(pos)
  this.bodyInstFlag = true
  return this.bodyInst

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