""" 虎牙 TAF 协议 Python 实现 基于前端 TAF/WUP 实现逆向 类型标签: 0x00 INT8 0x01 INT16 0x02 INT32 0x03 INT64 0x04 FLOAT 0x05 DOUBLE 0x06 STRING1 0x07 STRING4 0x08 MAP 0x09 LIST 0x0a STRUCT_BEGIN 0x0b STRUCT_END 0x0c ZERO 0x0d SIMPLE_LIST """ import io import struct from typing import Any class TafType: INT8 = 0x00 INT16 = 0x01 INT32 = 0x02 INT64 = 0x03 FLOAT = 0x04 DOUBLE = 0x05 STRING1 = 0x06 STRING4 = 0x07 MAP = 0x08 LIST = 0x09 STRUCT_BEGIN = 0x0A STRUCT_END = 0x0B ZERO = 0x0C SIMPLE_LIST = 0x0D # ============================================================ # 输出流(编码) # ============================================================ class TafOutputStream: """TAF 编码输出流""" def __init__(self): self.buf = io.BytesIO() def get_bytes(self) -> bytes: return self.buf.getvalue() # ---- head ---- def write_head(self, tag: int, data_type: int): if tag < 15: self.buf.write(struct.pack("B", (tag << 4) | data_type)) else: self.buf.write(struct.pack("BB", 0xF0 | data_type, tag)) # ---- 整数(带自动优化) ---- def write_int8(self, tag: int, value: int): if value == 0: self.write_head(tag, TafType.ZERO) else: self.write_head(tag, TafType.INT8) self.buf.write(struct.pack("b", value)) def write_int16(self, tag: int, value: int): if -128 <= value <= 127: self.write_int8(tag, value) else: self.write_head(tag, TafType.INT16) self.buf.write(struct.pack(">h", value)) def write_int32(self, tag: int, value: int): if -32768 <= value <= 32767: self.write_int16(tag, value) else: self.write_head(tag, TafType.INT32) self.buf.write(struct.pack(">i", value)) def write_int64(self, tag: int, value: int): if -2147483648 <= value <= 2147483647: self.write_int32(tag, value) else: self.write_head(tag, TafType.INT64) self.buf.write(struct.pack(">q", value)) def write_uint64(self, tag: int, value: int): """uint64:超过 int32 范围用 INT64""" if value <= 2147483647: self.write_int32(tag, value) else: self.write_head(tag, TafType.INT64) self.buf.write(struct.pack(">Q", value)) # ---- 浮点 ---- def write_float(self, tag: int, value: float): self.write_head(tag, TafType.FLOAT) self.buf.write(struct.pack(">f", value)) def write_double(self, tag: int, value: float): self.write_head(tag, TafType.DOUBLE) self.buf.write(struct.pack(">d", value)) # ---- 字符串 ---- def write_string(self, tag: int, value: str): encoded = value.encode("utf-8") length = len(encoded) if length > 255: self.write_head(tag, TafType.STRING4) self.buf.write(struct.pack(">I", length)) else: self.write_head(tag, TafType.STRING1) self.buf.write(struct.pack("B", length)) self.buf.write(encoded) # ---- 字节数组 ---- def write_bytes(self, tag: int, value: bytes): self.write_head(tag, TafType.SIMPLE_LIST) self.write_head(0, TafType.INT8) # 元素类型固定 INT8 self.write_int32(0, len(value)) self.buf.write(value) # ---- 布尔 ---- def write_boolean(self, tag: int, value: bool): self.write_int8(tag, 1 if value else 0) # ---- 结构体 ---- def write_struct_begin(self, tag: int): self.write_head(tag, TafType.STRUCT_BEGIN) def write_struct_end(self): self.write_head(0, TafType.STRUCT_END) def write_struct(self, tag: int, struct_obj): """写入结构体对象(需实现 write_to)""" self.write_struct_begin(tag) struct_obj.write_to(self) self.write_struct_end() # ---- Map ---- def write_map( self, tag: int, value: dict[Any, Any], key_writer=None, val_writer=None ): self.write_head(tag, TafType.MAP) self.write_int32(0, len(value)) for k, v in value.items(): if key_writer: key_writer(self, 0, k) else: self._write_any(0, k) if val_writer: val_writer(self, 1, v) else: self._write_any(1, v) # ---- List ---- def write_list(self, tag: int, value: list[Any], item_writer=None): self.write_head(tag, TafType.LIST) self.write_int32(0, len(value)) for item in value: if item_writer: item_writer(self, 0, item) else: self._write_any(0, item) def _write_any(self, tag: int, value: Any): if isinstance(value, bool): self.write_boolean(tag, value) elif isinstance(value, int): self.write_int64(tag, value) elif isinstance(value, float): self.write_double(tag, value) elif isinstance(value, str): self.write_string(tag, value) elif isinstance(value, bytes): self.write_bytes(tag, value) elif isinstance(value, dict): self.write_map(tag, value) elif isinstance(value, (list, tuple)): self.write_list(tag, list(value)) elif hasattr(value, "write_to"): self.write_struct(tag, value) else: raise TypeError(f"不支持的类型: {type(value)}") # ============================================================ # 输入流(解码)—— 完整实现,支持所有类型 # ============================================================ class TafInputStream: """TAF 解码输入流""" def __init__(self, data: bytes): self.buf = io.BytesIO(data) def peek_head(self) -> tuple[int, int]: """读取 head 但不消费(用于探测)""" pos = self.buf.tell() try: return self.read_head() finally: self.buf.seek(pos) def read_head(self) -> tuple[int, int]: """返回 (tag, type)""" data = self.buf.read(1) if not data: raise EOFError("读取到文件末尾") b = struct.unpack("B", data)[0] tag = (b >> 4) & 0x0F data_type = b & 0x0F if tag == 15: data = self.buf.read(1) if not data: raise EOFError("读取 tag 扩展字节失败") tag = struct.unpack("B", data)[0] return tag, data_type # ---- 跳过 ---- def skip_field(self, data_type: int): if data_type == TafType.ZERO: return if data_type == TafType.INT8: self.buf.read(1) elif data_type == TafType.INT16: self.buf.read(2) elif data_type == TafType.INT32: self.buf.read(4) elif data_type == TafType.INT64: self.buf.read(8) elif data_type == TafType.FLOAT: self.buf.read(4) elif data_type == TafType.DOUBLE: self.buf.read(8) elif data_type == TafType.STRING1: length = struct.unpack("B", self.buf.read(1))[0] self.buf.read(length) elif data_type == TafType.STRING4: length = struct.unpack(">I", self.buf.read(4))[0] self.buf.read(length) elif data_type == TafType.MAP: self._skip_map() elif data_type == TafType.LIST: self._skip_list() elif data_type == TafType.SIMPLE_LIST: # [head(0,INT8)] [int32 length] [bytes] self.read_head() # 元素类型 length = self._read_int_len() self.buf.read(length) elif data_type == TafType.STRUCT_BEGIN: self._skip_struct() elif data_type == TafType.STRUCT_END: pass else: raise ValueError(f"未知类型 0x{data_type:02x}") def _read_int_len(self) -> int: """读 map/list 长度(int32 带优化)""" _tag, dtype = self.read_head() return self._read_int_value(dtype) def _read_int_value(self, dtype: int) -> int: if dtype == TafType.ZERO: return 0 if dtype == TafType.INT8: return struct.unpack("b", self.buf.read(1))[0] if dtype == TafType.INT16: return struct.unpack(">h", self.buf.read(2))[0] if dtype == TafType.INT32: return struct.unpack(">i", self.buf.read(4))[0] if dtype == TafType.INT64: return struct.unpack(">q", self.buf.read(8))[0] raise ValueError(f"期望整数, 实际 0x{dtype:02x}") def _skip_struct(self): while True: _tag, dtype = self.read_head() if dtype == TafType.STRUCT_END: break self.skip_field(dtype) def _skip_map(self): count = self._read_int_len() for _ in range(count): _, kt = self.read_head() self.skip_field(kt) _, vt = self.read_head() self.skip_field(vt) def _skip_list(self): count = self._read_int_len() for _ in range(count): _, it = self.read_head() self.skip_field(it) # ---- 带跳过策略的字段读取:找到 tag,否则返回默认 ---- def _find_tag(self, target_tag: int, required: bool) -> tuple[int, int] | None: """逐个读 head,tag 相等则返回,tag 超过则回退并返回 None""" while True: pos = self.buf.tell() tag, dtype = self.read_head() if tag == target_tag: return (tag, dtype) if dtype == TafType.STRUCT_END: # 回退,让上层处理 STRUCT_END self.buf.seek(pos) if required: raise ValueError(f"未找到 tag={target_tag} (遇到 STRUCT_END)") return None if tag > target_tag: # 超过目标 tag,回退(字段不存在) self.buf.seek(pos) if required: raise ValueError(f"未找到 tag={target_tag} (遇到 tag={tag})") return None # tag < target_tag,跳过此字段 self.skip_field(dtype) # ---- 基本类型读取 ---- def read_int8(self, tag: int, required: bool = False, default: int = 0) -> int: found = self._find_tag(tag, required) if not found: return default return self._read_int_value(found[1]) def read_int16(self, tag: int, required: bool = False, default: int = 0) -> int: return self.read_int8(tag, required, default) def read_int32(self, tag: int, required: bool = False, default: int = 0) -> int: return self.read_int8(tag, required, default) def read_int64(self, tag: int, required: bool = False, default: int = 0) -> int: return self.read_int8(tag, required, default) def read_uint64(self, tag: int, required: bool = False, default: int = 0) -> int: found = self._find_tag(tag, required) if not found: return default dtype = found[1] if dtype == TafType.ZERO: return 0 if dtype == TafType.INT8: return struct.unpack("B", self.buf.read(1))[0] if dtype == TafType.INT16: return struct.unpack(">H", self.buf.read(2))[0] if dtype == TafType.INT32: return struct.unpack(">I", self.buf.read(4))[0] if dtype == TafType.INT64: return struct.unpack(">Q", self.buf.read(8))[0] raise ValueError(f"期望 uint, 实际 0x{dtype:02x}") def read_boolean( self, tag: int, required: bool = False, default: bool = False ) -> bool: return bool(self.read_int8(tag, required, 1 if default else 0)) def read_float( self, tag: int, required: bool = False, default: float = 0.0 ) -> float: found = self._find_tag(tag, required) if not found: return default dtype = found[1] if dtype == TafType.ZERO: return 0.0 if dtype == TafType.FLOAT: return struct.unpack(">f", self.buf.read(4))[0] if dtype == TafType.DOUBLE: return struct.unpack(">d", self.buf.read(8))[0] return float(self._read_int_value(dtype)) def read_double( self, tag: int, required: bool = False, default: float = 0.0 ) -> float: return self.read_float(tag, required, default) def read_string(self, tag: int, required: bool = False, default: str = "") -> str: found = self._find_tag(tag, required) if not found: return default dtype = found[1] if dtype == TafType.STRING1: length = struct.unpack("B", self.buf.read(1))[0] elif dtype == TafType.STRING4: length = struct.unpack(">I", self.buf.read(4))[0] else: raise ValueError(f"期望 string, 实际 0x{dtype:02x}") return self.buf.read(length).decode("utf-8", errors="replace") def read_bytes( self, tag: int, required: bool = False, default: bytes = b"" ) -> bytes: found = self._find_tag(tag, required) if not found: return default dtype = found[1] if dtype != TafType.SIMPLE_LIST: raise ValueError(f"期望 bytes, 实际 0x{dtype:02x}") self.read_head() # 元素类型 INT8 length = self._read_int_len() return self.buf.read(length) # ---- 复合类型 ---- def read_map( self, tag: int, required: bool = False, key_reader=None, val_reader=None ) -> dict: found = self._find_tag(tag, required) if not found: return {} if found[1] != TafType.MAP: raise ValueError(f"期望 map, 实际 0x{found[1]:02x}") count = self._read_int_len() result = {} for _ in range(count): _, kt = self.read_head() k = self._read_value(kt, key_reader) _, vt = self.read_head() v = self._read_value(vt, val_reader) result[k] = v return result def read_list(self, tag: int, required: bool = False, item_reader=None) -> list: found = self._find_tag(tag, required) if not found: return [] if found[1] != TafType.LIST: raise ValueError(f"期望 list, 实际 0x{found[1]:02x}") count = self._read_int_len() result = [] for _ in range(count): _, it = self.read_head() result.append(self._read_value(it, item_reader)) return result def read_struct(self, tag: int, struct_class, required: bool = False): """读取结构体,struct_class 需有无参构造 + read_from""" found = self._find_tag(tag, required) if not found: return None if found[1] != TafType.STRUCT_BEGIN: raise ValueError(f"期望 struct, 实际 0x{found[1]:02x}") obj = struct_class() obj.read_from(self) # 消费 STRUCT_END _t, dt = self.read_head() if dt != TafType.STRUCT_END: raise ValueError(f"期望 STRUCT_END, 实际 0x{dt:02x}") return obj def _read_value(self, dtype: int, reader=None): if reader: return reader(self, 0) # 自动推断 if dtype == TafType.STRING1: length = struct.unpack("B", self.buf.read(1))[0] return self.buf.read(length).decode("utf-8", errors="replace") if dtype == TafType.STRING4: length = struct.unpack(">I", self.buf.read(4))[0] return self.buf.read(length).decode("utf-8", errors="replace") if dtype in ( TafType.ZERO, TafType.INT8, TafType.INT16, TafType.INT32, TafType.INT64, ): return self._read_int_value(dtype) if dtype == TafType.STRUCT_BEGIN: # 未知 struct,跳过 self._skip_struct() return None self.skip_field(dtype) return None # ============================================================ # 结构体基类 # ============================================================ class TafStruct: """TAF 结构体基类:子类实现 write_to / read_from""" def write_to(self, os: TafOutputStream): raise NotImplementedError def read_from(self, ins: TafInputStream): raise NotImplementedError def to_dict(self) -> dict[str, Any]: """调试用:转字典""" return {k: v for k, v in self.__dict__.items() if not k.startswith("_")} def __repr__(self): return f"{self.__class__.__name__}({self.to_dict()})"