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
Ananta-ps/internal/gameserver/routers/game_router_gate_lua.go
T

209 lines
10 KiB
Go
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.
package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// ★★ GATE Lua 通道:在客户端进程里执行任意 Lua(2026-09-30 新增)。
// <para>原理(照 v0.5-2 捐赠端 GameRouter.LixiPolice.cs:378-382 的注释):</para>
// <code>
// 4229938 MasterToClientImpl.SyncDoToLuaMaster (0x1281DDE0) 调用内置的
// gCsToLuaHandler.GmRunLua —— 注册在 **Gate** 上 (0x14883B90)。
// </code>
// ⇒ 客户端本来就自带「服务端叫它跑一段 Lua」的能力,只是必须发在
// GATE 那条连接上,发在 game 连接上客户端不认。
// <para>主线此前缺的不是链路,是调用:
// PrivateServerApplication.cs:21-23 已经建好 GateSessionHub 并注入两个 router,
// LoginGateRouter.cs:114 每次登录都 _gateSessions.Touch()
// (日志 [GATE] authenticated session registered),
// GameRouter.cs:14 也拿到了 _gateSessions —— 但全仓零使用。
// 这里补上「绑定 + 下发」两步。</para>
// <para>⛑ 三个必须遵守的坑(都是捐赠端踩出来的):</para>
// <list type="number">
// <item>必须在 Gate 会话上发 SyncDoToLuaMaster(45437825);</item>
// <item>客户端 GmUtils.ParseUlongToLua(0x14677AC0) 用
// ulong.new(low32, high32) ⇒ uid 必须拆成两个 32 位传,
// 直接写十进制数字会被 float64 四舍五入(8300000000000000001 这种 id 必炸);</item>
// <item>脚本一律 pcall 包裹 + print_error,否则报错静默,
// 且客户端有 print_debug / print_error 可直接写进 Player.log 做回执。</item>
// </list>
// <para>⛑ 主线 TcpSession 没有 ClosedToken(捐赠端 13 处都在用),
// 这里统一用 Client.Connected + CancellationToken.None。</para>
//
//
// 在 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)。
// <para>⛑⛑ payload mark 必须用 44(ULongPayload)。捐赠端用的是 42(StringPayload),
// 但主线 StoryVehicleCodec4229938.ParsePayload 只认 5/6/12/44/35/36/37,
// 遇到别的 mark 会 throw InvalidDataException ⇒ 整条命令在
// commands.Add 之前就被丢弃 ⇒ 服务端什么都看不到。
// 44 还能顺带把 uid 原样带回来,正好用。</para>
//
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。
// <para>这是 GATE Lua 通不通的唯一硬判据:不依赖 print_debug
// 到底往不往 Player.log 写,也不依赖按钮有没有显示出来。</para>
//
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
}
//
// ★★ 修「盘查面板里点任何选项都没反应」。
// <para>根因(2026-10-01 实机日志取证)——客户端原生
// LX6/Manager/GamePlay/PoliceJob/PoliceExamineManager.lua 的两个入口都有反编译翻错:</para>
// <code>
// :190 ClickOption = function(self, optionId)
// if optionId ~= PoliceCfg.Ask then ← 翻错的比较
// if self.attachData.askDialogId and self.attachData.askDialogId &lt;= 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)
// </code>
// <para>⛑⛑ 这个文件是 luajit 反编译产物,**比较符整体不可信**(~=/==、
// &lt;=/&gt;= 都翻过)。别照抄它的判断,直接换掉函数本体。</para>
// <para>日志佐证:[DRMK-EXAM] ClickOption_Story optionId=8 unit=true … block=nil,
// unit=true 正好命中第二处早退条件。</para>
// <para>⛑ 面板的唯一点击入口是 PoliceInquiryPanelStore:OnListBtnClick
// (self.mgr:ClickOption(itemData.optionCfgId),self.mgr = gPoliceJobManager.examineMgr)
// ⇒ **必须换 ClickOption 本身**,只换 ClickOption_Story 有可能整条链都走不到。
// 两个都换,双保险。</para>
// <para>修法:真的调 cs:ClickOption_Story,同时把 optionId 回执给服务端;
// 服务端再推 SyncPoliceExamReaction(64480222)(主线注释里"唯一能让面板有反应的帧")。
// ⛑ 必须先写 self.PreOptionId —— 客户端 OnReceiveReaction 靠它取 option 配置。</para>
//
//
// ★★ 直接把「对方反应」喂给客户端 —— 绕开 SyncPoliceExamReaction(64480222)。
// <para>⛑⛑ 为什么不能用那条 RPC(2026-10-01 15:0x 实机取证):</para>
// <list type="bullet">
// <item>客户端 RPCDeserializeAuto.lua:350 把它标成 LuaOnly,也有 reader(:3281),
// 但**全仓没有任何 Lua 分发器**会把它交给 PoliceJobManager.OnReceiveReaction_Story;</item>
// <item>官方链路里 OnReceiveReaction_Story 是**被 L50 story(S002) 调**的
// (捐赠端 StoryS002Codec4229938.Reaction(child, section, reaction)),
// 主线没有 S002 codec ⇒ 这条 RPC 发了等于石沉大海。</item>
// <item>实机证据:推 reaction 后 Player.log 里 [DRMK] OnReceiveReaction **一行都没有**。</item>
// </list>
// ⇒ 反正 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)。
// <para>捐赠端注释的要点:调**原始** PrepareData_Story(就像
// S002PlayerNet.EnterExam 做的那样),不用自造追问都不拷南瓜 WM 的替身函数。
// 这是"盘查面板里所有选项点了没反应"的核心补救:
// 面板 UI 出得来,但 gPoliceJobManager.examineMgr.enterExamData 是空的,
// 所以每个选项都点不动。</para>
// <para>回执 DrmkExamPrepared 的 payload:1=ok,0=failed。</para>
//
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"`
}