First support 1.21.130

This commit is contained in:
AkmalFairuz
2025-12-10 23:05:46 +07:00
parent 2b6358ea51
commit 65693b1e85
22 changed files with 10348 additions and 40 deletions

4
go.mod
View File

@@ -24,7 +24,7 @@ require (
github.com/df-mc/jsonc v1.0.5 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
github.com/sandertv/go-raknet v1.14.3-0.20250305181847-6af3e95113d6 // indirect
golang.org/x/crypto v0.43.0 // indirect
@@ -32,3 +32,5 @@ require (
golang.org/x/net v0.46.0 // indirect
golang.org/x/text v0.30.0 // indirect
)
replace github.com/sandertv/gophertunnel => github.com/cooldogedev/gophertunnel v0.0.0-20251210050513-6e8700778c34

4
go.sum
View File

@@ -8,6 +8,8 @@ github.com/cooldogedev/gophertunnel v0.0.0-20250805131657-cc74f1797ff7 h1:IvKtI+
github.com/cooldogedev/gophertunnel v0.0.0-20250805131657-cc74f1797ff7/go.mod h1:lmRarAmn25V/+QeiUbUDXeA26bEaNlX1wGEM/rj39ew=
github.com/cooldogedev/gophertunnel v0.0.0-20250806215416-758d5047d822 h1:cW4FrZUxjdJBoSD3Fpw7Z86LQJHtTK7o9lIgGXuaZ3Q=
github.com/cooldogedev/gophertunnel v0.0.0-20250806215416-758d5047d822/go.mod h1:lmRarAmn25V/+QeiUbUDXeA26bEaNlX1wGEM/rj39ew=
github.com/cooldogedev/gophertunnel v0.0.0-20251210050513-6e8700778c34 h1:ZsWaF1vhNuTw7RnA6g4xQ7mN25KdQeaKO+8GOXkS0SI=
github.com/cooldogedev/gophertunnel v0.0.0-20251210050513-6e8700778c34/go.mod h1:IhLg93aMPY/rKgB7lKxTzOZ6i11OgkL0WinVs+gMvlI=
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/dragonfly v0.9.20-0.20241229163702-cc7e4ee0e3ce h1:mhBn/ezojzACPHeg+Y9NnU2bbYCozx27RgpfWUPw4i0=
@@ -57,6 +59,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs=
github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=

View File

@@ -0,0 +1,5 @@
{
"renamedIds": {
"minecraft:chain": "minecraft:iron_chain"
}
}

View File

@@ -0,0 +1,40 @@
package typeconf
import (
"fmt"
"github.com/samber/lo"
)
func StringToInt[T ~uint8 | ~int8 | ~uint16 | ~int16 | ~uint32 | ~int32 | ~uint64 | ~int64](s string, fallback int) T {
var result T
_, err := fmt.Sscanf(s, "%d", &result)
if err != nil {
return T(fallback)
}
return result
}
func IntToString[T ~uint8 | ~int8 | ~uint16 | ~int16 | ~uint32 | ~int32 | ~uint64 | ~int64](i T, fallback string) string {
return fmt.Sprintf("%d", i)
}
func BoolToByte(b bool) byte {
if b {
return 1
}
return 0
}
func ByteToBool(b byte) bool {
return b != 0
}
func IntToInt[T1 ~uint8 | ~int8 | ~uint16 | ~int16 | ~uint32 | ~int32 | ~uint64 | ~int64, T2 ~uint8 | ~int8 | ~uint16 | ~int16 | ~uint32 | ~int32 | ~uint64 | ~int64](i T1) T2 {
return T2(i)
}
func SliceIntToSliceInt[T1 ~uint8 | ~int8 | ~uint16 | ~int16 | ~uint32 | ~int32 | ~uint64 | ~int64, T2 ~uint8 | ~int8 | ~uint16 | ~int16 | ~uint32 | ~int32 | ~uint64 | ~int64](in []T1) []T2 {
return lo.Map(in, func(i T1, _ int) T2 {
return T2(i)
})
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -11,15 +11,16 @@ import (
type Animate struct {
// ActionType is the ID of the animation action to execute. It is one of the action type constants that
// may be found above.
ActionType int32
ActionType uint8
// EntityRuntimeID is the runtime ID of the player that the animation should be played upon. The runtime
// ID is unique for each world session, and entities are generally identified in packets using this
// runtime ID.
EntityRuntimeID uint64
// Data ...
Data float32
// RowingTime is the time for rowing actions.
RowingTime float32
// SwingSource is the source for swing actions. It is one of the action type constants that
// may be found above.
SwingSource protocol.Optional[string]
}
// ID ...
@@ -28,16 +29,30 @@ func (*Animate) ID() uint32 {
}
func (pk *Animate) Marshal(io protocol.IO) {
io.Varint32(&pk.ActionType)
if proto.IsProtoGTE(io, proto.ID898) {
io.Uint8(&pk.ActionType)
} else {
v := int32(pk.ActionType)
io.Varint32(&v)
pk.ActionType = uint8(v)
}
defaultRowingTime := float32(0)
io.Varuint64(&pk.EntityRuntimeID)
if proto.IsProtoGTE(io, proto.ID859) {
io.Float32(&pk.Data)
if pk.ActionType == packet.AnimateActionRowLeft || pk.ActionType == packet.AnimateActionRowRight {
io.Float32(&pk.RowingTime)
}
} else {
if pk.ActionType&0x80 != 0 {
io.Float32(&pk.RowingTime)
if proto.IsProtoLT(io, proto.ID898) {
const (
animateActionRowRight = 128
animateActionRowLeft = 129
)
if pk.ActionType == animateActionRowLeft || pk.ActionType == animateActionRowRight {
io.Float32(&defaultRowingTime)
}
}
} else if proto.IsProtoLT(io, proto.ID898) && pk.ActionType&0x80 != 0 {
io.Float32(&defaultRowingTime)
}
if proto.IsProtoGTE(io, proto.ID898) {
protocol.OptionalFunc(io, &pk.SwingSource, io.String)
}
}

View File

@@ -0,0 +1,59 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// AvailableCommands is sent by the server to send a list of all commands that
// the player is able to use on the server. This packet holds all the arguments
// of each commands as well, making it possible for the client to provide
// auto-completion and command usages. AvailableCommands packets can be resent,
// but the packet is often very big, so doing this very often should be avoided.
type AvailableCommands struct {
// EnumValues is a slice of all enum values of any enum in the
// AvailableCommands packet. EnumValues generally should contain each
// possible value only once. Enums are built by pointing to entries in this
// slice.
EnumValues []string
// ChainedSubcommandValues is a slice of all chained subcommand names. ChainedSubcommandValues generally should
//contain each possible value only once. ChainedSubcommands are built by pointing to entries in this slice.
ChainedSubcommandValues []string
// Suffixes, like EnumValues, is a slice of all suffix values of any command
// parameter in the AvailableCommands packet.
Suffixes []string
// Enums is a slice of all (fixed) command enums present in any of the
// commands.
Enums []proto.CommandEnum
// ChainedSubcommands is a slice of all subcommands that are followed by a chained command. An example usage of this
// is /execute which allows you to run another command as another entity or at a different position etc.
ChainedSubcommands []proto.ChainedSubcommand
// Commands is a list of all commands that the client should show
// client-side. The AvailableCommands packet replaces any commands sent
// before. It does not only add the commands that are sent in it.
Commands []proto.Command
// DynamicEnums is a slice of dynamic command enums. These command enums can
// be changed during runtime without having to resend an AvailableCommands
// packet.
DynamicEnums []protocol.DynamicEnum
// Constraints is a list of constraints that should be applied to certain
// options of enums in the commands above.
Constraints []protocol.CommandEnumConstraint
}
// ID ...
func (*AvailableCommands) ID() uint32 {
return packet.IDAvailableCommands
}
func (pk *AvailableCommands) Marshal(io protocol.IO) {
protocol.FuncSlice(io, &pk.EnumValues, io.String)
protocol.FuncSlice(io, &pk.ChainedSubcommandValues, io.String)
protocol.FuncSlice(io, &pk.Suffixes, io.String)
protocol.FuncIOSlice(io, &pk.Enums, proto.CommandEnumContext{EnumValues: pk.EnumValues}.Marshal)
protocol.Slice(io, &pk.ChainedSubcommands)
protocol.Slice(io, &pk.Commands)
protocol.Slice(io, &pk.DynamicEnums)
protocol.Slice(io, &pk.Constraints)
}

View File

@@ -0,0 +1,81 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// CommandOutput is sent by the server to the client to send text as output of a command. Most servers do not
// use this packet and instead simply send Text packets, but there is reason to send it.
// If the origin of a CommandRequest packet is not the player itself, but, for example, a websocket server,
// sending a Text packet will not do what is expected: The message should go to the websocket server, not to
// the client's chat. The CommandOutput packet will make sure the messages are relayed to the correct origin
// of the command request.
type CommandOutput struct {
// CommandOrigin is the data specifying the origin of the command. In other words, the source that the
// command request was from, such as the player itself or a websocket server. The client forwards the
// messages in this packet to the right origin, depending on what is sent here.
CommandOrigin protocol.CommandOrigin
// OutputType specifies the type of output that is sent. The OutputType sent by vanilla games appears to
// be 3, which seems to work.
OutputType string
// SuccessCount is the amount of times that a command was executed successfully as a result of the command
// that was requested. For servers, this is usually a rather meaningless fields, but for vanilla, this is
// applicable for commands created with Functions.
SuccessCount uint32
// OutputMessages is a list of all output messages that should be sent to the player. Whether they are
// shown or not, depends on the type of the messages.
OutputMessages []protocol.CommandOutputMessage
// DataSet ... TODO: Find out what this is for.
DataSet protocol.Optional[string]
}
// ID ...
func (*CommandOutput) ID() uint32 {
return packet.IDCommandOutput
}
func (pk *CommandOutput) Marshal(io protocol.IO) {
protocol.CommandOriginData(io, &pk.CommandOrigin)
if proto.IsProtoGTE(io, proto.ID898) {
io.String(&pk.OutputType)
} else {
v := getLegacyCommandOutputType(pk.OutputType)
io.Uint8(&v)
pk.OutputType = getLatestCommandOutputType(v)
}
io.Uint32(&pk.SuccessCount)
protocol.Slice(io, &pk.OutputMessages)
if proto.IsProtoGTE(io, proto.ID898) {
protocol.OptionalFunc(io, &pk.DataSet, io.String)
} else {
v, _ := pk.DataSet.Value()
io.String(&v)
pk.DataSet = protocol.Option(v)
}
}
var legacyCommandOutputTypeMap = map[byte]string{
0: packet.CommandOutputTypeNone,
1: packet.CommandOutputTypeLastOutput,
2: packet.CommandOutputTypeSilent,
3: packet.CommandOutputTypeAllOutput,
4: packet.CommandOutputTypeDataSet,
}
func getLegacyCommandOutputType(outputType string) byte {
for k, v := range legacyCommandOutputTypeMap {
if v == outputType {
return k
}
}
return 3 // Default to all output.
}
func getLatestCommandOutputType(outputType byte) string {
if v, ok := legacyCommandOutputTypeMap[outputType]; ok {
return v
}
return packet.CommandOutputTypeAllOutput // Default to all output.
}

View File

@@ -0,0 +1,43 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/internal/typeconf"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// CommandRequest is sent by the client to request the execution of a server-side command. Although some
// servers support sending commands using the Text packet, this packet is guaranteed to have the correct
// result.
type CommandRequest struct {
// CommandLine is the raw entered command line. The client does no parsing of the command line by itself
// (unlike it did in the early stages), but lets the server do that.
CommandLine string
// CommandOrigin is the data specifying the origin of the command. In other words, the source that the
// command was from, such as the player itself or a websocket server.
CommandOrigin protocol.CommandOrigin
// Internal specifies if the command request internal. Setting it to false seems to work and the usage of
// this field is not known.
Internal bool
// Version is the version of the command that is being executed. This field currently has no purpose or functionality.
Version string
}
// ID ...
func (*CommandRequest) ID() uint32 {
return packet.IDCommandRequest
}
func (pk *CommandRequest) Marshal(io protocol.IO) {
io.String(&pk.CommandLine)
protocol.CommandOriginData(io, &pk.CommandOrigin)
io.Bool(&pk.Internal)
if proto.IsProtoGTE(io, proto.ID898) {
io.String(&pk.Version)
} else {
v := typeconf.StringToInt[int32](pk.Version, 0)
io.Varint32(&v)
pk.Version = typeconf.IntToString[int32](v, "")
}
}

View File

@@ -0,0 +1,39 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/internal/typeconf"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// Event is sent by the server to send an event with additional data. It is typically sent to the client for
// telemetry reasons, much like the SimpleEvent packet.
type Event struct {
// EntityRuntimeID is the runtime ID of the player. The runtime ID is unique for each world session, and
// entities are generally identified in packets using this runtime ID.
EntityRuntimeID int64
// UsePlayerID ...
// TODO: Figure out what UsePlayerID is for.
UsePlayerID bool
// Event is the event that is transmitted.
Event protocol.Event
}
// ID ...
func (*Event) ID() uint32 {
return packet.IDEvent
}
func (pk *Event) Marshal(io protocol.IO) {
io.Varint64(&pk.EntityRuntimeID)
io.EventType(&pk.Event)
if proto.IsProtoGTE(io, proto.ID898) {
io.Bool(&pk.UsePlayerID)
} else {
v := typeconf.BoolToByte(pk.UsePlayerID)
io.Uint8(&v)
pk.UsePlayerID = typeconf.ByteToBool(v)
}
pk.Event.Marshal(io)
}

View File

@@ -0,0 +1,40 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/go-gl/mathgl/mgl32"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// Interact is sent by the client when it interacts with another entity in some way. It used to be used for
// normal entity and block interaction, but this is no longer the case now.
type Interact struct {
// Action type is the ID of the action that was executed by the player. It is one of the constants that
// may be found above.
ActionType byte
// TargetEntityRuntimeID is the runtime ID of the entity that the player interacted with. This is empty
// for the InteractActionOpenInventory action type.
TargetEntityRuntimeID uint64
// Position associated with the ActionType above. For the InteractActionMouseOverEntity, this is the
// position relative to the entity moused over over which the player hovered with its mouse/touch. For the
// InteractActionLeaveVehicle, this is the position that the player spawns at after leaving the vehicle.
Position protocol.Optional[mgl32.Vec3]
}
// ID ...
func (*Interact) ID() uint32 {
return packet.IDInteract
}
func (pk *Interact) Marshal(io protocol.IO) {
io.Uint8(&pk.ActionType)
io.Varuint64(&pk.TargetEntityRuntimeID)
if proto.IsProtoGTE(io, proto.ID898) {
protocol.OptionalFunc(io, &pk.Position, io.Vec3)
} else {
pos, _ := pk.Position.Value()
io.Vec3(&pos)
pk.Position = protocol.Option(pos)
}
}

View File

@@ -30,6 +30,8 @@ type MobEffect struct {
Duration int32
// Tick is the server tick at which the packet was sent. It is used in relation to CorrectPlayerMovePrediction.
Tick uint64
// Ambient specifies if the effect is ambient. If set to false, it will not get treated as an ambient effect.
Ambient bool
}
// ID ...
@@ -50,5 +52,9 @@ func (pk *MobEffect) Marshal(io protocol.IO) {
} else {
io.Uint64(&pk.Tick)
}
if proto.IsProtoGTE(io, proto.ID898) {
io.Bool(&pk.Ambient)
}
}
}

View File

@@ -13,9 +13,6 @@ type ResourcePackStack struct {
// join the server. If set to true, the client gets the option to either download the resource packs and
// join, or quit entirely. Behaviour packs never have to be downloaded.
TexturePackRequired bool
// BehaviourPack is a list of behaviour packs that the client needs to download before joining the server.
// All of these behaviour packs will be applied together, and the order does not necessarily matter.
BehaviourPacks []protocol.StackResourcePack
// TexturePacks is a list of texture packs that the client needs to download before joining the server.
// The order of these texture packs specifies the order that they are applied in on the client side. The
// first in the list will be applied first.
@@ -41,7 +38,10 @@ func (*ResourcePackStack) ID() uint32 {
func (pk *ResourcePackStack) Marshal(io protocol.IO) {
io.Bool(&pk.TexturePackRequired)
protocol.Slice(io, &pk.BehaviourPacks)
if proto.IsProtoLT(io, proto.ID898) {
var emptyResourcePack []protocol.StackResourcePack
protocol.Slice(io, &emptyResourcePack)
}
protocol.Slice(io, &pk.TexturePacks)
io.String(&pk.BaseGameVersion)
protocol.SliceUint32Length(io, &pk.Experiments)

View File

@@ -238,8 +238,6 @@ type StartGame struct {
// its index in the expected block palette. This is useful for servers that wish to support multiple protocol versions
// and custom blocks, but it will result in extra bytes being written for every block in a sub chunk palette.
UseBlockNetworkIDHashes bool
// TickDeathSystemsEnabled specifies if the new tick death systems are enabled.
TickDeathSystemsEnabled bool
// ServerAuthoritativeSound is currently unknown as to what it does.
ServerAuthoritativeSound bool
}
@@ -338,8 +336,9 @@ func (pk *StartGame) Marshal(io protocol.IO) {
io.UUID(&pk.WorldTemplateID)
io.Bool(&pk.ClientSideGeneration)
io.Bool(&pk.UseBlockNetworkIDHashes)
if proto.IsProtoGTE(io, proto.ID827) {
io.Bool(&pk.TickDeathSystemsEnabled)
if proto.IsProtoGTE(io, proto.ID827) && proto.IsProtoLT(io, proto.ID898) {
v := false
io.Bool(&v)
}
io.Bool(&pk.ServerAuthoritativeSound)
}

View File

@@ -50,7 +50,7 @@ type Text struct {
PlatformChatID string
// FilteredMessage is a filtered version of Message with all the profanity removed. The client will use
// this over Message if this field is not empty and they have the "Filter Profanity" setting enabled.
FilteredMessage string
FilteredMessage protocol.Optional[string]
}
// ID ...
@@ -59,8 +59,21 @@ func (*Text) ID() uint32 {
}
func (pk *Text) Marshal(io protocol.IO) {
io.Uint8(&pk.TextType)
if proto.IsProtoLT(io, proto.ID898) {
io.Uint8(&pk.TextType)
}
io.Bool(&pk.NeedsTranslation)
if proto.IsProtoGTE(io, proto.ID898) {
var categoryType uint8
if pk.TextType == TextTypeRaw || pk.TextType == TextTypeTip || pk.TextType == TextTypeSystem || pk.TextType == TextTypeObjectWhisper || pk.TextType == TextTypeObjectAnnouncement || pk.TextType == TextTypeObject {
categoryType = protocol.TextCategoryMessageOnly
} else if pk.TextType == TextTypeChat || pk.TextType == TextTypeWhisper || pk.TextType == TextTypeAnnouncement {
categoryType = protocol.TextCategoryAuthoredMessage
} else {
categoryType = protocol.TextCategoryMessageWithParameters
}
io.TextCategory(&categoryType)
}
switch pk.TextType {
case TextTypeChat, TextTypeWhisper, TextTypeAnnouncement:
io.String(&pk.SourceName)
@@ -74,6 +87,12 @@ func (pk *Text) Marshal(io protocol.IO) {
io.String(&pk.XUID)
io.String(&pk.PlatformChatID)
if proto.IsProtoGTE(io, proto.ID685) {
io.String(&pk.FilteredMessage)
if proto.IsProtoGTE(io, proto.ID898) {
protocol.OptionalFunc(io, &pk.FilteredMessage, io.String)
} else {
v, _ := pk.FilteredMessage.Value()
io.String(&v)
pk.FilteredMessage = protocol.Option(v)
}
}
}

213
legacyver/proto/command.go Normal file
View File

@@ -0,0 +1,213 @@
package proto
import (
"github.com/akmalfairuz/legacy-version/internal/typeconf"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"math"
)
// Command holds the data that a command requires to be shown to a player client-side. The command is shown in
// the /help command and auto-completed using this data.
type Command struct {
// Name is the name of the command. The command may be executed using this name, and will be shown in the
// /help list with it. It currently seems that the client crashes if the Name contains uppercase letters.
Name string
// Description is the description of the command. It is shown in the /help list and when starting to write
// a command.
Description string
// Flags is a combination of flags not currently known. Leaving the Flags field empty appears to work.
Flags uint16
// PermissionLevel is the command permission level that the player required to execute this command. The
// field no longer seems to serve a purpose, as the client does not handle the execution of commands
// anymore: The permissions should be checked server-side.
PermissionLevel string
// AliasesOffset is the offset to a CommandEnum that holds the values that
// should be used as aliases for this command.
AliasesOffset uint32
// ChainedSubcommandOffsets is a slice of offsets that all point to a different ChainedSubcommand from the
// ChainedSubcommands slice in the AvailableCommands packet.
ChainedSubcommandOffsets []uint32
// Overloads is a list of command overloads that specify the ways in which a command may be executed. The
// overloads may be completely different.
Overloads []protocol.CommandOverload
}
func (c *Command) Marshal(r protocol.IO) {
r.String(&c.Name)
r.String(&c.Description)
r.Uint16(&c.Flags)
if IsProtoGTE(r, ID898) {
r.String(&c.PermissionLevel)
} else {
permLevel := uint8(0)
r.Uint8(&permLevel)
c.PermissionLevel = "any"
}
r.Uint32(&c.AliasesOffset)
if IsProtoGTE(r, ID898) {
protocol.FuncSlice(r, &c.ChainedSubcommandOffsets, r.Uint32)
} else {
offsets := typeconf.SliceIntToSliceInt[uint32, uint16](c.ChainedSubcommandOffsets)
protocol.FuncSlice(r, &offsets, r.Uint16)
c.ChainedSubcommandOffsets = typeconf.SliceIntToSliceInt[uint16, uint32](offsets)
}
protocol.Slice(r, &c.Overloads)
}
func (c *Command) ToLatest() protocol.Command {
return protocol.Command{
Name: c.Name,
Description: c.Description,
Flags: c.Flags,
PermissionLevel: c.PermissionLevel,
AliasesOffset: c.AliasesOffset,
ChainedSubcommandOffsets: c.ChainedSubcommandOffsets,
Overloads: c.Overloads,
}
}
func (c *Command) FromLatest(latest protocol.Command) Command {
c.Name = latest.Name
c.Description = latest.Description
c.Flags = latest.Flags
c.PermissionLevel = latest.PermissionLevel
c.AliasesOffset = latest.AliasesOffset
c.ChainedSubcommandOffsets = latest.ChainedSubcommandOffsets
c.Overloads = latest.Overloads
return *c
}
// CommandEnum represents an enum in a command usage. The enum typically has a type and a set of options that
// are valid. A value that is not one of the options results in a failure during execution.
type CommandEnum struct {
// Type is the type of the command enum. The type will show up in the command usage as the type of the
// argument if it has a certain amount of arguments, or when Options is set to true in the
// command holding the enum.
Type string
// ValueIndices holds a list of indices that point to the EnumValues slice in the
// AvailableCommandsPacket. These represent the options of the enum.
ValueIndices []uint32
}
// CommandEnumContext holds context required for encoding command enums.
type CommandEnumContext struct {
EnumValues []string
}
// Marshal encodes/decodes a CommandEnum.
func (ctx CommandEnumContext) Marshal(r protocol.IO, x *CommandEnum) {
r.String(&x.Type)
if IsProtoGTE(r, ID898) {
protocol.FuncSlice(r, &x.ValueIndices, r.Uint32)
} else {
protocol.FuncIOSlice(r, &x.ValueIndices, ctx.enumOption)
}
}
// enumOption writes/reads a command enum option as a byte/uint16/uint32,
// depending on the amount of enum values.
func (ctx CommandEnumContext) enumOption(r protocol.IO, opt *uint32) {
n := len(ctx.EnumValues)
switch {
case n <= math.MaxUint8:
val := byte(*opt)
r.Uint8(&val)
*opt = uint32(val)
case n <= math.MaxUint16:
val := uint16(*opt)
r.Uint16(&val)
*opt = uint32(val)
default:
r.Uint32(opt)
}
}
// ToLatest ...
func (ce *CommandEnum) ToLatest() protocol.CommandEnum {
return protocol.CommandEnum{
Type: ce.Type,
ValueIndices: ce.ValueIndices,
}
}
// FromLatest ...
func (ce *CommandEnum) FromLatest(latest protocol.CommandEnum) CommandEnum {
ce.Type = latest.Type
ce.ValueIndices = latest.ValueIndices
return *ce
}
// ChainedSubcommand represents a subcommand that can have chained commands, such as /execute which allows you to run
// another command as another entity or at a different position etc.
type ChainedSubcommand struct {
// Name is the name of the chained subcommand and shows up in the list as a regular subcommand enum.
Name string
// Values contains the index and parameter type of the chained subcommand.
Values []ChainedSubcommandValue
}
func (x *ChainedSubcommand) Marshal(r protocol.IO) {
r.String(&x.Name)
protocol.Slice(r, &x.Values)
}
// ToLatest ...
func (x *ChainedSubcommand) ToLatest() protocol.ChainedSubcommand {
values := make([]protocol.ChainedSubcommandValue, len(x.Values))
for i, v := range x.Values {
values[i] = v.ToLatest()
}
return protocol.ChainedSubcommand{
Name: x.Name,
Values: values,
}
}
// FromLatest ...
func (x *ChainedSubcommand) FromLatest(latest protocol.ChainedSubcommand) ChainedSubcommand {
x.Name = latest.Name
x.Values = make([]ChainedSubcommandValue, len(latest.Values))
for i, v := range latest.Values {
x.Values[i] = (&ChainedSubcommandValue{}).FromLatest(v)
}
return *x
}
// ChainedSubcommandValue represents the value for a chained subcommand argument.
type ChainedSubcommandValue struct {
// Index is the index of the argument in the ChainedSubcommandValues slice from the AvailableCommands packet. This is
// then used to set the type specified by the Value field below.
Index uint32
// Value is a combination of the flags above and specified the type of argument. Unlike regular parameter types,
// this should NOT contain any of the special flags (valid, enum, suffixed or soft enum) but only the basic types.
Value uint32
}
func (x *ChainedSubcommandValue) Marshal(r protocol.IO) {
if IsProtoGTE(r, ID898) {
r.Varuint32(&x.Index)
r.Varuint32(&x.Value)
} else {
v := uint16(x.Index)
r.Uint16(&v)
x.Index = uint32(v)
vv := uint16(x.Value)
r.Uint16(&vv)
x.Value = uint32(vv)
}
}
// ToLatest ...
func (x *ChainedSubcommandValue) ToLatest() protocol.ChainedSubcommandValue {
return protocol.ChainedSubcommandValue{
Index: x.Index,
Value: x.Value,
}
}
// FromLatest ...
func (x *ChainedSubcommandValue) FromLatest(latest protocol.ChainedSubcommandValue) ChainedSubcommandValue {
x.Index = latest.Index
x.Value = latest.Value
return *x
}

View File

@@ -3,6 +3,7 @@ package proto
import "github.com/sandertv/gophertunnel/minecraft/protocol"
const (
ID898 = 898 // v1.21.130
ID859 = 859 // v1.21.120
ID844 = 844 // v1.21.110
ID827 = 827 // v1.21.100

View File

@@ -131,6 +131,16 @@ func convertPacketFunc(pid uint32, cur func() packet.Packet) func() packet.Packe
return func() packet.Packet { return &legacypacket.GameRulesChanged{} }
case packet.IDAnimate:
return func() packet.Packet { return &legacypacket.Animate{} }
case packet.IDAvailableCommands:
return func() packet.Packet { return &legacypacket.AvailableCommands{} }
case packet.IDCommandOutput:
return func() packet.Packet { return &legacypacket.CommandOutput{} }
case packet.IDCommandRequest:
return func() packet.Packet { return &legacypacket.CommandRequest{} }
case packet.IDEvent:
return func() packet.Packet { return &legacypacket.Event{} }
case packet.IDInteract:
return func() packet.Packet { return &legacypacket.Interact{} }
default:
return cur
}
@@ -193,7 +203,6 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
case *packet.ResourcePackStack:
pks[pkIndex] = &legacypacket.ResourcePackStack{
TexturePackRequired: pk.TexturePackRequired,
BehaviourPacks: pk.BehaviourPacks,
TexturePacks: pk.TexturePacks,
BaseGameVersion: pk.BaseGameVersion,
Experiments: pk.Experiments,
@@ -296,6 +305,7 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
Particles: pk.Particles,
Duration: pk.Duration,
Tick: pk.Tick,
Ambient: pk.Ambient,
}
case *packet.CameraAimAssist:
pks[pkIndex] = &legacypacket.CameraAimAssist{
@@ -599,7 +609,6 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
ScenarioID: pk.ScenarioID,
OwnerID: pk.OwnerID,
UseBlockNetworkIDHashes: pk.UseBlockNetworkIDHashes,
TickDeathSystemsEnabled: pk.TickDeathSystemsEnabled,
ServerAuthoritativeSound: pk.ServerAuthoritativeSound,
}
case *packet.CodeBuilderSource:
@@ -764,7 +773,57 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
ActionType: pk.ActionType,
EntityRuntimeID: pk.EntityRuntimeID,
Data: pk.Data,
RowingTime: pk.RowingTime,
SwingSource: pk.SwingSource,
}
case *packet.AvailableCommands:
commands := make([]proto.Command, len(pk.Commands))
for i, c := range pk.Commands {
commands[i] = (&proto.Command{}).FromLatest(c)
}
enums := make([]proto.CommandEnum, len(pk.Enums))
for i, e := range pk.Enums {
enums[i] = (&proto.CommandEnum{}).FromLatest(e)
}
chainedSubcommands := make([]proto.ChainedSubcommand, len(pk.ChainedSubcommands))
for i, c := range pk.ChainedSubcommands {
chainedSubcommands[i] = (&proto.ChainedSubcommand{}).FromLatest(c)
}
pks[pkIndex] = &legacypacket.AvailableCommands{
EnumValues: pk.EnumValues,
ChainedSubcommandValues: pk.ChainedSubcommandValues,
Suffixes: pk.Suffixes,
Enums: enums,
ChainedSubcommands: chainedSubcommands,
Commands: commands,
DynamicEnums: pk.DynamicEnums,
Constraints: pk.Constraints,
}
case *packet.CommandOutput:
pks[pkIndex] = &legacypacket.CommandOutput{
CommandOrigin: pk.CommandOrigin,
OutputType: pk.OutputType,
SuccessCount: pk.SuccessCount,
OutputMessages: pk.OutputMessages,
DataSet: pk.DataSet,
}
case *packet.CommandRequest:
pks[pkIndex] = &legacypacket.CommandRequest{
CommandLine: pk.CommandLine,
CommandOrigin: pk.CommandOrigin,
Internal: pk.Internal,
Version: pk.Version,
}
case *packet.Event:
pks[pkIndex] = &legacypacket.Event{
EntityRuntimeID: pk.EntityRuntimeID,
UsePlayerID: pk.UsePlayerID,
Event: pk.Event,
}
case *packet.Interact:
pks[pkIndex] = &legacypacket.Interact{
ActionType: pk.ActionType,
TargetEntityRuntimeID: pk.TargetEntityRuntimeID,
Position: pk.Position,
}
}
}
@@ -786,7 +845,6 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
case *legacypacket.ResourcePackStack:
pks[pkIndex] = &packet.ResourcePackStack{
TexturePackRequired: pk.TexturePackRequired,
BehaviourPacks: pk.BehaviourPacks,
TexturePacks: pk.TexturePacks,
BaseGameVersion: pk.BaseGameVersion,
Experiments: pk.Experiments,
@@ -886,6 +944,7 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
Particles: pk.Particles,
Duration: pk.Duration,
Tick: pk.Tick,
Ambient: pk.Ambient,
}
case *legacypacket.CameraAimAssist:
pks[pkIndex] = &packet.CameraAimAssist{
@@ -1177,7 +1236,6 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
ScenarioID: pk.ScenarioID,
OwnerID: pk.OwnerID,
UseBlockNetworkIDHashes: pk.UseBlockNetworkIDHashes,
TickDeathSystemsEnabled: pk.TickDeathSystemsEnabled,
ServerAuthoritativeSound: pk.ServerAuthoritativeSound,
}
case *legacypacket.CodeBuilderSource:
@@ -1316,7 +1374,50 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
ActionType: pk.ActionType,
EntityRuntimeID: pk.EntityRuntimeID,
Data: pk.Data,
RowingTime: pk.RowingTime,
SwingSource: pk.SwingSource,
}
case *legacypacket.AvailableCommands:
enums := make([]protocol.CommandEnum, len(pk.Enums))
for i, e := range pk.Enums {
enums[i] = e.ToLatest()
}
chainedSubcommands := make([]protocol.ChainedSubcommand, len(pk.ChainedSubcommands))
for i, c := range pk.ChainedSubcommands {
chainedSubcommands[i] = c.ToLatest()
}
commands := make([]protocol.Command, len(pk.Commands))
for i, c := range pk.Commands {
commands[i] = c.ToLatest()
}
pks[pkIndex] = &packet.AvailableCommands{
EnumValues: pk.EnumValues,
ChainedSubcommandValues: pk.ChainedSubcommandValues,
Suffixes: pk.Suffixes,
Enums: enums,
ChainedSubcommands: chainedSubcommands,
Commands: commands,
DynamicEnums: pk.DynamicEnums,
Constraints: pk.Constraints,
}
case *legacypacket.CommandOutput:
pks[pkIndex] = &packet.CommandOutput{
CommandOrigin: pk.CommandOrigin,
OutputType: pk.OutputType,
SuccessCount: pk.SuccessCount,
OutputMessages: pk.OutputMessages,
DataSet: pk.DataSet,
}
case *legacypacket.Event:
pks[pkIndex] = &packet.Event{
EntityRuntimeID: pk.EntityRuntimeID,
UsePlayerID: pk.UsePlayerID,
Event: pk.Event,
}
case *legacypacket.Interact:
pks[pkIndex] = &packet.Interact{
ActionType: pk.ActionType,
TargetEntityRuntimeID: pk.TargetEntityRuntimeID,
Position: pk.Position,
}
}
}

View File

@@ -9,7 +9,7 @@ import (
const (
// ItemVersion844 ...
ItemVersion844 = 241
ItemVersion844 = 251
// BlockVersion844 ...
BlockVersion844 int32 = (1 << 24) | (21 << 16) | (111 << 8)
)

View File

@@ -3,32 +3,31 @@ package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion859 ...
ItemVersion859 = 241
ItemVersion859 = 251
// BlockVersion859 ...
BlockVersion859 int32 = (1 << 24) | (21 << 16) | (120 << 8)
)
var (
//go:embed data/dragonfly_items.json
dragonflyLatestItemList []byte
//go:embed data/required_item_list_859.json
requiredItemList859 []byte
//go:embed data/block_states_859.nbt
blockStateData859 []byte
itemMappingLatestPocketMine = mapping.NewItemMapping(requiredItemList859, ItemVersion859)
itemMappingLatestDragonfly = mapping.NewItemMapping(dragonflyLatestItemList, ItemVersion859)
blockMappingLatest = mapping.NewBlockMapping(blockStateData859)
)
func itemMappingLatest(dragonflyMapping bool) mapping.Item {
if dragonflyMapping {
return itemMappingLatestDragonfly
func New859(dragonflyMapping bool) *Protocol {
itemMapping := mapping.NewItemMapping(requiredItemList859, ItemVersion859)
blockTranslator := lookupOrCreateBlockTranslator(859, BlockVersion859, blockStateData859)
return &Protocol{
ver: "1.21.120",
id: proto.ID859,
blockTranslator: blockTranslator,
itemTranslator: NewItemTranslator(itemMapping, itemMappingLatest(dragonflyMapping), blockTranslator.BlockMapping(), blockMappingLatest),
}
return itemMappingLatestPocketMine
}

34
legacyver/v898.go Normal file
View File

@@ -0,0 +1,34 @@
package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion898 ...
ItemVersion898 = 251
// BlockVersion898 ...
BlockVersion898 int32 = (1 << 24) | (21 << 16) | (130 << 8)
)
var (
//go:embed data/dragonfly_items.json
dragonflyLatestItemList []byte
//go:embed data/required_item_list_898.json
requiredItemList898 []byte
//go:embed data/block_states_898.nbt
blockStateData898 []byte
itemMappingLatestPocketMine = mapping.NewItemMapping(requiredItemList898, ItemVersion898)
itemMappingLatestDragonfly = mapping.NewItemMapping(dragonflyLatestItemList, ItemVersion898)
blockMappingLatest = mapping.NewBlockMapping(blockStateData898)
)
func itemMappingLatest(dragonflyMapping bool) mapping.Item {
if dragonflyMapping {
return itemMappingLatestDragonfly
}
return itemMappingLatestPocketMine
}