Files
TankIC/3DTank/tools/analyze_nullmap_trace.py
T

100 lines
4.3 KiB
Python
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.
# analyze_nullmap_trace.py - 把客户端采到的「可选 + 列表」位痕迹定位到具体字段
#
# 数据来源:镜像站 module_24635.js 的 Ht.t7h 补丁(可选读器)。它只对「内层读器是列表」的可选字段
# 记录 {hid, bit, inner, elem, elemKeys, stack},节流 POST 到 serve.js 的 /__dump__/nullmap_trace.json。
# 目的:确认 opt<list<…>> 在真实数据里到底占不占 nullmap 位(例如 ParticleEffectItem.colors)。
#
# 定位方法(不依赖 stack,因为镜像站用 eval 加载模块,栈里没有模块名):
# 1. 每个可选包装器实例在客户端被分配稳定 id(hid),同一字段的所有读取共享同一个 hid;
# 2. elemKeys = 内层列表元素 codec 的槽位名集合。槽位名全局唯一,
# 用它去 names/codec_fields.json 的 Slots 里反查,能唯一确定「元素是哪个 codec」;
# 3. 再找哪些字段的类型是 opt<list<该模型>>,若唯一即定位成功。
#
# 用法: python tools/analyze_nullmap_trace.py [--names names]
import json
import os
import sys
from collections import Counter, defaultdict
Root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def Arg(Name, Def):
if '--' + Name in sys.argv:
I = sys.argv.index('--' + Name)
if I + 1 < len(sys.argv):
return sys.argv[I + 1]
return Def
NamesDir = os.path.abspath(Arg('names', os.path.join(Root, 'names')))
TracePath = os.path.join(NamesDir, 'nullmap_trace.json')
FieldsPath = os.path.join(NamesDir, 'codec_fields.json')
def LoadSlots():
"""槽位集合 → [(codec 键, 模型名)],用于反查元素 codec。"""
with open(FieldsPath, 'r', encoding='utf-8') as F:
Codecs = json.load(F).get('Codecs') or {}
Out = defaultdict(list)
for Key, C in Codecs.items():
Slots = tuple(sorted(S.get('Slot') for S in (C.get('Slots') or [])))
if Slots:
Out[Slots].append((Key, C.get('ModelName') or C.get('ModelClassLocal') or '?'))
return Out, Codecs
def FindOwners(Codecs, ModelName):
"""类型为 opt<list<ModelName>> 的 (codec 键, 字段名)。"""
Out = []
Needle = 'opt<list<' + ModelName + '>>'
for Key, C in Codecs.items():
for F in C.get('Fields') or []:
if F.get('Type') == Needle:
Out.append((Key, F.get('Name')))
return Out
def Main():
if not os.path.exists(TracePath):
print('缺少 ' + TracePath)
print('请先用镜像站进一局战斗(module_24635.js 已打位痕迹补丁,会自动 POST)。')
return 1
with open(TracePath, 'r', encoding='utf-8') as F:
Data = json.load(F)
Rows = Data.get('trace') or []
print('位痕迹:%d 条(采集于 %s)' % (len(Rows), Data.get('at', '?')))
SlotIndex, Codecs = LoadSlots()
ByHid = defaultdict(list)
for R in Rows:
ByHid[R.get('hid')].append(R)
print(' 可选列表字段 %d 个(按 hid 分组):' % len(ByHid))
for Hid in sorted(ByHid, key=lambda H: (H is None, H)):
Group = ByHid[Hid]
ElemKeys = tuple(sorted(Group[0].get('elemKeys') or []))
Elems = SlotIndex.get(ElemKeys) or []
ElemName = Elems[0][1] if len(Elems) == 1 else Group[0].get('elem')
Owners = FindOwners(Codecs, ElemName) if ElemName else []
Bits = Counter(R.get('bit') for R in Group)
Pos = [R.get('pos') for R in Group if isinstance(R.get('pos'), int) and R.get('pos') >= 0]
Seq = ''.join('1' if R.get('bit') else '0' for R in Group[:40])
Where = (Owners[0][0] + ' . ' + str(Owners[0][1])) if len(Owners) == 1 else '?'
print(' hid=%-4s 次数=%-4d bit0=%-4d bit1=%-4d 元素=%-22s 字段=%s'
% (Hid, len(Group), Bits.get(0, 0), Bits.get(1, 0), ElemName or '?', Where))
if Pos:
print(' 客户端位游标 pos:首个=%d 最后=%d 前 12 个=%s'
% (Pos[0], Pos[-1], ','.join(str(P) for P in Pos[:12])))
if len(Seq) >= 4:
print(' 位序列:' + Seq)
if len(Elems) > 1:
print(' 元素多解:' + ', '.join(K + '(' + N + ')' for K, N in Elems[:5]))
if len(Owners) > 1:
print(' 字段多解:' + ', '.join(K + '.' + str(N) for K, N in Owners[:5]))
return 0
if __name__ == '__main__':
sys.exit(Main())