ReplayDojo
Dota 2
For developers

What is inside a Dota 2 replay file

A developer's look at the Dota 2 .dem format: how replays are downloaded and compressed, what entities and the combat log contain, and which parsers read them.

· 8 min read

A map dissolving into a grid of sample points with one yellow trace

Open a .dem file and you won’t find frames. It holds the network messages the game server sent during the match. Reading one means replaying that stream into a model of the game’s entities, which is what the client does when you watch a replay and what a parser does when you want the data instead. This page covers what we learned building the ReplayDojo pipeline, which turns a match ID into a 3D replay in a browser.

Getting the file

Valve serves match replays from its replay clusters at a URL built from three values:

http://replay<cluster>.valve.net/570/<match_id>_<replay_salt>.dem.bz2

The match ID is public. The cluster and the salt are not part of anything the player sees; the usual way to get them is OpenDota’s match endpoint, which returns a ready-made replay_url for matches it has seen. When OpenDota is unavailable, a full Valve URL pasted by the user carries everything needed, so our importer accepts those directly as a fallback. The URL stops working once Valve drops the replay, typically a week or two after the match.

What a .dem contains

The file is a sequence of length-prefixed messages, most of them Protocol Buffers. Two kinds carry nearly all the useful data:

  • Packet entities. Delta-encoded updates to every networked entity: heroes, creeps, buildings, wards, items, and the player and team resources that hold gold, net worth, last hits and the like. The schemas (send tables) are embedded at the start of the file, which is why a replay from an older patch can still be decoded by a parser built later.
  • The combat log. One entry per damage instance, heal, ability or item use, modifier, death, gold or XP change, with attacker, target and inflictor. Kills, assists and who took Roshan come from here.

Game events, chat, and user messages such as pings and the end-of-match summary are in there too. A replay does not contain mouse movement, camera position or anything else that stayed on a player’s machine.

Parsers

You almost certainly don’t want to write the entity decoder yourself. The open-source options we know well:

  • manta (Go), maintained by Dotabuff. Low-level and fast: you register callbacks for messages and entity changes. We use it for positions, state and the per-sample hero data.
  • clarity (Java). Mature, with good combat-log handling. Demos from patch 7.40 onward need clarity 4.0.1 or newer.

Rust and Python parsers exist as well; check when each was last updated before you build on one, because every large patch can change entity schemas or message types.

From entities to positions

A hero’s position is split across a cell index and an offset inside the cell, so you rebuild world coordinates from both. Entity updates arrive irregularly: a hero standing still sends nothing, one in a fight updates every tick. For playback we resample every unit to a fixed 15 samples per second, store positions, facing and animation state as keyframes in a compact binary file, and keep the slower stats (gold, items, abilities, cooldowns) as two-second samples in JSON. A 45-minute match comes out at a few megabytes, which a browser loads in a second or two.

Kills, towers, Roshan and fights are then derived once into a single event index that the kill feed, timeline, scoreboard and camera all read from, so they all show the same numbers.

Pitfalls we hit

  • Game time is not replay time. The file starts in the lobby, well before the horn, and pauses keep ticking. Count from the game-start tick and subtract pauses, or every clock is off by minutes.
  • Not every death is a kill. Aegis and Reincarnation deaths revive the hero a few seconds later in the same spot. Denies are deaths caused by a teammate. Count either as a kill and your score won’t match the scoreboard.
  • Kills credited to summons and creeps. The scoreboard credits the hero, but the combat log names the unit that landed the hit. Reconcile against the players’ kill counters.
  • Internal names. Entities use engine names that predate renames: npc_dota_hero_furion is Nature’s Prophet, windrunner is Windranger, and item class names don’t always match item keys. Keep an alias table.

More guides

All 9