111 lines
3 KiB
Go
111 lines
3 KiB
Go
package network
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"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 {
|
|
if h.protocolVersion != protocol.ProtocolVersion {
|
|
return fmt.Errorf("unsupported protocol %d, want %d", h.protocolVersion, protocol.ProtocolVersion)
|
|
}
|
|
r := pkt.Body()
|
|
name, err := r.String()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !validPlayerName(name) {
|
|
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()
|
|
}
|
|
|
|
func validPlayerName(name string) bool {
|
|
if len(name) == 0 || len(name) > 16 {
|
|
return false
|
|
}
|
|
for _, r := range name {
|
|
if r != '_' && (r < '0' || r > '9') && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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])
|
|
}
|