fix(challenge,scene): challenge id auto-detect, real ScRsp ids, teleport ownership
Round-2 fixes from the 01:44-01:53 client session:
- StartChallenge (MoC/PF/AS normal start) decoded challenge_id=0 -> client
retried and aborted. detectChallengeID scans top-level varints for the
first plausible id (challenge_data + known mode ranges) when the strict
tag-14 decode misses; payload hexdump logged when still 0
- StartChallengeScRsp now carries stage_info (tag 13) with boss node buff
echo, per the 4.6.51 proto
- GetSwitchHandData replies 8108 (the real ScRsp) - 8104 is
SwitchHandStartCsReq, we were sending a request id back at the client
- GetSceneMapInfo: strict proto parse failed on some payloads (layout
drift) which stalled the map UI ('no pillars / navigation loading') -
now a raw handler with a lenient wire walk fallback + hexdump logging
- teleport destination ('AA teleport landed in AP'): teleport ids repeat
on every floor and teleports.json was scanned globally in random map
order. Map clicks now resolve floor-first via scene_identifier, and the
global id scan never touches teleports.json
- tierce: arena 3014101 -> 3000101 fallback (client has no MapEntrance
3014101), same as StartChallenge
This commit is contained in:
1 parent
d364b9616f
commit
876f20d857
4 files changed
+229
-29
No files matched your search
@@ -567,10 +567,64 @@ func dedupeTruncate4(in []uint32) []uint32 {
|
||||
return out
|
||||
}
|
||||
|
||||
// looksLikeChallengeID: known challenge-id ranges across endgame modes.
|
||||
func looksLikeChallengeID(v uint64) bool {
|
||||
switch {
|
||||
case v >= 101 && v <= 119, v == 900,
|
||||
v >= 1001 && v <= 1035,
|
||||
v >= 2001 && v <= 2025,
|
||||
v >= 3001 && v <= 3020,
|
||||
v >= 5000 && v <= 5700, // MoC floors incl. 4.6 phases
|
||||
v >= 20000 && v <= 20300, // Pure Fiction
|
||||
v >= 30000 && v <= 30300: // Apocalyptic Shadow
|
||||
return true
|
||||
}
|
||||
cd := getChallengeData()
|
||||
_, okC := cd.Challenges[uint32(v)]
|
||||
_, okT := cd.Tierce[uint32(v)]
|
||||
return okC || okT
|
||||
}
|
||||
|
||||
// detectChallengeID: when the strict decoder misses the challenge id (field
|
||||
// layout drift between client builds), scan top-level varint fields for the
|
||||
// first value that looks like a challenge id.
|
||||
func detectChallengeID(payload []byte) (uint32, int) {
|
||||
buf := payload
|
||||
for len(buf) > 0 {
|
||||
tag, n := readUvarint(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n:]
|
||||
wireType := uint32(tag & 0x7)
|
||||
if wireType == 0 {
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
if looksLikeChallengeID(v) {
|
||||
return uint32(v), int(tag >> 3)
|
||||
}
|
||||
} else {
|
||||
skipWireField(wireType, &buf)
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// handleStartChallenge ports handle_start_challenge: respond cmd 1787 with
|
||||
// retcode(3) + cur_challenge(5) + lineup_list(6) + scene(10).
|
||||
// retcode(3) + cur_challenge(5) + lineup_list(6) + scene(10) + stage_info(13).
|
||||
func handleStartChallenge(s *Session, payload []byte) error {
|
||||
req := decodeStartChallengeReq(payload)
|
||||
if req.challengeID == 0 {
|
||||
if id, field := detectChallengeID(payload); id != 0 {
|
||||
Log("handle_start_challenge: strict decode missed, detected challenge_id=" + itoa(int(id)) + " at field " + itoa(field))
|
||||
req.challengeID = id
|
||||
} else {
|
||||
PacketLog("[CHALLENGE] challenge_id=0 payload=" + hexDump(payload, 128))
|
||||
}
|
||||
}
|
||||
Log("handle_start_challenge: challenge_id=" + itoa(int(req.challengeID)))
|
||||
|
||||
chosenAvatars := req.firstAvatars
|
||||
@@ -592,12 +646,13 @@ func handleStartChallenge(s *Session, payload []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to the standard arena when the mapped one is missing from res
|
||||
if res, err := common.GameRes(); err == nil {
|
||||
if !mapHasEntry(res, entrance) {
|
||||
Log("[CHALLENGE] Arena entrance missing in res.json, fallback to 3000101")
|
||||
entrance = 3000101
|
||||
}
|
||||
// the 4.6.51 client has no MapEntrance 3014101 (Rust lesson: Lua
|
||||
// GotoMapParam nil -> stuck loading) even though res.json has it
|
||||
if entrance == 3014101 {
|
||||
entrance = 3000101
|
||||
} else if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
|
||||
Log("[CHALLENGE] Arena entrance missing in res.json, fallback to 3000101")
|
||||
entrance = 3000101
|
||||
}
|
||||
|
||||
Log("[CHALLENGE] Start: challenge_id=" + itoa(int(req.challengeID)) + " (mapped " + itoa(int(baseID)) + ")")
|
||||
@@ -640,6 +695,20 @@ func handleStartChallenge(s *Session, payload []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4.6.51: StartChallengeScRsp.stage_info (tag 13) - boss node info echoes
|
||||
// the selected buff so the client can set up the challenge UI
|
||||
stageInfo := &goproto.ChallengeStageInfo{}
|
||||
if req.buffID != 0 {
|
||||
stageInfo.BossInfo = &goproto.ChallengeBossInfo{
|
||||
FirstNode: &goproto.ChallengeBossSingleNodeInfo{BuffId: req.buffID},
|
||||
SecondNode: &goproto.ChallengeBossSingleNodeInfo{BuffId: req.buffID},
|
||||
}
|
||||
}
|
||||
stageBuf, err := proto.Marshal(stageInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body = append(body, 0x18, 0x00) // tag 3: retcode = 0
|
||||
body = append(body, 0x2A) // tag 5: cur_challenge
|
||||
@@ -651,6 +720,9 @@ func handleStartChallenge(s *Session, payload []byte) error {
|
||||
body = append(body, 0x52) // tag 10: scene
|
||||
body = appendUvarint(body, uint64(len(sceneBuf)))
|
||||
body = append(body, sceneBuf...)
|
||||
body = append(body, 0x6A) // tag 13: stage_info
|
||||
body = appendUvarint(body, uint64(len(stageBuf)))
|
||||
body = append(body, stageBuf...)
|
||||
|
||||
return s.SendRaw(&NetPacket{CmdType: 1787, Body: body})
|
||||
}
|
||||
@@ -731,6 +803,15 @@ func handleStartChallengeTierce(s *Session, payload []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
// the 4.6.51 client cannot load arena 3014101 (no MapEntrance) - fall
|
||||
// back to the standard arena, mirroring handleStartChallenge
|
||||
if entrance == 3014101 {
|
||||
entrance = 3000101
|
||||
} else if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
|
||||
Log("[CHALLENGE] Tierce arena entrance missing in res.json, fallback to 3000101")
|
||||
entrance = 3000101
|
||||
}
|
||||
|
||||
if player := s.JSON(); player != nil {
|
||||
setChallengeBattleConfig(player, chosenAvatars, req.challengeID, event, buff)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user