Warcraft II map files: Lua parsing library

Warcraft II game engine uses this format for map files. External maps can be edited by official Warcraft II map editor and saved in .pud files. Maps supplied with the game (i.e. single player campaign) follow the same format, but are instead embedded inside the game container files.

There are two major versions: 0x11 (original one) and 0x13 (roughly corresponds to v1.33 of the game engine, although some of the features got limited support in v1.3).

File consists of a sequence of typed sections.

Application

Warcraft II engine

File extension

pud

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.6

References

This page hosts a formal specification of Warcraft II map files using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Lua source code to parse Warcraft II map files

warcraft_2_pud.lua

-- This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
--
-- This file is compatible with Lua 5.3

local class = require("class")
require("kaitaistruct")
local enum = require("enum")
local str_decode = require("string_decode")
local stringstream = require("string_stream")

-- 
-- Warcraft II game engine uses this format for map files. External
-- maps can be edited by official Warcraft II map editor and saved in
-- .pud files. Maps supplied with the game (i.e. single player
-- campaign) follow the same format, but are instead embedded inside
-- the game container files.
-- 
-- There are two major versions: 0x11 (original one) and 0x13 (roughly
-- corresponds to v1.33 of the game engine, although some of the
-- features got limited support in v1.3).
-- 
-- File consists of a sequence of typed sections.
-- See also: Source (http://cade.datamax.bg/war2x/pudspec.html)
Warcraft2Pud = class.class(KaitaiStruct)

Warcraft2Pud.Controller = enum.Enum {
  computer = 1,
  passive_computer = 2,
  nobody = 3,
  computer = 4,
  human = 5,
  rescue_passive = 6,
  rescue_active = 7,
}

Warcraft2Pud.TerrainType = enum.Enum {
  forest = 0,
  winter = 1,
  wasteland = 2,
  swamp = 3,
}

Warcraft2Pud.UnitType = enum.Enum {
  infantry = 0,
  grunt = 1,
  peasant = 2,
  peon = 3,
  ballista = 4,
  catapult = 5,
  knight = 6,
  ogre = 7,
  archer = 8,
  axethrower = 9,
  mage = 10,
  death_knight = 11,
  paladin = 12,
  ogre_mage = 13,
  dwarves = 14,
  goblin_sapper = 15,
  attack_peasant = 16,
  attack_peon = 17,
  ranger = 18,
  berserker = 19,
  alleria = 20,
  teron_gorefiend = 21,
  kurdan_and_sky_ree = 22,
  dentarg = 23,
  khadgar = 24,
  grom_hellscream = 25,
  human_tanker = 26,
  orc_tanker = 27,
  human_transport = 28,
  orc_transport = 29,
  elven_destroyer = 30,
  troll_destroyer = 31,
  battleship = 32,
  juggernaught = 33,
  deathwing = 35,
  gnomish_submarine = 38,
  giant_turtle = 39,
  gnomish_flying_machine = 40,
  goblin_zepplin = 41,
  gryphon_rider = 42,
  dragon = 43,
  turalyon = 44,
  eye_of_kilrogg = 45,
  danath = 46,
  khorgath_bladefist = 47,
  cho_gall = 49,
  lothar = 50,
  gul_dan = 51,
  uther_lightbringer = 52,
  zuljin = 53,
  skeleton = 55,
  daemon = 56,
  critter = 57,
  farm = 58,
  pig_farm = 59,
  human_barracks = 60,
  orc_barracks = 61,
  church = 62,
  altar_of_storms = 63,
  human_scout_tower = 64,
  orc_scout_tower = 65,
  stables = 66,
  ogre_mound = 67,
  gnomish_inventor = 68,
  goblin_alchemist = 69,
  gryphon_aviary = 70,
  dragon_roost = 71,
  human_shipyard = 72,
  orc_shipyard = 73,
  town_hall = 74,
  great_hall = 75,
  elven_lumber_mill = 76,
  troll_lumber_mill = 77,
  human_foundry = 78,
  orc_foundry = 79,
  mage_tower = 80,
  temple_of_the_damned = 81,
  human_blacksmith = 82,
  orc_blacksmith = 83,
  human_refinery = 84,
  orc_refinery = 85,
  human_oil_well = 86,
  orc_oil_well = 87,
  keep = 88,
  stronghold = 89,
  castle = 90,
  fortress = 91,
  gold_mine = 92,
  oil_patch = 93,
  human_start = 94,
  orc_start = 95,
  human_guard_tower = 96,
  orc_guard_tower = 97,
  human_cannon_tower = 98,
  orc_cannon_tower = 99,
  circle_of_power = 100,
  dark_portal = 101,
  runestone = 102,
  human_wall = 103,
  orc_wall = 104,
}

function Warcraft2Pud:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud:_read()
  self.sections = {}
  local i = 0
  while not self._io:is_eof() do
    self.sections[i + 1] = Warcraft2Pud.Section(self._io, self, self._root)
    i = i + 1
  end
end


Warcraft2Pud.SectionStartingResource = class.class(KaitaiStruct)

function Warcraft2Pud.SectionStartingResource:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.SectionStartingResource:_read()
  self.resources_by_player = {}
  local i = 0
  while not self._io:is_eof() do
    self.resources_by_player[i + 1] = self._io:read_u2le()
    i = i + 1
  end
end


-- 
-- Section that specifies terrain type for this map.
Warcraft2Pud.SectionEra = class.class(KaitaiStruct)

function Warcraft2Pud.SectionEra:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.SectionEra:_read()
  self.terrain = Warcraft2Pud.TerrainType(self._io:read_u2le())
end


-- 
-- Section that specifies format version.
Warcraft2Pud.SectionVer = class.class(KaitaiStruct)

function Warcraft2Pud.SectionVer:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.SectionVer:_read()
  self.version = self._io:read_u2le()
end


Warcraft2Pud.SectionDim = class.class(KaitaiStruct)

function Warcraft2Pud.SectionDim:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.SectionDim:_read()
  self.x = self._io:read_u2le()
  self.y = self._io:read_u2le()
end


-- 
-- Section that confirms that this file is a "map file" by certain
-- magic string and supplies a tag that could be used in
-- multiplayer to check that all player use the same version of the
-- map.
Warcraft2Pud.SectionType = class.class(KaitaiStruct)

function Warcraft2Pud.SectionType:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.SectionType:_read()
  self.magic = self._io:read_bytes(10)
  if not(self.magic == "\087\065\082\050\032\077\065\080\000\000") then
    error("not equal, expected " ..  "\087\065\082\050\032\077\065\080\000\000" .. ", but got " .. self.magic)
  end
  self.unused = self._io:read_bytes(2)
  self.id_tag = self._io:read_u4le()
end

-- 
-- unused (always set to $0a and $ff by editor, but can be anything for the game).
-- 
-- id tag (for consistency check in multiplayer).

Warcraft2Pud.SectionUnit = class.class(KaitaiStruct)

function Warcraft2Pud.SectionUnit:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.SectionUnit:_read()
  self.units = {}
  local i = 0
  while not self._io:is_eof() do
    self.units[i + 1] = Warcraft2Pud.Unit(self._io, self, self._root)
    i = i + 1
  end
end


Warcraft2Pud.Section = class.class(KaitaiStruct)

function Warcraft2Pud.Section:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.Section:_read()
  self.name = str_decode.decode(self._io:read_bytes(4), "ASCII")
  self.size = self._io:read_u4le()
  local _on = self.name
  if _on == "SLBR" then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionStartingResource(_io, self, self._root)
  elseif _on == "ERAX" then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionEra(_io, self, self._root)
  elseif _on == "OWNR" then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionOwnr(_io, self, self._root)
  elseif _on == "ERA " then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionEra(_io, self, self._root)
  elseif _on == "SGLD" then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionStartingResource(_io, self, self._root)
  elseif _on == "VER " then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionVer(_io, self, self._root)
  elseif _on == "SOIL" then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionStartingResource(_io, self, self._root)
  elseif _on == "UNIT" then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionUnit(_io, self, self._root)
  elseif _on == "DIM " then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionDim(_io, self, self._root)
  elseif _on == "TYPE" then
    self._raw_body = self._io:read_bytes(self.size)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Warcraft2Pud.SectionType(_io, self, self._root)
  else
    self.body = self._io:read_bytes(self.size)
  end
end


-- 
-- Section that specifies who controls each player.
Warcraft2Pud.SectionOwnr = class.class(KaitaiStruct)

function Warcraft2Pud.SectionOwnr:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.SectionOwnr:_read()
  self.controller_by_player = {}
  local i = 0
  while not self._io:is_eof() do
    self.controller_by_player[i + 1] = Warcraft2Pud.Controller(self._io:read_u1())
    i = i + 1
  end
end


Warcraft2Pud.Unit = class.class(KaitaiStruct)

function Warcraft2Pud.Unit:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Warcraft2Pud.Unit:_read()
  self.x = self._io:read_u2le()
  self.y = self._io:read_u2le()
  self.u_type = Warcraft2Pud.UnitType(self._io:read_u1())
  self.owner = self._io:read_u1()
  self.options = self._io:read_u2le()
end

Warcraft2Pud.Unit.property.resource = {}
function Warcraft2Pud.Unit.property.resource:get()
  if self._m_resource ~= nil then
    return self._m_resource
  end

  if  ((self.u_type == Warcraft2Pud.UnitType.gold_mine) or (self.u_type == Warcraft2Pud.UnitType.human_oil_well) or (self.u_type == Warcraft2Pud.UnitType.orc_oil_well) or (self.u_type == Warcraft2Pud.UnitType.oil_patch))  then
    self._m_resource = (self.options * 2500)
  end
  return self._m_resource
end

-- 
-- if gold mine or oil well, contains 2500 * this, otherwise 0 passive 1 active.