using Ananta.SDK.Rpc; using Ananta.Server.Protocol.Client4229938; using Ananta.Server.Handlers.LoginGate; namespace Ananta.Server.Handlers.Game; /// /// Build 4229938 private-server surface: world entry, direct character switching, /// combat, traversal/buffs and movement. Everything else falls back to typed-neutral RPC replies. /// internal sealed partial class GameRouter { internal const string WorldStateKey = "client4229938-world"; private readonly GateSessionHub? _gateSessions; internal GameRouter(GateSessionHub? gateSessions = null) => _gateSessions = gateSessions; /// /// 客户端挂起(切后台 / 失焦,RequestPlayerStartHangup)期间必须停止一切后台推送。 /// /// 客户端在 Hangup 时不再消费网络消息;服务端若继续全速写,TCP 缓冲会被灌满, /// 客户端回到前台时判定「与服务器的网络连接已断开」⇒ RPC session 归零 /// (Player.log: session=0|pid=0|0->0 + RPCNotify invalid socket)⇒ /// 进 Reconnect Module ⇒ 12 秒重连超时。 /// 实测 2026-09-22 23:58:19 / 23:58:32 / 00:05:19 三次断线都是这个形态, /// 且每次都紧跟在 [Davinci] type=TimeScaleAnomaly 之后 0.5 ms。 /// /// ⛑️ WorldEntryState.IsHangup 在加这道门之前全项目**只有写入、零处读取** /// (挂起只是记了个日志就不管了),这是断线重连的直接原因。 /// 所有后台推送循环在 tick 开头调用它:挂起期间只睡不推,恢复后自动继续。 /// internal static bool IsBackgroundPushPaused(Ananta.SDK.Network.TcpSession? session) { if (session is null) return false; var state = GetStateIfExists(session); if (state is null) return false; lock (state.SyncRoot) return state.IsHangup; } private static readonly HashSet Enabled4229938MethodIds = new() { // Login / world-entry barriers. MethodId.LoginGame, MethodId.RequestGameSceneData, MethodId.GetServerTimeGame, MethodId.GetSpriteToken, MethodId.CheckPSOPermissions, MethodId.AskSetGamePause, MethodId.AskLoadSceneCompleted, MethodId.AskLoadGameResCompleted, MethodId.AskLoadingFinished, MethodId.AskLoadedInSameScene, // Roster + direct character switching. MethodId.AskAllSpiritPanelData, MethodId.AskSwitchSpirit, // 客户端 GM 控制台的「就地切人」(69758035)。INVOKE,参数 = uint32 的 FightSpirit id。 // ⛑ 硬规则 94:不进这张表 = 编译通过但 handler 永不注册,客户端会 await 到超时。 // 之前 3 次 INVOKE 全部落到 unimplemented 空 ack,就是这个原因。 MethodId.GmSwitchSpiritHere, // 切人演出 phase 2 回执(2026-09-24,见 GameRouter.Switching.cs)。INVOKE, // ⛑ 硬规则 94:不进这张表 = 编译通过但 handler 永不注册,客户端会 await 到超时。 MethodId.ReportPreSwitchSpiritFinish, // Tab wheel summoning (嘉姆/帮派Boss, dial to the second wheel). Unlike the Z wheel this // one really asks the server, so it is how characters outside the client's CanJoin list // can be brought into the world. See GameRouter.GangBoss.cs. MethodId.AskSummonGangMember, // The same wheel's "disband all". Summoned characters have no other despawn path, so // leaving this unrouted means they accumulate permanently. MethodId.AskDestroyAllGangMember, // Fashion / wardrobe: client sends wear choices from the appearance panel; // the server echoes SyncSetSpiritFashions so the model keeps the skin after closing. MethodId.AskSetSpiritFashionsWithSource, // Wardrobe bookkeeping (mark-seen / favourite / read suit list). Ack-only INVOKEs: with no // route the client awaits a response that never comes and the wardrobe button hangs. MethodId.AskReadFashions, MethodId.AskFavoriteFashions, MethodId.AskReadFashionSuits, // Wardrobe dye (染色). Ack-only INVOKEs — the client commits the dye locally on err=0 and // only needs the server to remember it (see GameRouter.FashionColoring.cs). Unrouted, the // dye page waits on an INVOKE that is never answered and the apply button hangs. MethodId.AskUnlockFashionColoringSlot, MethodId.AskSetFashionColoringSchemeInfos, MethodId.AskApplyFashionColoringSchemeInfos, MethodId.AskResetFashionColoringSchemeInf, // Weapon skins (武器皮肤). Ownership is seeded at login from WeaponSkinConfig; these two // remember which skin each character actually wears. Ack-only INVOKEs — unrouted, the skin // picker waits on a reply that never comes. See GameRouter.WeaponSkins.cs. MethodId.AskSetWeaponSkins, MethodId.AskSyncWeaponSkinToSpirits, // Profession phone apps (清洁工 Washer / 赛车手 Racer). Entry and switch gate were already // open; only the content RPCs were unanswered, so each panel rendered an empty shell. // See GameRouter.JobApps.cs. MethodId.AskGetWasherMissionInfo, MethodId.AskAcceptWasherMission, MethodId.AskFinishWasherMission, MethodId.AskRacingCompetitionGroupInfo, // Phone menu skin parts (手机壁纸/装饰/挂件). The apply button only asks; the worn suit // lives in the per-character login-payload field SpiritInfo.MobileSkinInfo, so the server // has to store the choice and echo it back as SyncSpiritMobileSkinPartInfo. MethodId.AskSetMobileSkinPart, MethodId.AskResetMobileSkinPart, // 手机拨号线(T1-9)。通讯录与解锁进度在登录帧下发,这两条是拨号/通话后客户端 await 的回执: // 不注册 = 走中性兜底,客户端回调拿到非预期分支 ⇒ 通话记录不落本地、联系人选项一律播 FailDialogId。 // 见 GameRouter.Phone.cs。⛑ 回滚:把这两行删掉即可(handler 不注册 = 回到兜底)。 MethodId.AskPhoneAddCallRecord, MethodId.AskPhoneContactOptionAction, // 对话线(T1-4 / T2-15)。`AskStartDialog` 客户端 await 的是 **uint 返回值**,兜底回的是 // 0 字节空包 ⇒ 实测拨 Shannon Dairy 卡 20 秒;另两条是 void,回空 OK 即可。见 GameRouter.Dialog.cs。 // ⛑ 2026-09-25 补记:Dialog.cs 里的 AskStartDialog 当时只写了 handler、**漏了这三行白名单** // ⇒ 按硬规则 94 它根本不会注册。本次一并补齐。 MethodId.AskStartDialog, MethodId.AskDoDialogAction, MethodId.AskForceFinishDialog, // 牛奶车「车尾·回血」交互(2026-10-01 实机抓包:`AskDrinkMilk [0x04072CA2] body=8b` // = gadget uniqueId 0x5000000000000001,与 debugPlace 的 Seq 逐位吻合)。 // dump.cs:1730509 `UXRPCTask AskDrinkMilk(ulong instanceId)` ⇒ 返回 void,回空 OK 即可; // 回血本身由服务端推 SyncUnitAttrs + SyncUnitHp(见 GameRouter.MilkCar.cs)。 // ⛑ 回滚:删掉这一行即可(handler 不注册 = 回到兜底,点回血只有演出不回血)。 MethodId.AskDrinkMilk, // 杂项轻量回执(T1-1 NPC 语音 / T1-2 城市图鉴 / T1-18 锁定目标)。见 GameRouter.MiscAcks.cs。 // ⛑ AskSetClientLockTarget 实际是 NOTIFY,接它只为观察;注册 Both 以防将来改 INVOKE。 MethodId.AskAgentStartNpcSound, MethodId.AskReadCityPedia, MethodId.AskSetClientLockTarget, // 第二批(2026-09-25 19:5x):全部取自 `unknown-methods.log` 里**仍在兜底**的 INVOKE, // 按最后出现时间筛过(不是照清单抄 —— 清单状态列已滞后)。返回全是 void ⇒ 空 OK。 // 见 GameRouter.MiscAcks.cs 文件末尾的注释(含逐条的 dump 行号与最后出现时间)。 MethodId.AskDoNpcAction, MethodId.SyncWorldBattlePlayers, MethodId.AskReleaseLuaSlotEntityEvent, MethodId.AskAddPaokuLimit, MethodId.AskRemovePaokuLimit, MethodId.AskEnterMoveGround, MethodId.AskLeaveMoveGround, // 第三批(2026-09-27):前两批把「返回 void ⇒ 空 OK」的收完了,这五条**要真返回值**, // 空 OK 会让客户端在反序列化处拿到 null/0 字节而走错分支。返回体布局全部以 // `RPCDeserializeAuto.lua` 的返回值 reader 为准(不是照 dump.cs 猜): // AskTimePanelInfo → struct 无标记 + 空 7Bit 列表(`FF 01`) // GetUploadObjectUrl → complex `FF` + string/u32/string(本服无上传端点,回空值) // AskItemDestructibleCreate → 裸 u64(号走 IdCounterStore,硬规则 104) // AskStartClimb / AskEndClimb → 日志里是 **NOTIFY**,注册 Both 只为留证据 + 防改 INVOKE。 // 见 GameRouter.MiscAcks.cs 第三批注释。⛑ 回滚:删这五行即可(handler 不注册 = 回兜底)。 MethodId.AskTimePanelInfo, MethodId.GetUploadObjectUrl, MethodId.AskItemDestructibleCreate, MethodId.AskStartClimb, MethodId.AskEndClimb, // 场景设备「发信号」:`AskSceneDeviceSendSignal`(67144566) —— `unknown-methods.log` 里 // **出现最多的 INVOKE**(249 次)。返回 void ⇒ 空 OK;⛑⛑ handler 里**绝不推** // `SyncSceneDeviceSignalSend`(68477555)(客户端会把它当自己的请求发回来 ⇒ 真回环)。 // 见 GameRouter.SceneDevices.cs 文件末尾。 MethodId.AskSceneDeviceSendSignal, // 第四批(2026-09-27 23:3x):T1-44 **A 组 16 条 void 回执**,一次清完。 // 逐条核过方向:`INVOKE` 1~156 次、`NOTIFY` **全 0** ⇒ 全部按 Invoke 注册。 // 返回全是 void ⇒ 空 OK。其中受击/击飞四条做了字段解析(唯一能读到 `stiffId` / `stiffTime` / // `speed` 真值的通道)。见 GameRouter.MiscAcks.cs 第四批注释。⛑ 回滚:删这 16 行。 MethodId.ReportUnitHitFly, MethodId.ReportUnitHitFlyEnd, MethodId.ReportUnitFallGround, MethodId.ReportUnitFallGroundEnd, // 第五批(2026-09-29 20:4x):投技/钳制两条,**纯抓包用**(只打日志 + 空回执,不改行为)。 // 目的是确认「客户端到底发不发、什么时候发、enemyid 是谁」——目前这两条只有 // DefaultReturnCatalog 的空兜底,连一行日志都没有,无法判断投技演出的时序。 // 契约:各一个 u64 enemyid(ClientToGameSceneDelegate.lua:326 / :4775)。 // ⛑ 回滚:删这两行 + GameRouter.MiscAcks.cs 里对应的两个 handler。 MethodId.AskControlPowerHoldEnemyUp, MethodId.AskControlPowerHoldEnemyFallGrou, MethodId.AskCancelInteractionActionRedPoi, MethodId.ReportPlayerStartCommonInteract, MethodId.AskPlayerOnMetro, MethodId.AskRemoveMapPin, MethodId.GmActiveBadgeEffect, MethodId.GMActivateNpcProfile, MethodId.EndWorldLifeGame, MethodId.AskRestaurantBuyFoods, MethodId.RecordAgentBehavior, MethodId.ChangeVehicleDoorState, MethodId.AskStartGuideByCondition, MethodId.AskSetSpiritFashionVariantPreference, // 第五批(2026-09-27 23:4x):T1-44 **B 组 5 条要真返回值的**。空 OK 会让客户端拿到 0 字节 // 后在反序列化处走错分支 ⇒ 按 lua 的返回值 reader 造真返回体。 // `AskPutMapPinFar`→`PinData`(struct 无标记,Id 走 IdCounterStore,PinPos 回传请求的 x/z) // `GetCharacterRandomDialog`→裸 u32 / `StartWorldLifeGame`→裸 u64(发号) // `AskBasketballOperatorAction`→裸 u64(0) // `AskWushuTournamentSeasonData`→complex(全 0 + 空 Rounds 列表) // ⛑ 只保证结构与 await 正确,不保证语义(无对话表 / 无赛季数据 / 图钉不持久化)。 // 见 GameRouter.MiscAcks.cs 第五批注释。⛑ 回滚:删这 5 行。 MethodId.AskPutMapPinFar, MethodId.GetCharacterRandomDialog, MethodId.StartWorldLifeGame, MethodId.AskBasketballOperatorAction, MethodId.AskWushuTournamentSeasonData, // 第七批(2026-10-03):挑战记录 **T0-2** 的运行时那一半。 // 客户端 2026-10-02 11:58 实机跑过一轮完整挑战流程(AskNewChallengeRecordList → // StartNewChallenge → SetNewChallengeData → FinishNewChallenge),当时四条全落兜底。 // ⛑ 只接「记录读写」三条:`StartNewChallenge` / `SetNewChallengeData` 之外的 void 类与兜底字节 // 完全等价;`FinishNewChallenge` 要新造 4 个嵌套契约(Reader[607/308/1428/500])才能接, // 而兜底那 88 字节本来就是对的 ⇒ 不动它。详见 GameRouter.ChallengeRecords.cs 文件头。 // ⛑ 回滚:删这 3 行即可(handler 不注册 = 回到中性兜底)。 MethodId.SetNewChallengeData, MethodId.AskNewChallengeRecord, MethodId.AskNewChallengeRecordList, // 第六批(2026-09-29):`unknown-methods.log` **近三天(09-27 / 09-28)**仍在兜底、 // 且源码里确实没有 handler 的 **全部 13 条 INVOKE**。 // ⛑ 取数方式:日志是 09-15 起的累计,top30 里绝大多数早已注册 ⇒ 不能直接照抄频次榜, // 必须先按 ts 过滤再与源码做差集(硬规则 214)。这 13 条 NOTIFY 计数全为 0 ⇒ 全按 Invoke。 // 返回:11 条 void(空 OK)+ `AskCloseConnectionToGame` 回 `0u` // + `AskMultiverseStatus` 回 complex 空壳(见 MiscMethods4229938.cs)。 // ⛑ 回滚:删这 13 行即可(handler 不注册 = 回到中性兜底)。 MethodId.GMChangeNpcFavor, MethodId.GmDropReward, MethodId.FastReenter, MethodId.AskVehicleSendGamePlaySignal, MethodId.AskChangeSceneItemQuality, MethodId.AskPuppetGetup, MethodId.AskChangeRoleUseSystemName, MethodId.AskMetroHit, MethodId.AskMetroHitEnd, MethodId.AskSaveSettings, MethodId.AskCloseConnectionToGame, MethodId.AskClearSwitchInFashionCache, MethodId.AskMultiverseStatus, // 第七批(2026-09-29):客户端 **GM 控制台** 剩下的 9 条 INVOKE(各只出现过 1 次)。 // 频次低但**方向是 INVOKE** ⇒ 漏登记 = 控制台按钮 await 到超时(硬规则 94)。 // 返回全部 void ⇒ 空 OK。⛑ 不做真实现(缺系统解锁表,硬规则 230)。 // 加上第六批,近三天日志里的 **INVOKE 已 100% 有回执**。⛑ 回滚:删这 9 行。 MethodId.GmWorldRefreshEnemyGroup, MethodId.GmRecoverAllSkillCooldown, MethodId.GmFinishAllGuides, MethodId.GmUnlockSystem, MethodId.GmUnlockPlanningBoardMultiPlayer, MethodId.GmRefreshAllDestructibles, MethodId.GmCompleteAgentProfileTarget, MethodId.GmChangeSystem, MethodId.GmAddFan, // 第八批(2026-09-29):**全时段**仍未注册的 INVOKE 里返回 void 的 47 个。 // ⛑ 上一轮只统计「近三天」,结论「INVOKE 全部有回执」不严谨 —— 全时段还有 56 个。 // 本批一次接住其中 void 的 47 个(含 22 个 GM/Force 控制台命令:按了会 await 卡住)。 // 剩下 9 个要真返回值的见 GameRouter.MiscAcks.cs 第八批末尾「本批未做」清单。 // ⛑ 回滚:删这 47 行即可。 MethodId.AskTeleportOnCrossBoundary, MethodId.AskCreateHouseParkingVehicles, MethodId.AskDestroyHouseParkingVehicles, MethodId.AskUseItems, MethodId.AskPoliceTakeCaseReward, MethodId.AskActivateNpcProfile, MethodId.AskSwitchSpiritByTaskRole, MethodId.ForceAcceptTask, MethodId.ForceSubmitTask, MethodId.RecordSceneDeviceBehavior, MethodId.AskPlayerEnterWater, MethodId.AskPlayerLeaveWater, MethodId.AskStartTaxiNavigate, MethodId.AskEndTaxiNavigate, MethodId.AskEndTaxiTeleport, // 2026-09-30 出租车真实现(GameRouter.Taxi.cs):AskStart/AskEnd 原本是占位 AckVoid, // 现在由 Taxi.cs 接管;下面两条是本次新增注册(缺了 handler 根本不会被调用,规则 94)。 MethodId.AskTaxiAccelerate, MethodId.AskAetherAISetVehicleStatus, MethodId.AskCreateCustomLink, MethodId.AskCancelInviterPlayerInteractio, MethodId.AskUpdatePlayerScenarioInfo, MethodId.AskChangePlayerScenarioSlot, MethodId.AskChangeNameByItem, MethodId.AskSetSpiritWearFashionHiddenPartsWithSource, MethodId.AskTakeNpcProfileProgressRewardWithWeb, MethodId.AskReportArcadeGameResult, MethodId.AskPoliceTrailTeleport, MethodId.AskPoliceFakeFileTakeReward, MethodId.AskTradeListItem, MethodId.RequestRevive, MethodId.GmDaVinciCode, MethodId.GmAddVehicle, MethodId.GmAddHealItems, MethodId.GmAddAllSpirits, MethodId.GmUnlockBadge, MethodId.GmUnlockAllQuest, MethodId.GmSetMapEntrance, MethodId.GmSubmitEvent, MethodId.GmEnableThreatDebug, MethodId.GmStartEnemyDetectDebug, MethodId.GmStartEnemyDetectStateDebug, MethodId.GmEndEnemyStrategyDebug, MethodId.GmStartSyncAIAction, MethodId.GmEndSyncAIAction, MethodId.GmRegenerateWildEnemy, MethodId.GmKillEnemy, MethodId.GmEnemyAiStopFlagByCamp, MethodId.GmAllSwitchToBattle, MethodId.GmTestDamage, MethodId.GmDeathPartyForcePlayerWin, // 第九批(2026-09-29 16:2x 实机后补):历史日志里没有、本次会话第一次被点到的 3 条 GM INVOKE。 // ⛑ 「收口」是相对当前日志的 —— 每次实机完都要重跑 `_wm_pending_byid.py`(第八批的教训)。 // `GmAddWeapon` 是 0.5~1 秒连发重试,最该接的一条。返回全部 void。⛑ 回滚:删这 3 行。 MethodId.GmAddWeapon, MethodId.GmAddOtherBuff, MethodId.GmRemoveBuff, // 第十批(2026-09-29 20:4x):相对上一轮差集的**唯一新增 INVOKE**。 // RET 是 List7Bit(非空 void),handler 手拼 `FF 01`,见 GameRouter.MiscAcks.cs。 // ⛑ 回滚:删这 1 行 + MiscAcks.cs 里第十批那条 handler。 MethodId.AskSetSpiritFunctionSuitSchemeIn, // Combat / weapon sandbox. MethodId.AskSwitchWeapon, MethodId.AskSwitchFightStyle, MethodId.AskSetWeaponFightStyle, MethodId.AskLoadWeaponToSlot, MethodId.AskDepositSpiritWeapon, MethodId.AskExchangeWeaponSlot, MethodId.AskClientUseCommonSkill, MethodId.AskUseSkill, MethodId.AskSkillUseWeaponDurability, MethodId.AskWeaponEquipBullets, MethodId.ReportSkillEnd, MethodId.AskMultipleSkillHit2, MethodId.AskInterruptSkillExecute, MethodId.AskInterruptSkillExecuteStiff, MethodId.AskSkillExecuteEnd, MethodId.AskSkillExecute, MethodId.AskSkillDestructibleCreate, // Dropped-weapon pick-up. Paired with AskSkillDestructibleCreate above — see // GameRouter.WeaponDrop.cs: the drop hands out a destructible id and this one consumes it. MethodId.AskPickUpWeapon, // Scene prop activation report — see GameRouter.Gadget.cs. The client flips the prop // locally and tells us; we echo it back so the state survives a relog. MethodId.AskActiveDynamicGo, // Doors as gadgets (T2-11) — see GameRouter.Door.cs. The client reports a crossing and // waits for the INVOKE answer; unrouted it never entered the door flow at all. MethodId.AskPlayerEnterOrLeaveDoor, // Lifts — see GameRouter.Elevator.cs. The call button is only a request; the cab moves // when we echo SyncMobilePlatformInfo back, so these must be answered, not swallowed. MethodId.AskMoveMobilePlatform, MethodId.AskGetOnMobilePlatform, MethodId.AskGetOffMobilePlatform, // Scene devices (doors / vending / fridge / arcade) — see GameRouter.SceneDevices.cs. // The generic fallback already answers these with the generated 8-byte callback, but the // client also waits for the echoed state/value transition before it plays the interaction, // and the generated neutral answer to the Spoon condition check is "false". MethodId.AskSceneDeviceChangeState, MethodId.AskSceneDeviceRecordValue, MethodId.CheckCanOccupySceneDevice, MethodId.ReleaseOccupySceneDevice, MethodId.AskCheckSpoonConditionByType, MethodId.AskGetSpoonServerOutputPortIndexByType, MethodId.AskDoSpoonServerActionByType, // Client-spawned (local) enemy combat. These are the reports the client's own combat // sim emits about the units it owns; GameRouter.LocalEnemyCombat.cs answers the fall // handshake and logs the rest, so a locally generated enemy's real behaviour is // visible in the console instead of being inferred. MethodId.AskPlayHurtEffect, MethodId.AskEnemyStartFall, MethodId.AskEnemyEndFall, MethodId.AskEnemyFallGround, MethodId.AskEnemyUseClientSkill, // ⛑⛑ 硬规则 94:白名单是「允许注册的 [Handler]」集合,只在 C2S 方向生效; // 但 GameRouter 是 partial,EnemyStiff.cs / LocalEnemyCombat.cs 里新写的 handler 一样走 // RegisterSelected —— 不进这张表,代码编译通过、日志却永远不会出现(Invoke 还会让客户端 await 卡死)。 // 新增任何 [Handler] 后都必须同步补这里。 MethodId.AskAgentEnterStiff, MethodId.AskAgentExitStiff, MethodId.ReportUnitStandUp, MethodId.ReportEnemyPrepareFinish, MethodId.ReportAgentDiePerfEnd, MethodId.ReportEnemyBattleMoveEnd, // Client-decided traversal/web buffs. MethodId.AskAddClientBuff, MethodId.AskRemoveClientBuff, MethodId.AskEnterFeiSuoCrouch, MethodId.AskFeiSuoSuccess, MethodId.AskLeaveFeiSuoCrouch, // 蹲伏暗杀:客户端上报「谁在我背后」,我们回 SyncEnemyCanAssassinate 授权。 // 不登记 ⇒ 客户端每 0.4s 发一次但服务端完全收不到 ⇒ HUD 不出暗杀提示(规则 94)。 MethodId.ReportCanAssassinateEnemies, // Live transform tracking. MethodId.AskReportLogicAgentSyncData, MethodId.AskUnitMoveActionSimple, MethodId.AskUnitMoveActionSimpleWithGround, MethodId.AskUnitMoveActionWithGround, MethodId.AskUnitMoveAction, // Time/sync basics. MethodId.GetServerTimeGame, // Task panel: the client offers "settle/abandon the running task" on ESC whenever it // believes a task is active, and confirming it invokes this. Without an answer the // client's await never completes and the panel appears frozen. MethodId.AskDeleteTask, // Scene ("raid") switching. MethodId.AskPublicSwitchToPublicScene, MethodId.AskEnterRaidByMapEntrance, // 大地图「交通直达」同图传送(2026-09-29 补):MapSubSystem_Entrance.lua:566 的 // AskPlayerChangePositionByScenePortal 是 Invoke,不进白名单 handler 一次都不会被 // 调用(硬规则 94)⇒ 客户端 await 永久挂起 ⇒ 地铁站点了没反应。 MethodId.AskPlayerChangePositionByScenePortal, // Teleport (inbound requests; pushes need no registration). MethodId.AskTeleport, MethodId.ReportPreTeleportFinish, MethodId.ReportPostTeleportFinish, MethodId.Teleport, // 车辆改装店(ApplyCar / RepairShop)进场事务的两个 domain RPC: // 不进这张表 handler 一次都不会被调用(硬规则 94)⇒ 客户端停在 loading 黑屏, // 而且它们都是 INVOKE,漏登记会让客户端 await 永久挂起。见 GameRouter.Teleport.cs。 MethodId.AskTeleportToParkingWaypoint, MethodId.AskTeleportToLeavingWaypoint, // Vehicles: summon + owned fleet + client-driven drive loop + S011 boarding story. // Drive-loop notifies/invokes are accepted (no-op / empty-OK) so the client-side // drive loop never stalls — proven set from the sibling 08.05 build. MethodId.GmAddVehicle, MethodId.AskVehicleShopSpawnVehicle, // 改装店购买链(2026-09-26 自 repair-shop.zip 移植)。全是 INVOKE:不进白名单 // 客户端就永远 await 不到回包(面板点了没反应 / 卡住),所以必须注册。 MethodId.AskNpcShopCommodityInfo, MethodId.AskVehicleShopDestroyParking, MethodId.AskBuyCommodities, MethodId.AskBuyCommodity, MethodId.AskBuyModifyParts, MethodId.VehicleDriveStateChange, MethodId.AskGetUnlockedVehicles, MethodId.AskSummonVehicle, // T1-16 载具骇入 (ported from donor_7k). MethodId.AskHack, MethodId.AskHackVehicle, // EonBug app "pick a handle" dialog (63975191, INVOKE). Left unanswered it re-arms every // couple of seconds and the app can never be closed — see GameRouter.HackerApp.cs. MethodId.AskChangeHackerName, // EonBug forum actions (读新帖 63150736 / 接差事 63036377). Both return void; unrouted they // are INVOKEs the client awaits forever, so tapping a post never clears. MethodId.AskReadHackerNewPost, MethodId.AskAcceptHackerPostTask, MethodId.AskUpdateVehicleAITaskStatus, // Cat Express / 蓝猫物流 (UberSim) — courier board. All nine are INVOKE: an unregistered // one makes the client await a reply that never comes (hard rule 94), which is exactly what // AskGetTruckSatisfactionAverage was doing — 16 unhandled 0-byte calls in unknown-methods.log. MethodId.AskGetTruckJobOrders, MethodId.AskRefreshTruckOrder, MethodId.AskAcceptTruckJobOrder, MethodId.AskObsoleteTruckJobOrder, MethodId.AskAutoAcceptTruckJobOrder, MethodId.AskGetFinishedOrderWraps, MethodId.AskGetTruckSatisfactionAverage, MethodId.AskSetTruckJobDefaultVehicleId, MethodId.AskStartTruckOrderGuide, MethodId.AskPreSettleTruckOrder, MethodId.AskSettleTruckOrder, MethodId.AskFinishJob, MethodId.AskAddTruckOrderSpecialPointRewards, // 取货点 / 送货点坐标(自定义单路径 DeliveryTaskManager.lua:1870 会等它的回包)。 MethodId.AskQueryTruckPosInfo, // Urban-job (职业) handshakes. AskStartJob is the one that actually gates the courier app: // the "领取驾照" start button awaits it and only opens the order board from its callback, so // an unregistered id strands the player on the start screen instead of just losing a feature. MethodId.AskStartJob, MethodId.AskQuitJob, MethodId.AskAcceptTask, // T2-31 Seymour 大停电 (Blackout) — Gadgets_Hacker_Tetris 控制器回路。 // AskStart/AskFinish 是 Invoke:漏登记会让客户端 await 永久挂起,不只是"没功能"。 // 这几个 MethodId 之前都没在本仓出现(_handler_dup_check 基线干净),不存在重复注册。 MethodId.AskStartHackerTetris, MethodId.AskFinishHackerTetris, MethodId.ReportHackerTetrisCreation, MethodId.AskTaskGadgetsListLoadComplete, MethodId.AskTaskPlateLoadComplete, // T2-32 路人骇入(骇路人手机看信息)。AskHackingNpc / AskHackingNpcPress 是 INVOKE // (gReliableRpcManager:RegisterRPC 等回执),漏登记 ⇒ 长按骇入时客户端 await 永久挂起; // ReportBeHacked 是 NOTIFY,登记它只为不再落进 unknown-methods.log。 // 触发前提是服务端推过 SyncUnitHackableState(68941159) —— 见 GameRouter.HackNpc.cs。 MethodId.AskHackingNpc, MethodId.AskHackingNpcPress, MethodId.ReportBeHacked, // T2-32 黑客召唤物(蜘蛛机器人 / 送货无人机)的操控交接。 // 两个都是 gReliableRpcManager:RegisterRPC 等回执的 INVOKE:长按 F 回收、N 键切换操控。 // 不登记的话客户端 await 永久挂起,操控面板的按钮就是按了没反应。 MethodId.AskStopControlAgent, MethodId.AskControlAgent, // 巡卫官(里希 15021021)执勤终端的三条派遣(巡卫犬 2 / 直升机搜查 3 / 战术支援 4)。 // INVOKE:客户端 gReliableRpcManager 等回执。此前不在表内 ⇒ 走 DefaultUnknownInvoke 空 OK, // 按钮"点得动、没效果"。⛑ 硬规则 94:不进这张表 handler 一次都不会被调用。 // 见 GameRouter.PoliceDispatch.cs。回滚 = 删掉这一行(回到中性兜底)。 MethodId.AskPoliceDispatch, // 直升机搜查(同一条派遣线的第二组):AskHelicopterNpc 是 INVOKE 且客户端四个调用点都在 // RegisterRPC 等回调,漏登记 = 面板点了卡住;ScanChange 是 NOTIFY,登记只为不再落进 // unknown-methods.log;Stop 是 INVOKE(无参)。见 GameRouter.PoliceDispatch.cs。 MethodId.AskHelicopterNpc, MethodId.AskHelicopterScanChange, MethodId.AskPoliceStopHelicopterDispatch, // 正向逮捕 NPC(2026-09-25):AskGetArrestTimes 返回 int、AskIsArrested / AskPoliceEffectiveExam // 无返回值,三条都是 INVOKE ⇒ 漏登记就是面板点了卡住。AskPoliceExamInteract 由 C# 侧发 // (lua 零引用、RpcSurface 里也没有),形状未知,接住只为回执 + 取证 hex。 // AskInteractCmd 是 NOTIFY,登记只为不再落进 unknown-methods.log 并抓前 5 条字节。 // 见 GameRouter.PoliceArrest.cs。回滚 = 删这五行(回到中性兜底)。 MethodId.AskGetArrestTimes, MethodId.AskIsArrested, MethodId.AskPoliceEffectiveExam, MethodId.AskPoliceExamInteract, // 开罚单(2026-09-25):PoliceExamineManager.lua:1163 由 C# 侧发 AskPoliceFine(pid, fineIdList), // INVOKE ⇒ 不登记就是"点了开罚单没结果"(客户端 await 卡死 + 服务端从不回掉落帧)。 // 登记后由 GameRouter.PoliceFine.cs 回 ShowReceiveRewardDetail(64145515) Reason=PoliceFine // ⇒ 客户端弹罚款结果。回滚 = 删这一行 + 删 GameRouter.PoliceFine.cs。 MethodId.AskPoliceFineNpc, // 掉落物拾取(路人打落的手机 / NPC 携带物)。两个都是 INVOKE、返回体空 reader: // 客户端掉手机是本地行为(GM DebugEnableDropPhoneWhenDeath),但拾取时要服务端回执, // 不登记 ⇒ 走 DefaultUnknownInvoke 兜底、拾取动作卡在 await(硬规则 94)。 // 见 GameRouter.NpcPhoneDrop.cs。回滚 = 删这两行(回到中性兜底)。 MethodId.AskPickUpSceneItemTransferToItem, MethodId.AskEnemyItemPickUp, // Plate(盘)演出管线的四条客户端回执(2026-09-25 试点,见 GameRouter.Plate.cs)。 // 它们只在服务端推过 SyncPlateAddList(68554224) 之后才会发,unknown-methods.log 里 // 一次都没出现过 ⇒ 不登记就永远看不到,也就没有「线格式对不对」的判据。 // AskPlateLoadComplete / AskPlateResourceReady 是★通关判据。全按 INVOKE 回空 OK, // 免得客户端 await 挂死。回滚 = 删这四行。 MethodId.AskPlateLoadComplete, MethodId.AskPlateResourceReady, MethodId.AskPlateAgentCreateConfirmed, MethodId.AskNotifyPlateGameStallEvent, // ★★ 客户端主动问「好感 NPC 都在哪儿」(2026-09-25):`AskQueryAllFavorNpcAgentPos` // 在 unknown-methods.log 里出现过 54 次,我们一直回空字典 ⇒ 客户端眼里一个好感 NPC 都不在场。 // 真数据 = NpcActivityCatalog 的活动坐标 + RaidId(与常驻 NPC 落点同一份数据)。 // 见 GameRouter.NpcLife.cs。回滚 = 删这两行。 MethodId.AskQueryAllFavorNpcAgentPos, MethodId.AskQueryFavorNpcAgentPos, MethodId.AskPlayerStartEnterOrExitVehicle, MethodId.AskPlayerFinishEnterOrExitVehicle, MethodId.AskVehicleMove, MethodId.AskVehicleStartMove, MethodId.AskVehicleStopMove, MethodId.AskVehicleHorn, MethodId.AskEnterVehicleIndoor, MethodId.AskExitVehicleIndoor, MethodId.ReportDrivingVehicle, MethodId.AskKillVehicle, MethodId.AskChangeGoVehicleDriveState, MethodId.AskVehicleDeadEnd, MethodId.AskVehicleNitro, MethodId.AskVehicleStuck, MethodId.AskVehicleHit, MethodId.AskVehicleHitEnd, MethodId.AskClaimVehicleSeat, // 载具路线规划 / 自驾(2026-09-24,GameRouter.VehicleNav.cs)。前三条客户端正在每秒问一次, // 之前全落到 typed-default = 空路线; // 2026-09-27 补:Start/Stop/ChangeTarget 现在会真的下发 S2C 68349264 推/关自动驾驶, // 并把漏注册的 CancelTarget(67836393) 补上 —— 它是 INVOKE,缺回执会让 lua await 卡死(规则 84)。 MethodId.AskVehicleNavigationPathPoints, MethodId.AskVehicleNavigationPathPointsFromPos, MethodId.AskVehicleNavigationPathLength, MethodId.AskVehicleStartAutonomousDriving, MethodId.AskVehicleStopAutonomousDriving, MethodId.AskVehicleChangeAutonomousDrivingTarget, MethodId.AskVehicleCancelAutonomousDrivingTarget, MethodId.SyncStoryCoreClientInfo, MethodId.AskInteractCmd, MethodId.AskInvitePlayerInteractionAction, MethodId.AskStartPlayerInteractionAction, MethodId.AskReplyInvitePlayerInteractionAction, // GM console: C2S invokes (spawn vehicle, add enemy, teleport). The client // materializes GM entities locally; the server returns real ids and logs them. MethodId.GmSpawnVehicle, MethodId.GmAddEnemyWithPosition, MethodId.GmAddEnemy, MethodId.GmAddEnemyByPlayer, MethodId.GmAddNpc, MethodId.GmTeleportXYZ, // Aether city simulation (pedestrians / static NPCs / traffic). The client runs // the whole simulation locally; these are the C2S calls it makes once Aether is // initialized. They must be answered or the awaited tasks stall the spawner. MethodId.AskMassHideAreas, MethodId.AskAetherChangeQuality, MethodId.AskAetherAIBorrowVehicle, MethodId.AskChangePedToVehicleNpc, MethodId.AskReportAetherLeaflet, MethodId.AskAetherAICreateRaidVehicleAndClaimSeatByAether, MethodId.AskAetherAIRaidVehicleRegressAether, MethodId.AskRaidVehicleConvertToAether, // Formerly-unhandled batch (explicit compatibility handlers). // AskTradeGetMarketList moved down to the trade pair - it now has a real handler. MethodId.AskTradeGetHistoryPage, MethodId.AskPlayerRankingSummary, MethodId.AskPopularityPhoneFirstOpened, MethodId.AskPopularityUIOpened, MethodId.SyncOpenInspireHub, MethodId.AskQueryInspireHubAllGamePlayRecommendData, MethodId.AskQueryInspireHubAllGamePlayRankData, MethodId.AskLinkInfos, MethodId.GetLastMode, MethodId.AskSwitchLinkMode, MethodId.OnParkourStateChange, MethodId.AskPlayerCameraMove, MethodId.AskUpdatePlayerCameraRotation, MethodId.AskUpdatePlayerCameraFOV, MethodId.AskUpdatePlayerCameraAspectRatio, MethodId.AskVehicleNitroValue, MethodId.AskModifyVehicleTopSpeed, MethodId.AskVehicleContactDamage, MethodId.AskVehicleInteractConfig, MethodId.AskUpdateVehicleDestructibleParts, MethodId.AskGetVehicleRadioContent, // 车载电台切台通知(67959772)—— 见 GameRouter.VehicleRadio.cs。 MethodId.AskSwitchVehicleRadio, // 大地图入口点击传送(63284182)—— 见 GameRouter.MapEntrance.cs。 // ⛑ 不进这张表 = handler 永不注册(硬规则 94)⇒ 点入口仍然没反应。 MethodId.AskTouchMapEntrance, MethodId.SyncChangeSafeArea, MethodId.SyncChangeBuilding, MethodId.SyncChangeIndoor, MethodId.GetMailHeadList, MethodId.AskAkxSessionList, MethodId.AskGetAllMetroInfos, MethodId.AskMetroGadgetIds, MethodId.AskMomentsPostSimpleInfos, // 趴卜 (Bubble) interaction half: likes / mark-read / comments / taps. All seven are INVOKE // and used to fall through to the generic typed default, so nothing the player did in the // app was ever recorded. Return shapes come from DefaultReturnCatalog4229938. MethodId.AskMomentsPostInfos, MethodId.AskMomentsUnreadMessage, MethodId.AskMomentsHaveUnreadMessage, MethodId.AskMomentsMarkRead, MethodId.AskMomentsLikePost, MethodId.AskMomentsSendCommentWithId, MethodId.AskMomentsTapPostWithCount, MethodId.AskPanelOpenOrClose, MethodId.AskQueryPlayerUnlockNameEffect, MethodId.GetPersonalInfo, // Skill/combat accepts (logged empty replies; authoritative damage later). MethodId.AskInterruptSkillExecute2, MethodId.AskSkillExecute3, MethodId.AskSkillExecuteEnd2, MethodId.AskSkillAddState, MethodId.AskSkillOpenShield, MethodId.AskSkillCloseShield, MethodId.AskSkillTimeCurve, MethodId.ReportSkillAnimationEnd, MethodId.AskSkillSpawnItem, MethodId.AskSwitchSpiritComplete, MethodId.AskSpoonClientAttack, MethodId.AskVehicleSkillDamage, MethodId.AskSkillDestructibleCreateGadget, MethodId.AskSkillDestructibleCreateVehicle, // Time + weather. MethodId.AskPassingTime, MethodId.ChangePersonalTimeSetting, MethodId.AddPersonalTimeSetting, MethodId.GmSetWeather, MethodId.GmSetWeatherParam, MethodId.GmSetTime, MethodId.GmFixRaidTime, MethodId.GmPassingTime, // Destructibles / red dots / discards / hangup / curves. MethodId.AskNotifyDestructibleHits, MethodId.AskOperateDestructibleObject, MethodId.AskMoveDestructibleObjects, MethodId.AskBreakDestructibleObjects, MethodId.AskReadWeaponRedDots, MethodId.AskDiscardWeaponByInstanceId, MethodId.AskDiscardWeapon, MethodId.RequestPlayerStartHangup, MethodId.RequestPlayerStopHangup, MethodId.AskBreakSkillTimeCurve, // Economy: gacha draws, mall purchases and the in-game GM console (ALT+F1) wallet grants. // Without these ids the handlers exist but are never registered, and every client request // silently falls into logs/unknown-methods.log. MethodId.AskDrawGacha, MethodId.AskMallBuyCommodity, MethodId.AskTradeGetMarketList, MethodId.AskTradeBuyItem, MethodId.AddMoney, MethodId.AddItem, // Talent trees (天赋, T1-6). All five are void-returning INVOKEs the client awaits; without a // route they fall through to the typed-neutral fallback and every activation is silently // dropped, so the panel shows the node as locked no matter how often the player clicks. MethodId.AskActiveGameplayTalentLayer, MethodId.AskResetGameplayTalent, MethodId.AskActiveSpiritJobTalentLayer, MethodId.AskResetSpiritJobTalent, MethodId.AskConvertCommonSpiritTalentExp, // NPC shops (商店). AskNpcShop is the "open the shelf" INVOKE the client fires when the // player presses F on a clerk; without a route it lands in unknown-methods.log and the // panel opens empty because no SyncFullCommodityInfos ever arrives. MethodId.AskNpcShop, // 商店的库存类交易(2026-10-02 自 7k-v0.6 移植)。⛑ 这四条不是「实现了买卖」, // 是**刻意回绝**:捐赠端原注释 "must not fall back to an empty success reply, which // would falsely claim a purchase/sale occurred"。它们也不在 DefaultReturnCatalog 里 // ⇒ 不注册就会走 ReturnEmptyOkAsync(error=0)= 客户端以为交易成功。详见 // GameRouter.NpcShopTrades.cs。AskCloseNpcShop 只是让它不再灌 unknown-methods.log。 MethodId.AskBuyCommodityToBag, MethodId.AskBarterBuyCommodity, MethodId.AskSellCommodityToShop, MethodId.AskGeneralBuyBackWeaponToShop, MethodId.AskCloseNpcShop, // 环境 NPC 剧情 / 刺激事件收尾(2026-10-02 自 7k-v0.6 移植)。三条都是 Notify, // 只维护 WorldEntryState.StaticNpcPreparedPlotEvents 这条账本;trigger 成功后发 // SyncPlotCurrentEvent(EventId=0) 把反应关掉,否则 NPC 会卡在已演完的那条反应上。 // 详见 GameRouter.Plot.cs。 MethodId.AskPreparePlotEvent, MethodId.AskTriggerPlotEvent, MethodId.AskEndPreparePlotEvent, // T2 单发杂项(2026-10-02 自 7k-v0.6 移植)。两条纯记账 Notify + 三条没有等价 // runtime 的 Invoke(明确 error=1,不假装成功)。详见 GameRouter.T2Misc.cs。 MethodId.AskHackerBetray, MethodId.SyncTrackGPS, MethodId.AskDestroyGangMember, MethodId.AskAgentChangeWeaponByTemplateId, MethodId.AskResetTruckOrderGoods, // 老版衣柜入口(无 source 参数),补齐 short source=8 后走同一套穿戴逻辑。 // 见 GameRouter.Fashion.cs。 MethodId.AskSetSpiritFashions, // 行人路网:客户端为它手下的单位问路(详见 GameRouter.PedPath.cs)。 // 不接这条 ⇒ 走 DefaultReturnCatalog 兜底 = 永远回空路径 ⇒ 那个单位站着不动。 MethodId.AskClientNpcFindPath, MethodId.AskAetherNpcUpdatePathForObstacle, // GM 控制台职业命令族(GmJob*,2026-09-24 自捐赠端 DremkaPS 移植)。 // 不接这六条 ⇒ 客户端 ALT+F1 里每一次职业操作都掉进 unknown-methods.log, // 且 Invoke 类会让控制台那次调用一直 await 到超时。详见 GameRouter.GmJobs.cs。 MethodId.GmJobTake, MethodId.GmJobStart, MethodId.GmJobQuit, MethodId.GmJobPromote, MethodId.GmSetJobLevel, MethodId.GmJobEnd, // GM 传送面板的原生副本切换(2026-09-24 自捐赠端 DremkaPS 移植,见 GameRouter.SceneSwitch.cs)。 MethodId.EnterRaid, // 移动命令的回执(2026-09-24 自捐赠端 DremkaPS 移植,见 GameRouter.AgentCommandAck.cs)。 // 服务端发的 SyncLogicAgentCommand 是 fire-and-forget,不接这几条就永远分不清 // 「客户端收了但没跑」和「客户端根本拒收」。 MethodId.AskReportLogicAgentCommandSuccess2, MethodId.AskReportLogicAgentCommandFail, MethodId.AskReportLogicAgentCommandData2, MethodId.AskAgentPoiActionDone, // 迷雾 / 探索 POI / Aether 氛围层(2026-09-24 自捐赠端 DremkaPS 移植,见 GameRouter.GmFog.cs)。 // 这四条是抓包筛出来的:66 个候选里只有 6 个真被客户端发过,这里取其中与迷雾/氛围有关的 // 四条。⛑ GmGetAmbianceEnabled 是 0 字节 INVOKE,不接会让控制台 await 到超时。 MethodId.SyncEnterFogMapPoiId, MethodId.GmUnlockAllFogMap, MethodId.GmSetAmbianceEnabled, MethodId.GmGetAmbianceEnabled, // 搜打撤 / ExtractionShooter(2026-09-24 自捐赠端 DremkaPS 移植,见 GameRouter.Extraction.cs)。 // ⛑ [Handler] 写了不等于注册:不在这张表里的 methodId 会被 RegisterSelected 直接 continue // ⇒ 请求不报错、只回 `typed-default void 0b`,且 unknown-methods.log 里反而没有。 MethodId.AskExtractionShooterSearchContainer, MethodId.AskExtractionShooterEndSearchContainer, MethodId.AskExtractionShooterShiftItem, MethodId.AskExtractionShooterRemoveItem, MethodId.AskExtractionShooterLockItem, MethodId.AskExtractionShooterSortBag, MethodId.AskExtractionShooterSortBags, MethodId.AskExtractionShooterSplitItem, MethodId.AskExtractionShooterBagExpansion, MethodId.AskExtractionShooterTransferAllToStash, MethodId.AskExtractionShooterRaidSplitItem, MethodId.AskExtractionShooterShiftContainerItem, MethodId.AskExtractionShooterRemoveContainerItem, MethodId.AskExtractionShooterShiftContainerItemToBag, MethodId.AskExtractionShooterShiftBagItemToContainer, MethodId.AskExtractionShooterShiftContainerItemToSlot, MethodId.AskExtractionShooterShiftSlotItemToContainer, MethodId.AskQuitExtractionShooter, // 伪造匹配(2026-09-24,见 GameRouter.ExtractionMatch.cs):客户端点「开始」的那四条通用 // 开局请求。⛑ 这四条不是搜打撤专用 —— 别的玩法开局也走,所以 handler 内部还有一道 // fakeMatch 开关,默认关;开着的日子里任何一次点开始都会被劫到 preferredRaid。 MethodId.AskStartSingleMatch, MethodId.StartBotMatch, MethodId.StartMatchInTeam, MethodId.StartGameInTeam, // 娃娃机 / 小游戏机(ClawMachineJiGuan_game.gcp,2026-09-24,见 GameRouter.ClawMachine.cs)。 // 四条 Claw/购票 + 两条 SpoonServer 动作在 dump.cs 里都是 void 返回的 INVOKE: // 不接 ⇒ 图里的 SpoonServerMultiOutput 节点一直等到 context dispose,按 F 无反应。 MethodId.AskClawBuyTicket, MethodId.AskBuyMiniGameTicket, MethodId.AskClawSettlement, MethodId.AskClawDateFail, MethodId.AskDoSpoonServerAction, MethodId.AskDoSpoonServerActionByType, // MiniGame_Bee(娃娃机)结算三件套(2026-09-24,见 GameRouter.ClawMachine.cs)。 // unknown-methods.log 铁证:09-22 15:04:10 客户端连发这三条且我方当时全没接 ⇒ 结算卡死。 MethodId.SetMiniGame_BeeScore, MethodId.AskReportArcadeGameResultBee, MethodId.AskMiniGame_BeeSettlement, // 保龄球 / GameGround 玩法区(2026-09-24 自 AnantaBowlingLoader_v1.0.0.0 包内 // 「3_保龄球_移植进DRMK-PS_v1.7_A方案」移植,见 GameRouter.GameGround.cs / // GameRouter.NpcInvite.cs)。★ 硬门是 SyncGameGroundZoneInfo(68004609):客户端只有收到 // BowlingZoneInfo 才会建局,而它只在 EnterBowlingZone(67832701) 被调用后才可能到达, // 所以 EnterBowlingZone / GmEnterBowlingZone 不进表 = 走到机器前按 F 完全没反应。 // ⛑ 规则 94:不在这张表里 = handler 编译通过但永不注册。 // 打气球(龙栖村摊位,2026-09-29 自 donor v2.1 移植,见 GameRouter.Balloon.cs)。 // ★ 与保龄球同一个 GameGround 家族,硬门 = SyncGameGroundZoneInfo(68004609), // 只是多态 tag 换成 3(Concrete[449][3] = Auto.Reader[680])。 // ⛑ 规则 94:不在这张表里 = handler 编译通过但永不注册 ⇒ 客户端拿到 51 字节全零壳 // (ZoneSessionId 为空)⇒ 会话建不起来、面板退不出来。 // ⛑ 注意 AskBreakDestructibleObject(67463592,单数) 与表里已有的 // AskBreakDestructibleObjects(67891964,复数) 是**两个不同的号**,别互相顶掉: // 单数是「打爆上报(计分闭环)」,气球靠它算分。 MethodId.EnterBalloonZone, MethodId.GmEnterBalloonZone, MethodId.LeaveBalloon, MethodId.AskBreakDestructibleObject, // ★★★ R90(2026-09-29 自 donor v2.1 移植)—— 龙栖村摊位小游戏「能玩」的入口 RPC 全集, // 见 GameRouter.MiniGameEntry.cs。全部 INVOKE 型,缺一条 = 客户端 await 不到回执 // ⇒ 模态界面永久等待 ⇒ 只能大退。 // 返回值:除**三处**需手拼载荷外都是 void —— // EnterRingTossZone(裸 u64 pid)/ AskFinishChestnutGame(裸 byte)/ AskFarmerFry(空字典 FF 01)。 // ① 套圈(纯本地计分,只需一个非零 pid) MethodId.EnterRingTossZone, MethodId.RecordRingTossResult, MethodId.LeaveRingToss, // ② 组队对局入口 —— 龙栖村所有摊位的**公共**入口,优先级最高 MethodId.AskStartPartyMiniGameMatch, MethodId.AskConfirmPartyMiniGameMatch, MethodId.AskCancelPartyMiniGameMatch, // ③ 刮刮乐(入口在 IL2CPP/Spoon,Lua 0 命中;形状取自 AutoGen) MethodId.AskStartScratchGame, MethodId.AskLoseScratchGame, MethodId.AskFinishScratchGame, // ④ 五子棋 MethodId.EnterGomokuZoneDoublePlayer, MethodId.PlaceGomokuPiece, MethodId.LeaveGomoku, // ④b 五子棋**单机**(人机 / 残局)—— ★ RET 是**裸 u64** = 对手实体 uid。 // 不注册时兜底回 0 ⇒ 客户端 TryGetLogicAgent(0) 失败 ⇒ 对局起不来(点了不落子)。 // 实现见 GameRouter.GomokuSolo.cs(复刻骰子 SpawnDiceOpponentAsync)。 MethodId.EnterGomokuZoneDoubleAI, MethodId.EnterGomokuZoneEndGame, // ⑤ 炒茶 / 炒栗 —— ★★ 唯一一组**不是 void** 的返回: // AskStartFryChestnutGame → void;AskFinishChestnutGame → 裸 byte; // AskFarmerFry → Dict7Bit(回空字典 FF 01),走 ClientToGameDelegate。 // ⛔ 后两条必须 ReturnRawAsync 手拼载荷,照抄 ReturnEmptyOkAsync 会走位。 MethodId.AskStartFryChestnutGame, MethodId.AskFinishChestnutGame, MethodId.AskFarmerFry, // ★ 象棋 / ChineseChess(2026-10-03 移植自捐赠分支 DRMK-PS_09_HyNB-龙栖村小游戏 R182/R183, // 实现见 GameRouter.ChineseChess.cs + ChineseChessBoard.cs)。 // 客户端「进得去棋盘、开局后黑屏 / 能选子走不动」的根因是**服务端零推送**: // · ChineseChessPlayer.lua:224 在「等对手单位」处 coroutine.yield(无超时 token), // 而 OpponentAgentInstanceId 的唯一来源是 SyncGameGroundZoneInfo.ParticipantInfos // (专用推送 SyncGameGroundZonePlayerInfo 被 `Pid == myPid` 过滤,对手永远进不去); // · 回合翻转的唯一来源是 SyncGameGroundZoneTurnChange,客户端本地 MoveChessTo 不翻回合; // · AI 不在客户端(ChineseChessSearchChart.lua 零调用点)⇒ 应手只能服务端算。 // 返回契约已核:5 个入口 RET 全 void(兜底表里没有它们的 case); // QueryChineseChessPlayerInfo 兜底 body 19B = complex(681: dict32+dict32+u32+u32), // 与 ChineseChessPlayerInfo 的字段布局逐段吻合(见 DefaultReturnCatalog4229938.cs:…67492072)。 // ⛑ 回滚:删掉下面 6 行即可(handler 不注册 = 走 typed-default 兜底),不用删 ChineseChess*.cs。 MethodId.AskChineseChessEnterZoneDoubleAI, // 67307903 标准人机对局 MethodId.AskChineseChessEnterZoneEndGame, // 67616104 残局挑战 MethodId.AskChineseChessLeaveZone, // 67667163 离开 MethodId.QueryChineseChessPlayerInfo, // 67492072 开始面板要的残局/难度进度 MethodId.AskChineseChessMovePiece, // 67463405 落子 → 服务端算 AI 应手 MethodId.AskChineseChessSurrender, // 67452562 认输 → 推 GameOver // ★ 麻将 / Majiang(2026-10-04 移植自捐赠分支 DRMK-PS_09_HyNB-v1.9-R64, // 实现见 GameRouter.MahjongSolo.cs + MahjongRules4229938.cs(规则引擎零改动直拷))。 // 10-03 曾判「重活、建议单独排期」,10-04 重估推翻:该分支带 41KB 实机 wire 样本 // (.runtime/r62-mahjong-wire-samples.json),而样本里三个 NPC 座位就是 // Player101/102/103 ⇒ 单机形态(1 真人 + 3 NPC)是官方跑过的形态。 // 主线 MethodIds.json 与该分支字节级相同 ⇒ SyncMj* 常量本来就全齐。 // ⛑ 回滚:删掉下面 12 行即可(handler 不注册 = 走 typed-default 兜底)。 MethodId.AskStartMahjongGame, // 63184671 开局入口(Npc / Pve 房型) MethodId.MahjongPlayerReady, MethodId.ArrangeMjHolds, MethodId.HuanPai, MethodId.DingQue, MethodId.ChuPai, MethodId.Peng, MethodId.Gang, MethodId.Hu, MethodId.Guo, MethodId.TuoGuan, MethodId.BackToMahjong, MethodId.EnterBowlingZone, MethodId.GmEnterBowlingZone, MethodId.LeaveBowling, MethodId.RecordBowlingScore, MethodId.RecordAgentBowlingScore, MethodId.RecordAllBowlingScoreAndDrop, MethodId.AskBowlingRelease, MethodId.AskAgentBowlingRelease, MethodId.AskBowlingBuyTicket, MethodId.BroadcastBowlingClientInfo, MethodId.SetGameGroundPlayerReady, MethodId.SetGameGroundPlayerPlayAgain, MethodId.SetGameGroundPlayerSurrender, MethodId.LeaveGameGroundZone, MethodId.AskSimulationInviteNpc, MethodId.AskGetNpcRandomWearFashions, // 餐饮店(呼噜怪兽/猫咖、梦花园/女仆咖啡…)的「邀请伙伴」——**另一条**链, // 走 ClientToGameSceneDelegate 发 AskRestaurantInviteNpc(RestaurantConfig.Id),且是 NOTIFY。 // 修之前服务端报 `unimplemented notify AskRestaurantInviteNpc -> accepted`(只受理、不推送) // ⇒ 客户端永远等不到 SyncNpcChatLeaveGameplay ⇒ 按钮按了没反应。详见 GameRouter.NpcInvite.cs。 // ⛑ 硬规则 94:不进这张表 = handler 编译通过但永不注册(NOTIFY 也一样,只是不会 await 卡死)。 // 本机铁证:logs\unknown-methods.log 2026-09-22T11:01:53 发过 1 条 id=67057178 hex=C2832F05 // (= 87000002 女仆屋),一直只受理没实现。回滚 = 删这一行。 MethodId.AskRestaurantInviteNpc, // 「主题派对」开局 / 群聊邀请 / 结束(2026-09-29 自 v2.1 捐赠端移植, // 见 GameRouter.NpcInvite.cs;硬规则 94:不进白名单 = 编译过但永不注册)。 MethodId.StartSingleParty, MethodId.InviteMultiNpcChat, MethodId.PartyOver, // 飞镖 / DartsGame(2026-09-24,见 GameRouter.Darts.cs)。21:48:49~21:51:20 真机一局打完 // 只发过这三条(规则 112:按日志数排序,出现过才接): // StartDarts 63777955 / TriggerDartTiming 67926798 / RecordDarts 63225711 // dump.cs 三条都是 void ⇒ 空包 + err=0 即 OK。⚠ 只治 await,治不了 T-pose(动画在客户端 // Gameplay_Dart timeline 里,服务端插不上手)。 MethodId.StartDarts, MethodId.TriggerDartTiming, MethodId.RecordDarts, // 眼界 (Tuite)(2026-09-24,见 GameRouter.Tuite.cs)。列表本身走登录帧的 // PlayerClientInfoMinor.PlayerTuiteInfo.TuiteList,这几条是「点开一条 + 点赞/收藏/关注/ // 评论」的交互半边。不进表 = handler 编译通过但永不注册,点进去就是空的详情页 // (六种列表函数全部只读 TuiteList,没有一条是拉列表的请求)。 MethodId.AskTuiteGetDetail, MethodId.AskTuiteGetDetailByConfigId, MethodId.AskInteractTuite, MethodId.AskTuiteFollowRole, MethodId.AskReportTuiteDetailOpened, MethodId.AskTuiteGetCommentList, MethodId.AskTuiteLikeComment, MethodId.AskTuiteGetTimelineData, MethodId.AskTuiteTakeReward, // BBChat(2026-09-24,见 GameRouter.NpcChat.cs)。注意这不是玩家 IM —— 这个 app 里全是 // NPC 会话(红点来自 gRedDotUtils.CheckNpcChatHasRedDot())。会话列表走登录帧的 // InfoNpcCultivation.NpcChats / NpcGroupChats,这几条是「点开一条之后推进对话」的交互半边。 // 全部请求体只有一个 u32,客户端也没有为它们声明任何回包 reader ⇒ 只能回空 OK。 MethodId.InteractNpcChat, MethodId.InteractNpcChatToEnd, MethodId.ChatToNpc, MethodId.InviteNpcChat, MethodId.AskMarkNpcChatRead, MethodId.RequestNpcChatList, MethodId.AskCloseNpcChatWnd, MethodId.AskClearSpiritGroupChat, MethodId.AskClearNpcUncompletedInviteChat, // 骰子 / DiceGame(2026-09-27 移植自 DRMK-PS_09_HyNB-v1.9,见 GameRouter.Dice.cs)。 // 酒吧「午夜回响」吧台玩法。★ 硬门是 AskEnterDiceZoneDoubleAI(67898934) 的返回值 —— 契约是 // UXRPCTask,必须回一个**世界里真实存在的实体 uid**(造 NPC),回 0 或编造的 uid // 客户端都卡在「AgentId为0 / 等待 Agent 实体同步」。另外三条是 void,空包 + err=0 即可。 // ⛑ 回滚:把这四行删掉即可(handler 不注册 = 走 typed-neutral 兜底),不用删 Dice.cs。 MethodId.AskEnterDiceZoneDoubleAI, MethodId.AskDicePlayerAction, MethodId.AskLeaveDiceZone, MethodId.AskReportDiceBehavior, // 钓鱼 / FishingGame(2026-09-27,见 GameRouter.Fishing.cs)。 // ★ 硬门是 AskFishingSpotFullSync(63275440) 的回包 —— 契约是 // UXRPCTask,且 **Fishes 必须非空**: // FishingGame.lua:56 在 poolCount == 0 时直接 CleanGame()(反编译写成 ~= 0,语义是 == 0), // 空池 = 刚开局就被关掉。AskFishingSuccess(63577896) 是 void,空包 + err=0 即可。 // ⛑ 回滚:把这两行删掉即可(handler 不注册 = 走 typed-neutral 兜底),不用删 Fishing.cs。 MethodId.AskFishingSpotFullSync, MethodId.AskFishingSuccess, // 房产 / House(2026-09-30,见 GameRouter.House.cs)。 // 只有一条需要注册:AskLoadHouseData(63109556) 的契约是 // UXRPCTask(复杂返回),兜底表那份 57B 里 OwnerPid 是 0, // 我们要换成自己的 pid。**购买本身不需要新方法** —— 客户端复用的是 // AskNpcShopCommodityInfo / AskBuyCommodity(见 GameRouter.VehicleTuning.cs)。 // ⛑ 回滚:删掉这一行即可(回到兜底表的 OwnerPid=0 版本)。 MethodId.AskLoadHouseData, // 车库 / 停车(2026-09-30,见 GameRouter.Garage.cs)。 // 这一族 9 条在兜底表里全是 shape="void"(空 body),之前一个 handler 都没注册 ⇒ // 客户端「调取载具 / 驶离车库」整条链路断掉。9 条全部注册: // AskHouseParking / AskHouseMoveParkingSpace —— 记账 + 回推 SyncHouseParking // AskCreateHouseParkingVehicles —— spawn 车 + 回推 SyncHouseParkingEntityBinding(命门) // AskDestroyHouseParkingVehicles / AskTeleportToHouseGarage / AskTeleportFromHouseGarage // AskHouseCancelParking / GmHouseParking / GmHouseCancelParking // ⛑ 回滚:删这 9 行即可(handler 不注册 = 回到空 body 兜底,与改动前逐字节一致)。 MethodId.AskHouseParking, MethodId.AskHouseMoveParkingSpace, MethodId.AskHouseCancelParking, MethodId.AskTeleportToHouseGarage, MethodId.AskTeleportFromHouseGarage, MethodId.AskCreateHouseParkingVehicles, MethodId.AskDestroyHouseParkingVehicles, MethodId.GmHouseParking, MethodId.GmHouseCancelParking, // 家具机关 / 可互动家具(2026-10-01,见 GameRouter.HouseFurnitureGadget.cs)。 // 家具**模型**早就有了(AskLoadHouseData 的 FloorBuildInfoDict),但客户端建完模型 // 还会再问一次服务端要「机关实例 id」,这两条在兜底表里是 // case 63581446u: body=[0xFF,0x01] shape="struct(631:dict7-empty)" ← 恒空映射 // case 63759452u: body=[] shape="void" // ⇒ 椅子坐不了 / 衣柜开不了 / 电视点不亮。两条都注册。 // ⛑ 回滚:删这两行即可(回到恒空映射兜底,与改动前逐字节一致); // 或保留注册但把 gameplay.houseFurniture.gadgets 设为 false(热重载)。 MethodId.AskCreateHouseFurnitureGadgets, MethodId.AskDestoryHouseFurnitureGadgets, // 2026-09-29 自捐赠端 v1.9 整体移植(GameRouter.Fishing.cs 现在有 12 个 handler): // 不进这张表 = 编译通过但 handler 永不注册,GM 命令点了没反应、钓点永远刷不出鱼池。 // 这 12 个全是 C2S INVOKE handler;IGameToClient_SyncFishing* 是 S2C 通知目标、不在此表。 MethodId.GmAddFishingGear, MethodId.GmRemoveFishingGear, MethodId.GmInitFishingSpot, MethodId.GmSpawnFishingSpotFish, MethodId.GmResetAllFishingSpots, MethodId.GmClearFishing, MethodId.GmAddAllFishes, MethodId.GmOnFishingSuccess, MethodId.GmViewFishingSpot, MethodId.GmViewFishingInfo, // 2026-09-30 厕所 QTE / 马桶鸵鸟骑乘(GameRouter.ToiletQte.cs)。 // ⛑ 规则 94:不进这张表 = 编译通过但 handler 永不注册。 // AskRideAgent / AskStopRideAgent 是 C2S INVOKE(回包必须 0 字节); // AskToiletOstrichFlee 是 C2S **Notify**(QTE 失败时客户端上报)—— // 它是 Notify 也一样要进这张表,否则只会落进 DefaultUnknownNotify,服务端永远收不到。 MethodId.AskRideAgent, MethodId.AskStopRideAgent, // ★ 赛车 / Racing(2026-10-03 移植自捐赠分支 DRMK-PS_09_HyNB-赛车)。 // 「王牌车神·城中之夜」(ChallengeConfig 1003 → RelatedTask 60000147, raid 23300888)。 // 客户端点「开始挑战」只在本地 SetCurrentTask,服务端必须 (a) 记住当前任务并 // (b) 由 ScheduleRacingAutoStart 点火 4 拍 zone 包 + 检查点门 + 结算。 MethodId.AskAcceptAndSetCurrentTask, // 63961538 接受并设当前任务 → 赛车自启点火 // 赛事车辆 / 检查点计数上报(GameRouter.RacingVehicle.cs): // 不注册时落 typed-default 回 err=0 假 OK,服务端对赛道/车/进度一无所知。 MethodId.AskSetRacingCompetitionVehicle, // 63764162 赛事车辆(越过职业门后才收到) MethodId.AskSetTaskCounterValue, // 63753212 检查点计数上报(= 完赛判据) // ⛑ 不注册 StartNewChallenge(63092905) / SetNewChallengeData(63990449) / // FinishNewChallenge(63261145):主线 GameRouter.ChallengeRecords.cs 已接后两条, // 第一条兜底是 void 空包与 ack 字节等价;而 Finish 的回包主线兜底 129 字节才对 // (捐赠端写成空类 ChallengeResult 只回 1 字节,是错的 —— 硬规则 209)。 // ⛑ 回滚:删掉上面 3 行即可(handler 不注册 = 回到中性兜底),Racing*.cs 保留无害。 // ★ 音乐 / 演出线三条玩法(2026-10-04,见 GameRouter.LiveHouse.cs)。 // 接之前这些 id 全部落在 DefaultReturnCatalog 的空包兜底 ⇒ 面板能开、点得动, // 但(a)曲目成绩重进清零(b)首次引导永远不播(c)舞会点了没反应(d)直播间全空。 // ⛑ 回滚:删掉下面这 11 行即可回到中性兜底,GameRouter.LiveHouse.cs 留着无害。 // // —— 1. Livehouse 音游 —— MethodId.AskQueryLiveHouseInfo, // 63848409 首次游玩标记(决定 intro timeline) MethodId.AskLiveHouseMusicList, // 63885626 曲目进度列表 MethodId.SetLiveHouseMusicResult, // 63723141 成绩落盘 MethodId.LiveHouseMusicStart, // 63293678 开局 notify MethodId.LiveHouseMusicInterrupt, // 63752208 中断 notify MethodId.AskLiveHouseUseItem, // 63665380 金手指道具(放行,不重复扣) // // —— 2. PartyDance 舞池(三连 song → mode → start + 收摊)—— MethodId.AskSelectPartyDanceSong, // 63363245 MethodId.AskSelectPartyDanceMode, // 63715994 MethodId.AskStartPartyDance, // 63292540 ★ 推 SyncPartyDanceStart 的唯一点 MethodId.AskLeavePartyDance, // 63516846 推 SyncPartyDanceEnd // // —— 3. PartyLive 派对直播 —— MethodId.PartyLiveSwitch, // 63948406 开/关直播 MethodId.SendPartyEvent, // 63765017 点赞/送礼事件 MethodId.GmPartyLiveAddEvent, // 65785804 GM 推一条弹幕 MethodId.AskToiletOstrichFlee, }; internal RpcRouter Build() { var router = new RpcRouter("game"); MethodId.RegisterKnownNames(router); _ = AttributedHandlerRegistry.RegisterSelected(router, this, Enabled4229938MethodIds); router.OnUnknownInvoke(DefaultUnknownInvoke4229938); router.OnUnknownNotify(DefaultUnknownNotify4229938); return router; } }