feat(protocol): 离线回放支持多目录并补齐 pt 解码失败定位信息
This commit is contained in:
1 parent
353eb9b0a9
commit
ca42614056
4 files changed
+173
-34
No files matched your search
@@ -8,10 +8,13 @@
|
||||
// 环境变量 TANKIOFFLINE_PACKETS 可覆盖抓包目录(默认 ../3DTank/packets)。
|
||||
package App
|
||||
|
||||
import Core.ByteReader
|
||||
import Core.Frame
|
||||
import Core.Nullmap
|
||||
import Net.Generated.DownlinkRegistry
|
||||
import Net.Generated.ProtocolMetadata
|
||||
import Net.Generated.UplinkRegistry
|
||||
import Net.Protocol.S2cDecoder
|
||||
import Net.Protocol.SpaceFrame
|
||||
import Net.Wire.ErasedCodec
|
||||
|
||||
@@ -120,8 +123,114 @@ private fun RunOne(Path: String, Registry: Map<Long, ErasedCodec>, S: Stats) {
|
||||
}
|
||||
}
|
||||
|
||||
private val ExitProcessImpl: (Int) -> Unit = js("(function (C) { process.exit(C); })")
|
||||
|
||||
// 离线回放:把抓包目录里的 s2c_*.bin 按顺序喂给 S2cDecoder(自动明文/密文探测),
|
||||
// 解出每条记录并回编码比对。用于不依赖在线会话验证 pt/lt 容器链路。
|
||||
// TANKIOFFLINE_REPLAY 支持用 ';' 分隔多个目录(每个目录视为独立连接)。
|
||||
object Replay {
|
||||
|
||||
private class Totals {
|
||||
var Dirs = 0
|
||||
var Files = 0
|
||||
var Frames = 0
|
||||
var Pings = 0
|
||||
var Records = 0
|
||||
var Ok = 0
|
||||
var Bad = 0
|
||||
var Short = 0
|
||||
var Unknown = 0
|
||||
var HeaderErrors = 0
|
||||
var Errors = 0
|
||||
var Keys = ""
|
||||
val Containers = LinkedHashMap<String, Int>()
|
||||
}
|
||||
|
||||
fun Wanted(): Boolean = js("!!process.env.TANKIOFFLINE_REPLAY") as Boolean
|
||||
|
||||
fun Run() {
|
||||
val Raw = js("process.env.TANKIOFFLINE_REPLAY") as String
|
||||
val Dirs = Raw.split(';').filter { it.isNotBlank() }
|
||||
val T = Totals()
|
||||
for (Dir in Dirs) {
|
||||
RunDir(Dir, T)
|
||||
}
|
||||
println("合计:目录 ${T.Dirs}|文件 ${T.Files}|帧 ${T.Frames}|保活 ${T.Pings}|记录 ${T.Records}")
|
||||
println("回编码一致 ${T.Ok}|不一致 ${T.Bad}|短命令 ${T.Short}|未知 methodId ${T.Unknown}")
|
||||
println("记录头截断 ${T.HeaderErrors}|其它解码错误 ${T.Errors}|密钥=${T.Keys}")
|
||||
if (T.Containers.isNotEmpty()) {
|
||||
println("容器命中:" + T.Containers.entries.joinToString(",") { "${it.key}×${it.value}" })
|
||||
}
|
||||
val Passed = T.Bad == 0 && T.Errors == 0 && T.Records > 0
|
||||
println(if (Passed) "结果:通过" else "结果:失败")
|
||||
ExitProcessImpl(if (Passed) 0 else 1)
|
||||
}
|
||||
|
||||
private fun RunDir(Dir: String, T: Totals) {
|
||||
val Files = ListBinFiles(Dir, 10000)
|
||||
.filter { it.contains("s2c_") }
|
||||
.sorted()
|
||||
// 密钥与别名表按目录(= 一次独立连接)重置
|
||||
val Decoder = S2cDecoder()
|
||||
val Aliases = HashMap<Int, Pair<Long, Long>>()
|
||||
val BeforeBad = T.Bad
|
||||
val BeforeErrors = T.Errors
|
||||
val BeforeFrames = T.Frames
|
||||
val BeforeRecords = T.Records
|
||||
T.Dirs++
|
||||
|
||||
println("TankiOffline 离线回放:$Dir(${Files.size} 个 s2c 文件)")
|
||||
for (Path in Files) {
|
||||
T.Files++
|
||||
val Bytes = ReadFile(Path)
|
||||
val Bodies = Decoder.Feed(Bytes) { Line -> println(" $Line") }
|
||||
for (Body in Bodies) {
|
||||
T.Frames++
|
||||
val Nm = Nullmap.Size(Body, 0)
|
||||
if (Nm >= Body.size) {
|
||||
T.Pings++
|
||||
continue
|
||||
}
|
||||
val R = SpaceFrame.DecodeBody(Body, DownlinkRegistry.ByMethodId, Aliases)
|
||||
T.Records += R.Records.size
|
||||
T.Short += R.ShortCommandCount
|
||||
T.Unknown += R.UnknownMethodIds.size
|
||||
val Err = R.Error
|
||||
if (Err != null) {
|
||||
if (Err.startsWith("记录头截断")) T.HeaderErrors++ else T.Errors++
|
||||
println(" ⚠ $Path:$Err")
|
||||
}
|
||||
for (Rec in R.Records) {
|
||||
if (Rec.Payload.contentEquals(Rec.Reencoded)) T.Ok++ else {
|
||||
T.Bad++
|
||||
println(" ⚠ $Path methodId=${Rec.MethodId} 回编码不一致")
|
||||
}
|
||||
val Meta = ProtocolMetadata.Downlink[Rec.MethodId]
|
||||
val Label = Meta?.Name ?: Rec.MethodId.toString()
|
||||
if (Rec.MethodId == 3216143066888387731L || Rec.MethodId == 7640916300855664666L ||
|
||||
Rec.MethodId == 9038756763225879447L || Rec.MethodId == 8179162314198150518L
|
||||
) {
|
||||
T.Containers[Label] = (T.Containers[Label] ?: 0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val KeyText = Decoder.Key?.let { "$it" } ?: "null"
|
||||
T.Keys = if (T.Keys.isEmpty()) KeyText else "${T.Keys}/$KeyText"
|
||||
val Delta = (T.Bad - BeforeBad) + (T.Errors - BeforeErrors)
|
||||
println(
|
||||
" 小结:文件 ${Files.size}|帧 ${T.Frames - BeforeFrames}|记录 ${T.Records - BeforeRecords}" +
|
||||
"|密钥=$KeyText|错误 $Delta"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
if (!IsNode()) return
|
||||
if (Replay.Wanted()) {
|
||||
Replay.Run()
|
||||
return
|
||||
}
|
||||
if (AutoTest.Wanted()) {
|
||||
AutoTest.Run()
|
||||
return
|
||||
|
||||
@@ -1082,12 +1082,12 @@ object ModelRegistry {
|
||||
8618289906968528240L to RegistrationModelCCCodec,
|
||||
2474458842977623992L to RegistrationModelCCCodec,
|
||||
7300500077035374704L to EntranceResourcesCCCodec,
|
||||
6627517808336136295L to MaterialsCodec,
|
||||
6403067749608679876L to UserNotifierCCCodec,
|
||||
1850509145998626828L to UserActionsLoggerCCCodec,
|
||||
258682513334072065L to RankNotifierDataCodec,
|
||||
4255726087158005051L to UidNotifierDataCodec,
|
||||
3527205014716342329L to RankInfoCodec,
|
||||
3527205014716342329L to RankLoaderCCCodec,
|
||||
6627517808336136295L to UidCheckCCCodec,
|
||||
216902005816818019L to SpectatorsModelCCCodec,
|
||||
2085529474881905981L to StatisticsModelCCCodec,
|
||||
787936358514072261L to StatisticsDMCCCodec,
|
||||
@@ -1118,15 +1118,13 @@ object ModelRegistry {
|
||||
7588919819099098108L to BattleInviteCCCodec,
|
||||
2855626038259597130L to BattlePassPurchaseNotifierCCCodec,
|
||||
7805766859473669177L to ChallengeClientCCCodec,
|
||||
3349786357139026014L to TierCodec,
|
||||
8237913720896985151L to TierItemCodec,
|
||||
1987602940683756146L to StarsInfoCCCodec,
|
||||
8677996546985988365L to StarsInfoCCCodec,
|
||||
3349786357139026014L to ContractUserModelCCCodec,
|
||||
6514486185481319524L to CreatorTagCCCodec,
|
||||
965113061940997187L to CreatorTagsCCCodec,
|
||||
7341286352739641155L to DebtInfoCCCodec,
|
||||
3395764563215792356L to DonationAlertCCCodec,
|
||||
2108103923322474513L to DonationDataCodec,
|
||||
8464700182634408374L to UserDroppableGoldSettingsCCCodec,
|
||||
3503382027414679973L to TankHasGrenadeCCCodec,
|
||||
5801057999588514016L to ResistancesCCCodec,
|
||||
@@ -1156,13 +1154,11 @@ object ModelRegistry {
|
||||
1893408944113965505L to UserPropertiesCCCodec,
|
||||
9006361281849290638L to UserPushNotificationSettingsModelCCCodec,
|
||||
5895522515282663537L to UserAchievementCCCodec,
|
||||
7295738902766942560L to MainQuestBattleProgressCodec,
|
||||
701801920209410257L to MainQuestUserModelCCCodec,
|
||||
7105110962241677955L to MainQuestUserModelCCCodec,
|
||||
5167916989664044333L to ReferralEventUserModelCCCodec,
|
||||
4038465370530850950L to QuestCategoriesModelCCCodec,
|
||||
462434031489766566L to QuestCategoriesModelCCCodec,
|
||||
6680653332718076818L to QuestCategoryStatisticCodec,
|
||||
6966229709169193455L to RewardedVideosCCCodec,
|
||||
8659306545527654916L to RubyInfoCCCodec,
|
||||
2767408225195389970L to RubyInfoCCCodec,
|
||||
@@ -1206,17 +1202,17 @@ object ModelRegistry {
|
||||
5256681735304582291L to TwitchAccountModelCCCodec,
|
||||
2252704231082045810L to TwoStepVerificationModelCCCodec,
|
||||
2080556892932195426L to UserCountryCCCodec,
|
||||
1851605205665890036L to RewardInfoDataCodec,
|
||||
8517963146836182364L to RewardsInfoCCCodec,
|
||||
1851605205665890036L to RewardsInfoCCCodec,
|
||||
6071565290933648049L to ChatCCCodec,
|
||||
891137447706578619L to ClanChatCCCodec,
|
||||
868364325972433765L to NewsShowingCCCodec,
|
||||
7970528904804811572L to OiCodec,
|
||||
7267982595724632282L to MiM61860FrCodec,
|
||||
6359692933626160397L to SocialNetworkParametersCCCodec,
|
||||
7762582768388221832L to FlagBattleSFXCCCodec,
|
||||
7970528904804811572L to ArmsRaceCCCodec,
|
||||
7401419333842694749L to BattlefieldCCCodec,
|
||||
2101100305743734614L to BillboardCCCodec,
|
||||
7267982595724632282L to BillboardImageCCCodec,
|
||||
5958419804845264849L to BotNavigationCCCodec,
|
||||
2464705242400585109L to FacilitySphericalZoneCCCodec,
|
||||
5242494537362061802L to CommonFacilityCCCodec,
|
||||
@@ -1224,6 +1220,7 @@ object ModelRegistry {
|
||||
9172358609541951983L to MeteorStormCCCodec,
|
||||
2057320715890332864L to BattleMineCCCodec,
|
||||
8980131978880882207L to BattleStartDelayCCCodec,
|
||||
8237913720896985151L to DominationCCCodec,
|
||||
5385894712778438726L to BattleChatCCCodec,
|
||||
6445399806655453890L to InventoryCCCodec,
|
||||
3364392936313775902L to TargetHighlightingCCCodec,
|
||||
@@ -1233,6 +1230,7 @@ object ModelRegistry {
|
||||
2618111250571302204L to RugbyCCCodec,
|
||||
2269636304551395302L to BallExplosionCCCodec,
|
||||
6141842138930446750L to StatusEffectBattleCCCodec,
|
||||
7295738902766942560L to TeamJuggernautCCCodec,
|
||||
1475210252818583689L to BattleTutorialCCCodec,
|
||||
9114375113924783411L to BattleTutorialCCCodec,
|
||||
2145315487377727727L to BattlefieldBonusesCCCodec,
|
||||
@@ -1246,7 +1244,6 @@ object ModelRegistry {
|
||||
1661364280719338177L to DemomanSfxCCCodec,
|
||||
8316165996190533130L to DroneIndicatorCCCodec,
|
||||
4553925959338838726L to CrisisEffectCCCodec,
|
||||
5207857306014573244L to XCCodec,
|
||||
6424605984954645392L to TankEffectSFXCCCodec,
|
||||
8166810800706377715L to ActiveAfterDeathCCCodec,
|
||||
5779075235189237790L to EffectDescriptionCCCodec,
|
||||
@@ -1264,7 +1261,7 @@ object ModelRegistry {
|
||||
4693095212037357021L to BattleMapCCCodec,
|
||||
1688856467034284073L to MapBonusLightCCCodec,
|
||||
4885574251230758729L to ModerationToolInteropCCCodec,
|
||||
7894174029759745197L to ClassicParachuteCCCodec,
|
||||
7894174029759745197L to DroneParachuteCCCodec,
|
||||
4778752313022424015L to RadarCCCodec,
|
||||
3719295044981767592L to StatusEffectCCCodec,
|
||||
8889030940127130747L to StatusEffectCCCodec,
|
||||
@@ -1298,8 +1295,6 @@ object ModelRegistry {
|
||||
5194655544597440038L to LaserPointerCCCodec,
|
||||
7266201540452328992L to LaserPointerCCCodec,
|
||||
1663344340563853103L to RailgunCCCodec,
|
||||
1092307613749405757L to RicochetCCCodec,
|
||||
1130027771466355435L to ScorpioCCCodec,
|
||||
313670832122918648L to ScorpioSfxCCCodec,
|
||||
732152133982227177L to ShaftCCCodec,
|
||||
7700337090161867199L to ShotCCCodec,
|
||||
@@ -1311,6 +1306,8 @@ object ModelRegistry {
|
||||
6855550629220038001L to TerminatorCCCodec,
|
||||
6210980129845088505L to TerminatorSFXCCCodec,
|
||||
573691036321759779L to TeslaCCCodec,
|
||||
1092307613749405757L to ThunderCCCodec,
|
||||
1130027771466355435L to CassetteShootingCCCodec,
|
||||
8212033390179430143L to TsunamiCCCodec,
|
||||
5604117679922858821L to TsunamiShootSFXCCCodec,
|
||||
2803166101624878775L to RotatingTurretCCCodec,
|
||||
@@ -1329,8 +1326,8 @@ object ModelRegistry {
|
||||
5867914731324680416L to ShotgunShotCCCodec,
|
||||
2660691061343351669L to TeamLightCCCodec,
|
||||
171896995500198357L to TeamLightCCCodec,
|
||||
1500120709087433677L to TeamLightColorParamsCodec,
|
||||
6455142242556985012L to UltimateCCCodec,
|
||||
1500120709087433677L to AresUltimateCCCodec,
|
||||
7899544742191273818L to AresUltimateProjectileCCCodec,
|
||||
6210079079280471693L to CrusaderEffectCCCodec,
|
||||
3907559303616283277L to CrusaderUltimateCCCodec,
|
||||
@@ -1356,12 +1353,12 @@ object ModelRegistry {
|
||||
5543162332469864498L to TankInfoCCCodec,
|
||||
260111248825415414L to BattleBotControlCCCodec,
|
||||
2560105521800470759L to RemoteTankStateUpdatingCCCodec,
|
||||
4206032257972775630L to TankResistanceCodec,
|
||||
7127632667958387441L to TankResistancesCCCodec,
|
||||
2104999573658810427L to TankSpawnerCCCodec,
|
||||
7163171834328288344L to SpeedCharacteristicsCCCodec,
|
||||
2271787261806799336L to SuicideCCCodec,
|
||||
2150802556932617880L to TankCCCodec,
|
||||
4206032257972775630L to TurnOverCCCodec,
|
||||
6589716392600418185L to AvailableForAllCCCodec,
|
||||
9168562037264832365L to PassToShopCCCodec,
|
||||
3122964582367261689L to TemperatureCCCodec,
|
||||
@@ -1372,10 +1369,10 @@ object ModelRegistry {
|
||||
7391892068905080676L to DelayMountCategoryCCCodec,
|
||||
7109632754421804521L to DelayMountCategoryCCCodec,
|
||||
7640081207058999117L to DevicePropertiesCCCodec,
|
||||
6111098822553419499L to PaCodec,
|
||||
4692610703461978415L to ProAbonementRankDiscountCCCodec,
|
||||
6430862429652735997L to EmissionColorCCCodec,
|
||||
1534251830977989099L to EmissionColorCCCodec,
|
||||
6111098822553419499L to BazookaSupplyCCCodec,
|
||||
1865478986390468300L to GroupedCCCodec,
|
||||
658797348514407988L to ItemModelCCCodec,
|
||||
3438505192580052999L to ItemModelCCCodec,
|
||||
@@ -1397,15 +1394,18 @@ object ModelRegistry {
|
||||
6117306895090150422L to ItemViewCategoryCCCodec,
|
||||
4817031001612283794L to ProtectionSlotsCCCodec,
|
||||
2433120830275396692L to PresentsCCCodec,
|
||||
5988756521593027564L to DwCodec,
|
||||
7563487786199453443L to OverdriveDescriptionCCCodec,
|
||||
5988756521593027564L to RelatedResistanceModuleCCCodec,
|
||||
149643551432886459L to TurretMechanismCCCodec,
|
||||
5207857306014573244L to AvailableSkinsCCCodec,
|
||||
905970599512839002L to MountedSkinCCCodec,
|
||||
834829861153964627L to MountedSkinCCCodec,
|
||||
1164458258646872337L to AvailableShotSkinsCCCodec,
|
||||
7175193612424807833L to VuCodec,
|
||||
6680653332718076818L to SkinPreviewCCCodec,
|
||||
2108103923322474513L to CaptchaCCCodec,
|
||||
7175193612424807833L to ServerHaltCCCodec,
|
||||
4068618447512888231L to ColoringCCCodec,
|
||||
5255782026414758079L to BsCodec,
|
||||
5255782026414758079L to LobbyLayoutModelCCCodec,
|
||||
1259328549938381181L to VoCodec,
|
||||
7272122855372520854L to FriendsCCCodec,
|
||||
7286757952195114814L to UserContainerCCCodec,
|
||||
|
||||
@@ -51,19 +51,38 @@ object PtCodec : ModelCodec<PtPayload> {
|
||||
val CItems = R.ReadInt()
|
||||
if (CItems < 0 || CItems > 1 shl 20) throw ProtocolException("pt 项数不合法:$CItems")
|
||||
val Items = ArrayList<PtItem>(CItems)
|
||||
repeat(CItems) {
|
||||
// 最近几项的「modelId@起点..终点」轨迹:解码失败时一并抛出,便于判断是不是前面某项吃错字节导致漂移
|
||||
val Trace = ArrayList<String>()
|
||||
repeat(CItems) { Index ->
|
||||
val Start = R.Position
|
||||
val Id = R.ReadLong()
|
||||
if (Id == 0L) {
|
||||
Items.add(PtItem(Id, R.ReadLong()))
|
||||
} else {
|
||||
val Codec = ModelRegistry.ByModelId[Id]
|
||||
?: throw ProtocolException("pt 项 modelId=$Id 没有 codec")
|
||||
Items.add(PtItem(Id, Codec.DecodeAny(R, N)))
|
||||
?: throw ProtocolException(
|
||||
"pt 第 ${Index + 1}/$CItems 项 modelId=$Id 没有 codec(偏移 $Start)" +
|
||||
TraceHint(Trace)
|
||||
)
|
||||
val Value = try {
|
||||
Codec.DecodeAny(R, N)
|
||||
} catch (E: Throwable) {
|
||||
throw ProtocolException(
|
||||
"pt 第 ${Index + 1}/$CItems 项 modelId=$Id(${Codec::class.simpleName})解码失败" +
|
||||
"(偏移 $Start):${E.message}" + TraceHint(Trace)
|
||||
)
|
||||
}
|
||||
Items.add(PtItem(Id, Value))
|
||||
}
|
||||
if (Trace.size >= 4) Trace.removeAt(0)
|
||||
Trace.add("#${Index + 1} $Id@$Start..${R.Position}")
|
||||
}
|
||||
return PtPayload(Objects, Items)
|
||||
}
|
||||
|
||||
private fun TraceHint(Trace: List<String>): String =
|
||||
if (Trace.isEmpty()) "" else ";前几项:" + Trace.joinToString(",")
|
||||
|
||||
override fun Encode(W: ByteWriter, N: NullmapWriter, V: PtPayload) {
|
||||
W.WriteInt(V.Objects.size)
|
||||
for (O in V.Objects) {
|
||||
|
||||
@@ -433,18 +433,29 @@ def Main():
|
||||
def RegistryWithModelIds(ResolvedMap, NameMap):
|
||||
Lines = ["object ModelRegistry {", " val ByModelId: Map<Long, ErasedCodec> = mapOf("]
|
||||
Seen = set()
|
||||
for Key, (Def, Nodes) in ResolvedMap.items():
|
||||
C = Codecs[Key]
|
||||
Expr = OVERLAY_NAMES.get(Key, NameMap[Key] + "Codec")
|
||||
Ids = []
|
||||
if C.get("ModelId"):
|
||||
Ids.append(str(C["ModelId"]))
|
||||
Ids += [str(M) for M in (C.get("ModelIds") or [])]
|
||||
for Mid in Ids:
|
||||
if Mid in Seen:
|
||||
|
||||
def Emit(OnlySource: bool):
|
||||
for Key, (Def, Nodes) in ResolvedMap.items():
|
||||
C = Codecs[Key]
|
||||
HasSource = bool(C.get("ModelHow"))
|
||||
if OnlySource != HasSource:
|
||||
continue
|
||||
Seen.add(Mid)
|
||||
Lines.append(f" {Mid}L to {Expr},")
|
||||
Expr = OVERLAY_NAMES.get(Key, NameMap[Key] + "Codec")
|
||||
Ids = []
|
||||
if C.get("ModelId"):
|
||||
Ids.append(str(C["ModelId"]))
|
||||
Ids += [str(M) for M in (C.get("ModelIds") or [])]
|
||||
for Mid in Ids:
|
||||
if Mid in Seen:
|
||||
continue
|
||||
Seen.add(Mid)
|
||||
Lines.append(f" {Mid}L to {Expr},")
|
||||
|
||||
# 先信「源码登记点」(ModelHow 非空),再用指纹/旧 join 兜底:
|
||||
# 同一个 modelId 可能被两个 codec 认领(如 2108103923322474513 = CaptchaCC / DonationData),
|
||||
# 源码登记点才是真正上线的那一个。
|
||||
Emit(True)
|
||||
Emit(False)
|
||||
Lines.append(" )")
|
||||
Lines.append("}")
|
||||
return Lines
|
||||
|
||||
Reference in new issue
Block a user