Initial commit
This commit is contained in:
361
network/session.go
Normal file
361
network/session.go
Normal file
@@ -0,0 +1,361 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cubexteam/Frodding/net/info"
|
||||
"github.com/cubexteam/Frodding/net/packets"
|
||||
"github.com/cubexteam/Frodding/net/packets/bedrock"
|
||||
"github.com/cubexteam/Frodding/raknet"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
mu sync.Mutex
|
||||
server *Server
|
||||
rak *raknet.Session
|
||||
|
||||
UUID uuid.UUID
|
||||
XUID string
|
||||
Username string
|
||||
ClientID int64
|
||||
Language string
|
||||
SkinID string
|
||||
SkinData []byte
|
||||
CapeData []byte
|
||||
GeomName string
|
||||
GeomData string
|
||||
|
||||
Spawned bool
|
||||
X, Y, Z float32
|
||||
ViewDistance int32
|
||||
sentChunks map[[2]int32]bool
|
||||
}
|
||||
|
||||
func NewSession(srv *Server, rak *raknet.Session) *Session {
|
||||
return &Session{
|
||||
server: srv,
|
||||
rak: rak,
|
||||
UUID: uuid.New(),
|
||||
ViewDistance: 8,
|
||||
sentChunks: make(map[[2]int32]bool),
|
||||
Y: 64,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) SendPacket(pk packets.IPacket) {
|
||||
pk.Encode()
|
||||
s.sendBatch(pk.GetID(), pk.GetBuffer())
|
||||
}
|
||||
|
||||
func (s *Session) sendBatch(id byte, data []byte) {
|
||||
var payload bytes.Buffer
|
||||
payload.WriteByte(id)
|
||||
payload.Write(data)
|
||||
|
||||
var raw bytes.Buffer
|
||||
writeVarUint32(&raw, uint32(payload.Len()))
|
||||
raw.Write(payload.Bytes())
|
||||
|
||||
var compressed bytes.Buffer
|
||||
w, _ := zlib.NewWriterLevel(&compressed, zlib.BestSpeed)
|
||||
w.Write(raw.Bytes())
|
||||
w.Close()
|
||||
|
||||
batch := append([]byte{0xfe}, compressed.Bytes()...)
|
||||
s.rak.Send(batch, raknet.ReliableOrdered, 0)
|
||||
}
|
||||
|
||||
func writeVarUint32(buf *bytes.Buffer, v uint32) {
|
||||
for v >= 0x80 {
|
||||
buf.WriteByte(byte(v&0x7f) | 0x80)
|
||||
v >>= 7
|
||||
}
|
||||
buf.WriteByte(byte(v))
|
||||
}
|
||||
|
||||
func (s *Session) HandlePayload(data []byte) {
|
||||
defer func() { recover() }()
|
||||
if len(data) == 0 || data[0] != 0xfe {
|
||||
return
|
||||
}
|
||||
r, err := zlib.NewReader(bytes.NewReader(data[1:]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
decompressed, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
reader := bytes.NewReader(decompressed)
|
||||
for reader.Len() > 0 {
|
||||
pkLen, err := readVarUint32(reader)
|
||||
if err != nil || pkLen == 0 || int(pkLen) > reader.Len() {
|
||||
break
|
||||
}
|
||||
pkData := make([]byte, pkLen)
|
||||
reader.Read(pkData)
|
||||
s.handlePacketData(pkData)
|
||||
}
|
||||
}
|
||||
|
||||
func readVarUint32(r *bytes.Reader) (uint32, error) {
|
||||
var v uint32
|
||||
for i := uint(0); i < 35; i += 7 {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v |= uint32(b&0x7f) << i
|
||||
if b&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s *Session) handlePacketData(data []byte) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
id := data[0]
|
||||
body := data[1:]
|
||||
|
||||
switch id {
|
||||
case info.IDLogin:
|
||||
pk := bedrock.NewLoginPacket()
|
||||
pk.SetBuffer(body)
|
||||
pk.Decode()
|
||||
s.handleLogin(pk)
|
||||
case info.IDResourcePackClientResponse:
|
||||
pk := bedrock.NewResourcePackClientResponsePacket()
|
||||
pk.SetBuffer(body)
|
||||
pk.Decode()
|
||||
s.handleResourcePackResponse(pk)
|
||||
case info.IDMovePlayer:
|
||||
pk := bedrock.NewMovePlayerPacket()
|
||||
pk.SetBuffer(body)
|
||||
pk.Decode()
|
||||
s.handleMovePlayer(pk)
|
||||
case info.IDRequestChunkRadius:
|
||||
pk := bedrock.NewRequestChunkRadiusPacket()
|
||||
pk.SetBuffer(body)
|
||||
pk.Decode()
|
||||
s.handleChunkRadius(pk)
|
||||
case info.IDText:
|
||||
pk := bedrock.NewTextPacket(0, "", "")
|
||||
pk.SetBuffer(body)
|
||||
pk.Decode()
|
||||
s.handleText(pk)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) handleLogin(pk *bedrock.LoginPacket) {
|
||||
s.server.log.Infof("Player connecting: %s (protocol %d)", pk.Username, pk.Protocol)
|
||||
|
||||
if pk.Protocol < info.MinProtocol {
|
||||
s.SendPacket(bedrock.NewPlayStatusPacket(bedrock.PlayStatusLoginFailedClient))
|
||||
return
|
||||
}
|
||||
if pk.Protocol > info.LatestProtocol {
|
||||
s.SendPacket(bedrock.NewPlayStatusPacket(bedrock.PlayStatusLoginFailedServer))
|
||||
return
|
||||
}
|
||||
|
||||
s.UUID = pk.ClientUUID
|
||||
s.XUID = pk.ClientXUID
|
||||
s.Username = pk.Username
|
||||
s.ClientID = pk.ClientID
|
||||
s.Language = pk.Language
|
||||
s.SkinID = pk.SkinID
|
||||
s.SkinData = pk.SkinData
|
||||
s.CapeData = pk.CapeData
|
||||
s.GeomName = pk.GeometryName
|
||||
s.GeomData = pk.GeometryData
|
||||
|
||||
s.SendPacket(bedrock.NewPlayStatusPacket(bedrock.PlayStatusLoginSuccess))
|
||||
s.SendPacket(bedrock.NewResourcePackInfoPacket(false))
|
||||
}
|
||||
|
||||
func (s *Session) handleResourcePackResponse(pk *bedrock.ResourcePackClientResponsePacket) {
|
||||
switch pk.Status {
|
||||
case bedrock.ResourcePackResponseSendPacks:
|
||||
s.SendPacket(bedrock.NewResourcePackInfoPacket(false))
|
||||
case bedrock.ResourcePackResponseAllPacksSent:
|
||||
s.SendPacket(bedrock.NewResourcePackStackPacket(false))
|
||||
case bedrock.ResourcePackResponseCompleted:
|
||||
s.startGame()
|
||||
case bedrock.ResourcePackResponseRefused:
|
||||
s.SendPacket(bedrock.NewResourcePackStackPacket(false))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) startGame() {
|
||||
sg := bedrock.NewStartGamePacket()
|
||||
sg.EntityUniqueID = 1
|
||||
sg.EntityRuntimeID = 1
|
||||
sg.PlayerGameMode = 0
|
||||
sg.SpawnX = s.X
|
||||
sg.SpawnY = s.Y + 1
|
||||
sg.SpawnZ = s.Z
|
||||
sg.Seed = 12345
|
||||
sg.Dimension = 0
|
||||
sg.Generator = 1
|
||||
sg.GameMode = 0
|
||||
sg.Difficulty = 1
|
||||
sg.SpawnBX = 0
|
||||
sg.SpawnBY = 64
|
||||
sg.SpawnBZ = 0
|
||||
sg.AchievementsDisabled = true
|
||||
sg.Time = 6000
|
||||
sg.IsMultiplayer = true
|
||||
sg.BroadcastToLAN = true
|
||||
sg.CommandsEnabled = true
|
||||
sg.ForceTexturePacks = false
|
||||
sg.LevelName = s.server.cfg.ServerName
|
||||
sg.IsTrial = false
|
||||
sg.CurrentTick = 0
|
||||
sg.EnchantmentSeed = 0
|
||||
s.SendPacket(sg)
|
||||
|
||||
s.SendPacket(bedrock.NewSetTimePacket(6000))
|
||||
|
||||
adv := bedrock.NewAdventureSettingsPacket()
|
||||
adv.Flags = 0x20 | 0x80
|
||||
adv.CommandPermission = 0
|
||||
adv.Flags2 = 0x00
|
||||
adv.PlayerPermission = 1
|
||||
adv.EntityID = 1
|
||||
s.SendPacket(adv)
|
||||
|
||||
attr := bedrock.NewUpdateAttributesPacket(1)
|
||||
attr.Attributes = bedrock.DefaultAttributes()
|
||||
s.SendPacket(attr)
|
||||
}
|
||||
|
||||
func (s *Session) handleChunkRadius(pk *bedrock.RequestChunkRadiusPacket) {
|
||||
radius := pk.Radius
|
||||
if radius > 4 {
|
||||
radius = 4
|
||||
}
|
||||
if radius < 2 {
|
||||
radius = 2
|
||||
}
|
||||
s.ViewDistance = radius
|
||||
s.SendPacket(bedrock.NewChunkRadiusUpdatedPacket(radius))
|
||||
s.sendChunks()
|
||||
}
|
||||
|
||||
func (s *Session) sendChunks() {
|
||||
cx := int32(s.X) >> 4
|
||||
cz := int32(s.Z) >> 4
|
||||
r := s.ViewDistance
|
||||
chunkData := bedrock.BuildFlatChunk()
|
||||
|
||||
for x := cx - r; x <= cx+r; x++ {
|
||||
for z := cz - r; z <= cz+r; z++ {
|
||||
key := [2]int32{x, z}
|
||||
if s.sentChunks[key] {
|
||||
continue
|
||||
}
|
||||
s.sentChunks[key] = true
|
||||
s.SendPacket(bedrock.NewFullChunkDataPacket(x, z, chunkData))
|
||||
}
|
||||
}
|
||||
|
||||
if !s.Spawned {
|
||||
s.Spawned = true
|
||||
s.SendPacket(bedrock.NewPlayStatusPacket(bedrock.PlayStatusPlayerSpawn))
|
||||
s.server.log.Infof("%s joined the game", s.Username)
|
||||
s.server.BroadcastMessage(fmt.Sprintf("§e%s joined the game", s.Username))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) handleMovePlayer(pk *bedrock.MovePlayerPacket) {
|
||||
s.X = pk.X
|
||||
s.Y = pk.Y
|
||||
s.Z = pk.Z
|
||||
}
|
||||
|
||||
func (s *Session) handleText(pk *bedrock.TextPacket) {
|
||||
msg := pk.Message
|
||||
if strings.HasPrefix(msg, "/") {
|
||||
s.handleCommand(strings.TrimPrefix(msg, "/"))
|
||||
return
|
||||
}
|
||||
s.server.log.Chat(s.Username, msg)
|
||||
s.server.BroadcastMessage(fmt.Sprintf("<%s> %s", s.Username, msg))
|
||||
}
|
||||
|
||||
func (s *Session) handleCommand(cmd string) {
|
||||
args := strings.Fields(cmd)
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "list":
|
||||
sessions := s.server.GetSessions()
|
||||
names := make([]string, 0, len(sessions))
|
||||
for _, sess := range sessions {
|
||||
if sess.Spawned {
|
||||
names = append(names, sess.Username)
|
||||
}
|
||||
}
|
||||
s.SendMessage(fmt.Sprintf("§eOnline (%d/%d): %s", len(names), s.server.cfg.MaxPlayers, strings.Join(names, ", ")))
|
||||
case "gm":
|
||||
if len(args) < 2 {
|
||||
s.SendMessage("§cUsage: /gm <0|1|2>")
|
||||
return
|
||||
}
|
||||
var gm int32
|
||||
switch args[1] {
|
||||
case "0", "s", "survival":
|
||||
gm = 0
|
||||
case "1", "c", "creative":
|
||||
gm = 1
|
||||
case "2", "a", "adventure":
|
||||
gm = 2
|
||||
default:
|
||||
s.SendMessage("§cUsage: /gm <0|1|2>")
|
||||
return
|
||||
}
|
||||
s.setGameMode(gm)
|
||||
case "stop":
|
||||
s.server.BroadcastMessage("§cServer is shutting down!")
|
||||
s.server.Shutdown()
|
||||
default:
|
||||
s.SendMessage(fmt.Sprintf("§cUnknown command: %s", args[0]))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) setGameMode(gm int32) {
|
||||
s.SendPacket(bedrock.NewSetGameModePacket(gm))
|
||||
adv := bedrock.NewAdventureSettingsPacket()
|
||||
if gm == 1 {
|
||||
adv.Flags = 0x20 | 0x40
|
||||
adv.Flags2 = 0x40
|
||||
} else {
|
||||
adv.Flags = 0x20
|
||||
adv.Flags2 = 0x00
|
||||
}
|
||||
adv.CommandPermission = 0
|
||||
adv.PlayerPermission = 1
|
||||
adv.EntityID = 1
|
||||
s.SendPacket(adv)
|
||||
names := map[int32]string{0: "Survival", 1: "Creative", 2: "Adventure"}
|
||||
s.SendMessage(fmt.Sprintf("§aGame mode set to %s", names[gm]))
|
||||
}
|
||||
|
||||
func (s *Session) Disconnect(reason string) {
|
||||
s.SendPacket(bedrock.NewDisconnectPacket(reason, false))
|
||||
}
|
||||
|
||||
func (s *Session) SendMessage(msg string) {
|
||||
s.SendPacket(bedrock.NewTextPacket(bedrock.TextTypeSystem, "", msg))
|
||||
}
|
||||
Reference in New Issue
Block a user