ZeroAd: Loader

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

Versión del día 22/8/2026. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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 });
    }

})();