Skip to main content

Crate source2_demo

Crate source2_demo 

Source
Expand description

§source2-demo

Crates.io Documentation License

source2-demo is a Rust library for parsing Source 2 engine demo files.

§Supported Games

  • Dota 2
  • Deadlock
  • Counter-Strike 2

§Installation

Add the following to your Cargo.toml and enable the feature for the game you want to parse:

[dependencies]
# For Dota 2
source2-demo = { version = "0.5", features = ["dota"] }

# For Deadlock
# source2-demo = { version = "0.5", features = ["deadlock"] }

# For Counter-Strike 2
# source2-demo = { version = "0.5", features = ["cs2"] }

§Quick Start: Parsing Chat Messages

Here’s a simple program that prints chat messages from a Dota 2 replay. It handles the CDotaUserMsgChatMessage protobuf message and prints the player’s name and their message.

More examples can be found in the d2-examples and dl-examples directories.

use source2_demo::prelude::*;
use source2_demo::proto::*;

// Create a struct that implements the Default trait
#[derive(Default)]
struct Chat;

// Mark the impl block with the observer attribute
#[observer]
#[uses_all]
impl Chat {
    // Use the on_message attribute to mark the protobuf message handler
    #[on_message]
    fn handle_chat_msg(
        &mut self,
        ctx: &Context,
        chat_msg: CDotaUserMsgChatMessage, // Use any protobuf message as an argument
    ) -> ObserverResult {
        if let Ok(pr) = ctx.entities().get_by_class_name("CDOTA_PlayerResource") {
            let name: String = property!(
                pr,
                "m_vecPlayerData.{:04}.m_iszPlayerName",
                chat_msg.source_player_id()
            );
            println!("{}: {}", name, chat_msg.message_text());
        }
        Ok(())
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a parser
    let mut parser = Parser::from_reader(std::fs::File::open("replay.dem")?)?;

    // Register observers
    parser.register_observer::<Chat>();

    // Run the parser
    parser.run_to_end()?;

    Ok(())
}

§Quick Start: Rewriting Protobuf Messages

Use source2_demo::writer when you want to write a modified demo. The writer parses the input replay, applies registered rewriters, and writes a new replay stream to the output.

This example removes Dota 2 chat messages by rewriting a protobuf packet message:

use source2_demo::prelude::*;
use source2_demo::proto::CDotaUserMsgChatMessage;
use source2_demo::writer::*;
use std::fs::File;

#[derive(Default)]
struct RemoveChat;

#[rewriter]
impl RemoveChat {
    #[rewrite_packet_message]
    fn remove_chat(
        &mut self,
        _message: CDotaUserMsgChatMessage,
    ) -> Result<MessageRewrite, ParserError> {
        Ok(MessageRewrite::Drop)
    }
}

fn main() -> anyhow::Result<()> {
    let input = File::open("input.dem")?;
    let output = File::create("output.dem")?;

    let mut writer = DemoWriter::from_reader(input, output)?;
    writer.register_rewriter::<RemoveChat>();
    writer.run()?;

    Ok(())
}

§Quick Start: Rewriting Fields and String Tables

This Deadlock example anonymizes a replay by rewriting entity fields, a packet message, and userinfo entries in string tables. The full example is in dl-examples/anonymize-replay.

use source2_demo::prelude::*;
use source2_demo::proto::{
    CCitadelUserMsgPostMatchDetails, CMsgMatchMetaDataContents, CMsgPlayerInfo,
};
use source2_demo::writer::*;

#[derive(Default)]
struct ReplayAnonymizer;

#[rewriter]
impl ReplayAnonymizer {
    #[rewrite_field(class = "CCitadelPlayerController", field = "m_steamID")]
    fn remove_steam_id(&mut self, _value: u64) -> u64 {
        0
    }

    #[rewrite_packet_message]
    fn remove_post_match_details(
        &mut self,
        message: &mut CCitadelUserMsgPostMatchDetails,
    ) -> Result<MessageRewrite, ParserError> {
        if let Some(match_details) = message.match_details.as_mut() {
            let mut metadata = CMsgMatchMetaDataContents::decode(match_details.as_slice())?;
            if let Some(match_info) = metadata.match_info.as_mut() {
                match_info.match_id = Some(0);
                for player in &mut match_info.players {
                    player.account_id = Some(0);
                }
            }
            *match_details = metadata.encode_to_vec();
        }
        Ok(MessageRewrite::Rewrite)
    }

    #[rewrite_string_table_entry]
    fn remove_userinfo(
        &mut self,
        table_name: &str,
        entry: &mut StringTableEntryUpdate,
    ) -> Result<(), ParserError> {
        if table_name == "userinfo" {
            if let Some(value) = entry.value_mut() {
                let mut player = CMsgPlayerInfo::decode(value.as_slice())?;
                player.name = Some("Anonymous".to_string());
                player.xuid = Some(0);
                player.steamid = Some(0);
                *value = player.encode_to_vec();
            }
        }
        Ok(())
    }
}

§Building Examples

To build the examples, clone the repository and use Cargo:

git clone https://github.com/Rupas1k/source2-demo
cd source2-demo

# Build examples for a specific game
cd dl-examples # d2-examples
cargo build --release

§Run a Specific Example

cargo run --release -p example input.dem
cargo run --release -p example input.dem output.dem

§Features

The crate supports the following cargo features:

  • dota - Enable Dota 2 replay parsing (includes Dota 2 protobufs)
  • deadlock - Enable Deadlock replay parsing (includes Citadel protobufs)
  • cs2 - Enable Counter-Strike 2 replay parsing (includes CS2 protobufs)
  • unsafe - Disable bounds checking in the reader for improved performance.
  • mimalloc (default) - Use mimalloc as the global allocator for improved performance

You can enable multiple game features if needed:

source2-demo = { version = "*", features = ["dota", "cs2"] }

You can disable mimalloc if it causes issues on your platform (e.g., WebAssembly):

source2-demo = { version = "*", default-features = false }

§Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

§Credits

This project was inspired by and builds upon the work of other Source 2 demo parsers:

  • clarity - Java-based Source 2 replay parser
  • manta - Go-based Dota 2 replay parser

§License

This project is dual-licensed under either:

at your option.

§Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Modules§

error
Error types for the parser.
prelude
Prelude module for convenient reading/parsing imports.
proto
Protocol buffer definitions for Source 2 games.
writer
Demo rewriting APIs and lower-level bitstream writer utilities.

Macros§

property
Macro for getting property from crate::Entity.
try_property
Same as crate::property but returns None if property doesn’t exist for given crate::Entity or cannot be converted into given type.

Structs§

Class
Entity class definition.
Classes
Container for all entity classes in a replay.
Context
Current replay state accessible to observers.
Entities
Container for all entities in the replay.
Entity
Represents a game entity with its properties and state.
GameEvent
Represents a game event with its name and values.
GameEventList
Container for all game event definitions in a replay.
Interests
Bitflags for declaring observer interests.
Parser
Main parser for Source 2 demo files.
StringTable
A string table containing key-value pairs.
StringTableRow
A row in a string table.
StringTables
Container managing all string tables in a replay.

Enums§

EntityEvents
Events that can occur to entities during replay parsing.
EventValue
Value type for game event fields.
FieldValue
Value type for entity properties.

Traits§

DemoRunner
Trait for controlling replay parsing execution.
FieldRewriteResult
Converts a field rewrite handler result into an optional replacement.
IntoFieldValue
Converts ordinary Rust values into FieldValue replacements.
Observer
Observer trait for handling demo file events.

Type Aliases§

HashMap
Fast hash map using FxHash algorithm.
HashSet
Fast hash set using FxHash algorithm.
ObserverResult
Result type for observer callbacks.

Attribute Macros§

observer
Implements the Observer trait for your struct.
on_entity
Marks a method as an entity event handler.
on_game_event
Marks a method as a game event handler.
on_message
Marks a method as a protobuf message handler.
on_stop
Marks a method as a replay stop handler.
on_string_table
Marks a method as a string table update handler.
on_tick_end
Marks a method as a tick-end handler.
on_tick_start
Marks a method as a tick-start handler.
replace_entity_field
Replaces decoded entity field values with custom logic.
rewrite_demo_message
Rewrites an outer demo command payload.
rewrite_demo_string_tables
Rewrites a decoded CDemoStringTables outer demo message.
rewrite_field
Replaces decoded entity field values with class and field filters.
rewrite_packet_message
Rewrites an individual message inside a demo packet.
rewrite_packet_messages
Mutates the decoded packet message list after per-message rewrites.
rewrite_string_table_entry
Rewrites one decoded string table entry update.
rewrite_svc_create_string_table
Rewrites a decoded svc_CreateStringTable packet message.
rewrite_svc_update_string_table
Rewrites a decoded svc_UpdateStringTable packet message.
rewriter
Implements the DemoRewriter trait for your struct.
should_rewrite_entity
Filters which entities enter the entity field rewrite path.
should_track_entity
Filters which entities retain decoded field state while rewriting.
uses_all
Marks the impl block or individual method to enable all tracking.
uses_entities
Marks the impl block to enable entity tracking.
uses_game_events
Marks the impl block to enable game event tracking.
uses_string_tables
Marks the impl block to enable string table tracking.