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,115 @@
// 本文件按 NoPack 公共频道 raid S2C handler 的读取顺序构造固定包体。
package raid
import "errors"
const (
// RaidOtherChannelUserinfoMsgID 对应 sub_1D57390: u32 count + repeat u32 key。
RaidOtherChannelUserinfoMsgID uint16 = 819
// RaidOtherChannelRequestJoinResultMsgID 对应 sub_1D573E0: u32,u32。
RaidOtherChannelRequestJoinResultMsgID uint16 = 820
// RaidOtherChannelResponseJoinMsgID 对应 sub_CC2480: u8,u8。
RaidOtherChannelResponseJoinMsgID uint16 = 821
// RaidOtherChannelListPageMsgID 对应 sub_1D4C060: raw[20], rows12, rows13。
RaidOtherChannelListPageMsgID uint16 = 0x374
// RaidPublicListMsgID 对应 sub_1D7AA10。它才是“寻找攻坚队”窗口的
// 攻坚队对象快照,不是 0x374 的跨频道通用页容器。
RaidPublicListMsgID uint16 = 0x02f1
// RaidPublicListPageSize 来自 sub_1B4B520 的 33 * page 请求偏移。
RaidPublicListPageSize = 33
)
var errRaidPublicListPageTooLarge = errors.New("raid public list page exceeds 33 objects")
// PublicRaidListPage is the exact class-0/op753 snapshot consumed by
// sub_1D7AA10: total matching rows, returned row offset, row count, then full
// sub_17B8910 raid objects followed by their one-byte member counts.
type PublicRaidListPage struct {
TotalCount uint32
Offset uint32
Objects []RaidObjectMode0
}
// BuildPublicRaidListBody builds the native “寻找攻坚队” object page.
func BuildPublicRaidListBody(page PublicRaidListPage) ([]byte, error) {
if len(page.Objects) > RaidPublicListPageSize {
return nil, errRaidPublicListPageTooLarge
}
var writer packetWriter
writer.writeUint32(page.TotalCount)
writer.writeUint32(page.Offset)
writer.writeUint32(uint32(len(page.Objects)))
for _, object := range page.Objects {
if len(object.Members) > MaxAttackPartyMembers {
return nil, errRaidMemberTooMany
}
if err := writeRaidObjectBase(&writer, object); err != nil {
return nil, err
}
writer.writeByte(byte(len(object.Members)))
}
return writer.bytes(), nil
}
// OtherChannelListPage 是 S2C 0x374 的保守构造输入。
//
// MCP 20260705 复核:sub_1D4C060 开头读取 raw[20],随后读取
// u32 count + repeat raw[12],后半段再读取 u32 count + repeat raw[13]。
// 当前只把它作为 831 公共频道列表请求之后的候选列表页回包构造器,
// 不在 handler 中自动发送。
//
// Rows12 会被完整拷贝进步长 12 的容器。
// Rows13 是 0x183/0x34C/0x8F 同族 72 字节列表项的压缩格式:
// +0 u32, +4 u16, +6 u16, +8 u8, +9 u8, +10 u8, +11 u8;
// +12 暂未见直接使用。和扩展格式相比,0x374 不携带写入 item+0x24 的额外 u16,
// 因此客户端把 item+0x24 固定为 0。
type OtherChannelListPage struct {
Header [20]byte
Rows12 [][12]byte
Rows13 [][13]byte
}
// BuildOtherChannelListPageBody 构造 S2C 0x374 公共频道/攻坚列表页候选包体。
func BuildOtherChannelListPageBody(page OtherChannelListPage) []byte {
var writer packetWriter
writer.writeRaw(page.Header[:])
writeCheckUserRows12(&writer, page.Rows12)
writer.writeUint32(uint32(len(page.Rows13)))
for _, row := range page.Rows13 {
writer.writeRaw(row[:])
}
return writer.bytes()
}
// BuildOtherChannelUserinfoBody 构造 S2C 819 公共频道用户 key 列表。
func BuildOtherChannelUserinfoBody(keys []uint32) []byte {
var writer packetWriter
writer.writeUint32(uint32(len(keys)))
for _, key := range keys {
writer.writeUint32(key)
}
return writer.bytes()
}
// BuildOtherChannelRequestJoinResultBody 构造 S2C 820 的两个 u32 字段。
//
// MCP 20260705 复核:sub_1D573E0 读两个 u32 后调用
// sub_1C5B070(type=3, valueA, valueB)。valueA 必须匹配客户端对象 +552,
// valueB 是累加量,达到对象 +556 阈值后才置完成标志。它不是通用成功 ACK。
func BuildOtherChannelRequestJoinResultBody(valueA uint32, valueB uint32) []byte {
var writer packetWriter
writer.writeUint32(valueA)
writer.writeUint32(valueB)
return writer.bytes()
}
// BuildOtherChannelResponseJoinBody 构造 S2C 821 响应加入结果。
//
// MCP 20260705 复核:sub_CC2480 把 flag 写到 UI 对象 292 的 +4(bool),
// 把 resultOrSlot 写到 +8,然后触发 UI 610 刷新;实际语义仍由 owner 决定。
func BuildOtherChannelResponseJoinBody(resultOrSlot byte, flag byte) []byte {
var writer packetWriter
writer.writeByte(resultOrSlot)
writer.writeByte(flag)
return writer.bytes()
}