package etdd
import (
"encoding/binary"
"errors"
"fmt"
"math"
"os"
"regexp"
"strconv"
"strings"
)
const (
maxMessageLength int = 32768
maxStringChars int = 1024
maxConfigStrings int = 1024
bigInfoString int = 8192
gEntityNumBits int = 10
maxParseEntities int = 2048
maxGEntities int = 1 << uint(gEntityNumBits)
packetBackup int = 32
packetMask int = packetBackup - 1
maxPlayersNum int = 64
csServerInfo int = 0
csLevelStartTime int = 11
csPlayers int = 689
svcNop byte = byte(1)
svcGameState byte = byte(2)
svcConfigString byte = byte(3)
svcBaseLine byte = byte(4)
svcServerCommand byte = byte(5)
svcSnapshot byte = byte(7)
svcEof byte = byte(8)
Chat string = "c"
TeamChat string = "tc"
)
const (
psEventSequence int = 27
psEventNum int = 4
psEventParams int = 4
psClientNum int = psEventSequence + psEventNum + psEventParams + 1
)
const (
esType int = 0
)
const (
eeMod int = 1
eeVictim int = 20
eeAttacker int = 21
eeObituary int = 71
)
const (
etEvents int = 60
)
type snapshot struct {
valid bool
deltaNum int
messageNum int
serverTime int
flags int
parseEntitiesNum int
entitiesNum int
ps PlayerState
}
type entityState struct {
number int
fields [71]int
}
type PlayerState struct {
Num int
Name string
Team int
fields [77]int
}
type Demo struct {
file *os.File
commandSequence int
messageSequence int
parseEntities [maxParseEntities]entityState
baselines [maxGEntities]entityState
snapshots [packetBackup]snapshot
players [maxPlayersNum]PlayerState
snapshot snapshot
parseEntitiesNum int
startTime int
levelTime int
bloc int
initialized bool
illegible int
}
type message struct {
oob bool
data []byte
size int
read int
bit int
bloc *int
}
var entityFieldBits = []int{
8, 24, 8, 32, 32, 0, 0, 0, 0, 0, 0, 8, 32, 32, 0, 0, 0, 0, 0, 0, 32, 32, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 8, 32, 32, 9, 9, 16, 8, 24, 10, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 16, 8, 10, 10, 10, 32, 32, 32, 8, 8, 32, 32, 32, 4, 2,
}
var playerStateFieldBits = []int{
32, 8, 8, 16, -16, 0, 0, 0, 0, 0, 0, -16, -16, -16, 16, 0, 16, 16, 16, 16, 10, 16, 16,
10, 10, 8, 24, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 7, 4, 10, 0, 0, 0, 8, 8, 8, 8, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 16, 8, 8, 32, 8, 8, 8, 8, 32, 8, 8, 2,
}
func Open(name string) (Demo, error) {
d := Demo{}
for i := 0; i < maxPlayersNum; i++ {
d.players[i].Num = i
}
file, err := os.Open(name)
if err == nil {
d.file = file
}
return d, err
}
func (d *Demo) Read(handler *Logger) {
for {
if d.file == nil {
break
} else {
seq, err := d.readLittleLong()
if err != nil {
handler.Error(err)
break
}
d.messageSequence = seq
size, err := d.readLittleLong()
if err != nil {
handler.Error(err)
break
}
if size == math.MaxUint32 {
break
}
if size > maxMessageLength {
handler.Error(errors.New("message size overflow"))
break
}
m := message{false, make([]byte, size), size, 0, 0, &d.bloc}
r, err := d.readBuffer(m.data)
if err != nil {
if r < size {
handler.Error(errors.New(err.Error() + "; demo file was truncated"))
} else {
handler.Error(err)
}
break
}
m.Parse(handler, d)
}
}
handler.End()
}
func (d *Demo) readLittleLong() (int, error) {
s, err := d.readBytes(4)
if err != nil {
d.Close()
return -1, err
}
return int(binary.LittleEndian.Uint32(s)), nil
}
func (d *Demo) readBytes(n int) ([]byte, error) {
s := make([]byte, n)
_, err := d.readBuffer(s)
return s, err
}
func (d *Demo) readBuffer(buffer []byte) (int, error) {
r, err := d.file.Read(buffer)
if err != nil {
d.Close()
}
if r != len(buffer) && err == nil {
err = errors.New("incorrect number of bytes read")
}
return r, err
}
func (d *Demo) Close() {
if d.file != nil {
_ = d.file.Close()
d.file = nil
}
}
func (d *Demo) checkEvents(handler *Logger) {
for i := 0; i < d.snapshot.entitiesNum; i++ {
entity := &d.parseEntities[(d.snapshot.parseEntitiesNum+i)&(maxParseEntities-1)]
if entity.fields[esType] > etEvents {
switch (entity.fields[esType] - etEvents) & (^768) {
case eeObituary:
d.obituary(handler, entity.fields[eeVictim], entity.fields[eeAttacker], entity.fields[eeMod])
break
}
entity.fields[esType] = 0
}
}
}
func (d *Demo) obituary(handler *Logger, victimNum int, attackerNum int, mod int) {
var victim *PlayerState
var attacker *PlayerState
if victimNum < 0 || victimNum >= maxPlayersNum {
return
}
victim = &d.players[victimNum]
if attackerNum >= 0 && attackerNum < maxPlayersNum {
attacker = &d.players[attackerNum]
}
handler.Obituary(d.levelTime, victim, attacker, mod)
}
func (m *message) Parse(handler *Logger, demo *Demo) {
m.readLong()
for {
if demo.file == nil {
break
}
if m.read > m.size {
handler.Error(errors.New("read past end of server message"))
break
}
command := m.readByte()
if command == svcEof || demo.file == nil {
break
}
switch command {
case svcNop:
break
case svcGameState:
m.parseGameState(handler, demo)
break
case svcServerCommand:
m.parseCommandString(handler, demo)
break
case svcSnapshot:
m.parseSnapshot(handler, demo)
break
default:
demo.illegible++
handler.Error(errors.New(fmt.Sprintf("illegible server message %d", int(command))))
}
if demo.illegible == 10 {
demo.file = nil
break
}
}
m.parseBinaryMessage(handler, demo)
demo.checkEvents(handler)
}
func (m *message) parseGameState(handler *Logger, demo *Demo) {
demo.commandSequence = m.readLong()
for {
command := m.readByte()
if command == svcEof {
break
}
switch command {
case svcConfigString:
i := m.readShort()
if i < 0 || i >= maxConfigStrings {
handler.Error(errors.New(fmt.Sprintf("config strings overflow")))
demo.Close()
return
}
s := m.readBigString()
demo.cs(handler, i, s)
break
case svcBaseLine:
nn := m.readBits(10)
if nn < 0 || nn >= maxGEntities {
handler.Error(errors.New(fmt.Sprintf("baseline number out of range: %d", nn)))
demo.Close()
return
}
nullState := entityState{}
m.readDeltaEntity(demo, &nullState, &demo.baselines[nn], nn)
break
default:
handler.Error(errors.New(fmt.Sprintf("bad command byte %d", int(command))))
return
}
}
m.readLong()
m.readLong()
}
func (m *message) parseCommandString(handler *Logger, demo *Demo) {
seq := m.readLong()
s := m.readString()
if demo.commandSequence >= seq {
return
}
demo.commandSequence = seq
if len(s) == 0 || s[0] == '*' {
return
}
var command string
if !word(&s, &command) {
return
}
switch command {
case "chat", "print", "cpm", "cp", "b", "sc":
if command == "b" {
var __ string
word(&s, &__)
}
r := regexp.MustCompile(`^("|\s)*|("|\s|\^.)*$|\[lo[nf]]|" -?\d+ \d+$`)
s = r.ReplaceAllString(s, "")
if command == "sc" {
if s == "0" {
break
}
lines := strings.Split(strings.TrimSpace(s), "\n")
for _, line := range lines {
if line != "" {
handler.Print(demo.levelTime, line)
}
}
} else {
handler.Print(demo.levelTime, s)
}
break
case Chat, TeamChat, "cs":
var ps string
if word(&s, &ps) {
p, _ := strconv.Atoi(ps)
var message string
if quoted(&s, &message) {
if command == "cs" {
demo.cs(handler, p, message)
} else {
handler.Chat(demo.levelTime, demo.players[p], message, command)
}
}
}
break
default:
return
}
}
func (m *message) parseSnapshot(handler *Logger, demo *Demo) {
newSnap := snapshot{}
newSnap.messageNum = demo.messageSequence
newSnap.serverTime = m.readLong()
deltaNum := m.readBits(8)
if deltaNum == 0 {
newSnap.deltaNum = -1
} else {
newSnap.deltaNum = demo.messageSequence - deltaNum
}
newSnap.flags = m.readBits(8)
var old *snapshot
if newSnap.deltaNum <= 0 {
newSnap.valid = true
} else {
old = &demo.snapshots[newSnap.deltaNum&packetMask]
if old.valid && old.messageNum == newSnap.deltaNum && demo.parseEntitiesNum-old.parseEntitiesNum <= maxParseEntities-128 {
newSnap.valid = true
}
}
areaMask := m.readBits(8)
if areaMask > 32 {
return
}
for i := 0; i < areaMask; i++ {
m.readBits(8)
}
if old != nil {
m.readDeltaPlayerState(&old.ps, &newSnap.ps)
} else {
m.readDeltaPlayerState(nil, &newSnap.ps)
}
newSnap.parseEntitiesNum = demo.parseEntitiesNum
newSnap.entitiesNum = 0
index, oNum := 0, 0
var oldState *entityState = nil
if old == nil {
oNum = 99999
} else {
if index >= old.entitiesNum {
oNum = 99999
} else {
oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
oNum = oldState.number
}
}
for {
nn := m.readBits(gEntityNumBits)
if nn == maxGEntities-1 {
break
}
if m.read > m.size {
demo.Close()
return
}
for oNum < nn {
m.deltaEntity(demo, &newSnap, oNum, oldState, true)
index++
if index >= old.entitiesNum {
oNum = 99999
} else {
oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
oNum = oldState.number
}
}
if oNum == nn {
m.deltaEntity(demo, &newSnap, nn, oldState, false)
index++
if index >= old.entitiesNum {
oNum = 99999
} else {
oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
oNum = oldState.number
}
continue
}
if oNum > nn && nn < maxGEntities {
m.deltaEntity(demo, &newSnap, nn, &demo.baselines[nn], false)
}
}
for oNum != 99999 {
m.deltaEntity(demo, &newSnap, oNum, oldState, true)
index++
if index >= old.entitiesNum {
oNum = 99999
} else {
oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
oNum = oldState.number
}
}
if !newSnap.valid {
return
}
oldMessageNum := demo.snapshot.messageNum + 1
if newSnap.messageNum-oldMessageNum >= packetBackup {
oldMessageNum = newSnap.messageNum - (packetBackup - 1)
}
for oldMessageNum < newSnap.messageNum {
demo.snapshots[oldMessageNum&packetMask].valid = false
oldMessageNum++
}
demo.snapshot = newSnap
demo.snapshots[newSnap.messageNum&packetMask] = newSnap
demo.levelTime = newSnap.serverTime - demo.startTime
player := &demo.players[newSnap.ps.fields[psClientNum]]
for i := 0; i < len(player.fields); i++ {
player.fields[i] = newSnap.ps.fields[i]
}
}
func (m *message) parseBinaryMessage(handler *Logger, demo *Demo) {
m.bit = (m.bit + 1) & ^7
m.oob = true
size := m.size - m.read
if size <= 0 || size > maxMessageLength {
return
}
}
func (m *message) deltaEntity(demo *Demo, snapshot *snapshot, num int, oldState *entityState, unchanged bool) {
state := &demo.parseEntities[demo.parseEntitiesNum&(maxParseEntities-1)]
if unchanged {
*state = *oldState
} else {
m.readDeltaEntity(demo, oldState, state, num)
}
if state.number == maxGEntities-1 {
return
}
demo.parseEntitiesNum++
snapshot.entitiesNum++
}
func (m *message) readDeltaEntity(demo *Demo, from *entityState, to *entityState, number int) {
if m.readBits(1) == 1 {
*to = entityState{}
to.number = maxGEntities - 1
return
}
if m.readBits(1) == 0 {
*to = *from
to.number = number
return
}
lc := m.readBits(8)
if lc > len(entityFieldBits) {
lc = len(entityFieldBits)
}
to.number = number
for i := 0; i < lc; i++ {
if m.readBits(1) == 1 {
if entityFieldBits[i] == 0 {
if m.readBits(1) == 1 {
if m.readBits(1) == 0 {
to.fields[i] = m.readBits(13)
} else {
to.fields[i] = m.readBits(32)
}
}
} else {
if m.readBits(1) == 1 {
to.fields[i] = m.readBits(entityFieldBits[i])
}
}
}
}
}
func (m *message) readDeltaPlayerState(from *PlayerState, to *PlayerState) {
if from == nil {
from = &PlayerState{}
}
*to = *from
lc := m.readBits(8)
for i := 0; i < lc && i < len(playerStateFieldBits); i++ {
if m.readBits(1) == 1 {
if playerStateFieldBits[i] == 0 {
if m.readBits(1) == 0 {
to.fields[i] = m.readBits(13)
} else {
to.fields[i] = m.readBits(32)
}
} else {
to.fields[i] = m.readBits(playerStateFieldBits[i])
}
}
}
if m.readBits(1) == 1 {
for a := 0; a < 3; a++ {
if m.readBits(1) == 1 {
bits := m.readShort()
for i := 0; i < 16; i++ {
if (bits & (1 << uint(i))) != 0 {
m.readShort()
}
}
}
}
if m.readBits(1) == 1 {
bits := m.readShort()
for i := 0; i < 16; i++ {
if (bits & (1 << uint(i))) != 0 {
m.readLong()
}
}
}
}
if m.readBits(1) == 1 {
for j := 0; j < 4; j++ {
if m.readBits(1) == 1 {
bits := m.readShort()
for i := 0; i < 16; i++ {
if (bits & (1 << uint(i))) != 0 {
m.readShort()
}
}
}
}
}
for j := 0; j < 4; j++ {
if m.readBits(1) == 1 {
bits := m.readShort()
for i := 0; i < 16; i++ {
if (bits & (1 << uint(i))) != 0 {
m.readShort()
}
}
}
}
}
func (m *message) readLong() int {
c := m.readBits(32)
if m.read > m.size {
return -1
}
return c
}
func (m *message) readShort() int {
c := m.readBits(16)
if m.read > m.size {
return -1
}
return c
}
func (m *message) readString() string {
s := ""
for l := 0; l < maxStringChars && m.read <= m.size; l++ {
c := m.readByte()
if c == byte(0) {
break
}
if c == '%' || c > 127 {
c = '.'
}
s += string(c)
}
return s
}
func (m *message) readBigString() string {
s := ""
for l := 0; l < bigInfoString && m.read <= m.size; l++ {
c := m.readByte()
if c == byte(0) {
break
}
if c == '%' {
c = '.'
}
s += string(c)
}
return s
}
func (m *message) readByte() byte {
return byte(m.readBits(8))
}
func (m *message) readBits(bits int) int {
value, r, s, i, n := 0, 0, false, 0, 0
if bits < 0 {
bits = -bits
s = true
}
if m.oob {
if bits == 8 || bits == 16 || bits == 32 {
for i*8 < bits {
value |= int(m.data[m.read]) << uint8(i*8)
m.read += 1
m.bit += bits
i++
}
} else {
panic(fmt.Sprintf("can't read %d bits", bits))
}
} else {
if (bits & 7) != 0 {
n = bits & 7
for i = 0; i < n; i++ {
value |= HuffGetBit(m.data, &m.bit, m.bloc) << uint(i)
}
bits = bits - n
}
for i = 0; bits != 0 && i < bits; i += 8 {
HuffOffsetReceive(&r, m.data, &m.bit, m.bloc)
value |= r << uint(i+n)
}
m.read = (m.bit >> 3) + 1
}
if s && (value&(1<<uint(bits-1))) != 0 {
value |= -1 ^ ((1 << uint(bits)) - 1)
}
return value
}
func (d *Demo) cs(handler *Logger, p int, s string) {
if p == csServerInfo && !d.initialized {
d.initialized = true
bits := strings.Split(s, "\\")
vars := make(map[string]string)
for i := 1; i+1 < len(bits); i += 2 {
vars[bits[i]] = bits[i+1]
}
if hostname, ok := vars["sv_hostname"]; ok {
if mapname, ok := vars["mapname"]; ok {
handler.Start(hostname, mapname)
}
}
return
}
if p == csLevelStartTime {
d.startTime, _ = strconv.Atoi(s)
return
}
if p < csPlayers || p >= csPlayers+maxPlayersNum {
return
}
bits := strings.Split(s, "\\")
state := &d.players[p-csPlayers]
for i := 0; i < len(bits); i += 2 {
switch bits[i] {
case "n":
state.Name = bits[i+1]
break
case "t":
state.Team, _ = strconv.Atoi(bits[i+1])
break
}
}
}
func word(s *string, word *string) bool {
i := strings.Index(*s, " ")
if i < 1 {
return false
}
*word = (*s)[0:i]
*s = (*s)[i+1:]
return true
}
func quoted(s *string, quoted *string) bool {
if (*s)[0] == '"' {
*s = (*s)[1:]
}
i := strings.Index(*s, "\"")
if i < 1 {
return false
}
*quoted = (*s)[0:i]
*s = (*s)[i+1:]
return true
}