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

+297
View File
@@ -0,0 +1,297 @@
// 本文件解析攻坚队管理类 C2S 请求体。
package raid
import (
"encoding/binary"
"errors"
)
// InfoRequest 是 C2S 0x298/664 创建攻坚队和 0x29E/670 修改攻坚队信息的共用包体。
// MCP 证据:sub_1B49240 先写 u8,再写 dstr 名称,最后写 u8。
type InfoRequest struct {
RouteOrRaidType byte
NameBytes []byte
TailFlag byte
}
// RequestRaidInfoRequest 是 C2S 0x54F/1359 的请求体:u8 mode + u32 raid_key。
//
// 证据:客户端组包点 sub_17B1A90 依次 push 0x54F、一个字节 2、以及 raid_key,
// 实测正文 `01 01 00 17 00` 的后四字节 0x00170001 正是当前攻坚队的 key。
// (旧注释曾把后两个字节当成频道号,那是因为 raid_key 的高 16 位恰好就是频道。)
// S2C 1359 的当前 EXE handler sub_1D3A150 不读取正文,只把本地对象的 +0xE1E 置 1。
type RequestRaidInfoRequest struct {
Mode byte
RaidKey uint32
Raw [5]byte
}
// LeaveRequest 是 C2S 0x299/665 离开攻坚队包体。
// MCP 证据:sub_17B1E30/sub_324F850 只写一个 u16。
type LeaveRequest struct {
RaidOrMemberKey uint16
}
// RejoinRequest 是 C2S 0x29C/668 重新加入攻坚链路的客户端回包。
// MCP 证据:sub_1D638D0 的触发分支只回写一个 u32。
type RejoinRequest struct {
RaidKey uint32
}
// SetWaitingRequest 是 C2S 0x29B/667 的攻坚等待状态请求。
// MCP 证据:sub_1B4C2A0 分支发送 u8 0,再发送 u8 sub_17B1CD0(...)。
type SetWaitingRequest struct {
Flag byte
RouteRaidType byte
}
// EntryCostInfoRequest 是 C2S 0x292/658 的 raid 入场材料/消耗信息请求。
// MCP 20260705 证据:sub_17B19C0 发送 658,正文只写 u8(a2 != 0)。
type EntryCostInfoRequest struct {
Enabled byte
}
// SetSymbolRequest 是 C2S 0x296/662 的 raid 标记设置请求。
// MCP 20260705 证据:sub_17B1600 发送 662,正文写 u32,u32,u8;第三字段必须小于 3。
type SetSymbolRequest struct {
SourceValue uint32
TargetValue uint32
Symbol byte
}
// ManagerWorkRequest 是 C2S 0x29D/669 的小组编辑请求。
type ManagerWorkRequest struct {
ActionOrMode uint32
MemberCharKey uint32
TargetGroup uint32
}
// OtherChannelRequestJoinRequest 是 C2S 0x334/820 的跨频道攻坚申请。
// MCP 证据:sub_17B1530 写 u8,u16,u32,u16。
type OtherChannelRequestJoinRequest struct {
Mode byte
TargetKey uint16
ClientValue uint32
RouteRaidType uint16
}
// MemberChangeStateRequest 是 C2S 0x337/823 的成员状态变化请求。
// MCP 证据:sub_17B1C40 只写 u8 state。
type MemberChangeStateRequest struct {
State byte
}
// UserMoveChannelFailRequest 是 C2S 0x338/824 的跨频道移动失败上报。
// MCP 证据:sub_17B17A0 写 u8,u16。
type UserMoveChannelFailRequest struct {
Mode byte
TargetKey uint16
}
// OtherChannelListRequest 是 C2S 0x33F/831 的公共频道攻坚列表请求。
// MCP 证据:sub_17B3140 写 u8=1,或 u8=0 + raw[8] + dstr name。
type OtherChannelListRequest struct {
Mode byte
Context [8]byte
NameBytes []byte
HasContext bool
HasNameDstr bool
}
// CheckRaidUserRequest 是 C2S 0x379/889 的攻坚用户检查请求。
// MCP 证据:sub_17B1D50 写 u8,u16;S2C 889 是复杂列表,不在解析层伪造成功。
type CheckRaidUserRequest struct {
Mode byte
TargetKey uint16
}
var errRaidInfoBodyShort = errors.New("raid info body too short")
var errRaidInfoNameTruncated = errors.New("raid info name dstr truncated")
var errRaidRequestInfoBodyShort = errors.New("raid request info body must be five bytes")
var errRaidLeaveBodyShort = errors.New("raid leave body too short")
var errRaidStartBodyNotEmpty = errors.New("raid start body must be empty")
var errRaidRejoinBodyShort = errors.New("raid rejoin body too short")
var errRaidSetWaitingBodyShort = errors.New("raid set waiting body too short")
var errRaidEntryCostBodyShort = errors.New("raid entry cost info body too short")
var errRaidSetSymbolBodyShort = errors.New("raid set symbol body too short")
var errRaidSetSymbolInvalid = errors.New("raid set symbol must be less than 3")
var errManagerWorkBodyShort = errors.New("raid manager work body too short")
var errRaidOtherChannelJoinBodyShort = errors.New("raid other channel request join body too short")
var errRaidMemberStateBodyShort = errors.New("raid member change state body too short")
var errRaidMoveChannelFailBodyShort = errors.New("raid user move channel fail body too short")
var errRaidOtherChannelListBodyShort = errors.New("raid other channel list body too short")
var errRaidOtherChannelListNameTruncated = errors.New("raid other channel list name dstr truncated")
var errRaidCheckUserBodyShort = errors.New("raid check user body too short")
// DecodeCreateRaidRequest 按当前 EXE 写包顺序解析创建攻坚队请求。
func DecodeCreateRaidRequest(body []byte) (InfoRequest, error) {
return decodeInfoRequest(body)
}
// DecodeModifyRaidInfoRequest 按当前 EXE 写包顺序解析修改攻坚队信息请求。
func DecodeModifyRaidInfoRequest(body []byte) (InfoRequest, error) {
return decodeInfoRequest(body)
}
// DecodeRequestRaidInfoRequest 解析当前客户端发送的 1359 请求。
//
// 布局 u8 mode + u32 raid_key 来自组包点 sub_17B1A90(push 0x54F / push 2 /
// push raid_key)。实测样本的 mode 为 1 或 2:mode 1 是打开攻坚队窗口时的常规
// 刷新,mode 2 是客户端刚为某个 key 新建了对象、回头向服务端索取队信息。
func DecodeRequestRaidInfoRequest(body []byte) (RequestRaidInfoRequest, error) {
if len(body) != 5 {
return RequestRaidInfoRequest{}, errRaidRequestInfoBodyShort
}
var request RequestRaidInfoRequest
copy(request.Raw[:], body)
request.Mode = body[0]
request.RaidKey = binary.LittleEndian.Uint32(body[1:5])
return request, nil
}
func decodeInfoRequest(body []byte) (InfoRequest, error) {
if len(body) < 6 {
return InfoRequest{}, errRaidInfoBodyShort
}
nameLen := int(binary.LittleEndian.Uint32(body[1:5]))
end := 5 + nameLen
if end+1 != len(body) {
return InfoRequest{}, errRaidInfoNameTruncated
}
return InfoRequest{
RouteOrRaidType: body[0],
NameBytes: append([]byte(nil), body[5:end]...),
TailFlag: body[end],
}, nil
}
// DecodeLeaveRaidRequest parses the exact u16 body written by the current EXE.
func DecodeLeaveRaidRequest(body []byte) (LeaveRequest, error) {
if len(body) != 2 {
return LeaveRequest{}, errRaidLeaveBodyShort
}
return LeaveRequest{RaidOrMemberKey: binary.LittleEndian.Uint16(body[0:2])}, nil
}
// DecodeStartRaidRequest 校验开始攻坚请求;当前 EXE 没有写任何正文。
func DecodeStartRaidRequest(body []byte) error {
if len(body) != 0 {
return errRaidStartBodyNotEmpty
}
return nil
}
// DecodeRejoinRaidRequest 解析重新加入攻坚请求。
func DecodeRejoinRaidRequest(body []byte) (RejoinRequest, error) {
if len(body) != 4 {
return RejoinRequest{}, errRaidRejoinBodyShort
}
return RejoinRequest{RaidKey: binary.LittleEndian.Uint32(body[0:4])}, nil
}
// DecodeSetWaitingRequest 解析 667 攻坚等待状态请求。
func DecodeSetWaitingRequest(body []byte) (SetWaitingRequest, error) {
if len(body) != 2 {
return SetWaitingRequest{}, errRaidSetWaitingBodyShort
}
return SetWaitingRequest{Flag: body[0], RouteRaidType: body[1]}, nil
}
// DecodeEntryCostInfoRequest 解析 658 raid 入场消耗信息请求。
func DecodeEntryCostInfoRequest(body []byte) (EntryCostInfoRequest, error) {
if len(body) != 1 {
return EntryCostInfoRequest{}, errRaidEntryCostBodyShort
}
return EntryCostInfoRequest{Enabled: body[0]}, nil
}
// DecodeSetSymbolRequest 解析 662 raid 标记设置请求。
func DecodeSetSymbolRequest(body []byte) (SetSymbolRequest, error) {
if len(body) != 9 {
return SetSymbolRequest{}, errRaidSetSymbolBodyShort
}
if body[8] >= 3 {
return SetSymbolRequest{}, errRaidSetSymbolInvalid
}
return SetSymbolRequest{
SourceValue: binary.LittleEndian.Uint32(body[0:4]),
TargetValue: binary.LittleEndian.Uint32(body[4:8]),
Symbol: body[8],
}, nil
}
// DecodeManagerWorkRequest 按 NoPack sub_1B4D3D0/sub_1B480E0 的发送顺序解析 669。
func DecodeManagerWorkRequest(body []byte) (ManagerWorkRequest, error) {
if len(body) != 12 {
return ManagerWorkRequest{}, errManagerWorkBodyShort
}
return ManagerWorkRequest{
ActionOrMode: binary.LittleEndian.Uint32(body[0:4]),
MemberCharKey: binary.LittleEndian.Uint32(body[4:8]),
TargetGroup: binary.LittleEndian.Uint32(body[8:12]),
}, nil
}
// DecodeOtherChannelRequestJoinRequest 解析 820 跨频道攻坚申请请求。
func DecodeOtherChannelRequestJoinRequest(body []byte) (OtherChannelRequestJoinRequest, error) {
if len(body) != 9 {
return OtherChannelRequestJoinRequest{}, errRaidOtherChannelJoinBodyShort
}
return OtherChannelRequestJoinRequest{
Mode: body[0],
TargetKey: binary.LittleEndian.Uint16(body[1:3]),
ClientValue: binary.LittleEndian.Uint32(body[3:7]),
RouteRaidType: binary.LittleEndian.Uint16(body[7:9]),
}, nil
}
// DecodeMemberChangeStateRequest 解析 823 攻坚成员状态变化请求。
func DecodeMemberChangeStateRequest(body []byte) (MemberChangeStateRequest, error) {
if len(body) != 1 {
return MemberChangeStateRequest{}, errRaidMemberStateBodyShort
}
return MemberChangeStateRequest{State: body[0]}, nil
}
// DecodeUserMoveChannelFailRequest 解析 824 跨频道移动失败上报。
func DecodeUserMoveChannelFailRequest(body []byte) (UserMoveChannelFailRequest, error) {
if len(body) != 3 {
return UserMoveChannelFailRequest{}, errRaidMoveChannelFailBodyShort
}
return UserMoveChannelFailRequest{Mode: body[0], TargetKey: binary.LittleEndian.Uint16(body[1:3])}, nil
}
// DecodeOtherChannelListRequest 解析 831 公共频道攻坚列表请求。
func DecodeOtherChannelListRequest(body []byte) (OtherChannelListRequest, error) {
if len(body) < 1 {
return OtherChannelListRequest{}, errRaidOtherChannelListBodyShort
}
out := OtherChannelListRequest{Mode: body[0]}
if out.Mode != 0 {
if len(body) != 1 {
return OtherChannelListRequest{}, errRaidOtherChannelListBodyShort
}
return out, nil
}
if len(body) < 13 {
return OtherChannelListRequest{}, errRaidOtherChannelListBodyShort
}
copy(out.Context[:], body[1:9])
out.HasContext = true
nameLen := int(binary.LittleEndian.Uint32(body[9:13]))
end := 13 + nameLen
if end != len(body) {
return OtherChannelListRequest{}, errRaidOtherChannelListNameTruncated
}
out.NameBytes = append([]byte(nil), body[13:end]...)
out.HasNameDstr = true
return out, nil
}
// DecodeCheckRaidUserRequest 解析 889 攻坚用户检查请求。
func DecodeCheckRaidUserRequest(body []byte) (CheckRaidUserRequest, error) {
if len(body) != 3 {
return CheckRaidUserRequest{}, errRaidCheckUserBodyShort
}
return CheckRaidUserRequest{Mode: body[0], TargetKey: binary.LittleEndian.Uint16(body[1:3])}, nil
}