diff --git a/dissect/dump.go b/dissect/dump.go deleted file mode 100644 index 279456c..0000000 --- a/dissect/dump.go +++ /dev/null @@ -1,116 +0,0 @@ -package dissect - -import ( - "encoding/hex" - "github.com/rs/zerolog/log" - "io" - "strings" -) - -// Dump dumps packet information to w. Packets are separated line-by-line into sections based on game time. -func (r *Reader) Dump(w io.StringWriter) error { - time := []byte{0x1E, 0xF1, 0x11, 0xAB} - if r.Header.CodeVersion >= 7408213 { // Y8S1 - time = []byte{0x1F, 0x07, 0xEF, 0xC9} - } - timeIndex := 0 - username := []byte{0x22, 0x07, 0x94} - usernameIndex := 0 - i := 0 - playerIdIndex := make(map[string]string, 0) - var sb strings.Builder - _, err := w.WriteString("start:\n---------------\n") - if err != nil { - return err - } - for { - b, err := r.Bytes(1) - if err != nil { - return err - } - if b[0] != time[timeIndex] { - timeIndex = 0 - } else { - timeIndex++ - if timeIndex == 4 { - timeIndex = 0 - if r.Header.CodeVersion >= Y8S1 { - if err := readTime(r); err != nil { - return err - } - } else { - if err := readY7Time(r); err != nil { - return err - } - } - _, err := w.WriteString("\n\n" + r.timeRaw + ":\n---------------\n") - if err != nil { - return err - } - } - } - if b[0] != username[usernameIndex] { - usernameIndex = 0 - } else { - usernameIndex++ - if usernameIndex == 3 { - usernameIndex = 0 - if err = r.Skip(2); err != nil { - return err - } - u, err := r.String() - if err != nil { - return err - } - if u == "" { - usernameIndex = 0 - continue - } - idIndicator := []byte{0x33, 0xD8, 0x3D, 0x4F, 0x23} - if err := r.Seek(idIndicator); err != nil { - return err - } - id, err := r.Bytes(4) - if err != nil { - return err - } - playerIdIndex[strings.ToUpper(hex.EncodeToString(id))] = u - log.Debug().Str("username", u).Hex("i", id).Msg("found a user!") - } - } - if b[0] == 0x00 { - i++ - } else { - i = 0 - } - _, err = sb.WriteString(strings.ToUpper(hex.EncodeToString(b))) - if err != nil { - return err - } - if i == 4 { - out := sb.String() - if len(strings.Trim(out, "0")) == 0 { - sb.Reset() - i = 0 - continue - } - out = strings.TrimRight(out, "0") - if len(out)%2 != 0 { - out += "0" - } - if len(out) > 8 { - possiblePlayerId := out[len(out)-8:] - username := playerIdIndex[possiblePlayerId] - if len(username) > 0 { - out += " - " + username - } - } - _, err := w.WriteString(out + "\n") - if err != nil { - return err - } - sb.Reset() - i = 0 - } - } -} diff --git a/dissect/movement.go b/dissect/movement.go new file mode 100644 index 0000000..c06aaac --- /dev/null +++ b/dissect/movement.go @@ -0,0 +1,55 @@ +package dissect + +import "bytes" + +type MovementUpdates struct { + Username string `json:"username"` + Data []MovementUpdate `json:"data"` +} + +type MovementUpdate struct { + X float32 + Y float32 + Z float32 +} + +// TODO: there appears to be multiple types of movement updates (movement only, rotation only (?), movement + rotation (?)) +// that can be identified by 2 bytes +// gotta implement the other types of movement updates +var movementType = []byte{0xC0, 0x3F} + +func readMovement(r *Reader) error { + t, err := r.Bytes(2) + if err != nil { + return err + } + if !bytes.Equal(t, movementType) { + return nil + } + x, err := r.Float32() + if err != nil { + return err + } + y, err := r.Float32() + if err != nil { + return err + } + z, err := r.Float32() + if err != nil { + return err + } + if len(r.Movement) == 0 { + // TODO: how to ID the player and the timestamp?? + // maybe there is a consistent update rate? + r.Movement = append(r.Movement, MovementUpdates{ + Username: "N/A", + Data: []MovementUpdate{}, + }) + } + r.Movement[0].Data = append(r.Movement[0].Data, MovementUpdate{ + X: x, + Y: y, + Z: z, + }) + return nil +} diff --git a/dissect/reader.go b/dissect/reader.go index 7cbd33c..c0114a9 100644 --- a/dissect/reader.go +++ b/dissect/reader.go @@ -28,8 +28,9 @@ type Reader struct { planted bool readPartial bool // reads up to the player info packets playersRead int - Header Header `json:"header"` - MatchFeedback []MatchUpdate `json:"matchFeedback"` + Header Header `json:"header"` + MatchFeedback []MatchUpdate `json:"matchFeedback"` + Movement []MovementUpdates `json:"movement,omitempty"` Scoreboard Scoreboard } @@ -68,6 +69,7 @@ func NewReader(in io.Reader) (r *Reader, err error) { r.Listen([]byte{0x22, 0xA9, 0xC8, 0x58, 0xD9}, readDefuserTimer) r.Listen([]byte{0xEC, 0xDA, 0x4F, 0x80}, readScoreboardScore) r.Listen([]byte{0x4D, 0x73, 0x7F, 0x9E}, readScoreboardAssists) + r.Listen([]byte{0x60, 0x73, 0x85, 0xFE}, readMovement) return r, err } @@ -342,6 +344,15 @@ func (r *Reader) Uint64() (uint64, error) { return binary.LittleEndian.Uint64(b), nil } +func (r *Reader) Float32() (float32, error) { + b, err := r.Bytes(4) + if err != nil { + return 0, err + } + u := binary.LittleEndian.Uint32(b) + return math.Float32frombits(u), nil +} + func (r *Reader) Write(w io.Writer) (n int, err error) { return w.Write(r.b) } diff --git a/main.go b/main.go index c3ea95b..2f1535f 100644 --- a/main.go +++ b/main.go @@ -2,11 +2,12 @@ package main import ( "encoding/json" - "github.com/redraskal/r6-dissect/dissect" "io" "os" "strings" + "github.com/redraskal/r6-dissect/dissect" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/pflag" @@ -50,12 +51,7 @@ func main() { log.Fatal().Msg("dump requires a replay file input.") } if viper.GetBool("dump") { - outBin, err := os.OpenFile(out.Name()+".bin", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm) - if err != nil { - log.Fatal().Err(err).Send() - } - defer outBin.Close() - if err := writeRoundDump(in, out, outBin); err != nil { + if err := writeRoundDump(in, out); err != nil { log.Fatal().Err(err).Send() } return @@ -78,8 +74,10 @@ func setup() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) pflag.StringP("format", "f", "", "specifies the output format (json, excel)") pflag.StringP("output", "o", "", "specifies the output path") + // TODO: implement this flag + pflag.StringP("show", "s", "", "optionally provide additional data (movement)") pflag.BoolP("debug", "d", false, "sets log level to debug") - pflag.BoolP("dump", "p", false, "dumps packets to the output") + pflag.BoolP("dump", "p", false, "dumps decompressed replay to the output") pflag.Bool("info", false, "prints the replay header") pflag.BoolP("version", "v", false, "prints the version") pflag.Parse() @@ -170,6 +168,7 @@ func writeRound(in io.Reader, out io.Writer) error { dissect.Header MatchFeedback []dissect.MatchUpdate `json:"matchFeedback"` PlayerStats []dissect.PlayerRoundStats `json:"stats"` + Movement []dissect.MovementUpdates `json:"movement,omitempty"` } if err := r.Read(); !dissect.Ok(err) { return err @@ -179,18 +178,16 @@ func writeRound(in io.Reader, out io.Writer) error { r.Header, r.MatchFeedback, r.PlayerStats(), + r.Movement, }) } -func writeRoundDump(in io.Reader, out *os.File, outBin *os.File) error { +func writeRoundDump(in io.Reader, out *os.File) error { r, err := dissect.NewReader(in) if err != nil { return err } - if _, err := r.Write(outBin); err != nil { - return err - } - if err := r.Dump(out); !dissect.Ok(err) { + if _, err := r.Write(out); err != nil { return err } return nil