Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.cs
T

1127 lines
66 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Ananta.SDK.Rpc;
using Ananta.Server.Protocol.Client4229938;
using Ananta.Server.Handlers.LoginGate;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// Build 4229938 private-server surface: world entry, direct character switching,
/// combat, traversal/buffs and movement. Everything else falls back to typed-neutral RPC replies.
/// </summary>
internal sealed partial class GameRouter
{
internal const string WorldStateKey = "client4229938-world";
private readonly GateSessionHub? _gateSessions;
internal GameRouter(GateSessionHub? gateSessions = null) => _gateSessions = gateSessions;
/// <summary>
/// 客户端挂起(切后台 / 失焦,<c>RequestPlayerStartHangup</c>)期间必须停止一切后台推送。
///
/// 客户端在 Hangup 时不再消费网络消息;服务端若继续全速写,TCP 缓冲会被灌满,
/// 客户端回到前台时判定「与服务器的网络连接已断开」⇒ RPC session 归零
/// (Player.log: <c>session=0|pid=0|0->0</c> + <c>RPCNotify invalid socket</c>)⇒
/// 进 Reconnect Module ⇒ 12 秒重连超时。
/// 实测 2026-09-22 23:58:19 / 23:58:32 / 00:05:19 三次断线都是这个形态,
/// 且每次都紧跟在 <c>[Davinci] type=TimeScaleAnomaly</c> 之后 0.5 ms。
///
/// ⛑️ <c>WorldEntryState.IsHangup</c> 在加这道门之前全项目**只有写入、零处读取**
/// (挂起只是记了个日志就不管了),这是断线重连的直接原因。
/// 所有后台推送循环在 tick 开头调用它:挂起期间只睡不推,恢复后自动继续。
/// </summary>
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<uint> 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<uint>(非空 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<u32,u32>(回空字典 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<ulong>,必须回一个**世界里真实存在的实体 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<FishingSpotFullSyncInfo>,且 **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<HouseBuildingClientData>(复杂返回),兜底表那份 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;
}
}