<div align="center">
# boon-deadlock
[](https://crates.io/crates/boon-deadlock)
[](https://crates.io/crates/boon-deadlock)
[](https://docs.rs/boon-deadlock)
[](https://github.com/pnxenopoulos/boon/blob/main/LICENSE)
</div>
A fast [Deadlock](https://store.steampowered.com/app/1422450/Deadlock/) demo file (`.dem`) parser for Rust.
Part of the [Boon](https://github.com/pnxenopoulos/boon) project.
## Features
- Memory-mapped, zero-copy parsing for maximum throughput
- Match metadata (map, players, duration, build number)
- Full entity state at any tick via snapshot seeking
- Game event extraction with protobuf decoding
- Filtered tick streaming for efficient per-entity-class analysis
- Ability and modifier name lookups
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
boon-deadlock = "0.1"
```
Requires Rust 1.88+ (edition 2024).
## Quick Start
```rust,no_run
use std::path::Path;
use boon::Parser;
let parser = Parser::from_file(Path::new("match.dem")).unwrap();
parser.verify().unwrap();
// File header
let header = parser.file_header().unwrap();
println!("Map: {:?}", header.map_name);
println!("Build: {:?}", header.build_num);
// File info (playback time, players)
let info = parser.file_info().unwrap();
println!("Duration: {:?}s", info.playback_time);
```
## API Overview
### `Parser`
The main entry point. Owns the demo file data (memory-mapped or in-memory).
| `Parser::from_file(path)` | Open and memory-map a `.dem` file |
| `Parser::from_bytes(bytes)` | Parse from an in-memory buffer |
| `verify()` | Check magic bytes |
| `file_header()` | Decode `CDemoFileHeader` (map, server, build) |
| `file_info()` | Decode `CDemoFileInfo` (duration, players) |
| `messages()` | List all command headers in the file |
| `events(max_tick)` | Extract game events (legacy + Citadel user messages) |
| `parse_to_tick(tick)` | Parse to a specific tick, returning full entity state |
| `run_to_end(callback)` | Stream every tick with a callback |
| `run_to_end_filtered(filter, callback)` | Stream with an entity class filter (much faster) |
### `Context`
Returned by `parse_init`, `parse_to_tick`, and passed to tick callbacks. Contains:
- `entities` — all active entities (`EntityContainer`)
- `serializers` — field definitions per class
- `class_info` — class ID to name mappings
- `string_tables` — key-value tables (models, baselines, etc.)
- `tick` — current tick
- `tick_interval` — seconds per tick
### `Entity`
A single networked entity with class name and decoded field values.
```rust,ignore
// Look up fields by dotted path
let health = entity.get_by_name("m_iHealth", serializer);
let x = entity.get_by_name(
"CBodyComponent.m_skeletonInstance.m_vecOrigin.m_vecX",
serializer,
);
```
### Helper Functions
- `ability_name(id)` — resolve an ability hash to its name
- `modifier_name(id)` — resolve a modifier hash to its name
- `decode_event_payload(msg_type, data)` — decode a game event's protobuf payload
## Examples
Runnable examples are in [`examples/`](examples/). Each accepts a demo file path as a CLI argument.
```bash
# Match metadata
cargo run -p boon-deadlock --example info -- match.dem
# Game events (optionally filtered by name)
cargo run -p boon-deadlock --example events -- match.dem Damage
# Entity snapshot at a specific tick
cargo run -p boon-deadlock --example entities -- match.dem 5000
# Stream all ticks with a class filter
cargo run -p boon-deadlock --example player_ticks -- match.dem
```
| [`info`](examples/info.rs) | `file_header()`, `file_info()`, match metadata and player list |
| [`events`](examples/events.rs) | `events()`, event filtering, `decode_event_payload()` |
| [`entities`](examples/entities.rs) | `parse_to_tick()`, entity iteration, `get_by_name()`, `ability_name()` |
| [`player_ticks`](examples/player_ticks.rs) | `run_to_end_filtered()`, `resolve_field_key()`, per-tick streaming |
## Performance
For best throughput when you only need specific entity types, use `run_to_end_filtered` with a class filter. This skips field decoding for entities outside the filter set.
```rust,ignore
use std::collections::HashSet;
let filter: HashSet<&str> = ["CCitadelPlayerPawn"].into_iter().collect();
parser.run_to_end_filtered(&filter, |ctx| {
for (_, entity) in ctx.entities.iter() {
// Only CCitadelPlayerPawn entities are tracked
}
}).unwrap();
```
## License
MIT — see [LICENSE](https://github.com/pnxenopoulos/boon/blob/main/LICENSE) for details.