"""虎牙 TAF(JCE) 纯字节流递归解码器 — 不依赖项目 read_* 高层 API。 独立实现 head/value 解析,直接按 dtype 读取 value(已消费 head 的前提下), 避免与 TafInputStream.read_string 等"内部再读 head"的方法冲突。 """ from __future__ import annotations import struct # 类型标签(与 core/huya/taf_protocol.py TafType 一致) INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03 FLOAT, DOUBLE = 0x04, 0x05 STRING1, STRING4 = 0x06, 0x07 MAP, LIST = 0x08, 0x09 STRUCT_BEGIN, STRUCT_END = 0x0A, 0x0B ZERO, SIMPLE_LIST = 0x0C, 0x0D _STR = (STRING1, STRING4) _INTS = (INT8, INT16, INT32, INT64) class Taf: """带游标的字节读取器""" def __init__(self, data: bytes): self.d = data self.p = 0 def eof(self) -> bool: return self.p >= len(self.d) def peek_head(self): if self.eof(): raise EOFError b = self.d[self.p] tag = (b >> 4) & 0x0F dt = b & 0x0F if tag == 15: tag = self.d[self.p + 1] return tag, dt def read_head(self): tag, dt = self.peek_head() self.p += 1 if (self.d[self.p - 1] >> 4) & 0x0F == 15: self.p += 1 return tag, dt def read_len(self) -> int: """读 map/list 长度(int32 优化编码)""" tag, dt = self.read_head() return self.read_int_value(dt) def read_int_value(self, dt: int) -> int: if dt == ZERO: return 0 if dt == INT8: v = struct.unpack('b', self.d[self.p:self.p + 1])[0]; self.p += 1; return v if dt == INT16: v = struct.unpack('>h', self.d[self.p:self.p + 2])[0]; self.p += 2; return v if dt == INT32: v = struct.unpack('>i', self.d[self.p:self.p + 4])[0]; self.p += 4; return v if dt == INT64: v = struct.unpack('>q', self.d[self.p:self.p + 8])[0]; self.p += 8; return v raise ValueError(f"期望整数, 实际 0x{dt:02x} @{self.p}") def read_string_value(self, dt: int) -> str: if dt == STRING1: n = self.d[self.p]; self.p += 1 elif dt == STRING4: n = struct.unpack('>I', self.d[self.p:self.p + 4])[0]; self.p += 4 else: raise ValueError(f"期望 string, 实际 0x{dt:02x} @{self.p}") v = self.d[self.p:self.p + n].decode('utf-8', errors='replace'); self.p += n return v def skip_value(self, dt: int): if dt == ZERO: return if dt in _INTS: self.p += {INT8: 1, INT16: 2, INT32: 4, INT64: 8}[dt] elif dt in (FLOAT,): self.p += 4 elif dt in (DOUBLE,): self.p += 8 elif dt == STRING1: self.p += 1 + self.d[self.p] elif dt == STRING4: self.p += 4 + struct.unpack('>I', self.d[self.p:self.p + 4])[0] elif dt == SIMPLE_LIST: self.read_head() # 元素类型 n = self.read_len() self.p += n elif dt == MAP: n = self.read_len() for _ in range(n): _, kt = self.read_head(); self.skip_value(kt) _, vt = self.read_head(); self.skip_value(vt) elif dt == LIST: n = self.read_len() for _ in range(n): _, it = self.read_head(); self.skip_value(it) elif dt == STRUCT_BEGIN: while True: _, t = self.read_head() if t == STRUCT_END: break self.skip_value(t) # STRUCT_END 不需要处理 def decode_value(t: Taf, dt: int, depth: int = 0, max_depth: int = 6, max_items: int = 50): """给定已消费 head 的 dtype,递归读 value。""" if dt == ZERO: return 0 if dt in _INTS: return t.read_int_value(dt) if dt == FLOAT: v = struct.unpack('>f', t.d[t.p:t.p + 4])[0]; t.p += 4; return round(v, 6) if dt == DOUBLE: v = struct.unpack('>d', t.d[t.p:t.p + 8])[0]; t.p += 8; return round(v, 6) if dt in _STR: return t.read_string_value(dt) if dt == SIMPLE_LIST: t.read_head() # 元素类型 n = t.read_len() raw = t.d[t.p:t.p + n]; t.p += n # 尝试 utf8 可读 try: s = raw.decode('utf-8') if s.isprintable() and n < 500: return s except Exception: pass return {"_bytes": n, "hex": raw[:48].hex()} if dt == MAP: n = t.read_len() m = {} for _ in range(min(n, max_items)): kt_tag, kdt = t.read_head() k = decode_value(t, kdt, depth + 1) if kdt != STRUCT_BEGIN else _decode_struct(t, depth + 1) vt_tag, vdt = t.read_head() v = decode_value(t, vdt, depth + 1) if depth + 1 < max_depth else _skip_str(t, vdt) m[str(k)] = v return m if dt == LIST: n = t.read_len() items = [] for _ in range(min(n, max_items)): it_tag, idt = t.read_head() items.append(decode_value(t, idt, depth + 1) if depth + 1 < max_depth else _skip_str(t, idt)) return items if dt == STRUCT_BEGIN: return _decode_struct(t, depth + 1) return f"<0x{dt:02x}>" def _skip_str(t: Taf, dt: int): t.skip_value(dt) return f"<0x{dt:02x}>" def _decode_struct(t: Taf, depth: int = 0): """已消费 STRUCT_BEGIN,读到 STRUCT_END。""" out = {} cnt = 0 while not t.eof() and cnt < 100: try: tag, dt = t.read_head() except EOFError: break if dt == STRUCT_END: break key = f"t{tag}" if depth < 6: try: out[key] = decode_value(t, dt, depth) except Exception as e: t.skip_value(dt) out[key] = f"" else: t.skip_value(dt) out[key] = f"<0x{dt:02x}>" cnt += 1 return out def decode_struct_bytes(data: bytes): """解析一个 TAF struct 字节串(可含或不含 STRUCT_BEGIN 头)。""" t = Taf(data) out = {} cnt = 0 while not t.eof() and cnt < 100: try: tag, dt = t.read_head() except EOFError: break if dt == STRUCT_END: break key = f"t{tag}" try: out[key] = decode_value(t, dt, 0) except Exception as e: t.skip_value(dt) out[key] = f"" cnt += 1 return out