Files
90CNServer/server/pkg/protocol/frame.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

328 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package protocol
import (
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"io"
"sort"
"strings"
)
// 帧协议基础常量定义固定头、版本和默认 body 上限。
const (
FrameMagic uint16 = 0x5047 // 固定帧协议探测魔数:"GP",不是品牌缩写。
FrameVersion1 uint8 = 1
FrameHeaderSizeV1 = 32
MaxFrameBodySize uint32 = 4 * 1024 * 1024
)
// 标准帧 metadata 键名;链路字段兼容 W3C trace context。
const (
FrameMetadataTraceParent = "traceparent"
FrameMetadataTraceState = "tracestate"
FrameMetadataBaggage = "baggage"
// FrameMetadataIdempotencyKey 由客户端为一次业务请求生成,重试和重连时保持不变。
FrameMetadataIdempotencyKey = "idempotency-key"
frameMetadataMagic = "MD"
frameMetadataVersion = 1
frameMaxHeaderSize = 255
)
// 帧标志位描述 body 的压缩、加密和校验状态。
const (
FrameFlagCompressed uint16 = 1 << iota
FrameFlagEncrypted
FrameFlagChecksum
)
// 帧 codec 标识 body 的序列化格式。
const (
FrameCodecRaw uint16 = iota
FrameCodecProtobuf
FrameCodecJSON
)
// 帧解析和校验错误。
var (
ErrInvalidFrameMagic = errors.New("protocol frame magic is invalid")
ErrInvalidFrameVersion = errors.New("protocol frame version is invalid")
ErrInvalidFrameHeader = errors.New("protocol frame header is invalid")
ErrFrameBodyTooLarge = errors.New("protocol frame body is too large")
ErrFrameChecksumInvalid = errors.New("protocol frame checksum is invalid")
ErrFrameMetadataInvalid = errors.New("protocol frame metadata is invalid")
ErrFrameMetadataTooLarge = errors.New("protocol frame metadata is too large")
)
// Frame 是 gateway frame_v1 线协议的内存表示。
type Frame struct {
Version uint8
Flags uint16
Codec uint16
PacketID int32
MsgID uint32
Sequence uint64
Checksum uint32
Body []byte
Metadata map[string]string
}
// FrameFromPacket 把旧 packet 包装为 frame_v1。
func FrameFromPacket(pkt Packet, sequence uint64) Frame {
flags := uint16(0)
if pkt.Compressed {
flags |= FrameFlagCompressed
}
return Frame{
Version: FrameVersion1,
Flags: flags,
Codec: FrameCodecRaw,
PacketID: pkt.PacketID,
MsgID: pkt.MsgID,
Sequence: sequence,
Body: append([]byte(nil), pkt.Body...),
}
}
// Packet 把 frame 还原成旧 packet 视图并拷贝 body。
func (f Frame) Packet() Packet {
return Packet{
PacketID: f.PacketID,
MsgID: f.MsgID,
Compressed: f.Flags&FrameFlagCompressed != 0,
Body: append([]byte(nil), f.Body...),
}
}
func packetFromFrame(frame Frame) Packet {
return Packet{
PacketID: frame.PacketID,
MsgID: frame.MsgID,
Compressed: frame.Flags&FrameFlagCompressed != 0,
Body: frame.Body,
}
}
// ReadFrame 从 reader 读取并校验一个 frame_v1 帧。
func ReadFrame(r io.Reader, maxBodySize uint32) (Frame, error) {
maxBodySize = normMaxFrameBody(maxBodySize)
var header [FrameHeaderSizeV1]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return Frame{}, err
}
if binary.LittleEndian.Uint16(header[0:2]) != FrameMagic {
return Frame{}, ErrInvalidFrameMagic
}
version := header[2]
if version != FrameVersion1 {
return Frame{}, fmt.Errorf("%w: %d", ErrInvalidFrameVersion, version)
}
headerSize := int(header[3])
if headerSize < FrameHeaderSizeV1 {
return Frame{}, fmt.Errorf("%w: size=%d", ErrInvalidFrameHeader, headerSize)
}
var metadataHeader []byte
if headerSize > FrameHeaderSizeV1 {
metadataHeader = make([]byte, headerSize-FrameHeaderSizeV1)
if _, err := io.ReadFull(r, metadataHeader); err != nil {
return Frame{}, err
}
}
metadata, err := decodeFrameMeta(metadataHeader)
if err != nil {
return Frame{}, err
}
bodySize := binary.LittleEndian.Uint32(header[24:28])
if bodySize > maxBodySize {
return Frame{}, fmt.Errorf("%w: %d", ErrFrameBodyTooLarge, bodySize)
}
body := make([]byte, bodySize)
if bodySize > 0 {
if _, err := io.ReadFull(r, body); err != nil {
return Frame{}, err
}
}
frame := Frame{
Version: version,
Flags: binary.LittleEndian.Uint16(header[4:6]),
Codec: binary.LittleEndian.Uint16(header[6:8]),
PacketID: packetIDFromWire(binary.LittleEndian.Uint32(header[8:12])),
MsgID: binary.LittleEndian.Uint32(header[12:16]),
Sequence: binary.LittleEndian.Uint64(header[16:24]),
Checksum: binary.LittleEndian.Uint32(header[28:32]),
Body: body,
Metadata: metadata,
}
if frame.Flags&FrameFlagChecksum != 0 && crc32.ChecksumIEEE(body) != frame.Checksum {
return Frame{}, ErrFrameChecksumInvalid
}
return frame, nil
}
// WriteFrame 把 frame_v1 帧写入 writer。
func WriteFrame(w io.Writer, frame Frame) error {
version := frame.Version
if version == 0 {
version = FrameVersion1
}
if version != FrameVersion1 {
return fmt.Errorf("%w: %d", ErrInvalidFrameVersion, version)
}
bodySize := len(frame.Body)
if bodySize > int(MaxFrameBodySize) || uint64(bodySize) > uint64(^uint32(0)) {
return fmt.Errorf("%w: %d", ErrFrameBodyTooLarge, bodySize)
}
metadataHeader, err := encodeFrameMeta(frame.Metadata)
if err != nil {
return err
}
headerSize := FrameHeaderSizeV1 + len(metadataHeader)
if headerSize > frameMaxHeaderSize {
return fmt.Errorf("%w: header_size=%d", ErrFrameMetadataTooLarge, headerSize)
}
if frame.Flags&FrameFlagChecksum != 0 {
frame.Checksum = crc32.ChecksumIEEE(frame.Body)
} else {
frame.Checksum = 0
}
var header [FrameHeaderSizeV1]byte
binary.LittleEndian.PutUint16(header[0:2], FrameMagic)
header[2] = version
header[3] = uint8(headerSize) //nolint:gosec // G115:headerSize 已校验不超过 frameMaxHeaderSize。
binary.LittleEndian.PutUint16(header[4:6], frame.Flags)
binary.LittleEndian.PutUint16(header[6:8], frame.Codec)
binary.LittleEndian.PutUint32(header[8:12], packetIDToWire(frame.PacketID))
binary.LittleEndian.PutUint32(header[12:16], frame.MsgID)
binary.LittleEndian.PutUint64(header[16:24], frame.Sequence)
binary.LittleEndian.PutUint32(header[24:28], uint32(bodySize))
binary.LittleEndian.PutUint32(header[28:32], frame.Checksum)
if bodySize == 0 {
return writeFullBuffers(w, header[:], metadataHeader)
}
return writeFullBuffers(w, header[:], metadataHeader, frame.Body)
}
func normMaxFrameBody(maxBodySize uint32) uint32 {
if maxBodySize == 0 {
return MaxFrameBodySize
}
return maxBodySize
}
func packetIDFromWire(value uint32) int32 {
return int32(value) //nolint:gosec // G115:协议线格式用 uint32 保存 int32 的补码位模式。
}
func packetIDToWire(value int32) uint32 {
return uint32(value) //nolint:gosec // G115:协议线格式用 uint32 保存 int32 的补码位模式。
}
// TraceContextMetadata 构造 frame metadata 中的链路追踪字段。
func TraceContextMetadata(traceParent, traceState string) map[string]string {
out := make(map[string]string, 2)
if traceParent = strings.TrimSpace(traceParent); traceParent != "" {
out[FrameMetadataTraceParent] = traceParent
}
if traceState = strings.TrimSpace(traceState); traceState != "" {
out[FrameMetadataTraceState] = traceState
}
if len(out) == 0 {
return nil
}
return out
}
// CloneFrameMetadata 规范化并拷贝 frame metadata。
func CloneFrameMetadata(metadata map[string]string) map[string]string {
if len(metadata) == 0 {
return nil
}
out := make(map[string]string, len(metadata))
for key, value := range metadata {
key = strings.ToLower(strings.TrimSpace(key))
value = strings.TrimSpace(value)
if key == "" || value == "" {
continue
}
out[key] = value
}
if len(out) == 0 {
return nil
}
return out
}
func encodeFrameMeta(metadata map[string]string) ([]byte, error) {
metadata = CloneFrameMetadata(metadata)
if len(metadata) == 0 {
return nil, nil
}
if len(metadata) > 255 {
return nil, fmt.Errorf("%w: entries=%d", ErrFrameMetadataTooLarge, len(metadata))
}
keys := make([]string, 0, len(metadata))
for key := range metadata {
keys = append(keys, key)
}
sort.Strings(keys)
out := []byte{frameMetadataMagic[0], frameMetadataMagic[1], frameMetadataVersion, byte(len(keys))} //nolint:gosec // G115:metadata 条目数已限制在 255 以内。
for _, key := range keys {
value := metadata[key]
if len(key) > 255 || len(value) > 255 {
return nil, fmt.Errorf("%w: %s", ErrFrameMetadataTooLarge, key)
}
out = append(out, byte(len(key)), byte(len(value))) //nolint:gosec // G115:key/value 长度已限制在 255 以内。
out = append(out, key...)
out = append(out, value...)
if FrameHeaderSizeV1+len(out) > frameMaxHeaderSize {
return nil, fmt.Errorf("%w: header_size=%d", ErrFrameMetadataTooLarge, FrameHeaderSizeV1+len(out))
}
}
return out, nil
}
func decodeFrameMeta(extra []byte) (map[string]string, error) {
if len(extra) == 0 {
return nil, nil
}
if len(extra) < 4 || string(extra[:2]) != frameMetadataMagic {
return nil, ErrFrameMetadataInvalid
}
if extra[2] != frameMetadataVersion {
return nil, fmt.Errorf("%w: version=%d", ErrFrameMetadataInvalid, extra[2])
}
count := int(extra[3])
pos := 4
out := make(map[string]string, count)
for idx := 0; idx < count; idx++ {
if pos+2 > len(extra) {
return nil, ErrFrameMetadataInvalid
}
keyLen := int(extra[pos])
valueLen := int(extra[pos+1])
pos += 2
if keyLen == 0 || valueLen == 0 || pos+keyLen+valueLen > len(extra) {
return nil, ErrFrameMetadataInvalid
}
key := strings.ToLower(strings.TrimSpace(string(extra[pos : pos+keyLen])))
pos += keyLen
value := strings.TrimSpace(string(extra[pos : pos+valueLen]))
pos += valueLen
if key == "" || value == "" {
return nil, ErrFrameMetadataInvalid
}
out[key] = value
}
if pos != len(extra) {
return nil, ErrFrameMetadataInvalid
}
return out, nil
}