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
@@ -126,11 +126,24 @@ func init() {
|
||||
// req.EntryId doubles as a floor id in some map flows
|
||||
resolvedEntry = findEntryForFloor(res, req.EntryId)
|
||||
case req.TeleportId != 0:
|
||||
resolvedEntry = findEntryByTeleport(res, req.TeleportId, currentEntry())
|
||||
floorID := uint32(0)
|
||||
if sceneIdent != nil {
|
||||
floorID = sceneIdent.FloorId
|
||||
}
|
||||
if entry := findEntryByTeleportInFloor(res, req.TeleportId, floorID); entry != 0 {
|
||||
resolvedEntry = entry
|
||||
} else {
|
||||
resolvedEntry = findEntryByTeleport(res, req.TeleportId, currentEntry())
|
||||
}
|
||||
case sceneIdent != nil && sceneIdent.TeleportNigger != nil:
|
||||
if t, okc := sceneIdent.TeleportNigger.(*goproto.SceneIdentifier_MDAIDPPKOPO); okc && t.MDAIDPPKOPO != nil && t.MDAIDPPKOPO.PLIDNBMCIJH != 0 {
|
||||
// map teleport click: teleport id arrives via scene_identifier
|
||||
resolvedEntry = findEntryByTeleport(res, t.MDAIDPPKOPO.PLIDNBMCIJH, currentEntry())
|
||||
// map teleport click: teleport id + floor arrive via scene_identifier
|
||||
tpID := t.MDAIDPPKOPO.PLIDNBMCIJH
|
||||
if entry := findEntryByTeleportInFloor(res, tpID, sceneIdent.FloorId); entry != 0 {
|
||||
resolvedEntry = entry
|
||||
} else {
|
||||
resolvedEntry = findEntryByTeleport(res, tpID, currentEntry())
|
||||
}
|
||||
}
|
||||
case interactID != 0:
|
||||
resolvedEntry = findEntryByInteract(res, interactID, currentEntry())
|
||||
@@ -221,13 +234,33 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
handle("GetSceneMapInfo", func(s *Session, req *goproto.GetSceneMapInfoCsReq, rsp *goproto.GetSceneMapInfoScRsp) error {
|
||||
// decodeSceneIdentifiersLenient: the 4.6.51 client's cmd-1461 payload can
|
||||
// fail strict proto parsing (field layout drift between builds) — walk the
|
||||
// wire format manually and pull floor_id/content_id out of field 2.
|
||||
// GetSceneMapInfo as a raw handler: the 4.6.51 client's cmd-1461 payload
|
||||
// can fail strict proto parsing (layout drift), so fall back to a manual
|
||||
// wire walk instead of dropping the request (the map UI stalls without
|
||||
// this reply - no pillars, "navigation loading").
|
||||
rawHandlers[1461] = func(s *Session, payload []byte) error {
|
||||
res, err := common.GameRes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rsp := &goproto.GetSceneMapInfoScRsp{}
|
||||
|
||||
for _, si := range req.SceneIdentifiers {
|
||||
var identifiers []*goproto.SceneIdentifier
|
||||
var strictReq goproto.GetSceneMapInfoCsReq
|
||||
if err := proto.Unmarshal(payload, &strictReq); err == nil {
|
||||
identifiers = strictReq.SceneIdentifiers
|
||||
}
|
||||
if len(identifiers) == 0 {
|
||||
identifiers = decodeSceneIdentifiersLenient(payload)
|
||||
if len(identifiers) > 0 {
|
||||
PacketLog("[SCENE] GetSceneMapInfo: used lenient decode (strict parse failed)")
|
||||
}
|
||||
}
|
||||
|
||||
for _, si := range identifiers {
|
||||
floorID := si.FloorId
|
||||
mapInfo := &goproto.SceneMapInfo{
|
||||
FloorId: floorID,
|
||||
@@ -283,8 +316,9 @@ func init() {
|
||||
|
||||
rsp.SceneMapInfo = append(rsp.SceneMapInfo, mapInfo)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return s.Send(rsp)
|
||||
}
|
||||
|
||||
handle("SceneEntityMove", func(s *Session, req *goproto.SceneEntityMoveCsReq, rsp *goproto.SceneEntityMoveScRsp) error {
|
||||
player := s.JSON()
|
||||
@@ -413,16 +447,35 @@ func mapHasEntry(res *common.JsonConfig, entryID uint32) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func findEntryByTeleport(res *common.JsonConfig, teleportID uint32, fallback uint32) uint32 {
|
||||
// teleports.json covers floors whose res config has no inline teleports:
|
||||
// teleport id -> floor -> entry (via the floor name search)
|
||||
for floorID, m := range common.Teleports() {
|
||||
if _, ok := m[teleportID]; ok {
|
||||
if entry := findEntryForFloor(res, floorID); entry != 0 {
|
||||
return entry
|
||||
// findEntryByTeleportInFloor resolves a teleport inside a KNOWN floor
|
||||
// (map-teleport clicks always carry the floor in scene_identifier).
|
||||
func findEntryByTeleportInFloor(res *common.JsonConfig, teleportID, floorID uint32) uint32 {
|
||||
if floorID != 0 {
|
||||
suffix := fmt.Sprintf("_F%d", floorID)
|
||||
entryIDs := sortedU32Keys2(res.LevelOutputConfigs)
|
||||
for _, eid := range entryIDs {
|
||||
for name, cfg := range res.LevelOutputConfigs[eid] {
|
||||
if len(name) <= len(suffix) || name[len(name)-len(suffix):] != suffix {
|
||||
continue
|
||||
}
|
||||
for _, sc := range cfg.Scenes {
|
||||
if _, ok := sc.Teleports[teleportID]; ok {
|
||||
return eid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// floor known but teleport not in its res config (teleports.json-only
|
||||
// floors): the entry owning the floor is still the right destination
|
||||
return findEntryForFloor(res, floorID)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func findEntryByTeleport(res *common.JsonConfig, teleportID uint32, fallback uint32) uint32 {
|
||||
// NOTE: never scan teleports.json globally by id alone - teleport ids
|
||||
// (1001, 1002, ...) repeat on every floor, which sent players to a random
|
||||
// region ("AA teleport landed in AP").
|
||||
entryIDs := sortedU32Keys2(res.LevelOutputConfigs)
|
||||
for _, eid := range entryIDs {
|
||||
names := make([]string, 0, len(res.LevelOutputConfigs[eid]))
|
||||
@@ -466,6 +519,64 @@ func findEntryByInteract(res *common.JsonConfig, interactID uint32, fallback uin
|
||||
return fallback
|
||||
}
|
||||
|
||||
// decodeSceneIdentifiersLenient walks the raw wire format of a
|
||||
// GetSceneMapInfoCsReq payload: field 2 = repeated SceneIdentifier;
|
||||
// inside each: floor_id = 13 (varint), content_id = 7 (varint).
|
||||
func decodeSceneIdentifiersLenient(payload []byte) []*goproto.SceneIdentifier {
|
||||
var out []*goproto.SceneIdentifier
|
||||
buf := payload
|
||||
for len(buf) > 0 {
|
||||
tag, n := readUvarint(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n:]
|
||||
fieldNumber := uint32(tag >> 3)
|
||||
wireType := uint32(tag & 0x7)
|
||||
|
||||
if fieldNumber == 2 && wireType == 2 {
|
||||
l, n2 := readUvarint(buf)
|
||||
if n2 == 0 || int(l) > len(buf[n2:]) {
|
||||
break
|
||||
}
|
||||
sub := buf[n2 : n2+int(l)]
|
||||
buf = buf[n2+int(l):]
|
||||
|
||||
si := &goproto.SceneIdentifier{}
|
||||
for len(sub) > 0 {
|
||||
stag, n3 := readUvarint(sub)
|
||||
if n3 == 0 {
|
||||
break
|
||||
}
|
||||
sfn := uint32(stag >> 3)
|
||||
swt := uint32(stag & 0x7)
|
||||
sub2 := sub[n3:]
|
||||
if swt == 0 && sfn == 13 {
|
||||
v, n4 := readUvarint(sub2)
|
||||
if n4 == 0 {
|
||||
break
|
||||
}
|
||||
sub = sub2[n4:]
|
||||
si.FloorId = uint32(v)
|
||||
} else if swt == 0 && sfn == 7 {
|
||||
v, n4 := readUvarint(sub2)
|
||||
if n4 == 0 {
|
||||
break
|
||||
}
|
||||
sub = sub2[n4:]
|
||||
si.ContentId = uint32(v)
|
||||
} else {
|
||||
skipWireField(swt, &sub)
|
||||
}
|
||||
}
|
||||
out = append(out, si)
|
||||
} else {
|
||||
skipWireField(wireType, &buf)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sortedU32Keys2[V any](m map[uint32]V) []uint32 {
|
||||
keys := make([]uint32, 0, len(m))
|
||||
for k := range m {
|
||||
|
||||
Reference in new issue
Block a user