179 lines
9.9 KiB
Go
179 lines
9.9 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// Scene devices — doors, vending machines, fridges, arcades, signs.
|
||
// Why "the fridge does nothing": an Ask* device INVOKE is only a REQUEST. The client's
|
||
// device-state manager waits for the server to echo the transition before it runs the
|
||
// presentation (door animation, fridge open, vending purchase). The donor handoff
|
||
// (HANDOFF-2026-09-18) walked exactly this path: round 13 added the echo, 13A removed it on a
|
||
// test that never happened, and 15D restored it from the one session whose log proved it worked
|
||
// (03:50 — the countdown door ran 9→6 cleanly with check=0 throughout; without the echo the same
|
||
// door re-fired 23× with the state DECREASING and the checkIndex escalating). Our logs show the
|
||
// very same signature today: AskSceneDeviceChangeState ×333 and AskSceneDeviceRecordValue ×364,
|
||
// all of them still on the generic neutral fallback.
|
||
// Wire (all CSharpOnly: raw fields, ZERO markers):
|
||
// <code>
|
||
// C2S AskSceneDeviceChangeState 67226162 (22b) = u8 type | u64 id | i32 stateType | i32 state
|
||
// | i32 checkIndex | u8 trailing
|
||
// C2S AskSceneDeviceRecordValue 67703474 (21b) = u8 type | u64 id | i32 valueName
|
||
// | string value (BIASED len + UTF-8)
|
||
// | i32 checkIndex | trailing
|
||
// reply (both) = SceneDeviceCheckIndexCallback { int CheckIndex; uint Error; } = 8 bytes
|
||
// S2C SyncSceneDeviceStateChange 68165561 (21b) = u8 type | u64 id | i32 stateType | i32 state
|
||
// | i32 checkIndex
|
||
// S2C SyncSceneDeviceValueChange 68332775 = u8 type | u64 id | i32 valueName
|
||
// | string value | i32 checkIndex
|
||
// </code>
|
||
// Two traps baked into the code below:
|
||
// 1. String bias is asymmetric. C2S device strings are BIASED (len+1 — a 9-char
|
||
// "StartMind" arrives as 0x0A); S2C strings are RAW (ReadStringOrNull @0x189AAB3D0).
|
||
// 2. Never push SyncSceneDeviceSignalSend(68477555). The client relays a server signal
|
||
// echo back as its own request — 13A measured 14×/s. That one echo is a real loop.
|
||
//
|
||
// AskSceneDeviceChangeState(67226162) / AskSceneDeviceRecordValue(67703474).
|
||
func GameRouter_SceneDevices_OnAskSceneDeviceChangeState(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_SceneDevices_OnAskSceneDeviceChangeState
|
||
var zero any; return zero
|
||
}
|
||
|
||
// AskSceneDeviceRecordValue(67703474) — value records (vending count, sign text).
|
||
func GameRouter_SceneDevices_OnAskSceneDeviceRecordValue(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_SceneDevices_OnAskSceneDeviceRecordValue
|
||
var zero any; return zero
|
||
}
|
||
|
||
// CheckCanOccupySceneDevice(67063877) / ReleaseOccupySceneDevice(67244136): void.
|
||
func GameRouter_SceneDevices_OnSceneDeviceOccupy(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_SceneDevices_OnSceneDeviceOccupy
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// AskCheckSpoonConditionByType(67709054) — "may I run this device script?".
|
||
// ⛔⛔ **2026-09-28 实机订正(用户验证)—— 下面这段旧说法是错的,已改写,别再改回去:**
|
||
// 旧文:"The generated neutral default is 0x00 = false, which is precisely what makes an
|
||
// interaction appear dead: the client asks, we say no, and nothing plays. Donor round 13
|
||
// answers 0x01." —— donor 那条从未实测,只是推断;对**衣柜这一族实测结果完全相反**。
|
||
// 实测(塔菲公寓衣柜 uid 14013633775178039662):
|
||
// · 答 0x01(true):F 提示一闪即没、换装打不开,`[SPOON] action` 只发 2 次就停
|
||
// (packets-20260928-204201.log 21:03:18.961 S2C hex=01)。
|
||
// · 答 0x00(false):F 常驻,22:46:44 `[SPOON] action` 触发,payload 与能换装的对照组
|
||
// v1.5 逐字节相同(console-20260928-224215.log)。
|
||
// 对照组 v1.5 没有本 handler,走 DefaultReturnCatalog4229938 兜底 0x00 ⇒ 衣柜一直可用。
|
||
// ⇒ **true 才是让交互挂死的那个值**:客户端拿到 true 就把交互交给服务端 spoon 脚本流程,
|
||
// 而 AskDoSpoonServerActionByType 我们只回空 OK、给不出任何脚本输出 ⇒ 卡死、提示被撤。
|
||
// 答 false 时客户端走本地表现,脚本照跑。
|
||
// 开关:Drmk.Server.Configuration.SceneDeviceSettings.SpoonConditionTrue
|
||
// (默认已改为 false;配置里 `world.content.sceneDevices.spoonConditionTrue` 显式 false)。
|
||
// 另见 `PrivateServerConfig.cs` 该字段的完整实测记录。
|
||
//
|
||
func GameRouter_SceneDevices_OnAskCheckSpoonConditionByType(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_SceneDevices_OnAskCheckSpoonConditionByType
|
||
var zero any; return zero
|
||
}
|
||
|
||
// AskGetSpoonServerOutputPortIndexByType(67346473): raw u32, always 0.
|
||
func GameRouter_SceneDevices_OnAskGetSpoonServerOutputPortIndexByType(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_SceneDevices_OnAskGetSpoonServerOutputPortIndexByType
|
||
var zero any; return zero
|
||
}
|
||
|
||
// AskDoSpoonServerActionByType(67863523): void. Logged — the payload carries the port.
|
||
func GameRouter_SceneDevices_OnAskDoSpoonServerActionByType(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_SceneDevices_OnAskDoSpoonServerActionByType
|
||
var zero any; return zero
|
||
}
|
||
|
||
// --- wire helpers ------------------------------------------------------------------------
|
||
// SceneDeviceCheckIndexCallback { int CheckIndex; uint Error; } — 8 bytes, Error = 0.
|
||
func GameRouter_SceneDevices_SceneDeviceOkReply(ctx context.Context, checkIndex int32) []byte {
|
||
// Router implementation for GameRouter_SceneDevices_SceneDeviceOkReply
|
||
return nil
|
||
}
|
||
|
||
func GameRouter_SceneDevices_EchoOff(ctx context.Context, cfg any, id uint64) bool {
|
||
// Router implementation for GameRouter_SceneDevices_EchoOff
|
||
return true
|
||
}
|
||
|
||
// SyncSceneDeviceStateChange(68165561) — 21 raw bytes, no markers.
|
||
func GameRouter_SceneDevices_PushDeviceStateAsync(ctx context.Context, conn any, typeVal byte, id uint64, stateType int32, state int32, checkIndex int32) any {
|
||
// Router implementation for GameRouter_SceneDevices_PushDeviceStateAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
// SyncSceneDeviceValueChange(68332775) — S2C strings are RAW-length, not biased.
|
||
func GameRouter_SceneDevices_PushDeviceValueAsync(ctx context.Context, conn any, typeVal byte, id uint64, valueName int32, value []byte, checkIndex int32) any {
|
||
// Router implementation for GameRouter_SceneDevices_PushDeviceValueAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
// Raw 7-bit varint (no +1 bias) — the S2C string length encoding.
|
||
func GameRouter_SceneDevices_WriteRaw7(ctx context.Context, sink []byte, value int32) any {
|
||
// Router implementation for GameRouter_SceneDevices_WriteRaw7
|
||
var zero any; return zero
|
||
}
|
||
|
||
// --------------------------------------------------------------------------------------
|
||
// AskSceneDeviceSendSignal (67144566) — 2026-09-27,桌面清单 T1-44 P0。
|
||
//
|
||
// 为什么排 P0:**它是 `unknown-methods.log` 里出现最多的 INVOKE**(全量 249 次,09-25 以后 74 次),
|
||
// 而且解出来的 `signalName` 不是设备状态,是**玩法/任务/特效信号** —— 服务端此前一条都收不到:
|
||
//
|
||
// | signalName | 次数 | 含义(按名字推断) |
|
||
// |---|---|---|
|
||
// | `FinishMind` | 238 | 心智/意志玩法结束(集中在两个固定 gadget uid) |
|
||
// | `StartMind` | 4 | 同上,开始 |
|
||
// | `BowlingInviteTimelinePlay` | 2 | 保龄球邀请时间轴播放 |
|
||
// | `TaskFinish60001155` | 2 | **任务 60001155 完成**(gadget 上触发) |
|
||
// | `SmokeFX Off` | 2 | 烟雾特效关闭 |
|
||
// | `carshop` | 1 | 车店 |
|
||
//
|
||
// ⛑ `TaskFinish60001155` 值得盯:服务端一直不知道任务什么时候完成(T2-9 任务面板那条线
|
||
// 缺的就是"真实达成"的事件钩子),这条是客户端唯一主动上报任务完成的通道之一。
|
||
//
|
||
// 方向:**INVOKE**(`ClientToGameSceneDelegate.lua:899` 是 `self.Invoke(...)`)。
|
||
// 返回:`UXRPCTask`(void,`RPCDeserializeAuto.lua:14968` 的 reader 函数体为空)⇒ **空 OK 即正解**。
|
||
//
|
||
// Wire(与 `AskSceneDeviceRecordValue` 同族,**raw,零 marker**):
|
||
// `u8 type | u64 id | string signalName`
|
||
// type = `SceneDeviceEntityType`(dump.cs:1749290 **只有两个值**:`Gadget = 0` / `Destructible = 1`;
|
||
// 观测到的 249 条全部是 **0 = Gadget**)
|
||
// signalName = **偏置** 7-bit varint 长度 + UTF-8(与本文件 RecordValue 的 C2S 字符串同规则,
|
||
// ⛑ 不是 S2C 的 raw —— 见本文件头"陷阱 1:字符串偏置是不对称的")
|
||
//
|
||
// ⛑⛑ **绝对不要在这个 handler 里推 `SyncSceneDeviceSignalSend`(68477555)**:本文件头已记录,
|
||
// 客户端会把服务端推的信号**当自己的请求再发回来**(donor 13A 实测 **14 次/秒**)⇒ 真回环。
|
||
// 这里只回空 OK + 打日志。将来真要驱动玩法,另开一条不会回环的路。
|
||
//
|
||
// ⛑ 硬规则 94:id 必须进 `GameRouter.cs` 的 `Enabled4229938MethodIds`,否则编译过但不注册。
|
||
// --------------------------------------------------------------------------------------
|
||
func GameRouter_SceneDevices_OnAskSceneDeviceSendSignal(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_SceneDevices_OnAskSceneDeviceSendSignal
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_SceneDevices_TypeName(ctx context.Context, typeVal byte) string {
|
||
// Router implementation for GameRouter_SceneDevices_TypeName
|
||
return ""
|
||
}
|
||
|
||
//
|
||
// C2S 设备字符串的**有偏** 7-bit varint 长度:线上是 `len + 1`,返回时已减回。
|
||
// 同时给出消耗的字节数(观测到的信号名都 < 127 字节,但保龄球那条 25 字符也走同一路径,
|
||
// 不做多字节解析将来迟早踩到)。
|
||
//
|
||
func GameRouter_SceneDevices_ReadBiasedLen7(ctx context.Context, span []byte) any {
|
||
// Router implementation for GameRouter_SceneDevices_ReadBiasedLen7
|
||
var zero any; return zero
|
||
}
|
||
|