package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// ★★ GATE Lua 通道:在客户端进程里执行任意 Lua(2026-09-30 新增)。
// 原理(照 v0.5-2 捐赠端 GameRouter.LixiPolice.cs:378-382 的注释):
//
// 4229938 MasterToClientImpl.SyncDoToLuaMaster (0x1281DDE0) 调用内置的
// gCsToLuaHandler.GmRunLua —— 注册在 **Gate** 上 (0x14883B90)。
//
// ⇒ 客户端本来就自带「服务端叫它跑一段 Lua」的能力,只是必须发在
// GATE 那条连接上,发在 game 连接上客户端不认。
// 主线此前缺的不是链路,是调用:
// PrivateServerApplication.cs:21-23 已经建好 GateSessionHub 并注入两个 router,
// LoginGateRouter.cs:114 每次登录都 _gateSessions.Touch()
// (日志 [GATE] authenticated session registered),
// GameRouter.cs:14 也拿到了 _gateSessions —— 但全仓零使用。
// 这里补上「绑定 + 下发」两步。
// ⛑ 三个必须遵守的坑(都是捐赠端踩出来的):
//
// - 必须在 Gate 会话上发 SyncDoToLuaMaster(45437825);
// - 客户端 GmUtils.ParseUlongToLua(0x14677AC0) 用
// ulong.new(low32, high32) ⇒ uid 必须拆成两个 32 位传,
// 直接写十进制数字会被 float64 四舍五入(8300000000000000001 这种 id 必炸);
// - 脚本一律 pcall 包裹 + print_error,否则报错静默,
// 且客户端有 print_debug / print_error 可直接写进 Player.log 做回执。
//
// ⛑ 主线 TcpSession 没有 ClosedToken(捐赠端 13 处都在用),
// 这里统一用 Client.Connected + CancellationToken.None。
//
//
// 在 LoginGame 时把当前 GATE 会话绑到 game 会话上(照捐赠端
// Endpoints.cs:16-17)。⛑ 这是实例方法 —— 只有它能碰到 _gateSessions。
// 只绑一次:登录时的那个 gate,不是后来"全局当前"的某个 socket。
//
func GameRouter_GateLua_BindGateSession4229938(ctx context.Context, session *GameSession) any {
// Router implementation for GameRouter_GateLua_BindGateSession4229938
var zero any; return zero
}
// 取绑定好的 GATE 会话;没绑或已断都返回 false。
func GameRouter_GateLua_TryGetGateSession4229938(ctx context.Context, session *GameSession, gate any) bool {
// Router implementation for GameRouter_GateLua_TryGetGateSession4229938
return true
}
//
// ★ 在客户端进程里执行一段 Lua。wire = UxString(script)(客户端签名就是
// SyncDoToLuaMaster(string content),见 ux_game_all.dump.cs:15196)。
//
func GameRouter_GateLua_RunClientLuaAsync(ctx context.Context, session *GameSession, script string) any {
// Router implementation for GameRouter_GateLua_RunClientLuaAsync
var zero any; return zero
}
// ------------------------------------------------------------------ F 交互按钮
//
// 枚举服务端账本里的 NPC(三本:Ambient 行人 / 地盘敌人 / 功能 NPC),按离玩家由近到远排序。
// ⛑ 城里绝大多数路人是客户端本地实体,服务端没有它们的 id
// (GameRouter.PoliceArrest.cs:66-79 写死这个结论)⇒ 那些 NPC 挂不了 F,
// 只能对 GM 召唤 / 服务端 spawn 出来的目标操作。
//
func GameRouter_GateLua_EnumLedgerUnits4229938(ctx context.Context, session *GameSession) []any {
// Router implementation for GameRouter_GateLua_EnumLedgerUnits4229938
return nil
}
//
// 一条 story MessageCommand 的裸字节(供 Lua 用 writer:WriteByte 原样回吐):
// FF + Int7(1) + U8(4=MessageCommand) + U32(nid) + UxString(name) +
// U8(44=ULongPayload) + U64(value) + I64(0) + Bool(false)。
// ⛑⛑ payload mark 必须用 44(ULongPayload)。捐赠端用的是 42(StringPayload),
// 但主线 StoryVehicleCodec4229938.ParsePayload 只认 5/6/12/44/35/36/37,
// 遇到别的 mark 会 throw InvalidDataException ⇒ 整条命令在
// commands.Add 之前就被丢弃 ⇒ 服务端什么都看不到。
// 44 还能顺带把 uid 原样带回来,正好用。
//
func GameRouter_GateLua_StoryMessageBytes4229938(ctx context.Context, nid uint32, name string, value uint64) []byte {
// Router implementation for GameRouter_GateLua_StoryMessageBytes4229938
return nil
}
//
// ★ 回执探针:脚本一旦被客户端执行,就经
// gClientToGameSceneDelegate:Notify(67130745, …) 把一个 story command 发回服务端,
// 名字固定为 DrmkLuaRx。主线 GameRouter.VehicleStory.cs:45 的 handler 会把
// command 名打进日志([VEHICLE-STORY] cmd … name=DrmkLuaRx …)
// ⇒ 服务端日志一出现 DrmkLuaRx,就铁证客户端真的执行了 Lua。
// 这是 GATE Lua 通不通的唯一硬判据:不依赖 print_debug
// 到底往不往 Player.log 写,也不依赖按钮有没有显示出来。
//
func GameRouter_GateLua_BuildReceiptScript4229938(ctx context.Context, tag string) string {
// Router implementation for GameRouter_GateLua_BuildReceiptScript4229938
return ""
}
//
// ★ 生成一个「点了就开里希盘查」的 F 按钮(按钮文字默认「盘查」)。
// click 回调把 uid 经 67130745 的 DrmkExamRequest 回给服务端,
// 由 GameRouter.VehicleStory.cs 的 handler 接住再调 OpenExamAsync。
//
func GameRouter_GateLua_BuildExamButtonScript4229938(ctx context.Context, uid uint64, text string) string {
// Router implementation for GameRouter_GateLua_BuildExamButtonScript4229938
return ""
}
//
// optionId → reactionId。照捐赠端 PoliceReaction4229938
// (LixiPolice.cs:432-440)。
// ⛑ 默认 15002 是 **Detach(下车)**,**不是**上车;20 才是押送上车(20001)。
//
func GameRouter_GateLua_PoliceReactionForOption(ctx context.Context, optionId uint32) uint32 {
// Router implementation for GameRouter_GateLua_PoliceReactionForOption
return 0
}
//
// ★★ 修「盘查面板里点任何选项都没反应」。
// 根因(2026-10-01 实机日志取证)——客户端原生
// LX6/Manager/GamePlay/PoliceJob/PoliceExamineManager.lua 的两个入口都有反编译翻错:
//
// :190 ClickOption = function(self, optionId)
// if optionId ~= PoliceCfg.Ask then ← 翻错的比较
// if self.attachData.askDialogId and self.attachData.askDialogId <= 0 then
// ... ShowGeneralDialog ...
// end ← 没有 else ⇒ 静默
// else
// self.ClickOption_Story(self, optionId) ← 只有 Ask 才走得到
// end
// end
// :1040 ClickOption_Story = function(self, optionId)
// if self.BlockInteract or self.unit ~= nil then return end ← 翻错的比较
// ... gPoliceJobManager.cs:ClickOption_Story(self.unit.Pid, optionId)
//
// ⛑⛑ 这个文件是 luajit 反编译产物,**比较符整体不可信**(~=/==、
// <=/>= 都翻过)。别照抄它的判断,直接换掉函数本体。
// 日志佐证:[DRMK-EXAM] ClickOption_Story optionId=8 unit=true … block=nil,
// unit=true 正好命中第二处早退条件。
// ⛑ 面板的唯一点击入口是 PoliceInquiryPanelStore:OnListBtnClick
// (self.mgr:ClickOption(itemData.optionCfgId),self.mgr = gPoliceJobManager.examineMgr)
// ⇒ **必须换 ClickOption 本身**,只换 ClickOption_Story 有可能整条链都走不到。
// 两个都换,双保险。
// 修法:真的调 cs:ClickOption_Story,同时把 optionId 回执给服务端;
// 服务端再推 SyncPoliceExamReaction(64480222)(主线注释里"唯一能让面板有反应的帧")。
// ⛑ 必须先写 self.PreOptionId —— 客户端 OnReceiveReaction 靠它取 option 配置。
//
//
// ★★ 直接把「对方反应」喂给客户端 —— 绕开 SyncPoliceExamReaction(64480222)。
// ⛑⛑ 为什么不能用那条 RPC(2026-10-01 15:0x 实机取证):
//
// - 客户端 RPCDeserializeAuto.lua:350 把它标成 LuaOnly,也有 reader(:3281),
// 但**全仓没有任何 Lua 分发器**会把它交给 PoliceJobManager.OnReceiveReaction_Story;
// - 官方链路里 OnReceiveReaction_Story 是**被 L50 story(S002) 调**的
// (捐赠端 StoryS002Codec4229938.Reaction(child, section, reaction)),
// 主线没有 S002 codec ⇒ 这条 RPC 发了等于石沉大海。
// - 实机证据:推 reaction 后 Player.log 里 [DRMK] OnReceiveReaction **一行都没有**。
//
// ⇒ 反正 GATE Lua 能在客户端进程里跑任意代码,**直接调那个函数**即可,不必移植 S002。
//
func GameRouter_GateLua_BuildReactionScript4229938(ctx context.Context, reaction uint32, optionId uint32) string {
// Router implementation for GameRouter_GateLua_BuildReactionScript4229938
return ""
}
func GameRouter_GateLua_BuildPatchClickOptionScript4229938(ctx context.Context, ) string {
// Router implementation for GameRouter_GateLua_BuildPatchClickOptionScript4229938
return ""
}
//
// ★★ 初始化原生盘查数据(照捐赠端 PolicePrepareScript4229938,
// LixiPolice.cs:390-412)。
// 捐赠端注释的要点:调**原始** PrepareData_Story(就像
// S002PlayerNet.EnterExam 做的那样),不用自造追问都不拷南瓜 WM 的替身函数。
// 这是"盘查面板里所有选项点了没反应"的核心补救:
// 面板 UI 出得来,但 gPoliceJobManager.examineMgr.enterExamData 是空的,
// 所以每个选项都点不动。
// 回执 DrmkExamPrepared 的 payload:1=ok,0=failed。
//
func GameRouter_GateLua_BuildPrepareExamScript4229938(ctx context.Context, uid uint64) string {
// Router implementation for GameRouter_GateLua_BuildPrepareExamScript4229938
return ""
}
//
// 生成「给 uid 挂 / 摘 F 按钮」的 Lua。⛑ uid 必须拆成
// ulong.new(low32, high32)(捐赠端注释:十进制 Lua number 会被 float64 四舍五入)。
//
func GameRouter_GateLua_BuildUnitButtonScript4229938(ctx context.Context, uid uint64, text string, add bool) string {
// Router implementation for GameRouter_GateLua_BuildUnitButtonScript4229938
return ""
}
// 给面板用的账本条目。
type GateLuaUnit struct {
Uid uint64 `json:"uid"`
Ledger string `json:"ledger"`
Position Vec3 `json:"position"`
Distance float32 `json:"distance"`
}