From 5d099729aebf26548756af41d6019cd46c4474bd Mon Sep 17 00:00:00 2001 From: olebeck <31539311+olebeck@users.noreply.github.com> Date: Sun, 4 Sep 2022 15:24:55 +0200 Subject: [PATCH] make capture not produce broken files --- capture.go | 50 ++++++++++---------------------- main.go | 12 +++++--- merge.go | 2 +- proxy.go | 74 ++++++++++++++++++++++++++++++------------------ replay.go | 77 ++++++++++++++++++++++++++++++++++++-------------- skins-proxy.go | 7 +++-- skins.go | 2 +- utils.go | 12 +++++--- world.go | 8 ++++-- 9 files changed, 147 insertions(+), 97 deletions(-) diff --git a/capture.go b/capture.go index a862b08..ecbdc29 100644 --- a/capture.go +++ b/capture.go @@ -8,14 +8,12 @@ import ( "log" "net" "os" - "sync" "time" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcapgo" "github.com/google/subcommands" - "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" "github.com/sirupsen/logrus" ) @@ -29,41 +27,24 @@ func init() { 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 iface_index int - var src_ip, dst_ip net.IP + var prefix []byte if from_client { - iface_index = 1 - src_ip = SrcIp_client - dst_ip = SrcIp_server + prefix = []byte("client") } else { - iface_index = 2 - src_ip = SrcIp_server - dst_ip = SrcIp_client + prefix = []byte("server") } packet_data := bytes.NewBuffer(nil) - { - _pw := bytes.NewBuffer(nil) - pw := protocol.NewWriter(_pw, 0x0) - pk.Marshal(pw) - h := packet.Header{ - PacketID: pk.ID(), - } - h.Write(packet_data) - packet_data.Write(_pw.Bytes()) - } + pk.Write(packet_data) + packet_data.Write(payload) serialize_buf := gopacket.NewSerializeBuffer() err = gopacket.SerializeLayers( serialize_buf, gopacket.SerializeOptions{}, - &layers.IPv4{ - SrcIP: src_ip, - DstIP: dst_ip, - Length: uint16(packet_data.Len()), - }, + gopacket.Payload(prefix), gopacket.Payload(packet_data.Bytes()), ) if err != nil { @@ -74,7 +55,7 @@ func dump_packet(from_client bool, w *pcapgo.Writer, pk packet.Packet) { Timestamp: time.Now(), Length: len(serialize_buf.Bytes()), CaptureLength: len(serialize_buf.Bytes()), - InterfaceIndex: iface_index, + InterfaceIndex: 1, }, serialize_buf.Bytes()) if err != nil { log.Fatal(err) @@ -112,16 +93,15 @@ func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interfac w := pcapgo.NewWriter(fio) 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) { - _wl.Lock() - dump_packet(toServer, w, pk) - _wl.Unlock() - return pk, nil - }) + err = proxy.Run(ctx, address) if err != nil { - fmt.Fprintln(os.Stderr, err) + logrus.Error(err) return 1 } return 0 diff --git a/main.go b/main.go index 9688950..343a764 100644 --- a/main.go +++ b/main.go @@ -18,9 +18,11 @@ const TOKEN_FILE = "token.json" var version string -var G_debug bool -var G_preload_packs bool -var G_exit []func() = []func(){} +var ( + G_debug bool + G_preload_packs bool + G_exit []func() = []func(){} +) func exit() { logrus.Info("\nExiting\n") @@ -40,7 +42,7 @@ func register_command(sub subcommands.Command) { func main() { logrus.SetLevel(logrus.DebugLevel) if version != "" { - logrus.Info("bedrocktool version: %s\n", version) + logrus.Infof("bedrocktool version: %s\n", version) } ctx, cancel := context.WithCancel(context.Background()) @@ -108,6 +110,7 @@ func (*TransCMD) Synopsis() string { return "" } func (c *TransCMD) SetFlags(f *flag.FlagSet) { f.BoolVar(&c.auth, "auth", false, "if it should login to xbox") } + func (c *TransCMD) Usage() string { 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) return 0 } + func init() { register_command(&TransCMD{}) } diff --git a/merge.go b/merge.go index 7aed749..88cf1f4 100644 --- a/merge.go +++ b/merge.go @@ -73,7 +73,7 @@ func (c *MergeCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{ time.Sleep(1 * time.Second) if err := zip_folder(out_name+".mcworld", out_name); err != nil { - logrus.Info("zipping: %s", err) + logrus.Infof("zipping: %s", err) return 1 } diff --git a/proxy.go b/proxy.go index daee404..6982010 100644 --- a/proxy.go +++ b/proxy.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "strings" "sync" @@ -37,9 +38,19 @@ type ProxyContext struct { client *minecraft.Conn listener *minecraft.Listener 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 ( + PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr) PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) 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") { - 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 - proxy := ProxyContext{} - proxy.commands = make(map[string]IngameCommand) - var packs []*resource.Pack if G_preload_packs { - log.Info("Preloading resourcepacks") - serverConn, err := connect_server(ctx, server_address, nil, true) + p.log.Info("Preloading resourcepacks") + serverConn, err := connect_server(ctx, server_address, nil, true, nil) if err != nil { return fmt.Errorf("failed to connect to %s: %s", server_address, err) } serverConn.Close() packs = serverConn.ResourcePacks() - log.Infof("%d packs loaded\n", len(packs)) + p.log.Infof("%d packs loaded\n", len(packs)) } _status := minecraft.NewStatusProvider("Server") - proxy.listener, err = minecraft.ListenConfig{ + p.listener, err = minecraft.ListenConfig{ StatusProvider: _status, ResourcePacks: packs, AcceptedProtocols: []minecraft.Protocol{ @@ -160,46 +178,48 @@ func create_proxy(ctx context.Context, log *logrus.Logger, server_address string if err != nil { 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 { - log.Fatal(err) + p.log.Fatal(err) } - proxy.client = c.(*minecraft.Conn) + p.client = c.(*minecraft.Conn) - cd := proxy.client.ClientData() - proxy.server, err = connect_server(ctx, server_address, &cd, false) + cd := p.client.ClientData() + p.server, err = connect_server(ctx, server_address, &cd, false, p.packetFunc) if err != nil { return fmt.Errorf("failed to connect to %s: %s", server_address, err) } // 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) } - defer proxy.server.Close() - defer proxy.listener.Disconnect(proxy.client, G_disconnect_reason) + defer p.server.Close() + defer p.listener.Disconnect(p.client, G_disconnect_reason) - if onConnect != nil { - onConnect(&proxy) + if p.onConnect != nil { + p.onConnect(p) } wg := sync.WaitGroup{} cbs := []PacketCallback{ - proxy.CommandHandlerPacketCB, - packetCB, + p.CommandHandlerPacketCB, + } + if p.packetCB != nil { + cbs = append(cbs, p.packetCB) } // server to client wg.Add(1) go func() { defer wg.Done() - if err := proxyLoop(ctx, &proxy, false, cbs); err != nil { - log.Error(err) + if err := proxyLoop(ctx, p, false, cbs); err != nil { + p.log.Error(err) return } }() @@ -208,8 +228,8 @@ func create_proxy(ctx context.Context, log *logrus.Logger, server_address string wg.Add(1) go func() { defer wg.Done() - if err := proxyLoop(ctx, &proxy, true, cbs); err != nil { - log.Error(err) + if err := proxyLoop(ctx, p, true, cbs); err != nil { + p.log.Error(err) return } }() diff --git a/replay.go b/replay.go index 7c00a19..7a84bfe 100644 --- a/replay.go +++ b/replay.go @@ -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 { log.Infof("Reading replay %s", filename) - OLD_BROKEN := true + OLD_BROKEN := false f, err := os.Open(filename) 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)) - dummy_conn := minecraft.NewConn() - dummy_conn.SetGameData(minecraft.GameData{ - BaseGameVersion: "1.17.40", // SPECIFIC TO THE SERVER; TODO - }) - - proxy := ProxyContext{} - proxy.server = dummy_conn - - if onConnect != nil { - onConnect(&proxy) - } + proxy := NewProxy(logrus.StandardLogger()) + proxy.server = minecraft.NewConn() var fake_header []byte if OLD_BROKEN { @@ -58,6 +49,8 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename fake_header = fake_header_w.Bytes() } + game_started := false + start := time.Time{} for { data, ci, err := reader.ReadPacketData() @@ -67,31 +60,73 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename if start.Unix() == 0 { start = ci.Timestamp } - - payload := data[0x14:] - if len(payload) == 0 { + if len(data) < 0x14 { continue } + var payload []byte + var toServer bool 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 { return err } - pks, err := pk_data.Decode(dummy_conn) + pks, err := pk_data.Decode(proxy.server) if err != nil { log.Error(err) continue } for _, pk := range pks { - if data[0x10] == 127 { // to client - packetCB(pk, &proxy, false) + if game_started || OLD_BROKEN { + if packetCB != nil { + packetCB(pk, proxy, toServer) + } } 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) + } + } } } } diff --git a/skins-proxy.go b/skins-proxy.go index 823d145..7f89dd2 100644 --- a/skins-proxy.go +++ b/skins-proxy.go @@ -37,12 +37,15 @@ func (c *SkinProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interf out_path := fmt.Sprintf("skins/%s", hostname) 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 { process_packet_skins(proxy.client, out_path, pk, c.filter) } return pk, nil - }) + } + + err = proxy.Run(ctx, address) if err != nil { logrus.Error(err) return 1 diff --git a/skins.go b/skins.go index ced1f6b..fcbeb92 100644 --- a/skins.go +++ b/skins.go @@ -251,7 +251,7 @@ func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{} return 1 } - serverConn, err := connect_server(ctx, address, nil, false) + serverConn, err := connect_server(ctx, address, nil, false, nil) if err != nil { fmt.Fprint(os.Stderr, err) return 1 diff --git a/utils.go b/utils.go index 3a3353c..f9eeeb9 100644 --- a/utils.go +++ b/utils.go @@ -110,10 +110,14 @@ func server_url_to_name(server string) string { return host } -func connect_server(ctx context.Context, address string, ClientData *login.ClientData, want_packs bool) (serverConn *minecraft.Conn, err error) { - var packet_func func(header packet.Header, payload []byte, src, dst net.Addr) = nil - if G_debug { - packet_func = PacketLogger +func connect_server(ctx context.Context, address string, ClientData *login.ClientData, want_packs bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) { + packet_func := func(header packet.Header, payload []byte, src, dst net.Addr) { + if G_debug { + PacketLogger(header, payload, src, dst) + if packetFunc != nil { + packetFunc(header, payload, src, dst) + } + } } cd := login.ClientData{} diff --git a/world.go b/world.go index bae1b36..5fb1653 100644 --- a/world.go +++ b/world.go @@ -132,14 +132,18 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{ w.withPacks = c.packs 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 { pk = w.ProcessPacketClient(pk) } else { pk = w.ProcessPacketServer(pk) } return pk, nil - }) + } + + err = proxy.Run(ctx, server_address) if err != nil { fmt.Fprintln(os.Stderr, err) return 1