make capture not produce broken files

This commit is contained in:
olebeck 2022-09-04 15:24:55 +02:00
parent 7acc698443
commit 5d099729ae
9 changed files with 147 additions and 97 deletions

View File

@ -8,14 +8,12 @@ import (
"log" "log"
"net" "net"
"os" "os"
"sync"
"time" "time"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/google/gopacket/pcapgo" "github.com/google/gopacket/pcapgo"
"github.com/google/subcommands" "github.com/google/subcommands"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet" "github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -29,41 +27,24 @@ func init() {
register_command(&CaptureCMD{}) register_command(&CaptureCMD{})
} }
func dump_packet(from_client bool, w *pcapgo.Writer, pk packet.Packet) { func dump_packet(w *pcapgo.Writer, from_client bool, pk packet.Header, payload []byte) {
var err error var err error
var iface_index int var prefix []byte
var src_ip, dst_ip net.IP
if from_client { if from_client {
iface_index = 1 prefix = []byte("client")
src_ip = SrcIp_client
dst_ip = SrcIp_server
} else { } else {
iface_index = 2 prefix = []byte("server")
src_ip = SrcIp_server
dst_ip = SrcIp_client
} }
packet_data := bytes.NewBuffer(nil) packet_data := bytes.NewBuffer(nil)
{ pk.Write(packet_data)
_pw := bytes.NewBuffer(nil) packet_data.Write(payload)
pw := protocol.NewWriter(_pw, 0x0)
pk.Marshal(pw)
h := packet.Header{
PacketID: pk.ID(),
}
h.Write(packet_data)
packet_data.Write(_pw.Bytes())
}
serialize_buf := gopacket.NewSerializeBuffer() serialize_buf := gopacket.NewSerializeBuffer()
err = gopacket.SerializeLayers( err = gopacket.SerializeLayers(
serialize_buf, serialize_buf,
gopacket.SerializeOptions{}, gopacket.SerializeOptions{},
&layers.IPv4{ gopacket.Payload(prefix),
SrcIP: src_ip,
DstIP: dst_ip,
Length: uint16(packet_data.Len()),
},
gopacket.Payload(packet_data.Bytes()), gopacket.Payload(packet_data.Bytes()),
) )
if err != nil { if err != nil {
@ -74,7 +55,7 @@ func dump_packet(from_client bool, w *pcapgo.Writer, pk packet.Packet) {
Timestamp: time.Now(), Timestamp: time.Now(),
Length: len(serialize_buf.Bytes()), Length: len(serialize_buf.Bytes()),
CaptureLength: len(serialize_buf.Bytes()), CaptureLength: len(serialize_buf.Bytes()),
InterfaceIndex: iface_index, InterfaceIndex: 1,
}, serialize_buf.Bytes()) }, serialize_buf.Bytes())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -112,16 +93,15 @@ func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interfac
w := pcapgo.NewWriter(fio) w := pcapgo.NewWriter(fio)
w.WriteFileHeader(65536, layers.LinkTypeEthernet) w.WriteFileHeader(65536, layers.LinkTypeEthernet)
_wl := sync.Mutex{} proxy := NewProxy(logrus.StandardLogger())
proxy.packetFunc = func(header packet.Header, payload []byte, src, dst net.Addr) {
from_client := src.String() == proxy.client.LocalAddr().String()
dump_packet(w, from_client, header, payload)
}
err = create_proxy(ctx, logrus.StandardLogger(), address, nil, func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) { err = proxy.Run(ctx, address)
_wl.Lock()
dump_packet(toServer, w, pk)
_wl.Unlock()
return pk, nil
})
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) logrus.Error(err)
return 1 return 1
} }
return 0 return 0

12
main.go
View File

@ -18,9 +18,11 @@ const TOKEN_FILE = "token.json"
var version string var version string
var G_debug bool var (
var G_preload_packs bool G_debug bool
var G_exit []func() = []func(){} G_preload_packs bool
G_exit []func() = []func(){}
)
func exit() { func exit() {
logrus.Info("\nExiting\n") logrus.Info("\nExiting\n")
@ -40,7 +42,7 @@ func register_command(sub subcommands.Command) {
func main() { func main() {
logrus.SetLevel(logrus.DebugLevel) logrus.SetLevel(logrus.DebugLevel)
if version != "" { if version != "" {
logrus.Info("bedrocktool version: %s\n", version) logrus.Infof("bedrocktool version: %s\n", version)
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -108,6 +110,7 @@ func (*TransCMD) Synopsis() string { return "" }
func (c *TransCMD) SetFlags(f *flag.FlagSet) { func (c *TransCMD) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.auth, "auth", false, "if it should login to xbox") f.BoolVar(&c.auth, "auth", false, "if it should login to xbox")
} }
func (c *TransCMD) Usage() string { func (c *TransCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n" return c.Name() + ": " + c.Synopsis() + "\n"
} }
@ -127,6 +130,7 @@ func (c *TransCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
fmt.Println(BLACK_FG + BOLD + BLUE + " Trans " + PINK + " Rights " + WHITE + " Are " + PINK + " Human " + BLUE + " Rights " + RESET) fmt.Println(BLACK_FG + BOLD + BLUE + " Trans " + PINK + " Rights " + WHITE + " Are " + PINK + " Human " + BLUE + " Rights " + RESET)
return 0 return 0
} }
func init() { func init() {
register_command(&TransCMD{}) register_command(&TransCMD{})
} }

View File

@ -73,7 +73,7 @@ func (c *MergeCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
if err := zip_folder(out_name+".mcworld", out_name); err != nil { if err := zip_folder(out_name+".mcworld", out_name); err != nil {
logrus.Info("zipping: %s", err) logrus.Infof("zipping: %s", err)
return 1 return 1
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net"
"strings" "strings"
"sync" "sync"
@ -37,9 +38,19 @@ type ProxyContext struct {
client *minecraft.Conn client *minecraft.Conn
listener *minecraft.Listener listener *minecraft.Listener
commands map[string]IngameCommand commands map[string]IngameCommand
log *logrus.Logger
// called for every packet
packetFunc PacketFunc
// called after game started
onConnect ConnectCallback
// called on every packet after login
packetCB PacketCallback
} }
type ( type (
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error)
ConnectCallback func(proxy *ProxyContext) ConnectCallback func(proxy *ProxyContext)
) )
@ -127,30 +138,37 @@ func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCB
} }
} }
func create_proxy(ctx context.Context, log *logrus.Logger, server_address string, onConnect ConnectCallback, packetCB PacketCallback) (err error) { func NewProxy(log *logrus.Logger) *ProxyContext {
if log == nil {
log = logrus.StandardLogger()
}
return &ProxyContext{
log: log,
commands: make(map[string]IngameCommand),
}
}
func (p *ProxyContext) Run(ctx context.Context, server_address string) (err error) {
if strings.HasSuffix(server_address, ".pcap") { if strings.HasSuffix(server_address, ".pcap") {
return create_replay_connection(ctx, log, server_address, onConnect, packetCB) return create_replay_connection(ctx, p.log, server_address, p.onConnect, p.packetCB)
} }
GetTokenSource() // ask for login before listening GetTokenSource() // ask for login before listening
proxy := ProxyContext{}
proxy.commands = make(map[string]IngameCommand)
var packs []*resource.Pack var packs []*resource.Pack
if G_preload_packs { if G_preload_packs {
log.Info("Preloading resourcepacks") p.log.Info("Preloading resourcepacks")
serverConn, err := connect_server(ctx, server_address, nil, true) serverConn, err := connect_server(ctx, server_address, nil, true, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to connect to %s: %s", server_address, err) return fmt.Errorf("failed to connect to %s: %s", server_address, err)
} }
serverConn.Close() serverConn.Close()
packs = serverConn.ResourcePacks() packs = serverConn.ResourcePacks()
log.Infof("%d packs loaded\n", len(packs)) p.log.Infof("%d packs loaded\n", len(packs))
} }
_status := minecraft.NewStatusProvider("Server") _status := minecraft.NewStatusProvider("Server")
proxy.listener, err = minecraft.ListenConfig{ p.listener, err = minecraft.ListenConfig{
StatusProvider: _status, StatusProvider: _status,
ResourcePacks: packs, ResourcePacks: packs,
AcceptedProtocols: []minecraft.Protocol{ AcceptedProtocols: []minecraft.Protocol{
@ -160,46 +178,48 @@ func create_proxy(ctx context.Context, log *logrus.Logger, server_address string
if err != nil { if err != nil {
return err return err
} }
defer proxy.listener.Close() defer p.listener.Close()
log.Infof("Listening on %s\n", proxy.listener.Addr()) p.log.Infof("Listening on %s\n", p.listener.Addr())
c, err := proxy.listener.Accept() c, err := p.listener.Accept()
if err != nil { if err != nil {
log.Fatal(err) p.log.Fatal(err)
} }
proxy.client = c.(*minecraft.Conn) p.client = c.(*minecraft.Conn)
cd := proxy.client.ClientData() cd := p.client.ClientData()
proxy.server, err = connect_server(ctx, server_address, &cd, false) p.server, err = connect_server(ctx, server_address, &cd, false, p.packetFunc)
if err != nil { if err != nil {
return fmt.Errorf("failed to connect to %s: %s", server_address, err) return fmt.Errorf("failed to connect to %s: %s", server_address, err)
} }
// spawn and start the game // spawn and start the game
if err := spawn_conn(ctx, proxy.client, proxy.server); err != nil { if err := spawn_conn(ctx, p.client, p.server); err != nil {
return fmt.Errorf("failed to spawn: %s", err) return fmt.Errorf("failed to spawn: %s", err)
} }
defer proxy.server.Close() defer p.server.Close()
defer proxy.listener.Disconnect(proxy.client, G_disconnect_reason) defer p.listener.Disconnect(p.client, G_disconnect_reason)
if onConnect != nil { if p.onConnect != nil {
onConnect(&proxy) p.onConnect(p)
} }
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
cbs := []PacketCallback{ cbs := []PacketCallback{
proxy.CommandHandlerPacketCB, p.CommandHandlerPacketCB,
packetCB, }
if p.packetCB != nil {
cbs = append(cbs, p.packetCB)
} }
// server to client // server to client
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := proxyLoop(ctx, &proxy, false, cbs); err != nil { if err := proxyLoop(ctx, p, false, cbs); err != nil {
log.Error(err) p.log.Error(err)
return return
} }
}() }()
@ -208,8 +228,8 @@ func create_proxy(ctx context.Context, log *logrus.Logger, server_address string
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := proxyLoop(ctx, &proxy, true, cbs); err != nil { if err := proxyLoop(ctx, p, true, cbs); err != nil {
log.Error(err) p.log.Error(err)
return return
} }
}() }()

View File

@ -23,7 +23,7 @@ func SetUnexportedField(field reflect.Value, value interface{}) {
func create_replay_connection(ctx context.Context, log *logrus.Logger, filename string, onConnect ConnectCallback, packetCB PacketCallback) error { func create_replay_connection(ctx context.Context, log *logrus.Logger, filename string, onConnect ConnectCallback, packetCB PacketCallback) error {
log.Infof("Reading replay %s", filename) log.Infof("Reading replay %s", filename)
OLD_BROKEN := true OLD_BROKEN := false
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
@ -35,17 +35,8 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
} }
SetUnexportedField(reflect.ValueOf(reader).Elem().Field(5), uint32(0xFFFFFFFF)) SetUnexportedField(reflect.ValueOf(reader).Elem().Field(5), uint32(0xFFFFFFFF))
dummy_conn := minecraft.NewConn() proxy := NewProxy(logrus.StandardLogger())
dummy_conn.SetGameData(minecraft.GameData{ proxy.server = minecraft.NewConn()
BaseGameVersion: "1.17.40", // SPECIFIC TO THE SERVER; TODO
})
proxy := ProxyContext{}
proxy.server = dummy_conn
if onConnect != nil {
onConnect(&proxy)
}
var fake_header []byte var fake_header []byte
if OLD_BROKEN { if OLD_BROKEN {
@ -58,6 +49,8 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
fake_header = fake_header_w.Bytes() fake_header = fake_header_w.Bytes()
} }
game_started := false
start := time.Time{} start := time.Time{}
for { for {
data, ci, err := reader.ReadPacketData() data, ci, err := reader.ReadPacketData()
@ -67,31 +60,73 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
if start.Unix() == 0 { if start.Unix() == 0 {
start = ci.Timestamp start = ci.Timestamp
} }
if len(data) < 0x14 {
payload := data[0x14:]
if len(payload) == 0 {
continue continue
} }
var payload []byte
var toServer bool
if OLD_BROKEN { if OLD_BROKEN {
payload = append(fake_header, payload...) payload = append(fake_header, data[0x14:]...)
toServer = data[0x10] != 127
} else {
prefix := data[0:6]
payload = data[6:]
toServer = bytes.Equal(prefix, []byte("client"))
} }
pk_data, err := minecraft.ParseData(payload, dummy_conn) pk_data, err := minecraft.ParseData(payload, proxy.server)
if err != nil { if err != nil {
return err return err
} }
pks, err := pk_data.Decode(dummy_conn) pks, err := pk_data.Decode(proxy.server)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
continue continue
} }
for _, pk := range pks { for _, pk := range pks {
if data[0x10] == 127 { // to client if game_started || OLD_BROKEN {
packetCB(pk, &proxy, false) if packetCB != nil {
packetCB(pk, proxy, toServer)
}
} else { } else {
packetCB(pk, &proxy, true) 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,
})
game_started = true
if onConnect != nil {
onConnect(proxy)
}
}
} }
} }
} }

View File

@ -37,12 +37,15 @@ func (c *SkinProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interf
out_path := fmt.Sprintf("skins/%s", hostname) out_path := fmt.Sprintf("skins/%s", hostname)
os.MkdirAll(out_path, 0o755) os.MkdirAll(out_path, 0o755)
err = create_proxy(ctx, logrus.StandardLogger(), address, nil, func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) { proxy := NewProxy(logrus.StandardLogger())
proxy.packetCB = func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) {
if !toServer { if !toServer {
process_packet_skins(proxy.client, out_path, pk, c.filter) process_packet_skins(proxy.client, out_path, pk, c.filter)
} }
return pk, nil return pk, nil
}) }
err = proxy.Run(ctx, address)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return 1 return 1

View File

@ -251,7 +251,7 @@ func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}
return 1 return 1
} }
serverConn, err := connect_server(ctx, address, nil, false) serverConn, err := connect_server(ctx, address, nil, false, nil)
if err != nil { if err != nil {
fmt.Fprint(os.Stderr, err) fmt.Fprint(os.Stderr, err)
return 1 return 1

View File

@ -110,10 +110,14 @@ func server_url_to_name(server string) string {
return host return host
} }
func connect_server(ctx context.Context, address string, ClientData *login.ClientData, want_packs bool) (serverConn *minecraft.Conn, err error) { func connect_server(ctx context.Context, address string, ClientData *login.ClientData, want_packs bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) {
var packet_func func(header packet.Header, payload []byte, src, dst net.Addr) = nil packet_func := func(header packet.Header, payload []byte, src, dst net.Addr) {
if G_debug { if G_debug {
packet_func = PacketLogger PacketLogger(header, payload, src, dst)
if packetFunc != nil {
packetFunc(header, payload, src, dst)
}
}
} }
cd := login.ClientData{} cd := login.ClientData{}

View File

@ -132,14 +132,18 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
w.withPacks = c.packs w.withPacks = c.packs
w.ctx = ctx w.ctx = ctx
err = create_proxy(ctx, logrus.StandardLogger(), server_address, w.OnConnect, func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) { proxy := NewProxy(logrus.StandardLogger())
proxy.onConnect = w.OnConnect
proxy.packetCB = func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) {
if toServer { if toServer {
pk = w.ProcessPacketClient(pk) pk = w.ProcessPacketClient(pk)
} else { } else {
pk = w.ProcessPacketServer(pk) pk = w.ProcessPacketServer(pk)
} }
return pk, nil return pk, nil
}) }
err = proxy.Run(ctx, server_address)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
return 1 return 1