fix(gameserver): answer UI-gating cmds the 4.6.51 client stalls on
First real-client session (2026-10-07 01:00) showed the client walking and attacking but UI interactions stalling: ~120 CsReq dropped with no reply. - auto-empty responder: any unknown CsReq with a Cmd-enum ScRsp pair now gets an empty ScRsp, except the danger families (GridFight/ChessRogue/ EvolveBuild/ChimeraDuel/Jukebox/CycleScore) which must stay dropped (client Sync(nil) NREs on empty, per the Rust lessons) - GetUnreleasedBlockInfo: the Endgame entry waits on this - 4.6.51 wire is CsReq 4190 -> ScRsp 4112 retcode tag 14 (the Rust build answered 4130, an id this client never sends) - SetClientPaused acked (client pauses itself when opening menus/map) - explicit handlers: TakeQuestReward, GetFirstTalkNpc, QuitBattle, GetMapRotationData (Amphoreus teleport pillars), GetPamSkinData, GetAllServerPrefsData/UpdateServerPrefsData, GetHeartDialInfo, GetPetData, GetShopList - SendRaw now logs (dummy/raw replies were invisible in gameserver.log) - cmdpairs.go: req->rsp id pairs generated from the Cmd enum (865 pairs), stable across message renames - smoke test extended: 4190->4112 and SetClientPaused ack verified
This commit is contained in:
1 parent
580717c3d8
commit
91ee027eb7
8 files changed
+1154
-115
No files matched your search
@@ -1,6 +1,8 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
@@ -102,6 +104,29 @@ func dummy(name string) {
|
||||
dummyResponses[uint16(reqID)] = uint16(rspID)
|
||||
}
|
||||
|
||||
// dangerFamilies are client modules that Sync(nil) -> NRE -> black screen when
|
||||
// they receive an empty ScRsp (lesson recorded in the Rust packet.rs / HANDOFF):
|
||||
// never auto-answer these, drop them silently instead.
|
||||
var dangerFamilies = []string{
|
||||
"GridFight", "ChessRogue", "EvolveBuild", "ChimeraDuel",
|
||||
"Jukebox", "CycleScore", "MusicAlbum",
|
||||
}
|
||||
|
||||
func isDangerCmd(cmdID uint16) bool {
|
||||
name := goproto.NameByCmdID[cmdID]
|
||||
for _, fam := range dangerFamilies {
|
||||
if strings.Contains(name, fam) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// CycleScore cmd ids (4143/4146) and GridFight range carry generic names
|
||||
switch cmdID {
|
||||
case 4143, 4146, 8541, 5410, 5449, 7142, 3174, 9252, 8175:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DispatchMessage is the Go port of CommandHandler::on_message.
|
||||
func DispatchMessage(s *Session, cmdID uint16, payload []byte) {
|
||||
PacketLog(fmt.Sprintf("[PACKET RECV] cmd_id: %d %s (len: %d)", cmdID, goproto.NameByCmdID[cmdID], len(payload)))
|
||||
@@ -129,9 +154,16 @@ func DispatchMessage(s *Session, cmdID uint16, payload []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// 4. unknown — do NOT answer with an empty ScRsp: client modules
|
||||
// (GridFight/ChessRogue/EvolveBuild/ChimeraDuel/Jukebox) Sync(nil) and NRE
|
||||
// into a black screen (lesson recorded in the Rust packet.rs)
|
||||
// 4. unknown: auto-answer with an empty ScRsp when the Cmd enum defines the
|
||||
// pair, EXCEPT the danger families which must be dropped silently.
|
||||
// (Answering nothing made the client's UI modules stall - e.g. the Endgame
|
||||
// entry waits for GetUnreleasedBlockInfo and the pause menu waits for
|
||||
// SetClientPaused, leaving the game unclickable.)
|
||||
if rspID, ok := goproto.RspIDByReqID[uint32(cmdID)]; ok && !isDangerCmd(cmdID) {
|
||||
_ = s.SendRaw(&NetPacket{CmdType: uint16(rspID)})
|
||||
return
|
||||
}
|
||||
|
||||
if cmdID != 7159 {
|
||||
PacketLog(fmt.Sprintf("Unknown command ID: %d (%s)", cmdID, goproto.NameByCmdID[cmdID]))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user