Open-source project
dotabuff/manta avatar
dotabuff/manta

manta: a Dota 2 Source 2 replay parser that hands you raw events

Dotabuff's Dota 2 replay parser in Go

694 stars122 forksGoMIT

At a glance

What is it?
manta is Dotabuff's Go library for parsing Dota 2 Source 2 replays. It is deliberately low level: it decodes the replay and calls your callbacks with the raw messages, leaving the modelling of a match to you, which makes it a building block for analytics rather than a finished stats tool.
Who is it for?
manta fits a Go team building its own Dota 2 analytics on top of Source 2 replays, and it does not fit someone who wants match statistics out of the box, or who needs to read Source 1 replays from before the Reborn engine. Before adopting it, parse one replay from the replays directory with the callback pattern from the README, then run make update to confirm the generated protobuf bindings still match the SteamDatabase Protobufs project.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A parser with no opinion on your data model

manta is described as a fully functional Dota 2 replay parser written in Go, targeting the Source 2 engine used by Dota 2 Reborn. The Getting Started section sets expectations in a way that is worth quoting for its precision: it is a low-level replay parser, which means it will provide you access to the raw data in the replay, but it does not provide any opinion on how that data should be structured for your use case. You will need to create callback functions, inspect the raw data, and decide how you are going to use it.

That is the whole product decision, and it has consequences on both sides. On the good side, nothing is hidden from you. There is no intermediate object model to fight, and every message the engine emitted is reachable, which matters when you want something the default schema never anticipated. On the other side, turning a replay into a draft scoreboard, a gold graph or an item timeline is entirely your work, and the library gives you no schema to start from.

The audience is therefore developers building Dota 2 data tooling: a team with a Go backend that wants to compute its own metrics from match replays, or a statistics site that needs a parser it can extend. The homepage is dotabuff.com, the project is maintained with development sponsored by Dotabuff, and the licence is MIT.

The support route is also specific: the README points at the #dota2replay channel on QuakeNet as the best place for questions about Dota 2 replay parsing, and asks that GitHub issues be used for actual bugs in manta rather than usage questions.

Two entry points: a file stream or bytes in memory

The usage example opens a replay file and hands the handle to a stream parser, and the code comment points at the alternative for callers who already have the bytes.

go
import (
  "log"
  "os"

  "github.com/dotabuff/manta"
  "github.com/dotabuff/manta/dota"
)

The comment on the open call notes that a new parser instance can be created from a file, and that NewParser([]byte) is the alternative, which is the distinction that matters when the replay came from a download pipeline rather than from disk.

go
func main() {
  // Create a new parser instance from a file. Alternatively see NewParser([]byte)
  f, err := os.Open("my_replay.dem")
  if err != nil {
    log.Fatalf("unable to open file: %s", err)
  }
  defer f.Close()

  p, err := manta.NewStreamParser(f)
  if err != nil {
    log.Fatalf("unable to create parser: %s", err)
  }

The parser is started with a single call, and the example logs a completion line once it returns.

go
  // Start parsing the replay!
  p.Start()

  log.Printf("Parse Complete!\n")
}

The structure of the example says something about the intended workflow. The file is opened and closed by the caller, the parser takes an io reader, and the run is a blocking call that ends when the replay is exhausted. There is no context parameter, no cancellation path and no error return from Start in the example, so a long-running service would need its own way to stop work, and the README does not document one.

The callback registry is the API you actually program against

Everything a consumer does, it does through a callback registry on the parser. The example registers a handler for the OnCUserMessageSayText2 event, which is a chat message, and the handler receives the message struct and returns an error.

go
  // Register a callback, this time for the OnCUserMessageSayText2 event.
  p.Callbacks.OnCUserMessageSayText2(func(m *dota.CUserMessageSayText2) error {
    log.Printf("%s said: %s\n", m.GetParam1(), m.GetParam2())
    return nil
  })

The getters on the message type are the access path to the payload, and the naming follows the underlying protobuf definitions rather than a hand written domain model. That is a direct consequence of the low-level design: you get parameter getters, not named fields like player name or item, because the library stops at the message boundary.

The registry itself is generated. The README says you can run make update to re-generate protobufs and callbacks based on the SteamDatabase Protobufs project, and the repository layout shows the machinery: a callbacks.go file at the top level, a gen/ directory, and an updateprotos program under it, with the Makefile splitting the work into an update-protobufs step that runs that program and a generate step that runs gen/callbacks.go.

That is the maintenance story in one line. When Valve ships a new engine build with new message types, the bindings are regenerated against the upstream protobuf definitions rather than hand maintained, and the callback names follow that upstream naming. A consequence worth knowing is that the API surface is tied to an external project, so a rename or a removed message upstream shows up in your code as a missing method.

The format layers live in the repository, not in a dependency

The top level of the repository shows how much of the Source 2 format is implemented directly in manta. There are files named huffman.go, lzss.go, string_table.go, sendtable.go, quantizedfloat.go, envelope_fast.go and field_patch.go, alongside the generic decoding pieces: field.go, field_decoder.go, field_path.go, field_reader.go, field_state.go, field_type.go, entity.go, serializer.go, stream.go, reader.go, class.go, demo_packet.go, modifier.go and game_event.go. There is also a vbkv/ directory, whose purpose the README does not document.

Reading the layout, the parser decodes the demo file container, decompresses the payload, resolves the networked property tables through the send table and string table mechanisms, and then presents typed messages. The field-oriented files are the layer that turns those networked properties into something a callback can read, and field_patch.go is where the engine's patching of field definitions would be handled.

The practical implication is that manta is self-contained for this task. The dependency list in go.mod is short and mostly plumbing: the protobuf runtimes, the snappy compression library, the spew and difflib helpers and the testify test framework.

toml
module github.com/dotabuff/manta

go 1.23

toolchain go1.24.5

The module requires Go 1.23 with a toolchain line pinning go1.24.5, so a build environment needs that toolchain available or able to fetch it.

Fixtures and replay files are checked in for the test suite

Testing is where a parser earns trust, and the repository is set up for it. There are test files next to the code, with names such as entity_test.go, field_decoder_test.go, huffman_test.go, lzss_test.go, reader_test.go, string_table_test.go, manta_test.go and util_test.go, plus a fixtures/ directory and a replays/ directory.

The Makefile shows the shape of the test workflow. The default target builds, test runs the suite with coverage and verbose output, and a separate testnew target runs a single test by name, which is the test for matching newer replay builds. The bench target is pinned to one benchmark, BenchmarkMatch8552595443, with a fixed benchtime and count so the numbers are comparable between runs.

bash
test:
	go test -cover -v

There are also profile targets, cpuprofile and memprofile, which produce a pprof SVG for a CPU profile and an allocation space report, and a cover target that measures coverage across both the manta package and the vbkv package and then opens an HTML coverage report. A sync-replays target uses s3cmd to sync the .dem files to the s3://manta.dotabuff/ bucket in the us-west-2 region, which is how the replay corpus is distributed to contributors.

The limitation here is a familiar one for parsers of a proprietary binary format. A test suite can only prove that the fixtures still parse, so correctness is tied to the set of replay files the project has collected, and a new engine build that changes the container format will only be caught by whoever first runs a new replay through it.

Source 1 replays live in a separate project

The alternative to manta, for the older half of the replay archive, is not a flag but a different repository. The README asks whether you are looking to parse Source 1 replays of original Dota 2, and points to Yasha, which it credits to Michael Fellinger as Dotabuff's Source 1 parser.

The difference in approach between the two is the engine, not the design. manta targets Source 2, the Reborn engine, which is what current Dota 2 matches produce, and its message types and networking model are the Source 2 ones. Yasha handles the Source 1 format. A dataset spanning both eras needs both parsers, and the field names and event semantics do not line up one to one, so normalising across them is your problem.

The README also credits the parsers that came before, which tells you the lineage of the approach. The C++ parser Alice was built by Robin Dietrich, the Java parser clarity by Martin Schrodt, and an original C++ parser edith by Drew Schleck, with Jason Coene named as the long-time primary maintainer of manta. The pattern across all of them is the same: decode the engine's own message types, expose them, and let the consumer build meaning on top.

That lineage is also why the callback API looks the way it does, and why a project that needs a friendlier model would be better served by building one on top of manta rather than by switching to a parser that hides the raw data.

Current commits, no release tags, MIT terms

The repository is not archived and the last push was on 2026-10-07, so the parser is being kept in step with the format it reads. It has no GitHub releases, which means there is no versioned artefact to pin: consumers depend on the master branch of the module, and a change in generated bindings can arrive without a tag announcing it.

For a library whose whole purpose is tracking a format that Valve changes on its own schedule, that is a defensible choice, since a tagged release would freeze you against a format that has moved on. It does mean your build is as reproducible as the master branch is on the day you run it, and a team with a strict dependency policy should vendor the module rather than track a branch.

The licence is MIT, with the LICENSE file at the top level, which permits use, modification, distribution and commercial reuse with the copyright notice preserved. The project has adopted the Contributor Covenant Code of Conduct, with CONDUCT.md at the top level, and there is a .github directory holding the CI workflow that produces the build status badge shown at the top of the README.

Editorial conclusion

manta fits a Go team building its own Dota 2 analytics on top of Source 2 replays, and it does not fit someone who wants match statistics out of the box, or who needs to read Source 1 replays from before the Reborn engine. Before adopting it, parse one replay from the replays directory with the callback pattern from the README, then run make update to confirm the generated protobuf bindings still match the SteamDatabase Protobufs project.

Frequently asked questions

How do I get and use the manta Dota 2 replay parser?

Fetch the module with go get github.com/dotabuff/manta, open a .dem file, create a parser with manta.NewStreamParser, register a callback such as p.Callbacks.OnCUserMessageSayText2, and call p.Start to run the parse.

Does manta parse Dota 2 Source 1 replays?

No. Manta targets the Source 2 engine used by Dota 2 Reborn. For Source 1 replays the README points to Yasha, Dotabuff's Source 1 parser, which is a separate project.

How are the protobuf messages and callbacks kept up to date?

The README says you can run make update to re-generate protobufs and callbacks based on the SteamDatabase Protobufs project, and the Makefile splits that into an update-protobufs step and a generate step that run the programs under the gen directory.

What Go version does manta need?

The go.mod file declares go 1.23 with a toolchain line of go1.24.5. The runtime dependencies are the protobuf runtimes, snappy, and the spew, difflib and testify helpers.

Is there a higher level Dota 2 stats library built on manta?

The README describes manta as a low-level parser that provides raw data and no opinion on how it should be structured, so match statistics are left to the consumer. Building such a model on top of the callbacks is the intended use of the library.

Official sources

  1. dotabuff/manta on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dotabuff-manta.svg)](https://hysenlabs.com/projects/dotabuff-manta)

Was this useful?