bedrocktool/utils/replay.go

205 lines
4.9 KiB
Go

package utils
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"net"
"os"
"time"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
)
type replayHeader struct {
Version int32
}
var replayMagic = []byte("BTCP")
const (
currentReplayVersion = 2
)
func WriteReplayHeader(f io.Writer) {
f.Write(replayMagic)
header := replayHeader{
Version: currentReplayVersion,
}
binary.Write(f, binary.LittleEndian, &header)
}
func createReplayConnection(ctx context.Context, filename string, proxy *ProxyContext) error {
logrus.Infof("Reading replay %s", filename)
f, err := os.Open(filename)
if err != nil {
return err
}
stat, err := f.Stat()
if err != nil {
return err
}
totalSize := stat.Size()
// default version is version 1, since that didnt have a header
ver := 1
magic := make([]byte, 4)
io.ReadAtLeast(f, magic, 4)
if bytes.Equal(magic, replayMagic) {
var header replayHeader
if err := binary.Read(f, binary.LittleEndian, &header); err != nil {
return err
}
ver = int(header.Version)
} else {
logrus.Info("Version 1 capture assumed.")
f.Seek(-4, io.SeekCurrent)
}
server, client := &net.UDPAddr{
IP: net.IPv4(1, 1, 1, 1),
}, &net.UDPAddr{
IP: net.IPv4(2, 2, 2, 2),
}
proxy.clientAddr = client
proxy.Server = minecraft.NewConn()
gameStarted := false
i := 0
for {
i += 1
var magic uint32 = 0
var packetLength uint32 = 0
var toServer bool = false
timeReceived := time.Now()
offset, _ := f.Seek(0, io.SeekCurrent)
if offset == totalSize {
logrus.Info("Reached End")
break
}
binary.Read(f, binary.LittleEndian, &magic)
if magic != 0xAAAAAAAA {
return fmt.Errorf("wrong Magic")
}
binary.Read(f, binary.LittleEndian, &packetLength)
binary.Read(f, binary.LittleEndian, &toServer)
if ver >= 2 {
var timeMs int64
binary.Read(f, binary.LittleEndian, &timeMs)
timeReceived = time.UnixMilli(timeMs)
}
payload := make([]byte, packetLength)
n, err := f.Read(payload)
if err != nil {
logrus.Error(err)
break
}
if n != int(packetLength) {
return fmt.Errorf("truncated %d", i)
}
var magic2 uint32
binary.Read(f, binary.LittleEndian, &magic2)
if magic2 != 0xBBBBBBBB {
return fmt.Errorf("wrong Magic2")
}
pkData, err := minecraft.ParseData(payload, proxy.Server)
if err != nil {
return err
}
pks, err := pkData.Decode(proxy.Server)
if err != nil {
logrus.Error(err)
continue
}
for _, pk := range pks {
f := bytes.NewBuffer(nil)
b := protocol.NewWriter(f, 0)
pk.Marshal(b)
hdr := packet.Header{PacketID: pk.ID()}
var src, dst net.Addr
if toServer {
src = client
dst = server
} else {
src = server
dst = client
}
for _, handler := range proxy.handlers {
if handler.PacketFunc != nil {
handler.PacketFunc(hdr, f.Bytes(), src, dst)
}
}
if gameStarted {
for _, handler := range proxy.handlers {
if handler.PacketCB != nil {
handler.PacketCB(pk, toServer, timeReceived)
}
}
} else {
switch pk := pk.(type) {
case *packet.StartGame:
proxy.Server.SetGameData(minecraft.GameData{
WorldName: pk.WorldName,
WorldSeed: pk.WorldSeed,
Difficulty: pk.Difficulty,
EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode,
PersonaDisabled: pk.PersonaDisabled,
CustomSkinsDisabled: pk.CustomSkinsDisabled,
BaseGameVersion: pk.BaseGameVersion,
PlayerPosition: pk.PlayerPosition,
Pitch: pk.Pitch,
Yaw: pk.Yaw,
Dimension: pk.Dimension,
WorldSpawn: pk.WorldSpawn,
EditorWorld: pk.EditorWorld,
WorldGameMode: pk.WorldGameMode,
GameRules: pk.GameRules,
Time: pk.Time,
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
CustomBlocks: pk.Blocks,
Items: pk.Items,
PlayerMovementSettings: pk.PlayerMovementSettings,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
Experiments: pk.Experiments,
ClientSideGeneration: pk.ClientSideGeneration,
ChatRestrictionLevel: pk.ChatRestrictionLevel,
DisablePlayerInteractions: pk.DisablePlayerInteractions,
})
gameStarted = true
for _, handler := range proxy.handlers {
if handler.ConnectCB != nil {
handler.ConnectCB(nil)
}
}
}
}
}
}
for _, handler := range proxy.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
return nil
}