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"
"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

12
main.go
View File

@ -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{})
}

View File

@ -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
}

View File

@ -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
}
}()

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 {
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)
}
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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{}

View File

@ -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