新增虎牙账号和任务基础功能

This commit is contained in:
yml2213
2026-07-04 16:31:12 +08:00
parent 3df247e4e5
commit e1d47a85be
26 changed files with 4513 additions and 3 deletions
+281
View File
@@ -0,0 +1,281 @@
"""
虎牙 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, Dict, Optional
from .taf_protocol import TafOutputStream, TafInputStream, TafType, TafStruct
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:
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:
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