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

@@ -0,0 +1,128 @@
package protocol
import "encoding/binary"
const (
ChannelHeaderSize = 13
GameServerUpperHeaderSize = ChannelHeaderSize
GameServerUpperHeaderSize16 = 16
DefaultChannelClassification = 1
)
// ChannelHeader 是客户端发来的 raw upper 13 字节包头。
type ChannelHeader struct {
Classification byte
MsgID uint16
Length uint32
Checksum uint32
Seq uint16
}
type ChannelPacket struct {
Header ChannelHeader
Body []byte
}
// BuildChannelPacket 封装客户端到服务端方向使用的 13 字节 raw upper 包。
func BuildChannelPacket(msgID uint16, body []byte, seq uint16, classification byte) ([]byte, error) {
if classification == 0 {
classification = DefaultChannelClassification
}
total := ChannelHeaderSize + len(body)
if total > int(^uint32(0)) {
return nil, ErrPacketTooLarge
}
packet := make([]byte, total)
packet[0] = classification
binary.LittleEndian.PutUint16(packet[1:3], msgID)
binary.LittleEndian.PutUint32(packet[3:7], uint32(total))
binary.LittleEndian.PutUint16(packet[11:13], seq)
copy(packet[ChannelHeaderSize:], body)
sum, err := ChecksumRange(packet, 11, total-11)
if err != nil {
return nil, err
}
binary.LittleEndian.PutUint32(packet[7:11], sum)
return packet, nil
}
// BuildGameServerUpperPacket 封装 game 端口服务端发给客户端的 raw upper 包。
// 最新 C# 重写文件与 7001 upper 一致使用 13 字节头;16 字节头只保留为兼容实验。
func BuildGameServerUpperPacket(msgID uint16, body []byte, seq uint16, classification byte) ([]byte, error) {
return BuildGameServerUpperPacketWithHeaderSize(msgID, body, seq, classification, GameServerUpperHeaderSize)
}
// BuildGameServerUpperPacketWithHeaderSize 按指定头长封装 game raw upper。
//
// headerSize 只允许 13 或 16。16 字节模式会在 seq 后补 3 个保留字节,
// 用于回退旧实验;主路径必须保持 13 字节,避免客户端按 ChannelPacket 读体时错位。
func BuildGameServerUpperPacketWithHeaderSize(msgID uint16, body []byte, seq uint16, classification byte, headerSize int) ([]byte, error) {
if headerSize != ChannelHeaderSize && headerSize != GameServerUpperHeaderSize16 {
return nil, ErrPacketLength
}
total := headerSize + len(body)
if total > int(^uint32(0)) {
return nil, ErrPacketTooLarge
}
packet := make([]byte, total)
packet[0] = classification
binary.LittleEndian.PutUint16(packet[1:3], msgID)
binary.LittleEndian.PutUint32(packet[3:7], uint32(total))
binary.LittleEndian.PutUint16(packet[11:13], seq)
copy(packet[headerSize:], body)
sum, err := ChecksumRange(packet, 11, total-11)
if err != nil {
return nil, err
}
binary.LittleEndian.PutUint32(packet[7:11], sum)
return packet, nil
}
func ParseChannelPacket(packet []byte) (ChannelPacket, error) {
return parseChannelPacket(packet, true)
}
// ParseChannelPacketUnchecked 只按 13 字节 upper 包头切 body,不校验 checksum。
//
// 最新 game 端口上行的部分 raw upper 包会在 body 里携带 codec/encrypted payload,
// 线上的 0x02B2 样本显示同 msg/length 下 body 每次变化但 header checksum 固定。
// 分流阶段必须先保住 msg/length 语义,checksum 只能作为后续诊断信号。
func ParseChannelPacketUnchecked(packet []byte) (ChannelPacket, error) {
return parseChannelPacket(packet, false)
}
func parseChannelPacket(packet []byte, verifyChecksum bool) (ChannelPacket, error) {
if len(packet) < ChannelHeaderSize {
return ChannelPacket{}, ErrPacketTooShort
}
header := ChannelHeader{
Classification: packet[0],
MsgID: binary.LittleEndian.Uint16(packet[1:3]),
Length: binary.LittleEndian.Uint32(packet[3:7]),
Checksum: binary.LittleEndian.Uint32(packet[7:11]),
Seq: binary.LittleEndian.Uint16(packet[11:13]),
}
if header.Length < ChannelHeaderSize || int(header.Length) != len(packet) {
return ChannelPacket{}, ErrPacketLength
}
if verifyChecksum {
sum, err := ChecksumRange(packet, 11, len(packet)-11)
if err != nil {
return ChannelPacket{}, err
}
if sum != header.Checksum {
return ChannelPacket{}, ErrChecksumInvalid
}
}
return ChannelPacket{
Header: header,
Body: cloneBytes(packet[ChannelHeaderSize:]),
}, nil
}
func cloneBytes(data []byte) []byte {
if len(data) == 0 {
return nil
}
return append([]byte(nil), data...)
}