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