- mihoyo-variant KCP in pure Go (28-byte conv+token header), UDP gateway with 0xFF/0x145/0x194 handshake, ms-precision KCP clock + retransmit ticker - protobuf for 4.6.51 generated from the AstralOS StarRail.proto (4932 messages) plus CmdID map from // CmdID: comments (2114 ids) - sdkserver port: query_dispatch/query_gateway (Gateserver pb + base64), hotfix fetch, auth endpoints, SRTools freesr-data.json upload - gameserver handlers: login flow, scene load/spawn with real monster entity tracking, lineup, avatar/skin, missions, tutorials (1249+855), bag/equip, challenge incl. StartChallenge 1787 layout + Tierce 8973 + peak 8948, all raw tierce replies - SceneCastSkill camera bug fix: only real scene monsters (tracked per session) trigger battle; attacking props/NPCs/air replies retcode 0 without battle_info so the camera stays locked on the character - end-to-end smoke test simulating a client over real UDP: handshake, PlayerGetToken, and both SceneCastSkill paths - reuses the Rust PS data files unchanged (freesr-data.json, persistent, res.json, teleports.json, challenge_data.json, mission_unlocks.json)
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
package gameserver
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
HeadMagic uint32 = 0x9D74C714
|
|
TailMagic uint32 = 0xD7A152C8
|
|
)
|
|
|
|
// NetPacket is the framed protocol inside the KCP stream.
|
|
type NetPacket struct {
|
|
CmdType uint16
|
|
Head []byte
|
|
Body []byte
|
|
}
|
|
|
|
var ErrMalformedPacket = errors.New("malformed packet")
|
|
|
|
func (p *NetPacket) Encode() []byte {
|
|
out := make([]byte, 0, 12+len(p.Head)+len(p.Body))
|
|
var be32 [4]byte
|
|
var be16 [2]byte
|
|
|
|
binary.BigEndian.PutUint32(be32[:], HeadMagic)
|
|
out = append(out, be32[:]...)
|
|
binary.BigEndian.PutUint16(be16[:], p.CmdType)
|
|
out = append(out, be16[:]...)
|
|
binary.BigEndian.PutUint16(be16[:], uint16(len(p.Head)))
|
|
out = append(out, be16[:]...)
|
|
binary.BigEndian.PutUint32(be32[:], uint32(len(p.Body)))
|
|
out = append(out, be32[:]...)
|
|
out = append(out, p.Head...)
|
|
out = append(out, p.Body...)
|
|
binary.BigEndian.PutUint32(be32[:], TailMagic)
|
|
out = append(out, be32[:]...)
|
|
return out
|
|
}
|
|
|
|
// DecodeNetPacket parses one framed packet (must be a complete packet).
|
|
func DecodeNetPacket(data []byte) (*NetPacket, error) {
|
|
if len(data) < 16 {
|
|
return nil, fmt.Errorf("%w: too short (%d)", ErrMalformedPacket, len(data))
|
|
}
|
|
if binary.BigEndian.Uint32(data[0:4]) != HeadMagic {
|
|
return nil, fmt.Errorf("%w: bad head magic", ErrMalformedPacket)
|
|
}
|
|
cmdType := binary.BigEndian.Uint16(data[4:6])
|
|
headLen := int(binary.BigEndian.Uint16(data[6:8]))
|
|
bodyLen := int(binary.BigEndian.Uint32(data[8:12]))
|
|
if len(data) < 12+headLen+bodyLen+4 {
|
|
return nil, fmt.Errorf("%w: truncated", ErrMalformedPacket)
|
|
}
|
|
if binary.BigEndian.Uint32(data[12+headLen+bodyLen:]) != TailMagic {
|
|
return nil, fmt.Errorf("%w: bad tail magic", ErrMalformedPacket)
|
|
}
|
|
return &NetPacket{
|
|
CmdType: cmdType,
|
|
Head: data[12 : 12+headLen],
|
|
Body: data[12+headLen : 12+headLen+bodyLen],
|
|
}, nil
|
|
}
|
|
|
|
// NetOperation is the 20-byte handshake control packet.
|
|
type NetOperation struct {
|
|
Head uint32
|
|
Param1 uint32
|
|
Param2 uint32
|
|
Data uint32
|
|
Tail uint32
|
|
}
|
|
|
|
const (
|
|
NetOpConnectHead uint32 = 0xFF
|
|
NetOpConnectTail uint32 = 0xFFFFFFFF
|
|
NetOpAcceptHead uint32 = 0x145
|
|
NetOpAcceptTail uint32 = 0x14514545
|
|
NetOpDropHead uint32 = 0x194
|
|
NetOpDropTail uint32 = 0x19419494
|
|
)
|
|
|
|
func DecodeNetOperation(data []byte) (*NetOperation, error) {
|
|
if len(data) < 20 {
|
|
return nil, fmt.Errorf("%w: netop too short", ErrMalformedPacket)
|
|
}
|
|
return &NetOperation{
|
|
Head: binary.BigEndian.Uint32(data[0:4]),
|
|
Param1: binary.BigEndian.Uint32(data[4:8]),
|
|
Param2: binary.BigEndian.Uint32(data[8:12]),
|
|
Data: binary.BigEndian.Uint32(data[12:16]),
|
|
Tail: binary.BigEndian.Uint32(data[16:20]),
|
|
}, nil
|
|
}
|
|
|
|
func (o *NetOperation) Encode() []byte {
|
|
out := make([]byte, 20)
|
|
binary.BigEndian.PutUint32(out[0:4], o.Head)
|
|
binary.BigEndian.PutUint32(out[4:8], o.Param1)
|
|
binary.BigEndian.PutUint32(out[8:12], o.Param2)
|
|
binary.BigEndian.PutUint32(out[12:16], o.Data)
|
|
binary.BigEndian.PutUint32(out[16:20], o.Tail)
|
|
return out
|
|
}
|