Add support for protocol version 800 (1.21.80)

Introduced support for protocol 800, including biome definitions, camera presets, and player list updates. Upgraded dependencies, adjusted mappings, and enhanced the README to reflect recent changes.
This commit is contained in:
AkmalFairuz
2025-05-11 21:32:37 +07:00
parent bcb3e990d3
commit 8a209b75bf
14 changed files with 9454 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ import "github.com/sandertv/gophertunnel/minecraft"
// All returns a slice of all legacy protocol versions that are supported.
func All() []minecraft.Protocol {
return []minecraft.Protocol{
New786(),
New776(),
New766(),
New748(),

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
package legacypacket
import (
"encoding/base64"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/samber/lo"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// BiomeDefinitionList is sent by the server to let the client know all biomes that are available and
// implemented on the server side. When enabled, it also includes information for the client to
// accurately recreate the server-side generation in vanilla worlds/servers for increased performance.
type BiomeDefinitionList struct {
// BiomeDefinitions is a list of biomes that are available on the server.
BiomeDefinitions []protocol.BiomeDefinition
// StringList is a makeshift dictionary implementation Mojang created to try and reduce the size of the
// overall packet. It is a list of common strings that are used in the biome definitions, such as
// biome names, float values or query expressions.
StringList []string
}
// ID ...
func (*BiomeDefinitionList) ID() uint32 {
return packet.IDBiomeDefinitionList
}
func (pk *BiomeDefinitionList) Marshal(io protocol.IO) {
if proto.IsProtoGTE(io, proto.ID800) {
protocol.Slice(io, &pk.BiomeDefinitions)
protocol.FuncSlice(io, &pk.StringList, io.String)
} else {
io.Bytes(lo.ToPtr(append([]byte(nil), rawOldSerializedBiomeDefinitionList...)))
}
}
var rawOldSerializedBiomeDefinitionList []byte
func init() {
const s = `CgAKDWJhbWJvb19qdW5nbGUFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoTYmFtYm9vX2p1bmdsZV9oaWxscwUIZG93bmZhbGxmZmY/BQt0ZW1wZXJhdHVyZTMzcz8ACgViZWFjaAUIZG93bmZhbGzNzMw+BQt0ZW1wZXJhdHVyZc3MTD8ACgxiaXJjaF9mb3Jlc3QFCGRvd25mYWxsmpkZPwULdGVtcGVyYXR1cmWamRk/AAoSYmlyY2hfZm9yZXN0X2hpbGxzBQhkb3duZmFsbJqZGT8FC3RlbXBlcmF0dXJlmpkZPwAKGmJpcmNoX2ZvcmVzdF9oaWxsc19tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKFGJpcmNoX2ZvcmVzdF9tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKCmNvbGRfYmVhY2gFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw9AAoKY29sZF9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgpjb2xkX3RhaWdhBQhkb3duZmFsbM3MzD4FC3RlbXBlcmF0dXJlAAAAvwAKEGNvbGRfdGFpZ2FfaGlsbHMFCGRvd25mYWxszczMPgULdGVtcGVyYXR1cmUAAAC/AAoSY29sZF90YWlnYV9tdXRhdGVkBQhkb3duZmFsbM3MzD4FC3RlbXBlcmF0dXJlAAAAvwAKD2RlZXBfY29sZF9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8AChFkZWVwX2Zyb3plbl9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAAChNkZWVwX2x1a2V3YXJtX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKCmRlZXBfb2NlYW4FCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAA/AAoPZGVlcF93YXJtX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKBmRlc2VydAUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAACgxkZXNlcnRfaGlsbHMFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoOZGVzZXJ0X211dGF0ZWQFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoNZXh0cmVtZV9oaWxscwUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4AChJleHRyZW1lX2hpbGxzX2VkZ2UFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw+AAoVZXh0cmVtZV9oaWxsc19tdXRhdGVkBQhkb3duZmFsbJqZmT4FC3RlbXBlcmF0dXJlzcxMPgAKGGV4dHJlbWVfaGlsbHNfcGx1c190cmVlcwUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4ACiBleHRyZW1lX2hpbGxzX3BsdXNfdHJlZXNfbXV0YXRlZAUIZG93bmZhbGyamZk+BQt0ZW1wZXJhdHVyZc3MTD4ACg1mbG93ZXJfZm9yZXN0BQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKBmZvcmVzdAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzMz8ACgxmb3Jlc3RfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUzMzM/AAoMZnJvemVuX29jZWFuBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAAAAKDGZyb3plbl9yaXZlcgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAACgRoZWxsBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlAAAAQAAKDWljZV9tb3VudGFpbnMFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAAAAAoKaWNlX3BsYWlucwUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAAChFpY2VfcGxhaW5zX3NwaWtlcwUIZG93bmZhbGwAAIA/BQt0ZW1wZXJhdHVyZQAAAAAACgZqdW5nbGUFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoLanVuZ2xlX2VkZ2UFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUzM3M/AAoTanVuZ2xlX2VkZ2VfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzcz8ACgxqdW5nbGVfaGlsbHMFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoOanVuZ2xlX211dGF0ZWQFCGRvd25mYWxsZmZmPwULdGVtcGVyYXR1cmUzM3M/AAoTbGVnYWN5X2Zyb3plbl9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAAAACg5sdWtld2FybV9vY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgptZWdhX3RhaWdhBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlmpmZPgAKEG1lZ2FfdGFpZ2FfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmWamZk+AAoEbWVzYQUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAACgptZXNhX2JyeWNlBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlAAAAQAAKDG1lc2FfcGxhdGVhdQUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAAChRtZXNhX3BsYXRlYXVfbXV0YXRlZAUIZG93bmZhbGwAAAAABQt0ZW1wZXJhdHVyZQAAAEAAChJtZXNhX3BsYXRlYXVfc3RvbmUFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoabWVzYV9wbGF0ZWF1X3N0b25lX211dGF0ZWQFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAABAAAoPbXVzaHJvb21faXNsYW5kBQhkb3duZmFsbAAAgD8FC3RlbXBlcmF0dXJlZmZmPwAKFW11c2hyb29tX2lzbGFuZF9zaG9yZQUIZG93bmZhbGwAAIA/BQt0ZW1wZXJhdHVyZWZmZj8ACgVvY2VhbgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACgZwbGFpbnMFCGRvd25mYWxszczMPgULdGVtcGVyYXR1cmXNzEw/AAobcmVkd29vZF90YWlnYV9oaWxsc19tdXRhdGVkBQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlmpmZPgAKFXJlZHdvb2RfdGFpZ2FfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZQAAgD4ACgVyaXZlcgUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZQAAAD8ACg1yb29mZWRfZm9yZXN0BQhkb3duZmFsbM3MTD8FC3RlbXBlcmF0dXJlMzMzPwAKFXJvb2ZlZF9mb3Jlc3RfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZTMzMz8ACgdzYXZhbm5hBQhkb3duZmFsbAAAAAAFC3RlbXBlcmF0dXJlmpmZPwAKD3NhdmFubmFfbXV0YXRlZAUIZG93bmZhbGwAAAA/BQt0ZW1wZXJhdHVyZc3MjD8ACg9zYXZhbm5hX3BsYXRlYXUFCGRvd25mYWxsAAAAAAULdGVtcGVyYXR1cmUAAIA/AAoXc2F2YW5uYV9wbGF0ZWF1X211dGF0ZWQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAIA/AAoLc3RvbmVfYmVhY2gFCGRvd25mYWxsmpmZPgULdGVtcGVyYXR1cmXNzEw+AAoQc3VuZmxvd2VyX3BsYWlucwUIZG93bmZhbGzNzMw+BQt0ZW1wZXJhdHVyZc3MTD8ACglzd2FtcGxhbmQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmXNzEw/AAoRc3dhbXBsYW5kX211dGF0ZWQFCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmXNzEw/AAoFdGFpZ2EFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUAAIA+AAoLdGFpZ2FfaGlsbHMFCGRvd25mYWxszcxMPwULdGVtcGVyYXR1cmUAAIA+AAoNdGFpZ2FfbXV0YXRlZAUIZG93bmZhbGzNzEw/BQt0ZW1wZXJhdHVyZQAAgD4ACgd0aGVfZW5kBQhkb3duZmFsbAAAAD8FC3RlbXBlcmF0dXJlAAAAPwAKCndhcm1fb2NlYW4FCGRvd25mYWxsAAAAPwULdGVtcGVyYXR1cmUAAAA/AAA=`
rawOldSerializedBiomeDefinitionList, _ = base64.StdEncoding.DecodeString(s)
}

View File

@@ -0,0 +1,49 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
PlayerListActionAdd = iota
PlayerListActionRemove
)
// PlayerList is sent by the server to update the client-side player list in the in-game menu screen. It shows
// the icon of each player if the correct XUID is written in the packet.
// Sending the PlayerList packet is obligatory when sending an AddPlayer packet. The added player will not
// show up to a client if it has not been added to the player list, because several properties of the player
// are obtained from the player list, such as the skin.
type PlayerList struct {
// ActionType is the action to execute upon the player list. The entries that follow specify which entries
// are added or removed from the player list.
ActionType byte
// Entries is a list of all player list entries that should be added/removed from the player list,
// depending on the ActionType set.
Entries []proto.PlayerListEntry
}
// ID ...
func (*PlayerList) ID() uint32 {
return packet.IDPlayerList
}
// Marshal ...
func (pk *PlayerList) Marshal(io protocol.IO) {
io.Uint8(&pk.ActionType)
switch pk.ActionType {
case PlayerListActionAdd:
protocol.Slice(io, &pk.Entries)
case PlayerListActionRemove:
protocol.FuncIOSlice(io, &pk.Entries, proto.PlayerListRemoveEntry)
default:
io.UnknownEnumOption(pk.ActionType, "player list action type")
}
if pk.ActionType == PlayerListActionAdd {
for i := 0; i < len(pk.Entries); i++ {
io.Bool(&pk.Entries[i].Skin.Trusted)
}
}
}

View File

@@ -55,6 +55,18 @@ type CameraPreset struct {
AlignTargetAndCameraForward protocol.Optional[bool]
// AimAssist defines the aim assist to use when using this preset.
AimAssist protocol.Optional[protocol.CameraPresetAimAssist]
// ControlScheme is the control scheme that the client should use in this camera. It is one of the following:
// - ControlSchemeLockedPlayerRelativeStrafe is the default behaviour, this cannot be set when the client
// is in a custom camera.
// - ControlSchemeCameraRelative makes movement relative to the camera's transform, with the client's
// rotation being relative to the client's movement.
// - ControlSchemeCameraRelativeStrafe makes movement relative to the camera's transform, with the
// client's rotation being locked.
// - ControlSchemePlayerRelative makes movement relative to the player's transform, meaning holding
// left/right will make the player turn in a circle.
// - ControlSchemePlayerRelativeStrafe makes movement the same as the default behaviour, but can be
// used in a custom camera.
ControlScheme protocol.Optional[byte]
}
func (x *CameraPreset) FromLatest(cp protocol.CameraPreset) CameraPreset {
@@ -78,6 +90,7 @@ func (x *CameraPreset) FromLatest(cp protocol.CameraPreset) CameraPreset {
x.PlayerEffects = cp.PlayerEffects
x.AlignTargetAndCameraForward = cp.AlignTargetAndCameraForward
x.AimAssist = cp.AimAssist
x.ControlScheme = cp.ControlScheme
return *x
}
@@ -105,6 +118,7 @@ func (x *CameraPreset) ToLatest() protocol.CameraPreset {
PlayerEffects: x.PlayerEffects,
AlignTargetAndCameraForward: x.AlignTargetAndCameraForward,
AimAssist: x.AimAssist,
ControlScheme: x.ControlScheme,
}
}
@@ -146,4 +160,7 @@ func (x *CameraPreset) Marshal(r protocol.IO) {
if IsProtoGTE(r, ID766) {
protocol.OptionalMarshaler(r, &x.AimAssist)
}
if IsProtoGTE(r, ID800) {
protocol.OptionalFunc(r, &x.ControlScheme, r.Uint8)
}
}

View File

@@ -3,6 +3,7 @@ package proto
import "github.com/sandertv/gophertunnel/minecraft/protocol"
const (
ID800 = 800 // v1.21.80
ID786 = 786 // v1.21.70
ID776 = 776 // v1.21.60
ID766 = 766 // v1.21.50

97
legacyver/proto/player.go Normal file
View File

@@ -0,0 +1,97 @@
package proto
import (
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
// PlayerListEntry is an entry found in the PlayerList packet. It represents a single player using the UUID
// found in the entry, and contains several properties such as the skin.
type PlayerListEntry struct {
// UUID is the UUID of the player as sent in the Login packet when the client joined the server. It must
// match this UUID exactly for the correct XBOX Live icon to show up in the list.
UUID uuid.UUID
// EntityUniqueID is the unique entity ID of the player. This ID typically stays consistent during the
// lifetime of a world, but servers often send the runtime ID for this.
EntityUniqueID int64
// Username is the username that is shown in the player list of the player that obtains a PlayerList
// packet with this entry. It does not have to be the same as the actual username of the player.
Username string
// XUID is the XBOX Live user ID of the player, which will remain consistent as long as the player is
// logged in with the XBOX Live account.
XUID string
// PlatformChatID is an identifier only set for particular platforms when chatting (presumably only for
// Nintendo Switch). It is otherwise an empty string, and is used to decide which players are able to
// chat with each other.
PlatformChatID string
// BuildPlatform is the platform of the player as sent by that player in the Login packet.
BuildPlatform int32
// Skin is the skin of the player that should be added to the player list. Once sent here, it will not
// have to be sent again.
Skin protocol.Skin
// Teacher is a Minecraft: Education Edition field. It specifies if the player to be added to the player
// list is a teacher.
Teacher bool
// Host specifies if the player that is added to the player list is the host of the game.
Host bool
// SubClient specifies if the player that is added to the player list is a sub-client of another player.
SubClient bool
// PlayerColour is the colour of the player that is shown in UI elements, currently only used for the
// player locator bar.
PlayerColour int32
}
// Marshal encodes/decodes a PlayerListEntry.
func (x *PlayerListEntry) Marshal(r protocol.IO) {
r.UUID(&x.UUID)
r.Varint64(&x.EntityUniqueID)
r.String(&x.Username)
r.String(&x.XUID)
r.String(&x.PlatformChatID)
r.Int32(&x.BuildPlatform)
protocol.Single(r, &x.Skin)
r.Bool(&x.Teacher)
r.Bool(&x.Host)
r.Bool(&x.SubClient)
if IsProtoGTE(r, ID800) {
r.Int32(&x.PlayerColour)
}
}
// FromLatest ...
func (x *PlayerListEntry) FromLatest(y protocol.PlayerListEntry) PlayerListEntry {
x.UUID = y.UUID
x.EntityUniqueID = y.EntityUniqueID
x.Username = y.Username
x.XUID = y.XUID
x.PlatformChatID = y.PlatformChatID
x.BuildPlatform = y.BuildPlatform
x.Skin = y.Skin
x.Teacher = y.Teacher
x.Host = y.Host
x.SubClient = y.SubClient
x.PlayerColour = y.PlayerColour
return *x
}
// ToLatest ...
func (x *PlayerListEntry) ToLatest() protocol.PlayerListEntry {
return protocol.PlayerListEntry{
UUID: x.UUID,
EntityUniqueID: x.EntityUniqueID,
Username: x.Username,
XUID: x.XUID,
PlatformChatID: x.PlatformChatID,
BuildPlatform: x.BuildPlatform,
Skin: x.Skin,
Teacher: x.Teacher,
Host: x.Host,
SubClient: x.SubClient,
PlayerColour: x.PlayerColour,
}
}
// PlayerListRemoveEntry encodes/decodes a PlayerListEntry for removal from the list.
func PlayerListRemoveEntry(r protocol.IO, x *PlayerListEntry) {
r.UUID(&x.UUID)
}

View File

@@ -120,6 +120,10 @@ func convertPacketFunc(pid uint32, cur func() packet.Packet) func() packet.Packe
return func() packet.Packet { return &legacypacket.UpdatePlayerGameType{} }
case packet.IDSetActorMotion:
return func() packet.Packet { return &legacypacket.SetActorMotion{} }
case packet.IDBiomeDefinitionList:
return func() packet.Packet { return &legacypacket.BiomeDefinitionList{} }
case packet.IDPlayerList:
return func() packet.Packet { return &legacypacket.PlayerList{} }
default:
return cur
}
@@ -708,6 +712,20 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
Elements: pk.Elements,
Visibility: pk.Visibility,
}
case *packet.BiomeDefinitionList:
pks[pkIndex] = &legacypacket.BiomeDefinitionList{
BiomeDefinitions: pk.BiomeDefinitions,
StringList: pk.StringList,
}
case *packet.PlayerList:
entries := make([]proto.PlayerListEntry, len(pk.Entries))
for i, e := range pk.Entries {
entries[i] = (&proto.PlayerListEntry{}).FromLatest(e)
}
pks[pkIndex] = &legacypacket.PlayerList{
ActionType: pk.ActionType,
Entries: entries,
}
}
}
@@ -1213,6 +1231,20 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
Elements: pk.Elements,
Visibility: pk.Visibility,
}
case *legacypacket.BiomeDefinitionList:
pks[pkIndex] = &packet.BiomeDefinitionList{
BiomeDefinitions: pk.BiomeDefinitions,
StringList: pk.StringList,
}
case *legacypacket.PlayerList:
entries := make([]protocol.PlayerListEntry, len(pk.Entries))
for i, e := range pk.Entries {
entries[i] = e.ToLatest()
}
pks[pkIndex] = &packet.PlayerList{
ActionType: pk.ActionType,
Entries: entries,
}
}
}
return pks

View File

@@ -2,6 +2,8 @@ package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/internal/chunk"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/akmalfairuz/legacy-version/mapping"
)
@@ -18,6 +20,15 @@ var (
//go:embed data/block_states_786.nbt
blockStateData786 []byte
itemMappingLatest = mapping.NewItemMapping(requiredItemList786, ItemVersion786)
blockMappingLatest = mapping.NewBlockMapping(blockStateData786)
itemMapping786 = mapping.NewItemMapping(requiredItemList786, ItemVersion786)
blockMapping786 = mapping.NewBlockMapping(blockStateData786)
)
func New786() *Protocol {
return &Protocol{
ver: "1.21.70",
id: proto.ID786,
blockTranslator: NewBlockTranslator(blockMapping786, blockMappingLatest, chunk.NewNetworkPersistentEncoding(blockMapping786, BlockVersion786), chunk.NewBlockPaletteEncoding(blockMapping786, BlockVersion786), false),
itemTranslator: NewItemTranslator(itemMapping786, itemMappingLatest, blockMapping786, blockMappingLatest),
}
}

23
legacyver/v800.go Normal file
View File

@@ -0,0 +1,23 @@
package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion800 ...
ItemVersion800 = 251
// BlockVersion800 ...
BlockVersion800 int32 = (1 << 24) | (21 << 16) | (80 << 8)
)
var (
//go:embed data/required_item_list_800.json
requiredItemList800 []byte
//go:embed data/block_states_800.nbt
blockStateData800 []byte
itemMappingLatest = mapping.NewItemMapping(requiredItemList800, ItemVersion800)
blockMappingLatest = mapping.NewBlockMapping(blockStateData800)
)