first commit

This commit is contained in:
Ole Morud
2022-02-25 04:12:57 +01:00
commit eb8447abe2
10 changed files with 2352 additions and 0 deletions

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Dota-Replay-Analysis

2032
demo/demo.pb.go Normal file

File diff suppressed because it is too large Load Diff

8
go.mod Normal file
View File

@@ -0,0 +1,8 @@
module github.com/olemorud/replay-parser
go 1.17
require (
github.com/golang/snappy v0.0.4
google.golang.org/protobuf v1.27.1
)

10
go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

45
main.go Normal file
View File

@@ -0,0 +1,45 @@
package main
import (
"bufio"
"fmt"
"os"
"github.com/olemorud/replay-parser/parse"
)
func main() {
f, err := os.Open("replays/dog2.dem")
if err != nil {
fmt.Println(fmt.Errorf("error reading file: %v", err))
}
r := bufio.NewReader(f)
size, err := parse.First(r)
if err != nil {
fmt.Println(err)
}
if _, err := r.Discard(int(size) - 12); err != nil {
fmt.Println(fmt.Errorf("error jumping to last frame: %v", err))
}
frame, err := parse.DecodeNextFrame(r)
if err != nil {
fmt.Println(fmt.Errorf("error parsing frame: %v", err))
}
//fmt.Printf("%+v", frame.Message)
for _, player := range frame.Message.GameInfo.Dota.PlayerInfo {
fmt.Println(*player.HeroName)
fmt.Println(*player.PlayerName)
fmt.Println("Steam:", *player.Steamid)
fmt.Println("")
}
//fmt.Printf("%+v", )
}

3
parse/const.go Normal file
View File

@@ -0,0 +1,3 @@
package parse
const SIGNATURE = "PBDEMS2\x00"

98
parse/parse.go Normal file
View File

@@ -0,0 +1,98 @@
package parse
import (
"bufio"
"encoding/binary"
"fmt"
"io"
"github.com/golang/snappy"
"github.com/olemorud/replay-parser/demo"
"google.golang.org/protobuf/proto"
)
type Frame struct {
Kind uint64 `json:"kind"` // see demo.proto
Tick uint64 `json:"tick"` // time elapsed in replay time
Size uint64 `json:"size"` // size of message in bytes
Message *demo.CDemoFileInfo `json:"message"` // protobuf encoded message (may be compressed with snappy)
}
// If kind has a certain bit set, the frame message is compressed
// returns true if frame is compressed
func isCompressed(frame *Frame) bool {
return (frame.Kind&uint64(demo.EDemoCommands_DEM_IsCompressed) != 0)
}
// Checks if file header is correct and returns number the address of the last frame
func First(r *bufio.Reader) (uint64, error) {
// Check if first 8 bytes of file matches the source 2 replay file header
header := make([]byte, 8)
if _, err := io.ReadFull(r, header); err != nil {
return 0, fmt.Errorf("error when reading file: %v", err)
}
if string(header) != SIGNATURE {
return 0, fmt.Errorf("wrong file signature: %v, should be ", SIGNATURE)
}
// Read last frame
var offset uint32
err := binary.Read(r, binary.LittleEndian, &offset)
if err != nil {
return 0, fmt.Errorf("error reading number of frames in replay: %v", err)
}
/// remove later
println("size of demo is", offset, "frames")
return uint64(offset), nil
}
// Parses the next frame on the reader
func DecodeNextFrame(r *bufio.Reader) (*Frame, error) {
// Read kind, tick, size and message
frame := new(Frame)
var err error
if frame.Kind, err = binary.ReadUvarint(r); err != nil {
return nil, fmt.Errorf("error reading frame kind for frame: %v", err)
}
if frame.Tick, err = binary.ReadUvarint(r); err != nil {
return nil, fmt.Errorf("error reading frame tick for frame: %v", err)
}
if frame.Size, err = binary.ReadUvarint(r); err != nil {
return nil, fmt.Errorf("error reading frame size for frame: %v", err)
}
message := make([]byte, frame.Size)
io.ReadFull(r, message)
frame.Message = new(demo.CDemoFileInfo)
if isCompressed(frame) {
decoded, err := snappy.Decode(nil, message)
if err != nil {
return nil, fmt.Errorf("error decoding message: %v", err)
}
fmt.Println("I got decompressed :O")
proto.Unmarshal(decoded, frame.Message)
} else {
proto.Unmarshal(message, frame.Message)
}
return frame, nil
}
// Reads every single frame (probably not very efficient)
func DecodeAllFrames(r *bufio.Reader, frameCount uint64) ([]*Frame, error) {
replay := make([]*Frame, frameCount)
// Decode all frames and add them to *Frame slice
for i := 0; i < int(frameCount); i++ {
replay[i], _ = DecodeNextFrame(r)
}
return replay, nil
}

155
proto/demo.proto Normal file
View File

@@ -0,0 +1,155 @@
syntax = "proto2";
option go_package = "./demo";
option cc_generic_services = false;
enum EDemoCommands {
DEM_Error = -1;
DEM_Stop = 0;
DEM_FileHeader = 1;
DEM_FileInfo = 2;
DEM_SyncTick = 3;
DEM_SendTables = 4;
DEM_ClassInfo = 5;
DEM_StringTables = 6;
DEM_Packet = 7;
DEM_SignonPacket = 8;
DEM_ConsoleCmd = 9;
DEM_CustomData = 10;
DEM_CustomDataCallbacks = 11;
DEM_UserCmd = 12;
DEM_FullPacket = 13;
DEM_SaveGame = 14;
DEM_SpawnGroups = 15;
DEM_Max = 16;
DEM_IsCompressed = 64;
}
message CDemoFileHeader {
required string demo_file_stamp = 1;
optional int32 network_protocol = 2;
optional string server_name = 3;
optional string client_name = 4;
optional string map_name = 5;
optional string game_directory = 6;
optional int32 fullpackets_version = 7;
optional bool allow_clientside_entities = 8;
optional bool allow_clientside_particles = 9;
optional string addons = 10;
optional string demo_version_name = 11;
optional string demo_version_guid = 12;
optional int32 build_num = 13;
}
message CGameInfo {
message CDotaGameInfo {
message CPlayerInfo {
optional string hero_name = 1;
optional string player_name = 2;
optional bool is_fake_client = 3;
optional uint64 steamid = 4;
optional int32 game_team = 5;
}
message CHeroSelectEvent {
optional bool is_pick = 1;
optional uint32 team = 2;
optional uint32 hero_id = 3;
}
optional uint64 match_id = 1;
optional int32 game_mode = 2;
optional int32 game_winner = 3;
repeated .CGameInfo.CDotaGameInfo.CPlayerInfo player_info = 4;
optional uint32 leagueid = 5;
repeated .CGameInfo.CDotaGameInfo.CHeroSelectEvent picks_bans = 6;
optional uint32 radiant_team_id = 7;
optional uint32 dire_team_id = 8;
optional string radiant_team_tag = 9;
optional string dire_team_tag = 10;
optional uint32 end_time = 11;
}
optional .CGameInfo.CDotaGameInfo dota = 4;
}
message CDemoFileInfo {
optional float playback_time = 1;
optional int32 playback_ticks = 2;
optional int32 playback_frames = 3;
optional .CGameInfo game_info = 4;
}
message CDemoPacket {
optional bytes data = 3;
}
message CDemoFullPacket {
optional .CDemoStringTables string_table = 1;
optional .CDemoPacket packet = 2;
}
message CDemoSaveGame {
optional bytes data = 1;
optional fixed64 steam_id = 2;
optional fixed64 signature = 3;
optional int32 version = 4;
}
message CDemoSyncTick {
}
message CDemoConsoleCmd {
optional string cmdstring = 1;
}
message CDemoSendTables {
optional bytes data = 1;
}
message CDemoClassInfo {
message class_t {
optional int32 class_id = 1;
optional string network_name = 2;
optional string table_name = 3;
}
repeated .CDemoClassInfo.class_t classes = 1;
}
message CDemoCustomData {
optional int32 callback_index = 1;
optional bytes data = 2;
}
message CDemoCustomDataCallbacks {
repeated string save_id = 1;
}
message CDemoStringTables {
message items_t {
optional string str = 1;
optional bytes data = 2;
}
message table_t {
optional string table_name = 1;
repeated .CDemoStringTables.items_t items = 2;
repeated .CDemoStringTables.items_t items_clientside = 3;
optional int32 table_flags = 4;
}
repeated .CDemoStringTables.table_t tables = 1;
}
message CDemoStop {
}
message CDemoUserCmd {
optional int32 cmd_number = 1;
optional bytes data = 2;
}
message CDemoSpawnGroups {
repeated bytes msgs = 3;
}

BIN
replays/dog.dem Normal file

Binary file not shown.

BIN
replays/dog2.dem Normal file

Binary file not shown.