Skip to content

Commit

Permalink
Initial support for v1.19.30 (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustTalDevelops committed Sep 20, 2022
1 parent 11dcfd6 commit 9431d8f
Show file tree
Hide file tree
Showing 33 changed files with 680 additions and 211 deletions.
3 changes: 2 additions & 1 deletion go.mod
Expand Up @@ -4,11 +4,12 @@ go 1.18

require (
github.com/go-gl/mathgl v1.0.0
github.com/golang/snappy v0.0.4
github.com/google/uuid v1.3.0
github.com/klauspost/compress v1.15.1
github.com/muhammadmuzzammil1998/jsonc v1.0.0
github.com/pelletier/go-toml v1.9.4
github.com/sandertv/go-raknet v1.11.1
github.com/sandertv/go-raknet v1.12.0
go.uber.org/atomic v1.9.0
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Expand Up @@ -78,6 +78,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
Expand Down Expand Up @@ -121,8 +123,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sandertv/go-raknet v1.11.1 h1:0auvhHoZNyC/Z1l5xqniE3JE+w3MGO3n3JXEGHzdlRE=
github.com/sandertv/go-raknet v1.11.1/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y=
github.com/sandertv/go-raknet v1.12.0 h1:olUzZlIJyX/pgj/mrsLCZYjKLNDsYiWdvQ4NIm3z0DA=
github.com/sandertv/go-raknet v1.12.0/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
Expand Down
17 changes: 0 additions & 17 deletions internal/pool.go
Expand Up @@ -2,26 +2,9 @@ package internal

import (
"bytes"
"github.com/klauspost/compress/flate"
"io"
"sync"
)

// DecompressPool is a sync.Pool for io.ReadCloser flate readers. These are pooled for connections.
var DecompressPool = sync.Pool{
New: func() any {
return flate.NewReader(bytes.NewReader(nil))
},
}

// CompressPool is a sync.Pool for writeCloseResetter flate readers. These are pooled for connections.
var CompressPool = sync.Pool{
New: func() any {
w, _ := flate.NewWriter(io.Discard, 6)
return w
},
}

// BufferPool is a sync.Pool for buffers used to write compressed data to.
var BufferPool = sync.Pool{
New: func() any {
Expand Down
78 changes: 54 additions & 24 deletions minecraft/conn.go
Expand Up @@ -93,6 +93,9 @@ type Conn struct {
bufferedSend [][]byte
hdr *packet.Header

// readyToLogin is a bool indicating if the connection is ready to login. This is used to ensure that the client
// has received the relevant network settings before the login sequence starts.
readyToLogin bool
// loggedIn is a bool indicating if the connection was logged in. It is set to true after the entire login
// sequence is completed.
loggedIn bool
Expand Down Expand Up @@ -148,7 +151,7 @@ func newConn(netConn net.Conn, key *ecdsa.PrivateKey, log *log.Logger) *Conn {
log: log,
hdr: &packet.Header{},
}
conn.expectedIDs.Store([]uint32{packet.IDLogin})
conn.expectedIDs.Store([]uint32{packet.IDRequestNetworkSettings})
_, _ = rand.Read(conn.salt)

go func() {
Expand Down Expand Up @@ -602,6 +605,8 @@ func (conn *Conn) handlePacket(pk packet.Packet) error {
}()
switch pk := pk.(type) {
// Internal packets destined for the server.
case *packet.RequestNetworkSettings:
return conn.handleRequestNetworkSettings(pk)
case *packet.Login:
return conn.handleLogin(pk)
case *packet.ClientToServerHandshake:
Expand All @@ -618,6 +623,8 @@ func (conn *Conn) handlePacket(pk packet.Packet) error {
return conn.handleSetLocalPlayerAsInitialised(pk)

// Internal packets destined for the client.
case *packet.NetworkSettings:
return conn.handleNetworkSettings(pk)
case *packet.ServerToClientHandshake:
return conn.handleServerToClientHandshake(pk)
case *packet.PlayStatus:
Expand All @@ -638,26 +645,9 @@ func (conn *Conn) handlePacket(pk packet.Packet) error {
return nil
}

// handleLogin handles an incoming login packet. It verifies an decodes the login request found in the packet
// and returns an error if it couldn't be done successfully.
func (conn *Conn) handleLogin(pk *packet.Login) error {
// The next expected packet is a response from the client to the handshake.
conn.expect(packet.IDClientToServerHandshake)
var (
err error
authResult login.AuthResult
)
conn.identityData, conn.clientData, authResult, err = login.Parse(pk.ConnectionRequest)
if err != nil {
return fmt.Errorf("parse login request: %w", err)
}

// Make sure the player is logged in with XBOX Live when necessary.
if !authResult.XBOXLiveAuthenticated && conn.authEnabled {
_ = conn.WritePacket(&packet.Disconnect{Message: text.Colourf("<red>You must be logged in with XBOX Live to join.</red>")})
return fmt.Errorf("connection %v was not authenticated to XBOX Live", conn.RemoteAddr())
}

// handleRequestNetworkSettings handles an incoming RequestNetworkSettings packet. It returns an error if the protocol
// version is not supported, otherwise sending back a NetworkSettings packet.
func (conn *Conn) handleRequestNetworkSettings(pk *packet.RequestNetworkSettings) error {
found := false
for _, pro := range conn.acceptedProto {
if pro.ID() == pk.ClientProtocol {
Expand All @@ -676,6 +666,47 @@ func (conn *Conn) handleLogin(pk *packet.Login) error {
_ = conn.WritePacket(&packet.PlayStatus{Status: status})
return fmt.Errorf("%v connected with an incompatible protocol: expected protocol = %v, client protocol = %v", conn.identityData.DisplayName, protocol.CurrentProtocol, pk.ClientProtocol)
}

conn.expect(packet.IDLogin)
if err := conn.WritePacket(&packet.NetworkSettings{
CompressionThreshold: 512,
CompressionAlgorithm: packet.SnappyCompression{},
}); err != nil {
return fmt.Errorf("error sending network settings: %v", err)
}
_ = conn.Flush()
conn.enc.EnableCompression(packet.SnappyCompression{})
conn.dec.EnableCompression(packet.SnappyCompression{})
return nil
}

// handleNetworkSettings handles an incoming NetworkSettings packet, enabling compression for future packets.
func (conn *Conn) handleNetworkSettings(pk *packet.NetworkSettings) error {
conn.enc.EnableCompression(pk.CompressionAlgorithm)
conn.dec.EnableCompression(pk.CompressionAlgorithm)
conn.readyToLogin = true
return nil
}

// handleLogin handles an incoming login packet. It verifies and decodes the login request found in the packet
// and returns an error if it couldn't be done successfully.
func (conn *Conn) handleLogin(pk *packet.Login) error {
// The next expected packet is a response from the client to the handshake.
conn.expect(packet.IDClientToServerHandshake)
var (
err error
authResult login.AuthResult
)
conn.identityData, conn.clientData, authResult, err = login.Parse(pk.ConnectionRequest)
if err != nil {
return fmt.Errorf("parse login request: %w", err)
}

// Make sure the player is logged in with XBOX Live when necessary.
if !authResult.XBOXLiveAuthenticated && conn.authEnabled {
_ = conn.WritePacket(&packet.Disconnect{Message: text.Colourf("<red>You must be logged in with XBOX Live to join.</red>")})
return fmt.Errorf("connection %v was not authenticated to XBOX Live", conn.RemoteAddr())
}
if err := conn.enableEncryption(authResult.PublicKey); err != nil {
return fmt.Errorf("error enabling encryption: %v", err)
}
Expand All @@ -686,9 +717,6 @@ func (conn *Conn) handleLogin(pk *packet.Login) error {
func (conn *Conn) handleClientToServerHandshake() error {
// The next expected packet is a resource pack client response.
conn.expect(packet.IDResourcePackClientResponse, packet.IDClientCacheStatus)
if err := conn.WritePacket(&packet.NetworkSettings{CompressionThreshold: 512}); err != nil {
return fmt.Errorf("error sending network settings: %v", err)
}
if err := conn.WritePacket(&packet.PlayStatus{Status: packet.PlayStatusLoginSuccess}); err != nil {
return fmt.Errorf("error sending play status login success: %v", err)
}
Expand Down Expand Up @@ -1129,6 +1157,7 @@ func (conn *Conn) handleStartGame(pk *packet.StartGame) error {
conn.gameData = GameData{
Difficulty: pk.Difficulty,
WorldName: pk.WorldName,
WorldSeed: pk.WorldSeed,
EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode,
Expand All @@ -1151,6 +1180,7 @@ func (conn *Conn) handleStartGame(pk *packet.StartGame) error {
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
ChatRestrictionLevel: pk.ChatRestrictionLevel,
DisablePlayerInteractions: pk.DisablePlayerInteractions,
ClientSideGeneration: pk.ClientSideGeneration,
Experiments: pk.Experiments,
}
for _, item := range pk.Items {
Expand Down
43 changes: 32 additions & 11 deletions minecraft/dial.go
Expand Up @@ -187,28 +187,44 @@ func (d Dialer) DialContext(ctx context.Context, network, address string) (conn
// we are not aware of the identity data ourselves yet.
conn.identityData = identityData
}
c := make(chan struct{})
go listenConn(conn, d.ErrorLog, c)

conn.expect(packet.IDServerToClientHandshake, packet.IDPlayStatus)
if err := conn.WritePacket(&packet.Login{ConnectionRequest: request, ClientProtocol: d.Protocol.ID()}); err != nil {
l, c := make(chan struct{}), make(chan struct{})
go listenConn(conn, d.ErrorLog, l, c)

conn.expect(packet.IDNetworkSettings)
if err := conn.WritePacket(&packet.RequestNetworkSettings{ClientProtocol: d.Protocol.ID()}); err != nil {
return nil, err
}
_ = conn.Flush()

select {
case <-conn.close:
return nil, conn.closeErr("dial")
case <-ctx.Done():
return nil, conn.wrap(ctx.Err(), "dial")
case <-c:
// We've connected successfully. We return the connection and no error.
return conn, nil
case <-l:
// We've received our network settings, so we can now send our login request.
conn.expect(packet.IDServerToClientHandshake, packet.IDPlayStatus)
if err := conn.WritePacket(&packet.Login{ConnectionRequest: request, ClientProtocol: d.Protocol.ID()}); err != nil {
return nil, err
}
_ = conn.Flush()

select {
case <-conn.close:
return nil, conn.closeErr("dial")
case <-ctx.Done():
return nil, conn.wrap(ctx.Err(), "dial")
case <-c:
// We've connected successfully. We return the connection and no error.
return conn, nil
}
}
}

// listenConn listens on the connection until it is closed on another goroutine. The channel passed will
// receive a value once the connection is logged in.
func listenConn(conn *Conn, logger *log.Logger, c chan struct{}) {
func listenConn(conn *Conn, logger *log.Logger, l, c chan struct{}) {
defer func() {
_ = conn.Close()
}()
Expand All @@ -223,14 +239,19 @@ func listenConn(conn *Conn, logger *log.Logger, c chan struct{}) {
return
}
for _, data := range packets {
loggedInBefore := conn.loggedIn
loggedInBefore, readyToLoginBefore := conn.loggedIn, conn.readyToLogin
if err := conn.receive(data); err != nil {
logger.Printf("error: %v", err)
return
}
if !readyToLoginBefore && conn.readyToLogin {
// This is the signal that the connection is ready to login, so we put a value in the channel so that
// it may be detected.
l <- struct{}{}
}
if !loggedInBefore && conn.loggedIn {
// This is the signal that the connection was considered logged in, so we put a value in the
// channel so that it may be detected.
// This is the signal that the connection was considered logged in, so we put a value in the channel so
// that it may be detected.
c <- struct{}{}
}
}
Expand Down
23 changes: 23 additions & 0 deletions minecraft/protocol/ability.go
Expand Up @@ -34,6 +34,29 @@ const (
AbilityBaseWalkSpeed = 0.1
)

// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
type AbilityData struct {
// EntityUniqueID is a unique identifier of the player. It appears it is not required to fill this field
// out with a correct value. Simply writing 0 seems to work.
EntityUniqueID int64
// PlayerPermissions is the permission level of the player as it shows up in the player list built up using
// the PlayerList packet.
PlayerPermissions byte
// CommandPermissions is a set of permissions that specify what commands a player is allowed to execute.
CommandPermissions byte
// Layers contains all ability layers and their potential values. This should at least have one entry, being the
// base layer.
Layers []AbilityLayer
}

// Marshal encodes/decodes an AbilityData.
func (x *AbilityData) Marshal(r IO) {
r.Int64(&x.EntityUniqueID)
r.Uint8(&x.PlayerPermissions)
r.Uint8(&x.CommandPermissions)
SliceUint8Length(r, &x.Layers)
}

// AbilityLayer represents the abilities of a specific layer, such as the base layer or the spectator layer.
type AbilityLayer struct {
// Type represents the type of the layer. This is one of the AbilityLayerType constants defined above.
Expand Down
34 changes: 19 additions & 15 deletions minecraft/protocol/command.go
Expand Up @@ -42,21 +42,25 @@ const (
CommandArgSuffixed = 0x1000000
CommandArgSoftEnum = 0x4000000

CommandArgTypeInt = 1
CommandArgTypeFloat = 3
CommandArgTypeValue = 4
CommandArgTypeWildcardInt = 5
CommandArgTypeOperator = 6
CommandArgTypeTarget = 8
CommandArgTypeWildcardTarget = 10
CommandArgTypeFilepath = 17
CommandArgTypeString = 39
CommandArgTypeBlockPosition = 47
CommandArgTypePosition = 48
CommandArgTypeMessage = 51
CommandArgTypeRawText = 53
CommandArgTypeJSON = 57
CommandArgTypeCommand = 70
CommandArgTypeInt = 1
CommandArgTypeFloat = 3
CommandArgTypeValue = 4
CommandArgTypeWildcardInt = 5
CommandArgTypeOperator = 6
CommandArgTypeCompareOperator = 7
CommandArgTypeTarget = 8
CommandArgTypeWildcardTarget = 10
CommandArgTypeFilepath = 17
CommandArgTypeIntegerRange = 23
CommandArgTypeEquipmentSlots = 38
CommandArgTypeString = 39
CommandArgTypeBlockPosition = 47
CommandArgTypePosition = 48
CommandArgTypeMessage = 51
CommandArgTypeRawText = 53
CommandArgTypeJSON = 57
CommandArgTypeBlockStates = 67
CommandArgTypeCommand = 70
)
const (
// ParamOptionCollapseEnum specifies if the enum (only if the Type is actually an enum type. If not,
Expand Down
4 changes: 2 additions & 2 deletions minecraft/protocol/info.go
Expand Up @@ -2,7 +2,7 @@ package protocol

const (
// CurrentProtocol is the current protocol version for the version below.
CurrentProtocol = 545
CurrentProtocol = 554
// CurrentVersion is the current version of Minecraft as supported by the `packet` package.
CurrentVersion = "1.19.21"
CurrentVersion = "1.19.30"
)
1 change: 1 addition & 0 deletions minecraft/protocol/io.go
Expand Up @@ -45,6 +45,7 @@ type IO interface {
EntityMetadata(x *map[uint32]any)
Item(x *ItemStack)
ItemInstance(i *ItemInstance)
ItemDescriptorCount(i *ItemDescriptorCount)
MaterialReducer(x *MaterialReducer)
GameRule(x *GameRule)

Expand Down
22 changes: 0 additions & 22 deletions minecraft/protocol/item.go
Expand Up @@ -45,28 +45,6 @@ type ItemType struct {
MetadataValue uint32
}

// RecipeIngredientItem represents an item that may be used as a recipe ingredient.
type RecipeIngredientItem struct {
// NetworkID is the numerical network ID of the item. This is sometimes a positive ID, and sometimes a
// negative ID, depending on what item it concerns.
NetworkID int32
// MetadataValue is the metadata value of the item. For some items, this is the damage value, whereas for
// other items it is simply an identifier of a variant of the item.
MetadataValue int32
// Count is the count of items that the recipe ingredient is required to have.
Count int32
}

// Marshal encodes/decodes a RecipeIngredientItem.
func (x *RecipeIngredientItem) Marshal(r IO) {
r.Varint32(&x.NetworkID)
if x.NetworkID == 0 {
return
}
r.Varint32(&x.MetadataValue)
r.Varint32(&x.Count)
}

// ItemEntry is an item sent in the StartGame item table. It holds a name and a legacy ID, which is used to
// point back to that name.
type ItemEntry struct {
Expand Down

0 comments on commit 9431d8f

Please sign in to comment.