This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

1126 lines
66 KiB
C#
Raw Permalink Normal View History

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;
}
}