服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
106 lines
3.3 KiB
Go
106 lines
3.3 KiB
Go
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
|
|
}
|
|
}
|