Digital Garden

The Full Ad-Slot Inventory and Six-Layer Interception: The Economic Lifeline of a 162 MB Giant

2026-10-14 · ~5 min read · reverse engineering · Tencent Video · ad blocking · Frida · TRPC

When the last dex finished decompiling (32/32, 129,877 java files, the dex-class-index.json precise index of 307,176 defined classes, and a string library of 1,146,216 entries), the economic lifeline of this 162 MB giant — its advertising system — was finally mapped in full.

I. Eleven ad-slot categories

The list spreads by placement, each category carrying dual class-name/string evidence: splash ads (in-house qadsplash, the EAdSplashAdFlipCardUIType family), feed/card ads (the qadsdk SSP, with immersive alone under feed.impl holding 1,191 classes), player pre-roll ads (pre/mid/post, the QMTKmmRewardAdPlugin plugin), pause ads (QMTKmmFullPauseAdPlugin, at the demuxer layer), rewarded video (rewardad plus DKRewardedAdController), casting/DLNA ads (the whole com.qad.adslot family, with the AdSlotScene enum carrying AD_SCENE_DLNA), detail-page floats, ad-policy delivery (JCE AdPolicyRequest/Response, the master switch), H5/WebView ads (it even ships AdFilterUtil to filter its own H5 ads), the ad-slot state machine (QAdSlotState: INIT → REQUEST_START → REQUEST_SUCCESS / REQUEST_NO_AD / REQUEST_ERROR → CLOSED), and anti-block telemetry (reportFunnelPreRollAdLossBeforeLoad/AfterLoad — a pre-roll that fails to play is reported as “ad loss”).

II. The request chain and one key conclusion

The chain was reconstructed to the bottom: client → JCE AdPolicyRequest{adPolicyAppInfo, preloadModels[]} → server AdPolicyResponse{adPosInfos[], requestId} (deciding “which slots get ads”) → per-slot AdSlotRequestModel (getCallee() = trpc.business_feeds.video_ad_ssp_feeds.ServerAdFeedsVideo, TRPC/protobuf) → AdSlotResponse → the QAdSlotState state machine → QAdSlotManager.getAdView() rendering.

The key conclusion hides in the state machine’s enum: whether any ad slot “has ads” is ultimately decided by the server response, and the client state machine itself has the AD_STATE_REQUEST_NO_AD proper branch — forcing it to “no ads” is not a hard shove but walking its own empty-state path. This is the cleanest, least crash-prone interception approach.

III. Six-layer interception

The plan is ranked by cost-performance into six layers: L1 policy layer (clearing adPosInfos where the AdPolicyResponse lands — the master switch, affecting all “policy-driven” slots), L2 RPC layer (intercepting the callee trpc.business_feeds.video_ad_ssp_feeds.ServerAdFeedsVideo or replacing the response with an empty one), L3 state machine (forcing AD_STATE_REQUEST_NO_AD + close(), covering all slots on that framework including DLNA), L4 render layer (getAdView() returning null, a fallback against “has data but displays”), L5 position-specific (splash/pre-roll/pause/rewarded each with entry short-circuits), L6 telemetry layer (blocking ad-loss reporting to reduce being judged as “ad loss”). The recommended order is L1→L2→L3→L5→L4→L6 — the first three cover the vast majority of slots.

The first batch of interception scripts has already landed: adblock-tenvideo.js (Frida), implementing L1/L2/L3/L6, all with existence guards (skip and log if a class is absent), assuming no version details. The scope declaration is written at the head of the report and carved on the monument: only client-side ad-slot interception; no membership-privilege forgery, no DRM content decryption — that is server-side authorization, unsolvable client-side, and not done. That runtime verification was not performed (no device online) is also honestly noted.

Real coordinates

Comments

← Digital Garden