diff --git a/capture.go b/capture.go index ffcec33..f456aa7 100644 --- a/capture.go +++ b/capture.go @@ -43,12 +43,16 @@ func dump_packet(from_client bool, w *pcapgo.Writer, pk packet.Packet) { dst_ip = SrcIp_client } - var packet_data []byte + packet_data := bytes.NewBuffer(nil) { _pw := bytes.NewBuffer(nil) pw := protocol.NewWriter(_pw, 0x0) pk.Marshal(pw) - packet_data = _pw.Bytes() + h := packet.Header{ + PacketID: pk.ID(), + } + h.Write(packet_data) + packet_data.Write(_pw.Bytes()) } serialize_buf := gopacket.NewSerializeBuffer() @@ -58,9 +62,9 @@ func dump_packet(from_client bool, w *pcapgo.Writer, pk packet.Packet) { &layers.IPv4{ SrcIP: src_ip, DstIP: dst_ip, - Length: uint16(len(packet_data)), + Length: uint16(packet_data.Len()), }, - gopacket.Payload(packet_data), + gopacket.Payload(packet_data.Bytes()), ) if err != nil { log.Fatal(err) diff --git a/go.mod b/go.mod index 6b52f38..d142e88 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( //replace github.com/df-mc/dragonfly => ./dragonfly -replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.24.8-3 +replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.24.8-4 replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.8.3-0.20220902161600-2f9b3652bbb7 diff --git a/map_item.go b/map_item.go index 1c440be..f500924 100644 --- a/map_item.go +++ b/map_item.go @@ -181,13 +181,16 @@ func (m *MapUI) Send(w *WorldState) error { } m.send_lock.Unlock() - return w.proxy.client.WritePacket(&packet.ClientBoundMapItemData{ - MapID: VIEW_MAP_ID, - Width: 128, - Height: 128, - Pixels: img2rgba(m.img), - UpdateFlags: 2, - }) + if w.proxy.client != nil { + return w.proxy.client.WritePacket(&packet.ClientBoundMapItemData{ + MapID: VIEW_MAP_ID, + Width: 128, + Height: 128, + Pixels: img2rgba(m.img), + UpdateFlags: 2, + }) + } + return nil } func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) { diff --git a/proxy.go b/proxy.go index e52d520..684629b 100644 --- a/proxy.go +++ b/proxy.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "sync" "github.com/sandertv/gophertunnel/minecraft" @@ -35,11 +36,30 @@ type ProxyContext struct { client *minecraft.Conn listener *minecraft.Listener } + type ( PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) ConnectCallback func(proxy *ProxyContext) ) +func (p *ProxyContext) sendMessage(text string) { + if p.client != nil { + p.client.WritePacket(&packet.Text{ + TextType: packet.TextTypeSystem, + Message: "§8[§bBedrocktool§8]§r " + text, + }) + } +} + +func (p *ProxyContext) sendPopup(text string) { + if p.client != nil { + p.client.WritePacket(&packet.Text{ + TextType: packet.TextTypePopup, + Message: text, + }) + } +} + func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCBs []PacketCallback) error { var c1, c2 *minecraft.Conn if toServer { @@ -79,11 +99,9 @@ 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) { - /* - if strings.HasSuffix(server_address, ".pcap") { - return create_replay_connection(server_address) - } - */ + if strings.HasSuffix(server_address, ".pcap") { + return create_replay_connection(ctx, log, server_address, onConnect, packetCB) + } GetTokenSource() // ask for login before listening diff --git a/replay.go b/replay.go new file mode 100644 index 0000000..5b6f52a --- /dev/null +++ b/replay.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "fmt" + "os" + "reflect" + "time" + "unsafe" + + "github.com/google/gopacket" + "github.com/google/gopacket/pcapgo" + "github.com/sandertv/gophertunnel/minecraft" + "github.com/sirupsen/logrus" +) + +func SetUnexportedField(field reflect.Value, value interface{}) { + reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())). + Elem(). + Set(reflect.ValueOf(value)) +} + +type PayloadDecoder struct { + Payload []byte +} + +func (d PayloadDecoder) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { + return nil +} + +func create_replay_connection(ctx context.Context, log *logrus.Logger, filename string, onConnect ConnectCallback, packetCB PacketCallback) error { + fmt.Printf("Reading replay %s\n", filename) + + f, err := os.Open(filename) + if err != nil { + return err + } + reader, err := pcapgo.NewReader(f) + if err != nil { + return err + } + 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) + } + + /* FOR OLD BROKEN CAPTURES + fake_head := packet.Header{ + PacketID: packet.IDLevelChunk, + } + fake_header_w := bytes.NewBuffer(nil) + fake_head.Write(fake_header_w) + fake_header := fake_header_w.Bytes() + */ + + start := time.Time{} + for { + data, ci, err := reader.ReadPacketData() + if err != nil { + return err + } + if start.Unix() == 0 { + start = ci.Timestamp + } + + payload := data[0x14:] + if len(payload) == 0 { + continue + } + + // payload = append(fake_header, payload...) + + pk_data, err := minecraft.ParseData(payload, dummy_conn) + if err != nil { + return err + } + pks, err := pk_data.Decode(dummy_conn) + if err != nil { + log.Error(err) + continue + } + + for _, pk := range pks { + if data[0x10] == 127 { // to client + packetCB(pk, &proxy, false) + } else { + packetCB(pk, &proxy, true) + } + } + } +} diff --git a/skins.go b/skins.go index 9c2ae8f..5b02f74 100644 --- a/skins.go +++ b/skins.go @@ -129,7 +129,7 @@ func (skin *Skin) Write(output_path, name string) error { have_geometry, have_cape, have_animations, have_tint := len(skin.SkinGeometry) > 0, len(skin.CapeData) > 0, len(skin.Animations) > 0, len(skin.PieceTintColours) > 0 - os.MkdirAll(skin_dir, 0755) + os.MkdirAll(skin_dir, 0o755) if have_geometry { if err := skin.WriteGeometry(path.Join(skin_dir, "geometry.json")); err != nil { return err @@ -188,9 +188,11 @@ func write_skin(output_path, name string, skin protocol.Skin, filter string) { } } -var skin_players = make(map[string]string) -var skin_player_counts = make(map[string]int) -var processed_skins = make(map[string]bool) +var ( + skin_players = make(map[string]string) + skin_player_counts = make(map[string]int) + processed_skins = make(map[string]bool) +) func process_packet_skins(conn *minecraft.Conn, out_path string, pk packet.Packet, filter string) { switch _pk := pk.(type) { @@ -218,7 +220,7 @@ func process_packet_skins(conn *minecraft.Conn, out_path string, pk packet.Packe skin_players[player.UUID.String()] = name processed_skins[name] = true if conn != nil { - send_popup(conn, fmt.Sprintf("%s Skin was Saved", name)) + (&ProxyContext{client: conn}).sendPopup(fmt.Sprintf("%s Skin was Saved", name)) } } } @@ -236,6 +238,7 @@ func (c *SkinCMD) SetFlags(f *flag.FlagSet) { f.StringVar(&c.server_address, "address", "", "remote server address") f.StringVar(&c.filter, "filter", "", "player name filter prefix") } + func (c *SkinCMD) Usage() string { return c.Name() + ": " + c.Synopsis() + "\n" } @@ -264,7 +267,7 @@ func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{} println("Connected") println("Press ctrl+c to exit") - os.MkdirAll(out_path, 0755) + os.MkdirAll(out_path, 0o755) for { pk, err := serverConn.ReadPacket() diff --git a/utils.go b/utils.go index ac9db05..6a576cf 100644 --- a/utils.go +++ b/utils.go @@ -39,20 +39,6 @@ const SERVER_ADDRESS_HELP = `accepted server address formats: ` -func send_popup(conn *minecraft.Conn, text string) { - conn.WritePacket(&packet.Text{ - TextType: packet.TextTypePopup, - Message: text, - }) -} - -func send_message(conn *minecraft.Conn, text string) { - conn.WritePacket(&packet.Text{ - TextType: packet.TextTypeSystem, - Message: "§8[§bBedrocktool§8]§r " + text, - }) -} - func server_input(server string) (address, name string, err error) { if server == "" { // no arg provided, interactive input fmt.Printf("Enter Server: ") diff --git a/world.go b/world.go index 28ee8f5..d01101a 100644 --- a/world.go +++ b/world.go @@ -109,13 +109,13 @@ var IngameCommands = map[string]IngameCommand{ func setnameCommand(w *WorldState, cmdline []string) bool { w.WorldName = strings.Join(cmdline, " ") - send_message(w.proxy.client, fmt.Sprintf("worldName is now: %s", w.WorldName)) + w.proxy.sendMessage(fmt.Sprintf("worldName is now: %s", w.WorldName)) return true } func toggleVoid(w *WorldState, cmdline []string) bool { w.voidgen = !w.voidgen - send_message(w.proxy.client, fmt.Sprintf("using void generator: %t", w.voidgen)) + w.proxy.sendMessage(fmt.Sprintf("using void generator: %t", w.voidgen)) return true } @@ -496,7 +496,7 @@ func (w *WorldState) OnConnect(proxy *ProxyContext) { w.Dim = dimension_ids[uint8(dim_id)] } - send_message(w.proxy.client, "use /setname \nto set the world name") + w.proxy.sendMessage("use /setname \nto set the world name") G_exit = append(G_exit, func() { w.SaveAndReset() @@ -509,9 +509,11 @@ func (w *WorldState) OnConnect(proxy *ProxyContext) { default: t := time.NewTimer(1 * time.Second) for range t.C { - err := w.proxy.client.WritePacket(&MAP_ITEM_PACKET) - if err != nil { - return + if w.proxy.client != nil { + err := w.proxy.client.WritePacket(&MAP_ITEM_PACKET) + if err != nil { + return + } } } } @@ -550,7 +552,7 @@ func (w *WorldState) ProcessPacketServer(pk packet.Packet) packet.Packet { case *packet.LevelChunk: w.ProcessLevelChunk(pk) w.ui.Send(w) - send_popup(w.proxy.client, fmt.Sprintf("%d chunks loaded\nname: %s", len(w.chunks), w.WorldName)) + w.proxy.sendPopup(fmt.Sprintf("%d chunks loaded\nname: %s", len(w.chunks), w.WorldName)) case *packet.SubChunk: w.ProcessSubChunk(pk) case *packet.AvailableCommands: