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,154 @@
// 本文件解析时装和称号类 C2S 请求体。
// 字段顺序来自当前已整理的 C# 旧实现与 Go 侧现有协议号对齐结果,成功回包仍需 EXE/MCP 闭合。
package avatartitle
import (
"encoding/binary"
"fmt"
)
const listTypeAvatar byte = 1
// CompoundAvatarRequest 描述时装合成请求。
type CompoundAvatarRequest struct {
ConsumeSlot int16
Slot1 int16
ExpectedItemID1 int32
Slot2 int16
ExpectedItemID2 int32
RequestItemID int32
AbilityNo uint16
RequestedItemFlag byte
MaterialClass byte
}
// AvatarSocketRequest 描述时装开孔请求。
type AvatarSocketRequest struct {
TargetSlot int16
TargetItemID int32
MaterialSlot int16
}
// AvatarEmblemRequest 描述徽章镶嵌请求。
type AvatarEmblemRequest struct {
TargetSlot int16
TargetItemID int32
Emblems []EmblemApply
}
// EmblemApply 描述单个徽章应用记录。
type EmblemApply struct {
EmblemSlot int16
EmblemItemID int32
SocketIndex byte
}
// TitleBookRequest 描述称号簿放入或取出请求。
type TitleBookRequest struct {
ItemSpaceRaw int32
Slot int16
ItemID int32
Category int32
Index int32
}
// DecodeCompoundAvatarRequest 解析时装合成请求。
func DecodeCompoundAvatarRequest(body []byte) (CompoundAvatarRequest, error) {
if len(body) < 22 {
return CompoundAvatarRequest{}, fmt.Errorf("body too short: got %d want >= 22", len(body))
}
return CompoundAvatarRequest{
ConsumeSlot: readI16(body, 0),
Slot1: readI16(body, 2),
ExpectedItemID1: readI32(body, 4),
Slot2: readI16(body, 8),
ExpectedItemID2: readI32(body, 10),
RequestItemID: readI32(body, 14),
AbilityNo: binary.LittleEndian.Uint16(body[18:20]),
RequestedItemFlag: body[20],
MaterialClass: body[21],
}, nil
}
// DecodeAvatarSocketRequest 解析时装开孔请求。
func DecodeAvatarSocketRequest(body []byte) (AvatarSocketRequest, error) {
if len(body) < 8 {
return AvatarSocketRequest{}, fmt.Errorf("body too short: got %d want >= 8", len(body))
}
return AvatarSocketRequest{
TargetSlot: readI16(body, 0),
TargetItemID: readI32(body, 2),
MaterialSlot: readI16(body, 6),
}, nil
}
// DecodeAvatarEmblemRequest 解析时装徽章请求,兼容首字节为 Avatar list type 的包体。
func DecodeAvatarEmblemRequest(body []byte) (AvatarEmblemRequest, error) {
if len(body) >= 8 && body[0] == listTypeAvatar {
return decodeEmblemAt(body, 1)
}
return decodeEmblemAt(body, 0)
}
// DecodeTitleBookRequest 解析称号簿请求。
func DecodeTitleBookRequest(body []byte) (TitleBookRequest, error) {
if len(body) < 20 {
return TitleBookRequest{}, fmt.Errorf("body too short: got %d want >= 20", len(body))
}
return TitleBookRequest{
ItemSpaceRaw: readI32(body, 0),
Slot: int16(readI32(body, 4)),
ItemID: readI32(body, 8),
Category: readI32(body, 12),
Index: readI32(body, 16),
}, nil
}
func decodeEmblemAt(body []byte, offset int) (AvatarEmblemRequest, error) {
if len(body) < offset+7 {
return AvatarEmblemRequest{}, fmt.Errorf("body too short: got %d want >= %d", len(body), offset+7)
}
count := int(body[offset+6])
req := AvatarEmblemRequest{
TargetSlot: readI16(body, offset),
TargetItemID: readI32(body, offset+2),
Emblems: make([]EmblemApply, 0, count),
}
entryOffset := offset + 7
for i := 0; i < count; i++ {
if entryOffset+7 > len(body) {
return AvatarEmblemRequest{}, fmt.Errorf("emblem entry too short: index %d", i)
}
req.Emblems = append(req.Emblems, EmblemApply{
EmblemSlot: readI16(body, entryOffset),
EmblemItemID: readI32(body, entryOffset+2),
SocketIndex: body[entryOffset+6],
})
entryOffset += 7
}
return req, nil
}
func (r CompoundAvatarRequest) String() string {
return fmt.Sprintf("consume=%d slot1=(%d,%d) slot2=(%d,%d) requestItem=%d ability=%d materialClass=%d", r.ConsumeSlot, r.Slot1, r.ExpectedItemID1, r.Slot2, r.ExpectedItemID2, r.RequestItemID, r.AbilityNo, r.MaterialClass)
}
func (r AvatarSocketRequest) String() string {
return fmt.Sprintf("target=(%d,%d) material=%d", r.TargetSlot, r.TargetItemID, r.MaterialSlot)
}
func (r AvatarEmblemRequest) String() string {
return fmt.Sprintf("target=(%d,%d) emblems=%d", r.TargetSlot, r.TargetItemID, len(r.Emblems))
}
func (r TitleBookRequest) String() string {
return fmt.Sprintf("space=%d slot=%d item=%d category=%d index=%d", r.ItemSpaceRaw, r.Slot, r.ItemID, r.Category, r.Index)
}
func readI16(body []byte, offset int) int16 {
return int16(binary.LittleEndian.Uint16(body[offset:]))
}
func readI32(body []byte, offset int) int32 {
return int32(binary.LittleEndian.Uint32(body[offset:]))
}