@deademx/dota2
v3.1.1
Published
Dota 2 (Source 2) demo and replay parser with playback support for Node.js and browsers
Maintainers
Readme
@deademx/dota2 is a Dota 2 (Source 2) demo parser and replay player for Node.js, Deno, Bun, and browsers, built on top of @deademx/engine.
For the shared parser model, player lifecycle, interceptors, configuration, and the full API surface, see the engine documentation. This document covers Dota 2-specific usage only.
Other game implementations: deadem (Deadlock) and @deademx/cs2 (Counter-Strike 2).
Contents
- Installation Install from npm or use the browser bundle.
- Quick start Minimal example to parse a Dota 2 demo file.
- Examples Runnable example scripts covering parsing and player.
- Usage
Dota 2-specific usage patterns.
- Replay file
Parse a Dota 2 replay from a
.demfile. - Data extraction Extract Dota 2 chat and query entities.
- Playback and seeking Inspect Dota 2 state at a specific tick.
- Replay file
Parse a Dota 2 replay from a
- Compatibility Supported game patch and runtimes.
- Performance Measured throughput benchmarks.
- License Project licensing information.
Installation
Node.js / Deno / Bun
npm install @deademx/dota2 --saveimport { Parser, Player } from '@deademx/dota2';Browser
<script src="//cdn.jsdelivr.net/npm/@deademx/[email protected]/dist/deadem-dota2.min.js"></script>const { Parser, Player } = window.deademDota2;Quick start
import { createReadStream } from 'node:fs';
import { Parser, Printer } from '@deademx/dota2';
const parser = new Parser();
const printer = new Printer(parser);
await parser.parse(createReadStream(PATH_TO_DEM_FILE));
await parser.dispose();
printer.printStats();Parser and Player in @deademx/dota2 are drop-in subclasses of the engine classes — the schema registry is built automatically, so no extra wiring is required.
Examples
All example scripts live in the examples-node-dota2 package. They look for demo files in /demos; missing files are downloaded automatically from deadem.com.
Parsing
| # | Description | Source | Command |
| --- | --- | --- | --- |
| 100 | Parse a single replay file | 100_parse.js | node ./packages/examples-node-dota2/scripts/100_parse.js |
| 101 | Parse multiple replay files | 101_parse_multiple.js | node ./packages/examples-node-dota2/scripts/101_parse_multiple.js --matches="8773493455,8777738576" |
| 102 | Parse selected message types | 102_parse_selective.js | node ./packages/examples-node-dota2/scripts/102_parse_selective.js |
| 103 | Print chat messages | 103_parse_chat.js | node ./packages/examples-node-dota2/scripts/103_parse_chat.js |
| 104 | Rank high-churn entity classes and fields from ENTITY_PACKET deltas | 104_parse_entity_field_stats.js | node ./packages/examples-node-dota2/scripts/104_parse_entity_field_stats.js |
Player
| # | Description | Source | Command |
| --- | --- | --- | --- |
| 200 | Load, seek, play, and pause a replay | 200_play.js | node ./packages/examples-node-dota2/scripts/200_play.js |
Usage
Replay file
import { createReadStream } from 'node:fs';
import { Parser, Printer } from '@deademx/dota2';
const parser = new Parser();
const printer = new Printer(parser);
await parser.parse(createReadStream(PATH_TO_DEM_FILE));
await parser.dispose();
printer.printStats();Data extraction
Dota 2-specific message types are exposed via the extended MessagePacketType — for example, DOTA_UM_CHAT_MESSAGE, DOTA_UM_CHAT_EVENT, DOTA_UM_OVERHEAD_EVENT, DOTA_UM_COMBAT_LOG_DATA_HLTV, and more. Dota 2-specific string tables (MODIFIER_NAMES, COMBAT_LOG_NAMES, ECON_ITEMS, …) are exposed via the extended StringTableType.
Filter and extract chat messages:
import { InterceptorStage, MessagePacketType, Parser, ParserConfiguration } from '@deademx/dota2';
const parser = new Parser(new ParserConfiguration({
messagePacketTypes: [ MessagePacketType.DOTA_UM_CHAT_MESSAGE ]
}));
parser.registerPostInterceptor(InterceptorStage.MESSAGE_PACKET, (demoPacket, messagePacket) => {
if (messagePacket.type === MessagePacketType.DOTA_UM_CHAT_MESSAGE) {
console.log(messagePacket.data);
}
});
await parser.parse(readable);
await parser.dispose();Query entities and classes after the parse completes:
await parser.parse(readable);
const demo = parser.getDemo();
demo.getEntitiesByClassName('CDOTAPlayerController').forEach((entity) => {
console.log(entity.getField('m_iszPlayerName'));
});Playback and seeking
import { createReadStream } from 'node:fs';
import { Player } from '@deademx/dota2';
const player = new Player();
await player.load(createReadStream(PATH_TO_DEM_FILE));
await player.seekToTick(player.getLastTick());
const demo = player.getDemo();
console.log(`Ticks: ${player.getFirstTick()} → ${player.getLastTick()}`);
console.log(`Entities at last tick: ${demo.getEntities().length}`);
await player.dispose();Compatibility
- Game patch: tested with Dota 2 demos from patch
7.41band below. - Runtimes: Node.js v18+, Deno, and Bun.
- Browsers: Chrome, Firefox, Safari, Edge.
Performance
For configuration trade-offs see the engine performance notes.
| # | Configuration | Ticks/sec | 30-min replay, sec | Max Heap, MB | Max ArrayBuffers, MB | Max RSS, MB |
| - | --- | --- | --- | --- | --- | --- |
| 1 | No filters (ParserConfiguration.DEFAULT) | 25 464 +- 3.63% | ~2.12 | 104 +- 18.28% | 48 +- 28.16% | 330 +- 0.92% |
| 2 | messagePacketTypes allowlist excluding SVC_PACKET_ENTITIES | 168 743 +- 3.21% | ~0.32 | 39 +- 6.07% | 22 +- 13.72% | 232 +- 6.87% |
| 3 | entityClasses allowlist | 89 779 +- 0.75% | ~0.60 | 75 +- 23.59% | 17 +- 20.27% | 250 +- 2.96% |
Runtime: Node.js v22.14.0.
License
This project is licensed under the MIT License.
