CatGunner CookbookBardCat 1.1.59 · L1 ANALYSIS · tài liệu học
Trang chủTài liệu framework › 10 — Kiếm tiền: Quảng cáo (IAA) + Mua trong ứng dụng (IAP)
Nguồn: 10-monetization-iaa-iap.md · Cập nhật lần cuối: 2026-09-06 23:04 +07 · sha256 nguồn b3b20140c504

10 — Kiếm tiền: Quảng cáo (IAA) + Mua trong ứng dụng (IAP)

Nguồn: chưng cất từ restore SOT com.Chodun.CatGunner 1.1.59 (BardCat). Mọi C# / công thức ở đây là L1 ANALYSIS — đọc từ ARM64 / Hex-Rays của libil2cpp.so tại RVA ghi kèm, từ IL2CPP metadata đã giải mã, từ Main.unity, và từ hai lượt probe trên thiết bị: (quan sát IAA dưới segment 1 trên bản .sot — save riêng, đã pm clear sau khi xong). Không phải source gốc.

Ranh giới R1 / R9. SDK quảng cáo, IAP, attribution, offerwall KHÔNG được tích hợp vào bản restore — chúng nằm sau interface fail-closed (IAdService, IPurchaseService, …) và không bao giờ được cấp thưởng, entitlement hay purchase. Tài liệu này chưng cất THIẾT KẾ kiếm tiền (placement, trigger, gate, offer, timing), không chưng cất nội tại SDK. Không có đoạn nào ở đây mô tả cách giả mạo mua hàng hay bỏ qua quảng cáo.

Liên quan: 01 Kinh tế · 09 Prestige · 07 Tiến trình · [liveops catalog: LIVEOPS_SURFACE.md] · [liveops catalog: UNLOCK_GATES.md] · [liveops catalog: PROBE_RESULT.md] · [liveops catalog: PROBE_RESULT.md]


1. Vì sao monetization là "song sinh" của core loop

Trong game idle, người chơi chờ. Mọi thứ rút ngắn cái chờ đó là hàng hoá. CatGunner bán đúng 4 thứ:

Hàng hoá Bán bằng quảng cáo (IAA) Bán bằng tiền (IAP)
Nhân thu nhập / sát thương tạm thời Buff ×3, 300 s/lần buff_pack×3 VĨNH VIỄN
Nhân phần thưởng một lần Bonus sau Mine/Boss/Hunt/Challenge, Mission, Fish gacha PlusPack (×2 Monster/Boss, ×1.5 Mine)
Nhảy cóc tiến trình Rebirth tăng cường ×3 (1 lần/ngày) catgunner_rebirth_single (thêm 1 lần/ngày, mở ở stage ~100)
Bỏ ma sát FreeCash link → hết phải xem reward-ad remove_forced_ads / remove_all_ads

Hai cột không cạnh tranh nhau — chúng xếp tầng: cột IAA là cách người chơi F2P nếm trải giá trị của multiplier; cột IAP bán chính cái multiplier đó ở dạng vĩnh viễn. Đó là toàn bộ ý đồ thiết kế. Điểm nối kỹ thuật của hai cột là Shop_manager.Is_Remove_AD_All / Is_Buff_Pack — hai cờ non-consumable đọc trực tiếp bởi tầng quảng cáo (§4.1, §5).


2. Stable truths

Mức bằng chứng: disasm-proven = ARM64/Hex-Rays tại RVA ghi kèm · metadata-proven = IL2CPP metadata đã giải mã (dump.cs / field-default-value blob) · scene-YAML = đọc từ Main.unity · runtime-verified (live) = đo trên thiết bị bằng probe đã disasm · UNKNOWN = chưa biết, không được thay bằng số hợp lý.

2.1 Tầng quảng cáo (IAA)

Claim Mức Nguồn
Lớp Admob KHÔNG dùng Google AdMob — nó bọc SDK ADX (adop): AdxRewardedAd, AdxInterstitialAd, AdxBannerAd, ADXConfiguration. Tên lớp là di sản metadata-proven dump.cs:585348-585700
3 định dạng quảng cáo: rewarded · interstitial · banner. Mỗi định dạng có 1 ad-unit id cho Android + 1 cho iOS (6 hằng) + 2 App ID metadata-proven dump.cs:585362-585379, :585392, :585421-585422
10 AD_Type (0..9) = 10 loại phần thưởng của rewarded ad metadata-proven dump.cs:584959; §3
Dispatch phần thưởng nằm trong một coroutine: Admob.Reward_Get()switch(ad_type) → gọi đúng 1 manager disasm-proven <Reward_Get>d__63.MoveNext RVA 0x2A5F0B0
Sau mỗi rewarded ad: AD_Cool = 20.0f (hằng ghi thẳng 0x41A00000), Sound_manager.PlaySfx(7), và chỉ log AppsFlyer khi !Is_Remove_AD_All disasm-proven 0x2A5F0B0 @0x2a5f2d8/0x2a5f30c
AD_Cool đếm ngược bằng Time.deltaTime trong LateUpdate, kẹp ≥ 0 ⇒ 20 s giãn cách giữa 2 rewarded ad disasm-proven Admob.LateUpdate RVA 0x2A58CC8
Bấm nút reward khi AD_Cool > 0UI_manager.Alert("Try in {0} seconds") disasm-proven ShowRewardAd(AD_Type) 0x2A59294; literal 0x5E97D28
Đường tắt entitlement: nếu Shop_manager.Is_Remove_AD_All \|\| FreeCash_manager.RewardAdRemoved thì ShowRewardAd trao thưởng ngay, không hiện quảng cáo disasm-proven 0x2A59294 @0x2a59310-0x2a59334
Shop_manager.Is_RewardBonus_Auto = Is_Remove_AD_All \|\| FreeCash_manager.RewardAdRemoved — cùng vị ngữ, dùng để tắt interstitial và đổi nút "xem QC" thành nút "nhận luôn" disasm-proven 0x2B2D510; Mine_manager.SweepNoAds_Btn_obj dump.cs
5 Admob.Inter_Place: Rebirth=0, Mine=1, Challenge=2, Boss=3, Hunt=4 — khớp 1-1 với 5 field inter_daily_cnt_* của ServerConfig metadata-proven + disasm-proven dump.cs:584976; Inter_DailyLimit 0x2A5CC04
Inter_DailyLimit(place) chuyển thẳng sang getter ServerConfig tương ứng (tail-jump 0x2AC7E64/EC0/F1C/F78/FD4), default 0 disasm-proven 0x2A5CC04
Bộ đếm interstitial nằm ở PlayerPrefs, không phải SaveData: khoá "inter_place_cnt_" + (int)place, tổng "inter_place_cnt_whole", ngày "inter_place_date" (yyyyMMdd) disasm-proven Inter_CountKey 0x2A5C784, Inter_DayCheck 0x2A5C7F0, Inter_CountUp 0x2A5D644; literal 0x5EA3168/70/78
Inter_DayCheck() reset mọi bộ đếm khi PlayerPrefs["inter_place_date"] != today (duyệt Enum.GetValues(Inter_Place)) disasm-proven 0x2A5C7F0
Cổng interstitial (7 điều kiện, theo đúng thứ tự) — xem §4.2 để có pseudocode disasm-proven Inter_Placement_Able 0x2A5CD34
Is_Interstitial_Blocked() = is_Interstitial_Disable \|\| Shop_manager.Is_Remove_AD_forced \|\| Is_Remove_AD_All disasm-proven 0x2A5B4D4
Is_Banner_Blocked() = Is_Remove_AD_forced \|\| Is_Remove_AD_All (banner không có cờ tắt riêng trong Inspector) disasm-proven 0x2A5BC70
Banner chỉ chạy trong trận Boss / Hunt / Raid: ShowBannerAd gọi từ Boss_manager.Enter_Cor, Monster_manager.Enter_Cor, Raid_manager.Enter_Cor; HideBannerAd từ 3 Game_Exit/StopAllGameCoroutines tương ứng disasm-proven (xrefs) xrefs 0x2A5BCF4 / 0x2A5BD58
Banner: BANNER_HEIGHT_DP = 50, BANNER_RETRY_DELAY = 30 s, có bannerPlaceholder RectTransform + Refresh_SafeArea metadata-proven dump.cs:585392-585404
Khi trạng thái IAP đổi, Admob.Handle_InAppPurchaseChanged() huỷ ngay banner / reward-ad đã nạp / interstitial đã nạp nếu bị chặn disasm-proven 0x2A5C2CC
Hằng độ bền quảng cáo: REWARD_RETRY_BASE 15 s, REWARD_RETRY_MAX 120 s, REWARD_AD_EXPIRE_MIN 50 phút, LOAD_HARD_RESET_SEC 60 s metadata-proven dump.cs:585374-585377
Giá trị serialize trong scene: is_AD_Disable_Version 0, is_Interstitial_Disable 0, isTestMode 0, AD_Cool 0, adLoadTimeout 12, interLoadingSeconds 1.2, interLoadMaxAttempts 1, interLoadWaitPerAttempt 3 scene-YAML Main.unity:20647245-20647268 (MonoBehaviour &120003, GO 1737)
Bảng ánh xạ AD_Type → mã log AppsFlyer (m_ads_reward) = [5,6,1,2,3,4,7,8,9,10] disasm-proven (đọc .rodata) mảng 0x109EAE4, dùng tại 0x2A5F0B0

2.2 Tầng IAP

Claim Mức Nguồn
34 product id được TryApplyPurchase xử lý, ánh xạ sang 33 chỉ số offer (1..30, 36..38; vip_passvip_pass_2 dùng chung chỉ số 12; 31..35 bỏ trống) disasm-proven ItemIdxFromProductId RVA 0x2B2DE44 — bảng đầy đủ ở §6.1
Backend IAP = Unity StoreController (Unity IAP mới), có xác thực receipt phía server qua 2 Cloud Function (verifyInAppPurchaseApplePBV5 / verifyInAppPurchaseGoogleYooSu) metadata-proven dump.cs:600320-600321; ValidateAndComplete 0x2B33DAC
Chống trùng giao dịch: _inflightTxns (HashSet), ProcessedTxnIds (List, ProcessedTxnIdsMax = 50), MarkProcessed/ReleaseInflight metadata-proven + disasm-proven dump.cs:600334-600337; 0x2B33B94 / 0x2B33D38
6 non-consumable là cờ static của Shop_manager: Is_Remove_AD_forced@0x8, Is_Remove_AD_All@0x9, Is_AutoUpgrade_Pack@0xA, Is_Buff_Pack@0xB, Is_Dia_Pack@0xC, Is_Mine_Pack@0xD metadata-proven dump.cs:600306-600311
ApplyNonConsumable trao: autoupgrade_pack+500 dia; buff_pack+1000 dia; dia_pack+16000 dia; mine_pack+500 dia + reload Mine_Start_UI; remove_forced_ads/remove_all_adskhông dia, nhưng DiaPig.Init() nếu đây là cờ đầu tiên disasm-proven 0x2B311EC
Shop_manager.DiaRewards = [240, 600, 1500, 3500, 8000] cho dia_0..dia_4 metadata-proven (field-default-value blob) <PrivateImplementationDetails>.F68B1971…AD97D2 trong global-metadata.decrypted.dat
Khuyến mãi mua-lần-đầu = DỊCH TRÁI 1 BIT: amount = DiaRewards[i] << (Is_Dia_FirstBuy(i) ? 1 : 0)đúng gấp đôi, một lần cho mỗi bậc, cờ lưu trong Dia_FirstBuy_Used[], có DiaFirstBuy_Season_Current = 1 (mùa) disasm-proven ApplyDiaReward 0x2B32D90 @0x2b32e94; dump.cs:600315-600318
Shop_manager.Core_Amount_return = [100, 500, 1050, 3300, 750, 1750, 6000] cho 7 InApp_Core_Type; "Core" được trao qua Adventure_manager.Core_Get(Core_Type.<3>, amount) disasm-proven (đọc .rodata 0x109EB1C) 0x2B30018, ApplyCoreReward 0x2B304BC
3 gói core có nhịp thời gian: _daily_750/_weekly_1750/_weekly_6000 ghi LastDailyCorePurchase_dateTime / LastWeekly… (static 0x10/0x18/0x20) ⇒ mua lại theo chu kỳ (Is_Core_Special_Available, Core_Special_Remain_Duration) disasm-proven + metadata-proven 0x2B304BC; dump.cs:600312-600314; 0x2B24F9C / 0x2B25348
vip_pass + vip_pass_2ApplyVipPass(): Mission_manager.is_Vip = true, +500 dia disasm-proven 0x2B32C50
catgunner_luxury_pass catgunner_luxury_upgrade → thân hàm giống hệt nhau: is_Vip = is_Luxury = true (ghi word 257), +1000 dia, Mission_manager.Exp_Get(300) = 3 cấp battle-pass (Exp_balance = 100, Max_Lv = 25) disasm-proven 0x2B32AA4 / 0x2B328F8; dump.cs:587346-587350
catgunner_rebirth_singleApplyRebirthSingle()Rebirth_manager.UpgradeRebirth_Start_By_IAP(); nếu Rebirth_manager null thì hoãn, thử lại lần chạy sau (không mất hàng) disasm-proven 0x2B3280C; literal 0x5E9AAD8
StepUp.Gun_Rewards = [23, 28, 33, 39, 44], StepUp.Dia_Rewards = [0, 200, 400, 600, 1000] (5 bậc) metadata-proven (blob) <PrivateImplementationDetails>.EA64A4C1… / .AA838700…
DailyGun.MaxCounts = [2, 2, 1], PetCoin_Rewards = [300, 700, 1500], GunNum_Base = [20, 30, 35], WeekEpoch = 2024-01-01 metadata-proven (blob + cctor) DailyGun.cctor 0x2B26894
is_DiaPig_Unlock() = Is_Remove_AD_All \|\| Is_Remove_AD_forced; DiaPig_TickAmount() = (Is_Remove_AD_forced?1:0) + (Is_Remove_AD_All?1:0)heo kim cương là phần thưởng đi kèm của gói xoá quảng cáo, tick 1 hoặc 2 dia mỗi 60 s disasm-proven 0x2B27764 / 0x2B2823C; TICK_SECONDS = 60
InappCntForServer() = max(PurchaseCount, PurchaseHistory.Count), nếu 0 thì HasEverPurchased()số lần mua được gửi lên server phân khúc quảng cáo disasm-proven 0x2B36528; ConfigReq.inapp_cnt
place (chuỗi) đi kèm mọi lệnh mua để log AppsFlyer m_offer_page: store, popup, hud, banner, banner_reward, banner_enter, banner_weapon, auto_weapon/auto_pet/auto_fish disasm-proven Purchase* 0x2B2F618/0x2B2FF0C/0x2B2FF68/0x2B2FFC0; literal 0x5E9E7E8/F0/F8/800
vip_pass_2 không phải sản phẩm Play — được TryApplyPurchase/ItemIdxFromProductId xử lý nhưng không có trong 33 id store trả giá ⇒ 34 id trong code, 33 id đang bán negative evidence (live) iap_prices_store.json
remove_forced_ads 79.000đ là SKU rẻ nhất; dia_4dia_pack cùng 1.300.000đ là đắt nhất; các bậc Core (26k/105k/210k/631k) và StepUp (26k/130k/261k/523k/1.05M) là thang hình học ≈ ×2 derived (từ 33 giá đo) iap_prices_store.json
ĐÍNH CHÍNH LIVEOPS_SURFACE.md §6.1: mine_ticket, fish_free, banner_enter, banner_reward, banner_weapon, sp_iap, reward_hub, f_cash KHÔNG phải product id — chúng là chuỗi place/khoá analytics/tên màn Almedia disasm-proven (xrefs literal) Mine_manager.Ticket_Reload, Fish_manager.FreeGacha_Reload, PlusPack_Inapp_item.OnClick, AppsFlyerInit.AddSpecValues, AlmediaScreenExtensions, AppsFlyerInit.SendLog_Launch

2.3 Phân khúc (segment) — công tắc tổng của IAA

Bản giải thích dân dã đầy đủ: docs/functions/segment-lieu-ops.md — segment là gì bằng ví dụ đời thường, vòng đời một người chơi (cài → attribution → cấu hình → placement → đo → chọn cấu hình thắng), phân biệt ba tầng segment / ServerConfig / placement, danh sách cạm bẫy khi tự dựng, và mục "Nếu bạn muốn tự làm" 13 bước. Viết cho người không đọc disasm; mọi số và RVA ở đó lấy từ mục này.

ĐÍNH CHÍNH (QD-28, 2026-09-06): ba nút Seg 0/1/2 của panel debug gốc KHÔNG phải R1-outdocs/functions/cheat-panel.md từng xếp nhầm chúng vào cột "sẽ không dựng". AppsFlyerInit.Cheat_SetSegment (0x2A600CC) không gọi hàm SDK AppsFlyer nào: nó đặt Segment (static 0x8) + SegmentCheatOverride (0x21), ghi PlayerPrefs["af_segment"] / ["af_segment_cheat"], đổi nhãn segmentText, rồi gọi ServerConfig_manager.Cheat_OnSegmentChanged() (0x2AC96D0 — phát OnConfigApplied + SaveSystem.Save). Đã dựng 1:1, không lấn R1. Cái thật sự R1-out là đường AppsFlyer gán segment từ mạng (OnUserSegmentReceivedApplyServerSegment), xem §9.3.

Claim Mức Nguồn
ServerConfig_manager.ConfigRespđúng 15 field, 12 trong đó là số điều khiển quảng cáo. Không có field kinh tế nào metadata-proven + runtime-verified (live) dump.cs:589438; LIVEOPS_SURFACE.md §2.2; payload production ở PROBE_RESULT.md §1.1
EmbeddedDefaults(segment) (0x2AC7B20): segment 1 = [freecash_stage_min 15, freecash_stage_max_hud 35, offerwall_stage_min 10, inter_stage_min 12, inter_cooltime 0, rebirth 6, mine 2, challenge 0, boss 0, hunt 0, whole 99999, weapon_02 1]; segment ≠ 1 = 4 ngưỡng stage 99999, còn lại 0tắt sạch disasm-proven (.rodata) LIVEOPS_SURFACE.md §2.3; chứng minh offset ở §2.3a
Cả hai nhánh của EmbeddedDefaults đều đặt rule_version = "0" (literal 0x5E82F08) và reason = "client_embedded_default" (literal 0x5E9F3E0) ⇒ nhìn reason là biết config đến từ client hay server disasm-proven 0x2AC7B20
Server phân khúc theo: bundle_id, segment, uuid, country_code, platform, version, campaign, media_source, install_date, inapp_cnt, reward_ad_cnt, test, app_version (13 field ConfigReq) ⇒ nguồn cài đặt (AppsFlyer) + quốc gia + lịch sử chi tiêu quyết định bạn thấy bao nhiêu quảng cáo metadata-proven dump.cs:589413; LIVEOPS_SURFACE.md §2.1
AppsFlyerInit.Cheat_SetSegment(seg) ghi AppsFlyerInit.Segment (static 0x8), bật SegmentCheatOverride (0x21), lưu PlayerPrefs["af_segment"]/["af_segment_cheat"], rồi gọi ServerConfig_manager.Cheat_OnSegmentChanged() disasm-proven 0x2A600CC; dump.cs:585772-585783

2.3a Chứng minh ánh xạ 12 số → 12 field (đọc theo OFFSET, không theo thứ tự tên)

Đây là chỗ dễ sai nhất của cả tài liệu, nên ghi lại nguyên văn phép ghi bộ nhớ. EmbeddedDefaults(1) (0x2AC7B20) ghi ba khối 16 byte vào object ConfigResp:

*(_OWORD *)(obj + 40) = xmmword_105B0E0;   // 0x28 .. 0x37
*(_OWORD *)(obj + 56) = xmmword_105D5A0;   // 0x38 .. 0x47
*(_OWORD *)(obj + 72) = xmmword_105B890;   // 0x48 .. 0x57

Giá trị đọc từ .rodata (IDA get_dword): 105B0E0 = [15, 35, 10, 12] · 105D5A0 = [0, 6, 2, 0] · 105B890 = [0, 0, 99999, 1]

Ghép với offset field của ConfigResp (dump.cs:589438):

Offset Field Khối Giá trị segment 1
0x28 freecash_stage_min 105B0E0[0] 15
0x2C freecash_stage_max_hud 105B0E0[1] 35
0x30 offerwall_stage_min 105B0E0[2] 10
0x34 inter_stage_min 105B0E0[3] 12
0x38 inter_cooltime 105D5A0[0] 0
0x3C inter_daily_cnt_rebirth 105D5A0[1] 6
0x40 inter_daily_cnt_mine 105D5A0[2] 2
0x44 inter_daily_cnt_challenge 105D5A0[3] 0
0x48 inter_daily_cnt_boss 105B890[0] 0
0x4C inter_daily_cnt_hunt 105B890[1] 0
0x50 inter_daily_cnt_whole 105B890[2] 99999
0x54 weapon_02_ad_onoff 105B890[3] 1

Nhánh segment != 1 ghi vdupq_n_s32(0x1869F) = 99999 vào cả 4 ô của khối đầu và 0 vào 8 ô còn lại; segment = 2 nếu tham số là 2, ngược lại 0.

interstitial ở 12, FreeCash bật ở 15 và ẩn ở 36. Nếu ánh xạ bị lệch một nhịp (offerwall ← 35, inter ← 15) thì OfferWall phải lật ở 35 và interstitial ở 15 — đo được là không. Hai kênh độc lập (byte trong .rodata + hành vi trên thiết bị) cùng chỉ về một bảng.

Claim Mức Nguồn
Mô tả tiếng Việt của buff_pack xác nhận IsPermanentBuff áp cho CẢ hai buff Power và Money runtime-verified (live) phaseA/01_shop.png; đối chiếu IsBuffActive 0x2AE4AF8
Claim Mức Nguồn

3. Danh mục AD_Type — 10 giá trị và mỗi giá trị mở cái gì

public enum AD_Typedump.cs:584959. Dispatch: Admob.Reward_Get() 0x2A5F0B0.

# AD_Type Điểm gọi ShowRewardAd (RVA) Hàm nhận thưởng khi xem xong (RVA) Phần thưởng Mã AppsFlyer
0 Money_Buff Buff_Panel.AD_Watch 0x2AE4F38 Buff_manager.AddBuff(0) 0x2AE4510 moneyBuffTime = min(+300, 900) + Gamemanager.InvalidateMoneyCache() 5
1 Power_Buff Buff_Panel.AD_Watch 0x2AE4F38 Buff_manager.AddBuff(1) 0x2AE4510 powerBuffTime = min(+300, 900) 6
2 SpeedRun_Bonus SpeedRun_manager.Bonus_AD_Show 0x2B40E08, SweepBonus_AD_Show 0x2B41A04 SpeedRun_manager.Bonus_Get thưởng bonus của chế độ Play/Challenge 1
3 Mine_Bonus Mine_manager.Bonus_AD_Show 0x2AF562C, SweepBonus_AD_Show 0x2AF5B28 Mine_manager.Bonus_Get thưởng bonus mỏ 2
4 Boss_Bonus Boss_manager.Bonus_AD_Show 0x2AF21A8, SweepBonus_AD_Show 0x2AF2D94 Boss_manager.Bonus_Get thưởng bonus boss 3
5 Monster_Bonus Monster_manager.Bonus_AD_Show 0x2AFB094, SweepBonus_AD_Show 0x2AFAEC4 Monster_manager.Bonus_Get thưởng bonus Hunt (PetCoin) 4
6 Mission_Reward Mission_manager.Reward_Get 0x2A71A34 Mission_manager.Reward_Get_By_Ad nhận thưởng mission (kiểm FindFirstEmptyOwned != -1 trước) 7
7 Fish_Gacha Fish_manager.FreeGacha 0x2B00AB4 Fish_manager.FreeGacha_RewardGet 1 lượt gacha cá miễn phí 8
8 Upgrade_Rebirth Rebirth_manager.UpgradeRebirth_Start_By_IAP 0x2B23454 (tail @0x2b23668) Rebirth_manager.UpgradeRebirth_Start_By_Ad 0x2B233D0Rebirth_FX.Start_Internal bật is_UpgradeRebirththưởng Rebirth ×3 9
9 AD_Reward_Gun Gun_manager.AdRewardGun_TryWatch 0x2B0C484 Gun_manager.AdRewardGun_Acquire 0x2B0C508 giữ vĩnh viễn khẩu súng thưởng đang đếm ngược 10

Ghi chú quan trọng: IsBuffActive(type) trả true cho mọi type khi IsPermanentBuff — nghĩa là buff_pack bật vĩnh viễn cả hai buff, không phải một (0x2AE4AF8). GetBuffRemainingTime trả -1 khi vĩnh viễn (0x2AE4B4C) → Buff_Panel.Update (0x2AE4D4C) hiểu -1 là "ẩn nút xem QC, fill = 1.0".


4. Bề mặt IAA — 6 loại placement, 28 điểm kích hoạt quảng cáo trong code

|---|---|---|---| | Rewarded | 13 điểm gọi ShowRewardAd(AD_Type) (+1 overload ShowRewardAd(int) nối từ scene) | AD_Cool, _isShowing, is_AD_Disable_Version, mạng | Chạy (rewarded KHÔNG bị segment tắt) | Chạy | | Interstitial | 9 (5 Inter_Place) | 7 điều kiện §4.2 | Tắt (inter_stage_min = 99999) | Đủ tư cách chỉ Rebirth (6/ngày) + Mine (2/ngày) từ Stage 12 | | Banner | 3 show / 3 hide | Is_Banner_Blocked | Chạy khi vào Boss/Hunt/Raid | như segment 0 (không bị ServerConfig gate) | | OfferWall (Tapjoy) | 2 (Open_Hud, Open_Shop) | offerwall_stage_min | Tắt (99999) | BẬT từ Stage 10, vĩnh viễn | | FreeCash (AlmediaLink) | 1 panel + HUD + banner shop | freecash_stage_min + trạng thái Almedia | Tắt (99999) | Cửa sổ Stage 15 → 35, ngoài khoảng thì ẩn | | Ad-gun weapon_02 | (dùng lại 1 trong 13 điểm rewarded; CheckAdGunEvent là trigger cấp súng thử, không phải điểm hiện quảng cáo) | weapon_02_ad_onoff | Tắt (0) | BẬT (1) |

4.1 Rewarded — Admob.ShowRewardAd(AD_Type) 0x2A59294

ShowRewardAd(AD_Type type):
    if (Shop_manager.instance == null) -> NRE
    // ĐƯỜNG TẮT ENTITLEMENT (không có quảng cáo nào được hiện)
    if (Shop_manager.Is_Remove_AD_All || FreeCash_manager.RewardAdRemoved):
        ad_type = type; StartCoroutine(Reward_Get()); return       // @0x2a59310..0x2a59334
    if (is_AD_Disable_Version)            -> Alert("Try Later"); return
    if (_pendingShow || _isShowing)       -> Alert("Try Later"); return
    if (AD_Cool > 0)                      -> Alert("Try in {0} seconds", (int)AD_Cool); return
    ad_type = type
    if (rewardAd != null && rewardAd.IsLoaded() && Is_LoadedAdExpired())   // > 50 phút
        Log("[ADX] Loaded rewarded ad expired -> discard & reload"); Discard_RewardAd()
    if (rewardAd != null && rewardAd.IsLoaded())  -> TryShowLoadedAd()
    else if (!_sdkReady)                          -> Alert("Try Later")
    else if (Application.internetReachability == NotReachable) -> Alert("Check Network Connection")
    else { _pendingShow = true; ShowAdLoadingPanel(); StartLoadTimeout(); LoadRewardedAd(); }

Reward_Get() (0x2A5F0B0) — chạy sau khi SDK báo earned:

yield return null                                   // 1 frame
AD_Cool = 20.0f                                     // giãn cách toàn cục
Sound_manager.PlaySfx(7)
if (!Shop_manager.Is_Remove_AD_All)
    AppsFlyerInit.SendLog_AdsReward(AF_CODE[(int)ad_type])   // [5,6,1,2,3,4,7,8,9,10]
switch ((int)ad_type) { 0,1 -> Buff_manager.AddBuff(ad_type); 2 -> SpeedRun.Bonus_Get(); ... }

Bài học thiết kế: phần thưởng KHÔNG được trao trong callback của SDK mà trong một coroutine của game — nên đường "đã mua xoá quảng cáo" tái dùng đúng cùng một đường trao thưởng. Một điểm trao thưởng duy nhất = một điểm để kiểm toán. (Trong bản restore, đường này nằm sau IAdService fail-closed: NullAdService.ShowRewarded() trả failure, coroutine trao thưởng không chạy.)

4.1a Nhánh Granted — SỐ ĐO THẬT đầu tiên trên thiết bị (F024, 2026-09-06)

Cho tới 06-09, mọi thứ trong §4.1 là đọc từ binary: bản dựng lại đi qua IAdService, và NullAdService (fail-closed theo R1) luôn trả NotAvailable, nên không một nhánh nhận thưởng nào từng chạy trên máy. F024 (QD-23, và R1a trong quy tắc dự án (nội bộ)) thêm một provider quân xanh (SimulatedAdService, chỉ có dưới CATGUNNER_LEARNING_DEBUG, chỉ bind khi bật công tắc) để chạy được nhánh đó và đo. Quân xanh KHÔNG chứng minh luồng quảng cáo đã restore 1:1 — cái được nghiệm thu 1:1 là phía game xử lý callback.

Placement Mine_Bonus (AD_Type 3) — đo trên Pixel 8 Pro, APK f024-sim:

Bước Điều kiện / thời điểm Số đo (đọc từ save, không đọc bằng mắt)
Vào Mỏ cần 1 vé (TicketUse_Count < 2); ván dài 60 s vé 2/2 → 1/2
Panel Game End mở Reward_Panel_Reload 0x2AF5404 vừa vẽ vừa PHÁT Reward_Value Dia 980 → 1078 (+98 = Reward_Value)
Panel quảng cáo nhãn Phần thưởng = 4 × Reward_Value hiện 392 (= 4×98) ✔
Bấm Nhận ×4 → xem hết → Granted Bonus_Get 0x2AF5634 cộng (vì panel đã phát 1× lúc mở) Dia 1078 → 1372 (+294 = 3×98) ✔
Ngay sau khi cấp Bonus_Get gọi thẳng Game_Exit tự thoát về HUD chính
Đối chứng: bấm Bỏ quaDenied quân xanh trả Denied Dia 1468 → 1468, panel giữ nguyên, 0 exception ✔

⇒ Xác nhận bằng số đo cái mà trước đây chỉ suy từ binary: tổng tiền của một ván Mỏ có xem quảng cáo đúng bằng 4 × Reward_Value, và hệ số của riêng nhánh quảng cáo là , không phải . Sai lầm dễ mắc khi chỉ đọc UI: nhãn ghi ×4 nhưng hàm chỉ cộng .

Trigger phụ đo được cùng lượt — interstitial khi thoát Mỏ. Mine_manager.Game_Exit bắn interstitial chỉ khi _bonusAdWatched == false (cờ +0x72, do Bonus_Get bật). Đo: - thoát bằng Không, cảm ơn (chưa xem thưởng) → interstitial; - thoát bằng đường Bonus_Get (đã xem thưởng) → không interstitial.

Đây là một mẫu thiết kế đáng chép: người chơi vừa xem một rewarded thì được miễn interstitial ngay sau đó — tránh dí hai quảng cáo liên tiếp vào đúng người vừa hợp tác.

Trạng thái đo của 10 placement. Chỉ 6/10 đo được ngay; 4 cái còn lại không phải giới hạn R1 mà là nợ restore của bản dựng lại (chỗ gọi ShowRewarded chưa có thân):

AD_Type Đo được? Vì sao
Mine_Bonus ĐÃ ĐO (bảng trên)
SpeedRun_Bonus, Boss_Bonus, Monster_Bonus, Mission_Reward, Upgrade_Rebirth đo được, chưa đo chỗ gọi đã dựng, cần dựng điều kiện màn
Money_Buff, Power_Buff chưa Buff_manager.AddBuff còn rỗng
Fish_Gacha ĐÃ ĐO (§4.1b) F008 T5b, 2026-09-06 — Fish_manager.{FreeGacha, FreeGacha_RewardGet, PremiumGacha} đã dựng và chạy trọn nhánh Granted trên máy
AD_Reward_Gun chưa Gun_manager.{AdRewardGun_TryWatch, AdRewardGun_Acquire} rỗng

Chi tiết bộ giả lập + cổng an toàn (bản identity không chứa một byte nào của nó): docs/functions/gia-lap-quang-cao-iap.md.

4.1b Mẫu "quota ngày có phần cho không + phần gắn quảng cáo" — placement Fish_Gacha (F008 T5b)

Đây là kiểu placement thứ hai của game, khác hẳn §4.1/§4.1a. Mine_Bonusnhân phần thưởng vừa nhận (một lần, ở cuối một ván). Fish_Gachamột quota ngày có hai đoạn: đoạn đầu cho không, đoạn sau gắn quảng cáo. Đo trọn từng nấc trên Pixel 8 Pro, APK f008-t5b2, 0 exception.

FreeGacha():                                      // Fish_manager 0x2B00A24 — CHỈ định tuyến
    FreeGacha_Reload()                            // nạp lại quota nếu sang ngày mới — doc 07 §3.7
    if (Inventory.Count >= MAX_FISH)  -> Alert(MaxFish);      return    // trần kho chặn TRƯỚC
    if (FreeGachaCount >= 5)          -> Alert(MaxFreeGacha); return    // hết quota
    if (FreeGachaCount >= 3)          -> ShowRewarded(Fish_Gacha); return
    FreeGacha_RewardGet()                                              // cấp NGAY, không quảng cáo
FreeGachaCount Bấm nút Nhãn x{5−n} Biểu tượng quảng cáo
0, 1, 2 thưởng ngay, không quảng cáo x5 → x4 → x3 tắt
3, 4 phải xem rewarded rồi mới nhận x2 → x1 bật
≥ 5 Alert(MaxFreeGacha) x0, nút sang màu disable tắt lại

Đo được: ba lượt đầu ra thưởng ngay (x5 → x4 → x2, chỉ số cộng đúng bảng); lượt thứ tư hiện biểu tượng và đi nhánh rewarded, Granted → thưởng vào kho, nhãn x1; sau lượt thứ năm nhãn x0, biểu tượng biến mất, nút đổi màu disable. Save đối chứng: FreeGachaCount 3 → 5.

Ba quy tắc rút ra:

  1. Phần cho không phải đứng TRƯỚC phần gắn quảng cáo, không được trộn lẫn. Ba lượt đầu là dạy giá trị: người chơi thấy phần thưởng rơi ra và thấy chỉ số nhích lên ngay trên cùng màn hình. Tới lượt thứ tư, câu hỏi "xem quảng cáo chứ?" mới là câu hỏi có giá đã niêm yết. Gắn quảng cáo từ lượt đầu biến bề mặt thành máy bán hàng cho một món chưa ai biết giá. Ngoài ra, phần cho không giữ lý do quay lại hằng ngày cho cả nhóm người không bao giờ xem quảng cáo — bề mặt IAA không đuổi ai đi. Và biên trên (5) giữ cho nó không thành máy cày.
  2. Đặt cổng đếm bằng HAI hằng số trên MỘT bộ đếm, không bằng hai bộ đếm: FreeGacha_MaxCount = 5FreeGacha_NoAdCount = 3 cùng đọc FreeGachaCount. Đổi tỉ lệ cho-không/gắn-quảng-cáo chỉ là đổi một số; không có trạng thái thứ hai để lệch pha với save.
  3. Hiển thị biểu tượng quảng cáo theo DẢI, không theo NGƯỠNG. csharp AD_Draw_Icon_obj.SetActive((uint)(FreeGachaCount - 3) < 2u); // count ∈ {3,4} Một phép trừ + một so sánh unsigned bắt trọn cả hai biên. Viết count >= 3 thì biểu tượng dính lại vĩnh viễn sau khi hết quota và người chơi bấm vào một nút hứa quảng cáo để nhận về một thông báo lỗi. Tổng quát: visible = (uint)(used − free) < adSlots.

Cùng bề mặt còn có một nút trả tiền, và nó KHÔNG mua tỉ lệ tốt hơn — nó mua một SÀN cao hơn. Cả hai nút dùng chung một bảng tỉ lệ cộng dồn — {60, 30, 10, 0} đọc từ scene (Main.unity:20930271); nút miễn phí trả baseGrade + i với baseGrade = 0, nút 200 Dia trả baseGrade = 1. Vì mỗi bậc mạnh gấp ~3, dịch một bậc đã là gấp ba — không cần bảng tỉ lệ thứ hai. Ưu điểm kiến trúc: một bảng duy nhất phục vụ mọi nút, thêm một hạng nút chỉ là Roll(base + 1), không có hai bảng để lệch nhau. (Đo: Dia 1368 → 1168.)

Nút baseGrade Phân phối
miễn phí 0 hạng 0 (60%) · 1 (30%) · 2 (10%) · ~~3 (0%)~~
200 Dia 1 hạng 1 (60%) · 2 (30%) · 3 (10%) · ~~4 (0%)~~

Hai điều đáng đọc trong bảng này. (a) Phân phối lệch mạnh về bậc nền và đuôi bị cắt cụt — gacha ở đây là máy rót nguyên liệu, không phải máy tạo bất ngờ; toàn bộ cảm giác tiến bộ được đẩy sang mạch hợp nhất. (b) Ô thứ tư giữ chỗ nhưng để 0: bậc thưởng cao nhất có sẵn slot, bật lại chỉ là đổi một số trong scene, không phải sửa code — một mẹo vận hành rẻ cho live-ops.

⚠️ BẪY ĐÃ LÀM SAI MỘT LẦN Ở CHÍNH TÀI LIỆU NÀY (G1682). GachaChances là field publicđược serialize ⇒ giá trị trong Main.unity thắng lúc chạy. Ctor 0x2B01D08 ghi {40, 30, 20, 10}; bản đầu của §4.1b chép con số đó và publish ra dashboard. Bảng thật là {60, 30, 10, 0}. Cái bắt được lỗi là một quan sát trên máy: năm lượt Epic liên tiếp đều ra hạng thấp nhất — 0,6⁵ ≈ 7,8% (bình thường) so với 0,4⁵ ≈ 1% (đáng nghi). Quy tắc: với mọi bảng tỉ lệ / bảng giá / bảng cân bằng, kiểm loại field trước khi tin con số — xem doc 07 §4 mục 16. Ctor vẫn phải chép vào code (initializer là nợ 1:1 thật), nhưng viết con số của ctor vào tài liệu như cân bằng của game là sai. Hai việc khác nhau.

Thứ tự cổng có ý nghĩa đạo đức, không chỉ kỹ thuật: nhánh trả tiền kiểm kho đầy TRƯỚC khi kiểm tiền, nên kho đầy thì không bị trừ Dia. Đảo lại là ăn cắp.

Bẫy dựng lại (G1681): chép thẳng Admob.instance.ShowRewardAd(...) từ binary nổ NRE ở đúng lượt thứ tưAdmob là lớp R1-out (instance không bao giờ được gán). Mọi lời gọi tới lớp R1-out phải đi qua ServiceLocator.Ads.ShowRewarded(placementId, cb) và cấp thưởng trong nhánh Granted (§9.3). Cổng kiểm cho mọi lô mới: grep Admob.instance|IAP_manager.instance| Tapjoy_manager.instance trong file vừa dựng phải rỗng.

Chi tiết đầy đủ của hệ này: docs/functions/ca-fish.md.


4.2 Interstitial — chuỗi cổng đầy đủ (Inter_Placement_Able 0x2A5CD34)

bool Inter_Placement_Able(Inter_Place place):
 1. if (is_AD_Disable_Version)                                   -> false   // @0x2a5ce5c
 2. if (Is_Interstitial_Blocked())                               -> false   // @0x2a5ce98
        = is_Interstitial_Disable || Is_Remove_AD_forced || Is_Remove_AD_All
 3. if (_interShowing || _interPlacementRunning)                 -> false   // @0x2a5cf34
 4. stageDisplay = Tree_manager.instance != null
                   ? Tree_manager.instance.Stage_Value_Max / 10 + 1 : 0     // @0x2a5d080
    if (stageDisplay < ServerConfig.InterStageMin)               -> false   // @0x2a5d0b0
 5. cool = ServerConfig.InterCooltime
    if (cool >= 1 && _lastInterShownUtc != DateTime.MinValue
        && (UtcNow - _lastInterShownUtc).TotalSeconds < cool)    -> false   // @0x2a5d1d4
 6. Inter_DayCheck()                                                        // @0x2a5d270
    cnt = PlayerPrefs.GetInt("inter_place_cnt_" + (int)place)
    if (cnt >= Inter_DailyLimit(place))                          -> false   // @0x2a5d29c
 7. whole = PlayerPrefs.GetInt("inter_place_cnt_whole")
    if (whole >= ServerConfig.InterDailyCntWhole)                -> false   // @0x2a5d2e0
    return true

ShowPlacementInterstitial(place, onClosed) (0x2A5D508) → nếu không able thì gọi thẳng onClosed() (không bao giờ chặn luồng game). Nếu able → PlacementInterCo (0x2A5EADC):

_interPlacementRunning = true; interLoadingPanel.SetActive(true)
yield WaitForSecondsRealtime(interLoadingSeconds)          // scene: 1.2 s
for (attempt = 0; attempt < interLoadMaxAttempts; attempt++) {   // scene: 1
    if (IsInterstitialReady) break
    if (interstitialAd.IsLoaded() && Is_InterExpired()) Discard_InterAd()
    LoadInterstitialAd()
    t = 0; while (t < interLoadWaitPerAttempt) {                 // scene: 3 s
        if (IsInterstitialReady) break
        if (_interLoading) { t += unscaledDeltaTime; yield null } else yield WaitRealtime(0.5) }
}
interLoadingPanel.SetActive(false); _interPlacementRunning = false
if (IsInterstitialReady) { ShowInterstitialAd(onClosed); if (_interShowing) Inter_CountUp(place) }
else { LogWarning("[ADX] Placement 전면광고 로드 실패({0}회) → 스킵 — {1}"); onClosed?.Invoke(); LoadInterstitialAd() }

Inter_CountUp(place) (0x2A5D644): DayCheck+1 cho khoá của place → +1 cho wholePlayerPrefs.Save()_lastInterShownUtc = UtcNow → log.

Stage_Value_Max MaxStageDisplay OfferWall_Btn FreeCash_Btn Interstitial đủ tư cách Ngưỡng chạm
0 – 80 1 – 9
90 10 ✓ BẬT offerwall_stage_min = 10
100 11
110 12 Rebirth ✓ · Mine ✓ inter_stage_min = 12
130 14 Rebirth · Mine
140 15 ✓ BẬT Rebirth · Mine freecash_stage_min = 15
150 – 340 16 – 35 Rebirth · Mine
349 35 ✓ (cạnh cuối) Rebirth · Mine freecash_stage_max_hud = 35
350 36 ✗ ẨN LẠI Rebirth · Mine vượt max_hud
400 · 990 41 · 100 Rebirth · Mine

is_AD_Disable_Version = 0 · is_Interstitial_Disable = 0 · Is_Interstitial_Blocked = Is_Remove_AD_All || Is_Remove_AD_forced = 0 · reentrancy = 0 — chỉ duy nhất cổng stage (gate 4) đổi trạng thái theo tiến trình. Challenge / Boss / Hunt không xuất hiện ở bất kỳ hàng nào — chúng bị chặn thuần tuý bởi trần/ngày = 0 (gate 6), không phải bởi cổng stage. Ở segment 1, toàn bộ ngân sách interstitial nằm ở Rebirth (6/ngày) + Mine (2/ngày): đúng hai khoảnh khắc "vừa nhận một khoản thưởng lớn", nơi người chơi ít khó chịu nhất.

BIÊN QUAN SÁT (đừng đọc quá): phiên đo chỉ chứng minh ĐIỀU KIỆN ĐỦ TƯ CÁCH (Inter_Placement_Able trả true/false). IsInterstitialReady = 0 suốt phiên vì mediation không có fill trên bản .sot sideload ⇒ không quảng cáo nào được hiện, Inter_CountUp chưa bao giờ chạy, và việc bộ đếm/cooldown có được thực thi lúc hiện hay không vẫn UNKNOWN

9 điểm kích hoạt (tất cả đều là "đóng màn kết quả", không bao giờ giữa trận):

Inter_Place Điểm gọi (RVA) Điều kiện bổ sung tại điểm gọi
Rebirth (0) Rebirth_manager.Rebirth @0x2b22dfc !(rebirthWasBoosted \|\| reviewPopupOpened) — xem §7.4
Mine (1) Mine_manager.Sweep_Close @0x2af5c1c !_bonusAdWatched && !Shop_manager.Is_RewardBonus_Auto
Mine (1) Mine_manager.<Exit_Cor>d__38 @0x2af6514 _clearedThisRun && !_bonusAdWatched
Challenge (2) SpeedRun_manager.Sweep_Close @0x2b41af8 tương tự Mine
Challenge (2) SpeedRun_manager.<Exit_Cor>d__28 @0x2b42650 tương tự Mine
Boss (3) Boss_manager.Sweep_Close @0x2af2e88 tương tự Mine
Boss (3) Boss_manager.<Exit_Cor>d__32 @0x2af3308 tương tự Mine
Hunt (4) Monster_manager.SweepBonus_Get @0x2afb030 nhánh bị chặn: hàm đặt cờ +194 = 1 rồi mới kiểm !cờthực tế không bao giờ chạy
Hunt (4) Monster_manager.<Exit_Cor>d__46 @0x2afba4c tương tự Mine

Luật vàng nhìn thấy được: nếu người chơi vừa xem một rewarded ad (_bonusAdWatched), hoặc đã mua/được miễn quảng cáo (Is_RewardBonus_Auto), thì KHÔNG chèn interstitial. Game không bao giờ "phạt" người vừa tự nguyện xem quảng cáo.

4.3 Banner

  • Bật: Admob.ShowBannerAd() 0x2A5BCF4Boss_manager.<Enter_Cor>d__27, Monster_manager.<Enter_Cor>d__36, Raid_manager.<Enter_Cor>d__29.
  • Tắt: HideBannerAd() 0x2A5BD58Boss_manager.Game_Exit, Monster_manager.StopAllGameCoroutines, Raid_manager.Game_Exit, và Handle_InAppPurchaseChanged.
  • Chặn: Is_Banner_Blocked() = Is_Remove_AD_forced || Is_Remove_AD_All (0x2A5BC70).
  • Update_BannerPlaceholder() + Refresh_SafeArea() đẩy layout lên 50 dp để banner không che UI.
  • Banner chỉ tồn tại trong 3 chế độ trận đấu có thời lượng cố định, không có ở màn idle chính. Raid_manager xuất hiện ở đây là bằng chứng độc lập rằng Raid là nội dung đã code nhưng chưa mở (khớp UNLOCK_GATES §3 "Raid — KHÔNG có cổng nào").

4.4 OfferWall (Tapjoy, placement offerwall_aos)

(khối mã nội bộ đã lược bỏ)

  • Phần thưởng: kim cương, qua currency của Tapjoy → OnEarnedCurrencyGrantGem(amount) (0x2B2AB4C).
  • Chống mất thưởng: nếu save chưa nạp, ghi PlayerPrefs["ow_pending_gem"] = amount và trao ở lần chạy sau (RecoverPendingGem 0x2B29740). Timeout claim ClaimTimeoutSec = 20.
  • 2 nơi mở: Open_Hud() (place hud) và Open_Shop() (0x2B2A0F4/0x2B2A13C).
  • 3 object UI theo event: OfferWall_Hud_obj, Event_Hud_obj, Event_Shop_obj.
  • Đo sống (segment 1): OfferWall_Btn bật ở MaxStageDisplay = 10giữ bật vĩnh viễn tới

(khối mã nội bộ đã lược bỏ)

vẫn không làm nút hiện; chỉ khi ghi off_5E27948 + 0x38 = 1 nó mới bật. Mặc định trên máy fresh là 3.

Đính chính nhãn (có bằng chứng tĩnh): trường đó không phải AppsFlyerInit.Segment. script.json/ScriptMetadata ánh xạ off_5E27948AlmediaLink.AlmediaLinkSDK_TypeInfo, và AlmediaLinkSDK._almediaStatus nằm đúng ở static 0x38 (dump.cs:1108434). AppsFlyerInit.Segment là static 0x8 của off_5E27E20 (dump.cs:585775), do AppsFlyerInit.Cheat_SetSegment 0x2A600CC ghi — một trường hoàn toàn khác. enum AlmediaStatus = NotInitialized 0, Eligible 1, Linked 2, NotAvailable 3, Blocked 4, Disabled 5 (dump.cs:1110114) ⇒ mặc định 3 = NotAvailable, khớp chính xác quan sát "máy fresh không bao giờ thấy FreeCash dù ServerConfig có bật". Kết luận hành vi KHÔNG đổi — chỉ nhãn cơ chế được sửa: bản cài mới, hoặc thị trường mà Almedia không phục vụ, thì FreeCash không bao giờ lộ diện, bất kể ServerConfig nói gì.

  • Cửa sổ nổi: HUD chỉ hiện trong khoảng [freecash_stage_min, freecash_stage_max_hud] (segment 1: stage 15 → 35). Banner trong shop thì không có trần trên. Đo sống: Stage_Value_Max = 349 (Stage 35) ⇒ hiện; 350 (Stage 36) ⇒ ẩn lại
  • Phần thưởng: CURRENT_REWARD = RewardType.RemoveRewardAd (1)TryGiveLinkReward (0x2A6D3C4) đặt isRewardAdRemoved = true và gọi Admob.On_FreeCash_RewardAdRemoved() (0x2A5A37C) để huỷ quảng cáo đang nạp. Từ đó ShowRewardAd trao thưởng thẳng (§4.1). (RewardType.Gun = 2 + REWARD_GUN_NUM = 25 tồn tại nhưng không được chọn ở 1.1.59.)
  • Nếu save chưa nạp: pendingReward = true, trao sau (0x2A6D3C4 @0x2a6d3d8).
  • place lưu ở PlayerPrefs["fc_link_place"], xoá sau khi trao; log AppsFlyer m_freecash_link.
  • Hằng độ bền: INIT_MAX_RETRY 20, INIT_RETRY_DELAY 1 s, LINK_CHECK_TIMEOUT 10 s.

4.6 Súng thưởng quảng cáo weapon_02Gun_manager

CheckAdGunEvent()   0x2B0C74C          // gọi từ Gun_Item_List.FuseItems (sau khi người chơi GHÉP súng)
  if (AD_Gun_Event_Given) return
  if (!ServerConfig.Weapon02AdOn) return                    // weapon_02_ad_onoff
  if ( Has_Degree_Or_Higher(2)                              // đã có súng bậc >= 2 -> khỏi cần tặng
    || ( Has_Merged && AD_Gun_SlotIndex == -1
      && AddAdRewardGun(gunNum: 14, cooltimeSec: 1800f) != -1 ) )
      AD_Gun_Event_Given = true                             // một-lần-cho-cả-đời-tài-khoản
  • AddAdRewardGun (0x2B0C340): đặt súng #14 vào ô trống đầu tiên, ghi AD_Gun_EndUtcTicks = UtcNow + 1800 s (30 phút đếm ngược).
  • Update() (0x2B0C64C): mỗi 0.25 s (unscaled), nếu hết giờ không đang xem quảng cáo → AdRewardGun_Expire()súng biến mất.
  • Chạm vào súng đó (Gun_Item.OnPointerUp, Gun_Item_List.EquipToSlot, FuseItems) → AdRewardGun_TryWatch()ShowRewardAd(AD_Reward_Gun).
  • Xem xong → AdRewardGun_Acquire() (0x2B0C508): xoá timer + slot marker ⇒ giữ súng vĩnh viễn, bật NewGun_UI_ForceShow.

Đây là mẫu "dùng thử có hạn giờ" kinh điển: đưa món đồ vào tay người chơi, cho họ nếm sức mạnh, rồi đặt một quảng cáo giữa họ và việc giữ nó. Trigger là hành vi (vừa ghép súng lần đầu), không phải thời gian — nên nó rơi đúng lúc người chơi đang quan tâm tới súng.


5. Kinh tế Buff ×3

Đại lượng Giá trị Nguồn
Một lần xem quảng cáo cộng 300 s AddBuff 0x2AE4510
Trần tích luỹ 900 s (BUFF_MAX) — xem 3 lần liên tiếp là đầy 0x2AE4510 (fminf(x+300, 900))
Loại Money_Buff (0) và Power_Buff (1) — 2 đồng hồ độc lập (moneyBuffTime@0x28, powerBuffTime@0x2C) dump.cs:592890-592891
Hệ số ×3 (UpgradeRebirth_Multiplier cùng giá trị; hệ số buff áp trong chuỗi thu nhập) 01-economy-money.md; Cat_manager.Balance_Reload được gọi ngay sau AddBuff
Vĩnh viễn qua IAP IsPermanentBuff = Shop_manager.Is_Buff_Pack (static 0xB) ⇒ IsBuffActive(**mọi** type) = true 0x2AE4A08, 0x2AE4AF8
Hiển thị Buff_Panel.Update 0x2AE4D4C: fill = time / 900, text "{0}:{1}" phút:giây; vĩnh viễn ⇒ text rỗng, fill = 1.0, ẩn nút xem QC 0x2AE4D4C
Cache AddBuff(Money_Buff) gọi Gamemanager.InvalidateMoneyCache() — buff nằm trong chuỗi nhân thu nhập có cache LRU 0x2AE4510; xem 01-economy-money.md
Lưu SaveData.MoneyBuffTime / PowerBuffTime (offset 0x2A8/0x2AC), nạp qua LoadBuffTimes 0x2AE4180 FABLE_ANALYSIS §B-6

Giá của cùng một thứ: 300 s ×3 tiền = 1 quảng cáo. Vĩnh viễn ×3 cả hai = buff_pack 210.000đ (phaseA/01_shop.png). Đó là toàn bộ luận điểm bán hàng — và nó được đặt ở dòng thứ hai của cửa hàng, ngay dưới gói Auto Upgrade.


6. IAP — danh mục đầy đủ

6.1 34 product id → chỉ số offer → thứ được trao

Chỉ số lấy từ Shop_manager.ItemIdxFromProductId (0x2B2DE44), dùng cho log AppsFlyer m_offer_page.

Idx Product id Loại Được trao (hàm, RVA) Giá quan sát (VND, live)
1 dia_0 consumable ApplyDiaReward 0x2B32D90240 dia (×2 lần đầu) 52.000đ
2 dia_1 consumable 600 dia (×2 lần đầu) 131.000đ
3 dia_2 consumable 1500 dia (×2 lần đầu) 263.000đ
4 dia_3 consumable 3500 dia (×2 lần đầu) 526.000đ
5 dia_4 consumable 8000 dia (×2 lần đầu) 1.300.000đ
6 remove_forced_ads non-consumable Is_Remove_AD_forced = true; mở DiaPig 79,000đ
7 remove_all_ads non-consumable Is_Remove_AD_All = true; mở DiaPig; tắt interstitial + banner, và rewarded ad tự trao thưởng 263,000đ
8 autoupgrade_pack non-consumable Is_AutoUpgrade_Pack = true + 500 dia 211.000đ
9 buff_pack non-consumable Is_Buff_Pack = true (×3 Power+Money vĩnh viễn) + 1000 dia + reload Cat_manager/Buff_manager 210.000đ
10 dia_pack non-consumable Is_Dia_Pack = true + 16000 dia 1.300.000đ
11 mine_pack non-consumable Is_Mine_Pack = true + 500 dia + reload Mine_Start_UI 158,000đ
12 vip_pass vip_pass_2 non-consumable ApplyVipPass 0x2B32C50: Mission_manager.is_Vip = true + 500 dia vip_pass 210,000đ; vip_pass_2 KHÔNG có trên store (xem ghi chú dưới bảng)
13 catgunner_core_100 consumable ApplyCoreReward 0x2B304BCAdventure_manager.Core_Get(Core_Type.ALL, 100)Currency_Core[3] += 100 26,000đ
14 catgunner_core_500 consumable Currency_Core[3] += 500 105,000đ
15 catgunner_core_1050 consumable Currency_Core[3] += 1050 210,000đ
16 catgunner_core_3300 consumable Currency_Core[3] += 3300 631,000đ
17 catgunner_core_daily_750 consumable, nhịp ngày Currency_Core[3] += 750 + ghi LastDailyCorePurchase_dateTime 105,000đ
18 catgunner_core_weekly_1750 consumable, nhịp tuần Currency_Core[3] += 1750 + LastWeeklyCorePurchase_dateTime 210,000đ
22 catgunner_core_weekly_6000 consumable, nhịp tuần Currency_Core[3] += 6000 + LastWeekly6000CorePurchase_dateTime 631,000đ
19 catgunner_luxury_pass non-consumable ApplyLuxuryPass 0x2B32AA4: is_Vip = is_Luxury = true + 1000 dia + Exp 300 (3 cấp pass) 394,000đ
20 catgunner_luxury_upgrade non-consumable ApplyLuxuryUpgrade 0x2B328F8thân hàm giống hệt idx 19 210,000đ
21 catgunner_rebirth_single consumable ApplyRebirthSingle 0x2B3280CUpgradeRebirth_Start_By_IAPthêm 1 lượt Rebirth ×3 trong ngày 52,000đ
23-27 catgunner_stepup_0..4 consumable ×5 ApplyStepUpReward 0x2B2E90CStepUp.Gun_Rewards[i] = súng [23,28,33,39,44] + Dia_Rewards[i] = [0,200,400,600,1000] dia 26,000 / 130,000 / 261,000 / 523,000 / 1,050,000đ
28-30 catgunner_dailygun_0..2 consumable ×3 ApplyDailyGunReward 0x2B2EDB8DailyGun.ApplyReward(idx); nền súng GunNum_Base=[20,30,35], PetCoin [300,700,1500], giới hạn tuần MaxCounts=[2,2,1] 130,000 / 523,000 / 915,000đ
36 catgunner_pluspack_time consumable ApplyPlusPackReward 0x2B2EF0CPlusPack.ApplyReward(Time), hạn 7 ngày 104,000đ
37 catgunner_pluspack_reward consumable PlusPack.ApplyReward(Reward); ×2 Monster, ×2 Boss, ×1.5 Mine 234,000đ
38 catgunner_pluspack_auto consumable PlusPack.ApplyReward(Auto); giảm giờ Adventure (PlusPack_Auto_Reduce_Hours = 16) 156,000đ

Chỉ số 31-35 bỏ trống — có thể là sản phẩm đã gỡ hoặc chừa chỗ. Đây là bằng chứng phủ định, không phải suy đoán: ItemIdxFromProductId không trả về giá trị nào trong khoảng đó.

Giá: 33/34 id đã có giá thật (đóng G162). Dump Shop_manager.Localized_Price_return (0x2B367D4) read-only trên bản Play Store chính hãng: dict_count = 33, resolved = 33 ([liveops catalog: iap_prices_store.json]). 8/8 giá trùng khớp ảnh chụp cửa hàng của do store cấp — không phải hằng số trong build (§6.3).

vip_pass_2 KHÔNG phải sản phẩm Play — nó có mặt trong TryApplyPurchase/ItemIdxFromProductId nhưng không nằm trong 33 id mà store trả giá ⇒ 34 id trong code, 33 id đang bán. Nhiều khả năng là SKU cũ giữ lại để người đã mua vẫn khôi phục được. (Bằng chứng phủ định, không suy đoán về lý do.) Tương tự, fish_free, mine_ticket, sp_iap, reward_hub, f_cash, banner_* không xuất hiện trong dump giá — củng cố kết luận §2.2 rằng chúng là chuỗi place/khoá analytics.

Phân bố giá (33 SKU, VND): thấp nhất remove_forced_ads 79.000đ · cao nhất dia_4 = dia_pack 1.300.000đ. Ba bậc giá lặp lại rõ rệt — 26.000 / 105.000 / 210.000 / 631.000 (Core) và 26.000 / 130.000 / 261.000 / 523.000 / 1.050.000 (StepUp) gần như nhân đôi mỗi bậc: đây là một thang giá hình học ×2, không phải giá đặt tay từng món.

6.1a "Core" là tiền tệ gì (giải quyết phần lớn G163)

enum Core_Type { Forest = 0, Desert = 1, Tundra = 2, ALL = 3 }         // dump.cs:591540
Adventure_manager.Currency_Core : int[]                                 // instance +0x28
Adventure_manager.Core_Get(Core_Type type, int value)  0x2ADA8C8
    => Currency_Core[(int)type] += value;  OnCoreChanged?.Invoke();  Alert_Reload()
Adventure_manager.Core_Lose / CoreCount_return  — cùng chỉ mục
const Adventure_Reward_Core_perHour     = 20      // kiếm được bằng cách đi Trip
const Adventure_Reward_Core_PetDegree   = 0.1     // +0.1 Core mỗi bậc pet

Core là tiền tệ của Trip (Adventure), kiếm bằng cách cử pet đi chuyến (20/giờ), tiêu vào cây Ability của Trip. Có 4 ô: 3 ô theo map (Forest / Desert / Tundra) + ô ALL (index 3). Mọi IAP catgunner_core_* đều nạp vào ô ALL — tức loại Core dùng được cho mọi map. Panel Trip đang chạy hiển thị đúng 4 chip tiền tệ: 3 viên màu (xanh lá / cam / xanh dương, khớp thứ tự enum Forest/Desert/Tundra) + 1 ngôi sao tím "PrismCore" HYPOTHESIS (mức trung bình): ngôi sao tím = ô ALL (index 3) = "PrismCore" — dựa trên (a) Core_Type có đúng 4 giá trị, (b) panel có đúng 4 chip, (c) banner promo của panel mở PrismCore_Purchase_popup (Main.unity:22690) và (d) scene có 7 object Prism_Pack, 3 trong đó đánh dấu (Limited). Chưa xác nhận: chuỗi UI ↔ index chưa được đọc từ code (Adventure_Panel dump.cs:592475, Core_sprite_return dump.cs:591675-591681) → xem §10.

6.2 Đường đi của một giao dịch (không có bước nào ở đây được tái hiện trong bản restore)

<nút UI> -> Shop_manager.Purchase*(type, place)       // ghi _pendingPlace + _pendingIdx
        -> BeginPurchase(productId)   0x2B2E7D8       // ShowPurchasingPanel(); StoreController.PurchaseProduct
        -> [Unity IAP + store]
        -> OnPurchasePending(order)   0x2B31758       // GetTxnId, AlreadyOwned, _inflightTxns
        -> StartValidate(order, product) 0x2B32144
        -> ValidateAndComplete(...)   0x2B33DAC       // POST receipt -> Cloud Function verify*
        -> CompletePurchaseSuccess(...) 0x2B34164
        -> TryApplyPurchase(id, type) 0x2B32204       // <== ĐIỂM TRAO DUY NHẤT
        -> MarkProcessed(txnId); SaveSystem.Save(); LogFacebookPurchase / LogFirebasePurchase

TryApplyPurchase (0x2B32204) là một chuỗi so khớp id: 6 non-consumable → ApplyNonConsumable; catgunner_rebirth_single; catgunner_luxury_upgrade; catgunner_luxury_pass; vip_pass/vip_pass_2; TryGetCoreTypeFromId; rồi 4 nhánh theo prefix (dia_, catgunner_stepup_, catgunner_dailygun_, catgunner_pluspack_); không khớp gì → LogError("[IAP] Unknown product id '{0}' (type={1}) → apply 실패 처리") và trả false.

Ba mẫu đáng học từ đoạn này: 1. Trao hàng là idempotent và hoãn được. Nếu manager phụ trách còn null (chưa xong Awake), hàm trả false và log "확정 보류, 다음 실행 재시도" — giao dịch không được finalize, store sẽ phát lại ở lần chạy sau. Người chơi không bao giờ mất hàng vì thứ tự khởi tạo. 2. Một điểm trao duy nhất cho cả mua mới, khôi phục (RestorePurchases 0x2B35AD0) và đơn hoãn (_deferredOrders). 3. Prefix hoá sản phẩm có chỉ số (catgunner_stepup_0..4) — thêm bậc mới không phải sửa code dispatch, chỉ sửa mảng phần thưởng.

6.3 Định giá — cái game LÀM và cái game KHÔNG làm

  • Game không hardcode giá. Localized_Price_return(...) (6 overload, 0x2B367D4…) đọc giá từ Product.metadata của store, rồi FormatPrice_Display(price, iso) (0x2B368B0) định dạng theo CurrencyFormat / ZeroDecimalCurrency (2 bảng static trong .cctor 0x2B36F9C). ⇒ Mọi con số đồng trong tài liệu này là giá cửa hàng quan sát được ở VN tại thời điểm chụp, KHÔNG phải hằng số trong build.
  • TryGetPrice(productId, out decimal, out string currency) (0x2B36D00) cho phép UI hiển thị giá chuẩn từng thị trường.
  • Bảng giá thật (VN, 33 SKU) nằm ở §6.1 — dump từ chính Localized_Price_return trên bản store chính hãng ([liveops catalog: iap_prices_store.json]). Đọc nó như một mẫu định giá, không phải như hằng số của game: cùng build ở thị trường khác sẽ hiện con số khác.
  • ZeroDecimalCurrency là lý do VND hiện 52,000đ chứ không phải 52,000.00đ. Nội dung hai bảng (đọc từ .cctor 0x2B36F9C, G399): CurrencyFormat = 23 mục tiền tệ → chuỗi định dạng (+0x68); ZeroDecimalCurrency = {KRW, JPY, VND, IDR, CLP, HUF} (+0x70) — sáu tiền tệ không có phần thập phân. Cùng .cctor cấp ProcessedTxnIds (+0x40) và PurchaseHistory (+0x48) — thiếu chúng là autosave trên save mới ném mỗi 10 s (G399).

7. Kích hoạt offer & thời điểm

7.0 BA LỚP CỔNG xếp chồng — tutorial → thang stage → segment

LỚP 1 — TUTORIAL  (Tutorial_manager, dump.cs:602669)
    Save mới: CHỈ SpeedRun_Btn + Setting_Btn active.
    Shop / Gun / Skin / Mission / DailyCheck / Auto / Eco / DiaPig / Buff đều tắt,
    và KHÔNG do Contents_Lock_Reload bật  ->  Tutorial_manager mở dần.
    TutorialStep: Upgrade 0 -> Challenge 1 -> GunOpen 2 -> GunBuy 3 -> GunEquip 4 -> Done 5
    Scene: DisableOnTutorial[] = 32 object (Main.unity:20342185+)
        v   (người chơi làm xong tutorial)
LỚP 2 — THANG STAGE  (Tree_manager.Contents_Lock_Reload 0x2B4441C)
    Stage_Value_Max >= 20/30/50/90/110/110/340  ->  Rebirth/Mine/Fish/Boss/Pet/Hunt/Trip
        v   (nội dung mở dần)
LỚP 3 — SEGMENT  (ServerConfig_manager, 15 số từ server)
    MaxStageDisplay >= offerwall_stage_min / inter_stage_min / freecash_stage_min
    + trần ngày theo place + cooldown  ->  bề mặt QUẢNG CÁO bật/tắt

Hệ quả cho phễu kiếm tiền: một người chơi mới không nhìn thấy cả cửa hàng cho tới khi tutorial mở Shop_Btn. Nghĩa là thứ tự tiếp xúc là gameplay trước, cửa hàng sau, quảng cáo sau nữa (bề mặt IAA sớm nhất là OfferWall ở Stage 10 — tức khoảng 100 màn sau khi bắt đầu).

7.1 Công tắc tổng: segment

boot -> ServerConfig_manager.Awake 0x2AC8154
     -> LoadFromSaveData 0x2AC1FA8 (dùng cache) -> Tree_manager.Contents_Lock_Reload
     -> FetchRoutine 0x2AC9864 : POST {BASE_URL}/[endpoint], TIMEOUT_SEC 2,
        FAIL_RETRY_DELAY 60, FAIL_RETRY_MAX 1, POST 1 lần/ngày (NeedPost/TodayKey),
        FIRST_POST_WAIT_CONVERSION_SEC 10  (chờ AppsFlyer trả conversion data trước khi POST lần đầu)
     -> nếu chưa có phản hồi: EmbeddedDefaults(segment) 0x2AC7B20
segment Hệ quả
1 Bật IAA: FreeCash stage 15-35, OfferWall từ stage 10, interstitial từ stage 12, cap Rebirth 6/ngày, Mine 2/ngày, các place khác 0, cap toàn cục 99999, ad-gun bật
0 / ≠1 Tắt sạch IAA (mọi ngưỡng 99999, mọi cap 0, ad-gun tắt) — đây là mặc định là điều production đang trả về cho user thật (rule_version 41, reason ok)

Bài học vận hành lớn nhất của tài liệu này: một game có thể ship toàn bộ hạ tầng IAA và vẫn chạy 0 quảng cáo cho một phân khúc, chỉ bằng 15 con số từ server. Phân khúc quyết định bởi nguồn cài đặt / quốc gia / lịch sử chi tiêu (ConfigReq), không phải bởi build.

7.2 Offer gắn với TIẾN TRÌNH (đọc Stage_Value_Max, xem UNLOCK_GATES.md §1)

stageDisplay = Stage_Value_Max / 10 + 1

Bề mặt Điều kiện RVA
Interstitial stageDisplay >= inter_stage_min (seg 1: 12) 0x2A5CD34
OfferWall HUD stageDisplay >= offerwall_stage_min (seg 1: 10) 0x2B2B20C
FreeCash HUD freecash_stage_min <= stageDisplay <= freecash_stage_max_hud (seg 1: 15 → 35) 0x2A6CF08 + 0x2A6D1F8
FreeCash banner (shop) chỉ >= freecash_stage_min (không trần) 0x2A6D644
PlusPack banner Banner_Stage_Min = 190 (⇒ Stage 20-1) gói đó chưa active dump.cs const; PlusPack.Banner_ShouldShow 0x2B2C778

7.3 Offer gắn với HÀNH VI (không phải stage, không phải giờ)

Bề mặt Trigger RVA
Ad-gun weapon_02 ghép súng (Gun_Item_List.FuseItems) lần đầu, khi chưa có súng bậc ≥2 0x2B0C74C
StepUp banner !Is_AllPurchased && !Is_Expired && Gun_manager.Has_Degree_Or_Higher(2) (khớp Banner_Unlock_Degree = 2) 0x2B3A670
StepUp đồng hồ 7 ngày StepUp_Start() chỉ chạy lần đầu banner/popup được reload — đồng hồ bắt đầu khi người chơi nhìn thấy, không phải khi cài 0x2B3A328Hud_Reload/Popup_Reload
Interstitial sau Rebirth chỉ khi lần rebirth đó không được boost popup review không mở Rebirth() @0x2b22d9c
Rewarded "bonus" mở panel kết quả Mine/Boss/Hunt/Challenge (nút Bonus_Btn_obj, SweepBonus_Btn_obj); nếu Is_RewardBonus_Auto thì hiện SweepNoAds_Btn_obj thay thế dump.cs Mine_manager fields

7.4 Tương tác popup Review ↔ interstitial (mẫu đáng chép)

Rebirth_manager.Rebirth()  0x2B22AE0 — đuôi hàm:
    bool reviewOpened = Review_manager.instance.Review_Panel_Open();     // @0x2b22d24
    Mission_manager.instance.MissionValue_Increase(Mission_Type.Rebirth);
    if (is_UpgradeRebirth) { if (!is_UpgradeRebirth_IAP) { UpgradeRebirth_UseCount++;
                                                           UpgradeRebirth_LastDate = today; }
                             is_UpgradeRebirth = is_UpgradeRebirth_IAP = false; }
    SaveSystem.Save();
    if ( !(boosted || reviewOpened) && Admob.instance != null )          // @0x2b22d9c
        Admob.instance.ShowPlacementInterstitial(Inter_Place.Rebirth, null);

Review_Panel_Open() (0x2AC0DE8) trả true đúng một lần trong đời tài khoản (is_reviewed lưu trong SaveData) và chỉ có một người gọi: Rebirth().

⇒ Ba luật ưu tiên popup được mã hoá trong đúng một dòng if: 1. Người vừa trả tiền / xem quảng cáo để boost ⇒ không ăn thêm interstitial. 2. Xin đánh giá app thắng interstitial (chỉ có một cơ hội, và nó rơi vào lần rebirth đầu tiên — đúng khoảnh khắc người chơi vừa cảm thấy phần thưởng lớn nhất của game). 3. Còn lại thì interstitial, và vẫn phải qua 7 cổng ở §4.2.

7.5 Offer theo LỊCH (cố định trong binary, không remote)

Hệ Nhịp Hằng RVA
StepUp 7 ngày kể từ lần hiện đầu, 5 bậc, mua tuần tự StepCount 5, Duration_Days 7 Is_Expired 0x2B3A4E0, Current_Purchasable_Step 0x2B3A630
DailyGun xoay theo tuần, WeekEpoch = 2024-01-01, 3 món/tuần, giới hạn [2,2,1] lượt ProductCount 3 WeekStart_return 0x2B25A38, WeekIndex_return 0x2B25F34, GunNum_return 0x2B26080
PlusPack 7 ngày kể từ lúc mua, 3 loại độc lập Duration_Days 7 Is_Active 0x2B2B5EC, Remain_Duration 0x2B2BEE8
Core daily/weekly mốc LastDaily…/LastWeekly… Is_Core_Special_Available 0x2B24F9C
DiaPig tick 60 s, bù tối đa 3 tick khi quay lại TICK_SECONDS 60, CatchUpMaxPending 3 LIVEOPS_SURFACE §6
Trip — vé Ticket_Max = 20, nạp 1 vé / 4 giờ (Ticket_Charge_Interval_Hours = 4) dump.cs:591608+ Ticket_Refresh
DailyCheck mốc ngày, chuỗi 7 ngày lặp SyncWaitTimeout 4; bảng dia 70/200/400/600/800/1000/1500đã dựng lại hoàn chỉnh, xem §7.6 (G378) LIVEOPS_SURFACE §6; G378
UpgradeRebirth 1 lượt ad + 1 lượt IAP mỗi ngày, reset bằng chuỗi ngày UpgradeRebirth_DailyLimit 1, ..._IAP_DailyLimit 1 UpgradeRebirth_DayCheck 0x2B23260, Able_return 0x2B232B0, IAP_Able_return 0x2B2339C
Interstitial cap ngày theo place + cap toàn cục, reset bằng PlayerPrefs["inter_place_date"] ServerConfig 0x2A5C7F0

7.6 Điểm danh 7 ngày (DailyCheck) — hệ retention, đọc trọn từ binary (G378)

Mục tiêu học R9: đây là vòng lặp quay lại mỗi ngày của game — thứ nuôi số ngày hoạt động (retention) mà mọi offer khác đứng trên. 13 method đã dựng lại (OnSynced 0x2A6B470, OnCheckSettled 0x2A6B588, RefreshOrShow 0x2A6B4DC, is_DateChange 0x2A6B90C, is_GetAble 0x2A6BC64, CheckStatus_return 0x2A6B9F0, UI_reload 0x2A6B58C, NewUser_Reload 0x2A6B828, Open_Btn 0x2A6B720, Test_Btn 0x2A6B678, Get_Reward 0x2A6BC7C, DailyReward_Get 0x2A6BA9C, Show_WhenReady MoveNext 0x2A6BD08).

Claim Mức bằng chứng Nguồn / provenance
Bảng thưởng DiaReward_list (scene) = 70 / 200 / 400 / 600 / 800 / 1000 / 1500 dia cho ngày 1–7 — tổng 4 570 dia một chu kỳ, tăng dần với mốc nhảy ở ngày 7 scene-YAML G378
Điều kiện sang ngày mới = kiểm đồng hồ tin cậy TRƯỚC, rồi mới so ngày: is_DateChange = CanRolloverDaily("daily", maxPending 1) && LastCheck_Date.Date < Today — không có giờ tin cậy thì không có "ngày mới", bất kể đồng hồ máy disasm-proven is_DateChange 0x2A6B90C; G378
Chuỗi 7 ngày LẶP VÔ HẠN: CheckNum == 7 && đổi ngày ⇒ CheckNum = 0 — không có "tháng", không reset về 0 khi bỏ lỡ ngày (chỉ chờ ngày đổi) disasm-proven G378
Người dùng mới được lùi ngày một hôm (NewUser_Reload) ⇒ ngày 1 nhận được ngay lần mở đầu tiên — phần thưởng đầu tiên không bắt chờ disasm-proven NewUser_Reload 0x2A6B828; G378
Nhận thưởng kiểm lại điều kiện (is_GetAble) và từ chối kèm sfx Dont nếu không còn đủ; hợp lệ ⇒ Time_manager.RecordGain("daily") ghi sổ cái TRƯỚC khi cấp dia; rồi CheckNum++ TotalCheckNum++ (cặp), LastCheck_Date = Today, Dia_Attraction.Dia_Spawn (dia bay về ví), sfx Purchase disasm-proven Get_Reward 0x2A6BC7C, DailyReward_Get 0x2A6BA9C; G378
Mở panel ép kiểm lại giờ (Open_Btn → Time_Hacker_ForceCheck) — người chơi tự kích hoạt lần kiểm chống chỉnh giờ mỗi khi vào xem điểm danh disasm-proven Open_Btn 0x2A6B720; G378
Nút lùi ngày debug (Test_Btn) gate bằng Debug.isDebugBuildtắt trong bản phát hành (dùng được trên bản học vì debuggable) disasm-proven Test_Btn 0x2A6B678; G378
Show_WhenReady: yield nullWaitForCheck(4 s)Try_Show (chờ đồng bộ giờ trước khi hiện panel ngày mới) disasm-proven MoveNext 0x2A6BD08; G359, G378
TotalCheckNum (đếm suốt đời) tách khỏi CheckNum (vị trí trong chu kỳ) — số liệu retention và trạng thái UI không dùng chung một biến disasm-proven G378

Bốn quyết định đáng chép: (1) thưởng đầu tiên ngay lập tức (lùi ngày người mới) — chi phí bằng 0, gỡ rào cản đầu tiên; (2) chuỗi lặp, không phạt — bỏ lỡ một ngày không mất tiến độ, chỉ chờ; (3) đồng hồ tin cậy là điều kiện tiên quyết, không phải kiểm tra sau; (4) ghi sổ trước khi cấp — mọi thưởng theo thời gian đi qua cùng một sổ cái (RecordGain, doc 07 §2.6) nên có thể đối chiếu ngược.

8. Mô hình hành vi — phễu kiếm tiền

 TUTORIAL (lớp 1)
 ────────────────
 save mới ──> chỉ SpeedRun_Btn + Setting_Btn ──> Tutorial_manager mở dần Shop/Gun/Skin/Mission/
              DailyCheck/Auto/Eco/DiaPig/Buff ──> CHỈ TỪ ĐÂY cửa hàng mới tồn tại với người chơi

 TIẾN TRÌNH (lớp 2)            ĐIỀU KIỆN LỘ DIỆN (lớp 3)        OFFER                PHẦN THƯỞNG          TÁC ĐỘNG KINH TẾ
 ─────────                    ─────────────────                ─────                ───────────          ────────────────
 Stage_Value_Max  ──┬──> stageDisplay = SVM/10 + 1
                    │
                    ├──> >= inter_stage_min ────────────> interstitial ───────────> (không thưởng) ────> 0  (chỉ doanh thu)
                    │      && cooldown ok && cap ngày ok
                    │      && !boosted && !justWatchedRewarded
                    │
                    ├──> >= offerwall_stage_min ───────> OfferWall (Tapjoy) ──────> Dia ───────────────> mua shop / gacha
                    │      && Tapjoy connected
                    │
                    ├──> [freecash_min, freecash_max] ─> FreeCash link ───────────> RewardAdRemoved ───> MỌI rewarded
                    │      && Almedia ∈ {Eligible, Linked}                                                tự trao ⇒ buff/bonus
                    │      (seg 1: Stage 15→35, CÓ TRẦN TRÊN)                                             không còn ma sát
                    ├──> > 189 ─────────────────────────> WeeklyShop / PlusPack banner
                    └──> > 988 ─────────────────────────> catgunner_rebirth_single

 HÀNH VI                                                                                             CHUỖI THU NHẬP (doc 01)
 ───────                                                                                             ───────────────────────
 ghép súng lần đầu ─────> ad-gun #14, 30 phút ──> rewarded ──> giữ súng ────────────────────────────> Damage ↑ ⇒ stage nhanh hơn
 bấm nút Buff ──────────> rewarded ────────────> +300 s (trần 900) ─────────────────────────────────> ×3 Money hoặc ×3 Power
                                                                                                       (InvalidateMoneyCache)
 xong Mine/Boss/Hunt ───> rewarded "Bonus" ────> nhân thưởng màn ───────────────────────────────────> Money / PetCoin / Dia
 nhấn Rebirth ──────────> popup "xem QC để ×3" ─> is_UpgradeRebirth ────────────────────────────────> Ruby ×3 (doc 09 §4)
                          (1 lượt/ngày)
 IAP                                                                                                  
 ───
 buff_pack ─────────────> IsPermanentBuff ─────────────────────────────────────────────────────────> ×3 Money & Power VĨNH VIỄN
 remove_all_ads ────────> Is_Remove_AD_All ────────────────────────────────────────────────────────> tắt inter+banner,
                                                                                                       rewarded tự trao,
                                                                                                       DiaPig tick +1
 autoupgrade_pack ──────> Is_AutoUpgrade_Pack + 500 dia ───────────────────────────────────────────> tự mua upgrade
 dia_* / core_* ────────> Dia / Core ──────────────────────────────────────────────────────────────> gacha, relic, adventure
 rebirth_single ────────> +1 lượt Rebirth ×3/ngày (stage >= 100) ──────────────────────────────────> Ruby ×3 lần thứ hai

OfferWall (Stage 10 →, vĩnh viễn) · Interstitial (Stage 12 →, chỉ Rebirth 6/ngày + Mine 2/ngày) · FreeCash (Stage 15 → 35, rồi tự ẩn). Challenge/Boss/Hunt bị trần-0 chặn tuyệt đối.

Năm quan sát về hình dạng của phễu này:

  1. Interstitial là lớp duy nhất không trao gì cho người chơi — nên nó cũng là lớp bị gate nặng nhất: 7 điều kiện, cap theo từng place, cap toàn cục, cooldown, và 2 lần "miễn" (vừa xem rewarded, vừa được boost).
  2. Mọi thứ IAA trao đều là thứ IAP bán ở dạng vĩnh viễn. Không có phần thưởng quảng cáo nào không có bản IAP tương ứng. Quảng cáo là bản dùng thử của cửa hàng.
  3. FreeCash là một "IAP giá 0đ": nó cấp đúng cái mà remove_all_ads cấp cho tầng rewarded (Is_RewardBonus_Auto gộp cả hai vào một vị ngữ, 0x2B2D510). Người chơi trả bằng dữ liệu liên kết tài khoản thay vì tiền.
  4. Cửa sổ lộ diện có cả trần trên (freecash_stage_max_hud). Sau stage 35 (segment 1) HUD FreeCash tự biến mất — đo sống: 349 ⇒ hiện, 350 ⇒ ẩn. Offer chuyển đổi kém ở late-game thì bị thu hồi, không để rác trên màn hình.
  5. Ngân sách interstitial được dồn vào đúng 2 khoảnh khắc "vừa được thưởng lớn" (Rebirth, Mine), và bị tắt hẳn ở 3 chế độ còn lại bằng cách đặt trần/ngày = 0 — chứ không phải bằng một cờ bật/tắt riêng. Một con số 0 trong config làm việc của một feature flag.

9. Framework takeaway — tầng placement/offer/segment tổng quát

9.1 Bốn khái niệm, tách bạch

// 1) PLACEMENT — "ở đâu, khi nào có thể hiện"
interface IPlacement {
    string   Id { get; }                 // "inter.rebirth", "rewarded.money_buff"
    bool     CanShow(IGateContext ctx);  // chuỗi gate, fail-closed
    void     Show(Action onClosed);      // KHÔNG BAO GIỜ chặn: not-able -> onClosed() ngay
}

// 2) GATE — mỗi gate là một vị ngữ thuần, xếp theo thứ tự RẺ→ĐẮT, log lý do từ chối
interface IGate { bool Pass(IGateContext ctx, out string reason); }
// BuildFlagGate  -> hằng build (is_AD_Disable_Version)
// EntitlementGate-> người chơi đã mua quyền không thấy (remove_ads)
// ReentrancyGate -> đang hiện / đang chạy
// ProgressGate   -> progressMetric >= remote.minThreshold  (và <= maxThreshold nếu có trần)
// CooldownGate   -> now - lastShownUtc >= remote.cooldownSec
// PerPlaceCapGate-> counter[place] < remote.dailyCap[place]
// GlobalCapGate  -> counter[global] < remote.dailyCapWhole
// CourtesyGate   -> !justRewarded && !justPaid          <-- luật "không phạt người vừa trả"

// 3) OFFER — "bán cái gì, khi nào bật, hết hạn khi nào"
interface IOffer {
    bool IsActive(DateTime now, IPlayerState s);   // lịch + tiến trình + hành vi
    IReadOnlyList<Reward> Preview { get; }         // để UI vẽ trước khi mua
}

// 4) SEGMENT — công tắc tổng, do server cấp, có bản nhúng fail-safe
interface IRemoteAdConfig {                        // 15 số, KHÔNG có số kinh tế
    int  Segment { get; } string RuleVersion { get; }
    int  MinStage(string placementId); int  MaxStage(string placementId);
    int  CooldownSec { get; } int DailyCap(string placementId); int DailyCapGlobal { get; }
    bool FeatureOn(string featureId);
}

9.2 Bảy quy tắc rút ra từ SOT (mỗi quy tắc là một quyết định thật trong libil2cpp.so)

  1. Mặc định nhúng phải là "tắt", không phải "bật". EmbeddedDefaults(segment != 1) = 99999/0. Server chết ⇒ người chơi thấy ít quảng cáo hơn, không phải nhiều hơn. (0x2AC7B20)
  2. Remote config chỉ được chứa số PLACEMENT, không chứa số KINH TẾ. 15/15 field của ConfigResp là ad-placement — đã kiểm chứng bằng payload production thật. Cân bằng nằm trong build ⇒ mọi người chơi cùng một game. (PROBE_RESULT.md §4)
  3. Bộ đếm quảng cáo tách khỏi save game. PlayerPrefs cho inter_place_cnt_*, ow_pending_gem, fc_link_place; SaveData cho tiến trình. Reset save không mở lại quota quảng cáo, và ngược lại.
  4. Không bao giờ chặn luồng game vì quảng cáo. ShowPlacementInterstitial gọi onClosed() ngay khi gate fail; PlacementInterCo có timeout từng lớp (interLoadingSeconds, interLoadMaxAttempts, interLoadWaitPerAttempt, LOAD_HARD_RESET_SEC, adLoadTimeout).
  5. Entitlement chảy ngược vào tầng quảng cáo qua MỘT vị ngữ. Is_RewardBonus_Auto gộp "đã mua" và "đã liên kết FreeCash" — thêm nguồn miễn quảng cáo thứ ba chỉ cần sửa một hàm.
  6. Điểm trao thưởng duy nhất, idempotent, hoãn được. Cả rewarded (Reward_Get) và IAP (TryApplyPurchase) đều hội tụ về một hàm; nếu hệ thống đích chưa sẵn sàng thì không finalize và thử lại lần chạy sau.
  7. Lịch sự với người vừa trả. !_bonusAdWatched, !Is_RewardBonus_Auto, !boosted, !reviewOpened — bốn lần "miễn" nằm rải trong code, tất cả cùng một tinh thần.
  8. Xếp CHỒNG cổng, đừng gộp. Ba lớp độc lập — tutorial (onboarding), thang stage (nội dung), segment (kiếm tiền) — mỗi lớp một chủ sở hữu, một vòng đời, một cách đổi. Người chơi mới không
  9. Tắt một placement bằng SỐ, không bằng cờ. inter_daily_cnt_challenge = 0 tắt hẳn placement Challenge mà không cần thêm một trường boolean nào. Một trục số duy nhất (0 … 99999) vừa là công tắc, vừa là van tiết lưu — và cùng một dòng code (cnt >= limit) phục vụ cả hai.
  10. Bề mặt kém hiệu quả phải TỰ THU HỒI. freecash_stage_max_hud cho offer một hạn dùng theo tiến trình. Rất ít game làm điều này; hầu hết chỉ biết bật.
  11. Cho không trước, gắn quảng cáo sau — trên CÙNG một bộ đếm. Fish_Gacha: quota 5/ngày với 3 lượt đầu không quảng cáo (§4.1b). Hai hằng số (max, noAd) đọc một biến duy nhất, nên tỉ lệ cho-không/gắn-quảng-cáo là một tham số chỉnh được, không phải một nhánh code.
  12. Chỉ báo quảng cáo hiển thị theo DẢI, không theo ngưỡng. (uint)(used − free) < adSlots. Ngưỡng để lại một biểu tượng chết sau khi hết quota — nút hứa quảng cáo, trả về lỗi.

9.3 Ranh giới fail-closed cho bản restore (R1)

// Assets/Scripts/Services/IAdService.cs
public enum AdResult { Failed, Dismissed, Rewarded }
public interface IAdService {
    bool IsRewardedReady { get; }
    void ShowRewarded(string placementId, Action<AdResult> done);
    void ShowInterstitial(string placementId, Action done);
    void ShowBanner(); void HideBanner();
}
public sealed class NullAdService : IAdService {           // BINDING MẶC ĐỊNH
    public bool IsRewardedReady => false;
    public void ShowRewarded(string p, Action<AdResult> d) { Debug.Log($"[NullAd] {p}");
                                                             d?.Invoke(AdResult.Failed); }   // KHÔNG Rewarded
    public void ShowInterstitial(string p, Action d) => d?.Invoke();  // no-op, không chặn
    public void ShowBanner() { } public void HideBanner() { }
}
public interface IPurchaseService {
    bool IsOwned(string productId);                        // luôn false ở Null
    void Purchase(string productId, string place, Action<bool> done);   // luôn done(false)
}

Với IRemoteConfigService, bản Null phải trả đúng profile "tắt" — không phải null, không phải throw: MinStage(*) = 99999, DailyCap(*) = 0, FeatureOn(*) = false. Đó chính là (EmbeddedDefaults(1) trả đúng profile bật) — nên cả hai nhánh đều là hành vi đã kiểm chứng.

Bất biến bắt buộc: NullAdService không bao giờ trả AdResult.Rewarded; NullPurchaseService.IsOwned không bao giờ trả true; NullRemoteConfig không bao giờ trả giá trị bật quảng cáo — nó trả bản nhúng "tắt" (99999/0), đúng như SOT làm cho segment ≠ 1. Nhờ đó bản restore chạy được toàn bộ luồng UI monetization mà không cấp một entitlement nào.


10. UNKNOWN / bằng chứng phủ định — và probe phải bắt gì để đóng

Điều chưa biết Trạng thái Bắt gì để đóng (probe / device slot)
Thực thi cooldown / trần LÚC HIỆN quảng cáo UNKNOWN — lý do chính xác: ADX không bao giờ có fill trên bản sideload (_sdkReady = 1, _interLoading = 1, _interShowing = 0, IsInterstitialReady = 0 suốt phiên) ⇒ không quảng cáo nào được hiện, Inter_CountUp 0x2A5D644 chưa bao giờ chạy, ShowRewardAd 0x2A59294 / ShowPlacementInterstitial 0x2A5D508 được hook nhưng không bị gọi Cần một môi trường có fill quảng cáo thật (bản có bundle_id được mediation nhận). Sau đó: đóng panel Mine 3 lần liên tiếp và đọc PlayerPrefs["inter_place_cnt_1"] + ["inter_place_cnt_whole"] + _lastInterShownUtc sau mỗi lần; lặp qua nửa đêm để kiểm Inter_DayCheck
Lịch "Ngày mở" của map Trip nằm ở đâu trong code UNKNOWN — chỉ quan sát được trên UI (Forest T2/T5/CN · Desert T3/T6/CN · Glacier T4/T7/CN); không tìm thấy literal lịch trong stringliteral.json Decompile Adventure_Map_item (dump.cs:592454) / Adventure_Map_info_item (:592400) / Adventure_Panel (:592475); tìm DateTime.DayOfWeek
Tutorial_manager mở nút nào ở bước nào ✅ ĐÓNG (G375, G385): cổng vào = !_cleared && Lv(Power) < 1; BeginTutorial tắt bộ 32 DisableOnTutorial (= hàng 27 nút HUD); máy trạng thái Upgrade → Challenge → GunOpen → GunBuy → GunEquip → Done với coroutine kiểm điều kiện (MoneyCheckLoop đọc Lv(Power) mỗi 0.2 s, CheckSpeedRunClear, CheckGunSlotActive/Purchased/Equipped); CompleteTutorial bật lại cả hàng. Bước GunBuy nạp dia theo kịch bản (Dia ≤ 99 ⇒ Get_Dia(100 − Dia)) để lần mua đầu chắc chắn mua được — không phải đường kiếm tiền doc 07 §2.8
Chuỗi UI ↔ chỉ mục Currency_Core (ngôi sao tím = Core_Type.ALL index 3 = "PrismCore"?) HYPOTHESIS — 4 giá trị enum ↔ 4 chip trên panel, banner mở PrismCore_Purchase_popup Decompile Adventure_Panel + Core_sprite_return(int); hoặc chụp panel sau khi nạp Currency_Core[3]
Prism_Pack ×7 trong scene (3 bản (Limited)) — là IAP hay mua bằng Core? UNKNOWNkhông có product id prism* nào trong stringliteral.json, và ItemIdxFromProductId không nhận id nào như vậy ⇒ bằng chứng phủ định: không phải IAP của bản 1.1.59 Mở PrismCore_Purchase_popup trên .sot ở Trip Lv cao và chụp
PlusPack.Is_Banner_Stage_OK (0x2B2C6AC) so sánh chính xác cái gì UNKNOWN (IDA gộp biên hàm; chỉ có hằng Banner_Stage_Min = 190metadata-proven) Decompile lại sau khi sửa biên hàm, hoặc hook trả về
StepUp.Gun_Rewards = [23,28,33,39,44] ánh xạ sang khẩu súng nào ✅ ĐÓNG (derived, doc 11 §3.1): #23 유령 저격총 (S, LASER) · #28 미사일 SS (SS, MISSILE) · #33 미사일 SSS (SSS, MISSILE) · #39 폭발레이저 X (X, BLAST_SNIPER) · #44 폭발레이저 XX (XX, BLAST_SNIPER) — 5 bậc S → XX. Giá trị blob tái xác nhận bằng SHA-256 (G353) Gun_list scene 20307980; TABLE.md hàng 19
catgunner_luxury_pass vs catgunner_luxury_upgrade khác nhau ở đâu ✅ ĐÓNG: khác ở GIÁ. Thân hàm trao giống hệt (0x2B32AA40x2B328F8) nhưng store bán 394.000đ vs 210.000đ_upgrade là bản nâng cấp rẻ hơn cho người đã có VIP. Điều kiện hiển thị của từng panel vẫn UNKNOWN iap_prices_store.json; Purchase_Luxury 0x2B2FAC4 / Purchase_Luxury_Upgrade 0x2B2FBA4
vip_pass vs vip_pass_2 ✅ ĐÓNG một nửa: vip_pass = 210.000đ trên store; vip_pass_2 không được store trả giá ⇒ không phải sản phẩm Play ở bản này. Vì sao code vẫn giữ nó (khôi phục cho người đã mua?) vẫn UNKNOWN iap_prices_store.json; TryApplyPurchase 0x2B32204
Chỉ số offer 31-35 Bằng chứng phủ định: không product id nào trả về
Nhánh ShowRewardAd(8) trong UpgradeRebirth_Start_By_IAP Không thể đạt tới trong thực tế: hàm đặt is_UpgradeRebirth = is_UpgradeRebirth_IAP = 1 (word 257) rồi mới kiểm !is_UpgradeRebirth \|\| is_UpgradeRebirth_IAP ⇒ luôn đi nhánh Rebirth_FX.Start_Internal Hook 0x2b23668 dưới segment 1 để xác nhận 0 lần fire
Nhánh interstitial trong Monster_manager.SweepBonus_Get Không thể đạt tới: đặt cờ +194 = 1 rồi kiểm !cờ như trên
Admob.ShowRewardAd(int AdIndex) (0x2A59C04) — nút nào gọi UNKNOWN: 0 code-xref ⇒ nối từ scene bằng UnityEvent grep m_MethodName: ShowRewardAd trong Main.unity
Súng quảng cáo weapon_02 có nhánh nào trong cụm bắn của Cat không ✅ ĐÓNG — KHÔNG (G363): 12 RVA của cụm bắn đã quét, không có weapon_02 / AD_Type; súng thưởng chỉ chạm Gun_manager (§4.6) ⇒ không gì trong đường bắn route qua IAdService
Cơ chế kích hoạt Season reset và backend Redeem code UNKNOWN (kế thừa LIVEOPS_SURFACE §7) probe openpanel + bắt network
Nội dung /[endpoint] cho segment 12 thật trên production UNKNOWN — chỉ mới thấy segment 0 POST thử với test flag, hoặc Cheat_OnSegmentChanged (0x2AC96D0) rồi dump CachedConfigJson
Shop_manager.DiaRewards / Dia_FirstBuy_Used trên save mới ✅ ĐÓNG (G350): .cctor 0x2B36F9C cấp Dia_FirstBuy_Used = new bool[5]DiaRewards (blob SHA MATCH); Is_Dia_FirstBuy = 3 phép thử của binary (index < 0, Length <= index, !Used[index]), không null-guard TABLE.md hàng 17
Không quan sát thấy Gun_manager.Balance_Reload / Request_Sheet: 0 lần fire qua 3 phiên ⇒ bảng súng không remote-driven PROBE_RESULT.md §3 (G144 đã đóng)
Không quan sát thấy PlayFab.GetTitleData: 0 caller ⇒ TitleData không phải kênh liveops FABLE_ANALYSIS §U4

11. Raw evidence

  • [liveops catalog: PROBE_RESULT.md] — payload ServerConfig production thật (segment 0), sweep ngưỡng, đóng G144
  • [liveops catalog: 01_shop.png], 02_shop_scrolled.pnggiá + phần thưởng shop thật (VND)
  • [liveops catalog: phaseA_capture.json] — 9 419 node canvas, trạng thái active của mọi nút liveops
  • [liveops catalog: sweep.json] — bảng sweep ngưỡng 0→1000
  • [liveops catalog: PROBE_RESULT.md]thang bật/tắt IAA dưới segment 1 (§1.3), gate kép FreeCash (§1.4), biên quan sát "no fill" (§1.5), panel late-game (§2), lớp cổng tutorial (§6)
  • [liveops catalog: probe2d_capture.json] — bảng đo MaxStageDisplay → OfferWall/FreeCash/interstitial
  • [liveops catalog: iap_prices_store.json]33 giá VND thật dump read-only từ Localized_Price_return trên bản store chính hãng (dict_count 33, resolved 33)
  • [liveops catalog: 31_panel_Rebirth.png] — "Super Rebirth! ×3" + nút quảng cáo 1/1
  • [liveops catalog: 31_panel_Trip.png] — 4 tiền tệ, banner Mua → PrismCore, lịch map theo thứ
  • [liveops catalog: 30_all_unlocked_hud.png] — HUD sau khi mở đủ 7 feature
  • [liveops catalog: LIVEOPS_SURFACE.md] — 15 key ConfigResp, EmbeddedDefaults, Firebase RC, endpoint
  • [liveops catalog: UNLOCK_GATES.md] — thang Stage_Value_Max và công thức stageDisplay
  • [phân tích nội bộ] §U2, §B-6, §B-7 — cờ boost-kind, buff 300/900, ảnh hưởng segment
  • [evidence codegen: Rebirth.c]Rebirth() 0x2B22AE0 (đuôi: review ↔ interstitial)
  • [evidence codegen: PRESTIGE_RULES.md] — boost ×3, daily limit, kind 1/2/3
  • dump.csAdmob :585348, AD_Type :584959, Inter_Place :584976, FreeCash_manager :586919, Review_manager :588967, Buff_manager :592886, OfferWall_manager :599641, DailyGun :599310, PlusPack :599810, Shop_manager :600302, InApp_* enum :600059-600102, Mission_manager.is_Vip/is_Luxury :587346
  • stringliteral.json — product id, place, khoá PlayerPrefs, log ADX
  • [native: global-metadata.decrypted.dat] — blob khởi tạo mảng static (DiaRewards, StepUp.Gun_Rewards/Dia_Rewards, DailyGun.MaxCounts/PetCoin_Rewards/GunNum_Base)
  • [export AssetRipper: Main.unity:20647245-20647268] — giá trị Admob serialize trong scene
  • [restore project: Services] — ranh giới R1 fail-closed của bản restore