DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,214 @@
|
||||
package protocol
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
const (
|
||||
TCPOuterHeaderSize = 12
|
||||
InnerHeaderSize = 9
|
||||
MinTCPFrameSize = TCPOuterHeaderSize + InnerHeaderSize + LatestGameHeaderSize
|
||||
NormalInnerKind = 2
|
||||
)
|
||||
|
||||
// TransportOptions 控制最新 game TCP/UDP 外壳字段。
|
||||
type TransportOptions struct {
|
||||
Sequence uint32
|
||||
Route byte
|
||||
Flag byte
|
||||
OuterKind uint16
|
||||
OuterValue4 uint32
|
||||
OuterValue8 uint32
|
||||
}
|
||||
|
||||
// LatestGameTransportHeader 是最新 game transport 外壳视图。
|
||||
type LatestGameTransportHeader struct {
|
||||
OuterKind uint16
|
||||
TotalLength uint16
|
||||
OuterValue4 uint32
|
||||
OuterValue8 uint32
|
||||
InnerKind byte
|
||||
Sequence uint32
|
||||
BusinessLength uint16
|
||||
Route byte
|
||||
Flag byte
|
||||
}
|
||||
|
||||
// LatestGameTransportRecord 是一个 transport inner record。
|
||||
type LatestGameTransportRecord struct {
|
||||
TransportHeader LatestGameTransportHeader
|
||||
GameHeader LatestGameHeader
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// BuildLatestGameTCP 按最新 game TCP transport 封包。
|
||||
//
|
||||
// 对应 IDA:0x35F27E0 按 frame+2 的 u16 totalLen 拆 TCP 帧;
|
||||
// 0x35EC7A0 跳过 12 字节外层;0x35EDED0 按 u8 kind、u32 seq、+5 长度、+9 payload 解析 kind=2 inner record。
|
||||
func BuildLatestGameTCP(cmd byte, typ uint16, body []byte, options TransportOptions) ([]byte, error) {
|
||||
businessPacket, err := BuildLatestGamePacket(cmd, typ, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildLatestGameTCPFromBusinessPacket(businessPacket, options)
|
||||
}
|
||||
|
||||
// BuildLatestGameTCPFromBusinessPacket 把 7 字节业务包包进最新 TCP transport。
|
||||
func BuildLatestGameTCPFromBusinessPacket(businessPacket []byte, options TransportOptions) ([]byte, error) {
|
||||
if len(businessPacket) < LatestGameHeaderSize {
|
||||
return nil, ErrPacketTooShort
|
||||
}
|
||||
if len(businessPacket) > int(^uint16(0)) {
|
||||
return nil, ErrPacketTooLarge
|
||||
}
|
||||
total := TCPOuterHeaderSize + InnerHeaderSize + len(businessPacket)
|
||||
if total > int(^uint16(0)) {
|
||||
return nil, ErrPacketTooLarge
|
||||
}
|
||||
packet := make([]byte, total)
|
||||
binary.LittleEndian.PutUint16(packet[0:2], options.OuterKind)
|
||||
binary.LittleEndian.PutUint16(packet[2:4], uint16(total))
|
||||
binary.LittleEndian.PutUint32(packet[4:8], options.OuterValue4)
|
||||
binary.LittleEndian.PutUint32(packet[8:12], options.OuterValue8)
|
||||
writeInnerRecord(packet[TCPOuterHeaderSize:], businessPacket, options)
|
||||
return packet, nil
|
||||
}
|
||||
|
||||
// BuildLatestGameUDP 按最新 game UDP inner record 封包。
|
||||
func BuildLatestGameUDP(cmd byte, typ uint16, body []byte, options TransportOptions) ([]byte, error) {
|
||||
businessPacket, err := BuildLatestGamePacket(cmd, typ, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildLatestGameUDPFromBusinessPacket(businessPacket, options)
|
||||
}
|
||||
|
||||
// BuildLatestGameUDPFromBusinessPacket 把 7 字节业务包包进 UDP inner record。
|
||||
func BuildLatestGameUDPFromBusinessPacket(businessPacket []byte, options TransportOptions) ([]byte, error) {
|
||||
if len(businessPacket) < LatestGameHeaderSize {
|
||||
return nil, ErrPacketTooShort
|
||||
}
|
||||
if len(businessPacket) > int(^uint16(0)) {
|
||||
return nil, ErrPacketTooLarge
|
||||
}
|
||||
packet := make([]byte, InnerHeaderSize+len(businessPacket))
|
||||
writeInnerRecord(packet, businessPacket, options)
|
||||
return packet, nil
|
||||
}
|
||||
|
||||
// ParseLatestGameTCPRecords 解析一个完整最新 game TCP frame。
|
||||
func ParseLatestGameTCPRecords(frame []byte) ([]LatestGameTransportRecord, error) {
|
||||
if len(frame) < TCPOuterHeaderSize {
|
||||
return nil, ErrPacketTooShort
|
||||
}
|
||||
total := int(binary.LittleEndian.Uint16(frame[2:4]))
|
||||
if total < MinTCPFrameSize || total > len(frame) {
|
||||
return nil, ErrPacketLength
|
||||
}
|
||||
outerKind := binary.LittleEndian.Uint16(frame[0:2])
|
||||
outerValue4 := binary.LittleEndian.Uint32(frame[4:8])
|
||||
outerValue8 := binary.LittleEndian.Uint32(frame[8:12])
|
||||
records := make([]LatestGameTransportRecord, 0, 1)
|
||||
offset := TCPOuterHeaderSize
|
||||
for offset < total {
|
||||
record, consumed, err := parseInnerRecord(frame, offset, total-offset, outerKind, uint16(total), outerValue4, outerValue8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, record)
|
||||
offset += consumed
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, ErrPacketLength
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// ParseLatestGameUDPRecords 解析一个或多个 UDP inner record。
|
||||
func ParseLatestGameUDPRecords(packet []byte) ([]LatestGameTransportRecord, error) {
|
||||
if len(packet) < InnerHeaderSize+LatestGameHeaderSize {
|
||||
return nil, ErrPacketTooShort
|
||||
}
|
||||
records := make([]LatestGameTransportRecord, 0, 1)
|
||||
offset := 0
|
||||
for offset < len(packet) {
|
||||
record, consumed, err := parseInnerRecord(packet, offset, len(packet)-offset, 0, 0, 0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, record)
|
||||
offset += consumed
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, ErrPacketLength
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// SplitLatestGameTCPFrames 从流式 buffer 中切出完整最新 game TCP frame。
|
||||
//
|
||||
// 如果 frame+2 的长度小于最小包长,按客户端拆帧逻辑跳 1 字节重同步。
|
||||
func SplitLatestGameTCPFrames(buffer []byte) (frames [][]byte, remaining []byte, skipped int) {
|
||||
offset := 0
|
||||
for offset <= len(buffer)-4 {
|
||||
frameLength := int(binary.LittleEndian.Uint16(buffer[offset+2 : offset+4]))
|
||||
if frameLength < MinTCPFrameSize {
|
||||
offset++
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
if len(buffer)-offset < frameLength {
|
||||
break
|
||||
}
|
||||
frames = append(frames, cloneBytes(buffer[offset:offset+frameLength]))
|
||||
offset += frameLength
|
||||
}
|
||||
return frames, cloneBytes(buffer[offset:]), skipped
|
||||
}
|
||||
|
||||
func writeInnerRecord(target []byte, businessPacket []byte, options TransportOptions) {
|
||||
target[0] = NormalInnerKind
|
||||
binary.LittleEndian.PutUint32(target[1:5], options.Sequence)
|
||||
binary.LittleEndian.PutUint16(target[5:7], uint16(len(businessPacket)))
|
||||
target[7] = options.Route
|
||||
target[8] = options.Flag
|
||||
copy(target[InnerHeaderSize:], businessPacket)
|
||||
}
|
||||
|
||||
func parseInnerRecord(data []byte, offset int, available int, outerKind uint16, tcpTotal uint16, outerValue4 uint32, outerValue8 uint32) (LatestGameTransportRecord, int, error) {
|
||||
if available < InnerHeaderSize {
|
||||
return LatestGameTransportRecord{}, 0, ErrPacketTooShort
|
||||
}
|
||||
innerKind := data[offset]
|
||||
if innerKind != NormalInnerKind {
|
||||
return LatestGameTransportRecord{}, 0, ErrInnerKind
|
||||
}
|
||||
sequence := binary.LittleEndian.Uint32(data[offset+1 : offset+5])
|
||||
businessLength := int(binary.LittleEndian.Uint16(data[offset+5 : offset+7]))
|
||||
route := data[offset+7]
|
||||
flag := data[offset+8]
|
||||
recordLength := InnerHeaderSize + businessLength
|
||||
if businessLength < LatestGameHeaderSize || recordLength > available {
|
||||
return LatestGameTransportRecord{}, 0, ErrPacketLength
|
||||
}
|
||||
businessOffset := offset + InnerHeaderSize
|
||||
businessEnd := businessOffset + businessLength
|
||||
packet, err := ParseLatestGamePacket(data[businessOffset:businessEnd])
|
||||
if err != nil {
|
||||
return LatestGameTransportRecord{}, 0, err
|
||||
}
|
||||
record := LatestGameTransportRecord{
|
||||
TransportHeader: LatestGameTransportHeader{
|
||||
OuterKind: outerKind,
|
||||
TotalLength: tcpTotal,
|
||||
OuterValue4: outerValue4,
|
||||
OuterValue8: outerValue8,
|
||||
InnerKind: innerKind,
|
||||
Sequence: sequence,
|
||||
BusinessLength: uint16(businessLength),
|
||||
Route: route,
|
||||
Flag: flag,
|
||||
},
|
||||
GameHeader: packet.Header,
|
||||
Body: packet.Body,
|
||||
}
|
||||
return record, recordLength, nil
|
||||
}
|
||||
Reference in new issue
Block a user