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,105 @@
package protocol
import (
"bytes"
"encoding/binary"
"longheng.io/server/internal/modules/dnf/dnfenum"
)
const (
LegacyChannelHeaderSize = 11
DofLoginTokenSize = 32
DofLoginPrefaceSize = LegacyChannelHeaderSize + DofLoginTokenSize
DofAskChannelSize = 35
)
// IsDofLoginPrefacePrefix 判断连接开头是否为 NoPack/DOF 登录预握手。
func IsDofLoginPrefacePrefix(data []byte) bool {
return hasLegacyPrefix(data, 0, dnfenum.LegacyMsgDofLoginPreface)
}
// IsDofChannelProbePrefix 判断是否为 NoPack/DOF 预登录中的 0x09 脚本请求。
func IsDofChannelProbePrefix(data []byte) bool {
return hasLegacyPrefix(data, 0, dnfenum.LegacyMsgDofGetScript)
}
// IsDofAskChannelPrefix 判断是否为 DOF 预登录后的频道目录请求。
func IsDofAskChannelPrefix(data []byte) bool {
return hasLegacyPrefix(data, 0, dnfenum.LegacyMsgAskChannelInfo)
}
// BuildDofLoginAck 构造 DOF 预登录 ack。
//
// 真实客户端先只读前 11 字节 ack,后续 4 字节保留位和 32 字节 key
// 仍按旧服格式一次写出,避免后续读取阶段缺数据。
func BuildDofLoginAck(aesKey string) []byte {
body := make([]byte, 36)
copy(body[4:], []byte(aesKey))
return BuildLegacyChannelPacket(dnfenum.LegacyMsgDofLoginAck, body)
}
// BuildLegacyChannelPacket 构造 DOF 旧式频道包。
func BuildLegacyChannelPacket(msg dnfenum.LegacyChannelMsg, body []byte) []byte {
return buildLegacyPacket(dnfenum.ChannelPacketClass, msg, body)
}
// BuildLegacyClientPacket 构造测试和兼容入口使用的客户端旧式频道包。
func BuildLegacyClientPacket(msg dnfenum.LegacyChannelMsg, body []byte) []byte {
total := LegacyChannelHeaderSize + len(body)
if fixed := legacyFixedLength(msg); fixed > total {
total = fixed
}
packet := make([]byte, total)
writeLegacyHeader(packet[:LegacyChannelHeaderSize], 0, byte(msg), total)
copy(packet[LegacyChannelHeaderSize:], body)
return packet
}
func buildLegacyPacket(command byte, msg dnfenum.LegacyChannelMsg, body []byte) []byte {
packet := make([]byte, LegacyChannelHeaderSize+len(body))
writeLegacyHeader(packet[:LegacyChannelHeaderSize], command, byte(msg), len(packet))
copy(packet[LegacyChannelHeaderSize:], body)
return packet
}
// LegacyPayload 返回旧式频道包的 payload 副本。
func LegacyPayload(packet []byte) []byte {
if len(packet) <= LegacyChannelHeaderSize {
return nil
}
return cloneBytes(packet[LegacyChannelHeaderSize:])
}
func hasLegacyPrefix(data []byte, command byte, msg dnfenum.LegacyChannelMsg) bool {
if len(data) < LegacyChannelHeaderSize {
return false
}
var want [LegacyChannelHeaderSize]byte
writeLegacyHeader(want[:], command, byte(msg), legacyFixedLength(msg))
return bytes.Equal(data[:LegacyChannelHeaderSize], want[:])
}
func writeLegacyHeader(header []byte, command byte, msgID byte, totalLen int) {
header[0] = command
header[1] = msgID
binary.LittleEndian.PutUint32(header[2:6], uint32(totalLen))
header[10] = 1
}
func legacyFixedLength(msg dnfenum.LegacyChannelMsg) int {
switch msg {
case dnfenum.LegacyMsgDofLoginPreface:
return DofLoginPrefaceSize
case dnfenum.LegacyMsgDofGetScript:
return LegacyChannelHeaderSize
case dnfenum.LegacyMsgAskChannelInfo:
return DofAskChannelSize
default:
length := int(uint16(msg) >> 8)
if length > 0 {
return length
}
return LegacyChannelHeaderSize
}
}