Files
2026-08-31 10:55:44 +08:00

289 lines
9.9 KiB
Python
Raw Permalink 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.
"""
虎牙 Wup 协议 Python 实现
基于前端 WUP 实现逆向
Wup 包结构:
[4字节长度][Wup body]
Wup body = tag1:iVersion, tag2:cPacketType, tag3:iMessageType,
tag4:iRequestId, tag5:sServantName, tag6:sFuncName,
tag7:sBuffer(bytes), tag8:iTimeout, tag9:context(map), tag10:status(map)
sBuffer = Map<"tReq", 编码后的请求结构体>
"""
import struct
from typing import Any
from .taf_protocol import TafInputStream, TafOutputStream, TafStruct, TafType
class WupRequest:
"""Wup 请求对象"""
def __init__(self):
self.iVersion: int = 3 # tag 1
self.cPacketType: int = 0 # tag 2
self.iMessageType: int = 0 # tag 3
self.iRequestId: int = 0 # tag 4
self.sServantName: str = "" # tag 5
self.sFuncName: str = "" # tag 6
self.sBuffer: bytes = b"" # tag 7
self.iTimeout: int = 3000 # tag 8
self.context: dict[str, str] = {} # tag 9
self.status: dict[str, str] = {} # tag 10
self.newdata: dict[str, bytes] = {}
def setServant(self, name: str):
self.sServantName = name
def setFunc(self, name: str):
self.sFuncName = name
def setRequestId(self, req_id: int):
self.iRequestId = req_id
def writeStruct(self, key: str, struct_data):
"""
写入请求参数到 newdata[key]
Args:
key: 键名(通常是 "tReq"
struct_data: TafStruct 对象 / dict / list
"""
os = TafOutputStream()
if isinstance(struct_data, TafStruct) or hasattr(struct_data, "write_to"):
# 结构体对象:STRUCT_BEGIN + 内容 + STRUCT_END
os.write_struct(0, struct_data)
elif isinstance(struct_data, dict):
os.write_struct_begin(0)
for tag_id, (field_name, field_value) in enumerate(struct_data.items()):
self._write_field(os, tag_id, field_value)
os.write_struct_end()
elif isinstance(struct_data, (list, tuple)):
# wsLaunch 等用 list 参数
os.write_head(0, TafType.LIST)
os.write_int32(0, len(struct_data))
for i, item in enumerate(struct_data, start=1):
self._write_field(os, i, item)
else:
raise TypeError(f"不支持的 struct_data 类型: {type(struct_data)}")
self.newdata[key] = os.get_bytes()
def _write_field(self, os: TafOutputStream, tag: int, value: Any):
if isinstance(value, bool):
os.write_boolean(tag, value)
elif isinstance(value, int):
os.write_int64(tag, value)
elif isinstance(value, float):
os.write_double(tag, value)
elif isinstance(value, str):
os.write_string(tag, value)
elif isinstance(value, bytes):
os.write_bytes(tag, value)
elif isinstance(value, dict):
os.write_map(tag, value)
elif isinstance(value, (list, tuple)):
os.write_list(tag, list(value))
elif hasattr(value, "write_to"):
os.write_struct(tag, value)
else:
raise TypeError(f"不支持的字段类型: {type(value)}")
def encode(self) -> bytes:
"""编码为完整 Wup 包(含长度前缀)"""
# 1. newdata -> Map (tag 0)
data_os = TafOutputStream()
data_os.write_head(0, TafType.MAP)
data_os.write_int32(0, len(self.newdata))
for k, v in self.newdata.items():
data_os.write_string(0, k)
data_os.write_bytes(1, v)
self.sBuffer = data_os.get_bytes()
# 2. Wup 头
wup_os = TafOutputStream()
wup_os.write_int16(1, self.iVersion)
wup_os.write_int8(2, self.cPacketType)
wup_os.write_int32(3, self.iMessageType)
wup_os.write_int32(4, self.iRequestId)
wup_os.write_string(5, self.sServantName)
wup_os.write_string(6, self.sFuncName)
wup_os.write_bytes(7, self.sBuffer)
wup_os.write_int32(8, self.iTimeout)
wup_os.write_map(9, self.context)
wup_os.write_map(10, self.status)
wup_body = wup_os.get_bytes()
# 3. 长度前缀
length = 4 + len(wup_body)
return struct.pack(">I", length) + wup_body
def normalize_wup_payload(data: bytes) -> bytes:
"""去掉可选的 4 字节 WUP 长度前缀,返回裸 WUP body"""
if len(data) < 4:
return data
declared_len = struct.unpack(">I", data[0:4])[0]
if declared_len == len(data) or declared_len + 4 == len(data):
return data[4:]
return data
class WupResponse:
"""Wup 响应对象"""
def __init__(self):
self.iVersion: int = 0
self.cPacketType: int = 0
self.iMessageType: int = 0
self.iRequestId: int = 0
self.sServantName: str = ""
self.sFuncName: str = ""
self.sBuffer: bytes = b""
self.iTimeout: int = 0
self.context: dict[str, str] = {}
self.status: dict[str, str] = {}
self.newdata: dict[str, bytes] = {}
def decode(self, data: bytes):
"""解码响应(不包含长度前缀;若含前缀会自动跳过)"""
data = normalize_wup_payload(data)
ins = TafInputStream(data)
# 按字段读,直到结束
try:
while True:
tag, dtype = ins.peek_head()
if tag > 10:
break
if tag == 1:
ins.read_head()
self.iVersion = ins._read_int_value(dtype)
elif tag == 2:
ins.read_head()
self.cPacketType = ins._read_int_value(dtype)
elif tag == 3:
ins.read_head()
self.iMessageType = ins._read_int_value(dtype)
elif tag == 4:
ins.read_head()
self.iRequestId = ins._read_int_value(dtype)
elif tag == 5:
ins.read_head()
self.sServantName = _read_string_value(ins, dtype)
elif tag == 6:
ins.read_head()
self.sFuncName = _read_string_value(ins, dtype)
elif tag == 7:
ins.read_head()
self.sBuffer = _read_bytes_value(ins, dtype)
self._decode_buffer()
elif tag == 8:
ins.read_head()
self.iTimeout = ins._read_int_value(dtype)
elif tag == 9:
# context map
ins.read_head()
self.context = _read_map_value(ins, dtype)
elif tag == 10:
ins.read_head()
self.status = _read_map_value(ins, dtype)
break
else:
ins.read_head()
ins.skip_field(dtype)
except EOFError:
pass
def _decode_buffer(self):
"""解析 sBuffer 里的 newdata Map"""
if not self.sBuffer:
return
ins = TafInputStream(self.sBuffer)
try:
tag, dtype = ins.read_head()
if tag == 0 and dtype == TafType.MAP:
count = ins._read_int_len()
for _ in range(count):
_, kt = ins.read_head()
key = _read_string_value(ins, kt)
_, vt = ins.read_head()
val = _read_bytes_value(ins, vt)
self.newdata[key] = val
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
print(f"[WupResponse] 解析 newdata 失败: {e}")
def readStruct(self, key: str, struct_class=None):
"""
读取响应结构体
Args:
key: "tRsp" / "tResp" / "tReq"(响应里通常是 tRsp
struct_class: 结构体类(需实现 read_from),None 则返回原始 bytes
"""
data = self.newdata.get(key)
if not data and key == "tRsp":
data = self.newdata.get("tResp")
if not data and key == "tResp":
data = self.newdata.get("tRsp")
if not data:
return None
if struct_class is None:
return data
ins = TafInputStream(data)
# newdata 里的结构体以 STRUCT_BEGIN 开头
try:
_tag, dtype = ins.peek_head()
if dtype == TafType.STRUCT_BEGIN:
ins.read_head() # 消费 STRUCT_BEGIN
obj = struct_class()
obj.read_from(ins)
return obj
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
print(f"[WupResponse] 解析 {struct_class.__name__} 失败: {e}")
return None
# ============================================================
# 辅助:按已知 dtype 读取值
# ============================================================
def _read_string_value(ins: TafInputStream, dtype: int) -> str:
if dtype == TafType.STRING1:
length = struct.unpack("B", ins.buf.read(1))[0]
elif dtype == TafType.STRING4:
length = struct.unpack(">I", ins.buf.read(4))[0]
else:
return ""
return ins.buf.read(length).decode("utf-8", errors="replace")
def _read_bytes_value(ins: TafInputStream, dtype: int) -> bytes:
if dtype != TafType.SIMPLE_LIST:
return b""
ins.read_head() # 元素类型 INT8
length = ins._read_int_len()
return ins.buf.read(length)
def _read_map_value(ins: TafInputStream, dtype: int) -> dict:
if dtype != TafType.MAP:
return {}
count = ins._read_int_len()
result = {}
for _ in range(count):
_, kt = ins.read_head()
k = _read_string_value(ins, kt)
_, vt = ins.read_head()
v = (
_read_string_value(ins, vt)
if vt in (TafType.STRING1, TafType.STRING4)
else ""
)
result[k] = v
return result