Skip to content

Commit 959333d

Browse files
fix(player): sync avs decrypt pipeline (loader 1.35.12)
- bump api_hidden (f034e9c): session key lấy từ JWT `token` trên segment URL (avsToken của player không còn đúng), trimSegmentHeader PNG-aware 128 byte, User-Agent cho request của media3 (/seg-key/ trả 403 khi thiếu UA), domain mặc định animevietsub.sale + migrate host cũ đang lưu - docs/skills/sync-animevietsub-decrypt: mapping mới (decryptSegmentUrl sessionKey?, f12 PNG-aware), thêm section pipeline segment (BYTERANGE @128 → AES-128-CBC IV = media sequence, CF scope) và checklist mục7–9
1 parent e89f38a commit 959333d

2 files changed

Lines changed: 57 additions & 12 deletions

File tree

‎docs/agents/skills/sync-animevietsub-decrypt.md‎

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
| File | Purpose |
1010
|------------------------------------------------|---------------------------------------------------------------------|
1111
| `data/remote/api_hidden/SegmentDecryptUtil.kt` | All crypto utilities (f2, f3, f4, f7, f8, f10, f12, decryptSegment) |
12-
| `data/remote/api_hidden/M3u8Result.kt` | Result data class (content, skGlobalDecoded, avsCryptoHarden) |
12+
| `data/remote/api_hidden/M3u8Result.kt` | Result data class (content, avsCryptoHarden) |
1313
| `data/remote/api_hidden/AnimeApiCore.kt` | `getM3u8Content()` + `fnCrypto()` + `extractIdAndSid()` |
14-
| `data/remote/api_hidden/AnimeApiPlayer.kt` | `lastSkGlobalDecoded`, `lastAvsCryptoHarden` state |
14+
| `data/remote/api_hidden/AnimeApiPlayer.kt` | `lastAvsCryptoHarden` state |
1515
| `data/remote/api_hidden/AnimeApi.kt` | `segmentUrlInterceptor` + `segmentDataInterceptor` wiring |
1616

1717
## Function mapping
@@ -27,8 +27,15 @@ f4(token) → parseEnvelope(token): EnvelopeData
2727
f7(n) → lcgNext(n) (private inline)
2828
f8(content, etag) → descrambleContent(content, etag)
2929
f10(token) → extractJtiOdd(token): String
30-
f12(data) → trimSegmentHeader(data): ByteArray
31-
decryptSegment(url, sk) → decryptSegmentUrl(url, skGlobalDecoded): String
30+
f12(data) → trimSegmentHeader(data): ByteArray — KHÁC TS 1 chút:
31+
TS f12 = slice(127) (dead code, không gọi); Kotlin cắt
32+
128 byte CHỈ KHI dữ liệu còn PNG magic (thấy trong bản
33+
trả full file). Thấy payload thẳng (BYTERANGE) → giữ nguyên.
34+
decryptSegment(url, sk?) → decryptSegmentUrl(url, sessionKey?): String
35+
sessionKey = null (mặc định) → lấy SK từ JWT `token`
36+
trên chính segment URL (f10). KHÔNG truyền SK của
37+
player (avsToken) — kể từ 1.35.12 hai khóa khác nhau,
38+
ép dùng SK của player sẽ ra URL rác.
3239
transferStag(stag) → base64Decode(stag) (private inline)
3340
```
3441

@@ -38,7 +45,7 @@ transferStag(stag) → base64Decode(stag) (private inline)
3845

3946
1. `extractIdAndSid()`: fetch player page HTML, extract `id`, `_avsSk`, `_avsCryptoHarden`,
4047
`_avsCryptoHardenShadow`
41-
2. `skGlobalDecoded = f10(avsSk)`, `fc = btoa("cross-origin")`
48+
2. `fc = btoa("cross-origin")`
4249
3. Build playlist URL: `https://<host>/playlist/{id}/playlist.m3u8?token={avsSk}&fc={fc}`
4350
4. Fetch playlist → parse X-Envelope header (JSON with `cn`/`sk`/`ts`/`uid`) or fallback
4451
`X-edge-Tag`/`X-Cache-Node`/`X-Request-Trace`/`X-Proxy-Digest`
@@ -70,19 +77,51 @@ TS `f9`:**
7077
```
7178
getPlayerLink():
7279
iframe path:
73-
core.getM3u8Content() → M3u8Result(content, skGlobalDecoded, avsCryptoHarden)
74-
→ stores lastSkGlobalDecoded, lastAvsCryptoHarden
80+
core.getM3u8Content() → M3u8Result(content, avsCryptoHarden)
81+
→ stores lastAvsCryptoHarden
82+
headers = AnimeDataSource.getHeaders(baseUrl) — PHẢI kèm User-Agent
83+
(CF của stream.googleapiscdn.com trả 403 challenge khi thiếu UA)
7584
decryptM3u8 path:
76-
→ clears lastSkGlobalDecoded = null, lastAvsCryptoHarden = false
85+
→ clears lastAvsCryptoHarden = false
7786
```
7887

7988
### AnimeApi.kt
8089

8190
```
82-
segmentUrlInterceptor: uses decryptSegmentUrl() when _avsCryptoHarden=true
83-
segmentDataInterceptor: always uses trimSegmentHeader() (f12)
91+
segmentUrlInterceptor: LUÔN BẬN (stateless) — decryptSegmentUrl(url) tự lấy SK
92+
từ JWT `token` trên segment URL. Lỗi giải mã → giữ nguyên URL (fallback 404
93+
graceful thay vì crash). Không còn gate theo lastAvsCryptoHarden.
94+
segmentDataInterceptor: luôn dùng trimSegmentHeader() (f12, PNG-aware)
8495
```
8596

97+
## Segment pipeline (site đổi 10/2026 — loader 1.35.12)
98+
99+
Segment URL dạng placeholder `https://stream.googleapiscdn.com/hls/{fileId}.ts?e=...&i=...&token=JWT`,
100+
Media3 xử lý theo thứ tự:
101+
102+
1. **URL interceptor**: `decryptSegmentUrl()` → URL media thật trên
103+
`lh3.googleusercontent.com` (HMAC-SHA256 + AES-CTR, counter = `i`).
104+
SK = f10(JWT `token` **trên chính URL segment**) — *không còn* dùng
105+
avsToken của player (2 khóa khác nhau, ép dùng sai → URL rác).
106+
2. **BYTERANGE**: playlist có `#EXT-X-BYTERANGE:<len>@128` → Media3 tự request
107+
`Range: bytes=128-` (lh3 trả 206) → payload thẳng, hết 128 byte header PNG.
108+
`segmentDataInterceptor` chỉ cắt PNG khi server trả trọn file (phòng hờ).
109+
3. **AES-128-CBC**: Media3 tự làm theo `#EXT-X-KEY`
110+
(`https://stream.googleapiscdn.com/seg-key/...`, key 16 byte, IV = media
111+
sequence) — không cần code riêng, chỉ cần request key đúng UA: `/seg-key/`
112+
trả **403 challenge khi thiếu User-Agent** (không UA → 403; Chrome/Dalvik → 200).
113+
4. Kết quả: MPEG-TS (h264 1080p + aac).
114+
115+
**Cloudflare**: `CloudflareManager` bypass (WebView solve + `WebViewCookieJar`)
116+
chỉ áp dụng cho request OkHttp của app (fetch HTML player + playlist).
117+
**Media3 KHÔNG đi qua CloudflareManager** → header của `PlayerData` phải tự
118+
mang User-Agent trình duyệt (`AnimeDataSource.getHeaders(baseUrl)` — không
119+
`ignoreUserAgent=true`).
120+
121+
**Domain**: `HOST_CURL_DEFAULT = animevietsub.sale` (host cũ `animevietsub.pl`
122+
đã chết — DNS không resolve) + `resolveDomain()` đọc `transform.json` từ
123+
`animevsubtv/data-animevsub-ext` để migrate host cũ đang lưu trong storage.
124+
86125
## Bitwise pitfalls (TS → Kotlin)
87126

88127
These are the most critical correctness issues when porting 32-bit integer operations from JS to
@@ -140,4 +179,10 @@ interpretation.
140179
4. **`SegmentDecryptUtil.kt`**: after any changes, verify bitwise correctness (see pitfalls above).
141180
5. **`AnimeApiPlayer.kt`**: verify state vars are correctly set/cleared.
142181
6. **`AnimeApi.kt`**: verify interceptors still match.
143-
7. Run: `make format`
182+
7. **`decryptSegmentUrl()`** (`SegmentDecryptUtil.kt`): SK mặc định phải lấy từ
183+
JWT `token` trên segment URL (f10). Đối chiếu với `decryptSegment()` trong
184+
run.ts (nếu run.ts thêm `sessionKey` → mirror y hệt).
185+
8. **`trimSegmentHeader()`**: format PNG wrapper + `#EXT-X-BYTERANGE:<len>@128`
186+
có đổi không? Header mới dài bao nhiêu byte?
187+
9. Run: `make format` + `make check`; kiểm chứng nhanh: `bun test` trong repo
188+
decrypt rồi lấy 1 segment giải ra ffprobe (xem `run.spec.ts`).

0 commit comments

Comments
 (0)