package methods // // Vehicle summon/ownership wire contracts for client 4229938. Field order is the wire // order, verified 1:1 against lua/LuaGen/AutoGen/RPCSerializeAuto.lua // (WriteVehicleClientInfo, WriteLogicVehicleClientInfo, WriteSummonVehicleResult, // WritePlayerVehicleClientDetail, WriteRaidVehicleSeatInfo, WriteNewClientBoardingInfo, // WriteBoardingExtInfo, WritePlayerVehicleDriveStateInfo, WriteRaidVehicleSyncData, // WriteVehicleClientPart) and ClientToGameSceneDelegate.lua arg serializers. // New-vs-4091149 deltas already applied: LogicVehicleClientInfo.VehicleSpoonName (tail) // and PlayerVehicleClientDetail.IsPersistent (tail). // // // Stock vehicle HP notification; normal SyncUnitHp only handles BaseUnit. // Ported from donor_7k (T1-16 载具骇入): CanHack() requires CurrentHp > 0, and // SyncSpawnVehicle carries no HP fields, so this packet is mandatory for hackability. // // // Server notify body: SyncUnitBoardingInfo (68699595) — NPC 的座位挂载句柄 // (客户端 LogicAgentAttachVehicleHandle.Setup:原生座位偏移/旋转 + attach 句柄每帧跟随车辆)。 // ★ 字段顺序 = wire 顺序,2026-09-30 逐字段对照客户端 // ux_game_all.dump.cs:22762 ClientBoardingInfo 核过:
// ulong EntityId / ulong VehicleUId / byte SeatIndex / UXVector3 PositionOffset / // UXVector3 RotationOffset / bool CanBeEjected / bool UseSpecificAction / // uint ActionGroup / uint ActionId。
// 根签名是 SyncUnitBoardingInfo(ClientBoardingInfo info, bool force) // ⇒ 见 SyncUnitBoardingInfo4229938。
// ⛑ 客户端把这个 mid 标成 CSharpOnly(RPCDeserializeAuto.lua:1153), // Lua 侧没有明文 Reader ⇒ 只能按 dump.cs 字段布局核(规则 239 的客户端取证走 C# 侧)。 // 1(FF) + 8 + 8 + 1 + 12 + 12 + 1 + 1 + 4 + 4 = 52 B。 // // // Server notify: SyncUnitBoardingInfo (68699595) 全包 = info + force。 // 合计 53 B。⛑ 捐赠端 v0.5-2 PoliceVehicle.cs:82-91 是手写字节 // (U8(255) + U64 + U64 + U8 + F32×6 + Bool×2 + U32×2 + Bool(true)), // 与这里序列化出来的结果逐字节相同(已核)。 // ⛑ 解除挂载时 VehicleUId = 0 ⇒ 客户端同时清掉 attach 与原生座位注册。 // // Server notify: SyncPlayerMoveToDriveSeat (68778293). // Server notify: SyncPlayerExitVehicle (exit leg). // // 上下车驱动状态。C2S:AskPlayerStartEnterOrExitVehicle(67225310) / // AskPlayerFinishEnterOrExitVehicle(67211905);S2C: // SyncPlayerStartEnterOrExitVehicl / SyncPlayerVehicleStateChange / // SyncPlayerFinishEnterOrExitVehic(GameRouter.VehicleStory.cs 的强制上下车)。 // ⛑⛑ 2026-10-03:**不是 Inline**。客户端 // ClientToGameSceneDelegate.lua:1317 / :1352 用的是 // SerializeBase.WriteComplex(writer, syncdata, WritePlayerVehicleDriveStateInfo, "syncdata", false) // ⇒ 顶层 struct 前面有一个 presence 字节(0xFF)。 // 实机包体(14:46:43 上车,35B)逐字节对齐: // // FF presence(有值) // 64 00 00 00 00 00 00 00 Pid = 100 // 01 EnterOrLeave(上车=1 / 下车=0) // 01 00 34 F6 8D 40 CC 71 VehicleEntityId = 8200000000000000001 // 00 00 00 00 SeatIndex = 0 // 00 IfForce // 00 00 00 00 OpenDoorTypeId // 00 00 00 00 OpenDoorActionSpeed // 00 00 00 00 OpenDoorActionClipLength // // 以前标 Inline = true ⇒ 少读那 1 字节 ⇒ 尾部剩 1 字节 ⇒ TryGetArgs 失败, // 日志常年 AskPlayerStartEnterOrExitVehicle undecodable bytes=35,后果是 // **服务端根本没处理过上下车**(CurrentVehicleId / LastDrivenVehicleId / // 「车流车坐进去先摘出车道流」这些后续逻辑全没跑)。 // ⛑ 同步影响:S2C 那三包也用这个 DTO,改后它们同样带 presence 字节 —— 这是按 // 「C2S 与 S2C 由同一份契约生成」的对称性推断,需要实机确认强制上下车按钮仍正常。 // 回滚:还原 .bak-20261003-drivestate。 // // Client invoke arg: AskPublicSwitchToPublicScene (63491547). // Client invoke arg: AskEnterRaidByMapEntrance (63510026). // Aether vehicle init (SyncAetherAIInitDatas, 68704070). Lists stay empty. // Never serialized (all Aether init lists are empty); satisfies the serializer. // --- ClientTrafficIntersectionInitInfo (dump.cs:1743618) ------------------------------------ // // ★ 这是 AetherAIInitData.Intersections 的真实元素类型。我们此前一直用上面的空 stub, // 等于永远发「零个路口」—— 客户端的路口/信号灯系统从来没有拿到过初始化数据。 // // dump 内存布局:基类 TrafficIntersectionPeriodInfo 在 0x10/0x11/0x12,派生在 0x18/0x20/0x24 // (即内存里基类在前)。但历史笔记记的 wire 顺序是「派生先、基类后」。 // 两者可能不一致(UxSerializer 按声明顺序写,与内存布局无关),所以两个版本都留着, // 由配置 `initVariants` 选哪一个 —— 探测阶段两边都试,看客户端日志哪边有反应。 // // MassTrafficIntersectionState: Start=0 / Transition=1 / Finishing=2。 // 派生字段在前:InstanceId, ZoneIndex, CurrentState, 然后基类三个周期 byte。 // 基类字段在前(照 dump 内存偏移 0x10/0x11/0x12 排)。 // top-int7 framing + 真路口(派生先)。 // top-int7 framing + 真路口(基类先)。 // // Same four fields, but NOT Inline: UxSerializer emits the single-argument 0xFF object // marker first. The 4229938 reader consumes that marker before RaidId (see the note on // AetherAIInitData4229938), so this is the framing to use. // // // ★★ 唯一实测正确的 SyncAetherAIInitDatas(68704070) 线格式(2026-09-20 客户端验证)。 // 0xFF 对象标记 + u32 RaidId + bool HasZoneGraph + i32 ZoneStorageDataHandle // + 0xFF list presence + Int7(count+1)。空路口表 ⇒ 共 12 字节: // `FF 188B6301 01 611ABBC1 FF 01`(donor `--probe-aether` 输出一致)。 // 别换成 Int32 count 版(AetherAIInitDataTop4229938),客户端会 capacity 负数崩溃。 // // Bool-first field order, no object marker (diagnostic variant). // Client notify arg: AskInteractCmd (67390408). Logged + accepted. // Client notify: ReportDrivingVehicle (67528706). // Client notify: AskVehicleNitroValue (67533682). // Client invoke: AskVehicleNitro (67037029). // Client notify: AskModifyVehicleTopSpeed (67920147). // Client notify: AskVehicleHorn (67983272). // Client notify: AskVehicleContactDamage (67589692). // Full contact-damage struct (WriteVehicleContactDamageData order). // Client invoke: AskGetVehicleRadioContent (67725622). // // AskGetVehicleRadioContent(67725622) 的返回体 = UX.Game.RadioRandomContent // (TypeDefIndex 33514,字段顺序取自 il2cpp dump,与 DefaultReturnCatalog 的既有 15 字节 // 全零回包 FF 00 00 00 00 00 … 完全对齐 —— 1 字节 complex 标记 + 1+4+4+4+1)。 // 所以这里不能 Inline = true:inline 会省掉那个 0xFF,客户端 // Base.ReadComplex(reader, Auto.Reader[594]) 就会把 ContentType 当成标记字节读掉。 // 字段语义(RadioPlayerManager.GetVoicesByContent): // ContentType 1=Ad 2=News 3=Chat 4=Comment(3/4 都查 RadioContentDJs)5=SpecialNews // ContentId 主内容 id(按 ContentType 查对应表拿 SoundId) // StartContentId 开场口播 id(查 RadioContentDJs;广告/新闻为 0) // EndContentId 收尾口播 id(同上) // MarkType 1=Middle 2=End —— 决定串词插在歌的哪个 cue 上 // // // Client notify: AskSwitchVehicleRadio (67959772),参数一个 u32(电台序号)。 // 客户端 RadioPlayerManager.SwitchRadio / SwitchTargetRadio 本地已经切完了才发这帧, // 服务端只做记录(切台本身没有任何服务端权威状态)。 // // Client notify: SyncChangeSafeArea (63310118). // Client invoke: SyncChangeBuilding (63495099). // Client invoke: SyncChangeIndoor (63927817). // --- 路口相位更新 SyncAetherAIIntersectionUpdateDa (68819947) ------------------------------- // // ★★ 2026-10-02 从捐赠端 PrivateServerPor 移植(`VehicleAiTaskMethods.cs`)。这是「让红绿灯真的亮」 // 的那一半:init 只把路口表交给客户端,灯柱要按相位变色还得周期发这条包。 // // ⛑⛑ 契约来源(与 init 那对类型同源,互相印证): // dump.cs TypeDefIndex 32648/32650: // class TrafficIntersectionPeriodInfo { byte CurrentPeriodIndex; byte NextPeriodIndex; byte RailPeriodIndex; } // class ClientTrafficIntersectionPeriodUpdateInfo : TrafficIntersectionPeriodInfo // { ulong IntersectionIndex /*0x18*/; MassTrafficIntersectionState CurrentState /*0x20*/; } // 声明顺序 = **派生字段在前、基类三 byte 在后**,与主线 `TrafficIntersectionInitInfoDerived4229938` // 完全同构(后者是 InstanceId/ZoneIndex/CurrentState 在前 + 三个周期 byte)。 // // ⛑ 这条包在 `RPCDeserializeAuto.lua` 里是 `CSharpOnly`(第 1157 行 `midToExportOption[68819947]`) // ⇒ **lua 侧没有 Reader 可对照**(硬规则 209/239 在这里不适用),只能按 dump 定序。 // // 客户端消费方:`AetherCity.Common.AetherIntersectionManager.OnSyncIntersectionPeriodUpdateInfo`。 // MassTrafficIntersectionState: Start=0 / Transition=1 / Finishing=2。 type MassTrafficIntersectionState4229938 byte const ( MassTrafficIntersectionState4229938_Start MassTrafficIntersectionState4229938 = 0 MassTrafficIntersectionState4229938_Transition MassTrafficIntersectionState4229938 = 1 MassTrafficIntersectionState4229938_Finishing MassTrafficIntersectionState4229938 = 2 ) // // 68819947 的顶层:只有一个 data(与捐赠端逐字一致)。 // ⛑⛑ **必须 Inline**:捐赠端 `VehicleAiTaskMethods.cs:18` 就是 `[UxContract(Inline = true)]`, // 顶**不写**对象标记;`data` 字段自己是 `[UxContract]`(非 Inline)⇒ 由它写那一个 `FF`。 // 我第一版漏了 Inline ⇒ 变成 `FF FF …`,整包偏移 1 字节,客户端会解歪。 // 与 init 的 `AetherAIInitData`(非 Inline,根上写一个 FF)**总标记数都是 1**,互相印证。 // // // 68819947 的载荷。**字段顺序 = wire 顺序,别动。** // IntersectionIndex 取 CSV 第一列 zone_index(配置 intersectionIndexSource)。 //