ZeroAd: Loader

Hybrid detection loader with console log reading & multi-platform support

Versão de: 22/08/2026. Veja: a última versão.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         ZeroAd: Loader
// @namespace    https://greasyfork.org/en/users/1483567-vujohn123
// @version      5.0.0
// @description  Hybrid detection loader with console log reading & multi-platform support
// @author       ZeroAd Team
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    if (window.__zaLoaderLoaded) return;
    window.__zaLoaderLoaded = true;

    const CONFIG = {
        DEBUG: true,
        RETRY_DELAY: 100,
        MAX_RETRIES: 5,
        DETECTION_INTERVAL: 200,    // ms giữa các lần quét
        MAX_DETECTION_ATTEMPTS: 30, // ~6 giây
    };
    const PREFIX = '[ZeroAd:Loader]';
    const log = {
        info: (...args) => console.log(PREFIX, 'ℹ️', ...args),
        debug: (...args) => CONFIG.DEBUG && console.log(PREFIX, '🐛', ...args),
        warn: (...args) => console.warn(PREFIX, '⚠️', ...args),
        error: (...args) => console.error(PREFIX, '❌', ...args),
        success: (...args) => console.log(PREFIX, '✅', ...args),
    };

    // ============================================================
    //  PLATFORM MAP
    // ============================================================
    const SCRIPT_MAP = {
        'poki': 579407,
        'crazygames': 579404,
        'adinplay': 579403,
        'gamevui': 579405,
        'unity': 583938,
        'gamedistribution': 583939,
        'playgama': 592446,
        'yandex': 592456,
    };
    const FALLBACK_PLATFORM = 'adinplay';

    const loaded = new Set();
    const pending = new Set();
    let detectionCount = 0;

    // ============================================================
    //  LOAD SCRIPT VỚI RETRY
    // ============================================================
    function loadScriptWithRetry(platformKey, retries = CONFIG.MAX_RETRIES, delay = CONFIG.RETRY_DELAY) {
        const id = SCRIPT_MAP[platformKey];
        if (!id || loaded.has(platformKey) || pending.has(platformKey)) return;
        pending.add(platformKey);
        const url = `https://greasyfork.org/scripts/${id}/code/${id}.user.js`;
        const script = document.createElement('script');
        const tryLoad = (attempt) => {
            script.onload = () => {
                loaded.add(platformKey);
                pending.delete(platformKey);
                log.success(`Loaded ${platformKey}`);
            };
            script.onerror = () => {
                if (attempt < retries) {
                    const nextDelay = delay * Math.pow(2, attempt);
                    log.warn(`Retry ${platformKey} in ${nextDelay}ms (attempt ${attempt+1}/${retries})`);
                    setTimeout(() => tryLoad(attempt + 1), nextDelay);
                } else {
                    pending.delete(platformKey);
                    log.error(`Failed to load ${platformKey} after ${retries} attempts`);
                }
            };
            script.src = url;
            (document.head || document.documentElement).appendChild(script);
        };
        tryLoad(0);
    }

    // ============================================================
    //  DETECTION ENGINE – Hybrid
    // ============================================================
    function detectPlatforms() {
        const detected = new Set();
        const host = window.location.hostname.toLowerCase();
        const doc = document;

        // ----- 1. Hostname-based -----
        if (host.includes('poki.com')) detected.add('poki');
        if (host.includes('crazygames.com')) detected.add('crazygames');
        if (host.includes('gamevui.vn')) detected.add('gamevui');
        if (host.includes('gamedistribution.com')) detected.add('gamedistribution');
        if (host.includes('playgama.com')) detected.add('playgama');
        if (host.includes('yandex') || host.includes('games.yandex')) detected.add('yandex');

        // ----- 2. Global objects / classes -----
        if (window.PokiSDK) detected.add('poki');
        if (window.CrazyGames || window.CrazygamesAds) detected.add('crazygames');
        if (window.GVAdBreak) detected.add('gamevui');
        if (window.GDSdk || window.gdApi) detected.add('gamedistribution');
        if (window.PlayGama || window.playgama || window.pg || window.PG) detected.add('playgama');
        if (window.ysdk || window.YaGames) detected.add('yandex');

        // ----- 3. Unity detection (by object or canvas) -----
        if (window.unityInstance || window.gameInstance || window.UnityLoader) {
            detected.add('unity');
        } else {
            // Tìm canvas có class unity-canvas hoặc WebGL context
            const canvases = doc.querySelectorAll('canvas');
            for (const canvas of canvases) {
                if (canvas.classList.contains('unity-canvas')) {
                    detected.add('unity');
                    break;
                }
                try {
                    const ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
                    if (ctx) {
                        // Nếu canvas có WebGL context và không phải là canvas của quảng cáo
                        detected.add('unity');
                        break;
                    }
                } catch(e) {}
            }
        }

        // ----- 4. Script src scanning -----
        const scripts = doc.querySelectorAll('script[src]');
        for (const script of scripts) {
            const src = script.src.toLowerCase();
            if (src.includes('poki.com/sdk')) detected.add('poki');
            if (src.includes('sdk.crazygames.com')) detected.add('crazygames');
            if (src.includes('gamevui.vn')) detected.add('gamevui');
            if (src.includes('gamedistribution.com')) detected.add('gamedistribution');
            if (src.includes('playgama.com')) detected.add('playgama');
            if (src.includes('yandex') || src.includes('games.yandex')) detected.add('yandex');
            if (src.includes('unity')) detected.add('unity');
        }

        // ----- 5. Meta tags / window properties -----
        const metaPlatform = doc.querySelector('meta[name="platform"]');
        if (metaPlatform) {
            const p = metaPlatform.getAttribute('content');
            if (p && SCRIPT_MAP[p]) detected.add(p);
        }
        if (window.gamePlatform && SCRIPT_MAP[window.gamePlatform]) {
            detected.add(window.gamePlatform);
        }

        // ----- 6. Console log reader (đã được tích hợp bên dưới) -----
        // Console logs sẽ được xử lý qua sự kiện, sẽ gọi detectPlatforms() lại nếu cần

        // Nếu không có gì -> fallback
        if (detected.size === 0) {
            detected.add(FALLBACK_PLATFORM);
        }

        return detected;
    }

    // ============================================================
    //  CONSOLE LOG READER – Lắng nghe logs để phát hiện platform
    // ============================================================
    let consoleLogDetected = new Set();
    function setupConsoleReader() {
        // Lưu trữ các hàm console gốc
        const originalLog = console.log;
        const originalWarn = console.warn;
        const originalError = console.error;

        const checkLog = (args) => {
            if (!args || args.length === 0) return;
            const msg = args.join(' ').toLowerCase();
            let extra = new Set();

            // Các pattern đặc trưng
            if (msg.includes('poki') || msg.includes('poki sdk')) extra.add('poki');
            if (msg.includes('crazygames') || msg.includes('crazygames sdk')) extra.add('crazygames');
            if (msg.includes('gamevui')) extra.add('gamevui');
            if (msg.includes('gamedistribution')) extra.add('gamedistribution');
            if (msg.includes('playgama')) extra.add('playgama');
            if (msg.includes('yandex') || msg.includes('ysdk') || msg.includes('yagames')) extra.add('yandex');
            if (msg.includes('unity') || msg.includes('unity webgl')) extra.add('unity');
            if (msg.includes('adinplay')) extra.add('adinplay');

            // Nếu có phát hiện mới, trigger re-detection
            if (extra.size > 0) {
                for (const p of extra) {
                    if (!consoleLogDetected.has(p)) {
                        consoleLogDetected.add(p);
                        log.debug(`Console log detected: ${p}`);
                        // Kích hoạt quét lại ngay
                        scheduleDetection();
                    }
                }
            }
        };

        console.log = function(...args) {
            originalLog.apply(console, args);
            checkLog(args);
        };
        console.warn = function(...args) {
            originalWarn.apply(console, args);
            checkLog(args);
        };
        console.error = function(...args) {
            originalError.apply(console, args);
            checkLog(args);
        };

        // Cũng có thể dùng sự kiện nếu có (một số trình duyệt hỗ trợ)
        // Không cần vì đã override hàm
        log.debug('Console reader installed');
    }

    // ============================================================
    //  DEBOUNCED DETECTION
    // ============================================================
    let detectionTimer = null;
    function scheduleDetection() {
        if (detectionTimer) {
            clearTimeout(detectionTimer);
        }
        detectionTimer = setTimeout(() => {
            detectionTimer = null;
            runDetection();
        }, 100);
    }

    function runDetection() {
        const platforms = detectPlatforms();
        log.debug('Detection result:', [...platforms]);
        for (const key of platforms) {
            if (!loaded.has(key) && !pending.has(key)) {
                loadScriptWithRetry(key);
            }
        }
        detectionCount++;
        // Nếu đã quét đủ số lần và vẫn không thấy platform mới -> dừng polling
        if (detectionCount >= CONFIG.MAX_DETECTION_ATTEMPTS) {
            log.debug('Max detection attempts reached, stopping periodic checks');
            if (window.__zaDetectionInterval) {
                clearInterval(window.__zaDetectionInterval);
                window.__zaDetectionInterval = null;
            }
        }
    }

    // ============================================================
    //  MUTATION OBSERVER
    // ============================================================
    function setupObserver() {
        const observer = new MutationObserver(() => {
            scheduleDetection();
        });
        observer.observe(document.documentElement, { childList: true, subtree: true, attributes: false });
        log.debug('MutationObserver installed');
    }

    // ============================================================
    //  GLOBAL TRAPS
    // ============================================================
    function installTraps() {
        const sdkNames = [
            'PokiSDK', 'CrazyGames', 'CrazygamesAds', 'GVAdBreak',
            'GDSdk', 'gdApi', 'unityInstance', 'gameInstance', 'UnityLoader',
            'PlayGama', 'playgama', 'pg', 'PG',
            'ysdk', 'YaGames'
        ];
        sdkNames.forEach(name => {
            let _value = window[name];
            Object.defineProperty(window, name, {
                configurable: true,
                enumerable: true,
                get() { return _value; },
                set(newVal) {
                    _value = newVal;
                    log.debug(`Trap: window.${name} assigned`);
                    scheduleDetection();
                }
            });
        });
        log.debug('Global traps installed');
    }

    // ============================================================
    //  PERIODIC SCAN
    // ============================================================
    function startPeriodicScan() {
        if (window.__zaDetectionInterval) return;
        window.__zaDetectionInterval = setInterval(() => {
            // Chỉ quét nếu chưa đạt max attempts
            if (detectionCount < CONFIG.MAX_DETECTION_ATTEMPTS) {
                runDetection();
            } else {
                // Nếu đã quét đủ mà không có gì mới, có thể dừng
                // Nhưng vẫn để cho các trap và observer kích hoạt lại nếu cần
            }
        }, CONFIG.DETECTION_INTERVAL);
        log.debug(`Periodic scan started (every ${CONFIG.DETECTION_INTERVAL}ms)`);
    }

    // ============================================================
    //  INIT
    // ============================================================
    function init() {
        installTraps();
        setupConsoleReader();
        setupObserver();
        startPeriodicScan();

        // Quét ngay lập tức
        setTimeout(() => runDetection(), 10);

        // Khi DOMContentLoaded, quét lại để bắt các script tải muộn
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                log.debug('DOMContentLoaded – re-detecting');
                runDetection();
            });
        } else {
            // Nếu đã sẵn sàng, quét lại sau 500ms
            setTimeout(runDetection, 500);
        }

        log.info('Hybrid Loader initialized');
    }

    // Chờ head tồn tại
    if (document.head) {
        init();
    } else {
        const headObserver = new MutationObserver(() => {
            if (document.head) {
                headObserver.disconnect();
                init();
            }
        });
        headObserver.observe(document.documentElement, { childList: true, subtree: true });
    }

})();