DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

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

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

No files matched your search

+327
View File
@@ -0,0 +1,327 @@
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
}