protocol

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package protocol implements the Minecraft wire format: framing, the primitive encodings, chunk decoding, and the per-version tables that say which packet ID means what.

It knows nothing about playing the game. Everything here is about getting bytes on and off a socket correctly, which is why it is separable from the package that decides what to do with them.

Files

conn.go       framing, compression, the read/write halves of a connection
varint.go     VarInt and VarLong, the two encodings everything else rests on
reader.go     bounded field decoding; accumulates the first error
writer.go     field encoding
chunk.go      paletted chunk containers and block-state lookup
version.go    Version, VersionSpec, and the per-version lookups
registry.go   the name and protocol-number registries
constants.go  wire constants: packet states, dig statuses, slot indices
names.go      namespacing helpers for item and entity names
uuid.go       offline-mode UUID derivation

The generated tables are not here. They are ~9,700 lines across three files, which in one directory buries the ten hand-written ones above; they live in protocol/versions instead.

Versions

Almost everything that changes between Minecraft releases is a number in a table rather than a branch in code. Packet IDs are dense indices that shift whenever Mojang inserts a packet; entity and item names are indexed by wire ID; block-state classification is a set of ranges. Those tables are generated from minecraft-data by internal/gen/genversion.mjs into protocol/versions, where they register themselves from init.

So this package defines the machinery and protocol/versions supplies the data. Importing protocol alone gives you an empty registry — something must import protocol/versions for ByName and ByProtocol to resolve anything. The understudy package does that for you; a program using protocol directly needs the blank import itself.

Keeping the data out means a test, or a tool that speaks one version, can build exactly the Version it needs through NewVersion without linking in three full tables.

The handful of genuine format differences that cannot be expressed as a table live in ChunkFormat, and each is documented with the symptom it causes when it is wrong. They share a shape: nothing errors at the mistake, and a short read surfaces several sections downstream.

Decoding

Reader accumulates the first error and keeps going, so a packet decoder reads every field and checks once at the end rather than after each. Every length that arrives from the wire is bounded before it is used to size an allocation or as a divisor — see MaxStringLen, MaxSections and MaxBitsPerEntry. That is not defensive programming for its own sake: a desynced stream puts arbitrary bytes where a length prefix should be, and an unbounded one turns a single corrupt packet into an out-of-memory kill or a division by zero.

Concurrency

A Conn may be written from any goroutine; reads are single-threaded by contract, since there is one read loop. A Version is immutable once registered and safe to share. Reader and Writer are not safe for concurrent use, and neither are ChunkColumn and ChunkSection — the caller holding those holds the lock.

Index

Constants

View Source
const (
	OverworldMinY int32 = -64
	NetherMinY    int32 = 0
)

OverworldMinY and NetherMinY are the two dimension floors this client sees. The floor is not carried in the chunk packet, so it is inferred from the section count rather than by decoding the dimension registry.

View Source
const (
	ClientCommandPerformRespawn int32 = 0
	ClientCommandRequestStats   int32 = 1
)

ClientCommand action IDs, the argument to the client_command packet.

View Source
const (
	DigStart       int32 = 0
	DigCancel      int32 = 1
	DigFinish      int32 = 2
	DigDropStack   int32 = 3
	DigDropItem    int32 = 4
	DigReleaseUse  int32 = 5
	DigSwapOffhand int32 = 6
)

BlockDig statuses, the first field of the block_dig packet.

Breaking a block is a *pair*: DigStart then DigFinish. The server measures the interval against the expected break time for the block and the held tool, so sending only one of them, or sending them too close together, leaves the block standing with no error reported.

View Source
const (
	FaceBottom int32 = 0 // -Y
	FaceTop    int32 = 1 // +Y
	FaceNorth  int32 = 2 // -Z
	FaceSouth  int32 = 3 // +Z
	FaceWest   int32 = 4 // -X
	FaceEast   int32 = 5 // +X
)

Block faces, as used by dig and place. The face decides which side a block is placed against, so it is not cosmetic.

View Source
const (
	MainHand int32 = 0
	OffHand  int32 = 1
)

Hands.

View Source
const (
	MovementOnGround               uint8 = 1 << 0
	MovementHasHorizontalCollision uint8 = 1 << 1
)

MovementFlags bits, the trailing field of the movement packets.

View Source
const (
	InputForward  uint8 = 1 << 0
	InputBackward uint8 = 1 << 1
	InputLeft     uint8 = 1 << 2
	InputRight    uint8 = 1 << 3
	InputJump     uint8 = 1 << 4
	InputSneak    uint8 = 1 << 5
	InputSprint   uint8 = 1 << 6
)

PlayerInput bitflags. Sneaking moved here in 26.1 — entity_action no longer carries start/stop_sneaking, so a client that looks for it there finds nothing and silently never sneaks.

View Source
const Absent int32 = -1

Absent is the ID of a packet a version does not have.

View Source
const AirState int32 = 0

AirState is the block state ID for air, which is 0 in every version.

View Source
const CompressionDisabled = -1

CompressionDisabled is the threshold value meaning "no compression yet". The server switches compression on mid-login by sending set_compression, so this is the starting state of every connection.

View Source
const DefaultNamespace = "minecraft"

DefaultNamespace is the namespace every vanilla identifier lives in.

View Source
const DefaultStackSize int32 = 64

DefaultStackSize is what an unrecognised item is assumed to stack to.

View Source
const FaceCount int32 = 6

FaceCount is how many block faces there are; a face is always 0..5.

View Source
const MaxBitsPerEntry uint8 = 32

MaxBitsPerEntry caps the entry width of a paletted container.

Direct encoding needs ceil(log2(stateCount)) bits, which is 15 for every version this client speaks. The cap exists because bitsPerEntry arrives from the wire and is used as a divisor: a corrupt or hostile value above 64 makes 64/bits zero, and the next division panics the whole process. Rejecting it here keeps a malformed chunk a dropped packet rather than a crash.

View Source
const MaxPacketSize = 1 << 23 // 8 MiB

MaxPacketSize bounds a single frame. The server should never send anything close to this; the cap exists so a desynced length prefix fails fast instead of making the client allocate gigabytes.

View Source
const MaxSections = 64

MaxSections bounds how tall a decoded column may be. The overworld is 24 sections and no vanilla dimension exceeds that; the cap stops a malformed blob from being read as an unbounded run of sections.

View Source
const MaxStringLen = 32767 * 4

MaxStringLen bounds a decoded string.

The protocol's own limit is 32767 characters, which is at most four bytes each. Anything longer is a desynced stream rather than a real field, and without a cap the length prefix — which is whatever bytes happened to land at that offset — decides how much memory this client allocates.

View Source
const RelativeMoveUnit = 1.0 / 4096.0

RelativeMoveUnit converts the i16 deltas in the relative-move packets to blocks. Entity movement is sent in 1/4096ths of a block, so treating the raw value as blocks puts entities thousands of blocks away.

View Source
const SectionHeight = 16

SectionHeight is the edge length of a chunk section: 16×16×16 blocks.

View Source
const ShapeUnit = 32

ShapeUnit is how many parts of a block a Box coordinate counts in.

Vanilla geometry is built from sixteenths, with a handful of thirty-seconds (the amethyst buds), so 1/32 represents every shape exactly. Storing integer units rather than floats keeps the tables four times smaller and, more usefully, makes comparisons exact: a slab is 16 units tall, never 0.49999.

Variables

View Source
var FullCube = Box{0, 0, 0, ShapeUnit, ShapeUnit, ShapeUnit}

FullCube is the shape of an ordinary block. Named so comparisons against it read as an assertion rather than a magic literal.

Functions

func AppendVarInt

func AppendVarInt(dst []byte, v int32) []byte

AppendVarInt appends the VarInt encoding of v to dst.

func AppendVarLong

func AppendVarLong(dst []byte, v int64) []byte

AppendVarLong appends the VarLong encoding of v to dst.

func BareName

func BareName(name string) string

BareName strips the namespace from an identifier: "minecraft:oak_log" becomes "oak_log". An unqualified name is returned unchanged.

func DecodeBlockPos

func DecodeBlockPos(v int64) (x, y, z int32)

DecodeBlockPos unpacks the 64-bit block position form written by Writer.BlockPos: x:26, z:26, y:12, each signed. The shifts sign-extend by shifting left then arithmetic-right, which is why they are written as a pair rather than a mask.

func Names

func Names() []string

Names lists the registered version names, sorted.

func Namespaced

func Namespaced(name string) string

Namespaced qualifies a bare identifier with the vanilla namespace, leaving an already-qualified one alone: "zombie" becomes "minecraft:zombie", while "mypack:widget" is returned unchanged.

Every lookup keyed on a wire name goes through here. Callers hand this package names typed by a human ("dirt", "diamond_pickaxe"), while the wire only ever carries the qualified form — normalising in one place is what keeps a bare name from silently matching nothing.

func ReadVarInt

func ReadVarInt(r io.ByteReader) (int32, error)

ReadVarInt reads a VarInt from r.

func ReadVarLong

func ReadVarLong(r io.ByteReader) (int64, error)

ReadVarLong reads a VarLong from r.

func Register

func Register(v *Version)

Register adds a version to the registry. It panics on a duplicate name or protocol number, which can only be a build-time mistake in the generated tables — two versions claiming one protocol number would otherwise make auto-detection silently pick whichever registered last.

func ValidFace

func ValidFace(face int32) bool

ValidFace reports whether a face value addresses a real block side.

Worth checking at any boundary that accepts one from outside: the wire encodes the face as a single signed byte, so an out-of-range value is truncated rather than rejected — face 260 becomes face 4, and the block is placed against a side the caller never named.

func VarIntLen

func VarIntLen(v int32) int

VarIntLen reports how many bytes AppendVarInt would write, for sizing a buffer before encoding into it.

Types

type Box

type Box [6]int8

Box is one axis-aligned collision box in units of 1/ShapeUnit of a block, relative to the block's own corner.

Coordinates can fall outside 0..ShapeUnit: fences and walls stand 1.5 blocks tall so their collision reaches 48, and a few shapes start slightly negative. Anything treating a box as clamped to its own block will get those wrong.

func (Box) Empty

func (b Box) Empty() bool

Empty reports whether the box encloses nothing.

func (Box) Height

func (b Box) Height() float64

Height returns the box's vertical extent in blocks.

func (Box) MaxX

func (b Box) MaxX() int8

MaxX returns the box's upper X bound, in units of 1/ShapeUnit.

func (Box) MaxY

func (b Box) MaxY() int8

MaxY returns the box's upper Y bound, in units of 1/ShapeUnit.

func (Box) MaxZ

func (b Box) MaxZ() int8

MaxZ returns the box's upper Z bound, in units of 1/ShapeUnit.

func (Box) MinX

func (b Box) MinX() int8

MinX returns the box's lower X bound, in units of 1/ShapeUnit.

func (Box) MinY

func (b Box) MinY() int8

MinY returns the box's lower Y bound, in units of 1/ShapeUnit.

func (Box) MinZ

func (b Box) MinZ() int8

MinZ returns the box's lower Z bound, in units of 1/ShapeUnit.

type ChunkColumn

type ChunkColumn struct {
	X, Z     int32
	MinY     int32
	Sections []*ChunkSection
}

ChunkColumn is a 16×N×16 column of sections at a chunk coordinate.

func ParseChunkData

func ParseChunkData(v *Version, x, z int32, data []byte) (*ChunkColumn, error)

ParseChunkData decodes the chunkData blob of a map_chunk packet.

Sections are read until the buffer is exhausted rather than from a declared count, because the count depends on the dimension's height and this client deliberately never decodes the dimension registry.

func (*ChunkColumn) BlockState

func (c *ChunkColumn) BlockState(x, y, z int32) int32

BlockState returns the state at absolute world coordinates, or air if the coordinate falls outside the column.

func (*ChunkColumn) SetBlockState

func (c *ChunkColumn) SetBlockState(x, y, z, state int32)

SetBlockState overwrites a single block, for the block-update packets.

A uniform (bitsPerEntry 0) section cannot represent two different states, so it is expanded to a direct-encoded section first. Sections are almost always uniform air, and a single placed block is exactly the case that breaks that assumption.

type ChunkFormat

type ChunkFormat struct {
	HasFluidCount bool
	HasSizePrefix bool
	NBTHeightmaps bool
}

ChunkFormat captures the parts of the chunk encoding that changed between versions. These are the three that actually bite:

  • HasSizePrefix: before 1.21.5 each paletted container carried a VarInt count of longs. From 1.21.5 it is computed instead, saving a byte.
  • HasFluidCount: from 26.1 each section carries a second int16 after the solid block count.
  • NBTHeightmaps: before 1.21.5 the heightmaps in a chunk packet are a single (nameless) NBT compound. From 1.21.5 they are a prefixed array of {VarInt type, prefixed array of long}. Nothing here reads heightmaps, but they sit between the coordinates and the chunk data, so walking them with the wrong shape puts the data blob at the wrong offset.

All three are invisible until they are wrong, and then they surface as a short read several sections downstream — nowhere near the actual mistake. The 1.21.5 chunk rework moved two of them at once, which is why HasSizePrefix and NBTHeightmaps share a threshold.

type ChunkSection

type ChunkSection struct {
	// contains filtered or unexported fields
}

ChunkSection is one 16³ cube of block states.

The states are kept in the wire's own paletted form rather than expanded to 4096 int32s per section. A loaded view distance is hundreds of sections, and expanding them all would cost tens of megabytes to answer questions about a handful of blocks.

A ChunkSection is not safe for concurrent use; callers hold the lock.

func (*ChunkSection) BlockState

func (s *ChunkSection) BlockState(x, y, z int32) int32

BlockState returns the state at a local coordinate, each 0..15.

type ComponentEncoding

type ComponentEncoding struct {
	// NestedStacksCountFirst: an item stack held inside a component leads with
	// its count rather than its id, and an empty container slot is a zero count
	// rather than an absent optional. Affects container, charged_projectiles,
	// bundle_contents and use_remainder.
	NestedStacksCountFirst bool

	// TagsAreBareStrings: a component whose value is a tag writes the tag name
	// on its own, where 26.1 wraps it in a holder set — a count of zero
	// followed by the name. Affects damage_resistant and
	// provides_banner_patterns.
	TagsAreBareStrings bool

	// RegistryRefsHaveLeadingFlag: six components write a byte before their
	// registry reference, which is 1 in every sample seen. Affects damage_type,
	// instrument, jukebox_playable, provides_trim_material, chicken/variant and
	// zombie_nautilus/variant — and no other reference, so break_sound, trim
	// and banner_patterns are untouched. True on 1.21.11 and, notably, false on
	// 1.21.4, so the older version is not simply "more of the same".
	RegistryRefsHaveLeadingFlag bool

	// EntityDataKeepsTypeInNBT: entity_data, bucket_entity_data,
	// block_entity_data and the bees inside a hive carry their type inside the
	// compound rather than hoisted in front of it. A pig spawn egg is 30 bytes
	// of nbt on 1.21.4 and 11 with the type pulled out on 26.1.
	EntityDataKeepsTypeInNBT bool

	// LegacyBlockPredicates: can_place_on and can_break hold predicates that
	// match on blocks, state and nbt only — no component matchers at all — and
	// the list is followed by a show-in-tooltip bool, which later versions
	// moved out into tooltip_display. A pickaxe restricted to dirt and stone is
	// eight bytes here and nine on 26.1.
	LegacyBlockPredicates bool

	// EquippableHasNoShearing: equippable stops after three flags, without the
	// equip-on-interact and shearable flags or the shearing sound that later
	// versions add. Twelve bytes becomes nine.
	EquippableHasNoShearing bool

	// TrimHasTooltipFlag: an armour trim carries the show-in-tooltip bool that
	// later versions moved out into tooltip_display.
	TrimHasTooltipFlag bool

	// ProfileHasNoVariantTag: a profile leads straight into its optional name
	// rather than with the partial/resolved discriminator, so a head named
	// Notch starts `01 05 Notch` rather than `00 01 05 Notch`.
	ProfileHasNoVariantTag bool
}

ComponentEncoding describes the ways component payloads differ between versions, as measured rather than as guessed.

Every field is false for 26.1 and true for 1.21.11 and 1.21.4. They were found by putting the same items on servers of each version and comparing the bytes: of sixty-seven components sampled on both, fifty-one are byte for byte identical and the rest fall into these three groups.

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a framed Minecraft connection.

It deliberately knows nothing about packet semantics — only how to get bytes on and off the wire. Reads are single-threaded by contract (one read loop), but writes are mutex-guarded because keep-alive and command traffic are generated from different goroutines.

func Dial

func Dial(addr string, timeout time.Duration) (*Conn, error)

Dial opens a TCP connection to a Minecraft server.

func NewConn

func NewConn(c net.Conn) *Conn

NewConn wraps an already-established connection.

Exported so the framing layer can be driven over an in-memory pipe — a fake server in a test, or a proxy — without going through Dial and a real socket.

func (*Conn) Close

func (c *Conn) Close() error

Close closes the underlying socket.

func (*Conn) CompressionThreshold

func (c *Conn) CompressionThreshold() int

CompressionThreshold returns the current threshold, or CompressionDisabled.

func (*Conn) ReadPacket

func (c *Conn) ReadPacket() (Packet, error)

ReadPacket reads one frame.

Uncompressed framing is [VarInt length][VarInt id][payload]. Once compression is on it becomes [VarInt packetLength][VarInt dataLength][body], where dataLength == 0 means the body is stored raw (it was under the threshold) and any other value is the *decompressed* size of a zlib body. Treating a 0 marker as a zlib stream is the classic bug here, so the two cases are handled explicitly.

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the server address.

func (*Conn) SetCompressionThreshold

func (c *Conn) SetCompressionThreshold(t int)

SetCompressionThreshold enables (>= 0) or disables (< 0) compression. It is safe to call while reads and writes are in flight.

func (*Conn) SetReadDeadline

func (c *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline bounds how long the next read may block.

func (*Conn) WritePacket

func (c *Conn) WritePacket(payload []byte) error

WritePacket writes an encoded payload (which already begins with the packet ID VarInt) as one frame. It is safe to call from multiple goroutines: the control API writes while the read loop runs.

type Packet

type Packet struct {
	ID   int32
	Data []byte
}

Packet is one decoded frame: its ID and its payload with the ID stripped.

func (Packet) Reader

func (p Packet) Reader() *Reader

Reader returns a Reader positioned at the first field of the payload.

type PacketIDs

type PacketIDs struct {
	SBHandshake int32

	SBLoginStart           int32
	SBLoginAcknowledged    int32
	CBLoginDisconnect      int32
	CBLoginEncryptionBegin int32
	CBLoginSuccess         int32
	CBLoginCompress        int32

	SBConfigSettings            int32
	SBConfigFinishConfiguration int32
	SBConfigKeepAlive           int32
	SBConfigPong                int32
	SBConfigSelectKnownPacks    int32
	SBConfigAcceptCodeOfConduct int32
	CBConfigDisconnect          int32
	CBConfigFinishConfiguration int32
	CBConfigKeepAlive           int32
	CBConfigPing                int32
	CBConfigSelectKnownPacks    int32
	CBConfigCodeOfConduct       int32

	SBPlayTeleportConfirm    int32
	SBPlayAttack             int32
	SBPlayChatMessage        int32
	SBPlayClientCommand      int32
	SBPlayUseEntity          int32
	SBPlayUseItem            int32
	SBPlayKeepAlive          int32
	SBPlayPosition           int32
	SBPlayPositionLook       int32
	SBPlayLook               int32
	SBPlayBlockDig           int32
	SBPlayHeldItemSlot       int32
	SBPlayArmAnimation       int32
	SBPlayBlockPlace         int32
	SBPlayWindowClick        int32
	SBPlayCloseWindow        int32
	SBPlaySelectTrade        int32
	SBPlayCraftRecipeRequest int32
	SBPlayContainerButton    int32
	SBPlayNameItem           int32
	SBPlaySetBeaconEffect    int32
	SBPlaySetCreativeSlot    int32
	SBPlayPlayerInput        int32
	SBPlayEntityAction       int32
	SBPlayChunkBatchReceived int32
	SBPlayPlayerLoaded       int32

	CBPlaySpawnEntity        int32
	CBPlayBlockChange        int32
	CBPlayKickDisconnect     int32
	CBPlayUnloadChunk        int32
	CBPlayKeepAlive          int32
	CBPlayMapChunk           int32
	CBPlayLogin              int32
	CBPlayRelEntityMove      int32
	CBPlayEntityMoveLook     int32
	CBPlayEntityTeleport     int32
	CBPlayDeathCombatEvent   int32
	CBPlayPosition           int32
	CBPlayEntityDestroy      int32
	CBPlayMultiBlockChange   int32
	CBPlayRespawn            int32
	CBPlayUpdateHealth       int32
	CBPlayGameStateChange    int32
	CBPlayEntityEffect       int32
	CBPlayRemoveEntityEffect int32
	CBPlayWindowItems        int32
	CBPlaySetSlot            int32
	CBPlayOpenWindow         int32
	CBPlayCloseWindow        int32
	CBPlayTradeList          int32
	CBPlayRecipeBookAdd      int32
	CBPlayHeldItemSlot       int32
	CBPlayCollect            int32
	CBPlayChunkBatchStart    int32
	CBPlayChunkBatchFinished int32
}

PacketIDs holds every packet ID this client uses, for one protocol version.

IDs are dense indices that shift whenever Mojang inserts a packet, so they cannot be constants. A field of -1 means the packet does not exist in that version; since real IDs are non-negative, an absent packet simply never matches a dispatch and never gets sent.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader decodes a packet payload. Every read is bounds-checked and the first failure is sticky: once Err is set, later reads return zero values instead of panicking. That lets a decoder read a whole packet and check the error once at the end rather than after every field.

A Reader is not safe for concurrent use.

func NewReader

func NewReader(buf []byte) *Reader

NewReader returns a Reader over buf. The buffer is not copied, so it must not be modified while the Reader is in use.

func (*Reader) Bool

func (r *Reader) Bool() bool

Bool reads a single byte as a boolean.

func (*Reader) Err

func (r *Reader) Err() error

Err returns the first error encountered, if any.

func (*Reader) F32

func (r *Reader) F32() float32

F32 reads a big-endian IEEE-754 single.

func (*Reader) F64

func (r *Reader) F64() float64

F64 reads a big-endian IEEE-754 double.

func (*Reader) Fail

func (r *Reader) Fail(err error)

Fail records an error from a caller that decoded something impossible.

The reader itself only catches short reads. A length that is well-formed but cannot describe the bytes remaining is a decode error the caller can see and the reader cannot, and it has to stop the read the same way.

func (*Reader) I8

func (r *Reader) I8() int8

I8 reads a signed byte.

func (*Reader) I16

func (r *Reader) I16() int16

I16 reads a big-endian signed 16-bit integer.

func (*Reader) I32

func (r *Reader) I32() int32

I32 reads a big-endian signed 32-bit integer.

func (*Reader) I64

func (r *Reader) I64() int64

I64 reads a big-endian signed 64-bit integer.

func (*Reader) ReadByte

func (r *Reader) ReadByte() (byte, error)

ReadByte satisfies io.ByteReader so the VarInt helpers can read from here.

func (*Reader) Remaining

func (r *Reader) Remaining() []byte

Remaining returns the undecoded tail of the payload, or nil once a read has failed. It aliases the underlying buffer.

func (*Reader) Skip

func (r *Reader) Skip(n int)

Skip advances the cursor past n bytes without decoding them.

For fields this client does not need but must still step over exactly — the pre-1.21.5 NBT heightmaps being the case it exists for. It shares take's bounds check, so a length that overruns the buffer sets the error rather than moving the cursor somewhere impossible.

func (*Reader) String

func (r *Reader) String() string

String reads a length-prefixed UTF-8 string, rejecting implausible lengths rather than allocating whatever the prefix claims. See MaxStringLen.

func (*Reader) U8

func (r *Reader) U8() uint8

U8 reads an unsigned byte.

func (*Reader) UUID

func (r *Reader) UUID() UUID

UUID reads a 128-bit UUID in wire order.

func (*Reader) VarInt

func (r *Reader) VarInt() int32

VarInt reads a variable-length 32-bit integer.

func (*Reader) VarLong

func (r *Reader) VarLong() int64

VarLong reads a variable-length 64-bit integer.

type State

type State int

State is a protocol state. The packet ID namespace is scoped to the state, so 0x00 means different things in each — decoding without tracking state is the classic way to misread a stream.

const (
	StateHandshaking State = iota
	StateLogin
	StateConfiguration
	StatePlay
)

The four protocol states, in the order a connection passes through them.

func (State) String

func (s State) String() string

type UUID

type UUID [16]byte

UUID is a raw 128-bit Minecraft UUID in wire order.

func OfflineUUID

func OfflineUUID(name string) UUID

OfflineUUID derives the UUID an offline-mode server assigns to a username.

Vanilla computes `UUID.nameUUIDFromBytes(("OfflinePlayer:"+name).getBytes(UTF_8))`, a plain RFC-4122 v3 (MD5) UUID. The bot has to derive the identical value, because on an offline-mode server the UUID *is* the player's identity: every statistic, advancement and permission the server records is keyed by it.

Get this wrong and the bot plays perfectly while anything checking up on it looks at a player who does not exist. Any other tool that addresses the same player must derive the UUID the same way.

func (UUID) String

func (u UUID) String() string

String renders the canonical 8-4-4-4-12 hyphenated form.

type Version

type Version struct {
	Name     string
	Protocol int32
	Chunk    ChunkFormat
	Packets  PacketIDs
	// contains filtered or unexported fields
}

Version is everything this client needs to know that varies between Minecraft versions.

A Version is immutable once registered and is shared by every Client speaking that version, so all its methods are safe for concurrent use.

func ByName

func ByName(name string) (*Version, error)

ByName looks up a version by its Minecraft version string, e.g. "26.1".

func ByProtocol

func ByProtocol(p int32) (*Version, error)

ByProtocol looks up a version by its wire protocol number. This is what a server-list ping reports, so it is the entry point for auto-detection.

func NewVersion

func NewVersion(spec VersionSpec) *Version

NewVersion builds a Version from a spec. It does not register it; call Register for that.

func (*Version) BlocksMovement

func (v *Version) BlocksMovement(state int32) bool

BlocksMovement reports whether a state has any collision at all.

Deliberately distinct from IsSolid, which answers from the coarse boundingBox ranges. Where they disagree the shape is right — IsSolid calls a fence solid and a slab solid without distinguishing them, and calls a pressure plate solid when it has no collision worth the name.

func (*Version) CollisionHeight

func (v *Version) CollisionHeight(state int32) float64

CollisionHeight returns how far up a block a player would be lifted by standing on it, in blocks, measured from the block's own floor.

This is what a boolean cannot express and what movement actually needs: a slab is 0.5, a full block 1.0, a fence 1.5 — so a fence is not a step, it is a wall, and treating it as solid-and-therefore-steppable walks a bot into it and stalls with no error.

Boxes that do not start at the block floor are ignored: standing on the top half of a vertical slab is not something walking gets you to.

func (*Version) CollisionShape

func (v *Version) CollisionShape(state int32) []Box

CollisionShape returns the boxes a block state collides with.

An empty result means the state has no collision at all — air, but also grass, torches and carpet-thin decoration. Callers must not read "no boxes" as "unknown": an unknown state also returns none, which is why HasCollisionData exists to tell the two apart.

func (*Version) ComponentEncoding

func (v *Version) ComponentEncoding() (ComponentEncoding, bool)

ComponentEncoding reports how this version encodes component payloads, and whether that is known at all.

Separate from HasComponentIDs because the two are independent. 1.21.11's ids are known — generated from its own registries report — and its payloads still differ from 26.1's, so knowing which id is which buys nothing on its own.

func (*Version) ComponentKind

func (v *Version) ComponentKind(wire int32) (int32, bool)

ComponentKind translates a data component's wire id into the canonical id the decoder recognises, reporting whether this version has an id for it.

The translation matters because the ids are per-version registry indices. On 1.21.11 an attribute_modifiers component arrives as 13 and on 26.1 as 16, and nothing in the payload says which was meant — so reading one as the other does not fail, it consumes the wrong number of bytes and desynchronises everything after it.

func (*Version) EffectName

func (v *Version) EffectName(id int32) string

EffectName returns a status effect's namespaced name.

func (*Version) EntityTypeName

func (v *Version) EntityTypeName(id int32) string

EntityTypeName resolves a wire entity type ID to its namespaced name.

func (*Version) HasCollisionData

func (v *Version) HasCollisionData(state int32) bool

HasCollisionData reports whether the version's tables describe this state.

A state outside the table is not "empty", it is unmapped — a version mismatch, or a modded block. Movement code has to treat that as an obstacle rather than as clear air, because walking confidently into an unknown block is a stall with no error attached.

func (*Version) HasComponentIDs

func (v *Version) HasComponentIDs() bool

HasComponentIDs reports whether this version's component ids are known at all.

func (*Version) IsAir

func (v *Version) IsAir(state int32) bool

IsAir reports whether a block state is any kind of air.

Not just state 0: cave_air and void_air are distinct states with their own IDs, and underground they are the overwhelming majority. Treating only state 0 as air makes a crosshair ray stop dead on the first cave air block it meets.

func (*Version) IsFluid

func (v *Version) IsFluid(state int32) bool

IsFluid reports whether a block state is a liquid.

func (*Version) IsFullCube

func (v *Version) IsFullCube(state int32) bool

IsFullCube reports whether a state fills its block exactly.

This is the case the old boolean got right, and it is worth keeping cheap: most movement questions have a fast answer when the block is a plain cube.

func (*Version) IsLava

func (v *Version) IsLava(state int32) bool

IsLava reports whether a block state is lava.

func (*Version) IsSolid

func (v *Version) IsSolid(state int32) bool

IsSolid reports whether a block state blocks movement.

func (*Version) IsTargetable

func (v *Version) IsTargetable(state int32) bool

IsTargetable reports whether the crosshair would stop on a block.

This is deliberately NOT IsSolid. Vanilla raycasts the block's *outline* shape, not its collision shape, and the two differ for exactly the blocks a test suite cares about: cobweb, crops, torches and flowers are all walk-through, so a collision-based ray passes straight through them and reports an empty line of sight to something standing right in front of the crosshair. Fluids are excluded because a block can be targeted through water.

func (*Version) IsWater

func (v *Version) IsWater(state int32) bool

IsWater reports whether a block state is water or a bubble column.

Water is emphatically not just "non-solid air": it cancels fall damage completely and drowns anything that stays submerged. Treating it as empty is how a bot falls into a lake like a stone and dies at the bottom.

func (*Version) ItemID

func (v *Version) ItemID(name string) (int32, bool)

ItemID resolves a namespaced item name to its wire ID. A bare name is assumed to be in the minecraft namespace.

func (*Version) ItemName

func (v *Version) ItemName(id int32) string

ItemName resolves a wire item ID to its namespaced name.

func (*Version) SlotDisplayKind

func (v *Version) SlotDisplayKind(wire int32) (int32, bool)

SlotDisplayKind translates a recipe book slot display kind into the canonical one, reporting whether this version has an id for it.

func (*Version) StackSize

func (v *Version) StackSize(id int32) int32

StackSize returns how many of an item fit in one slot.

This is not cosmetic for goal feasibility: totems stack to 1, so "hold 5 totems" needs five whole slots, while "hold 2304 dirt" needs exactly 36 — every storage slot a player has.

func (*Version) StackSizeOf

func (v *Version) StackSizeOf(name string) int32

StackSizeOf returns the stack size for an item name, defaulting to 64 for anything unrecognised.

func (*Version) SupportsAttackPacket

func (v *Version) SupportsAttackPacket() bool

SupportsAttackPacket reports whether the version has a dedicated attack packet. Versions before 26.1 fold attacking into use_entity with a mode field, which this client does not implement — so attacking is unavailable there rather than silently doing nothing.

type VersionSpec

type VersionSpec struct {
	Name     string
	Protocol int32
	Chunk    ChunkFormat
	Packets  PacketIDs

	// Components describes how this version encodes component payloads. Leave
	// nil for a version whose encodings have not been checked; setting it
	// wrongly desynchronises windows silently.
	Components *ComponentEncoding

	// ComponentIDs maps this version's data component wire ids to canonical
	// ids. Generated by internal/gen/gencomponents.mjs from the server's own
	// registries report; leave nil for a version whose ids are unknown.
	//
	// They are dense registry indices, like packet ids, and shift whenever
	// Mojang inserts a component — between 1.21.4 and 26.1 only five of
	// sixty-seven kept their number. Unlike packet ids they are in no published
	// dataset, which is why they come from the server rather than from
	// minecraft-data.
	ComponentIDs map[int32]int32

	// SlotDisplayIDs maps this version's slot display kinds to canonical ones.
	// Generated alongside ComponentIDs.
	SlotDisplayIDs map[int32]int32

	// EntityNames and ItemNames are indexed by wire ID; an empty string means
	// the ID is unused in this version.
	EntityNames []string
	// EffectNames is indexed by effect id.
	EffectNames []string
	ItemNames   []string
	// ItemStacks is indexed by wire ID. A non-positive entry means the default.
	ItemStacks []int32

	// Shapes holds the distinct collision shapes, indexed by ShapeRuns.
	Shapes [][]Box
	// ShapeRuns maps block-state ranges to a shape, as sorted {lo, hi, shape}.
	ShapeRuns [][3]int32

	// Block-state classification, as sorted inclusive [lo, hi] ranges.
	Solid [][2]int32
	Water [][2]int32
	Lava  [][2]int32
	Air   [][2]int32
}

VersionSpec describes a protocol table to build.

It exists because a Version's tables are unexported — which is right, they are an implementation detail of the lookups — but that also meant only the generated files, being in this package, could construct one. Anything wanting a small synthetic version (a test, a tool) had no way to make it.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer encodes a packet payload. The methods chain, so a packet reads as a single expression in field order — which is how it is checked against the protocol description.

A Writer is not safe for concurrent use.

func NewWriter

func NewWriter(packetID int32) *Writer

NewWriter returns a Writer with the given packet ID already encoded, which is where every outbound payload starts.

func (*Writer) BlockPos

func (w *Writer) BlockPos(x, y, z int32) *Writer

BlockPos writes a block coordinate in the packed form the protocol uses: a single 64-bit word of x:26, z:26, y:12, in that order. Note the ordering — z sits between x and y, which is the usual place to get this wrong, and a wrong packing addresses a real block somewhere else entirely rather than erroring.

func (*Writer) Bool

func (w *Writer) Bool(v bool) *Writer

Bool appends a boolean as a single byte.

func (*Writer) Bytes

func (w *Writer) Bytes() []byte

Bytes returns the encoded payload. It aliases the Writer's buffer, so it must not be retained across further writes.

func (*Writer) F16

func (w *Writer) F16(v float32) *Writer

F16 writes an IEEE-754 half-precision float.

Minecraft uses these where a full float would be wasteful and the precision does not matter — the "lpVec3" (low-precision vec3) carrying the point on an entity that an interaction hit, which only has to be good enough to tell one part of a boat from another.

Getting the width wrong here is not subtle: the server reports the packet as longer or shorter than it expected and drops the connection, which is at least loud.

func (*Writer) F32

func (w *Writer) F32(v float32) *Writer

F32 appends a big-endian IEEE-754 single.

func (*Writer) F64

func (w *Writer) F64(v float64) *Writer

F64 appends a big-endian IEEE-754 double.

func (*Writer) I8

func (w *Writer) I8(v int8) *Writer

I8 appends a signed byte.

func (*Writer) I16

func (w *Writer) I16(v int16) *Writer

I16 appends a big-endian signed 16-bit integer.

func (*Writer) I32

func (w *Writer) I32(v int32) *Writer

I32 appends a big-endian signed 32-bit integer.

func (*Writer) I64

func (w *Writer) I64(v int64) *Writer

I64 appends a big-endian signed 64-bit integer.

func (*Writer) String

func (w *Writer) String(v string) *Writer

String appends a length-prefixed UTF-8 string.

func (*Writer) U8

func (w *Writer) U8(v uint8) *Writer

U8 appends an unsigned byte.

func (*Writer) U16

func (w *Writer) U16(v uint16) *Writer

U16 writes a big-endian unsigned 16-bit value.

func (*Writer) UUID

func (w *Writer) UUID(v UUID) *Writer

UUID appends a 128-bit UUID in wire order.

func (*Writer) VarInt

func (w *Writer) VarInt(v int32) *Writer

VarInt appends a variable-length 32-bit integer.

func (*Writer) VarLong

func (w *Writer) VarLong(v int64) *Writer

VarLong appends a variable-length 64-bit integer.

Directories

Path Synopsis
Package versions holds one protocol table per Minecraft version.
Package versions holds one protocol table per Minecraft version.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL