diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index 936001f..a30f753 100644 --- a/3DTank/docs/KtRebuild_Plan.md +++ b/3DTank/docs/KtRebuild_Plan.md @@ -519,6 +519,24 @@ - 下一步:用户拥有的物品列表来自 GarageCC 的引用字段(`itemsOnDepot` / `itemsOnMarket` / `mountedItems`)与模型命令流;等这部分抓包/命令解码补齐后,用目录把 id 解析成 `GarageItemObject` 再派发 `SetItemsOnDepot` / `SetItemsOnMarket` / `SetMountedItems`。 + - wire → 状态层模型适配(`App/Session/WireAdapters.kt`): + - 修正了一个会静默失败的问题:`DownlinkActions` 之前直接 `Value as UserPropertiesCC`, + 但 codec 解出的是 `Net.Generated.UserPropertiesCC`(字段 `Field1…`), + 动作要的是 `App.State.UserPropertiesCC`(语义字段),运行时必然 ClassCastException + (会话自检里的「失败 3」就是这类转换)。 + - 生成器 `gen_downlink_actions.js` 增加 `Adapters` 表:需要转换的模型改为调用 + `WireAdapters.ToXxx(Value as Net.Generated.Xxx)`;当前覆盖 3 个模型 + (UserProperties / ReferralEventUserModel / GaragePreview)。 + - `UserPropertiesCC` 的 wire 字段 ↔ 语义名取自 module_47243 的 `Gs.t7h`(写入顺序) + 与 `fi(lg).toString`(槽位 → 名称),逐位对应关系登记在适配器文件头; + `rankBounds` 的嵌套对象拆成状态层的 `rankBounds` / `topBound` 两个字符串。 + - 会话自检新增合成用例:构造一条 `UserPropertiesCC` Pt 记录,断言 + `Uid / Id / Crystals / Rank / Score / HasSpectatorPermissions` 写进 `RootState.User`。 + - 同名类型冲突:`ProtocolMetadata.Downlink` 里部分方法的实际 Kotlin 类型带后缀 + (`GaragePreviewModelCC0004`、`UserPropertiesCC5452` 等,跨切片同名类改名)。 + 生成器现在读取该表,跳过「实际类型 ≠ 语义模型名」的方法(4 条), + 不再生成必然 ClassCastException 的映射;清单记在 `downlink_actions.json` 的 + `SkippedTypeConflicts`。 - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, 已按字段具名为 `(inline@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); `launchParams` 是纯透传(`(pass-through)`,无 reducer),已把字段收口为 `Map`。 diff --git a/3DTank/names/downlink_actions.json b/3DTank/names/downlink_actions.json index 08b0587..906f721 100644 --- a/3DTank/names/downlink_actions.json +++ b/3DTank/names/downlink_actions.json @@ -8,7 +8,7 @@ ], "Methods": 681, "Resolved": 477, - "Matched": 13, + "Matched": 9, "MatchedModels": [ { "Model": "GaragePreviewModelCC", @@ -26,6 +26,28 @@ "Field": "user" } ], + "SkippedTypeConflicts": [ + { + "MethodId": "8836555743203700004", + "Model": "GaragePreviewModelCC", + "Type": "GaragePreviewModelCC0004" + }, + { + "MethodId": "2940608322101861340", + "Model": "ReferralEventUserModelCC", + "Type": "ReferralEventUserModelCC1340" + }, + { + "MethodId": "5628691596950015452", + "Model": "UserPropertiesCC", + "Type": "UserPropertiesCC5452" + }, + { + "MethodId": "7864034006236084309", + "Model": "UserPropertiesCC", + "Type": "UserPropertiesCC4309" + } + ], "Models": [ { "Model": "AndroidPayCC", @@ -830,24 +852,12 @@ } ], "Actions": [ - { - "MethodId": "8836555743203700004", - "Model": "GaragePreviewModelCC", - "Action": "InitGarageResources", - "Field": "params" - }, { "MethodId": "2940606982196178690", "Model": "ReferralEventUserModelCC", "Action": "SetReferralEventData", "Field": "eventData" }, - { - "MethodId": "2940608322101861340", - "Model": "ReferralEventUserModelCC", - "Action": "SetReferralEventData", - "Field": "eventData" - }, { "MethodId": "1880027114297758713", "Model": "ReferralEventUserModelCC", @@ -878,18 +888,6 @@ "Action": "SetUserPropertiesAction", "Field": "user" }, - { - "MethodId": "5628691596950015452", - "Model": "UserPropertiesCC", - "Action": "SetUserPropertiesAction", - "Field": "user" - }, - { - "MethodId": "7864034006236084309", - "Model": "UserPropertiesCC", - "Action": "SetUserPropertiesAction", - "Field": "user" - }, { "MethodId": "3977381235093458189", "Model": "UserPropertiesCC", diff --git a/3DTank/tools/gen_downlink_actions.js b/3DTank/tools/gen_downlink_actions.js index 262d281..6f485b8 100644 --- a/3DTank/tools/gen_downlink_actions.js +++ b/3DTank/tools/gen_downlink_actions.js @@ -25,6 +25,22 @@ const MethodTable = JSON.parse(fs.readFileSync(path.join(Names, 'method_table.js const CodecFields = JSON.parse(fs.readFileSync(path.join(Names, 'codec_fields.json'), 'utf8')).Codecs; const ModelBinding = JSON.parse(fs.readFileSync(path.join(Names, 'model_binding.json'), 'utf8')).Entries; const Inventory = fs.readFileSync(path.join(KotlinRoot, 'Inventory.kt'), 'utf8'); +const MetadataPath = path.resolve( + P, '..', 'TankiOffline', 'net', 'src', 'jsMain', 'kotlin', 'Net', 'Generated', 'Metadata.kt', +); + +// 方法 → 生成代码里实际使用的 Kotlin 类型名。 +// 同名协议模型跨切片冲突时,生成器会加后缀(如 GaragePreviewModelCC0004); +// 这类方法不能按语义名去匹配动作,否则运行时 ClassCastException。 +const MethodType = new Map(); +try { + const Meta = fs.readFileSync(MetadataPath, 'utf8'); + const Re = /(\d+)L to ProtocolCommandMeta\(\d+L, "([^"]+)"/g; + let M; + while ((M = Re.exec(Meta))) MethodType.set(M[1], M[2]); +} catch (E) { + // 没有生成产物时退化为「不检查类型冲突」,保持旧行为。 +} // ---------------------------------------------------------------- 模型名解析 const ByModuleCodec = new Map(); @@ -62,6 +78,21 @@ function ParseActions(Text) { const Actions = ParseActions(Inventory); const ModelNames = new Set(Rows.map((R) => R.Model).filter(Boolean)); +// 模型名 → 候选动作(字段类型恰为该模型,且动作只有这一个字段) +// 注意:wire 模型(Net.Generated)与状态层模型(App.State)同名但类型不同, +// 这些模型必须经 App/Session/WireAdapters.kt 显式转换,不能直接 `as`。 +const Adapters = { + UserPropertiesCC: 'App.Session.WireAdapters.ToUserProperties', + ReferralEventUserModelCC: 'App.Session.WireAdapters.ToReferralEvent', + GaragePreviewModelCC: 'App.Session.WireAdapters.ToGaragePreview', +}; + +function ValueExpr(Model, TypeName) { + return Adapters[Model] + ? `${Adapters[Model]}(Value as Net.Generated.${TypeName})` + : `Value as ${TypeName}`; +} + // 模型名 → 候选动作(字段类型恰为该模型,且动作只有这一个字段) const ActionByModel = new Map(); for (const [Name, Fields] of Actions) { @@ -75,6 +106,7 @@ for (const [Name, Fields] of Actions) { // ---------------------------------------------------------------- 汇总 const Matched = []; const MatchedModels = []; +const SkippedTypeConflicts = []; const Models = []; for (const Name of [...ModelNames].sort()) { const Cand = ActionByModel.get(Name) || []; @@ -82,6 +114,11 @@ for (const Name of [...ModelNames].sort()) { Models.push({ Model: Name, Methods: Methods.length, Actions: Cand.map((C) => C.Action) }); if (Cand.length === 1) { for (const Id of Methods) { + const ActualType = MethodType.get(String(Id)); + if (ActualType && ActualType !== Name) { + SkippedTypeConflicts.push({ MethodId: Id, Model: Name, Type: ActualType }); + continue; + } Matched.push({ MethodId: Id, Model: Name, Action: Cand[0].Action, Field: Cand[0].Field }); } MatchedModels.push({ Model: Name, Action: Cand[0].Action, Field: Cand[0].Field }); @@ -101,6 +138,7 @@ fs.writeFileSync( Resolved: Rows.filter((R) => R.Model).length, Matched: Matched.length, MatchedModels, + SkippedTypeConflicts, Models, Actions: Matched, }, @@ -121,13 +159,16 @@ Lines.push('// → Inventory.kt 里唯一一个「只带该模型字段」的 Lines.push('//'); Lines.push('// 覆盖范围:仅自动可判定的组合;其余协议模型需要 CC → 多字段的转换动作,'); Lines.push('// 见 names/downlink_actions.json 的 Models 清单(后续按协议逐个补转换器)。'); +Lines.push('//'); +Lines.push('// wire 模型(Net.Generated)与状态层模型(App.State)同名但类型不同;'); +Lines.push('// 需要转换的模型走 App/Session/WireAdapters.kt,生成器里登记在 Adapters 表。'); Lines.push('package App.State'); Lines.push(''); Lines.push('/** 每条记录解码后,用 (Any) -> Any? 构造对应动作;未覆盖的 methodId 不在表里。 */'); Lines.push('val DownlinkActions: Map Any?> = mapOf('); for (const M of Matched) { Lines.push( - ' ' + M.MethodId + 'L to { Value -> ' + M.Action + '(Value as ' + M.Model + ') },', + ' ' + M.MethodId + 'L to { Value -> ' + M.Action + '(' + ValueExpr(M.Model, M.Model) + ') },', ); } Lines.push(')'); @@ -140,7 +181,9 @@ for (const M of MatchedModels) { return ByModuleCodec.get(Key) === M.Model; }).map((E) => E.ModelId); for (const Id of Ids) { - Lines.push(' ' + Id + 'L to { Value -> ' + M.Action + '(Value as ' + M.Model + ') },'); + Lines.push( + ' ' + Id + 'L to { Value -> ' + M.Action + '(' + ValueExpr(M.Model, M.Model) + ') },', + ); } } Lines.push(')'); @@ -149,5 +192,6 @@ fs.writeFileSync(path.join(KotlinRoot, 'DownlinkActions.kt'), Lines.join('\n'), console.log( `methodId ${Rows.length} 个,解析出协议模型 ${Rows.filter((R) => R.Model).length} 个;` + - `自动匹配动作 ${Matched.length} 条,涉及模型 ${new Set(Matched.map((M) => M.Model)).size} 个。`, + `自动匹配动作 ${Matched.length} 条,涉及模型 ${new Set(Matched.map((M) => M.Model)).size} 个;` + + `跳过同名类型冲突 ${SkippedTypeConflicts.length} 条。`, ); diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionCheck.kt b/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionCheck.kt index fd9ddab..4b2be0c 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionCheck.kt @@ -115,6 +115,46 @@ object SessionCheck { Router.RouteAll(List) } + // 合成一条 UserPropertiesCC 容器记录:验证 wire 模型 → 动作 → reducer 的闭环。 + // 抓包里没有用户属性帧,所以这里只用固定的 wire 值做通路自检(不依赖抓包)。 + val Probe = SessionRouter() + val UserWire = Net.Generated.UserPropertiesCC( + Field1 = 11, + CanUseGroup = true, + Field3 = 500, + Field4 = 7, + Field5 = 22, + Field6 = 0L, + Field7 = 33, + Field8 = 9, + HasSpectatorPermissions = true, + Field10 = 44, + Field11 = 777001L, + PartnerUid = null, + Field13 = 5, + RankBounds = Net.Generated.RankBounds(4, 9), + Field15 = 1, + Field16 = 55, + Field17 = 66, + Field18 = 77, + UID = "uid-check", + UserProfileUrl = "https://example/", + Field21 = 88, + Field22 = 99, + ) + val UserPt = Net.Protocol.PtPayload( + listOf(Net.Protocol.PtObject(777001L, 0L)), + listOf(Net.Protocol.PtItem(1893408944113965505L, UserWire)), + ) + Probe.Route(Net.Protocol.DecodedRecord(777001L, 0L, UserPt, ByteArray(0), ByteArray(0))) + val U = Probe.Store.State.User + val UserOk = U.Uid == "uid-check" && U.Id == 777001L && U.Crystals == 500 && + U.Rank == 5 && U.Score == 66 && U.HasSpectatorPermissions && Probe.Applied == 1 + println( + "用户属性闭环:Uid=${U.Uid}|Id=${U.Id}|Crystals=${U.Crystals}|Rank=${U.Rank}" + + "|Score=${U.Score}|应用=${Probe.Applied}|${if (UserOk) "通过" else "失败"}" + ) + println("文件 $Frames|记录 $Records|回编码一致 $Ok|不一致 $Bad|记录头截断 $HeaderErrors|其它错误 $Errors|别名表=${Session.AliasCount}") println("Pt 容器 $PtRecords 条|项 $PtItems|单条最多 $PtMaxItems") for (S in PtSample) println(" pt 样本:$S") @@ -149,12 +189,25 @@ object SessionCheck { if (TopUnmapped.isNotEmpty()) { println("未覆盖 id:" + TopUnmapped.joinToString(",") { "${it.key}×${it.value}" }) } + if (Router.FailedIds.isNotEmpty()) { + val TopFailed = Router.FailedIds.entries.sortedByDescending { it.value }.take(6) + println( + "失败 id:" + TopFailed.joinToString(",") { + val Name = App.State.ModelNamesByModelId[it.key] + (if (Name != null) "$Name(${it.key})" else it.key.toString()) + "×" + it.value + } + ) + } + if (Router.FailedReasons.isNotEmpty()) { + val TopReason = Router.FailedReasons.entries.sortedByDescending { it.value }.take(2) + println("失败原因:" + TopReason.joinToString(";") { "${it.key}×${it.value}" }) + } val Top = Methods.entries.sortedByDescending { it.value }.take(5) if (Top.isNotEmpty()) { println("高频 methodId:" + Top.joinToString(",") { "${it.key}×${it.value}" }) } // 抓包目录里混有入口/握手帧,解不出记录是正常现象;只要所有解出的记录都能回编码即可。 - val Passed = Bad == 0 && Records > 0 && Router.Catalog.Items.isNotEmpty() + val Passed = Bad == 0 && Records > 0 && Router.Catalog.Items.isNotEmpty() && UserOk println(if (Passed) "结果:通过" else "结果:失败") ExitSession(if (Passed) 0 else 1) } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionRouter.kt b/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionRouter.kt index 2965402..49a08d9 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionRouter.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Session/SessionRouter.kt @@ -12,6 +12,7 @@ import App.State.RootState import Net.Protocol.DecodedRecord import Net.Protocol.PtPayload import State.ReduxStore +import kotlin.js.js class SessionRouter(InitialState: RootState = RootState()) { @@ -35,6 +36,15 @@ class SessionRouter(InitialState: RootState = RootState()) { /** 未覆盖的协议模型名计数(model_registry 能解析出名字的部分)。 */ val UnmappedModels: MutableMap = LinkedHashMap() + /** 转换/派发失败的 id 计数(methodId 或 Pt item modelId)。 */ + val FailedIds: MutableMap = LinkedHashMap() + + /** 失败原因计数(异常信息),便于定位是类型不匹配还是字段缺失。 */ + val FailedReasons: MutableMap = LinkedHashMap() + + private fun RuntimeName(V: Any?): String = + js("(function (V) { return V && V.constructor ? V.constructor.name : '?' })(V)") as String + /** 已应用动作的类名(调试 / 自检用)。 */ val AppliedNames: MutableList = ArrayList() @@ -69,6 +79,9 @@ class SessionRouter(InitialState: RootState = RootState()) { } } catch (E: Throwable) { Failed++ + FailedIds[Item.Id] = (FailedIds[Item.Id] ?: 0) + 1 + val Why = (E.message ?: E.toString()) + " @ " + RuntimeName(Item.Value) + FailedReasons[Why] = (FailedReasons[Why] ?: 0) + 1 } } return Last @@ -89,6 +102,9 @@ class SessionRouter(InitialState: RootState = RootState()) { Action } catch (E: Throwable) { Failed++ + FailedIds[Record.MethodId] = (FailedIds[Record.MethodId] ?: 0) + 1 + val Why = E.message ?: E.toString() + FailedReasons[Why] = (FailedReasons[Why] ?: 0) + 1 null } } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Session/WireAdapters.kt b/TankiOffline/app/src/jsMain/kotlin/App/Session/WireAdapters.kt new file mode 100644 index 0000000..6673f04 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/Session/WireAdapters.kt @@ -0,0 +1,79 @@ +// WireAdapters.kt —— 线上协议模型(Net.Generated)→ 状态层动作载荷(App.State) +// +// 为什么需要这一层: +// codec 解出来的是 `Net.Generated.*`(按字段表生成的 wire 模型,匿名字段叫 Field1…), +// 而动作 / reducer 期望的是 `App.State.*`(按原包 toString / 调用点还原的语义模型)。 +// 两边同名但类型不同,直接 `as` 会在运行时 ClassCastException,所以必须显式转换。 +// +// 字段对应证据: +// · UserPropertiesCC:module_47243 的 codec `Gs.t7h` 按 wire 字段顺序写入模型槽位, +// 同模块 `fi(lg).toString` 给出槽位 → 语义名。逐位对应: +// Field1 bazookaGearScore, Field2 canUseGroup, Field3 crystals, Field4 crystalsRating, +// Field5 droneGearScore, Field6 eventPassEndTime, Field7 gearScore, Field8 goldsTakenRating, +// Field9 hasSpectatorPermissions, Field10 hullGearScore, Field11 id, Field12 partnerUid, +// Field13 rank, Field14 rankBounds, Field15 registrationTimestamp, Field16 resistanceGearScore, +// Field17 score, Field18 scoreRating, Field19 uid, Field20 userProfileUrl, +// Field21 userRating, Field22 weaponGearScore. +// 原包 `toString` 里 rankBounds 是嵌套对象,`he4().toString()`;状态层拆成 rankBounds / topBound 两个字符串。 +// · ReferralEventUserModelCC:wire 命名字段与 App.State 顺序一致(Field6/7/8 对应 +// referralCategory / referralQuestsSize / referrerCategory)。 +// · GaragePreviewModelCC:Field1..4 是相机参数(float),状态层用 Int/Int/Double/Int 收口, +// GarageBox / SkyboxFrontSide 是资源 id(long)。 +package App.Session + +import App.State.GaragePreviewModelCC as StateGaragePreview +import App.State.ReferralEventUserModelCC as StateReferralEvent +import App.State.UserPropertiesCC as StateUserProperties +import Net.Generated.GaragePreviewModelCC as WireGaragePreview +import Net.Generated.ReferralEventUserModelCC as WireReferralEvent +import Net.Generated.UserPropertiesCC as WireUserProperties + +object WireAdapters { + + fun ToUserProperties(V: WireUserProperties): StateUserProperties = StateUserProperties( + bazookaGearScore = V.Field1, + canUseGroup = V.CanUseGroup, + crystals = V.Field3, + crystalsRating = V.Field4, + droneGearScore = V.Field5, + eventPassEndTime = V.Field6.toInt(), + gearScore = V.Field7, + goldsTakenRating = V.Field8, + hasSpectatorPermissions = V.HasSpectatorPermissions, + hullGearScore = V.Field10, + id = V.Field11, + partnerUid = V.PartnerUid, + rank = V.Field13, + rankBounds = V.RankBounds.Field1.toString(), + topBound = V.RankBounds.Field2.toString(), + registrationTimestamp = V.Field15, + resistanceGearScore = V.Field16, + score = V.Field17, + scoreRating = V.Field18, + uid = V.UID, + userProfileUrl = V.UserProfileUrl, + userRating = V.Field21, + weaponGearScore = V.Field22, + ) + + fun ToReferralEvent(V: WireReferralEvent): StateReferralEvent = StateReferralEvent( + hideReferralCategory = V.HideReferralCategory, + hideReferrerCategory = V.HideReferrerCategory, + hidedAnnounce = V.HidedAnnounce ?: 0L, + promoCodeDialogBackGround = V.PromoCodeDialogBackGround, + promoCodeDialogImage = V.PromoCodeDialogImage, + referralCategory = V.Field6, + referralQuestsSize = V.Field7, + referrerCategory = V.Field8, + referrerPanelTitle = V.ReferrerPanelTitle, + ) + + fun ToGaragePreview(V: WireGaragePreview): StateGaragePreview = StateGaragePreview( + cameraAltitude = V.Field1.toInt(), + cameraDistance = V.Field2.toInt(), + cameraFov = V.Field3.toDouble(), + cameraPitch = V.Field4.toInt(), + garageBox = V.GarageBox, + skyboxFrontSide = V.SkyboxFrontSide, + ) +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/DownlinkActions.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/DownlinkActions.kt index ab7e2c5..f72d0ef 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/DownlinkActions.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/DownlinkActions.kt @@ -7,28 +7,27 @@ // // 覆盖范围:仅自动可判定的组合;其余协议模型需要 CC → 多字段的转换动作, // 见 names/downlink_actions.json 的 Models 清单(后续按协议逐个补转换器)。 +// +// wire 模型(Net.Generated)与状态层模型(App.State)同名但类型不同; +// 需要转换的模型走 App/Session/WireAdapters.kt,生成器里登记在 Adapters 表。 package App.State /** 每条记录解码后,用 (Any) -> Any? 构造对应动作;未覆盖的 methodId 不在表里。 */ val DownlinkActions: Map Any?> = mapOf( - 8836555743203700004L to { Value -> InitGarageResources(Value as GaragePreviewModelCC) }, - 2940606982196178690L to { Value -> SetReferralEventData(Value as ReferralEventUserModelCC) }, - 2940608322101861340L to { Value -> SetReferralEventData(Value as ReferralEventUserModelCC) }, - 1880027114297758713L to { Value -> SetReferralEventData(Value as ReferralEventUserModelCC) }, - 276076212290602640L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 7864195052961084235L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 3982372259754314184L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 6492680006777979140L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 5628691596950015452L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 7864034006236084309L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 3977381235093458189L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 848734776772436751L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, - 8468360636859619961L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, + 2940606982196178690L to { Value -> SetReferralEventData(App.Session.WireAdapters.ToReferralEvent(Value as Net.Generated.ReferralEventUserModelCC)) }, + 1880027114297758713L to { Value -> SetReferralEventData(App.Session.WireAdapters.ToReferralEvent(Value as Net.Generated.ReferralEventUserModelCC)) }, + 276076212290602640L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, + 7864195052961084235L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, + 3982372259754314184L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, + 6492680006777979140L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, + 3977381235093458189L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, + 848734776772436751L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, + 8468360636859619961L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, ) /** Pt/Lt 容器里的 modelId → 动作(同一批协议模型的容器形态)。 */ val DownlinkModelActions: Map Any?> = mapOf( - 3730185794267514852L to { Value -> InitGarageResources(Value as GaragePreviewModelCC) }, - 5167916989664044333L to { Value -> SetReferralEventData(Value as ReferralEventUserModelCC) }, - 1893408944113965505L to { Value -> SetUserPropertiesAction(Value as UserPropertiesCC) }, + 3730185794267514852L to { Value -> InitGarageResources(App.Session.WireAdapters.ToGaragePreview(Value as Net.Generated.GaragePreviewModelCC)) }, + 5167916989664044333L to { Value -> SetReferralEventData(App.Session.WireAdapters.ToReferralEvent(Value as Net.Generated.ReferralEventUserModelCC)) }, + 1893408944113965505L to { Value -> SetUserPropertiesAction(App.Session.WireAdapters.ToUserProperties(Value as Net.Generated.UserPropertiesCC)) }, )