Dota 2 Replay Parser – Bruno’s Enhanced Edition

January 18th, 2013 by Cyborgmatt Leave a reply »
Advertisement

Our friend Statsman Bruno has put together a treat for the Dota community. Bruno’s Replay Parser is an enhanced Dota 2 replay parser that provides a mega dump of information in a nice and easy to use JSON format. As well as the tool itself you can also find in-depth user and developer guides in this post. Enjoy!

Quentin Tidehuntino

Dota 2 Replay Parser *

A Word From Bruno *

Hi! Bruno here with a new awesome tool for the Dota community!

As most of you know I’m a stats fan and we’ve been collecting info on pro matches for a long while. What some of you might not know is that we’ve been doing that manually, watching the VODs/replays and entering the info by hand. The reason we did that is because the bulk of the info we stored couldn’t be easily extracted automatically. Valve released a replay parser back then, but still missed a core aspect that made us keep doing it manually.

Recently, however, that core aspect was added so I decided to finish my early parser in order to start using it on the site. A bit of further analysis on the replay (I hadn’t been able to look at it in depth before), made me realize that there was LOTS more info to extract than I initially thought, and I figured that, while it would still be really cool to have all that in the site, other people might find some use to such info.

So I used the couple weeks from the holiday break to not only properly finish the parser but also make it ready and useful for anyone who might want it. With this parser you’ll be able to feed it any replay and it’ll extract tons of useful information for you to analyze, make graphs, make statistics, and whatever else you desire.

If you find a replay that this parser cannot parse for any reason, feel free to drop me an email with the match ID (assuming the match is still downloadable) at [email protected] and I’ll see if it’s something fixable in the next version.

As with everything I made for Dota 2, this is entirely free to use and distribute, I hope you get to build lots of awesome stuff with it and make the Dota 2 Community better and better each day. I’ll resume updating Dota Academy in a few days (using this tool and with all the new stats), working on the Fantasy League (which may or may not add new game modes soon for those who got bored of the current format!)… and who knows, maybe we’ll get to see each other’s faces this year as well!

Cheers! – Bruno

Dota 2 Replay Parser Download *

You can find the latest version of the Dota 2 replay parser tool here:

Dota2_ReplayParser.zip (v1.02)

Last updated: 29/01/2013

29/01/2013 Changelog

  • Fixed Roshan Output always saying Radiant Team killed Roshan.
  • Fixed Building Names convention when killed by creeps.
  • Added Team who paused to pauses.json
  • Fixed a typo in buildings.txt
  • Fixed escape characters in chat breaking the json.
  • Fixed parser sometimes not finding the txt files if called from the command line.
  • Added Value field to combatlog.json so that you know when someone damages/heal someone, how much damage/heal is done!
  • Fixed a case of barracks kill detection by creeps where it couldn’t determine which building it was.

20/01/2013 Changelog

  • Added Steam IDs to the player list.

Requirements

The VS2012 Redistributable (x86) package is also required, which you can get from here: http://www.microsoft.com/en-us/download/details.aspx?id=30679

Dota 2 Replay Parser – User Manual *

Hey everyone, this guide will explain how to use the replay parser and the output format for everything it spews. This assumes no knowledge at all of any technical stuff, although if you want to make something significant out of it, it’s highly recommended you know how to work with json files and with a language that can parse those and do with them whatever you wish. The next few pages will explore its usage in detail, as to WHY would you use a replay parser? Here are some ideas:

  • For individual replays: You can go for more in depth stats that just the end game score. See who killed you the most, where your gold came mostly from, when the game started turning around, you can even recreate XP/Gold graphs as a team or even individually. Check who won each lane, which team had rune control, check item progressions, and whether the timings were acceptable.
  • For a group of replays of the same hero: Check which item progressions are most effective and how they affect farming speed and killing potential. Check the matchups that cause them the least and the most trouble, when they peak, how much solo kill potential they have, and compare everything to other heroes to decide what playstyle it fits in.
  • For a group of replays of the same player: Analyze his strong points, can he make things happen in the lane? Does he prioritize farming over killing? Is he, as a support pulling (and successfully last hitting) a lot? How’s his propensity for ganking other lanes?

There are probably hundreds of other applications, feel free to create your own, I’m positive lots of good stuff can come out from a tool like this. I know that I will be using it for Dota Academy for sure!

Usage *

The Dota 2 Replay Parser is a command line tool, meaning that there’s no graphical interface to it. There are 2 ways of running it

a) From the command line, go to the folder where DotaParser.exe is and write DotaParser.exe yourreplay.dem   replacing “yourreplay.dem” for whatever name your replay has.

b) From a Windows explorer window, drag a replay file and drop it on the DotaParser.exe file.

Limitations *

Due to the way certain information is extracted, the Dota 2 Parser is created to work with 5v5 replays. Most features should work in any kind of composition as long as both teams have the same amount of players as well, but there may be cases where certain information will not be available. If the amount of players in each team is not equal from the beginning, some information will be inaccurate.

Also, for a few rare matches featuring Nature’s Prophet, sometimes Level Up/Item  information will not be entirely available, i.e: for 2 or more heroes (most likely just 2) the level up and items will show as  “Hero 1 leveled up, etc” instead of the hero name. This happens because we can’t know straight away who’s who and we rely on a way that can deduce that 99% of the time for non-nature’s prophet heroes. If just 1 hero fails out of 10, it’s easy to deduce who it is by process of elimination, but if it’s 2, it’s not possible to know who’s who. The info will still be there, you’ll have to replace the “Hero 1” and “Hero 2” placeholders for the real heroes manually.

Finally, this replay parser hasn’t still been tested with Same Hero replays and will probably not work because there’s not enough information to detect who’s who in certain events like Gold Overheads and Level ups. Information will be consistent, but it might refer to the wrong player.

There are a few bugs that can’t be solved at the moment (Jan 2013) until Valve issues a fix. They’ll eventually do so, and from that moment on the bugs will disappear from newer replays. These bugs are:

a) Assists sometimes not counting. 0 damage instances don’t show in the combat log, however they DO count for assists, so while tracking damage for assists if a hero damages a refracted TA or a backtracking Faceless Void for example, the assist will not be recorded in the replay parser because that event never shows in the combat log.

b) Creep Kills from tombstone zombies don’t count for CS. Zombies are not considered player controlled units, so if a zombie kills/damages a creep, as none of them are considered player controlled units, the log entry is not added, so we don’t know it happened.

c) Gold from Greevil’s Greed is not tracked in overhead gold. It just doesn’t have an entry in the replay files, so we can’t know when it happens.

d) Combat Log events from Creeps killing towers aren’t stored, we can know that A tower fell (and when), and with that most of the time we can deduce which tower it was, but sometimes we can’t, and some tower kill messages might be a bit vague.

How to get replays *

Easiest way to get a replay is to go to the client and download it from there. The replay will be saved to the dota 2 replay path which is <yoursteamfolder>\steamapps\common\dota 2 beta\dota\replays so you just grab it from here and copy it to your DotaParser folder. The replays have a .dem extension and are named after their match ID.

You can also find the replay download links on the Dotabuff site if you know the link to the match.

Output *

When you run a replay through the Dota 2 Replay Parser, a number of files are created in the subfolders output and json (which will be created if they don’t exist).

A .txt file named as the match’s match ID will be placed on the output folder. This has no particular format and it’s mostly as a human-readable recap of all the events. I do not recommend using this file to extract the information.

A .log file named as the match’s match ID will be placed on the output folder. This is the dump of the combat log in a human-readable format.

A folder named as the match’s match ID will be created on the json folder, inside you’ll see lots of .json files which are described in the following paragraphs:

banpicks.json *

This file is an empty json  (  { }  ) for non-CM matches, for CM matches, the json holds one field called “banpicks” of type array.

Each element of the banpicks array is an object that contains 3 fields:

is_pick: boolean field, true if it’s a pick, false if it’s a ban.

team: integer field, 2=dire, 3=radiant.

hero_id: integer field, hero id from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

Hero ID mappings at the time of this post:

{
  "result": {
    "heroes": [
      {
        "name": "npc_dota_hero_antimage",
        "id": 1,
        "localized_name": "Anti-Mage"
      },
      {
        "name": "npc_dota_hero_axe",
        "id": 2,
        "localized_name": "Axe"
      },
      {
        "name": "npc_dota_hero_bane",
        "id": 3,
        "localized_name": "Bane"
      },
      {
        "name": "npc_dota_hero_bloodseeker",
        "id": 4,
        "localized_name": "Bloodseeker"
      },
      {
        "name": "npc_dota_hero_crystal_maiden",
        "id": 5,
        "localized_name": "Crystal Maiden"
      },
      {
        "name": "npc_dota_hero_drow_ranger",
        "id": 6,
        "localized_name": "Drow Ranger"
      },
      {
        "name": "npc_dota_hero_earthshaker",
        "id": 7,
        "localized_name": "Earthshaker"
      },
      {
        "name": "npc_dota_hero_juggernaut",
        "id": 8,
        "localized_name": "Juggernaut"
      },
      {
        "name": "npc_dota_hero_mirana",
        "id": 9,
        "localized_name": "Mirana"
      },
      {
        "name": "npc_dota_hero_nevermore",
        "id": 11,
        "localized_name": "Shadow Fiend"
      },
      {
        "name": "npc_dota_hero_morphling",
        "id": 10,
        "localized_name": "Morphling"
      },
      {
        "name": "npc_dota_hero_phantom_lancer",
        "id": 12,
        "localized_name": "Phantom Lancer"
      },
      {
        "name": "npc_dota_hero_puck",
        "id": 13,
        "localized_name": "Puck"
      },
      {
        "name": "npc_dota_hero_pudge",
        "id": 14,
        "localized_name": "Pudge"
      },
      {
        "name": "npc_dota_hero_razor",
        "id": 15,
        "localized_name": "Razor"
      },
      {
        "name": "npc_dota_hero_sand_king",
        "id": 16,
        "localized_name": "Sand King"
      },
      {
        "name": "npc_dota_hero_storm_spirit",
        "id": 17,
        "localized_name": "Storm Spirit"
      },
      {
        "name": "npc_dota_hero_sven",
        "id": 18,
        "localized_name": "Sven"
      },
      {
        "name": "npc_dota_hero_tiny",
        "id": 19,
        "localized_name": "Tiny"
      },
      {
        "name": "npc_dota_hero_vengefulspirit",
        "id": 20,
        "localized_name": "Vengeful Spirit"
      },
      {
        "name": "npc_dota_hero_windrunner",
        "id": 21,
        "localized_name": "Windrunner"
      },
      {
        "name": "npc_dota_hero_zuus",
        "id": 22,
        "localized_name": "Zeus"
      },
      {
        "name": "npc_dota_hero_kunkka",
        "id": 23,
        "localized_name": "Kunkka"
      },
      {
        "name": "npc_dota_hero_lina",
        "id": 25,
        "localized_name": "Lina"
      },
      {
        "name": "npc_dota_hero_lich",
        "id": 31,
        "localized_name": "Lich"
      },
      {
        "name": "npc_dota_hero_lion",
        "id": 26,
        "localized_name": "Lion"
      },
      {
        "name": "npc_dota_hero_shadow_shaman",
        "id": 27,
        "localized_name": "Shadow Shaman"
      },
      {
        "name": "npc_dota_hero_slardar",
        "id": 28,
        "localized_name": "Slardar"
      },
      {
        "name": "npc_dota_hero_tidehunter",
        "id": 29,
        "localized_name": "Tidehunter"
      },
      {
        "name": "npc_dota_hero_witch_doctor",
        "id": 30,
        "localized_name": "Witch Doctor"
      },
      {
        "name": "npc_dota_hero_riki",
        "id": 32,
        "localized_name": "Riki"
      },
      {
        "name": "npc_dota_hero_enigma",
        "id": 33,
        "localized_name": "Enigma"
      },
      {
        "name": "npc_dota_hero_tinker",
        "id": 34,
        "localized_name": "Tinker"
      },
      {
        "name": "npc_dota_hero_sniper",
        "id": 35,
        "localized_name": "Sniper"
      },
      {
        "name": "npc_dota_hero_necrolyte",
        "id": 36,
        "localized_name": "Necrolyte"
      },
      {
        "name": "npc_dota_hero_warlock",
        "id": 37,
        "localized_name": "Warlock"
      },
      {
        "name": "npc_dota_hero_beastmaster",
        "id": 38,
        "localized_name": "Beastmaster"
      },
      {
        "name": "npc_dota_hero_queenofpain",
        "id": 39,
        "localized_name": "Queen of Pain"
      },
      {
        "name": "npc_dota_hero_venomancer",
        "id": 40,
        "localized_name": "Venomancer"
      },
      {
        "name": "npc_dota_hero_faceless_void",
        "id": 41,
        "localized_name": "Faceless Void"
      },
      {
        "name": "npc_dota_hero_skeleton_king",
        "id": 42,
        "localized_name": "Skeleton King"
      },
      {
        "name": "npc_dota_hero_death_prophet",
        "id": 43,
        "localized_name": "Death Prophet"
      },
      {
        "name": "npc_dota_hero_phantom_assassin",
        "id": 44,
        "localized_name": "Phantom Assassin"
      },
      {
        "name": "npc_dota_hero_pugna",
        "id": 45,
        "localized_name": "Pugna"
      },
      {
        "name": "npc_dota_hero_templar_assassin",
        "id": 46,
        "localized_name": "Templar Assassin"
      },
      {
        "name": "npc_dota_hero_viper",
        "id": 47,
        "localized_name": "Viper"
      },
      {
        "name": "npc_dota_hero_luna",
        "id": 48,
        "localized_name": "Luna"
      },
      {
        "name": "npc_dota_hero_dragon_knight",
        "id": 49,
        "localized_name": "Dragon Knight"
      },
      {
        "name": "npc_dota_hero_dazzle",
        "id": 50,
        "localized_name": "Dazzle"
      },
      {
        "name": "npc_dota_hero_rattletrap",
        "id": 51,
        "localized_name": "Clockwerk"
      },
      {
        "name": "npc_dota_hero_leshrac",
        "id": 52,
        "localized_name": "Leshrac"
      },
      {
        "name": "npc_dota_hero_furion",
        "id": 53,
        "localized_name": "Nature's Prophet"
      },
      {
        "name": "npc_dota_hero_life_stealer",
        "id": 54,
        "localized_name": "Lifestealer"
      },
      {
        "name": "npc_dota_hero_dark_seer",
        "id": 55,
        "localized_name": "Dark Seer"
      },
      {
        "name": "npc_dota_hero_clinkz",
        "id": 56,
        "localized_name": "Clinkz"
      },
      {
        "name": "npc_dota_hero_omniknight",
        "id": 57,
        "localized_name": "Omniknight"
      },
      {
        "name": "npc_dota_hero_enchantress",
        "id": 58,
        "localized_name": "Enchantress"
      },
      {
        "name": "npc_dota_hero_huskar",
        "id": 59,
        "localized_name": "Huskar"
      },
      {
        "name": "npc_dota_hero_night_stalker",
        "id": 60,
        "localized_name": "Night Stalker"
      },
      {
        "name": "npc_dota_hero_broodmother",
        "id": 61,
        "localized_name": "Broodmother"
      },
      {
        "name": "npc_dota_hero_bounty_hunter",
        "id": 62,
        "localized_name": "Bounty Hunter"
      },
      {
        "name": "npc_dota_hero_weaver",
        "id": 63,
        "localized_name": "Weaver"
      },
      {
        "name": "npc_dota_hero_jakiro",
        "id": 64,
        "localized_name": "Jakiro"
      },
      {
        "name": "npc_dota_hero_batrider",
        "id": 65,
        "localized_name": "Batrider"
      },
      {
        "name": "npc_dota_hero_chen",
        "id": 66,
        "localized_name": "Chen"
      },
      {
        "name": "npc_dota_hero_spectre",
        "id": 67,
        "localized_name": "Spectre"
      },
      {
        "name": "npc_dota_hero_doom_bringer",
        "id": 69,
        "localized_name": "Doom"
      },
      {
        "name": "npc_dota_hero_ancient_apparition",
        "id": 68,
        "localized_name": "Ancient Apparition"
      },
      {
        "name": "npc_dota_hero_ursa",
        "id": 70,
        "localized_name": "Ursa"
      },
      {
        "name": "npc_dota_hero_spirit_breaker",
        "id": 71,
        "localized_name": "Spirit Breaker"
      },
      {
        "name": "npc_dota_hero_gyrocopter",
        "id": 72,
        "localized_name": "Gyrocopter"
      },
      {
        "name": "npc_dota_hero_alchemist",
        "id": 73,
        "localized_name": "Alchemist"
      },
      {
        "name": "npc_dota_hero_invoker",
        "id": 74,
        "localized_name": "Invoker"
      },
      {
        "name": "npc_dota_hero_silencer",
        "id": 75,
        "localized_name": "Silencer"
      },
      {
        "name": "npc_dota_hero_obsidian_destroyer",
        "id": 76,
        "localized_name": "Outworld Devourer"
      },
      {
        "name": "npc_dota_hero_lycan",
        "id": 77,
        "localized_name": "Lycanthrope"
      },
      {
        "name": "npc_dota_hero_brewmaster",
        "id": 78,
        "localized_name": "Brewmaster"
      },
      {
        "name": "npc_dota_hero_shadow_demon",
        "id": 79,
        "localized_name": "Shadow Demon"
      },
      {
        "name": "npc_dota_hero_lone_druid",
        "id": 80,
        "localized_name": "Lone Druid"
      },
      {
        "name": "npc_dota_hero_chaos_knight",
        "id": 81,
        "localized_name": "Chaos Knight"
      },
      {
        "name": "npc_dota_hero_meepo",
        "id": 82,
        "localized_name": "Meepo"
      },
      {
        "name": "npc_dota_hero_treant",
        "id": 83,
        "localized_name": "Treant Protector"
      },
      {
        "name": "npc_dota_hero_ogre_magi",
        "id": 84,
        "localized_name": "Ogre Magi"
      },
      {
        "name": "npc_dota_hero_undying",
        "id": 85,
        "localized_name": "Undying"
      },
      {
        "name": "npc_dota_hero_rubick",
        "id": 86,
        "localized_name": "Rubick"
      },
      {
        "name": "npc_dota_hero_disruptor",
        "id": 87,
        "localized_name": "Disruptor"
      },
      {
        "name": "npc_dota_hero_nyx_assassin",
        "id": 88,
        "localized_name": "Nyx Assassin"
      },
      {
        "name": "npc_dota_hero_naga_siren",
        "id": 89,
        "localized_name": "Naga Siren"
      },
      {
        "name": "npc_dota_hero_keeper_of_the_light",
        "id": 90,
        "localized_name": "Keeper of the Light"
      },
      {
        "name": "npc_dota_hero_wisp",
        "id": 91,
        "localized_name": "Wisp"
      },
      {
        "name": "npc_dota_hero_visage",
        "id": 92,
        "localized_name": "Visage"
      },
      {
        "name": "npc_dota_hero_slark",
        "id": 93,
        "localized_name": "Slark"
      },
      {
        "name": "npc_dota_hero_medusa",
        "id": 94,
        "localized_name": "Medusa"
      },
      {
        "name": "npc_dota_hero_troll_warlord",
        "id": 95,
        "localized_name": "Troll Warlord"
      },
      {
        "name": "npc_dota_hero_centaur",
        "id": 96,
        "localized_name": "Centaur Warrunner"
      },
      {
        "name": "npc_dota_hero_magnataur",
        "id": 97,
        "localized_name": "Magnus"
      },
      {
        "name": "npc_dota_hero_shredder",
        "id": 98,
        "localized_name": "Timbersaw"
      },
      {
        "name": "npc_dota_hero_bristleback",
        "id": 99,
        "localized_name": "Bristleback"
      },
      {
        "name": "npc_dota_hero_tusk",
        "id": 100,
        "localized_name": "Tusk"
      }
    ]
    ,
    "count": 99
  }
}
									

buildings.json *

This file holds one field called “buildingtimers” of type array with N objects where N is the amount of destroyed buildings.

Each element of the buildingtimers array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

building: string field, contains the name of the building, which can be either of these:

npc_dota_goodguys_tower_unknown
npc_dota_goodguys_tower1_unknown
npc_dota_goodguys_tower2_unknown
npc_dota_goodguys_tower3_unknown
npc_dota_goodguys_melee_rax_bot
npc_dota_goodguys_melee_rax_mid
npc_dota_goodguys_melee_rax_top
npc_dota_goodguys_range_rax_bot
npc_dota_goodguys_range_rax_mid
npc_dota_goodguys_range_rax_bot
npc_dota_goodguys_tower1_bot
npc_dota_goodguys_tower1_mid
npc_dota_goodguys_tower1_top
npc_dota_goodguys_tower2_bot
npc_dota_goodguys_tower2_mid
npc_dota_goodguys_tower2_top
npc_dota_goodguys_tower3_bot
npc_dota_goodguys_tower3_mid
npc_dota_goodguys_tower3_top
npc_dota_goodguys_tower4
npc_dota_badguys_tower_unknown
npc_dota_badguys_tower1_unknown
npc_dota_badguys_tower2_unknown
npc_dota_badguys_tower3_unknown
npc_dota_badguys_melee_rax_bot
npc_dota_badguys_melee_rax_mid
npc_dota_badguys_melee_rax_top
npc_dota_badguys_range_rax_bot
npc_dota_badguys_range_rax_mid
npc_dota_badguys_range_rax_top
npc_dota_badguys_tower1_bot
npc_dota_badguys_tower1_mid
npc_dota_badguys_tower1_top
npc_dota_badguys_tower2_bot
npc_dota_badguys_tower2_mid
npc_dota_badguys_tower2_top
npc_dota_badguys_tower3_bot
npc_dota_badguys_tower3_mid
npc_dota_badguys_tower3_top
npc_dota_badguys_tower4
									

killer: string field, hero name (not Localized name) from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

buybacks.json *

This file holds one field called “buybacks” of type array with N objects where N is the amount of times someone bought back.

Each element of the buybacks array is an object that contains 2 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

hero: string field, hero name (not Localized name) from here:

chat.json *

This file holds one field called “chat” of type array with N objects where N is the amount of times someone talked in all chat (team chat is not stored in the replays).

Each element of the chat array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

player: string containing the screen name of the player who talked.

msg: string containing the chat message.

cs.json *

This file holds one field called “cs” of type array with N objects where N is the amount of times a creep/tower/anything that counts as a CS is killed by a non-friendly unit (as that would be a deny).

Each element of the cs array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

hero: string field, hero name (not Localized name) from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

kill: string containing the name of the killed unit.

denies.json *

This file holds one field called “denies” of type array with N objects where N is the amount of times a creep/tower/anything that counts as a CS is killed by a friendly unit .

Each element of the denies array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

hero: string field, hero name (not Localized name) from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

kill: string containing the name of the killed unit.

glyphs.json *

This file holds one field called “glyphs” of type array with N objects where N is the amount of times someone used glyph.

Each element of the glyphs array is an object that contains 2 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

team: integer field, 3=radiant, 2=dire

gold.json *

This file holds one field called “gold” of type array with N objects where N is the amount of times a hero gets gold for anything other than passive gold.

Each element of the gold array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

hero: string field, hero name (not Localized name) from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

gold: integer, how much gold the hero got.

herokills.json *

This file holds one field called “herokills” of type array with N objects where N is the amount of times a hero gets gold for anything other than passive gold.

Each element of the herokillsarray is an object that contains 5 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

dead: string field, Dead hero name (not Localized name) from here:  https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

killer: string field, Killer name, get it from the name field (not Localized name) from the same link:

aegis: boolean field, true if it was an aegis/reincarnation kill (doesn’t count for death counts)

assists: array field, contains a list of strings with the names from the assistants. Get the name list from the link above.

itemtimes.json *

This file holds one field called “itemtimes” of type array with N objects where N is the amount of times a hero gets a new item in its inventory for the first time.

Each element of the itemtimes array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

hero: string field, hero name (not Localized name) from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

item: string containing the item modifier name (from the items.txt table)

levelups.json *

This file holds one field called “leveluptimes” of type array with N objects where N is the amount of times any hero levels up.

Each element of the leveluptimes array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

hero: string field, hero name (not Localized name) from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

level: integer field, The level that hero just got to. (hence, it starts from 2)

pauses.json *

This file holds one field called “pauses” of type array with N objects where N is the amount of pauses in the game.

Each element of the pausesarray is an object that contains 2 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

length: integer field, how long in ticks the pause lasted.

roshan.json *

This file holds one field called “roshtimers” of type array with N objects where N is the amount of times roshan was slain.

Each element of the roshtimers array is an object that contains 3 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

side: integer field, 3=radiant 2=dire. Team who killed roshan.

killer: string field, hero name (not Localized name) of the person who got the aegis from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

runes.json *

This file holds one field called “runes” of type array with N objects where N is the amount of times any hero grabs a rune.

Each element of the runes array is an object that contains 4 fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

rune: integer field. 0 = DD 1= Haste 2 = Illusion 3 = Invisibility 4= Regen

hero: string field, hero name (not Localized name) from here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

bottle: boolean field, true if the hero bottled the rune, false if he just grabbed it.

combatlog.json *

This file holds one field called “combatlog” of type array with N objects where N is the amount of times an entry is added to the combat log. This happens any time a player controlled unit either:

-Deals damage

-Receives damage

-Heals another unit

-Is healed by another unit

-Gains a buff/debuff

-Loses a buff/debuff

-Dies

Each element contains several fields:

time: integer field, time in ticks where the event happens from the 0:00 game clock time. 30 ticks = 1 second.

replaytime: integer field, time in ticks where the event happens from the replay clock time. 30 ticks = 1 second.

attacker: The string identifier from the unit that deals the damage/heals/applies the debuff/kills in this log event.

source: The string identifier from the hero that owns the attacker (if the attacker is the hero, the source is also the hero), if the attacker is a non-player controlled unit, the source is that same unit.

target: The string identifier from the unit that receives the damage/is healed/receives the debuff/dies in this log event.

targetsource: The string identifier from the hero that owns the target (if the target is the hero, the targetsource is also the hero), if the target is a non-player controlled unit, the targetsource is that same unit.

inflictor: The string identifier for the buff/debuff being applied (in buff/debuff apply/removal log entries)

type: An integer indicating the entry type: 0= Damage 1= Heal 2= Buff/debuff being applied 3=Buff/Debuff being removed/expiring 4=Death

String identifiers can be found in the dictionary.txt file.

The Text Files *

As you can see, the application comes with a few text files, it’s important that those files exist in order for the application to function properly. They contain many references of what means what and what skill belongs to which hero. The reason these are in txt files is for me or others to be able to patch the application in order to take into consideration future heroes and game changes (like hero reworks, new neutrals or even new creeps/structures/whatever) without having to get a new .exe file.

Particularly if your combat log files are displaying strings like

03:56 (00:35). Bounty Hunter gets the modifier_bounty_hunter_wind_walk buff/debuff.

instead of

03:56 (00:35). Bounty Hunter gets the Wind Walk buff/debuff.

you can go to the dictionary.txt and add a line at the end of the file saying

modifier_bounty_hunter_wind_walk,Wind Walk

And your future combat logs will have the proper strings. Make sure you check frequently to see if there are new txt files, especially after new heroes come out, otherwise the parser might have troubles parsing said heroes’ information.

There are lots of entries missing in the dictionary.txt file, pretty much because there’s no fast way to get a full list of all spells/buffs/units/etc, and I got bored halfway through. If anyone completes/improves the dictionary and wants to send me the updated copy, I’ll upload it.

Thanks *

Thanks to Cyborgmatt to help me release this tool. To all of the usual suspects, to my friends who have never even seen a dota game but have endured me talking about it a lot. To the community for all the support and love, you guys are great, I hope we get to interact more in the future. To Valve, because they made such an awesome game and such great tools to play with. To all the community figures/players/casters/writers/etc who do awesome things for the community day after day instead of “every few months” like I do. Rock on!

Dota 2 Replay Parser – Dev Manual *

Hey everyone, this guide is somewhat technical in nature, but still understandable by people without developing knowledge. The next few pages will explain how the Dota 2 Parser works (at least my version of it). There are several reasons for this:

a) Maybe you want to develop your own parser, but you don’t want to spend much time trying to understand the whole replay specification.
b) You might be working/have worked on replay parsing, and by reading this you might realize that something’s not done in the most efficient way and you want to point that out to improve the quality of this parser, alternatively you might get a few different approaches to problems you’ve been solving.
c) You’re just a curious Dota fan that wants to understand what’s underneath the hood.
d) You want to use this tool, but it doesn’t have exactly what you need, but this document makes it clear that it CAN be obtained, so you might want to ask for eventual additions.

With this in mind, what comes next is an in depth description of the Dota 2 Parser Tool and my commentary on its intricacies.

Thanks *

This normally goes at the end, but I want to start by thanking everyone who helped me during the development of this tool.

To Shostakovich, ASX, LD, Luminous and Atre, the first guys who were willing to bear with me when I started with Dota Academy and my goal of improving the analytical aspect of the pro scene and still back me up and give me awesome ideas to improve what there is and to make new things.

To all of the Valve guys for being incredibly helpful and supportive in the development of a tool of this characteristic, even willing to modify their schema to fit some things that were useful to me, and discuss their ideas on interesting metrics to analyze, especially Icefrog, Tams, Doug, Mugsy, Zoid and ttocs all of whom I frequently bothered at some point or another of the process.
To Stu, one of my oldest and best friends which I met playing Dota, 8 years ago now, who helped me a bit with some of the most boring part of the coding (even though you didn’t respect naming conventions!).

And to everyone else, community figures, players, casters, behind the scenes guys, people who show their support, thank you, really. I have the luck of being able to work on something that’s extremely rewarding and gratifying and you make it worthwhile.

The Replay Format *

Dota 2 replays (the .dem files) are stored using Google’s Protobuf format. Data is not encrypted but it’s bit compressed, meaning that reverse engineering fields without a specification is possible but extremely annoying (e.g: 3 bytes of data could actually contain 4 6-bit values, or 2 12-bit values for example and there’s no way of knowing which without staring at the data for month and trying to deduce patterns).  Fortunately Valve provided a partial specification.

Protobuf actually allows lots of cool things, mainly the ability to change the specification while still being compatible with older versions, so, if tomorrow Dota 2 has Wood as a new resource (or add a minigame with new mechanics like diretide or frostivus), Protobuf allows you to change the specification so that a replay parser (or the client itself) can parse the new file AND the old files equally, it’s rather nifty.

Another thing that it allows you to do is to provide partial specifications. A specification is a manuscript that allows you to decode the replay and make sense of the values (solving the aforementioned problem of the 3 bytes of data), a partial specification allows you to make sense of part of it, while leaving a part unspecified, either because it’s uninteresting, because you don’t want people to know exactly what’s in there or whatever other reason. The Replay analyzing tool that Valve provides has a partial specification, but it’s more than enough to get pretty much everything interesting there is with a few exceptions. In the next  few paragraphs we’ll discuss what we can and can’t get from the replay files.

Uses and Limitations *

It’s worth noting that the data from a game can be divided in 4 categories based on their availability in its protobuf’s specs:

a) Readily available. Actually most of the cool info is somewhat readily available. You need to jump through a few hoops, but 80% of what makes a dota game is just out there for anyone to grab.

b) Unavailable by design: A replay’s main function is to be played by the client to recreate a game. Because replays are stored in external servers and not locally, replays have to be as lightweight as possible, and often lacks information that the client itself calculates. We can only speculate as to what is and isn’t there as long as we don’t have information on the unspecified part of the replay, which leads to:

c) Available but not specified. Some info is contained in the part of the file that’s not specified. Interestingly, there’s a full snapshot of the game stored every single minute. Remember in the early dota 2 days where you could go forward in a replay but going backwards would take a lot of time? That’s because there were no intermediate snapshots so you could recreate a later state by fast forwarding all events from your current point in the replay, but going backwards meant starting from scratch and fast forwarding to the point you wanted to go. Intermediate snapshots allowed for rewinding replays, being able to save games (that’s why saved games restore at the nearest minute) and it also caused a funny lag issue every minute for a week back in early beta that was quickly solved. Those snapshots contain tons of information that’s not specified. Some of it useful, some of it useless for our purposes, some of it a single recap of the information that was already  spread in all the other parts of the replay. The other parts of the replay that aren’t snapshots also provide incremental updates and modifications to this unspecified info (also unspecified), probably some of it would be of use. Some of this unspecified info is sometimes added to the replay format, for example the Picks/Bans information from CM games, because it’s cool to have.

d) Unavailable because no one thought of it. Sometimes some things escape through the cracks, or aren’t necessary but are so handy/practical/costless that Valve would add them as the community requests it. Examples of things that have been added are the owner of a source of damage (Combat Logs would track that an eidolon killed a creep, but initially wouldn’t track whose eidolon it was, as it could’ve been a rubick eidolon, or dominated by a chen/enchantress). Examples of things that aren’t there but will eventually be are 0-damage instances of damage in the combat log (for example attacking a TA with refraction on), which is useful because it counts for assists, but as it isn’t in the combat log, there’s no way of knowing.

Having said that, it’s time of discussing what currently is and isn’t there. Let’s start from what isn’t, because the list is shorter:

Positional info. There’s no way of knowing where each unit is at every instant, with the information on the specified part of the replay.

Skill Usage. Similarly, there’s no tracking of skills used in the specified part of the replay, there’s damage/debuff/buff tracking, and it says what caused the damage (e.g: you can know that Lion dealt 300dmg to Rubick using Finger of Death, but you can’t know that QoP blinked from point A to point B because blinking deals no damage nor causes any buff/debuff so it’s not in the combat log). Similarly, for skills that deal damage conditionally, like Pudge’s hook, you can know that Pudge used a hook when it hits something, but not when it misses because it generates no entry in the combat log. I guess you could track sounds being played, you know that when pudge throws a hook, there’s always the a sound played. By analyzing said sounds it would be possible to track certain things like this… but there should be a better way. This might be there in the future, or it might not. Hard to say. It’d be cool for stuff like skill accuracy/efficiency or propensity, etc.

Instant Player HP/MP. You can roughly calculate HP based on the combat log, but it’s extremely inaccurate, specially in moments where nothing’s happening and regen does its work. This would be really cool to have as well.

Team chat. It’s not recorded in replays, sorry, it makes sense as well, as much as you’d want to see what the other team is saying about your awesome 23/0/2 Drow Ranger, it’s something that could potentially cause troubles if it was there. A case of abuse would be a pro team that communicates via chat (There are pro teams that do that a lot!), By scrimming against them and then reading their chat, you could get valuable information on their playstyle, morale, whatever. I’m pretty sure Team chat will never be in the replay. This also applies to team voice communication.

There are probably a few other minor things I forgot to mention, but that’s the core of it. On the other hand we DO have lots of nice data, namely:

Combat Log. If you ever looked at the top left part of the screen while on a replay, you probably saw a small log icon that has a combat log that keeps track of every time a unit receives/deals damage, gets/loses a buff/debuff, gets healed or dies. There are some exceptions that are not catalogued, but most of it is there. This allows us to track things like Kills/Deaths/Assists, Creep Kills and Denies.

Chat events (not chatlog). Pretty much everything that appears in text on the screen that comes from the game is available, examples of this are when Roshan Dies, when someone gets an aegis (and who), when a team uses a glyph, when a team pauses, someone getting a rune, etc.  This also offers us quite valuable info.

Particle effects. There’s a table with all the particle effects for the game and also information whenever one of those effects is used (and which entity generated them). This is not an exhaustive list, from what I’ve noticed, but it DOES have every single time the “level up” effect appears, which allows us to know when anyone levels up.

Active Modifier entries. There’s a table with all the active modifiers. Active Modifiers are everything that affects an entity either by granting it stats/buffing/debuffing, including items, spells and auras among other things. The list doesn’t seem to be exhaustive, but it contains every single item. Along with that table there are several entries when any active modifier is activated on an entity. This, when used for items, can tell you the exact times when an item appears for the first time in a hero, meaning you can get item timings. Consumables aren’t included here.

Overhead Alerts. There are lots of messages that pop above units’ heads because of different reasons, when you deny an allied unit, you get a “!” sign. When you kill an enemy unit you get a number with your color saying how much money you got from that. When you miss an attack you get a “miss” message. Overhead alerts are mainly useful to calculate GPM by gathering all the instances where your hero got gold via an external source, and to help calculate denies in a few circumstances. There are other uses (to calculate how many times a vanguard blocked damage, or how many times you got a critical strike, or a miss, but they’re not particularly interesting.

Additional basic demo info. This includes lots of things that could be obtained otherwise, but are wrapped up here for convenience, mainly match Id, player names and the heroes they used, steam ids of each player, picks and bans. This list might grow in the future.

In the next section, we’ll explain a replay’s basic format and with that understanding, we’ll be able to explain where everything is and how to obtain it.

The Replay Format *

The best way to understand the replay format is to grab Valve’s demoinfo2 tool and dump a whole replay into a text file and look at it. Even then, it’s a bit confusing at first sight, but the core idea is the following:

– The game’s basic time unit is a tick. 30 ticks equal 1 second.

– Game information/state is stored in packets every 2 ticks. Whatever happened in the last 2 ticks is reflected in this packet called a DEM_Packet

– Every 1800 ticks (1 minute), there’s additional information on a DEM_FullPacket, this holds a snapshot of the game state until that point. Most of it is unspecified tables, but a few of the specified ones are really useful.

– Additionally, at the first and last ticks, there are additional packets with extra info (SignOn packets, SendTables packets, FileInfo, FileHeader packets and so on). Other than the FileInfo packet (which comes at the end), there’s not much more of interest in these packets apparently, as most of it is not specified.

– Dem_Packets contain events of many types, chat events, combatlog events, overhead alerts, particle effect creation, etc. They also contain updates to the entity data from the last FullPacket, but it’s not in the specs. Mostly we’ll use the events here to parse.

– FullPackets contain lots of string tables relating IDs to Particle Effects, Active Modifiers and other stuff. It also contains certain logging information that’s not readily available on the individual packets, like Active Modifiers creation time which is useful for finding item purchase times.

The following are events that are useful for data extraction. Note that the representations here are text based (and based on the messages’ DebugStrings), as a developer, you’ll work with structures containing this data and you should work with said structures. Dumping the structures to text and parsing from the text is slow and pointless, so don’t do it, kids!

DEM_FileInfo packet *

What it looks like:

==== #32785: Tick:65476 'DEM_FileInfo' Size:556 UncompressedSize:760 ====
---- CDemoFileInfo (556 bytes) -----------------
playback_time: 2182.5334
playback_ticks: 65476
playback_frames: 32732
game_info {
  dota {
    match_id: 87404327
    game_mode: 2
    game_winner: 3
    player_info {
      hero_name: "npc_dota_hero_wisp"
      player_name: "FnaticRC.`whiteBeard"
      is_fake_client: false
      steamid: 76561197981870695
    }
    player_info {
      hero_name: "npc_dota_hero_gyrocopter"
      player_name: "FnaticRC.DurpDurp"
      is_fake_client: false
      steamid: 76561197968857302
    }
    player_info {
      hero_name: "npc_dota_hero_life_stealer"
      player_name: "FnaticRC.SMURF-"
      is_fake_client: false
      steamid: 76561198043990053
    }
    player_info {
      hero_name: "npc_dota_hero_nyx_assassin"
      player_name: "FnaticRC.Rolo"
      is_fake_client: false
      steamid: 76561198036925677
    }
    player_info {
      hero_name: "npc_dota_hero_windrunner"
      player_name: "FnaticRC.tabako"
      is_fake_client: false
      steamid: 76561197983456227
    }
    player_info {
      hero_name: "npc_dota_hero_undying"
      player_name: "TurtleEGM"
      is_fake_client: false
      steamid: 76561197964182156
    }
    player_info {
      hero_name: "npc_dota_hero_dazzle"
      player_name: "TurtleHailoQ"
      is_fake_client: false
      steamid: 76561198019577886
    }
    player_info {
      hero_name: "npc_dota_hero_invoker"
      player_name: "Turtle .-.' Niqua"
      is_fake_client: false
      steamid: 76561197985441310
    }
    player_info {
      hero_name: "npc_dota_hero_luna"
      player_name: "Standin.Elefantpappa"
      is_fake_client: false
      steamid: 76561198079730290
    }
    player_info {
      hero_name: "npc_dota_hero_templar_assassin"
      player_name: "TURTLEsmulgullig"
      is_fake_client: false
      steamid: 76561198026047756
    }
    leagueid: 21
    picks_bans {
      is_pick: false
      team: 2
      hero_id: 62
    }
    picks_bans {
      is_pick: false
      team: 3
      hero_id: 82
    }
    picks_bans {
      is_pick: false
      team: 2
      hero_id: 65
    }
    picks_bans {
      is_pick: false
      team: 3
      hero_id: 97
    }
    picks_bans {
      is_pick: true
      team: 2
      hero_id: 91
    }
    picks_bans {
      is_pick: true
      team: 3
      hero_id: 46
    }
    picks_bans {
      is_pick: true
      team: 3
      hero_id: 85
    }
    picks_bans {
      is_pick: true
      team: 2
      hero_id: 72
    }
    picks_bans {
      is_pick: true
      team: 2
      hero_id: 88
    }
    picks_bans {
      is_pick: true
      team: 3
      hero_id: 48
    }
    picks_bans {
      is_pick: false
      team: 2
      hero_id: 64
    }
    picks_bans {
      is_pick: false
      team: 3
      hero_id: 55
    }
    picks_bans {
      is_pick: false
      team: 2
      hero_id: 52
    }
    picks_bans {
      is_pick: false
      team: 3
      hero_id: 80
    }
    picks_bans {
      is_pick: false
      team: 2
      hero_id: 90
    }
    picks_bans {
      is_pick: false
      team: 3
      hero_id: 39
    }
    picks_bans {
      is_pick: true
      team: 2
      hero_id: 54
    }
    picks_bans {
      is_pick: true
      team: 3
      hero_id: 74
    }
    picks_bans {
      is_pick: true
      team: 2
      hero_id: 21
    }
    picks_bans {
      is_pick: true
      team: 3
      hero_id: 50
    }
    radiant_team_id: 109099
    dire_team_id: 184145
  }
}
									

Where is it:

It’s the last packet of every replay file.

What’s in it:

Most of it is self explanatory. Game_mode is 1 for AP, 2 for CM, 3 for SD, 4 for RD and 5 for AR. Team and winner is 2= Dire, 3=Radiant

Ban/Pick information is there only since late december, so it’s probably not available in replays with ID smaller than 85 million or so. Hero ID mappings for bans/picks can be found here: https://api.steampowered.com/IEconDOTA2_570/GetHeroes/v0001/?key=<your api key here>&language=en_us *

*To get your API key, go here: http://steamcommunity.com/dev/apikey

Hero ID mappings at the time of this post:

{
  "result": {
    "heroes": [
      {
        "name": "npc_dota_hero_antimage",
        "id": 1,
        "localized_name": "Anti-Mage"
      },
      {
        "name": "npc_dota_hero_axe",
        "id": 2,
        "localized_name": "Axe"
      },
      {
        "name": "npc_dota_hero_bane",
        "id": 3,
        "localized_name": "Bane"
      },
      {
        "name": "npc_dota_hero_bloodseeker",
        "id": 4,
        "localized_name": "Bloodseeker"
      },
      {
        "name": "npc_dota_hero_crystal_maiden",
        "id": 5,
        "localized_name": "Crystal Maiden"
      },
      {
        "name": "npc_dota_hero_drow_ranger",
        "id": 6,
        "localized_name": "Drow Ranger"
      },
      {
        "name": "npc_dota_hero_earthshaker",
        "id": 7,
        "localized_name": "Earthshaker"
      },
      {
        "name": "npc_dota_hero_juggernaut",
        "id": 8,
        "localized_name": "Juggernaut"
      },
      {
        "name": "npc_dota_hero_mirana",
        "id": 9,
        "localized_name": "Mirana"
      },
      {
        "name": "npc_dota_hero_nevermore",
        "id": 11,
        "localized_name": "Shadow Fiend"
      },
      {
        "name": "npc_dota_hero_morphling",
        "id": 10,
        "localized_name": "Morphling"
      },
      {
        "name": "npc_dota_hero_phantom_lancer",
        "id": 12,
        "localized_name": "Phantom Lancer"
      },
      {
        "name": "npc_dota_hero_puck",
        "id": 13,
        "localized_name": "Puck"
      },
      {
        "name": "npc_dota_hero_pudge",
        "id": 14,
        "localized_name": "Pudge"
      },
      {
        "name": "npc_dota_hero_razor",
        "id": 15,
        "localized_name": "Razor"
      },
      {
        "name": "npc_dota_hero_sand_king",
        "id": 16,
        "localized_name": "Sand King"
      },
      {
        "name": "npc_dota_hero_storm_spirit",
        "id": 17,
        "localized_name": "Storm Spirit"
      },
      {
        "name": "npc_dota_hero_sven",
        "id": 18,
        "localized_name": "Sven"
      },
      {
        "name": "npc_dota_hero_tiny",
        "id": 19,
        "localized_name": "Tiny"
      },
      {
        "name": "npc_dota_hero_vengefulspirit",
        "id": 20,
        "localized_name": "Vengeful Spirit"
      },
      {
        "name": "npc_dota_hero_windrunner",
        "id": 21,
        "localized_name": "Windrunner"
      },
      {
        "name": "npc_dota_hero_zuus",
        "id": 22,
        "localized_name": "Zeus"
      },
      {
        "name": "npc_dota_hero_kunkka",
        "id": 23,
        "localized_name": "Kunkka"
      },
      {
        "name": "npc_dota_hero_lina",
        "id": 25,
        "localized_name": "Lina"
      },
      {
        "name": "npc_dota_hero_lich",
        "id": 31,
        "localized_name": "Lich"
      },
      {
        "name": "npc_dota_hero_lion",
        "id": 26,
        "localized_name": "Lion"
      },
      {
        "name": "npc_dota_hero_shadow_shaman",
        "id": 27,
        "localized_name": "Shadow Shaman"
      },
      {
        "name": "npc_dota_hero_slardar",
        "id": 28,
        "localized_name": "Slardar"
      },
      {
        "name": "npc_dota_hero_tidehunter",
        "id": 29,
        "localized_name": "Tidehunter"
      },
      {
        "name": "npc_dota_hero_witch_doctor",
        "id": 30,
        "localized_name": "Witch Doctor"
      },
      {
        "name": "npc_dota_hero_riki",
        "id": 32,
        "localized_name": "Riki"
      },
      {
        "name": "npc_dota_hero_enigma",
        "id": 33,
        "localized_name": "Enigma"
      },
      {
        "name": "npc_dota_hero_tinker",
        "id": 34,
        "localized_name": "Tinker"
      },
      {
        "name": "npc_dota_hero_sniper",
        "id": 35,
        "localized_name": "Sniper"
      },
      {
        "name": "npc_dota_hero_necrolyte",
        "id": 36,
        "localized_name": "Necrolyte"
      },
      {
        "name": "npc_dota_hero_warlock",
        "id": 37,
        "localized_name": "Warlock"
      },
      {
        "name": "npc_dota_hero_beastmaster",
        "id": 38,
        "localized_name": "Beastmaster"
      },
      {
        "name": "npc_dota_hero_queenofpain",
        "id": 39,
        "localized_name": "Queen of Pain"
      },
      {
        "name": "npc_dota_hero_venomancer",
        "id": 40,
        "localized_name": "Venomancer"
      },
      {
        "name": "npc_dota_hero_faceless_void",
        "id": 41,
        "localized_name": "Faceless Void"
      },
      {
        "name": "npc_dota_hero_skeleton_king",
        "id": 42,
        "localized_name": "Skeleton King"
      },
      {
        "name": "npc_dota_hero_death_prophet",
        "id": 43,
        "localized_name": "Death Prophet"
      },
      {
        "name": "npc_dota_hero_phantom_assassin",
        "id": 44,
        "localized_name": "Phantom Assassin"
      },
      {
        "name": "npc_dota_hero_pugna",
        "id": 45,
        "localized_name": "Pugna"
      },
      {
        "name": "npc_dota_hero_templar_assassin",
        "id": 46,
        "localized_name": "Templar Assassin"
      },
      {
        "name": "npc_dota_hero_viper",
        "id": 47,
        "localized_name": "Viper"
      },
      {
        "name": "npc_dota_hero_luna",
        "id": 48,
        "localized_name": "Luna"
      },
      {
        "name": "npc_dota_hero_dragon_knight",
        "id": 49,
        "localized_name": "Dragon Knight"
      },
      {
        "name": "npc_dota_hero_dazzle",
        "id": 50,
        "localized_name": "Dazzle"
      },
      {
        "name": "npc_dota_hero_rattletrap",
        "id": 51,
        "localized_name": "Clockwerk"
      },
      {
        "name": "npc_dota_hero_leshrac",
        "id": 52,
        "localized_name": "Leshrac"
      },
      {
        "name": "npc_dota_hero_furion",
        "id": 53,
        "localized_name": "Nature's Prophet"
      },
      {
        "name": "npc_dota_hero_life_stealer",
        "id": 54,
        "localized_name": "Lifestealer"
      },
      {
        "name": "npc_dota_hero_dark_seer",
        "id": 55,
        "localized_name": "Dark Seer"
      },
      {
        "name": "npc_dota_hero_clinkz",
        "id": 56,
        "localized_name": "Clinkz"
      },
      {
        "name": "npc_dota_hero_omniknight",
        "id": 57,
        "localized_name": "Omniknight"
      },
      {
        "name": "npc_dota_hero_enchantress",
        "id": 58,
        "localized_name": "Enchantress"
      },
      {
        "name": "npc_dota_hero_huskar",
        "id": 59,
        "localized_name": "Huskar"
      },
      {
        "name": "npc_dota_hero_night_stalker",
        "id": 60,
        "localized_name": "Night Stalker"
      },
      {
        "name": "npc_dota_hero_broodmother",
        "id": 61,
        "localized_name": "Broodmother"
      },
      {
        "name": "npc_dota_hero_bounty_hunter",
        "id": 62,
        "localized_name": "Bounty Hunter"
      },
      {
        "name": "npc_dota_hero_weaver",
        "id": 63,
        "localized_name": "Weaver"
      },
      {
        "name": "npc_dota_hero_jakiro",
        "id": 64,
        "localized_name": "Jakiro"
      },
      {
        "name": "npc_dota_hero_batrider",
        "id": 65,
        "localized_name": "Batrider"
      },
      {
        "name": "npc_dota_hero_chen",
        "id": 66,
        "localized_name": "Chen"
      },
      {
        "name": "npc_dota_hero_spectre",
        "id": 67,
        "localized_name": "Spectre"
      },
      {
        "name": "npc_dota_hero_doom_bringer",
        "id": 69,
        "localized_name": "Doom"
      },
      {
        "name": "npc_dota_hero_ancient_apparition",
        "id": 68,
        "localized_name": "Ancient Apparition"
      },
      {
        "name": "npc_dota_hero_ursa",
        "id": 70,
        "localized_name": "Ursa"
      },
      {
        "name": "npc_dota_hero_spirit_breaker",
        "id": 71,
        "localized_name": "Spirit Breaker"
      },
      {
        "name": "npc_dota_hero_gyrocopter",
        "id": 72,
        "localized_name": "Gyrocopter"
      },
      {
        "name": "npc_dota_hero_alchemist",
        "id": 73,
        "localized_name": "Alchemist"
      },
      {
        "name": "npc_dota_hero_invoker",
        "id": 74,
        "localized_name": "Invoker"
      },
      {
        "name": "npc_dota_hero_silencer",
        "id": 75,
        "localized_name": "Silencer"
      },
      {
        "name": "npc_dota_hero_obsidian_destroyer",
        "id": 76,
        "localized_name": "Outworld Devourer"
      },
      {
        "name": "npc_dota_hero_lycan",
        "id": 77,
        "localized_name": "Lycanthrope"
      },
      {
        "name": "npc_dota_hero_brewmaster",
        "id": 78,
        "localized_name": "Brewmaster"
      },
      {
        "name": "npc_dota_hero_shadow_demon",
        "id": 79,
        "localized_name": "Shadow Demon"
      },
      {
        "name": "npc_dota_hero_lone_druid",
        "id": 80,
        "localized_name": "Lone Druid"
      },
      {
        "name": "npc_dota_hero_chaos_knight",
        "id": 81,
        "localized_name": "Chaos Knight"
      },
      {
        "name": "npc_dota_hero_meepo",
        "id": 82,
        "localized_name": "Meepo"
      },
      {
        "name": "npc_dota_hero_treant",
        "id": 83,
        "localized_name": "Treant Protector"
      },
      {
        "name": "npc_dota_hero_ogre_magi",
        "id": 84,
        "localized_name": "Ogre Magi"
      },
      {
        "name": "npc_dota_hero_undying",
        "id": 85,
        "localized_name": "Undying"
      },
      {
        "name": "npc_dota_hero_rubick",
        "id": 86,
        "localized_name": "Rubick"
      },
      {
        "name": "npc_dota_hero_disruptor",
        "id": 87,
        "localized_name": "Disruptor"
      },
      {
        "name": "npc_dota_hero_nyx_assassin",
        "id": 88,
        "localized_name": "Nyx Assassin"
      },
      {
        "name": "npc_dota_hero_naga_siren",
        "id": 89,
        "localized_name": "Naga Siren"
      },
      {
        "name": "npc_dota_hero_keeper_of_the_light",
        "id": 90,
        "localized_name": "Keeper of the Light"
      },
      {
        "name": "npc_dota_hero_wisp",
        "id": 91,
        "localized_name": "Wisp"
      },
      {
        "name": "npc_dota_hero_visage",
        "id": 92,
        "localized_name": "Visage"
      },
      {
        "name": "npc_dota_hero_slark",
        "id": 93,
        "localized_name": "Slark"
      },
      {
        "name": "npc_dota_hero_medusa",
        "id": 94,
        "localized_name": "Medusa"
      },
      {
        "name": "npc_dota_hero_troll_warlord",
        "id": 95,
        "localized_name": "Troll Warlord"
      },
      {
        "name": "npc_dota_hero_centaur",
        "id": 96,
        "localized_name": "Centaur Warrunner"
      },
      {
        "name": "npc_dota_hero_magnataur",
        "id": 97,
        "localized_name": "Magnus"
      },
      {
        "name": "npc_dota_hero_shredder",
        "id": 98,
        "localized_name": "Timbersaw"
      },
      {
        "name": "npc_dota_hero_bristleback",
        "id": 99,
        "localized_name": "Bristleback"
      },
      {
        "name": "npc_dota_hero_tusk",
        "id": 100,
        "localized_name": "Tusk"
      }
    ]
    ,
    "count": 99
  }
}
									

The main use for this packet is to know which player is picking which hero and, in CM matches, what’s the pick/ban order.

CombatLog Messages *

What it looks like:

dota_combatlog eventid:190 
 type: 0 
 sourcename: 3 
 targetname: 79 
 attackername: 3 
 inflictorname: 0 
 attackerillusion: 0 
 targetillusion: 0 
 value: 44 
 health: 853 
 timestamp: 1215.758057 
 targetsourcename: 79 
									

Where is it:

In DEM_Packets, it’s a CSVCMsg_GameEvent whose descriptor name is dota_combatlog. It appears in the same tick that the event happens.

What’s in it:

type:

0= damage

1= healing

2= buff/debuff being applied

3= buff/debuff being removed

4= death

sourcename:

ID of the owner of the entity that’s performing the combat log action. That’s the same unit as the unit dealing damage in case of non-player-controlled entities or players, or the player that owns it in case of unit controlled players.

attackername:

ID of the entity performing the combat log action.

targetname:

ID of the entity affected by the combat log action.

targetsourcename:

ID of the owner of the entity affected by the combat log action.That’s the same unit as the unit affected in case of non-player-controlled entities or players, or the player that owns it in case of unit controlled players.

So if an Enigma Eidolon attacks an invoker Forge Spirit, source = Enigma, attacker = eidolon, target = forge spirit, targetsource = Invoker

inflictorname:

ID of the buff/debuff inflicted (only for type 2 or 3 messages).

attackerillusion:

1 if the attacker is an illusion, 0 otherwise.

targetillusion:

1 if the target is an illusion, 0 otherwise.

value:

The HP lost/gained by the target (for type 0 or 1 messages) due to that action.

health:

The current HP of the target AFTER that action was made.

timestamp:

The Combat Log Timestamp. This is not the game clock time nor the replay time, but as the game clock gets desynchronized with the replay time (1 second of the game clock is not always 30 ticks, sometimes it’s a bit more, sometimes it’s a bit less), the timestamp is a great reference to fix the eventual desynchronizations. This timestamp is a float number in seconds.

The IDs are obtained from a CombatLogTable which we’ll talk about soon.

It’s important to know that at least one of the source or target source has to be a player for this to show in the Combat Log. So creeps dealing damage to other creeps, for example, aren’t included here.

As you can see, here you can get lots of information, for example.

Creep Kills:

– Filter Type 4 entries only.

– Make sure the source is a player and that the target is not a player.

– If player is dire, make sure that the source is either a radiant player (minions), or the same unit, in which case, make sure that the unit you’re killing is not a dire unit.

– Same for radiant, but the opposite.

Denies:

– Filter Type 4 entries only.

– Make sure the source is a player and that the target is not a player.

– If player is dire, make sure that the source is either a dire player (denying minions), or the same unit, in which case, make sure that the unit you’re killing is a dire unit (standard deny).

– Same for radiant, but the opposite.

Hero Kills/Deaths/Assists (although we need a bit more info for this which we’ll talk about later):

– Track Type 0 entries from with source and target being players, persist the directional relation between attacker and receiver for 20 seconds, which is the assist time.

– Filter Type 4 entries.

– Make sure the target is a player.

– Make sure the target is not wielding an aegis or that it’s not a skeleton king with his ulti off cooldown(Can’t do that with the Combat Log, we’ll explain how to get this later). If he is, no death/kill/assist is awarded.

– If the source is not a player, check if it’s a neutral creep. In which case, a death is awarded to the target but no assist/kill is awarded to anyone.

– If the source is not a player nor a neutral creep, then the hero died to a creep/building/fountain. Check if you tracked any damage in the last 20 seconds from any player. If it’s only 1 player, that player takes the kill, if it’s more than 1, no kill is awarded and everyone in the list gets an assist.

– If the source is a player from your same team, it’s a deny, award a death but no kills nor assists.

– If the source is a player from the other team, award a kill to that player, award a death to the dead unit and check your tracking to see who gets an assist.

The assist tracking is imperfect at the moment. As a relic of its DotA past, Dota 2 counts damage fully absorbed for assists (Void’s Backtrack, TA’s Refraction, but not things like Missed attacks), however those 0 damage instances do not appear in the combat log, so if your only contribution to killing a TA was dealing damage while it has refraction up, you’ll miss the assist. You can track buffs/debuffs and, based on that, infer some cases (like a QoP casting Shadow Strike on a TA), the damage will not show, but the debuff will, so you’ll assume it has to have dealt damage at a certain point. There’s almost no point in doing this, as it will still miss all those other instances of damage where no debuff is applied. I assume that eventually Valve will add those 0 damage instances to the combat log, and with that, the assists will work appropriately.

The creep tracking is a bit buggy at the moment as well, particularly because of a few overlooked exceptions. Such is the case with undying zombies not being considered combat-log worthy by themselves, and thus not registering combat log events when they hit creeps, so if they last hit one, we can’t know. That’s going to be fixed eventually, however.

Building Kills:

– Filter Type 4 entries.

– Make sure the target is a tower.

– Checking whether it was a deny is made by checking whether the source is on the same team as the tower.

Because the combat log only covers information where either the source or the targetsource are players, this method only covers towers destroyed by players, to calculate the towers destroyed by creeps, another method (which will be explained later) is needed.

Roshan Kills:

-Filter Type 4 entries whose target is Roshan. We can also know who killed it that way.

We probably want to know who picked the Aegis here as well, that must be done differently, as will be explained in a later section.

ChatEvents *

ChatEvents actually encompass a multitude of messages, but all of them have a similar format. This events are for every event that appears in text when you’re playing, including but not limited to Glyphs, Buybacks, Tower Kills, Hero Kills and Pauses. An important thing about this: There’s no timestamp on these messages, you only get the replay tick (which tells you how far along the replay this is. To convert to Game time, you have to find where the game begins and, from there, take into  account the length of the pauses taken after that. Game tick for an event will be Tick – Start Tick – (Total Paused Ticks after Start Tick).

What it looks like:

---- CDOTAUserMsg_ChatEvent (8 bytes) -----------------
type: CHAT_MESSAGE_TOWER_KILL 
value: 3
playerid_1: 8
playerid_2: -1
									

Where is it:

In DEM_Packets, in the frame when it happens.

What’s in it:

The 4 values in every packet are type, value, playerid_1 and playerid_2. Type is the event type, but the other packets vary depending on what packet type we’re talking about.

Player IDs are obtained by the order in the FileInfo packet, the first hero listed there has ID 0, and so on until ID 9 for the last one.

Examples:

type: CHAT_MESSAGE_TOWER_KILL (and CHAT_MESSAGE_TOWER_DENY and CHAT_MESSAGE_BARRACKS_KILL)

value:  The team who killed the tower(3=Radiant. 2=Dire), In the barracks message it says the barrack but not the team!  (The barrack is a power of 2 starting from 1=dire bottom melee, and incrementing to the next power of 2 going from melee to range, from bottom to top, from dire to radiant.)

playerid_1: The player who got the last hit (-1if it was a creep)

playerid_2: Unused

Note: as you can see, there’s no information on what tower/barracks it is, this presents a problem because while sometimes it can be inferred, (At any given moment no more than 3 towers can fall), and if you have posterior info on 2 of those, you can know which one it is, sometimes you don’t have that info though, and you can’t know.

To solve this, you need to think of it as a Sudoku of towers.

  • If a tower falls and no hero killed it: Check the overhead gold for several repetitions of a 200, 240, 280 or 320 message in the same tick. That will tell you what tier that tower it is.
  • Whenever you identify an event with a specific tower, remove that tower from every other event’s realm of possibility.
  • Whenever you find a combat log message dealing damage to a T2 or T3 tower, any event from there on, can’t be the T1 or T2 towers that were in that same lane, because to damage that T2/T3, the previous ones have to have fallen.
  • Whenever you find a combat log message dealing damage to a Tower, if that tower was one of the possibilities for one of the already fallen towers, remove that possibility.
  • Whenever you identify a T2, T3 tower or barrack, check if the previous towers from that lane were already identified, if they weren’t, check if only one of the previous events can be either of those towers, in that case, that has to be it.
  • Whenever, by process of elimination, you find that a building HAS to be a specific tower, rerun all the previous analysis to see if you discover new information.

At the end of this, you’ll have all the towers that can be inferred, in some cases you will have 2 or 3 choices for a tower, in which case we set it as  “Unknown Tx Tower” where x is that tower’s tier. Barracks can always be deduced They go in incremental powers of 2, starting by the Dire side to the Dire Side, Bottom to Top, Melee to Ranged, so Bottom Melee Dire Rax = 1 and Top Ranged Radiant Rax = 2048.

For most standard matches this should suffice for all that matters. If you feel this is not enough, I might research into finding the “Dire’s Bottom Tower has fallen” sounds that appear a few seconds after the tower falls to deduce the remaining part, but as they don’t occur in the same frame, and depending on what’s going on, it might not sound, it’s a bit unreliable.

—- CDOTAUserMsg_ChatEvent (8 bytes) —————–

type: CHAT_MESSAGE_GLYPH_USED

value: Always 0

playerid_1: Team that used it (3=Radiant, 2=Dire)

playerid_2: Unused

—- CDOTAUserMsg_ChatEvent (8 bytes) —————–

type: CHAT_MESSAGE_AEGIS (Also CHAT_MESSAGE_AEGIS_STOLEN when a player from the other team takes the Aegis and CHAT_MESSAGE_DENIED_AEGIS when a player kills the Aegis)

value: Always 0

playerid_1: The Id of the player who got/denied the Aegis

playerid_2: Unused

This can be used in conjunction with the Combat Log to get all info about a Roshan kill. You just have to look the AEGIS message that comes, at most, 10 minutes after a Roshan kill. I guess someone dropping an Aegis and leaving it in the floor until the next rosh could mess this up, but that NEVER happens.

—- CDOTAUserMsg_ChatEvent (8 bytes) —————–

type: CHAT_MESSAGE_PAUSED (or CHAT_MESSAGE_UNPAUSED)

value: Always 0

playerid_1: ID of Player who paused

playerid_2: Unused.

This helps you track the amount of time a game was paused to adjust for other events.

—- CDOTAUserMsg_ChatEvent (8 bytes) —————–

type: CHAT_MESSAGE_BUYBACK

value: Always 0

playerid_1: ID of the Player who bought back.

playerid_2: Unused.

Self explanatory by now.

—- CDOTAUserMsg_ChatEvent (8 bytes) —————–

type: CHAT_MESSAGE_RUNE_PICKUP (and CHAT_MESSAGE_RUNE_BOTTLE)

value: Rune type. 0=DD, 1=Haste, 2=Illusion, 3=Invisibility, 4=Regen

playerid_1: Player who used/bottled the rune.

playerid_2: Unused.

This message appears when a rune is bottled or activated. The PICKUP name is deceptive, because it also shows when a rune is used from a bottle, so in order to get just rune pickups, you have to filter RUNE_PICKUP messages within 2 minutes of a RUNE_BOTTLE that share every field to avoid the false positives.

—- CDOTAUserMsg_ChatEvent (8 bytes) —————–

type: CHAT_MESSAGE_COURIER_LOST

value: Always lost

playerid_1: ID of the Player who killed the courier (-1 if it was a creep/tower/whatever)

playerid_2: Unused.

Self Explanatory again.

There are a few more ChatEvents, like when a hero disconnects, reconnects, abandons, hero kills, first blood, you get megacreeps, and a few others. You might find some use for them if you want to go in heavy detail.

SayText2 *

SayText2 events are All Chat Messages. The format is very simple

—- CUserMsg_SayText2 (40 bytes) —————–

client: Client ID of the person speaking. Client ID is based, I think, on connection order, but we don’t care about this, because we have the player name to match with the players anyways.

chat: Always true, I think. Irrelevant.

format: Always “DOTA_Chat_All” because Team Chat isn’t recorded

prefix: The name of the player, e.g: “FnaticRC.SMURF-”

text: What it’s being said, e.g: “gg”

CDOTAUserMsg_ParticleManager *

What it looks like:

---- CDOTAUserMsg_ParticleManager (15 bytes) -----------------
type: DOTA_PARTICLE_MANAGER_EVENT_CREATE
index: 75
create_particle {
  particle_name_index: 944
  attach_type: 1
  entity_handle: 295032
}
									

Where is it:

In the DEM_Packets. There are several types, we’re only interested in the DOTA_PARTICLE_MANAGER_EVENT_CREATE

What’s in it:

type: We only care about DOTA_PARTICLE_MANAGER_EVENT_CREATE

index: Particle unique identifier (for other Particle Manager related events)

particle_name_index: The particle name, which can be found on a string table. This is what lets us know what particle is the one being created.

attach_type: I have no idea what this is, but it seems to be either a 1, 2, 5 or 8.

entity_handle: The Entity ID of the object that spawns the particles. This is uncorrelated to any other ID in the game. Apparently players always get the same 10 ids (they’re 6~7 digit numbers), but I haven’t found a reliable way of mapping them every time. That’s why we’ll have to resort to a little trick to do so which should be effective except under the weirdest circumstances.

This is a type of message that appears whenever a particle is created or destroyed. We’re interested in finding one specific particle: LevelUps, but we’ll take a look at all of them, because if we find a hero specific particle, we can determine that the entity_handle belongs to said hero. This doesn’t work if at least 2 heroes don’t spawn any particle effect in game at all (i.e: don’t use their skills). If it’s only 1, we can deduce by process of elimination. These entity IDs also map to the modifiers, and there are hero-specific modifiers as well. With both particles and modifiers, it’s easy to deduce at least 9 heroes (thus being able to deduce the 10th by elimination) in most cases.

CDOTAUserMsg_UnitEvent *

What it looks like:

---- CDOTAUserMsg_UnitEvent (201 bytes) -----------------
msg_type: DOTA_UNIT_SPEECH_CLIENTSIDE_RULES
entity_index: 33
speech_match_on_client {
  concept: 18
  recipient_type: 8
  responsequery {
    facts {
      key: 29
      valtype: STRING
      val_string: "pre_game"
    }
    facts {
      key: 0
      valtype: STRING
      val_string: "TLK_DOTA_CUSTOM"
    }
    facts {
      key: 101
      valtype: NUMERIC
      val_numeric: 1
    }
    facts {
      key: 8
      valtype: NUMERIC
      val_numeric: 0
    }
    facts {
      key: 16
      valtype: NUMERIC
      val_numeric: 56
    }
    facts {
      key: 34
      valtype: STRING
      val_string: "npc_dota_hero_announcer"
    }
    facts {
      key: 137
      valtype: STRING
      val_string: "Announcer"
    }
    facts {
      key: 138
      valtype: NUMERIC
      val_numeric: 150
    }
    facts {
      key: 139
      valtype: NUMERIC
      val_numeric: 1
    }
    facts {
      key: 140
      valtype: NUMERIC
      val_numeric: 0
    }
    facts {
      key: 141
      valtype: NUMERIC
      val_numeric: 1
    }
    facts {
      key: 142
      valtype: STRING
      val_string: "dota"
    }
  }
  randomseed: 1617819336
}
									

Where is it:

In DEM_Packets.

What it is:

Well, unit events are for lots of things, but the ones we care about are the ones with the DOTA_UNIT_SPEECH_CLIENTSIDE_RULES. These messages appear whenever the game plays an announcer sound, or any other sound that’s not related to a hero’s actions.

I haven’t interpreted every single message, but I know 2 that are really important.

Every packet of this type has a speech_match_on_client  field, which in term has a responsequery field of its own, which has a facts field. If you check the first entry of this facts field, it might be an int value or a string value. If it’s a string and the string is “pre_game”, you know with 100% certainty that it’s the sound that says “Prepare for battle”, exactly 80 seconds before the game starts (minute 0:00, when creeps spawn). If it’s a string and the string is “game_start”, then you know with 100% certainty that it’s the message that says “The Battle Begins!” which sounds 3 seconds before the game starts.

It’s important to check for both messages, because either of those might not appear if there’s something else going on (like heroes dying or a roshan kill). This is used to synchronize game time (clock) with replay time (the time in the replay bar).

CDOTAUserMsg_OverheadEvent *

What it looks like:

---- CDOTAUserMsg_OverheadEvent (9 bytes) -----------------
message_type: OVERHEAD_ALERT_GOLD
value: 43
target_player_entindex: 2
target_entindex: 157
									

Where it is:

In the DEM_Packets.

What it is:

Overhead Events are every text/icon that appear on top of heroes/creeps on some events. OVERHEAD_ALERT_GOLD, whenever a hero does something that gives him gold (kill a unit, sell something, get gold from a tower/roshan/courier kill), OVERHEAD_ALERT_DENY is when a “!” sign appears because of a deny (be it a hero or a creep). Other Alert messages includes Block, Spell damage, Critical, Miss, and Poison Damage, but they’re not as important for our purposes.

The value field tells us how much gold it is, in the gold message, and it’s 0 in the deny message. The target_entindex tells us over which unit does the message appear, we do not really care much about that, but what we do care about is the target_player_entindex, which is the player to whom the gold/assist belongs. Unsurprisingly, this ID is not related to any of the ids from before, and it has to do, in the case of players, with connection times (First one will be 1, etc). There’s no real way of knowing the connection order that I know off (there are CONNECT packets but they aren’t related to to heroes or players). So we use a little trick here: We store all the events, then we look in the combat log for creep kills. When we find one, we check if there’s only one OverheadGold event at that specific tick. If that’s the case, then you know that ID belongs to the player that lasthitted. This doesn’t work if for some abnormal reason a hero didn’t get a last hit ALL match (or at least every single creep kill was at the same time as other things dying). You could do some heuristics to infer even if that’s not the case (try denies, check with towers to separate between teams, check for tower kills with gold > 350 to know who lasthit the tower, etc.)

This also doesn’t work with Alchemist’s Greevil Greed for some reason (it isn’t listed). Probably just an oversight from Valve.

CDemoStringTables *

What it looks like:

#1 CombatLogNames flags:0x1 (8 Items) 261 bytes
    #0 'dota_unknown' (0 bytes)
    #1 'npc_dota_hero_luna' (0 bytes)
    #2 'modifier_luna_lunar_blessing_aura' (0 bytes)
    #3 'npc_dota_creep_goodguys_melee' (0 bytes)
    #4 'npc_dota_hero_ancient_apparition' (0 bytes)
    #5 'npc_dota_creep_badguys_melee' (0 bytes)
    #6 'npc_dota_creep_goodguys_ranged' (0 bytes)
    #7 'npc_dota_creep_badguys_ranged' (0 bytes)
   (...)

#18 ModifierNames flags:0x1 (917 Items) 32214 bytes
    #0 'modifier_item_bracer' (0 bytes)
    #1 'modifier_item_gauntlets' (0 bytes)
    #2 'modifier_item_circlet' (0 bytes)
    #3 'modifier_item_slippers' (0 bytes)
    #4 'modifier_item_wraith_band' (0 bytes)
    #5 'modifier_item_mantle' (0 bytes)
    #6 'modifier_item_null_talisman' (0 bytes)
    #7 'modifier_tango_heal' (0 bytes)
(...)

#11 ParticleEffectNames flags:0x1 (3969 Items) 115648 bytes
    #0 'error' (0 bytes)
    #1 'healing_clarity' (0 bytes)
    #2 'healing_tango' (0 bytes)
    #3 'healing_flask_b' (0 bytes)
    #4 'item_sheepstick' (0 bytes)
    #5 'healing_clarity_b' (0 bytes)
    #6 'healing_flask' (0 bytes)
    #7 'aura_assault_b' (0 bytes)
    #8 'aura_endurance' (0 bytes)

#19 ActiveModifiers flags:0x1 (170 Items) 6134 bytes
    #0 entry_type: DOTA_MODIFIER_ENTRY_TYPE_ACTIVE
parent: 73759
index: 1
serial_num: 1
name: 841
creation_time: 1
caster: 73759
aura: false
    #1 entry_type: DOTA_MODIFIER_ENTRY_TYPE_ACTIVE
parent: 1712160
index: 1
serial_num: 2
name: 841
creation_time: 1
caster: 1712160
aura: false
									

Where is it:

On DEM_FullPacket and the DEM_StringTables packets. CombatLog table only shows items that have participated in the combat log until that point in the game, so you should go to the last DEM_FullPacket to get the complete one.

What’s in it:

CombatLog Tables map numbers to Players/Units/Spells/Buffs/Debuffs, and anything that might appear in the Combat Log.

ModifierNames tables map numbers to anything that might externally modify your hero’s stats like Items and Auras. This is useful to track item times.

ParticleEffectNames  tables map numbers to particle effects. Of special importance is the levelup particle effect, as it allows you to track players’ level up times. Another useful particle effect to track is the “death_tombstone” particle from skeleton king’s reincarnate, that way we can avoid false positives with his death.

ActiveModifiers are different from the others, as they’re not static lists but rather contain information on creation times, so I’ll describe them in more detail:

#1 entry_type: DOTA_MODIFIER_ENTRY_TYPE_ACTIVE

parent: The ID of the source of the entity acquiring the modifier

index: An index to identify it among several packets (index doesn’t imply order though)

serial_num: Unique ID for the entry

name: The item/effect ID from the ModifierNames table.

creation_time: Timestamp.

caster: The ID of the entity acquiring the modifier.

aura: Whether the effect is an aura.

The IDs from the caster and parent fields are the same that we found using the Particle events. With that and the timestamp, we can get when any item is acquired. Note that this tracks when the item gets to your hero and not when it’s purchased. But for statistical purposes, this is the number we care about.

Also to note is that because this info is in the fullpacket you don’t have the tick where it happens, but rather the timestamp, from which you can infer the gametime (by extrapolating from the combatlogs who also have timestamps), and from the gametime you have to find the replay time, as opposed to what you’d do in any other info packet.

5. The full parsing process.

So, having described the individual parts, the process works somewhat like this:

-Parse the replay, store all the things in the proper structures, but do nothing with it. While doing this, track the times when pauses begin/end and also the tick where the game begins.

– Replace all instances of each number in the combat log with the proper spell/hero/unit/whatever.

– Do all the aforementioned tricks to match the entities from the Particles, Modifiers and Overhead messages with players.

– Now you’re ready to pretty much surf through each table and get the relevant info.

Wishlist *

There are a few things that could be improved from their current status, or that could be added, some of these things could be done with either complicated heuristics, things I don’t know how to do, or by Valve adding a small amount of info to the replays:

– Better detection of Overhead and Modifiers/Particle IDs. (There’s probably a more reliable way to know what appears because of what and to whom it belongs).

-Overhead Gold packets for Greevil’s Greed gold!

– Proper creep kill detection when an undying zombie kills a creep (and any other weird cases that might exist).

– Better Assist detection for cases with refraction/backtrack (Adding 0-damage entries to the combat log would work)

– Better Tower Kill Detection, when killed by non heroes (Adding Combat Log entries when a building dies regardless of killer, as opposed to “only when a hero kills it” seems like the best solution)

– Skill builds! I know they’re there, I don’t know where they are. If you know, let me know. If you’re Valve and want to dump a string like “13121412224333+4++++++++++”or create a “HERO_USE_SKILLPOINT” message for each hero, that’d be peachy too!

– Criticals/Evasion/On Hit effect tracking, which I’m not doing because I don’t really find a cool use for it.

And on a more “can’t hurt to dream” thing:

– Some way to track when ANY skill is casted, regardless of whether it deals damage, lands or whatever. You can possibly do that with lots of skills tracking particles at the moment, but not every skill has a particle effect. You can also possibly do the ones that don’t have particle effects tracking sounds, but it’s awfully inconvenient and unreliable.

– Instant Position and HP/MP on all heroes. That would be cool.

Is there anything else I’m missing? Let me know and I’ll add it here.

Additional Info *

Feel free to use this replay parser binary as is, I would appreciate if you credited me if you’re using it in any other application. I’ll probably release the source code eventually if there’s enough interest, at the moment it’s a bit too untidy for my own tastes.

If you need to contact me, you can do so at [email protected]

Cheers!

Bruno

Advertisement
  • kecchu

    this is a lot of work o_o
    thx

  • Soundwave

    wa wat..

  • wat

    wat

  • gangland banger

    so bruno IS icefrog ok

  • Anonymous

    Press x to .json

  • Havocked

    Cool sounds like somethign cool for teh community

    BTW Cyborg what happened to GIVEAWAY!!!!

  • http://twitter.com/diggtrap Peiul

    Awesome. Nothing more to say. I’m done.

  • http://twitter.com/StanTheOneSage Stan

    That’s…a lot of words… definitely interesting…ICEFROG

  • MF DOOM

    He’s the Statman!

    Ski bi di bi di do bap do

    Do bam do

    Bada bwi ba ba bada bo

    Baba ba da bo

    Bwi ba ba ba do

  • Guy

    my brain exploded from the amounts of wat

  • Ice2k5

    Good to know that Bruno actually can write code, didn’t thought that about him!

  • Smart man

    This is too much to read bruno, Some people might not have the time to read it all up, If you care enough try to summarize and make a video introduction and important things to know.

    • nebb

      If you care about this type of stuff enough to be able to use it you’ll care enough to read it. Awesome, awesome work Bruno

    • Mr. Cookie

      wtf.. the amount of time u waste reading it up doesnt compare to the fucking time he wasted writting it up and building it up x 100.. Just go away.. And it is very sumarized actually.,. Rlly well done Bruno. Epic job.

  • babidi

    I LOVE YOU BRUNO

  • cesardka

    Thanks a lot for this tool and good job on doing such hard work!

  • http://twitter.com/Magnusm1 Magnus Lidman

    So it’s used for collecting stats and data, correct?

  • http://twitter.com/JOKERdota2 JOKER

    thanks , useful

  • http://twitter.com/FabrizioEkite Fabrizio Ekite

    the script it’s super effective! but it really needs a visual interface for a better readability that is acutually very low. Anyway superjob Bruno!!!! and tx Matt for this awesome post!

  • Serird

    Someone know how the introduction screen was made?
    (look like SFM, but Dota 2’s map don’t load in SFM ._. )

  • Thomas Hansen

    I need the source code to this.

  • DotaStatsRock

    Thanks bruno. FYI, running from the commandline gives me a “cant find dictionary.txt” error, and yes the entire zip file was extracted to the same directory. If I drag a dem file onto the exe in explorer however, it works.

    • Djei

      Are you still getting this issue? I tried contacting bruno by mail but there seems to be some issue with the address he gave

      • Bruno

        Should be uploading a new version with a fix for this soon

  • endage

    wtf is this

  • http://www.facebook.com/oleg.verevkin.3 Oleg Verevkin

    error msvcp110

    • Justin Mackritis

      same, did you figure out how to fix it?

  • KazeZeroX

    How can we fix “not a valid Win32 application” ?

  • http://www.facebook.com/cordell.brathwaite Cordell Brathwaite

    Bruno doesn’t just sit on his ass all day and laugh at pyrion flax? OMG

  • Bilwi

    Stu … is that the guy i played WoW with in THC? Jesus if it is, hit me on vanTuni @ steam!

  • MiNerLinG

    valve hates community creating the better tools then they’ve created, so good luck.

  • eXcuvator

    A nice tool you have written there for dota2. Would have been a shame if someone would … invalidate it.

  • cyz

    Bruno, do you know if it’s possible to detect when a ward is placed/dies? I can’t seem to find any information on the mechanics of wards and it’d be nice if we could do some analysis on warding.

    • TheMagician

      Bruno cannot do that. As he stated in his post, the technique used to parse the replay is not reliable at all. Only magician have real datas using svc_PacketEntities message.

  • derp

    err…yes, my house is blue underneath a dinosaur wearing a bra on a bicycle

  • AmidoriA

    Nice program but i noticed some little bug in .txt result about Roshan

    It got

    Roshan Kills:

    27:08 (21:58). Dire team. Grabber: npc_dota_hero_phantom_lancer

    Other log in this program will output the hero’s name not id.

    :D

  • Dipri

    Hello, good day. I’m interested to know if you can download all the games of a tournament and use your tool for analysis. Might I explain how I can download?

  • XXDD

    Ain’t nobody got time to read this !

  • Paulo Clash

    nemly e nemlerey

  • manoel

    When I tried to use the program, it appeared to be missing the MSCVP110.dll. what can I do to solve this?

  • Hi

    I cant use this parser,when i click on the .exe,it runs it for a split second and then the window closes.What do i do to fix this problem?

  • FlaggyVisitor

    Hi Cyborgmatt, there will be any update of this replay parser?

  • Furor

    Hi, copied a replay to the parser and opened the parse text file. Is there any way I can see the Total Damage done by each hero for the entire game?

  • Sovereing2027

    useless
    post command like dota_sf_hud, pls

  • Alexandre Tolstenko Nogueira

    Can you share the source code?

  • Bosco

    Hi, i would like to know if this still works. Im a dota caster ind amateur statsman, ill love to know if this tool is still working. Thx.