Files
live-hub-py/artifacts/guid_session_consistency/DIFF_FILE
T

189 lines
9.7 KiB
Plaintext
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.
diff --git a/core/huya/elite_session.py b/core/huya/elite_session.py
index 31f2449..0eb740c 100644
--- a/core/huya/elite_session.py
+++ b/core/huya/elite_session.py
@@ -94,6 +94,13 @@ class HuyaEliteWssSession:
)
if not initialized:
raise RuntimeError(f"虎牙 {kind} WSS 初始化失败")
+ if kind == "activity" and self.client._launch_guid:
+ # wsLaunch 的 sGuid 是当前连接的真实会话值,供后续业务 Cookie
+ # 与 HTTP baseinfo 继续复用,避免沿用登录时的旧 guid。
+ self.cookie = self.client._cookie_with_guid(
+ self.cookie, self.client._launch_guid
+ )
+ self.guid = self.client._launch_guid
except Exception:
self.close()
raise
diff --git a/core/huya/wss_client.py b/core/huya/wss_client.py
index 929c622..c665afd 100644
--- a/core/huya/wss_client.py
+++ b/core/huya/wss_client.py
@@ -26,6 +26,7 @@ from typing import Any, cast
import websockets
+from .cookie_utils import cookie_pairs, normalize_cookie_pairs
from .frame_decoder import _decode_taf_struct, _truncate, format_wss_log
from .taf_protocol import TafInputStream, TafOutputStream, TafStruct, TafType
from .wup_protocol import WupRequest, WupResponse
@@ -192,6 +193,26 @@ class HuyaWssClient:
return normalized
return f"{normalized}; {cookie}"
+ @staticmethod
+ def _cookie_with_guid(cookie: str, guid: str) -> str:
+ """用当前 WSS 会话的 sGuid 替换 Cookie 中的旧 guid。"""
+ value = str(guid or "").strip()
+ if not value:
+ return cookie or ""
+ pairs = cookie_pairs(cookie)
+ replaced = False
+ normalized: list[tuple[str, str]] = []
+ for key, item_value in pairs:
+ if key == "guid":
+ if not replaced:
+ normalized.append((key, value))
+ replaced = True
+ continue
+ normalized.append((key, item_value))
+ if not replaced:
+ normalized.append(("guid", value))
+ return normalize_cookie_pairs(normalized)
+
@staticmethod
def _build_biz_user(uid: int, cookie: str):
"""构造 WSS 业务 UserId(HAR 实证:guid 为空,cookie 不额外加前缀)"""
@@ -389,7 +410,12 @@ class HuyaWssClient:
if launch_rsp is None:
self.logger("[WSS] 活动 wsLaunch 无响应")
return False
- await self.send_auth(uid, cookie, sequence=SEQ_ACTIVITY_BIZ)
+ # wsLaunch 会为当前连接签发新的 sGuid。AUTH、getConfig 和后续业务
+ # 请求必须使用同一个值;登录 Cookie 里的旧 guid 不能继续复用。
+ if self._launch_guid:
+ cookie = self._cookie_with_guid(cookie, self._launch_guid)
+ guid = self._launch_guid
+ await self.send_auth(uid, cookie, sequence=SEQ_ACTIVITY_BIZ, guid=guid)
# 9.1 抓包:getConfig 使用 wsLaunch 响应返回的新 guid;业务 UserId 仍为空 guid。
config_rsp = await self.call_get_config_activity(
uid, cookie, guid=self._launch_guid
@@ -570,7 +596,13 @@ class HuyaWssClient:
return body[5 : 5 + wup_len]
return body
- async def send_auth(self, uid: int, cookie: str, sequence: int = SEQ_WSLAUNCH):
+ async def send_auth(
+ self,
+ uid: int,
+ cookie: str,
+ sequence: int = SEQ_WSLAUNCH,
+ guid: str = "",
+ ):
"""cmd 0x0a AUTH — 对齐 9.1 抓包(828 活动帧)。
9.1 活动 AUTH 结构(逐字节核验):
@@ -591,8 +623,8 @@ class HuyaWssClient:
os.write_string(1, WSS_COOKIE_UA)
os.write_string(2, HuyaWssClient._normalize_biz_cookie(cookie))
guid_match = re.search(r"(?:^|;\s*)guid=([^;]+)", cookie or "")
- guid = (guid_match.group(1).strip() if guid_match else "") or self._launch_guid
- os.write_string(3, guid or "")
+ auth_guid = (guid or "").strip() or (guid_match.group(1).strip() if guid_match else "") or self._launch_guid
+ os.write_string(3, auth_guid)
os.write_int8(4, 1)
os.write_string(5, "HUYA&ZH&2052")
os.write_string(6, "")
diff --git "a/docs/HUYA_COOKIE\350\267\257\347\272\277\347\212\266\346\200\201.md" "b/docs/HUYA_COOKIE\350\267\257\347\272\277\347\212\266\346\200\201.md"
index 69e5423..d67e890 100644
--- "a/docs/HUYA_COOKIE\350\267\257\347\272\277\347\212\266\346\200\201.md"
+++ "b/docs/HUYA_COOKIE\350\267\257\347\272\277\347\212\266\346\200\201.md"
@@ -15,7 +15,7 @@ App 登录可以得到新鲜的 `udb_cred`、`udb_uid`、`yyuid`、`udb_biztoken
| `udb_cred`、`udb_uid`、`yyuid`、`udb_biztoken` | 已完成 | App 登录与 QR 绑定链 |
| `sdid`、`hdid` | 已完成 | hydevice / `df/collect` |
| `udb_deviceid`、`udb_guiddata`、`udb_anobiztoken`、`udb_anouid` | 已完成 | `anon_device.py` |
-| `guid` | 会话路径完成,回退算法未完全确认 | `wsLaunch` 返回值优先;回退值为结构仿造 |
+| `guid` | 会话路径已闭合 | 活动 `wsLaunch` 每次签发 32 位 `sGuid`;初始化时统一覆盖 AUTH、getConfig 和后续业务 Cookie,`956a/966a/976a` 等中间段不是本地版本常量 |
| `__yamid_new` | 已完成 | `room_normal.js` `generate32` Python 复刻 |
| `game_did` | 已完成 | 35 位、63 字符集规则复刻 |
| `_yasids` | 格式完成,精确共享种子未确认 | 与 `__yamid_new` 使用同类时钟生成器 |
@@ -47,10 +47,9 @@ SDK/Python 输出:d07a45de567322edcd42312f38e610be
## 尚未闭合的工程项
1. 将真实 WebView/Chrome 的 `getFeature()` 结果接入登录调用,替代当前的设备画像近似值。
-2. 明确 `guid` 的 `956a` / `966a` 版本差异和无 `wsLaunch` 时的真实生成规则。
-3. 让活动会话补齐后的 Cookie 持久化回账号,避免原始 16 字段账号每次任务重新生成设备字段。
-4. 为 `raw_cookie` 增加重建网页 Cookie 的服务入口。
-5. 商城下单、支付和支付状态轮询保持单独人工验证,不纳入只读回归。
+2. 让活动会话补齐后的 Cookie 持久化回账号,避免原始 16 字段账号每次任务重新生成设备字段。
+3. 为 `raw_cookie` 增加重建网页 Cookie 的服务入口。
+4. 商城下单、支付和支付状态轮询保持单独人工验证,不纳入只读回归。
## 证据入口
diff --git a/docs/HUYA_WEB_FIELD_TRACE.md b/docs/HUYA_WEB_FIELD_TRACE.md
index 8a7f9fe..4c82581 100644
--- a/docs/HUYA_WEB_FIELD_TRACE.md
+++ b/docs/HUYA_WEB_FIELD_TRACE.md
@@ -77,7 +77,7 @@ qimeisdk-web 0.6.3 源码已定位:
| 字段 | 来源 | 风控关联性 | 建议 |
| --- | --- | --- | --- |
-| `guid` | 浏览器脚本(有固定段 `0a..966a`)| 高(设备与会话绑定) | **必须按结构仿造或复用 wsLaunch 会话值**;随机纯 hex 与真实结构不符 |
+| `guid` | 活动 `wsLaunch` 响应 `sGuid`;页面 SDK 负责持久化 | 高(设备与会话绑定) | **优先复用当前 wsLaunch 会话值**;`956a/966a/976a` 等中间段随服务端返回变化,不能固化为版本常量 |
| `__yamid_new` | 浏览器脚本(CBCC 前缀+种子)| 高(与 rootsid 同源)| `room_normal.js` `generate32` 已复刻;精确 rootsid 共享计数仍待确认 |
| `_qimei_uuid42` | QIMEI SDK 0.6.3 VM 生成器 `e=6064` | 中 | Python 已按时间编码、CRC32 和随机半字节逐项复刻 |
| `_qimei_fingerprint` | QIMEI SDK 0.6.3 x64hash128(feature_string, 31) | 中 | Python 已复刻,输入字段见路线状态文档 |
@@ -87,7 +87,7 @@ qimeisdk-web 0.6.3 源码已定位:
**当时的下一步记录(部分已完成,保留作历史)**:
1. `_qimei_fingerprint` 已改为浏览器能力特征串的 `x64hash128`,见路线状态文档。
-2. `guid` 已优先使用 `wsLaunch` 会话值,回退固定段仍待确认。
+2. `guid` 已统一使用 `wsLaunch` 会话值;活动 AUTH、getConfig 与后续业务 Cookie 均复用同一返回值。
3. `__yamid_new` 已定位 `room_normal.js` `generate32` 并完成 Python 复刻。
## 六、核心字段生成算法逆向结果(2026-09-01 晚,浏览器实测 + 源码定位)
diff --git a/tests/test_huya_elite_protocol.py b/tests/test_huya_elite_protocol.py
index 71a10e6..ce4360d 100644
--- a/tests/test_huya_elite_protocol.py
+++ b/tests/test_huya_elite_protocol.py
@@ -146,3 +146,37 @@ def test_activity_auth_frame_matches_latest_capture_layout():
# body 必须与 9.1 828 AUTH 同样以 TAIL_BYTES 收尾
assert message.body[-len(TAIL_BYTES):] == TAIL_BYTES
+
+
+def test_activity_cookie_guid_is_replaced_by_launch_guid():
+ old = "0a" + "1" * 30
+ new = "0a" + "2" * 30
+ cookie = f"udb_uid=1; guid={old}; x=y; guid={old}"
+
+ result = HuyaWssClient._cookie_with_guid(cookie, new)
+
+ assert result == f"udb_uid=1; guid={new}; x=y"
+
+
+def test_activity_auth_accepts_explicit_launch_guid_over_cookie_guid():
+ async def build_frame():
+ client = HuyaWssClient()
+ client._activity_mode = True
+ client.ws = type("FakeWs", (), {"send": AsyncMock()})()
+ await client.send_auth(
+ 1199664135026,
+ "guid=" + "0a" * 16 + "; udb_cred=" + "x" * 300,
+ sequence=0x1D000109,
+ guid="0b" * 16,
+ )
+ return client.ws.send.call_args.args[0]
+
+ message = WssMessage.decode(asyncio.run(build_frame()))
+ stream = TafInputStream(message.body[: -len(TAIL_BYTES)])
+ assert stream.read_head() == (3, TafType.ZERO)
+ stream.read_int64(0)
+ stream.read_string(1)
+ assert stream.read_head() == (2, TafType.STRING4)
+ cookie_len = int.from_bytes(stream.buf.read(4), "big")
+ stream.buf.read(cookie_len)
+ assert stream.read_string(3) == "0b" * 16