From 33e64463f408d7afe39738cf6a9903220d2a3a89 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sat, 17 Sep 2022 21:37:49 +0100 Subject: [PATCH 1/6] portal: Add TCP alternative to RakNet --- server/server.go | 20 +- session/server_conn.go | 225 +++++++++++++++++++++ session/session.go | 19 +- session/tcpprotocol/connection_request.go | 26 +++ session/tcpprotocol/connection_response.go | 32 +++ session/tcpprotocol/id.go | 22 ++ session/tcpprotocol/player_identity.go | 43 ++++ socket/handler_register_server.go | 8 +- socket/packet/id.go | 2 +- socket/packet/register_server.go | 4 + 10 files changed, 384 insertions(+), 17 deletions(-) create mode 100644 session/server_conn.go create mode 100644 session/tcpprotocol/connection_request.go create mode 100644 session/tcpprotocol/connection_response.go create mode 100644 session/tcpprotocol/id.go create mode 100644 session/tcpprotocol/player_identity.go diff --git a/server/server.go b/server/server.go index cf69834..3335e40 100644 --- a/server/server.go +++ b/server/server.go @@ -6,17 +6,20 @@ import ( // Server represents a server connected to the proxy which players can join and play on. type Server struct { - name string - address string + name string + address string + useRakNet bool playerCount atomic.Int64 } -// New creates a new Server with the provided name, group and address. -func New(name, address string) *Server { +// New creates a new Server with the provided name, group and address as well as if the connection should use the RakNet +// protocol or not. +func New(name, address string, useRakNet bool) *Server { s := &Server{ - name: name, - address: address, + name: name, + address: address, + useRakNet: useRakNet, } return s @@ -33,6 +36,11 @@ func (s *Server) Address() string { return s.address } +// UseRakNet returns if a connection should use the RakNet protocol when connecting to the server. +func (s *Server) UseRakNet() bool { + return s.useRakNet +} + // IncrementPlayerCount increments the player count of the server. func (s *Server) IncrementPlayerCount() { s.playerCount.Add(1) diff --git a/session/server_conn.go b/session/server_conn.go new file mode 100644 index 0000000..2367784 --- /dev/null +++ b/session/server_conn.go @@ -0,0 +1,225 @@ +package session + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "github.com/klauspost/compress/snappy" + "github.com/paroxity/portal/session/tcpprotocol" + "github.com/sandertv/gophertunnel/minecraft" + "github.com/sandertv/gophertunnel/minecraft/protocol" + "github.com/sandertv/gophertunnel/minecraft/protocol/login" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" + "go.uber.org/atomic" + "io" + "net" + "sync" + "time" +) + +// ServerConn represents a connection that can be used between the proxy and a server to communicate on behalf of a client. +type ServerConn interface { + io.Closer + // GameData returns specific game data set to the connection for the player to be initialised with. This data is + // obtained from the server during the login process. + GameData() minecraft.GameData + // DoSpawnTimeout starts the game for the client in the server with a timeout after which an error is returned if the + // client has not yet spawned by that time. DoSpawnTimeout will start the spawning sequence using the game data found + // in conn.GameData(), which was sent earlier by the server. + DoSpawnTimeout(timeout time.Duration) error + // ReadPacket reads a packet from the Conn, depending on the packet ID that is found in front of the packet data. If + // a read deadline is set, an error is returned if the deadline is reached before any packet is received. ReadPacket + // must not be called on multiple goroutines simultaneously. If the packet read was not implemented, a *packet.Unknown + // is returned, containing the raw payload of the packet read. + ReadPacket() (packet.Packet, error) + // WritePacket encodes the packet passed and writes it to the Conn. The encoded data is buffered until the next 20th + // of a second, after which the data is flushed and sent over the connection. + WritePacket(packet.Packet) error +} + +// TCPConn represents a player's connection to a server that is using the TCP protocol instead of RakNet. +type TCPConn struct { + conn net.Conn + pool packet.Pool + + identityData login.IdentityData + clientData login.ClientData + gameData minecraft.GameData + + sendMu sync.Mutex + hdr *packet.Header + buf *bytes.Buffer + shieldID atomic.Int32 + + spawn chan struct{} +} + +// NewTCPConn attempts to create a new TCP-based connection to the provided server using the provided client data. If +// successful the connection will be returned, otherwise an error will be returned instead. +func NewTCPConn(address, playerAddress string, identityData login.IdentityData, clientData login.ClientData) (*TCPConn, error) { + conn := &TCPConn{ + identityData: identityData, + clientData: clientData, + pool: packet.NewPool(), + buf: bytes.NewBuffer(make([]byte, 0, 4096)), + hdr: &packet.Header{}, + spawn: make(chan struct{}, 1), + } + err := conn.dial(address, playerAddress) + if err != nil { + return nil, err + } + return conn, nil +} + +// dial attempts to dial a connection to the provided address for the player. An error is returned if it failed to dial. +func (conn *TCPConn) dial(address, playerAddress string) error { + tcpConn, err := net.Dial("tcp", address) + if err != nil { + return err + } + conn.conn = tcpConn + err = conn.WritePacket(&tcpprotocol.PlayerIdentity{ + IdentityData: conn.identityData, + ClientData: conn.clientData, + Address: playerAddress, + }) + if err != nil { + return err + } + pk, err := conn.ReadPacket() + if err != nil { + return err + } + startGame, ok := pk.(*packet.StartGame) + if !ok { + return fmt.Errorf("expected start game packet, got %T (%d)", pk, pk.ID()) + } + conn.gameData = minecraft.GameData{ + Difficulty: startGame.Difficulty, + WorldName: startGame.WorldName, + EntityUniqueID: startGame.EntityUniqueID, + EntityRuntimeID: startGame.EntityRuntimeID, + PlayerGameMode: startGame.PlayerGameMode, + BaseGameVersion: startGame.BaseGameVersion, + PlayerPosition: startGame.PlayerPosition, + Pitch: startGame.Pitch, + Yaw: startGame.Yaw, + Dimension: startGame.Dimension, + WorldSpawn: startGame.WorldSpawn, + EditorWorld: startGame.EditorWorld, + GameRules: startGame.GameRules, + Time: startGame.Time, + ServerBlockStateChecksum: startGame.ServerBlockStateChecksum, + CustomBlocks: startGame.Blocks, + Items: startGame.Items, + PlayerMovementSettings: startGame.PlayerMovementSettings, + WorldGameMode: startGame.WorldGameMode, + ServerAuthoritativeInventory: startGame.ServerAuthoritativeInventory, + Experiments: startGame.Experiments, + } + return nil +} + +// Close ... +func (conn *TCPConn) Close() error { + return conn.conn.Close() +} + +// GameData ... +func (conn *TCPConn) GameData() minecraft.GameData { + return conn.gameData +} + +// DoSpawnTimeout ... +func (conn *TCPConn) DoSpawnTimeout(timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + select { + case <-ctx.Done(): + return fmt.Errorf("spawn timeout") + case <-conn.spawn: + return nil + } +} + +// ReadPacket ... +func (conn *TCPConn) ReadPacket() (pk packet.Packet, err error) { + var l uint32 + if err := binary.Read(conn.conn, binary.LittleEndian, &l); err != nil { + return nil, err + } + + data := make([]byte, l) + read, err := conn.conn.Read(data) + if err != nil { + return nil, err + } + if read != int(l) { + return nil, fmt.Errorf("expected %v bytes, got %v", l, read) + } + + decoded, err := snappy.Decode(nil, data) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(decoded) + header := &packet.Header{} + if err := header.Read(buf); err != nil { + return nil, err + } + + pkFunc, ok := conn.pool[header.PacketID] + if !ok { + return nil, fmt.Errorf("unknown packet %v", header.PacketID) + } + + defer func() { + if recoveredErr := recover(); recoveredErr != nil { + err = fmt.Errorf("%T: %w", pk, recoveredErr.(error)) + } + }() + pk = pkFunc() + pk.Unmarshal(protocol.NewReader(buf, 0)) + if buf.Len() > 0 { + return nil, fmt.Errorf("still have %v bytes unread", buf.Len()) + } + + if _, ok := pk.(*packet.StartGame); ok { + close(conn.spawn) + } + + return pk, nil +} + +// WritePacket ... +func (conn *TCPConn) WritePacket(pk packet.Packet) error { + conn.sendMu.Lock() + conn.hdr.PacketID = pk.ID() + _ = conn.hdr.Write(conn.buf) + + pk.Marshal(protocol.NewWriter(conn.buf, conn.shieldID.Load())) + + data := conn.buf.Bytes() + conn.buf.Reset() + conn.sendMu.Unlock() + + encoded := snappy.Encode(nil, data) + + buf := bytes.NewBuffer(make([]byte, 0, 4+len(encoded))) + + if err := binary.Write(buf, binary.LittleEndian, int32(len(encoded))); err != nil { + return err + } + if _, err := buf.Write(encoded); err != nil { + return err + } + + if _, err := conn.conn.Write(buf.Bytes()); err != nil { + return err + } + + return nil +} diff --git a/session/session.go b/session/session.go index ebbe262..22b29f0 100644 --- a/session/session.go +++ b/session/session.go @@ -33,8 +33,8 @@ type Session struct { loginMu sync.RWMutex serverMu sync.RWMutex server *server.Server - serverConn *minecraft.Conn - tempServerConn *minecraft.Conn + serverConn ServerConn + tempServerConn ServerConn entities *i64set.Set playerList *b16set.Set @@ -105,13 +105,16 @@ func New(conn *minecraft.Conn, store *Store, loadBalancer LoadBalancer, log inte // dial dials a new connection to the provided server. It then returns the connection between the proxy and // that server, along with any error that may have occurred. -func (s *Session) dial(srv *server.Server) (*minecraft.Conn, error) { +func (s *Session) dial(srv *server.Server) (ServerConn, error) { i := s.conn.IdentityData() i.XUID = "" - return minecraft.Dialer{ - ClientData: s.conn.ClientData(), - IdentityData: i, - }.Dial("raknet", srv.Address()) + if srv.UseRakNet() { + return minecraft.Dialer{ + ClientData: s.conn.ClientData(), + IdentityData: i, + }.Dial("raknet", srv.Address()) + } + return NewTCPConn(srv.Address(), s.conn.RemoteAddr().String(), i, s.conn.ClientData()) } // login performs the initial login sequence for the session. @@ -152,7 +155,7 @@ func (s *Session) Server() *server.Server { } // ServerConn returns the connection for the session's current server. -func (s *Session) ServerConn() *minecraft.Conn { +func (s *Session) ServerConn() ServerConn { s.waitForLogin() s.serverMu.RLock() defer s.serverMu.RUnlock() diff --git a/session/tcpprotocol/connection_request.go b/session/tcpprotocol/connection_request.go new file mode 100644 index 0000000..956538e --- /dev/null +++ b/session/tcpprotocol/connection_request.go @@ -0,0 +1,26 @@ +package tcpprotocol + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" +) + +// ConnectionRequest is sent by the proxy to request a connection for a player who is attempting to join the server. +type ConnectionRequest struct { + // ProtocolVersion is the protocol version of the TCP protocol used by the proxy. + ProtocolVersion uint32 +} + +// ID ... +func (pk *ConnectionRequest) ID() uint32 { + return IDConnectionRequest +} + +// Marshal ... +func (pk *ConnectionRequest) Marshal(w *protocol.Writer) { + w.Uint32(&pk.ProtocolVersion) +} + +// Unmarshal ... +func (pk *ConnectionRequest) Unmarshal(r *protocol.Reader) { + r.Uint32(&pk.ProtocolVersion) +} diff --git a/session/tcpprotocol/connection_response.go b/session/tcpprotocol/connection_response.go new file mode 100644 index 0000000..47af5e9 --- /dev/null +++ b/session/tcpprotocol/connection_response.go @@ -0,0 +1,32 @@ +package tcpprotocol + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" +) + +const ( + ConnectionResponseSuccess byte = iota + ConnectionResponseInvalidProtocol +) + +// ConnectionResponse is sent by the server in response to a ConnectionRequest packet. It contains the response and if +// the player was able to connect to the server successfully. +type ConnectionResponse struct { + // Response is the response from the server. This can be one of the constants above. + Response byte +} + +// ID ... +func (pk *ConnectionResponse) ID() uint32 { + return IDConnectionResponse +} + +// Marshal ... +func (pk *ConnectionResponse) Marshal(w *protocol.Writer) { + w.Uint8(&pk.Response) +} + +// Unmarshal ... +func (pk *ConnectionResponse) Unmarshal(r *protocol.Reader) { + r.Uint8(&pk.Response) +} diff --git a/session/tcpprotocol/id.go b/session/tcpprotocol/id.go new file mode 100644 index 0000000..08ae70a --- /dev/null +++ b/session/tcpprotocol/id.go @@ -0,0 +1,22 @@ +package tcpprotocol + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" + "math" +) + +// ProtocolVersion is the current supported version of the protocol. If a server is using an outdated version of the +// protocol, players will be unable to connect. This constant gets updated every time the protocol is changed. +const ProtocolVersion = 1 + +const ( + IDConnectionRequest uint32 = math.MaxUint32 - iota + IDConnectionResponse + IDPlayerIdentity +) + +func init() { + packet.Register(IDPlayerIdentity, func() packet.Packet { return &PlayerIdentity{} }) + packet.Register(IDConnectionRequest, func() packet.Packet { return &ConnectionRequest{} }) + packet.Register(IDConnectionResponse, func() packet.Packet { return &ConnectionResponse{} }) +} diff --git a/session/tcpprotocol/player_identity.go b/session/tcpprotocol/player_identity.go new file mode 100644 index 0000000..980b6fd --- /dev/null +++ b/session/tcpprotocol/player_identity.go @@ -0,0 +1,43 @@ +package tcpprotocol + +import ( + "encoding/json" + "github.com/sandertv/gophertunnel/minecraft/protocol" + "github.com/sandertv/gophertunnel/minecraft/protocol/login" +) + +// PlayerIdentity is sent by the proxy to give the server information about the client that would usually be sent within +// the login sequence. +type PlayerIdentity struct { + // IdentityData contains identity data of the player logged in. + IdentityData login.IdentityData + // ClientData is a container of client specific data of a Login packet. It holds data such as the skin of a player, + // but also its language code and device information. + ClientData login.ClientData + // Address is the address of the player that has joined the server. + Address string +} + +// ID ... +func (pk *PlayerIdentity) ID() uint32 { + return IDPlayerIdentity +} + +// Marshal ... +func (pk *PlayerIdentity) Marshal(w *protocol.Writer) { + identityData, _ := json.Marshal(pk.IdentityData) + clientData, _ := json.Marshal(pk.ClientData) + w.ByteSlice(&identityData) + w.ByteSlice(&clientData) + w.String(&pk.Address) +} + +// Unmarshal ... +func (pk *PlayerIdentity) Unmarshal(r *protocol.Reader) { + var identityData, clientData []byte + r.ByteSlice(&identityData) + r.ByteSlice(&clientData) + r.String(&pk.Address) + _ = json.Unmarshal(identityData, &pk.IdentityData) + _ = json.Unmarshal(clientData, &pk.ClientData) +} diff --git a/socket/handler_register_server.go b/socket/handler_register_server.go index 9d360e5..ce23f0f 100644 --- a/socket/handler_register_server.go +++ b/socket/handler_register_server.go @@ -11,7 +11,11 @@ type RegisterServerHandler struct{ requireAuth } // Handle ... func (*RegisterServerHandler) Handle(p packet.Packet, srv Server, c *Client) error { pk := p.(*packet.RegisterServer) - srv.ServerRegistry().AddServer(server.New(c.Name(), pk.Address)) - srv.Logger().Debugf("socket connection \"%s\" has registered itself as a server with the address \"%s\"", c.Name(), pk.Address) + srv.ServerRegistry().AddServer(server.New(c.Name(), pk.Address, pk.UseRakNet)) + mode := "TCP" + if pk.UseRakNet { + mode = "RakNet" + } + srv.Logger().Debugf("socket connection \"%s\" has registered itself as a server with the address \"%s\" (%s)", c.Name(), pk.Address, mode) return nil } diff --git a/socket/packet/id.go b/socket/packet/id.go index 58cbff6..4b08947 100644 --- a/socket/packet/id.go +++ b/socket/packet/id.go @@ -2,7 +2,7 @@ package packet // ProtocolVersion is the protocol version supported by the proxy. It will only accept clients that match this version, // and it should be incremented every time the protocol changes. -const ProtocolVersion = 1 +const ProtocolVersion = 2 const ( IDAuthRequest uint16 = iota diff --git a/socket/packet/register_server.go b/socket/packet/register_server.go index b560b72..ef71517 100644 --- a/socket/packet/register_server.go +++ b/socket/packet/register_server.go @@ -6,6 +6,8 @@ import "github.com/sandertv/gophertunnel/minecraft/protocol" type RegisterServer struct { // Address is the address of the server in the format ip:port. Address string + // UseRakNet is if a connection should use the RakNet protocol when connecting to the server. + UseRakNet bool } // ID ... @@ -16,9 +18,11 @@ func (pk *RegisterServer) ID() uint16 { // Marshal ... func (pk *RegisterServer) Marshal(w *protocol.Writer) { w.String(&pk.Address) + w.Bool(&pk.UseRakNet) } // Unmarshal ... func (pk *RegisterServer) Unmarshal(r *protocol.Reader) { r.String(&pk.Address) + r.Bool(&pk.UseRakNet) } From 3b91d723e3f4feb4a2df641659aab506cca1005a Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sun, 18 Sep 2022 10:08:20 +0100 Subject: [PATCH 2/6] tcpprotocol: Use a dialer and a listener similar to gophertunnel --- go.mod | 7 +- go.sum | 16 +- session/server_conn.go | 197 ------------------ session/session.go | 7 +- session/tcpprotocol/id.go | 22 -- .../{ => packet}/connection_request.go | 2 +- .../{ => packet}/connection_response.go | 4 +- session/tcpprotocol/packet/id.go | 7 + .../{ => packet}/player_identity.go | 8 +- 9 files changed, 39 insertions(+), 231 deletions(-) delete mode 100644 session/tcpprotocol/id.go rename session/tcpprotocol/{ => packet}/connection_request.go (96%) rename session/tcpprotocol/{ => packet}/connection_response.go (92%) create mode 100644 session/tcpprotocol/packet/id.go rename session/tcpprotocol/{ => packet}/player_identity.go (77%) diff --git a/go.mod b/go.mod index 307410a..f5dfefa 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/paroxity/portal go 1.18 require ( + github.com/df-mc/dragonfly v0.8.3 github.com/google/uuid v1.3.0 github.com/klauspost/compress v1.15.4 github.com/mattn/go-colorable v0.1.11 @@ -13,15 +14,16 @@ require ( ) require ( + github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect + github.com/cespare/xxhash v1.1.0 // indirect github.com/df-mc/atomic v1.10.0 // indirect github.com/go-gl/mathgl v1.0.0 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.8 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect github.com/sandertv/go-raknet v1.11.1 // indirect - github.com/stretchr/testify v1.7.0 // indirect golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect + golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 // indirect golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect golang.org/x/net v0.0.0-20220513224357-95641704303c // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect @@ -30,5 +32,4 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index e40a36f..e61dc4d 100644 --- a/go.sum +++ b/go.sum @@ -33,7 +33,13 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0= +github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -44,6 +50,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg= github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc= +github.com/df-mc/dragonfly v0.8.3 h1:IyffRhmaqyZ9s0/XrxMmr5tGyUPsmeUi+iapP0T+Qok= +github.com/df-mc/dragonfly v0.8.3/go.mod h1:ObfYlB77fxGLqU2CLquvk8ibAEMYoixiXfs7pxrOGCI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -93,7 +101,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -137,12 +144,13 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvv github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -170,6 +178,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 h1:x03zeu7B2B11ySp+daztnwM5oBJ/8wGUSqrwcw9L0RA= +golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -410,9 +420,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/session/server_conn.go b/session/server_conn.go index 2367784..699fed7 100644 --- a/session/server_conn.go +++ b/session/server_conn.go @@ -1,20 +1,9 @@ package session import ( - "bytes" - "context" - "encoding/binary" - "fmt" - "github.com/klauspost/compress/snappy" - "github.com/paroxity/portal/session/tcpprotocol" "github.com/sandertv/gophertunnel/minecraft" - "github.com/sandertv/gophertunnel/minecraft/protocol" - "github.com/sandertv/gophertunnel/minecraft/protocol/login" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" - "go.uber.org/atomic" "io" - "net" - "sync" "time" ) @@ -37,189 +26,3 @@ type ServerConn interface { // of a second, after which the data is flushed and sent over the connection. WritePacket(packet.Packet) error } - -// TCPConn represents a player's connection to a server that is using the TCP protocol instead of RakNet. -type TCPConn struct { - conn net.Conn - pool packet.Pool - - identityData login.IdentityData - clientData login.ClientData - gameData minecraft.GameData - - sendMu sync.Mutex - hdr *packet.Header - buf *bytes.Buffer - shieldID atomic.Int32 - - spawn chan struct{} -} - -// NewTCPConn attempts to create a new TCP-based connection to the provided server using the provided client data. If -// successful the connection will be returned, otherwise an error will be returned instead. -func NewTCPConn(address, playerAddress string, identityData login.IdentityData, clientData login.ClientData) (*TCPConn, error) { - conn := &TCPConn{ - identityData: identityData, - clientData: clientData, - pool: packet.NewPool(), - buf: bytes.NewBuffer(make([]byte, 0, 4096)), - hdr: &packet.Header{}, - spawn: make(chan struct{}, 1), - } - err := conn.dial(address, playerAddress) - if err != nil { - return nil, err - } - return conn, nil -} - -// dial attempts to dial a connection to the provided address for the player. An error is returned if it failed to dial. -func (conn *TCPConn) dial(address, playerAddress string) error { - tcpConn, err := net.Dial("tcp", address) - if err != nil { - return err - } - conn.conn = tcpConn - err = conn.WritePacket(&tcpprotocol.PlayerIdentity{ - IdentityData: conn.identityData, - ClientData: conn.clientData, - Address: playerAddress, - }) - if err != nil { - return err - } - pk, err := conn.ReadPacket() - if err != nil { - return err - } - startGame, ok := pk.(*packet.StartGame) - if !ok { - return fmt.Errorf("expected start game packet, got %T (%d)", pk, pk.ID()) - } - conn.gameData = minecraft.GameData{ - Difficulty: startGame.Difficulty, - WorldName: startGame.WorldName, - EntityUniqueID: startGame.EntityUniqueID, - EntityRuntimeID: startGame.EntityRuntimeID, - PlayerGameMode: startGame.PlayerGameMode, - BaseGameVersion: startGame.BaseGameVersion, - PlayerPosition: startGame.PlayerPosition, - Pitch: startGame.Pitch, - Yaw: startGame.Yaw, - Dimension: startGame.Dimension, - WorldSpawn: startGame.WorldSpawn, - EditorWorld: startGame.EditorWorld, - GameRules: startGame.GameRules, - Time: startGame.Time, - ServerBlockStateChecksum: startGame.ServerBlockStateChecksum, - CustomBlocks: startGame.Blocks, - Items: startGame.Items, - PlayerMovementSettings: startGame.PlayerMovementSettings, - WorldGameMode: startGame.WorldGameMode, - ServerAuthoritativeInventory: startGame.ServerAuthoritativeInventory, - Experiments: startGame.Experiments, - } - return nil -} - -// Close ... -func (conn *TCPConn) Close() error { - return conn.conn.Close() -} - -// GameData ... -func (conn *TCPConn) GameData() minecraft.GameData { - return conn.gameData -} - -// DoSpawnTimeout ... -func (conn *TCPConn) DoSpawnTimeout(timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - select { - case <-ctx.Done(): - return fmt.Errorf("spawn timeout") - case <-conn.spawn: - return nil - } -} - -// ReadPacket ... -func (conn *TCPConn) ReadPacket() (pk packet.Packet, err error) { - var l uint32 - if err := binary.Read(conn.conn, binary.LittleEndian, &l); err != nil { - return nil, err - } - - data := make([]byte, l) - read, err := conn.conn.Read(data) - if err != nil { - return nil, err - } - if read != int(l) { - return nil, fmt.Errorf("expected %v bytes, got %v", l, read) - } - - decoded, err := snappy.Decode(nil, data) - if err != nil { - return nil, err - } - - buf := bytes.NewBuffer(decoded) - header := &packet.Header{} - if err := header.Read(buf); err != nil { - return nil, err - } - - pkFunc, ok := conn.pool[header.PacketID] - if !ok { - return nil, fmt.Errorf("unknown packet %v", header.PacketID) - } - - defer func() { - if recoveredErr := recover(); recoveredErr != nil { - err = fmt.Errorf("%T: %w", pk, recoveredErr.(error)) - } - }() - pk = pkFunc() - pk.Unmarshal(protocol.NewReader(buf, 0)) - if buf.Len() > 0 { - return nil, fmt.Errorf("still have %v bytes unread", buf.Len()) - } - - if _, ok := pk.(*packet.StartGame); ok { - close(conn.spawn) - } - - return pk, nil -} - -// WritePacket ... -func (conn *TCPConn) WritePacket(pk packet.Packet) error { - conn.sendMu.Lock() - conn.hdr.PacketID = pk.ID() - _ = conn.hdr.Write(conn.buf) - - pk.Marshal(protocol.NewWriter(conn.buf, conn.shieldID.Load())) - - data := conn.buf.Bytes() - conn.buf.Reset() - conn.sendMu.Unlock() - - encoded := snappy.Encode(nil, data) - - buf := bytes.NewBuffer(make([]byte, 0, 4+len(encoded))) - - if err := binary.Write(buf, binary.LittleEndian, int32(len(encoded))); err != nil { - return err - } - if _, err := buf.Write(encoded); err != nil { - return err - } - - if _, err := conn.conn.Write(buf.Bytes()); err != nil { - return err - } - - return nil -} diff --git a/session/session.go b/session/session.go index 22b29f0..7b0bb67 100644 --- a/session/session.go +++ b/session/session.go @@ -6,6 +6,7 @@ import ( "github.com/paroxity/portal/event" "github.com/paroxity/portal/internal" "github.com/paroxity/portal/server" + "github.com/paroxity/portal/session/tcpprotocol" "github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" @@ -114,7 +115,11 @@ func (s *Session) dial(srv *server.Server) (ServerConn, error) { IdentityData: i, }.Dial("raknet", srv.Address()) } - return NewTCPConn(srv.Address(), s.conn.RemoteAddr().String(), i, s.conn.ClientData()) + return tcpprotocol.Dialer{ + ClientData: s.conn.ClientData(), + IdentityData: i, + EnableClientCache: s.conn.ClientCacheEnabled(), + }.Dial("tcp", srv.Address(), s.conn.RemoteAddr().String()) } // login performs the initial login sequence for the session. diff --git a/session/tcpprotocol/id.go b/session/tcpprotocol/id.go deleted file mode 100644 index 08ae70a..0000000 --- a/session/tcpprotocol/id.go +++ /dev/null @@ -1,22 +0,0 @@ -package tcpprotocol - -import ( - "github.com/sandertv/gophertunnel/minecraft/protocol/packet" - "math" -) - -// ProtocolVersion is the current supported version of the protocol. If a server is using an outdated version of the -// protocol, players will be unable to connect. This constant gets updated every time the protocol is changed. -const ProtocolVersion = 1 - -const ( - IDConnectionRequest uint32 = math.MaxUint32 - iota - IDConnectionResponse - IDPlayerIdentity -) - -func init() { - packet.Register(IDPlayerIdentity, func() packet.Packet { return &PlayerIdentity{} }) - packet.Register(IDConnectionRequest, func() packet.Packet { return &ConnectionRequest{} }) - packet.Register(IDConnectionResponse, func() packet.Packet { return &ConnectionResponse{} }) -} diff --git a/session/tcpprotocol/connection_request.go b/session/tcpprotocol/packet/connection_request.go similarity index 96% rename from session/tcpprotocol/connection_request.go rename to session/tcpprotocol/packet/connection_request.go index 956538e..2c0f421 100644 --- a/session/tcpprotocol/connection_request.go +++ b/session/tcpprotocol/packet/connection_request.go @@ -1,4 +1,4 @@ -package tcpprotocol +package packet import ( "github.com/sandertv/gophertunnel/minecraft/protocol" diff --git a/session/tcpprotocol/connection_response.go b/session/tcpprotocol/packet/connection_response.go similarity index 92% rename from session/tcpprotocol/connection_response.go rename to session/tcpprotocol/packet/connection_response.go index 47af5e9..18f5826 100644 --- a/session/tcpprotocol/connection_response.go +++ b/session/tcpprotocol/packet/connection_response.go @@ -1,4 +1,4 @@ -package tcpprotocol +package packet import ( "github.com/sandertv/gophertunnel/minecraft/protocol" @@ -6,7 +6,7 @@ import ( const ( ConnectionResponseSuccess byte = iota - ConnectionResponseInvalidProtocol + ConnectionResponseUnsupportedProtocol ) // ConnectionResponse is sent by the server in response to a ConnectionRequest packet. It contains the response and if diff --git a/session/tcpprotocol/packet/id.go b/session/tcpprotocol/packet/id.go new file mode 100644 index 0000000..44b116f --- /dev/null +++ b/session/tcpprotocol/packet/id.go @@ -0,0 +1,7 @@ +package packet + +const ( + IDConnectionRequest uint32 = 0x3FF - iota + IDConnectionResponse + IDPlayerIdentity +) diff --git a/session/tcpprotocol/player_identity.go b/session/tcpprotocol/packet/player_identity.go similarity index 77% rename from session/tcpprotocol/player_identity.go rename to session/tcpprotocol/packet/player_identity.go index 980b6fd..ec052c0 100644 --- a/session/tcpprotocol/player_identity.go +++ b/session/tcpprotocol/packet/player_identity.go @@ -1,4 +1,4 @@ -package tcpprotocol +package packet import ( "encoding/json" @@ -14,6 +14,10 @@ type PlayerIdentity struct { // ClientData is a container of client specific data of a Login packet. It holds data such as the skin of a player, // but also its language code and device information. ClientData login.ClientData + // EnableClientCache, if set to true, enables the client blob cache for the client. This means that the server will + // send chunks as blobs, which may be saved by the client so that chunks don't have to be transmitted every time, + // resulting in less network transmission. + EnableClientCache bool // Address is the address of the player that has joined the server. Address string } @@ -29,6 +33,7 @@ func (pk *PlayerIdentity) Marshal(w *protocol.Writer) { clientData, _ := json.Marshal(pk.ClientData) w.ByteSlice(&identityData) w.ByteSlice(&clientData) + w.Bool(&pk.EnableClientCache) w.String(&pk.Address) } @@ -37,6 +42,7 @@ func (pk *PlayerIdentity) Unmarshal(r *protocol.Reader) { var identityData, clientData []byte r.ByteSlice(&identityData) r.ByteSlice(&clientData) + r.Bool(&pk.EnableClientCache) r.String(&pk.Address) _ = json.Unmarshal(identityData, &pk.IdentityData) _ = json.Unmarshal(clientData, &pk.ClientData) From 05d88fbcbf155a94c8086801423d8531821a061c Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sun, 18 Sep 2022 10:08:41 +0100 Subject: [PATCH 3/6] tcpprotocol: Missing files --- session/tcpprotocol/conn.go | 329 ++++++++++++++++++++++++++++++++ session/tcpprotocol/dialer.go | 50 +++++ session/tcpprotocol/listener.go | 56 ++++++ session/tcpprotocol/protocol.go | 16 ++ 4 files changed, 451 insertions(+) create mode 100644 session/tcpprotocol/conn.go create mode 100644 session/tcpprotocol/dialer.go create mode 100644 session/tcpprotocol/listener.go create mode 100644 session/tcpprotocol/protocol.go diff --git a/session/tcpprotocol/conn.go b/session/tcpprotocol/conn.go new file mode 100644 index 0000000..19f7d28 --- /dev/null +++ b/session/tcpprotocol/conn.go @@ -0,0 +1,329 @@ +package tcpprotocol + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "github.com/google/uuid" + "github.com/klauspost/compress/snappy" + packet2 "github.com/paroxity/portal/session/tcpprotocol/packet" + "github.com/sandertv/gophertunnel/minecraft" + "github.com/sandertv/gophertunnel/minecraft/protocol" + "github.com/sandertv/gophertunnel/minecraft/protocol/login" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" + "go.uber.org/atomic" + "net" + "sync" + "time" +) + +// Conn represents a player's connection to a server that is using the TCP protocol instead of RakNet. +type Conn struct { + conn net.Conn + pool packet.Pool + + identityData login.IdentityData + clientData login.ClientData + gameData minecraft.GameData + + enableClientCache bool + + sendMu sync.Mutex + hdr *packet.Header + buf *bytes.Buffer + shieldID atomic.Int32 + + spawn chan struct{} +} + +// newConn attempts to create a new TCP-based connection to the provided server using the provided client data. If +// successful the connection will be returned, otherwise an error will be returned instead. +func newConn(netConn net.Conn) *Conn { + conn := &Conn{ + conn: netConn, + pool: packet.NewPool(), + buf: bytes.NewBuffer(make([]byte, 0, 4096)), + hdr: &packet.Header{}, + spawn: make(chan struct{}, 1), + } + return conn +} + +// login attempts to connect to a server by identifying the player and waiting to receive the StartGame packet. An error +// is returned if it failed to connect. +func (conn *Conn) login(playerAddress string) error { + err := conn.WritePacket(&packet2.ConnectionRequest{ + ProtocolVersion: ProtocolVersion, + }) + if err != nil { + return err + } + pk, err := conn.ReadPacket() + if err != nil { + return err + } + connectionResponse, ok := pk.(*packet2.ConnectionResponse) + if !ok { + return fmt.Errorf("expected ConnectionResponse packet, got %T (%d)", pk, pk.ID()) + } + switch connectionResponse.Response { + case packet2.ConnectionResponseUnsupportedProtocol: + return fmt.Errorf("unsupported protocol version %d", ProtocolVersion) + } + err = conn.WritePacket(&packet2.PlayerIdentity{ + IdentityData: conn.identityData, + ClientData: conn.clientData, + EnableClientCache: conn.enableClientCache, + Address: playerAddress, + }) + if err != nil { + return err + } + pk, err = conn.ReadPacket() + if err != nil { + return err + } + startGame, ok := pk.(*packet.StartGame) + if !ok { + return fmt.Errorf("expected StartGame packet, got %T (%d)", pk, pk.ID()) + } + conn.gameData = minecraft.GameData{ + Difficulty: startGame.Difficulty, + WorldName: startGame.WorldName, + EntityUniqueID: startGame.EntityUniqueID, + EntityRuntimeID: startGame.EntityRuntimeID, + PlayerGameMode: startGame.PlayerGameMode, + BaseGameVersion: startGame.BaseGameVersion, + PlayerPosition: startGame.PlayerPosition, + Pitch: startGame.Pitch, + Yaw: startGame.Yaw, + Dimension: startGame.Dimension, + WorldSpawn: startGame.WorldSpawn, + EditorWorld: startGame.EditorWorld, + GameRules: startGame.GameRules, + Time: startGame.Time, + ServerBlockStateChecksum: startGame.ServerBlockStateChecksum, + CustomBlocks: startGame.Blocks, + Items: startGame.Items, + PlayerMovementSettings: startGame.PlayerMovementSettings, + WorldGameMode: startGame.WorldGameMode, + ServerAuthoritativeInventory: startGame.ServerAuthoritativeInventory, + Experiments: startGame.Experiments, + } + return nil +} + +// identify attempts to identify the player attempting to connect to the server through the PlayerIdentity packet. An +// error is returned if it failed to identify. +func (conn *Conn) identify() error { + pk, err := conn.ReadPacket() + if err != nil { + return err + } + connectionRequest, ok := pk.(*packet2.ConnectionRequest) + if !ok { + return fmt.Errorf("expected ConnectionRequest packet, got %T (%d)", pk, pk.ID()) + } + response := packet2.ConnectionResponseSuccess + if connectionRequest.ProtocolVersion != ProtocolVersion { + response = packet2.ConnectionResponseUnsupportedProtocol + } + err = conn.WritePacket(&packet2.ConnectionResponse{ + Response: response, + }) + if err != nil { + return err + } + pk, err = conn.ReadPacket() + if err != nil { + return err + } + playerIdentity, ok := pk.(*packet2.PlayerIdentity) + if !ok { + return fmt.Errorf("expected PlayerIdentity packet, got %T (%d)", pk, pk.ID()) + } + conn.identityData = playerIdentity.IdentityData + conn.clientData = playerIdentity.ClientData + conn.enableClientCache = playerIdentity.EnableClientCache + return nil +} + +// Close ... +func (conn *Conn) Close() error { + return conn.conn.Close() +} + +// IdentityData ... +func (conn *Conn) IdentityData() login.IdentityData { + return conn.identityData +} + +// ClientData ... +func (conn *Conn) ClientData() login.ClientData { + return conn.clientData +} + +// ClientCacheEnabled ... +func (conn *Conn) ClientCacheEnabled() bool { + return conn.enableClientCache +} + +// ChunkRadius ... +func (conn *Conn) ChunkRadius() int { + //TODO implement me + return 8 +} + +// Latency ... +func (conn *Conn) Latency() time.Duration { + //TODO implement me + return time.Millisecond * 20 +} + +// Flush ... +func (conn *Conn) Flush() error { + return nil +} + +// RemoteAddr ... +func (conn *Conn) RemoteAddr() net.Addr { + return conn.conn.RemoteAddr() +} + +// GameData ... +func (conn *Conn) GameData() minecraft.GameData { + return conn.gameData +} + +// DoSpawnTimeout ... +func (conn *Conn) DoSpawnTimeout(timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + select { + case <-ctx.Done(): + return fmt.Errorf("spawn timeout") + case <-conn.spawn: + return nil + } +} + +// StartGameContext ... +func (conn *Conn) StartGameContext(_ context.Context, data minecraft.GameData) error { + for _, item := range data.Items { + if item.Name == "minecraft:shield" { + conn.shieldID.Store(int32(item.RuntimeID)) + break + } + } + return conn.WritePacket(&packet.StartGame{ + Difficulty: data.Difficulty, + EntityUniqueID: data.EntityUniqueID, + EntityRuntimeID: data.EntityRuntimeID, + PlayerGameMode: data.PlayerGameMode, + PlayerPosition: data.PlayerPosition, + Pitch: data.Pitch, + Yaw: data.Yaw, + Dimension: data.Dimension, + WorldSpawn: data.WorldSpawn, + EditorWorld: data.EditorWorld, + GameRules: data.GameRules, + Time: data.Time, + Blocks: data.CustomBlocks, + Items: data.Items, + AchievementsDisabled: true, + Generator: 1, + EducationFeaturesEnabled: true, + MultiPlayerGame: true, + MultiPlayerCorrelationID: uuid.Must(uuid.NewRandom()).String(), + CommandsEnabled: true, + WorldName: data.WorldName, + LANBroadcastEnabled: true, + PlayerMovementSettings: data.PlayerMovementSettings, + WorldGameMode: data.WorldGameMode, + ServerAuthoritativeInventory: data.ServerAuthoritativeInventory, + Experiments: data.Experiments, + BaseGameVersion: data.BaseGameVersion, + GameVersion: protocol.CurrentVersion, + }) +} + +// ReadPacket ... +func (conn *Conn) ReadPacket() (pk packet.Packet, err error) { + var l uint32 + if err := binary.Read(conn.conn, binary.LittleEndian, &l); err != nil { + return nil, err + } + + data := make([]byte, l) + read, err := conn.conn.Read(data) + if err != nil { + return nil, err + } + if read != int(l) { + return nil, fmt.Errorf("expected %v bytes, got %v", l, read) + } + + decoded, err := snappy.Decode(nil, data) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(decoded) + header := &packet.Header{} + if err := header.Read(buf); err != nil { + return nil, err + } + + pkFunc, ok := conn.pool[header.PacketID] + if !ok { + return nil, fmt.Errorf("unknown packet %v", header.PacketID) + } + + defer func() { + if recoveredErr := recover(); recoveredErr != nil { + err = fmt.Errorf("%T: %w", pk, recoveredErr.(error)) + } + }() + pk = pkFunc() + pk.Unmarshal(protocol.NewReader(buf, 0)) + if buf.Len() > 0 { + return nil, fmt.Errorf("still have %v bytes unread", buf.Len()) + } + + if _, ok := pk.(*packet.StartGame); ok { + close(conn.spawn) + } + + return pk, nil +} + +// WritePacket ... +func (conn *Conn) WritePacket(pk packet.Packet) error { + conn.sendMu.Lock() + conn.hdr.PacketID = pk.ID() + _ = conn.hdr.Write(conn.buf) + + pk.Marshal(protocol.NewWriter(conn.buf, conn.shieldID.Load())) + + data := conn.buf.Bytes() + conn.buf.Reset() + conn.sendMu.Unlock() + + encoded := snappy.Encode(nil, data) + + buf := bytes.NewBuffer(make([]byte, 0, 4+len(encoded))) + + if err := binary.Write(buf, binary.LittleEndian, int32(len(encoded))); err != nil { + return err + } + if _, err := buf.Write(encoded); err != nil { + return err + } + + if _, err := conn.conn.Write(buf.Bytes()); err != nil { + return err + } + + return nil +} diff --git a/session/tcpprotocol/dialer.go b/session/tcpprotocol/dialer.go new file mode 100644 index 0000000..39a4f81 --- /dev/null +++ b/session/tcpprotocol/dialer.go @@ -0,0 +1,50 @@ +package tcpprotocol + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol/login" + "net" +) + +// Dialer allows specifying specific settings for connection to a Minecraft server. The zero value of Dialer is used for +// the package level Dial function. +type Dialer struct { + // IdentityData is the identity data used to login to the server with. It includes the username, UUID and XUID of the + // player. The IdentityData object is obtained using Minecraft auth if Email and Password are set. If not, the object + // provided here is used, or a default one if left empty. + IdentityData login.IdentityData + // ClientData is the client data used to login to the server with. It includes fields such as the skin, locale and + // UUIDs unique to the client. If empty, a default is sent produced using defaultClientData(). + ClientData login.ClientData + // EnableClientCache, if set to true, enables the client blob cache for the client. This means that the server will + // send chunks as blobs, which may be saved by the client so that chunks don't have to be transmitted every time, + // resulting in less network transmission. + EnableClientCache bool +} + +// Dial dials a Minecraft connection to the address passed over the network passed. The network is typically "tcp". A +// Conn is returned which may be used to receive packets from and send packets to. A zero value of a Dialer struct is +// used to initiate the connection. A custom Dialer may be used to specify additional behaviour. +func Dial(network, address, playerAddress string) (*Conn, error) { + var d Dialer + return d.Dial(network, address, playerAddress) +} + +// Dial dials a Minecraft connection to the address passed over the network passed. The network is typically +// "raknet". A Conn is returned which may be used to receive packets from and send packets to. +func (d Dialer) Dial(network, address, playerAddress string) (*Conn, error) { + netConn, err := net.Dial(network, address) + if err != nil { + return nil, err + } + + conn := newConn(netConn) + conn.identityData = d.IdentityData + conn.clientData = d.ClientData + conn.enableClientCache = d.EnableClientCache + + err = conn.login(playerAddress) + if err != nil { + return nil, err + } + return conn, nil +} diff --git a/session/tcpprotocol/listener.go b/session/tcpprotocol/listener.go new file mode 100644 index 0000000..877a38d --- /dev/null +++ b/session/tcpprotocol/listener.go @@ -0,0 +1,56 @@ +package tcpprotocol + +import ( + "github.com/df-mc/dragonfly/server/session" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" + "net" +) + +// Listener implements a Minecraft listener on top of an unspecific net.Listener. It abstracts away the login sequence +// of connecting clients and provides the implements the net.Listener interface to provide a consistent API. +type Listener struct { + listener net.Listener +} + +// Listen announces on the local network address. The network must be "tcp", "tcp4", "tcp6", "unix", "unixpacket" +// A Listener is returned which may be used to accept connections. If the host in the address parameter is empty or a +// literal unspecified IP address, Listen listens on all available unicast and anycast IP addresses of the local system. +func Listen(network, address string) (*Listener, error) { + l, err := net.Listen(network, address) + if err != nil { + return nil, err + } + listener := &Listener{ + listener: l, + } + return listener, nil +} + +// Accept ... +func (l *Listener) Accept() (session.Conn, error) { + netConn, err := l.listener.Accept() + if err != nil { + return nil, err + } + + conn := newConn(netConn) + err = conn.identify() + if err != nil { + return nil, err + } + return conn, nil +} + +// Disconnect ... +func (l *Listener) Disconnect(conn session.Conn, message string) error { + _ = conn.WritePacket(&packet.Disconnect{ + HideDisconnectionScreen: message == "", + Message: message, + }) + return conn.Close() +} + +// Close ... +func (l *Listener) Close() error { + return l.listener.Close() +} diff --git a/session/tcpprotocol/protocol.go b/session/tcpprotocol/protocol.go new file mode 100644 index 0000000..86d64ab --- /dev/null +++ b/session/tcpprotocol/protocol.go @@ -0,0 +1,16 @@ +package tcpprotocol + +import ( + packet2 "github.com/paroxity/portal/session/tcpprotocol/packet" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" +) + +// ProtocolVersion is the current supported version of the protocol. If a server is using an outdated version of the +// protocol, players will be unable to connect. This constant gets updated every time the protocol is changed. +const ProtocolVersion = 1 + +func init() { + packet.Register(packet2.IDPlayerIdentity, func() packet.Packet { return &packet2.PlayerIdentity{} }) + packet.Register(packet2.IDConnectionRequest, func() packet.Packet { return &packet2.ConnectionRequest{} }) + packet.Register(packet2.IDConnectionResponse, func() packet.Packet { return &packet2.ConnectionResponse{} }) +} From 644fd4107048b97e5469e881072cab85317d1a3d Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sun, 18 Sep 2022 10:16:40 +0100 Subject: [PATCH 4/6] tcpprotocol/conn.go: Kind of clean up --- session/tcpprotocol/conn.go | 41 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/session/tcpprotocol/conn.go b/session/tcpprotocol/conn.go index 19f7d28..6f7069e 100644 --- a/session/tcpprotocol/conn.go +++ b/session/tcpprotocol/conn.go @@ -59,18 +59,16 @@ func (conn *Conn) login(playerAddress string) error { if err != nil { return err } - pk, err := conn.ReadPacket() + + connectionResponse, err := expect[*packet2.ConnectionResponse](conn) if err != nil { return err } - connectionResponse, ok := pk.(*packet2.ConnectionResponse) - if !ok { - return fmt.Errorf("expected ConnectionResponse packet, got %T (%d)", pk, pk.ID()) - } switch connectionResponse.Response { case packet2.ConnectionResponseUnsupportedProtocol: return fmt.Errorf("unsupported protocol version %d", ProtocolVersion) } + err = conn.WritePacket(&packet2.PlayerIdentity{ IdentityData: conn.identityData, ClientData: conn.clientData, @@ -80,14 +78,11 @@ func (conn *Conn) login(playerAddress string) error { if err != nil { return err } - pk, err = conn.ReadPacket() + + startGame, err := expect[*packet.StartGame](conn) if err != nil { return err } - startGame, ok := pk.(*packet.StartGame) - if !ok { - return fmt.Errorf("expected StartGame packet, got %T (%d)", pk, pk.ID()) - } conn.gameData = minecraft.GameData{ Difficulty: startGame.Difficulty, WorldName: startGame.WorldName, @@ -117,14 +112,11 @@ func (conn *Conn) login(playerAddress string) error { // identify attempts to identify the player attempting to connect to the server through the PlayerIdentity packet. An // error is returned if it failed to identify. func (conn *Conn) identify() error { - pk, err := conn.ReadPacket() + connectionRequest, err := expect[*packet2.ConnectionRequest](conn) if err != nil { return err } - connectionRequest, ok := pk.(*packet2.ConnectionRequest) - if !ok { - return fmt.Errorf("expected ConnectionRequest packet, got %T (%d)", pk, pk.ID()) - } + response := packet2.ConnectionResponseSuccess if connectionRequest.ProtocolVersion != ProtocolVersion { response = packet2.ConnectionResponseUnsupportedProtocol @@ -135,14 +127,11 @@ func (conn *Conn) identify() error { if err != nil { return err } - pk, err = conn.ReadPacket() + + playerIdentity, err := expect[*packet2.PlayerIdentity](conn) if err != nil { return err } - playerIdentity, ok := pk.(*packet2.PlayerIdentity) - if !ok { - return fmt.Errorf("expected PlayerIdentity packet, got %T (%d)", pk, pk.ID()) - } conn.identityData = playerIdentity.IdentityData conn.clientData = playerIdentity.ClientData conn.enableClientCache = playerIdentity.EnableClientCache @@ -327,3 +316,15 @@ func (conn *Conn) WritePacket(pk packet.Packet) error { return nil } + +func expect[T packet.Packet](conn *Conn) (T, error) { + pk, err := conn.ReadPacket() + if err != nil { + return nil, err + } + t, ok := pk.(T) + if !ok { + return nil, fmt.Errorf("received unexpected packet %T", pk) + } + return t, nil +} From f82af1fef86cf22157dce3727a71d21800cf7ce3 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Sat, 7 Jan 2023 17:10:54 +0000 Subject: [PATCH 5/6] portal: fix go.mod and go.sum --- go.mod | 3 ++- go.sum | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 6e13041..172d268 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/df-mc/dragonfly v0.8.3 github.com/go-gl/mathgl v1.0.0 github.com/google/uuid v1.3.0 + github.com/klauspost/compress v1.15.13 github.com/mattn/go-colorable v0.1.11 github.com/sandertv/gophertunnel v1.26.0 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e @@ -19,11 +20,11 @@ require ( github.com/df-mc/atomic v1.10.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/klauspost/compress v1.15.13 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect github.com/sandertv/go-raknet v1.12.0 // indirect golang.org/x/crypto v0.4.0 // indirect + golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 // indirect golang.org/x/image v0.2.0 // indirect golang.org/x/net v0.4.0 // indirect golang.org/x/oauth2 v0.3.0 // indirect diff --git a/go.sum b/go.sum index 0555bb9..65a6f46 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,16 @@ +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0= +github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg= github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc= +github.com/df-mc/dragonfly v0.8.3 h1:IyffRhmaqyZ9s0/XrxMmr5tGyUPsmeUi+iapP0T+Qok= +github.com/df-mc/dragonfly v0.8.3/go.mod h1:ObfYlB77fxGLqU2CLquvk8ibAEMYoixiXfs7pxrOGCI= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= @@ -35,6 +43,8 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvv github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -45,6 +55,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 h1:x03zeu7B2B11ySp+daztnwM5oBJ/8wGUSqrwcw9L0RA= +golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ= golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI= @@ -93,4 +105,3 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 48fdc800b5c8af8bc79a0db11a33f78cccc047d1 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC Date: Fri, 10 Feb 2023 20:18:42 +0000 Subject: [PATCH 6/6] tcpprotocol/conn.go: Fix expect errors --- go.mod | 9 +++-- go.sum | 21 ++++++++--- session/tcpprotocol/conn.go | 71 +++++++++++++++++++------------------ 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index 491b544..372508c 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/paroxity/portal go 1.18 require ( + github.com/df-mc/dragonfly v0.9.2 github.com/go-gl/mathgl v1.0.0 github.com/google/uuid v1.3.0 + github.com/klauspost/compress v1.15.15 github.com/mattn/go-colorable v0.1.11 github.com/sandertv/gophertunnel v1.27.2 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e @@ -13,21 +15,22 @@ require ( ) require ( + github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect + github.com/cespare/xxhash v1.1.0 // indirect github.com/df-mc/atomic v1.10.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/klauspost/compress v1.15.13 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect github.com/sandertv/go-raknet v1.12.0 // indirect golang.org/x/crypto v0.5.0 // indirect + golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/image v0.3.0 // indirect golang.org/x/net v0.5.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.4.0 // indirect + golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index e92e505..6bf071e 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,16 @@ +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0= +github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg= github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc= +github.com/df-mc/dragonfly v0.9.2 h1:VoffCnFiiOd03P5X+ZsbuZkSuNELYh1Zc51v+0A/3HA= +github.com/df-mc/dragonfly v0.9.2/go.mod h1:hGGjGbLxpcn7nVTZOrk8kPfeGGntaMOqqcbuugZauyI= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= @@ -17,8 +25,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= -github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -35,6 +43,8 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvv github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -45,6 +55,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= @@ -67,8 +79,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -93,4 +105,3 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/session/tcpprotocol/conn.go b/session/tcpprotocol/conn.go index 6f7069e..cca8d6b 100644 --- a/session/tcpprotocol/conn.go +++ b/session/tcpprotocol/conn.go @@ -60,11 +60,11 @@ func (conn *Conn) login(playerAddress string) error { return err } - connectionResponse, err := expect[*packet2.ConnectionResponse](conn) + connectionResponse, err := expect(conn, packet2.IDConnectionResponse) if err != nil { return err } - switch connectionResponse.Response { + switch connectionResponse.(*packet2.ConnectionResponse).Response { case packet2.ConnectionResponseUnsupportedProtocol: return fmt.Errorf("unsupported protocol version %d", ProtocolVersion) } @@ -79,32 +79,33 @@ func (conn *Conn) login(playerAddress string) error { return err } - startGame, err := expect[*packet.StartGame](conn) + startGame, err := expect(conn, packet.IDStartGame) if err != nil { return err } + startGamePacket := startGame.(*packet.StartGame) conn.gameData = minecraft.GameData{ - Difficulty: startGame.Difficulty, - WorldName: startGame.WorldName, - EntityUniqueID: startGame.EntityUniqueID, - EntityRuntimeID: startGame.EntityRuntimeID, - PlayerGameMode: startGame.PlayerGameMode, - BaseGameVersion: startGame.BaseGameVersion, - PlayerPosition: startGame.PlayerPosition, - Pitch: startGame.Pitch, - Yaw: startGame.Yaw, - Dimension: startGame.Dimension, - WorldSpawn: startGame.WorldSpawn, - EditorWorld: startGame.EditorWorld, - GameRules: startGame.GameRules, - Time: startGame.Time, - ServerBlockStateChecksum: startGame.ServerBlockStateChecksum, - CustomBlocks: startGame.Blocks, - Items: startGame.Items, - PlayerMovementSettings: startGame.PlayerMovementSettings, - WorldGameMode: startGame.WorldGameMode, - ServerAuthoritativeInventory: startGame.ServerAuthoritativeInventory, - Experiments: startGame.Experiments, + Difficulty: startGamePacket.Difficulty, + WorldName: startGamePacket.WorldName, + EntityUniqueID: startGamePacket.EntityUniqueID, + EntityRuntimeID: startGamePacket.EntityRuntimeID, + PlayerGameMode: startGamePacket.PlayerGameMode, + BaseGameVersion: startGamePacket.BaseGameVersion, + PlayerPosition: startGamePacket.PlayerPosition, + Pitch: startGamePacket.Pitch, + Yaw: startGamePacket.Yaw, + Dimension: startGamePacket.Dimension, + WorldSpawn: startGamePacket.WorldSpawn, + EditorWorld: startGamePacket.EditorWorld, + GameRules: startGamePacket.GameRules, + Time: startGamePacket.Time, + ServerBlockStateChecksum: startGamePacket.ServerBlockStateChecksum, + CustomBlocks: startGamePacket.Blocks, + Items: startGamePacket.Items, + PlayerMovementSettings: startGamePacket.PlayerMovementSettings, + WorldGameMode: startGamePacket.WorldGameMode, + ServerAuthoritativeInventory: startGamePacket.ServerAuthoritativeInventory, + Experiments: startGamePacket.Experiments, } return nil } @@ -112,13 +113,13 @@ func (conn *Conn) login(playerAddress string) error { // identify attempts to identify the player attempting to connect to the server through the PlayerIdentity packet. An // error is returned if it failed to identify. func (conn *Conn) identify() error { - connectionRequest, err := expect[*packet2.ConnectionRequest](conn) + connectionRequest, err := expect(conn, packet2.IDConnectionRequest) if err != nil { return err } response := packet2.ConnectionResponseSuccess - if connectionRequest.ProtocolVersion != ProtocolVersion { + if connectionRequest.(*packet2.ConnectionRequest).ProtocolVersion != ProtocolVersion { response = packet2.ConnectionResponseUnsupportedProtocol } err = conn.WritePacket(&packet2.ConnectionResponse{ @@ -128,13 +129,14 @@ func (conn *Conn) identify() error { return err } - playerIdentity, err := expect[*packet2.PlayerIdentity](conn) + playerIdentity, err := expect(conn, packet2.IDPlayerIdentity) if err != nil { return err } - conn.identityData = playerIdentity.IdentityData - conn.clientData = playerIdentity.ClientData - conn.enableClientCache = playerIdentity.EnableClientCache + playerIdentityPacket := playerIdentity.(*packet2.PlayerIdentity) + conn.identityData = playerIdentityPacket.IdentityData + conn.clientData = playerIdentityPacket.ClientData + conn.enableClientCache = playerIdentityPacket.EnableClientCache return nil } @@ -317,14 +319,13 @@ func (conn *Conn) WritePacket(pk packet.Packet) error { return nil } -func expect[T packet.Packet](conn *Conn) (T, error) { +func expect(conn *Conn, expectedID uint32) (packet.Packet, error) { pk, err := conn.ReadPacket() if err != nil { - return nil, err + return pk, err } - t, ok := pk.(T) - if !ok { + if pk.ID() != expectedID { return nil, fmt.Errorf("received unexpected packet %T", pk) } - return t, nil + return pk, nil }