Files
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

129 lines
4.3 KiB
Go
Raw Permalink 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"
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...)
}