Files
90CNServer/server/internal/services/dnfbridge/peer_response_refusal.go
T
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

67 lines
3.1 KiB
Go

// 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.