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
2727f7(n) → lcgNext(n) (private inline)
2828f8(content, etag) → descrambleContent(content, etag)
2929f10(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.
3239transferStag(stag) → base64Decode(stag) (private inline)
3340```
3441
@@ -38,7 +45,7 @@ transferStag(stag) → base64Decode(stag) (private inline)
3845
39461 . ` 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") `
42493 . Build playlist URL: ` https://<host>/playlist/{id}/playlist.m3u8?token={avsSk}&fc={fc} `
43504 . 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```
7178getPlayerLink():
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
88127These are the most critical correctness issues when porting 32-bit integer operations from JS to
@@ -140,4 +179,10 @@ interpretation.
1401794 . ** ` SegmentDecryptUtil.kt ` ** : after any changes, verify bitwise correctness (see pitfalls above).
1411805 . ** ` AnimeApiPlayer.kt ` ** : verify state vars are correctly set/cleared.
1421816 . ** ` 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