package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // 室内扇区(sector_high_indoor_<IndoorId>)—— 服务端主动驱动,不需要 frida 注入器。 // // // ★★★ 这是 2026-09-27 之前服务端唯一的「协议上完全可行、但完全够不着」的通道: // 6 个 indoor sector 的 MethodId 常量早就存在(MethodId.cs 3074 / 3813 / 3650 / // 2162 / 1977 / 3885),但全仓零使用 —— 没有任何 Handler 推过它们。 // (⛔ 2026-09-27 订正:之前记成「连常量都没有」是错的,常量在,缺的是 Handler。) // 通道归属:独立于 gadget 网格(68956797)/ sceneitem 网格(68969593)/ 单件摆放 // (68843189)/ PCG(无外部触发点)—— 是第五条通道。sector 只管「这块区域显不显示」, // 子树里是 _lights / _decoration(灯、镜子、LOD 组等静态装饰)。 // 家具本体不归它管(挂在 GadgetInstanceRoot,走 F/A 通道), // 所以室内完整还原 = 本通道开区域 + PropRestock 补家具,两条都要。 // IndoorId 从哪来:不需要客户端上报,也不需要 frida 导出 // WorldStateIndoorSectorConfig。服务端读 Configs/IndoorConfig.json, // 按「离玩家最近的室内条目」推出 IndoorId。 // 实测对得上:客户端日志 Enter GpsBound: RaidId 23300888, IndoorId 61 // ⇔ IndoorConfig.Id=61 = "WowDonald's" @ (859,1847),玩家在 (866,1840),差 9.5 m。 // ★★ 2026-09-27 补证:推下去的到底是哪个号 // 1. 先由位置推出 IndoorId(上面这条,来自 IndoorConfig); // 2. 再用 Configs/WorldStateIndoorSectorConfig.json 把 IndoorId 翻成 // 扇区 Id(17600002…17600109) —— 详见 IndoorSectorCatalog4229938。 // 扇区 Id ≠ IndoorId,是该表的 BelongingIndoorId 字段指回 IndoorId。 // 一个 IndoorId 可以有多个扇区(Spiko Hotel 7 个、ACD Nova Branch 9 个)。 // 3. 参数语义 2026-09-27 20:1x 已用 A/B 定案:就是扇区 Id。 // 同一轮推 9 个扇区 Id + 1 个裸 IndoorId,客户端只报错裸 IndoorId: // #NoCreateIssue Nexus SetIndoorSectorState …(17600101,True)…(17600109,True),(76,True) // + config not found for id 76 —— 9 个扇区 Id 全被接受。 // ⇒ alsoPushRawIndoorId 已默认关闭,别再打开。 // ⛑ SyncLoadIndoorSector 这条 9 个里成功 7 个(2026-09-27 20:4x 实机)。 // 客户端每轮固定报 2 条 // LoadSectorByName: nameHash:-1386073846 / -1386073848 not found, // 时间戳与服务端每次推送逐秒对齐(20:41:45/:50/:55 …),hash 恒定不变 ⇒ // 不是随机错位,是「9 个扇区里有 2 个的场景对象不在当前已加载子场景里」。 // 另外 9 个 id 在 SetIndoorSectorState 里 0 条 config not found(全被接受)。 // ⇒ 通道是通的,剩下 2 个多半在别的楼层(ACD MapFloorList=[-1,1,2])。 // (hash 不是 FNV1/FNV1a/CRC32/djb2/sdbm/BKDR/RS/JS/PJW/ELF/DEK/AP/Java31/ // Murmur2/OAAT,已逐个反查过 44 个 SectorName,别再反查。) // ★ 2026-09-27 20:4x 抖动根因 + 修复:ACD Nova Branch(Id=76) 与 // ACD快乐房(Id=129) 的 MiniMapPos 矩形互相重叠,纯几何最近邻 // 在重叠区每几秒 76↔129 跳一次 ⇒ 客户端反复建删 9 棵扇区子树。 // 修法两条:① 优先采信客户端 SyncChangeIndoor 上报的权威 indoorConfigId // (preferClientReport);② 再叠 switchDelayMs 防抖(客户端自己也会 // 瞬时抖到 0,光靠 ① 治不了)。实测把玩家传送到 76 的非重叠地段后立刻稳定。 // ⛔ 覆盖上限:全表 44 扇区 / 约 25 场馆,全是剧情室内;普通店铺(麦当劳等) // 一条都没有,走 PCG,本通道管不着。 // 线格式(C# 原生,无 lua 实现 —— 已从 lua_full 全文 grep 确认为 native): // // S2C SyncLoadIndoorSector(68095220) = uint indoorSectorControlId // → 4 字节裸 UInt32(单字段 args,无 marker) // S2C SyncIndoorSectorControl(68971505) = Dictionary<uint, bool> // → FF(marker)+ Int7 count(7bit,+1 偏置) // + 每条 (UInt32 LE key + u8 0/1) // // 依据:dump.cs UXRPCMethodArgs68095220.indoorSectorControlId / // UXRPCMethodArgs68971505.indoorSectorControl;集合字段恒定带 marker(硬规则 182)。 // ★★ 2026-09-27 19:5x 实机订正(必须记住): // count 一开始写成了 I32(照搬 UxContract.WriteDictionary 的框架默认 // UxCountEncoding.Int32),结果客户端把 4 字节计数当 varint 读,整条 payload 错位: // Player.log 刷了 153 行 // SetIndoorSectorState: config not found for id 1694498816 / 1711341836 / …。 // 把收到的错位字节反推求解后,九个产物值与「FF + Int7(count) + U32 LE key」的模拟 // 结果逐个相等(差分全 0),而且「发 10 条 / 客户端只处理 9 条」正好对应 // varint(0x0A)=10 再 −1 ⇒ Int7。 // ⇒ 这条字段在客户端契约里是显式 [UxCollection(Count=Int7)], // 不能用框架默认的 Int32(见 TOPIC_序列化UxContract.md 第 10 行)。 // ⇒ 以后再手写 any Dictionary payload,count 一律先试 Int7。 // 回滚:world.content.indoorSectors.enabled = false(不需要重新编译)。 // func GameRouter_IndoorSector_IndoorSectorStateOf(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_IndoorSector_IndoorSectorStateOf var zero any; return zero } // 重连 / 切场景:忘掉当前室内,下次重新判定(客户端也把扇区状态丢了)。 func GameRouter_IndoorSector_ResetIndoorSector(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_IndoorSector_ResetIndoorSector var zero any; return zero } // // 由 gadget 网格 tick 顺带驱动。玩家走进/离开某个 IndoorConfig 条目附近时, // 推 SyncLoadIndoorSector + SyncIndoorSectorControl。 // func GameRouter_IndoorSector_TickIndoorSectorAsync(ctx context.Context, session *GameSession, state any, px float32, pz float32) any { // Router implementation for GameRouter_IndoorSector_TickIndoorSectorAsync var zero any; return zero } // 把上一次推下去的扇区全部关掉(离开室内 / 换室内时调用)。 func GameRouter_IndoorSector_ClosePushedAsync(ctx context.Context, session *GameSession, owner any, previousIndoorId uint32) any { // Router implementation for GameRouter_IndoorSector_ClosePushedAsync var zero any; return zero } // SyncLoadIndoorSector(68095220) —— 单个 UInt32,单字段 args 不带 marker。 func GameRouter_IndoorSector_PushLoadIndoorSectorAsync(ctx context.Context, session *GameSession, id uint32) any { // Router implementation for GameRouter_IndoorSector_PushLoadIndoorSectorAsync var zero any; return zero } // // SyncIndoorSectorControl(68971505) —— Dictionary<uint,bool>: // FF marker + Int32 count(原始,非 7bit)+ 每条 (UInt32 key + u8)。 // func GameRouter_IndoorSector_PushIndoorSectorControlAsync(ctx context.Context, session *GameSession, id uint32, on bool) any { // Router implementation for GameRouter_IndoorSector_PushIndoorSectorControlAsync var zero any; return zero } // // SyncIndoorSectorControl(68971505) —— Dictionary<uint,bool>,一次带多条: // FF marker + Int32 count(原始,非 7bit)+ 每条 (UInt32 key + u8)。 // 一个 IndoorId 常常对应好几个扇区(Spiko Hotel 7 个、ACD Nova Branch 9 个), // 所以批量推,避免连发 9 个包。 // func GameRouter_IndoorSector_PushIndoorSectorControlBatchAsync(ctx context.Context, session *GameSession, pairs []any) any { // Router implementation for GameRouter_IndoorSector_PushIndoorSectorControlBatchAsync var zero any; return zero } type IndoorSectorSessionState struct { SyncRoot any `json:"syncRoot"` // 上一次推下去的 indoor id;0 = 当前判定在室外。 CurrentId uint32 `json:"currentId"` // 进过至少一个室内(用于区分「还没判定过」和「判定为室外」)。 HasJudged bool `json:"hasJudged"` LastPushMs int64 `json:"lastPushMs"` WarnedEmpty bool `json:"warnedEmpty"` WarnedNoRaid bool `json:"warnedNoRaid"` Pushes int32 `json:"pushes"` // 上一次真正推下去的扇区 Id 列表(离开时要逐条关掉)。 PushedSectors []uint32 `json:"pushedSectors"` // 上一次是否额外推了「裸 IndoorId」那一路。 PushedRawId bool `json:"pushedRawId"` WarnedNoSectorTable bool `json:"warnedNoSectorTable"` // // 防抖用:正在观察的候选 indoor id(0 = 没有候选)。 // 见 PrivateServerConfigStore 的 indoorSectors.switchDelayMs。 // PendingId uint32 `json:"pendingId"` // 候选 id 第一次被观察到的时刻(TickCount64)。 PendingSinceMs int64 `json:"pendingSinceMs"` }