Files
CAtlas0784 59ea97b1a6 feat: port GaC-hkrpg-ps to Go with SceneCastSkill camera-detach fix
- 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)
2026-10-07 00:22:23 +07:00

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
}