DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,66 @@
|
||||
// peer_response_refusal.go owns the nine-byte "player pressed 否" form of the
|
||||
// class-1/op11 peer response.
|
||||
//
|
||||
// Live capture 2026-09-26 08:32:39 and 08:32:48 -- two presses of 否 in the
|
||||
// party-application prompt -- plus 07:24:24 before them:
|
||||
//
|
||||
// 点「否」 body = 16 00 | 00 | 00 00 00 00 | 85 00 (9 bytes)
|
||||
// 点「是」 body = 16 00 | 00 | 00 00 00 00 (7 bytes)
|
||||
//
|
||||
// party.DecodeResponsePeerRequest insists on exactly seven bytes and the only
|
||||
// nine-byte fallback was runtimeRaidPeerResponseDeclined, which gated on mode
|
||||
// 10. A refused PARTY application therefore never decoded: the server logged
|
||||
// `game-party-response-peer-body-short` and returned, so the pending invite was
|
||||
// never consumed and the applicant received nothing at all.
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"longheng.io/server/internal/modules/dnf/party"
|
||||
)
|
||||
|
||||
// currentPeerResponseAnswer is one op11 answer, refusal included.
|
||||
type currentPeerResponseAnswer struct {
|
||||
Request party.ResponsePeerRequest
|
||||
Refused bool
|
||||
Reason uint16
|
||||
}
|
||||
|
||||
// decodeCurrentPeerResponseAnswer accepts both wire forms of op11: the plain
|
||||
// seven-byte answer, and the nine-byte refusal. It returns the original decode
|
||||
// error when neither matches, so the caller can still log the short-body case.
|
||||
func decodeCurrentPeerResponseAnswer(body []byte) (currentPeerResponseAnswer, error) {
|
||||
parsed, err := party.DecodeResponsePeerRequest(body)
|
||||
if err == nil {
|
||||
return currentPeerResponseAnswer{Request: parsed}, nil
|
||||
}
|
||||
if refusal, reason, ok := party.DecodeResponsePeerRefusal(body); ok {
|
||||
return currentPeerResponseAnswer{Request: refusal, Refused: true, Reason: reason}, nil
|
||||
}
|
||||
return currentPeerResponseAnswer{}, err
|
||||
}
|
||||
|
||||
// A refusal is now consumed, logged, and then deliberately left unanswered.
|
||||
//
|
||||
// Two live attempts on 2026-09-26 both failed, and the client reverse-engineering
|
||||
// explains why:
|
||||
//
|
||||
// - class-0/op8 (09:43) made the requester's client print 玩家[%s]已接受组队请求
|
||||
// -- the opposite of what happened. That line is string 624 (trade version
|
||||
// 625) and the client has no refusal twin for it, so op8 cannot express a
|
||||
// refusal at all.
|
||||
// - class-1/op10 failure (09:50, code 0) was received -- the client trace shows
|
||||
// `class=1 opcode=10 packet_len=18` right after our two-byte body -- and did
|
||||
// nothing. A request takes exactly one answer and the success ACK already
|
||||
// spent it.
|
||||
//
|
||||
// The message the operator expected (对方不在线,或拒绝了您的组队请求) exists twice in
|
||||
// the client's string table, but neither copy is reachable in this build: the
|
||||
// bytes `push 0x111A6` / `push 0x111A7` occur nowhere in DNF.exe, and 70054 does
|
||||
// not appear as a 4-byte value in any section. The scanner behind that claim was
|
||||
// validated against ids that ARE referenced -- `push 0x11205` (70149) has 26
|
||||
// sites, and `push 0x112e1` (70369) resolves to 0x1911B39, the exact instruction
|
||||
// found by hand. The same holds for the trade pair 70056/70057, so the whole
|
||||
// quartet is dead in this client.
|
||||
//
|
||||
// This matches the reference server: 86JP's PartyHandler cancels the invite on a
|
||||
// type-0 refusal and sends the inviter nothing.
|
||||
Reference in new issue
Block a user