Vanilla-faithful overworld generator (final_density + multi-noise biomes), full connection lifecycle (status/login/configuration/play), chunk streaming, creative block editing, and the protocol/nbt/registry infrastructure.
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package network
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"regionio/internal/protocol"
|
|
"regionio/internal/server"
|
|
)
|
|
|
|
// handleLogin drives the offline-mode login phase:
|
|
//
|
|
// C→S Login Start → derive offline profile
|
|
// S→C Set Compression → (optional) enable zlib for later packets
|
|
// S→C Login Success → confirm the profile
|
|
// C→S Login Acknowledged → switch to the Configuration phase
|
|
//
|
|
// Encryption (online mode) is intentionally not implemented here.
|
|
func (h *handler) handleLogin(pkt protocol.Packet) error {
|
|
switch pkt.ID {
|
|
case protocol.LoginStartID:
|
|
return h.handleLoginStart(pkt)
|
|
case protocol.LoginAcknowledgedID:
|
|
// Client acknowledges Login Success; both sides enter Configuration.
|
|
h.conn.SetState(protocol.StateConfiguration)
|
|
h.log.Info("player logged in",
|
|
"name", h.conn.Profile.Name,
|
|
"uuid", uuidString(h.conn.Profile.UUID))
|
|
return h.beginConfiguration()
|
|
default:
|
|
return errors.New("unexpected packet in login state")
|
|
}
|
|
}
|
|
|
|
func (h *handler) handleLoginStart(pkt protocol.Packet) error {
|
|
r := pkt.Body()
|
|
name, err := r.String()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if name == "" || len(name) > 16 {
|
|
return errors.New("invalid login name")
|
|
}
|
|
// The client also sends a UUID, but in offline mode we derive our own so it
|
|
// is stable and independent of what the client claims.
|
|
if _, err := r.UUID(); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.conn.Profile = server.Profile{
|
|
UUID: server.OfflineUUID(name),
|
|
Name: name,
|
|
}
|
|
|
|
// Negotiate compression before Login Success so that packet (and every
|
|
// later one) is sent in the compressed format.
|
|
if t := h.srv.Config().CompressionThreshold; t >= 0 {
|
|
w := protocol.NewWriter(protocol.VarIntLen(int32(t)))
|
|
w.VarInt(int32(t))
|
|
if err := h.conn.SendWriter(protocol.SetCompressionID, w); err != nil {
|
|
return err
|
|
}
|
|
h.conn.EnableCompression(int32(t))
|
|
}
|
|
|
|
return h.sendLoginSuccess()
|
|
}
|
|
|
|
// sendLoginSuccess writes the Login Success packet. For protocol 775 the body
|
|
// is: UUID, Username, then a VarInt-prefixed array of profile properties (none
|
|
// in offline mode).
|
|
func (h *handler) sendLoginSuccess() error {
|
|
p := h.conn.Profile
|
|
w := protocol.NewWriter(16 + len(p.Name) + 2)
|
|
w.UUID(p.UUID)
|
|
w.String(p.Name)
|
|
w.VarInt(0) // property count
|
|
return h.conn.SendWriter(protocol.LoginSuccessID, w)
|
|
}
|
|
|
|
// uuidString formats a UUID as the canonical 8-4-4-4-12 hex string.
|
|
func uuidString(u [16]byte) string {
|
|
const hexdigits = "0123456789abcdef"
|
|
var b [36]byte
|
|
j := 0
|
|
for i := 0; i < 16; i++ {
|
|
if i == 4 || i == 6 || i == 8 || i == 10 {
|
|
b[j] = '-'
|
|
j++
|
|
}
|
|
b[j] = hexdigits[u[i]>>4]
|
|
b[j+1] = hexdigits[u[i]&0x0f]
|
|
j += 2
|
|
}
|
|
return string(b[:j])
|
|
}
|