docs: add godoc comments to all exported functions and methods
This commit is contained in:
@@ -1,407 +0,0 @@
|
||||
# Strom API Reference
|
||||
|
||||
Strom is a Go Minecraft protocol library for building servers, bots, and proxies.
|
||||
|
||||
## Packages
|
||||
|
||||
### `mc/api` — Accounts & Authentication
|
||||
|
||||
```go
|
||||
// Account holds a player's name, UUID, and access token.
|
||||
type Account struct{ Name string; UUID uuid.UUID; AccessToken string }
|
||||
|
||||
// NewAccount creates a profile from name, UUID, and access token.
|
||||
func NewAccount(name string, uuid uuid.UUID, ygg string) *Account
|
||||
|
||||
// NewAccountFromYGG parses an YGGDRASIL-style access token into an Account.
|
||||
func NewAccountFromYGG(ygg string) *Account
|
||||
|
||||
// NewOfflineAccount creates an offline (cracked) account from a display name.
|
||||
func NewOfflineAccount(name string) *Account
|
||||
|
||||
// HasJoined validates a session with Mojang's session server.
|
||||
func HasJoined(name, server, ip string) (ProfileResp, error)
|
||||
|
||||
// Profile fetches a player profile by UUID (with or without signatures).
|
||||
func Profile(id string, unsigned bool) (ProfileResp, error)
|
||||
|
||||
// NameToId / NameToIdBulk look up UUIDs from player names.
|
||||
func NameToId(name string) (IdName, error)
|
||||
func NameToIdBulk(names []string) ([]IdName, error)
|
||||
|
||||
// BlockedServers returns Mojang's blocklist.
|
||||
func BlockedServers() ([]string, error)
|
||||
|
||||
// MojangPublicKeys returns Mojang's current signing keys.
|
||||
func MojangPublicKeys() (MojangPublicKeysResp, error)
|
||||
```
|
||||
|
||||
### `mc/client` — Client Connections
|
||||
|
||||
```go
|
||||
// Connect dials a Minecraft server with SRV record lookup.
|
||||
func Connect(ctx context.Context, addr string, version string) (*proto.Conn, error)
|
||||
|
||||
// ConnectVersionLess dials a server without version negotiation (used for status pings).
|
||||
func ConnectVersionLess(ctx context.Context, addr string) (*proto.Conn, error)
|
||||
|
||||
// Login performs the full login handshake (online mode + encryption).
|
||||
// Functional options: WithoutDns, WithContext, WithVersion.
|
||||
func Login(connectTo string, account *api.Account, settings ...func(*loginSettings)) (*proto.Conn, error)
|
||||
|
||||
// LoginRaw logs in on an already-connected proto.Conn.
|
||||
func LoginRaw(c *proto.Conn, account *api.Account) error
|
||||
|
||||
// IgnoreConfig discards all Configuration-state packets (use when bundling login → play).
|
||||
func IgnoreConfig(c *proto.Conn) error
|
||||
|
||||
// MakeHandshakePacket builds a Handshake packet for the given next state.
|
||||
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (*v1_21_8.HandshakingToServerPacketSetProtocol, error)
|
||||
func MakeHandshakePacketAddr(s *proto.Conn, nextState proto_base.State, addr string) (*v1_21_8.HandshakingToServerPacketSetProtocol, error)
|
||||
|
||||
// Status pings a server and returns the parsed response.
|
||||
func Status(ctx context.Context, addr string) (server.StatusResponse, error)
|
||||
|
||||
// StatusNoDns is like Status but skips SRV record resolution.
|
||||
func StatusNoDns(addr string) (server.StatusResponse, error)
|
||||
|
||||
// StatusRaw returns a low-level StatusClient for custom status workflows.
|
||||
func StatusRaw(ctx context.Context, addr string) (*StatusClient, error)
|
||||
```
|
||||
|
||||
### `mc/client.StatusClient` — Programmatic Status Pings
|
||||
|
||||
```go
|
||||
type StatusClient struct {
|
||||
*proto.Conn
|
||||
Status string // raw JSON status response
|
||||
PingSendTime time.Time
|
||||
PingReceiveTime time.Time
|
||||
DoPingRoundTripTime bool
|
||||
}
|
||||
|
||||
func (s *StatusClient) OnStatus(p *v1_21_8.StatusToClientPacketServerInfo) error
|
||||
func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) error
|
||||
```
|
||||
|
||||
### `mc/server` — Server / Listen Side
|
||||
|
||||
```go
|
||||
// StartServerWithOnConn listens on addr, accepts TCP connections, and calls onConn per connection.
|
||||
func StartServerWithOnConn(listenAddr string, onConn func(c *proto.Conn) error) error
|
||||
|
||||
// Servee wraps a raw net.Conn into a proto.Conn with Servee actor.
|
||||
func Servee(cNet net.Conn) *proto.Conn
|
||||
|
||||
// Kick sends a disconnect packet to the client.
|
||||
func Kick(c *proto.Conn, reason *text.Component, status string) error
|
||||
|
||||
// ServeLogin handles the full login sequence (handshake → encryption → login → config → play).
|
||||
// Functional options: WithOtherAccount, WithRawStatus, WithStatus,
|
||||
// WithCompatibleVersions, WithCompatibleVersionsRange, WithoutOnlineMode, WithoutEncryption.
|
||||
func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (*LoginServer, error)
|
||||
|
||||
// ServeStatus handles a status-only connection (server list ping).
|
||||
func ServeStatus(c *proto.Conn, s StatusResponse) error
|
||||
|
||||
// ServeConfig drives the configuration phase.
|
||||
func ServeConfig(c *proto.Conn) error
|
||||
```
|
||||
|
||||
### `mc/proto` — Connection & Packet Helpers
|
||||
|
||||
```go
|
||||
// NewConn / NewConnCtx create a new proto.Conn with an embedded event loop.
|
||||
func NewConn() *Conn
|
||||
func NewConnCtx(ctx context.Context) *Conn
|
||||
|
||||
// SimplePacketToBytes encodes a typed packet to raw wire bytes (no length prefix).
|
||||
func SimplePacketToBytes(packet proto_base.EncodeDecodeAble) ([]byte, error)
|
||||
|
||||
// SimplePacketToBytesLenPrefix encodes a typed packet with varint length prefix.
|
||||
func SimplePacketToBytesLenPrefix(packet proto_base.EncodeDecodeAble) ([]byte, error)
|
||||
|
||||
// SimpleBytesToPacket decodes raw wire bytes into a typed packet.
|
||||
func SimpleBytesToPacket(packetBytes []byte, version int32, direction proto_base.Direction, state proto_base.State) (proto_base.EncodeDecodeAble, error)
|
||||
|
||||
// Packet lookup by type/name.
|
||||
func LookUpTypeByPacketInfo(direction proto_base.Direction, state proto_base.State, pid, version int32) (proto_base.PacketInfo, bool)
|
||||
func LookupPacketInfoByType(packet proto_base.EncodeDecodeAble) (proto_base.PacketInfo, bool)
|
||||
func LookupPacketInfoByNameProtocolVersionAndState(name string, version int32, state proto_base.State) (proto_base.PacketInfo, bool)
|
||||
```
|
||||
|
||||
#### `proto.Conn` Methods
|
||||
|
||||
```go
|
||||
// SetVersion loads the packet definitions for the given Minecraft version.
|
||||
func (c *Conn) SetVersion(version string) error
|
||||
|
||||
// SetState transitions the connection to a new protocol state (Handshake, Status, Login, Config, Play).
|
||||
func (c *Conn) SetState(s proto_base.State)
|
||||
|
||||
// State returns the current protocol state.
|
||||
func (c *Conn) State() proto_base.State
|
||||
|
||||
// Send encodes and writes a typed packet for the current state.
|
||||
func (c *Conn) Send(packet proto_base.EncodeDecodeAble) error
|
||||
|
||||
// Receive reads, decodes, and returns the next packet.
|
||||
func (c *Conn) Receive() (proto_base.EncodeDecodeAble, error)
|
||||
|
||||
// StartConn begins the event loop — blocks until the connection closes.
|
||||
func (c *Conn) StartConn() error
|
||||
|
||||
// Fire dispatches an event through the handler chain.
|
||||
func (c *Conn) Fire(event any) error
|
||||
|
||||
// RegisterCritical registers a handler that closes the loop if it returns an error.
|
||||
func (c *Conn) RegisterCritical(hs ...any)
|
||||
|
||||
// RegisterCriticalUntilLatest registers handlers for all versions between the packet version and the latest supported version.
|
||||
// Auto-unregisters when SetVersion bumps the version.
|
||||
func (c *Conn) RegisterCriticalUntilLatest(hs ...any)
|
||||
|
||||
// RegisterCriticalUntil registers handlers up to the specified version.
|
||||
func (c *Conn) RegisterCriticalUntil(until string, hs ...any)
|
||||
|
||||
// HandlerFunctions maps packet types to registered handler reflect.Values.
|
||||
// Useful for direct handler invocation in tests.
|
||||
func (c *Conn) HandlerFunctions map[reflect.Type][]reflect.Value
|
||||
```
|
||||
|
||||
### `mc/event` — Event Loop
|
||||
|
||||
```go
|
||||
// NewLoop / NewLoopCtx create an event loop.
|
||||
func NewLoop() *Loop
|
||||
func NewLoopCtx(ctx context.Context) *Loop
|
||||
|
||||
// RegisterCritical registers a handler that closes the loop on error.
|
||||
func (l *Loop) RegisterCritical(hs ...any)
|
||||
|
||||
// FireFound dispatches an event to matching handlers.
|
||||
func (l *Loop) FireFound(event any) (found bool, err error)
|
||||
|
||||
// ValidateHandler checks that a function matches the handler signature (func(packet T) error).
|
||||
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value)
|
||||
```
|
||||
|
||||
### `mc/proto_base` — Wire Types & Primitives
|
||||
|
||||
```go
|
||||
// Packet types and metadata lookup.
|
||||
type State int32 // Handshake, Status, Login, Config, Play
|
||||
type Direction int // ToServer, ToClient
|
||||
type Actor int // Server, Client
|
||||
type PacketInfo struct { Direction Direction; State State; Version int32; PacketId int32 }
|
||||
|
||||
// Varint encoding.
|
||||
func EncodeVarInt(w io.Writer, v int32) error
|
||||
func DecodeVarInt(r io.Reader) (int32, error)
|
||||
func EncodeVarLong(w io.Writer, v int64) error
|
||||
func DecodeVarLong(r io.Reader) (int64, error)
|
||||
|
||||
// String encoding (varint-prefixed UTF-8).
|
||||
func EncodeString(w io.Writer, s string) error
|
||||
func DecodeString(r io.Reader) (string, error)
|
||||
|
||||
// Packet direction helpers.
|
||||
func (a Actor) SendDirection() Direction
|
||||
func (a Actor) ReceiveDirection() Direction
|
||||
func (d Direction) Opposite() Direction
|
||||
func (d Direction) String() string
|
||||
```
|
||||
|
||||
### `mc/data` — Minecraft Version Data
|
||||
|
||||
```go
|
||||
// Version lookup.
|
||||
func LookUpVersionByName(name string) (VersionInfo, error)
|
||||
func LookUpVersionByProtocolVersion(version int32) (VersionInfo, error)
|
||||
func LookUpVersionByDataVersion(version int32) (VersionInfo, error)
|
||||
func MustLookupProtocolVersion(version string) int32
|
||||
|
||||
// Block data.
|
||||
func BlocksForVersion(v string) []*Block
|
||||
func LookupBlockByStateId(version string, stateId int32) (*Block, bool)
|
||||
func LookupBlockByName(version string, name string) (*Block, bool)
|
||||
func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (int32, error)
|
||||
func FromBlockState(version string, stateId int32) (*Block, map[string]string, error)
|
||||
|
||||
// Biome data.
|
||||
func BiomesForVersion(v string) []*Biome
|
||||
func LookupBiomeById(version string, id int32) (*Biome, bool)
|
||||
func LookupBiomeByName(version string, name string) (*Biome, bool)
|
||||
|
||||
// Item data.
|
||||
func ItemsForVersion(v string) []*Item
|
||||
func LookupItemById(version string, id int32) (*Item, bool)
|
||||
func LookupItemByName(version string, name string) (*Item, bool)
|
||||
|
||||
// Noise / generation data.
|
||||
func LoadMultiNoise(version string) (*MultiNoiseData, error)
|
||||
|
||||
// Registry data.
|
||||
func LoadRegistry(name string) (*nbt2.Tag, error)
|
||||
|
||||
// Generic JSON loading (relative to minecraft-data directory).
|
||||
func LoadJson(path string, data any) error
|
||||
func LoadVersionedJson(version, dataName string, data any) error
|
||||
```
|
||||
|
||||
### `mc/crypto` — Encryption Helpers
|
||||
|
||||
```go
|
||||
func AuthDigest(elems ...[]byte) string
|
||||
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8
|
||||
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8
|
||||
func FromOfflinePlayer(displayName string) uuid.UUID
|
||||
```
|
||||
|
||||
### `mc/text` — Text Components
|
||||
|
||||
```go
|
||||
// Pretty wraps a plain string in a Component.
|
||||
func Pretty(s string) *Component
|
||||
|
||||
// PrettyF wraps a formatted string in a Component.
|
||||
func PrettyF(format string, args ...any) *Component
|
||||
|
||||
// PrettyPlaceholders replaces placeholders in a translation string.
|
||||
func PrettyPlaceholders(format string, placeholders map[string]any) *Component
|
||||
```
|
||||
|
||||
### `mc/nbt` — Named Binary Tag I/O
|
||||
|
||||
```go
|
||||
func ReadFile(r io.Reader, data any) error
|
||||
func ReadUnstructuredFile(r io.Reader) (*Tag, error)
|
||||
func ReadUnstructuredFilePath(path string) (*Tag, error)
|
||||
func WriteUnstructuredFile(w io.Writer, n *Tag) error
|
||||
func WriteUnstructuredFilePath(path string, n *Tag) error
|
||||
func PrintSNBTAny(t any, w io.Writer) error
|
||||
```
|
||||
|
||||
### `mc/level` — World & Chunk Data
|
||||
|
||||
```go
|
||||
// Chunk reading.
|
||||
func ReadChunkFromChunkPacketData(r io.Reader, version string, worldHeight int) (*Chunk, error)
|
||||
|
||||
// Section / storage format helpers.
|
||||
func MakeBiomeFormat(version string) StorageFormat
|
||||
func MakeBlockFormat(version string) StorageFormat
|
||||
func ReadSectionStorage(r io.Reader, format StorageFormat) (*Storage, error)
|
||||
func WriteSectionStorage(w io.Writer, s *Storage) error
|
||||
func SectionDecodePacket(r io.Reader, version string) (Section, error)
|
||||
func SectionEncodePacket(w io.Writer, s Section) error
|
||||
func SectionEquals(a, b Section) bool
|
||||
func CountStorage(s *Storage, v int32) int
|
||||
```
|
||||
|
||||
#### `mc/level/anvil` — Anvil Region Format
|
||||
|
||||
```go
|
||||
func ReadLevelData(r io.Reader) (Level, error)
|
||||
func ReadChunkHeader(r *os.File) (*ChunkHeader, error)
|
||||
```
|
||||
|
||||
#### `mc/level/structure` / `mc/level/litematic`
|
||||
|
||||
```go
|
||||
func Load(fileName string) (*Structure, error) // structure
|
||||
func LoadFromPath(path string) (*Structure, error) // litematic
|
||||
func LoadFromFile(r io.Reader) (*Structure, error) // litematic
|
||||
```
|
||||
|
||||
### `mc/gen` — World Generation
|
||||
|
||||
```go
|
||||
func XoroshiroFromSeed(seed int64) Xoroshiro
|
||||
func XoroshiroFromString(s string) Xoroshiro
|
||||
func PerlinNoiseFromXoroshiro(x Xoroshiro) PerlinNoise
|
||||
func OctaveFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin, lenn, nmax int) Octave
|
||||
func DoublePerlinFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin, lenn, nmax int) DoublePerlin
|
||||
func BiomeNoiseFromXoroshiro(x Xoroshiro, large bool) BiomeNoise
|
||||
func CreateBiomeDepthSpline() Spline
|
||||
```
|
||||
|
||||
### `mc/mapstructure` — Tag-Based Decoder
|
||||
|
||||
```go
|
||||
// Decode populates a struct from a map using struct tags.
|
||||
func Decode(data any, v any) error
|
||||
|
||||
// NewFormat creates a custom format with options.
|
||||
func NewFormat(name string, opts ...Option) *Format
|
||||
|
||||
// Options.
|
||||
func WithRequireAll() Option
|
||||
func WithErrorOnExtra() Option
|
||||
func WithTrySnakeCase() Option
|
||||
func WithTryLowCase() Option
|
||||
func WithTypeCodec(decodeFn, encodeFn any) Option
|
||||
```
|
||||
|
||||
### `mc/util` — Miscellaneous Helpers
|
||||
|
||||
```go
|
||||
func CamelCase(s string) string
|
||||
func SnakeCase(s string) string
|
||||
func FirstLetterLower(s string) string
|
||||
func FloorDiv(a, b int32) int32
|
||||
func BpeByNum(x float64) uint8
|
||||
func Must(err error)
|
||||
```
|
||||
|
||||
## Handler Signature
|
||||
|
||||
All event handlers must match:
|
||||
```go
|
||||
func(typedPacket) error
|
||||
```
|
||||
The argument must implement `proto_base.EncodeDecodeAble` (has `Decode`/`Encode` methods). Return `event.HandlerDoneErr{}` to stop the loop cleanly, or any other error to close the connection.
|
||||
|
||||
## Quick Recipes
|
||||
|
||||
### Server list ping
|
||||
```go
|
||||
// Programmatic
|
||||
resp, err := client.Status(context.Background(), "mc.hypixel.net:25565")
|
||||
|
||||
// With raw JSON
|
||||
s, err := client.StatusRaw(ctx, addr)
|
||||
_ = json.Unmarshal([]byte(s.Status), &resp)
|
||||
```
|
||||
|
||||
### Start a server
|
||||
```go
|
||||
server.StartServerWithOnConn(":25565", func(c *proto.Conn) error {
|
||||
c.RegisterCriticalUntilLatest(handleHandshake, handleLogin)
|
||||
return c.StartConn()
|
||||
})
|
||||
```
|
||||
|
||||
### Construct a packet without a connection
|
||||
```go
|
||||
// Encode to wire format
|
||||
b, err := proto.SimplePacketToBytesLenPrefix(&v1_21_8.StatusToServerPacketPing{Time: 1234})
|
||||
|
||||
// Decode from wire format
|
||||
packet, err := proto.SimpleBytesToPacket(bytes, 772, proto_base.ToClient, proto_base.Status)
|
||||
info, ok := packet.(*v1_21_8.StatusToClientPacketServerInfo)
|
||||
```
|
||||
|
||||
### Look up version data
|
||||
```go
|
||||
v, _ := data.LookUpVersionByName("1.21.8")
|
||||
block, _ := data.LookupBlockByName("1.21.8", "minecraft:stone")
|
||||
protocol := data.MustLookupProtocolVersion("1.21.8")
|
||||
```
|
||||
|
||||
### Read NBT
|
||||
```go
|
||||
tag, _ := nbt.ReadUnstructuredFile(file)
|
||||
data.Level level
|
||||
nbt.ReadFile(file, &data)
|
||||
```
|
||||
@@ -289,6 +289,9 @@ func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error)
|
||||
return
|
||||
}
|
||||
|
||||
// NewAccountFromYGG creates an Account by authenticating the YGG token against
|
||||
// Mojang's API. It makes an HTTP call to fetch the player profile and returns
|
||||
// an Account with name and UUID populated, or an error if authentication fails.
|
||||
func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
||||
a = &Account{
|
||||
Ygg: ygg,
|
||||
@@ -303,6 +306,8 @@ func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// NewAccount creates an Account from explicit name, UUID, and YGG token without
|
||||
// making any HTTP calls. The account uses Mojang's default API servers.
|
||||
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
|
||||
return &Account{
|
||||
Name: name,
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"github.com/admin-else/strom/mc/crypto"
|
||||
)
|
||||
|
||||
// NewOfflineAccount creates an offline-mode Account with a UUID derived from the
|
||||
// player name. No HTTP calls are made; the UUID is computed locally.
|
||||
func NewOfflineAccount(name string) *Account {
|
||||
return &Account{
|
||||
Name: name,
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BlockedServers fetches the list of SHA-1 hashes of blocked server IPs from
|
||||
// Mojang's session server. The returned strings are hex-encoded hashes.
|
||||
func BlockedServers() (s []string, err error) {
|
||||
resp, err := http.Get("https://sessionserver.mojang.com/blockedservers")
|
||||
if err != nil {
|
||||
@@ -36,6 +38,9 @@ type ProfileResp struct {
|
||||
Properties []ProfileProperty `json:"properties"`
|
||||
}
|
||||
|
||||
// HasJoined checks with Mojang's session server whether a player has joined a
|
||||
// given server. name is the player name, server is the server hash, and ip is
|
||||
// the optional player IP address. Returns the player's profile on success.
|
||||
func HasJoined(name, server, ip string) (p ProfileResp, err error) {
|
||||
url := "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + server
|
||||
if ip != "" {
|
||||
@@ -62,6 +67,8 @@ type MojangPublicKeysResp struct {
|
||||
AuthenticationKeys []string `json:"authenticationKeys"`
|
||||
}
|
||||
|
||||
// MojangPublicKeys fetches Mojang's public keys for profile property signing and
|
||||
// player certificate verification from the session server.
|
||||
func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
|
||||
resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/profile/public_key_by_token")
|
||||
if err != nil {
|
||||
@@ -72,6 +79,8 @@ func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Profile fetches a player's profile by UUID from Mojang's session server.
|
||||
// If unsigned is false, signed profile properties are included in the response.
|
||||
func Profile(id string, unsinged bool) (p ProfileResp, err error) {
|
||||
url := "https://sessionserver.mojang.com/session/minecraft/profile/" + id
|
||||
if !unsinged {
|
||||
@@ -91,6 +100,8 @@ type IdName struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// NameToId resolves a Minecraft player name to a UUID and canonical name by
|
||||
// making an HTTP GET to Mojang's API.
|
||||
func NameToId(name string) (idName IdName, err error) {
|
||||
// may also use these:
|
||||
//https://api.mojang.com/users/profiles/minecraft/<player name>
|
||||
@@ -105,6 +116,8 @@ func NameToId(name string) (idName IdName, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// NameToIdBulk resolves multiple Minecraft player names to UUIDs and canonical
|
||||
// names in a single HTTP POST to Mojang's API.
|
||||
func NameToIdBulk(names []string) (idNames []IdName, err error) {
|
||||
b := bytes.NewBuffer(nil)
|
||||
err = json.NewEncoder(b).Encode(names)
|
||||
|
||||
@@ -13,6 +13,7 @@ func (m *mod) onKeepAlive(p *v1_21_8.PlayToClientPacketKeepAlive) (err error) {
|
||||
return m.Send(&v1_21_8.PlayToServerPacketKeepAlive{KeepAliveId: p.KeepAliveId})
|
||||
}
|
||||
|
||||
// Start begins responding to keep-alive packets on the connection.
|
||||
func Start(c *proto.Conn) {
|
||||
m := &mod{Conn: c}
|
||||
m.RegisterUntilLatest(m.onKeepAlive)
|
||||
|
||||
@@ -47,6 +47,7 @@ func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketK
|
||||
return
|
||||
}
|
||||
|
||||
// IgnoreConfig acknowledges configuration packets on the connection and transitions it to the Play state once configuration is complete.
|
||||
func IgnoreConfig(c *proto.Conn) (err error) {
|
||||
ci := &ConfigIgnorer{c}
|
||||
ci.RegisterCritical(ci.Default)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||
)
|
||||
|
||||
// MakeHandshakePacket creates a handshake packet using the connection's remote address as the server host.
|
||||
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
|
||||
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ func (s *LoginClient) OnStart() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// LoginRaw performs the login handshake on an already-established connection without DNS resolution or configuration handling.
|
||||
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
||||
lc := &LoginClient{
|
||||
Conn: c,
|
||||
@@ -166,18 +167,21 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// WithoutDns returns a login setting that disables SRV DNS resolution during login.
|
||||
func WithoutDns() func(ls *loginSettings) {
|
||||
return func(ls *loginSettings) {
|
||||
ls.NoDns = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext returns a login setting that sets the context used for the connection dial and DNS resolution.
|
||||
func WithContext(ctx context.Context) func(ls *loginSettings) {
|
||||
return func(ls *loginSettings) {
|
||||
ls.Ctx = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// WithVersion returns a login setting that forces a specific Minecraft version string for the connection.
|
||||
func WithVersion(version string) func(ls *loginSettings) {
|
||||
return func(ls *loginSettings) {
|
||||
ls.Version = version
|
||||
@@ -191,6 +195,7 @@ type loginSettings struct {
|
||||
IgnoreConfig bool
|
||||
}
|
||||
|
||||
// Login resolves the address with SRV DNS, connects, performs the login handshake, and handles the configuration phase, returning an authenticated connection ready for play.
|
||||
func Login(connectTo string, account *api.Account, settings ...func(ls *loginSettings)) (c *proto.Conn, err error) {
|
||||
ls := &loginSettings{}
|
||||
|
||||
|
||||
@@ -41,10 +41,12 @@ type CFB8 struct {
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewCFB8Decrypt creates a new CFB8 stream cipher in decrypt mode.
|
||||
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
|
||||
return newCFB8(c, iv, true)
|
||||
}
|
||||
|
||||
// NewCFB8Encrypt creates a new CFB8 stream cipher in encrypt mode.
|
||||
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8 {
|
||||
return newCFB8(c, iv, false)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ func uuidV3(space []byte, data []byte) uuid.UUID {
|
||||
return u
|
||||
}
|
||||
|
||||
// FromOfflinePlayer generates a UUID for an offline-mode (cracked) player from their display name.
|
||||
func FromOfflinePlayer(displayName string) uuid.UUID {
|
||||
return uuidV3([]byte("OfflinePlayer:"), []byte(displayName))
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type Biome struct {
|
||||
|
||||
var BiomesCache = make(map[string][]*Biome)
|
||||
|
||||
// BiomesForVersion returns the biome definitions for the given Minecraft version, caching them after the first load.
|
||||
func BiomesForVersion(v string) (ret []*Biome) {
|
||||
var ok bool
|
||||
if ret, ok = BiomesCache[v]; ok {
|
||||
@@ -39,6 +40,7 @@ func BiomesForVersion(v string) (ret []*Biome) {
|
||||
return b
|
||||
}
|
||||
|
||||
// LookupBiomeById returns the biome with the given numeric ID in the given version.
|
||||
func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
||||
for _, b := range BiomesForVersion(version) {
|
||||
if b.Id == id {
|
||||
@@ -52,6 +54,7 @@ func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
||||
|
||||
var UnknownBiomeNameErr = errors.New("unknown biome Name")
|
||||
|
||||
// LookupBiomeByName returns the biome with the given name in the given version.
|
||||
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
|
||||
for _, b := range BiomesForVersion(version) {
|
||||
if b.Name == name {
|
||||
|
||||
@@ -59,6 +59,7 @@ type BlockState struct {
|
||||
var BlocksCache = make(map[string][]*Block)
|
||||
var BlocksCacheIdMap = make(map[string]map[int32]*Block)
|
||||
|
||||
// BlocksForVersion returns the block definitions for the given Minecraft version, caching them after the first load.
|
||||
func BlocksForVersion(v string) (ret []*Block) {
|
||||
var ok bool
|
||||
if ret, ok = BlocksCache[v]; ok {
|
||||
@@ -84,6 +85,7 @@ func BlocksForVersion(v string) (ret []*Block) {
|
||||
return b
|
||||
}
|
||||
|
||||
// LookupBlockByStateId returns the block for the given global palette state ID in the given version.
|
||||
func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool) {
|
||||
idmap, found := BlocksCacheIdMap[version]
|
||||
if !found {
|
||||
@@ -101,6 +103,7 @@ func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool)
|
||||
|
||||
var UnknownBlockNameErr = errors.New("unknown block Name")
|
||||
|
||||
// LookupBlockByName returns the block with the given name in the given version.
|
||||
func LookupBlockByName(version string, name string) (block *Block, ok bool) {
|
||||
for _, b := range BlocksForVersion(version) {
|
||||
if b.Name == name {
|
||||
@@ -111,6 +114,7 @@ func LookupBlockByName(version string, name string) (block *Block, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// StateIdFromBlocKAndStateMap returns the global palette state ID for the given block name and state property map in the given version.
|
||||
func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (stateId int32, err error) {
|
||||
b, ok := LookupBlockByName(version, name)
|
||||
if !ok {
|
||||
@@ -135,6 +139,7 @@ var BadBlockStateTypeErr = errors.New("bad block state type")
|
||||
var BadEnumErr = errors.New("bad enum")
|
||||
var SupplyAllStatesErr = errors.New("supply all states")
|
||||
|
||||
// FromBlockState converts a global palette state ID back into a block definition and a map of state properties.
|
||||
func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]string, err error) {
|
||||
b, ok := LookupBlockByStateId(version, stateId)
|
||||
if !ok {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
//go:embed minecraft-data/pc minecraft-data/dataPaths.json
|
||||
var MinecraftData embed.FS
|
||||
|
||||
// LoadJson reads a JSON file from the embedded minecraft-data FS and unmarshals it into data.
|
||||
func LoadJson(path string, data any) (err error) {
|
||||
b, err := MinecraftData.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -21,6 +22,7 @@ func LoadJson(path string, data any) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// LoadVersionedJson loads a version-specific JSON data file from the embedded minecraft-data FS and unmarshals it into data.
|
||||
func LoadVersionedJson(version, dataName string, data any) (err error) {
|
||||
b, err := MinecraftData.ReadFile("minecraft-data/" + Paths.Data[version][dataName] + "/" + dataName + ".json")
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,7 @@ type Item struct {
|
||||
var ItemsCache = make(map[string][]*Item)
|
||||
var ItemsCacheMutex = &sync.RWMutex{}
|
||||
|
||||
// ItemsForVersion returns the item definitions for the given Minecraft version, caching them after the first load.
|
||||
func ItemsForVersion(v string) (ret []*Item) {
|
||||
var ok bool
|
||||
ItemsCacheMutex.RLock()
|
||||
@@ -35,6 +36,7 @@ func ItemsForVersion(v string) (ret []*Item) {
|
||||
return b
|
||||
}
|
||||
|
||||
// LookupItemById returns the item with the given numeric ID in the given version.
|
||||
func LookupItemById(version string, id int32) (item *Item, ok bool) {
|
||||
for _, i := range ItemsForVersion(version) {
|
||||
if i.Id == id {
|
||||
@@ -46,6 +48,7 @@ func LookupItemById(version string, id int32) (item *Item, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// LookupItemByName returns the item with the given name in the given version.
|
||||
func LookupItemByName(version string, name string) (item *Item, ok bool) {
|
||||
for _, i := range ItemsForVersion(version) {
|
||||
if i.Name == name {
|
||||
|
||||
@@ -20,6 +20,7 @@ type VersionInfo struct {
|
||||
|
||||
var ProtocolVersions []VersionInfo
|
||||
|
||||
// LookUpVersionByName returns the VersionInfo for the given Minecraft version name (e.g. "1.21.11").
|
||||
func LookUpVersionByName(name string) (ret VersionInfo, err error) {
|
||||
for _, v := range ProtocolVersions {
|
||||
if v.MinecraftVersion == name {
|
||||
@@ -31,6 +32,7 @@ func LookUpVersionByName(name string) (ret VersionInfo, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// LookUpVersionByProtocolVersion returns the VersionInfo for the given protocol version number.
|
||||
func LookUpVersionByProtocolVersion(version int32) (ret VersionInfo, err error) {
|
||||
for _, v := range ProtocolVersions {
|
||||
if v.Version == version {
|
||||
@@ -42,6 +44,7 @@ func LookUpVersionByProtocolVersion(version int32) (ret VersionInfo, err error)
|
||||
return
|
||||
}
|
||||
|
||||
// LookUpVersionByDataVersion returns the VersionInfo for the given Minecraft data version number.
|
||||
func LookUpVersionByDataVersion(version int32) (ret VersionInfo, err error) {
|
||||
for _, v := range ProtocolVersions {
|
||||
if v.DataVersion == version {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
//go:embed registry/*
|
||||
var Registry embed.FS
|
||||
|
||||
// LoadRegistry loads a registry NBT file by name from the embedded registry data.
|
||||
func LoadRegistry(name string) (n *nbt2.Tag, err error) {
|
||||
r, err := Registry.Open("registry/" + name + ".nbt")
|
||||
if err != nil {
|
||||
|
||||
@@ -69,6 +69,8 @@ var WhileClosingErr = errors.New("error while closing")
|
||||
var ContextDoneErr = errors.New("context done")
|
||||
var RecvNotOkErr = errors.New("recv not ok")
|
||||
|
||||
// ValidateHandler validates that h is a function taking a single argument and returning an error.
|
||||
// It panics if the handler does not match the expected signature.
|
||||
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
||||
hv = reflect.ValueOf(h)
|
||||
if hv.Kind() != reflect.Func {
|
||||
@@ -235,6 +237,7 @@ func (l *Loop) StartLoop() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// NewLoop creates a new Loop with a background context.
|
||||
func NewLoop() (l *Loop) {
|
||||
return NewLoopCtx(context.Background())
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ type BiomeNoise struct {
|
||||
Temperature, Humidity, ContinentalNess, Erosion, Shift, Weirdness DoublePerlin
|
||||
}
|
||||
|
||||
// BiomeNoiseFromXoroshiro creates a BiomeNoise from the given Xoroshiro source.
|
||||
func BiomeNoiseFromXoroshiro(x Xoroshiro, large bool) (v BiomeNoise) {
|
||||
x = x.Split()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ type DoublePerlin struct {
|
||||
B Octave
|
||||
}
|
||||
|
||||
// DoublePerlinFromXoroshiro creates a DoublePerlin from the given Xoroshiro source.
|
||||
func DoublePerlinFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (d DoublePerlin) {
|
||||
na := -1
|
||||
nb := -1
|
||||
|
||||
@@ -116,6 +116,7 @@ func (n PerlinNoise) Sample(x, y, z float64, yamp, ymin float64) (v float64) {
|
||||
return lerp(t3, l1, l5)
|
||||
}
|
||||
|
||||
// PerlinNoiseFromXoroshiro initializes a PerlinNoise from the given Xoroshiro source.
|
||||
func PerlinNoiseFromXoroshiro(x Xoroshiro) (p PerlinNoise) {
|
||||
p.xoffset = x.NextFloat64() * 256.0
|
||||
p.yoffset = x.NextFloat64() * 256.0
|
||||
|
||||
@@ -28,6 +28,7 @@ var octaveLacunaInits = []float64{1, .5, .25, 1. / 8, 1. / 16, 1. / 32, 1. / 64,
|
||||
var octavePersistInits = []float64{0, 1, 2. / 3, 4. / 7, 8. / 15, 16. / 31, 32. / 63, 64. / 127, 128. / 255, 256. / 511, 512. / 1023,
|
||||
1024. / 2047, 2048. / 4095, 4096. / 8191, 8192. / 16383, 16384. / 32767, 32768. / 65535}
|
||||
|
||||
// OctaveFromXoroshiro creates an Octave (multi-octave Perlin noise) from the given Xoroshiro source.
|
||||
func OctaveFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (o Octave) {
|
||||
if (-omin < 0 || -omin >= len(octaveLacunaInits)) || (lenn < 0 || lenn >= len(octavePersistInits)) {
|
||||
panic("octave initialization out of range")
|
||||
|
||||
@@ -183,6 +183,7 @@ func landSpline(f, g, h, i, j, k float32, bl bool) Spline {
|
||||
return s
|
||||
}
|
||||
|
||||
// CreateBiomeDepthSpline builds the Minecraft biome depth spline used for terrain generation.
|
||||
func CreateBiomeDepthSpline() Spline {
|
||||
sp1 := landSpline(-0.15, 0.00, 0.0, 0.1, 0.00, -0.03, false)
|
||||
sp2 := landSpline(-0.10, 0.03, 0.1, 0.1, 0.01, -0.03, false)
|
||||
|
||||
@@ -42,6 +42,7 @@ func (x *Xoroshiro) NextFloat64() float64 {
|
||||
return float64(x.Next()>>(64-53)) * 1.1102230246251565e-16
|
||||
}
|
||||
|
||||
// XoroshiroFromString creates an Xoroshiro from an MD5 hash of the given string.
|
||||
func XoroshiroFromString(s string) (x Xoroshiro) {
|
||||
sum := md5.Sum([]byte(s))
|
||||
x.L = binary.BigEndian.Uint64(sum[:8])
|
||||
@@ -49,6 +50,7 @@ func XoroshiroFromString(s string) (x Xoroshiro) {
|
||||
return
|
||||
}
|
||||
|
||||
// XoroshiroFromSeed creates an Xoroshiro from an int64 seed.
|
||||
func XoroshiroFromSeed(seed int64) (x Xoroshiro) {
|
||||
x.L = uint64(seed) ^ 0x6a09e667f3bcc909
|
||||
x.H = x.L + 0x9e3779b97f4a7c15
|
||||
|
||||
@@ -174,6 +174,7 @@ type Attribute struct {
|
||||
Base float64
|
||||
}
|
||||
|
||||
// ReadLevelData reads and decodes the level.dat NBT file from a reader.
|
||||
func ReadLevelData(r io.Reader) (l Level, err error) {
|
||||
var lDataWrapped struct{ Data Level }
|
||||
err = nbt.ReadFile(r, &lDataWrapped)
|
||||
|
||||
@@ -52,6 +52,7 @@ type ChunkHeader struct {
|
||||
Chunk, Time [ChunksRegionFile]int32
|
||||
}
|
||||
|
||||
// ReadChunkHeader reads the chunk header from an MCA region file.
|
||||
func ReadChunkHeader(r *os.File) (ch *ChunkHeader, err error) {
|
||||
var h ChunkHeader
|
||||
err = binary.Read(r, binary.BigEndian, &h)
|
||||
|
||||
@@ -10,6 +10,7 @@ type Chunk struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
// ReadChunkFromChunkPacketData decodes chunk packet data from a reader into a Chunk.
|
||||
func ReadChunkFromChunkPacketData(r io.Reader, version string, worldHeight int) (c *Chunk, err error) {
|
||||
c = &Chunk{}
|
||||
c.Version = version
|
||||
|
||||
@@ -94,6 +94,7 @@ type Structure struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
// LoadFromPath loads a litematic file from disk.
|
||||
func LoadFromPath(path string) (s *Structure, err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
@@ -104,6 +105,7 @@ func LoadFromPath(path string) (s *Structure, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// LoadFromFile reads and parses a litematic file from a reader.
|
||||
func LoadFromFile(r io.Reader) (s *Structure, err error) {
|
||||
n, err := nbt.ReadUnstructuredFile(r)
|
||||
if err != nil {
|
||||
@@ -112,6 +114,7 @@ func LoadFromFile(r io.Reader) (s *Structure, err error) {
|
||||
return Load(n.Value)
|
||||
}
|
||||
|
||||
// Load decodes raw NBT data into a litematic Structure.
|
||||
func Load(v any) (s *Structure, err error) {
|
||||
s = &Structure{}
|
||||
err = mapstructure.Decode(v, &s.File)
|
||||
|
||||
@@ -30,6 +30,7 @@ type Section struct {
|
||||
Blocks, Biomes *Storage
|
||||
}
|
||||
|
||||
// MakeBiomeFormat returns the StorageFormat for biome data at the given version.
|
||||
func MakeBiomeFormat(version string) StorageFormat {
|
||||
directBpe := uint8(math.Ceil(math.Log2(float64(len(data2.BiomesForVersion(version))))))
|
||||
return StorageFormat{
|
||||
@@ -39,6 +40,7 @@ func MakeBiomeFormat(version string) StorageFormat {
|
||||
}
|
||||
}
|
||||
|
||||
// MakeBlockFormat returns the StorageFormat for block data at the given version.
|
||||
func MakeBlockFormat(version string) StorageFormat {
|
||||
directBpe := uint8(math.Ceil(math.Log2(float64(len(data2.BlocksForVersion(version))))))
|
||||
return StorageFormat{
|
||||
@@ -51,6 +53,7 @@ func MakeBlockFormat(version string) StorageFormat {
|
||||
|
||||
var BadPaletteLenErr = errors.New("bad palette length")
|
||||
|
||||
// ReadSectionStorage reads a packed section storage from a reader.
|
||||
func ReadSectionStorage(r io.Reader, format StorageFormat) (s *Storage, err error) {
|
||||
var bpe uint8
|
||||
err = binary.Read(r, binary.BigEndian, &bpe)
|
||||
@@ -97,6 +100,7 @@ func ReadSectionStorage(r io.Reader, format StorageFormat) (s *Storage, err erro
|
||||
return format.ImportFromReader(r, bpe, pallette)
|
||||
}
|
||||
|
||||
// WriteSectionStorage writes a packed section storage to a writer.
|
||||
func WriteSectionStorage(w io.Writer, s *Storage) (err error) {
|
||||
err = binary.Write(w, binary.BigEndian, s.bpe)
|
||||
if err != nil {
|
||||
@@ -122,6 +126,7 @@ func WriteSectionStorage(w io.Writer, s *Storage) (err error) {
|
||||
return binary.Write(w, binary.BigEndian, s.data)
|
||||
}
|
||||
|
||||
// SectionDecodePacket decodes a chunk section from the Minecraft packet wire format.
|
||||
func SectionDecodePacket(r io.Reader, version string) (s Section, err error) {
|
||||
s = Section{}
|
||||
err = binary.Read(r, binary.BigEndian, &s.BlockCount)
|
||||
@@ -136,6 +141,7 @@ func SectionDecodePacket(r io.Reader, version string) (s Section, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// SectionEncodePacket encodes a chunk section to the Minecraft packet wire format.
|
||||
func SectionEncodePacket(w io.Writer, s Section) (err error) {
|
||||
err = binary.Write(w, binary.BigEndian, s.BlockCount)
|
||||
if err != nil {
|
||||
@@ -149,6 +155,7 @@ func SectionEncodePacket(w io.Writer, s Section) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// SectionEquals returns whether two sections are equal.
|
||||
func SectionEquals(a, b Section) bool {
|
||||
if a.BlockCount != b.BlockCount {
|
||||
return false
|
||||
|
||||
@@ -250,6 +250,7 @@ func (r *Storage) Equals(other *Storage) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CountStorage counts the number of elements in the storage that match the given value.
|
||||
func CountStorage(s *Storage, v int32) (a int) {
|
||||
for i := range s.format.Len {
|
||||
if w, _ := s.Get(i); w == v {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
)
|
||||
|
||||
// Load reads a structure NBT file and returns the decoded Structure.
|
||||
func Load(fileName string) (s *Structure, err error) {
|
||||
n, err := nbt.ReadUnstructuredFilePath(fileName)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package mapstructure
|
||||
|
||||
var DefaultFormat = NewFormat("mapstructure", WithRequireAll(), WithTrySnakeCase(), WithTryLowCase())
|
||||
|
||||
// Decode is a stub placeholder. Use Format.Decode instead.
|
||||
func Decode(data any, v any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ type Format struct {
|
||||
encoders map[reflect.Type]func(any, string) (any, error)
|
||||
}
|
||||
|
||||
// NewFormat creates a new Format with the given name and options.
|
||||
func NewFormat(name string, opts ...Option) *Format {
|
||||
f := &Format{
|
||||
Name: name,
|
||||
@@ -39,30 +40,35 @@ func NewFormat(name string, opts ...Option) *Format {
|
||||
|
||||
type Option func(*Format)
|
||||
|
||||
// WithRequireAll returns an Option that requires all struct fields to be present in the map.
|
||||
func WithRequireAll() Option {
|
||||
return func(f *Format) {
|
||||
f.requireAll = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithErrorOnExtra returns an Option that causes an error when the map contains unmatched keys.
|
||||
func WithErrorOnExtra() Option {
|
||||
return func(f *Format) {
|
||||
f.errorOnExtra = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithTrySnakeCase returns an Option that tries to match struct fields using snake_case names.
|
||||
func WithTrySnakeCase() Option {
|
||||
return func(f *Format) {
|
||||
f.trySnakeCase = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithTryLowCase returns an Option that tries to match struct fields using lowercase names.
|
||||
func WithTryLowCase() Option {
|
||||
return func(f *Format) {
|
||||
f.tryLowCase = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithTypeCodec returns an Option that registers custom decode/encode functions for a type.
|
||||
func WithTypeCodec(decodeFn, encodeFn any) Option {
|
||||
dt := reflect.TypeOf(decodeFn)
|
||||
et := reflect.TypeOf(encodeFn)
|
||||
@@ -94,6 +100,7 @@ func WithTypeCodec(decodeFn, encodeFn any) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Decode populates target (a pointer to a struct) from data (a map[string]any).
|
||||
func (f *Format) Decode(data any, target any) error {
|
||||
v := reflect.ValueOf(target)
|
||||
if v.Kind() != reflect.Pointer || v.IsNil() {
|
||||
@@ -110,6 +117,7 @@ func (f *Format) Decode(data any, target any) error {
|
||||
return f.decode(m, v)
|
||||
}
|
||||
|
||||
// Encode converts source (a struct or pointer to a struct) into a map[string]any.
|
||||
func (f *Format) Encode(source any) (map[string]any, error) {
|
||||
v := reflect.ValueOf(source)
|
||||
if v.Kind() == reflect.Pointer {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
var Format = mapstructure.NewFormat("nbt", mapstructure.WithRequireAll(), mapstructure.WithTrySnakeCase(), mapstructure.WithTryLowCase())
|
||||
|
||||
// ReadFile reads a gzipped NBT file from r and decodes it into data using the package-level Format.
|
||||
func ReadFile(file io.Reader, data any) (err error) {
|
||||
n, err := ReadUnstructuredFile(file)
|
||||
if err != nil {
|
||||
@@ -18,6 +19,7 @@ func ReadFile(file io.Reader, data any) (err error) {
|
||||
return Format.Decode(n.Value, data)
|
||||
}
|
||||
|
||||
// WriteUnstructuredFilePath writes an NBT tag as a gzipped file to the given filesystem path.
|
||||
func WriteUnstructuredFilePath(path string, n *Tag) (err error) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
@@ -27,6 +29,7 @@ func WriteUnstructuredFilePath(path string, n *Tag) (err error) {
|
||||
return WriteUnstructuredFile(f, n)
|
||||
}
|
||||
|
||||
// WriteUnstructuredFile writes an NBT tag as a gzipped file to w.
|
||||
func WriteUnstructuredFile(file io.Writer, n *Tag) (err error) {
|
||||
gzw := gzip.NewWriter(file)
|
||||
defer gzw.Close()
|
||||
@@ -34,6 +37,7 @@ func WriteUnstructuredFile(file io.Writer, n *Tag) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ReadUnstructuredFile reads a gzipped NBT file from r and returns the root Tag.
|
||||
func ReadUnstructuredFile(file io.Reader) (n *Tag, err error) {
|
||||
r, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
@@ -45,6 +49,7 @@ func ReadUnstructuredFile(file io.Reader) (n *Tag, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ReadUnstructuredFilePath reads a gzipped NBT file from the given filesystem path and returns the root Tag.
|
||||
func ReadUnstructuredFilePath(path string) (n *Tag, err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -73,6 +73,7 @@ type Conn struct {
|
||||
Log *slog.Logger
|
||||
}
|
||||
|
||||
// StartConn starts the connection event loop, registering the receive routine and login tick handler.
|
||||
func (c *Conn) StartConn() (err error) {
|
||||
c.RegisterEventSource(c.ActivateReceiveRoutine())
|
||||
c.RegisterCritical(c.OnLoginTick)
|
||||
@@ -81,6 +82,7 @@ func (c *Conn) StartConn() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// OnLoginTick handles a login tick by receiving a packet and firing it as an event.
|
||||
func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
|
||||
packet, err := c.Receive()
|
||||
if err != nil {
|
||||
@@ -90,30 +92,35 @@ func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// SetState sets the connection's protocol state.
|
||||
func (c *Conn) SetState(state proto_base.State) {
|
||||
c.stateMutex.Lock()
|
||||
defer c.stateMutex.Unlock()
|
||||
c.state = state
|
||||
}
|
||||
|
||||
// State returns the current connection protocol state.
|
||||
func (c *Conn) State() proto_base.State {
|
||||
c.stateMutex.RLock()
|
||||
defer c.stateMutex.RUnlock()
|
||||
return c.state
|
||||
}
|
||||
|
||||
// SetAsyncPackets enables or disables asynchronous packet handling in the receive routine.
|
||||
func (c *Conn) SetAsyncPackets(asyncPackets bool) {
|
||||
c.asyncPacketsMutex.Lock()
|
||||
defer c.asyncPacketsMutex.Unlock()
|
||||
c.asyncPackets = asyncPackets
|
||||
}
|
||||
|
||||
// GetAsyncPackets returns whether asynchronous packet handling is enabled.
|
||||
func (c *Conn) GetAsyncPackets() bool {
|
||||
c.asyncPacketsMutex.RLock()
|
||||
defer c.asyncPacketsMutex.RUnlock()
|
||||
return c.asyncPackets
|
||||
}
|
||||
|
||||
// SetVersion sets the connection's Minecraft version by version name and looks up the corresponding protocol version.
|
||||
func (c *Conn) SetVersion(version string) (err error) {
|
||||
versionData, err := data.LookUpVersionByName(version)
|
||||
if err != nil {
|
||||
@@ -124,6 +131,7 @@ func (c *Conn) SetVersion(version string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// SetProtocolVersion sets the connection's version by protocol version number and looks up the corresponding version name.
|
||||
func (c *Conn) SetProtocolVersion(version int32) (err error) {
|
||||
versionData, err := data.LookUpVersionByProtocolVersion(version)
|
||||
if err != nil {
|
||||
@@ -202,6 +210,7 @@ func SimplePacketToBytes(packet proto_base.EncodeDecodeAble) (b []byte, err erro
|
||||
return
|
||||
}
|
||||
|
||||
// SimplePacketToBytesLenPrefix is like SimplePacketToBytes but also prepends the length-prefix framing.
|
||||
func SimplePacketToBytesLenPrefix(packet proto_base.EncodeDecodeAble) (b []byte, err error) {
|
||||
b, err = SimplePacketToBytes(packet)
|
||||
if err != nil {
|
||||
@@ -282,6 +291,7 @@ func (c *Conn) sendRegisteredPacket(packet proto_base.EncodeDecodeAble) (err err
|
||||
return c.SendRaw(packetBuff.Bytes())
|
||||
}
|
||||
|
||||
// Send encodes and sends a packet to the remote peer, translating protocol versions if needed.
|
||||
func (c *Conn) Send(packet proto_base.EncodeDecodeAble) (err error) {
|
||||
switch packet := packet.(type) {
|
||||
case *UnCodablePacket:
|
||||
@@ -292,6 +302,7 @@ func (c *Conn) Send(packet proto_base.EncodeDecodeAble) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Receive reads and decodes a raw packet from the remote peer.
|
||||
func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
|
||||
if c.R == nil {
|
||||
return
|
||||
@@ -335,10 +346,12 @@ func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
// NewConn creates a new Conn with default settings and a background context.
|
||||
func NewConn() (ret *Conn) {
|
||||
return NewConnCtx(context.Background())
|
||||
}
|
||||
|
||||
// NewConnCtx creates a new Conn with the given context, setting the handshaking state and latest version.
|
||||
func NewConnCtx(ctx context.Context) (ret *Conn) {
|
||||
ret = &Conn{}
|
||||
ret.SetState(proto_base.Handshaking)
|
||||
|
||||
@@ -22,6 +22,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// LookUpTypeByPacketInfo looks up a packet's type information by direction, state, packet id, and protocol version.
|
||||
func LookUpTypeByPacketInfo(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.PacketInfo, ok bool) {
|
||||
pP, ok := pidMap[PidEtc{direction, state, pid, version}]
|
||||
if !ok {
|
||||
@@ -39,6 +40,7 @@ func LookUpTypeByPacketInfo(direction proto_base.Direction, state proto_base.Sta
|
||||
return
|
||||
}
|
||||
|
||||
// LookUpTypeByPacketInfoAndCopyType looks up a packet and returns a newly allocated instance of its type.
|
||||
func LookUpTypeByPacketInfoAndCopyType(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.EncodeDecodeAble, ok bool) {
|
||||
i, ok := LookUpTypeByPacketInfo(direction, state, pid, version)
|
||||
if !ok {
|
||||
@@ -48,6 +50,7 @@ func LookUpTypeByPacketInfoAndCopyType(direction proto_base.Direction, state pro
|
||||
return
|
||||
}
|
||||
|
||||
// LookupPacketInfoByType looks up a packet's type information by matching against the type of the given packet value.
|
||||
func LookupPacketInfoByType(packet proto_base.EncodeDecodeAble) (p proto_base.PacketInfo, ok bool) {
|
||||
undecodedPacket, isUndecoable := packet.(*UnCodablePacket)
|
||||
if isUndecoable {
|
||||
@@ -64,6 +67,7 @@ func LookupPacketInfoByType(packet proto_base.EncodeDecodeAble) (p proto_base.Pa
|
||||
return
|
||||
}
|
||||
|
||||
// LookupPacketInfoByTypeAndState looks up a packet's type information by type and state.
|
||||
func LookupPacketInfoByTypeAndState(packet proto_base.EncodeDecodeAble, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
|
||||
for _, p := range proto_generated.Packets {
|
||||
if state == p.State && reflect.TypeOf(p.Type) == reflect.TypeOf(packet) {
|
||||
@@ -73,6 +77,7 @@ func LookupPacketInfoByTypeAndState(packet proto_base.EncodeDecodeAble, state pr
|
||||
return
|
||||
}
|
||||
|
||||
// LookupPacketInfoByNameProtocolVersionAndState looks up a packet's type information by name, protocol version, and state.
|
||||
func LookupPacketInfoByNameProtocolVersionAndState(name string, version int32, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
|
||||
for _, p := range proto_generated.Packets {
|
||||
if state == p.State && p.Name == name && p.ProtocolVersion == version {
|
||||
@@ -82,6 +87,7 @@ func LookupPacketInfoByNameProtocolVersionAndState(name string, version int32, s
|
||||
return
|
||||
}
|
||||
|
||||
// LookupPacketInfoByNameProtocolVersionStateAndDirection looks up a packet's type information by name, protocol version, state, and direction.
|
||||
func LookupPacketInfoByNameProtocolVersionStateAndDirection(name string, version int32, state proto_base.State, direction proto_base.Direction) (p proto_base.PacketInfo, ok bool) {
|
||||
for _, p := range proto_generated.Packets {
|
||||
if state == p.State && p.Name == name && p.ProtocolVersion == version && p.Direction == direction {
|
||||
|
||||
@@ -27,18 +27,21 @@ type RawConn struct {
|
||||
MaxPacketSize int32
|
||||
}
|
||||
|
||||
// SetCompressionThreshold sets the packet compression threshold. Values below 0 disable compression.
|
||||
func (c *RawConn) SetCompressionThreshold(threshold int32) {
|
||||
c.CompressionThresholdMut.Lock()
|
||||
defer c.CompressionThresholdMut.Unlock()
|
||||
c.compressionThreshold = threshold
|
||||
}
|
||||
|
||||
// GetCompressionThreshold returns the current compression threshold.
|
||||
func (c *RawConn) GetCompressionThreshold() int32 {
|
||||
c.CompressionThresholdMut.RLock()
|
||||
defer c.CompressionThresholdMut.RUnlock()
|
||||
return c.compressionThreshold
|
||||
}
|
||||
|
||||
// SendRaw sends raw packet bytes over the connection, applying compression if the threshold is met.
|
||||
func (c *RawConn) SendRaw(rawPacketBytes []byte) (err error) {
|
||||
if c.MaxPacketSize > 0 && int32(len(rawPacketBytes)) > c.MaxPacketSize {
|
||||
err = errors.New("packet too large")
|
||||
@@ -90,6 +93,7 @@ func (c *RawConn) SendRaw(rawPacketBytes []byte) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ReceiveRaw reads and decompresses (if needed) a raw packet from the connection.
|
||||
func (c *RawConn) ReceiveRaw() (packetBytes []byte, err error) {
|
||||
rawPacketLen, err := proto_base.DecodeVarInt(c.R)
|
||||
if err != nil {
|
||||
@@ -142,6 +146,7 @@ func (c *RawConn) ReceiveRaw() (packetBytes []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// SetSecret enables AES/CFB8 encryption on the connection using the shared secret.
|
||||
func (c *RawConn) SetSecret(sharedSecret []byte) (err error) {
|
||||
var b cipher.Block
|
||||
b, err = aes.NewCipher(sharedSecret)
|
||||
|
||||
@@ -16,6 +16,7 @@ type Reader struct {
|
||||
Version int32
|
||||
}
|
||||
|
||||
// NewReader creates a new replay Reader from an io.Reader.
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
return &Reader{r: r}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ type Writer struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewWriter creates a new replay Writer from an io.Writer.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{w: w}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/admin-else/strom/mc/proto_generated"
|
||||
)
|
||||
|
||||
// SmartConvertibleTo reports whether a value of type from can be recursively converted to type to.
|
||||
func SmartConvertibleTo(from, to reflect.Type) bool {
|
||||
switch from.Kind() {
|
||||
case reflect.Struct:
|
||||
@@ -36,6 +37,7 @@ func SmartConvertibleTo(from, to reflect.Type) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// SmartConvert recursively converts a reflect.Value from one type to another compatible type.
|
||||
func SmartConvert(from reflect.Value, to reflect.Type) (ret reflect.Value) {
|
||||
switch from.Kind() {
|
||||
case reflect.Struct:
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// UnimplementedErr is returned when a code path is not yet implemented.
|
||||
var (
|
||||
UnimplementedErr = errors.New("unimplemented")
|
||||
)
|
||||
|
||||
// Direction indicates whether a packet is sent to the server or to the client.
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
@@ -18,6 +20,7 @@ const (
|
||||
ToClient
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of the Direction.
|
||||
func (d Direction) String() string {
|
||||
switch d {
|
||||
case ToServer:
|
||||
@@ -29,10 +32,12 @@ func (d Direction) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Opposite returns the reverse Direction.
|
||||
func (d Direction) Opposite() Direction {
|
||||
return d ^ 1
|
||||
}
|
||||
|
||||
// State represents the Minecraft connection state.
|
||||
type State int32
|
||||
|
||||
const (
|
||||
@@ -43,6 +48,7 @@ const (
|
||||
Play
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of the State.
|
||||
func (s State) String() string {
|
||||
switch s {
|
||||
case Handshaking:
|
||||
@@ -60,13 +66,16 @@ func (s State) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Actor represents a participant in a Minecraft connection.
|
||||
type Actor int
|
||||
|
||||
const (
|
||||
Servee Actor = iota // Servee is the one being served
|
||||
// Servee is the one being served (e.g. the backend server in a proxy).
|
||||
Servee Actor = iota
|
||||
Client
|
||||
)
|
||||
|
||||
// String returns a human-readable representation of the Actor.
|
||||
func (a Actor) String() string {
|
||||
switch a {
|
||||
case Servee:
|
||||
@@ -78,6 +87,7 @@ func (a Actor) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// SendDirection returns the Direction in which this Actor sends packets.
|
||||
func (a Actor) SendDirection() Direction {
|
||||
switch a {
|
||||
case Servee:
|
||||
@@ -89,6 +99,7 @@ func (a Actor) SendDirection() Direction {
|
||||
}
|
||||
}
|
||||
|
||||
// ReceiveDirection returns the Direction from which this Actor receives packets.
|
||||
func (a Actor) ReceiveDirection() Direction {
|
||||
switch a {
|
||||
case Servee:
|
||||
@@ -101,13 +112,19 @@ func (a Actor) ReceiveDirection() Direction {
|
||||
}
|
||||
|
||||
type (
|
||||
// ToDo is a placeholder type for unimplemented packet fields.
|
||||
ToDo struct{}
|
||||
// RestBuffer represents the remaining unread bytes in a packet buffer.
|
||||
RestBuffer []byte
|
||||
)
|
||||
|
||||
// ToDoError is returned when a ToDo placeholder is used.
|
||||
var ToDoError = errors.New("to do")
|
||||
|
||||
// BadTypeError is returned when an unexpected type is encountered.
|
||||
var BadTypeError = errors.New("bad type")
|
||||
|
||||
// Encode writes the RestBuffer to w.
|
||||
func (b *RestBuffer) Encode(w io.Writer) (err error) {
|
||||
n, err := w.Write(*b)
|
||||
if n != len(*b) {
|
||||
@@ -116,11 +133,13 @@ func (b *RestBuffer) Encode(w io.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Decode reads all remaining bytes from r into the RestBuffer.
|
||||
func (b *RestBuffer) Decode(r io.Reader) (err error) {
|
||||
*b, err = io.ReadAll(r)
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeString encodes a length-prefixed UTF-8 string in Minecraft varint format.
|
||||
func EncodeString(w io.Writer, s string) (err error) {
|
||||
err = EncodeVarInt(w, int32(len(s)))
|
||||
if err != nil {
|
||||
@@ -130,6 +149,7 @@ func EncodeString(w io.Writer, s string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeString decodes a length-prefixed UTF-8 string in Minecraft varint format.
|
||||
func DecodeString(r io.Reader) (ret string, err error) {
|
||||
l, err := DecodeVarInt(r)
|
||||
if err != nil {
|
||||
@@ -146,16 +166,19 @@ func DecodeString(r io.Reader) (ret string, err error) {
|
||||
return string(rawString), nil
|
||||
}
|
||||
|
||||
// Encode is a no-op stub that always returns ToDoError.
|
||||
func (t ToDo) Encode(_ io.Writer) (err error) {
|
||||
err = ToDoError
|
||||
return
|
||||
}
|
||||
|
||||
// Decode is a no-op stub that always returns ToDoError.
|
||||
func (t ToDo) Decode(_ io.Reader) (ret ToDo, err error) {
|
||||
err = ToDoError
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeVarInt encodes a signed 32-bit integer in Minecraft varint format.
|
||||
func EncodeVarInt(w io.Writer, v int32) (err error) {
|
||||
uv := uint32(v)
|
||||
for {
|
||||
@@ -175,6 +198,7 @@ func EncodeVarInt(w io.Writer, v int32) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeVarInt decodes a signed 32-bit integer in Minecraft varint format.
|
||||
func DecodeVarInt(r io.Reader) (ret int32, err error) {
|
||||
for i := range 5 { // 32/7
|
||||
var b uint8 = 0
|
||||
@@ -191,6 +215,7 @@ func DecodeVarInt(r io.Reader) (ret int32, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeVarLong encodes a signed 64-bit integer in Minecraft varlong format.
|
||||
func EncodeVarLong(w io.Writer, v int64) (err error) {
|
||||
uv := uint64(v)
|
||||
for {
|
||||
@@ -210,6 +235,7 @@ func EncodeVarLong(w io.Writer, v int64) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeVarLong decodes a signed 64-bit integer in Minecraft varlong format.
|
||||
func DecodeVarLong(r io.Reader) (ret int64, err error) {
|
||||
for i := range 10 { // ceil(64/7)
|
||||
var b uint8
|
||||
@@ -226,6 +252,7 @@ func DecodeVarLong(r io.Reader) (ret int64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ErroringIndex looks up key k in map m and returns an error if the key is not found.
|
||||
func ErroringIndex[K comparable, V any, M map[K]V](m M, i K) (v V, err error) {
|
||||
var ok bool
|
||||
v, ok = m[i]
|
||||
@@ -235,11 +262,14 @@ func ErroringIndex[K comparable, V any, M map[K]V](m M, i K) (v V, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeDecodeAble is implemented by types that can be encoded and decoded for
|
||||
// Minecraft protocol packets.
|
||||
type EncodeDecodeAble interface {
|
||||
Encode(w io.Writer) (err error)
|
||||
Decode(r io.ReadSeeker) (err error)
|
||||
}
|
||||
|
||||
// PacketInfo holds metadata about a Minecraft protocol packet.
|
||||
type PacketInfo struct {
|
||||
Type EncodeDecodeAble
|
||||
Name string
|
||||
@@ -249,6 +279,7 @@ type PacketInfo struct {
|
||||
ProtocolVersion int32
|
||||
}
|
||||
|
||||
// Bool2uint8 converts a bool to a uint8 (1 for true, 0 for false).
|
||||
func Bool2uint8(b bool) uint8 {
|
||||
if b {
|
||||
return 1
|
||||
@@ -256,6 +287,7 @@ func Bool2uint8(b bool) uint8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Bool2uint16 converts a bool to a uint16 (1 for true, 0 for false).
|
||||
func Bool2uint16(b bool) uint16 {
|
||||
if b {
|
||||
return 1
|
||||
@@ -263,6 +295,7 @@ func Bool2uint16(b bool) uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Bool2uint32 converts a bool to a uint32 (1 for true, 0 for false).
|
||||
func Bool2uint32(b bool) uint32 {
|
||||
if b {
|
||||
return 1
|
||||
@@ -270,6 +303,7 @@ func Bool2uint32(b bool) uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Bool2uint64 converts a bool to a uint64 (1 for true, 0 for false).
|
||||
func Bool2uint64(b bool) uint64 {
|
||||
if b {
|
||||
return 1
|
||||
@@ -277,13 +311,17 @@ func Bool2uint64(b bool) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// LpVec3d is a compressed 3D velocity vector used in Minecraft packets.
|
||||
type LpVec3d struct {
|
||||
X, Y, Z float64
|
||||
}
|
||||
|
||||
// Encode encodes the LpVec3d as a compressed velocity triple into w.
|
||||
func (v *LpVec3d) Encode(w io.Writer) error {
|
||||
return writeVelocity(w, *v)
|
||||
}
|
||||
|
||||
// Decode decodes a compressed velocity triple from r into the LpVec3d.
|
||||
func (v *LpVec3d) Decode(r io.ReadSeeker) error {
|
||||
v3, err := readVelocity(r)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ type ConfigServer struct {
|
||||
registryData map[string]any
|
||||
}
|
||||
|
||||
// ServeConfig runs the configuration phase: sends known packs, registry data, and tags, then transitions to play.
|
||||
func ServeConfig(c *proto.Conn) (err error) {
|
||||
cs := &ConfigServer{Conn: c}
|
||||
n, err := data.LoadRegistry(c.Version)
|
||||
|
||||
@@ -17,6 +17,7 @@ var (
|
||||
CantKickInStateErr = errors.New("cant kick in this state")
|
||||
)
|
||||
|
||||
// Kick sends a disconnect/kick packet appropriate for the connection's current state.
|
||||
func Kick(c *proto.Conn, reason *text.Component, status string) (err error) {
|
||||
if c.Actor != proto_base.Servee {
|
||||
err = NotClientErr
|
||||
|
||||
@@ -204,6 +204,7 @@ func (l *LoginServer) FinishLogin() (err error) {
|
||||
|
||||
type LoginServerSetting func(*LoginServer)
|
||||
|
||||
// WithOtherAccount returns a setting that overrides the login identity with the given account.
|
||||
func WithOtherAccount(a *api2.Account) LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.Given = &NameAndUUID{
|
||||
@@ -213,12 +214,14 @@ func WithOtherAccount(a *api2.Account) LoginServerSetting {
|
||||
}
|
||||
}
|
||||
|
||||
// WithRawStatus returns a setting that sets the raw JSON status response payload.
|
||||
func WithRawStatus(status []byte) LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.Status = status
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatus marshals a StatusResponse to JSON and passes it to WithRawStatus.
|
||||
func WithStatus(response StatusResponse) LoginServerSetting {
|
||||
var status []byte
|
||||
status, err := json.Marshal(response)
|
||||
@@ -229,6 +232,7 @@ func WithStatus(response StatusResponse) LoginServerSetting {
|
||||
return WithRawStatus(status)
|
||||
}
|
||||
|
||||
// WithCompatibleVersionsRange returns a setting that restricts the server to protocol versions in [start, end].
|
||||
func WithCompatibleVersionsRange(start, end int32) LoginServerSetting {
|
||||
var versions []int32
|
||||
for i := start; i <= end; i++ {
|
||||
@@ -237,24 +241,28 @@ func WithCompatibleVersionsRange(start, end int32) LoginServerSetting {
|
||||
return WithCompatibleVersions(versions...)
|
||||
}
|
||||
|
||||
// WithCompatibleVersions returns a setting that restricts the server to the given protocol versions.
|
||||
func WithCompatibleVersions(versions ...int32) LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.CompatibleVersions = versions
|
||||
}
|
||||
}
|
||||
|
||||
// WithoutOnlineMode returns a setting that disables online-mode authentication.
|
||||
func WithoutOnlineMode() LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.OnlineMode = false
|
||||
}
|
||||
}
|
||||
|
||||
// WithoutEncryption returns a setting that disables connection encryption.
|
||||
func WithoutEncryption() LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.Encryption = false
|
||||
}
|
||||
}
|
||||
|
||||
// ServeLogin runs the full login sequence (handshake, encryption, auth, and configuration handoff) on a connection.
|
||||
func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer, err error) {
|
||||
ret = &LoginServer{Conn: c}
|
||||
ret.OnlineMode = true
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/admin-else/strom/mc/text"
|
||||
)
|
||||
|
||||
// Servee wraps a net.Conn in a new proto.Conn configured for the servee side.
|
||||
func Servee(c net.Conn) (ret *proto.Conn) {
|
||||
ret = proto.NewConn()
|
||||
ret.Actor = proto_base.Servee
|
||||
@@ -24,6 +25,7 @@ type ConnAcceptor func(c net.Conn) (err error)
|
||||
|
||||
type Factory func(c *proto.Conn) (h ConnAcceptor, err error)
|
||||
|
||||
// ServeClient accepts a single client connection, creates a handler via the factory, and runs it.
|
||||
func ServeClient(cNet net.Conn, factory Factory) {
|
||||
c := Servee(cNet)
|
||||
defer c.Close()
|
||||
@@ -40,6 +42,7 @@ func ServeClient(cNet net.Conn, factory Factory) {
|
||||
}
|
||||
}
|
||||
|
||||
// StartServerWithFactory listens on the given address and spawns ServeClient for every accepted connection.
|
||||
func StartServerWithFactory(listenAddr string, factory Factory) (err error) {
|
||||
l, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
@@ -57,6 +60,8 @@ func StartServerWithFactory(listenAddr string, factory Factory) (err error) {
|
||||
|
||||
var ShutDownServerErr = errors.New("server shutting down")
|
||||
|
||||
// StartServerWithOnConn listens on the given address and calls onConn for every accepted connection.
|
||||
// The server loop stops when onConn returns ShutDownServerErr.
|
||||
func StartServerWithOnConn(listenAddr string, onConn func(c *proto.Conn) (err error)) (err error) {
|
||||
l, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
|
||||
@@ -56,6 +56,7 @@ func (p *StatusServer) OnStatusPing(packet *v1_21_8.StatusToServerPacketPing) (e
|
||||
return p.Send(&v1_21_8.StatusToClientPacketPing{Time: packet.Time})
|
||||
}
|
||||
|
||||
// ServeStatus runs the server list ping sequence, responding with the given StatusResponse.
|
||||
func ServeStatus(c *proto.Conn, s StatusResponse) (err error) {
|
||||
status, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
|
||||
@@ -526,6 +526,7 @@ func replacePlaceholders(s string, values map[string]any) string {
|
||||
return result
|
||||
}
|
||||
|
||||
// PrettyPlaceholders parses a legacy formatted string with named placeholders replaced from the given map, returning a Component.
|
||||
func PrettyPlaceholders(format string, placeholders map[string]any) *Component {
|
||||
s := replacePlaceholders(format, placeholders)
|
||||
return Pretty(s)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
var boundaries = []rune{' ', '_', '-', '.', ',', ';', ':', '(', ')', '[', ']', '{', '}'}
|
||||
|
||||
// CamelCase converts a string with word boundaries to CamelCase.
|
||||
func CamelCase(s string) string {
|
||||
ret := ""
|
||||
atWordBoundary := true
|
||||
@@ -24,6 +25,7 @@ func CamelCase(s string) string {
|
||||
return ret
|
||||
}
|
||||
|
||||
// SnakeCase converts a CamelCase string to snake_case.
|
||||
func SnakeCase(s string) string {
|
||||
ret := ""
|
||||
for i, c := range s {
|
||||
@@ -38,6 +40,7 @@ func SnakeCase(s string) string {
|
||||
return ret
|
||||
}
|
||||
|
||||
// FirstLetterLower returns the string with the first rune lowercased.
|
||||
func FirstLetterLower(s string) string {
|
||||
return string(unicode.ToLower(rune(s[0]))) + s[1:]
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package util
|
||||
|
||||
import "math"
|
||||
|
||||
// FloorDiv returns floor division (rounds toward negative infinity).
|
||||
func FloorDiv(a, b int32) int32 {
|
||||
return (a - ((a%b + b) % b)) / b
|
||||
}
|
||||
|
||||
// BpeByNum returns the minimum bits per element needed to represent x distinct values.
|
||||
func BpeByNum(x float64) uint8 {
|
||||
return uint8(math.Ceil(math.Log2(x)))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package util
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MustOk returns v if ok is true, otherwise panics.
|
||||
func MustOk[T any](v T, ok bool) T {
|
||||
if !ok {
|
||||
panic("not ok")
|
||||
@@ -9,6 +10,7 @@ func MustOk[T any](v T, ok bool) T {
|
||||
return v
|
||||
}
|
||||
|
||||
// MustT returns v if err is nil, otherwise panics.
|
||||
func MustT[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -16,12 +18,14 @@ func MustT[T any](v T, err error) T {
|
||||
return v
|
||||
}
|
||||
|
||||
// Must panics if err is non-nil.
|
||||
func Must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertTypeError asserts that v is of type T and returns it, or returns an error.
|
||||
func AssertTypeError[T any](v any) (T, error) {
|
||||
var zero T
|
||||
r, ok := v.(T)
|
||||
@@ -31,6 +35,7 @@ func AssertTypeError[T any](v any) (T, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// MapGetError returns the value for key k from map m, or an error if not present.
|
||||
func MapGetError[K comparable, V any, M map[K]V](m M, k K) (V, error) {
|
||||
v, ok := m[k]
|
||||
if !ok {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
// OrderedKeys returns the keys of the map sorted in ascending order.
|
||||
func OrderedKeys[Map map[K]V, K cmp.Ordered, V any](m Map) []K {
|
||||
keysIter := maps.Keys(m)
|
||||
keys := slices.Collect(keysIter)
|
||||
@@ -13,6 +14,7 @@ func OrderedKeys[Map map[K]V, K cmp.Ordered, V any](m Map) []K {
|
||||
return keys
|
||||
}
|
||||
|
||||
// AssertAndConvertMapValues asserts and converts all values in a map[string]any to type T.
|
||||
func AssertAndConvertMapValues[T any, M map[K]any, K cmp.Ordered](m M) map[K]T {
|
||||
ret := map[K]T{}
|
||||
for k, v := range m {
|
||||
@@ -21,6 +23,7 @@ func AssertAndConvertMapValues[T any, M map[K]any, K cmp.Ordered](m M) map[K]T {
|
||||
return ret
|
||||
}
|
||||
|
||||
// ReverseMap returns a new map with keys and values swapped.
|
||||
func ReverseMap[M map[K]V, K cmp.Ordered, V cmp.Ordered](m M) map[V]K {
|
||||
ret := map[V]K{}
|
||||
for k, v := range m {
|
||||
@@ -29,6 +32,7 @@ func ReverseMap[M map[K]V, K cmp.Ordered, V cmp.Ordered](m M) map[V]K {
|
||||
return ret
|
||||
}
|
||||
|
||||
// PopFront removes and returns the first element of the slice.
|
||||
func PopFront[T any](s []T) (T, []T, bool) {
|
||||
if len(s) == 0 {
|
||||
var zero T
|
||||
@@ -37,6 +41,7 @@ func PopFront[T any](s []T) (T, []T, bool) {
|
||||
return s[0], s[1:], true
|
||||
}
|
||||
|
||||
// CombineNamAndData returns a slice containing name and data.
|
||||
func CombineNamAndData(name string, data any) []any {
|
||||
return []any{name, data}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user