RegionIO/internal/network/login.go
Master290 a7bb9496ae Initial commit: RegionIO Minecraft server core (26.1.2/protocol 775)
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.
2026-06-24 00:32:51 +03:00

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])
}