Greasy Fork is available in English.

ZeroAd: Loader

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

As of 22.08.2026. See апошняя версія.

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();