Files
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

298 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 本文件解析攻坚队管理类 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
}