ZeroAd: Loader

Hybrid detector loader – hostname + globals + console logs + script src (Full coverage)

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         ZeroAd: Loader
// @namespace    https://greasyfork.org/en/users/1483567-vujohn123
// @version      6.0.0
// @description  Hybrid detector loader – hostname + globals + console logs + script src (Full coverage)
// @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 };
    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,
        'y8': 592513,
        'facebook': null,       // TODO
        'gamemonetize': null,
        'gamepix': null,
        'kongregate': null,
        'lagged': null,
        'playhop': null,
        'gameflare': null,
        'ezoic': null
    };
    const FALLBACK_PLATFORM = 'adinplay';
    const loaded = new Set();
    const pending = new Set();

    // ============================================================
    //  DETECTION ENGINE – HYBRID (FULL COVERAGE)
    // ============================================================
    function detectPlatforms() {
        const detected = new Set();
        const host = window.location.hostname.toLowerCase();

        // ---------- 1. Hostname-based ----------
        const hostMap = {
            'poki.com': 'poki',
            'crazygames.com': 'crazygames',
            'gamevui.vn': 'gamevui',
            'gamedistribution.com': 'gamedistribution',
            'playgama.com': 'playgama',
            'yandex': 'yandex',
            'games.yandex': 'yandex',
            'y8.com': 'y8',
            'y8games.com': 'y8',
            'gamemonetize.com': 'gamemonetize',
            'gamepix.com': 'gamepix',
            'kongregate.com': 'kongregate',
            'lagged.com': 'lagged',
            'playhop.com': 'playhop',
            'gameflare.com': 'gameflare'
        };
        for (const [key, value] of Object.entries(hostMap)) {
            if (host.includes(key)) detected.add(value);
        }

        // ---------- 2. Global objects (MỞ RỘNG) ----------
        // GameDistribution
        if (window.GD_OPTIONS || window.gdApi || window.GDSdk) {
            detected.add('gamedistribution');
        }
        // Y8
        if (window.ID || window.ID?.ads || window.ID?.Event || window.y8 || window.y8?.sdk) {
            detected.add('y8');
        }
        // CrazyGames
        if (window.CrazyGames || window.CrazyGames?.SDK || window.CrazySDK || window.CrazygamesAds) {
            detected.add('crazygames');
        }
        // Poki
        if (window.PokiSDK) {
            detected.add('poki');
        }
        // PlayGama
        if (window.bridge || window.PlayGama || window.pg || window.PG) {
            detected.add('playgama');
        }
        // Yandex
        if (window.YaGames || window.ysdk) {
            detected.add('yandex');
        }
        // GameVui
        if (window.GVAdBreak) {
            detected.add('gamevui');
        }
        // Facebook Instant Games
        if (window.FBInstant) {
            detected.add('facebook');
        }
        // Kongregate
        if (window.KongregateAPI) {
            detected.add('kongregate');
        }
        // Ezoic
        if (window.EzoicAd) {
            detected.add('ezoic');
        }

        // ---------- 3. Unity detection ----------
        if (window.unityInstance || window.gameInstance || window.UnityLoader ||
            document.querySelector('canvas.unity-canvas')) {
            detected.add('unity');
        }

        // ---------- 4. Script src detection (MỞ RỘNG) ----------
        const scripts = document.querySelectorAll('script[src]');
        for (const s of scripts) {
            const src = s.src.toLowerCase();
            if (src.includes('poki')) detected.add('poki');
            if (src.includes('crazygames') || src.includes('crazygames-sdk')) detected.add('crazygames');
            if (src.includes('gamevui')) detected.add('gamevui');
            if (src.includes('gamedistribution') || src.includes('main.min.js')) detected.add('gamedistribution');
            if (src.includes('playgama')) detected.add('playgama');
            if (src.includes('yandex')) detected.add('yandex');
            if (src.includes('y8') || src.includes('id.net') || src.includes('gamebreakbeta')) detected.add('y8');
            if (src.includes('gamemonetize')) detected.add('gamemonetize');
            if (src.includes('gamepix')) detected.add('gamepix');
            if (src.includes('kongregate')) detected.add('kongregate');
            if (src.includes('lagged')) detected.add('lagged');
            if (src.includes('playhop')) detected.add('playhop');
            if (src.includes('gameflare')) detected.add('gameflare');
            if (src.includes('ezoic')) detected.add('ezoic');
            if (src.includes('fbinstant') || src.includes('facebook')) detected.add('facebook');
        }

        // ---------- 5. Fallback ----------
        if (detected.size === 0) detected.add(FALLBACK_PLATFORM);

        return detected;
    }

    // ============================================================
    //  CONSOLE LOG SNIFFER (MỞ RỘNG)
    // ============================================================
    function setupConsoleSniffer() {
        const originalLog = console.log;
        const originalWarn = console.warn;
        const originalError = console.error;

        const sniff = (args) => {
            if (!args || args.length === 0) return;
            const msg = args.join(' ');
            if (typeof msg !== 'string') return;

            const lower = msg.toLowerCase();
            let detected = false;

            // GameDistribution
            if (lower.includes('gamedistribution') || lower.includes('gd-api') || lower.includes('gd_')) {
                loadPlatform('gamedistribution');
                detected = true;
            }
            // Y8
            if (lower.includes('y8') || lower.includes('id.net') || lower.includes('id.ads')) {
                loadPlatform('y8');
                detected = true;
            }
            // CrazyGames
            if (lower.includes('crazygames')) {
                loadPlatform('crazygames');
                detected = true;
            }
            // Poki
            if (lower.includes('poki')) {
                loadPlatform('poki');
                detected = true;
            }
            // PlayGama
            if (lower.includes('playgama') || lower.includes('bridge')) {
                loadPlatform('playgama');
                detected = true;
            }
            // Yandex
            if (lower.includes('yandex') || lower.includes('ysdk') || lower.includes('yagames')) {
                loadPlatform('yandex');
                detected = true;
            }
            // Unity
            if (lower.includes('unity') || lower.includes('unityengine') || lower.includes('unityplayer')) {
                loadPlatform('unity');
                detected = true;
            }
            // Facebook Instant Games
            if (lower.includes('fbinstant') || lower.includes('facebook instant')) {
                loadPlatform('facebook');
                detected = true;
            }
            // Kongregate
            if (lower.includes('kongregate')) {
                loadPlatform('kongregate');
                detected = true;
            }
            // Ezoic
            if (lower.includes('ezoic')) {
                loadPlatform('ezoic');
                detected = true;
            }
        };

        console.log = function(...args) {
            sniff(args);
            return originalLog.apply(this, args);
        };
        console.warn = function(...args) {
            sniff(args);
            return originalWarn.apply(this, args);
        };
        console.error = function(...args) {
            sniff(args);
            return originalError.apply(this, args);
        };
        log.debug('Console sniffer installed');
    }

    // ============================================================
    //  LOADER ENGINE
    // ============================================================
    function loadPlatform(platformKey) {
        if (!platformKey) return;
        if (loaded.has(platformKey) || pending.has(platformKey)) return;
        const scriptId = SCRIPT_MAP[platformKey];
        if (!scriptId) {
            log.warn(`No script ID for platform: ${platformKey}`);
            return;
        }
        pending.add(platformKey);
        const url = `https://greasyfork.org/scripts/${scriptId}/code/${scriptId}.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 < CONFIG.MAX_RETRIES) {
                    const delay = CONFIG.RETRY_DELAY * Math.pow(2, attempt);
                    log.warn(`Retry ${platformKey} in ${delay}ms (attempt ${attempt+1}/${CONFIG.MAX_RETRIES})`);
                    setTimeout(() => tryLoad(attempt + 1), delay);
                } else {
                    pending.delete(platformKey);
                    log.error(`Failed to load ${platformKey} after ${CONFIG.MAX_RETRIES} attempts`);
                }
            };
            script.src = url;
            (document.head || document.documentElement).appendChild(script);
        };
        tryLoad(0);
    }

    // ============================================================
    //  TRAPS & OBSERVERS (MỞ RỘNG)
    // ============================================================
    function installGlobalTrap() {
        const sdkNames = [
            // GameDistribution
            'GD_OPTIONS', 'gdApi', 'GDSdk',
            // Y8
            'ID', 'y8',
            // CrazyGames
            'CrazyGames', 'CrazySDK', 'CrazygamesAds',
            // Poki
            'PokiSDK',
            // PlayGama
            'bridge', 'PlayGama', 'pg', 'PG',
            // Yandex
            'YaGames', 'ysdk',
            // GameVui
            'GVAdBreak',
            // Unity
            'unityInstance', 'gameInstance', 'UnityLoader',
            // Facebook
            'FBInstant',
            // Kongregate
            'KongregateAPI',
            // Ezoic
            'EzoicAd'
        ];
        sdkNames.forEach(name => {
            let _value = window[name];
            Object.defineProperty(window, name, {
                configurable: true,
                enumerable: true,
                get() { return _value; },
                set(newVal) {
                    _value = newVal;
                    const detected = detectPlatforms();
                    detected.forEach(key => loadPlatform(key));
                }
            });
        });
        log.debug('Global traps installed');
    }

    function setupObserver() {
        const observer = new MutationObserver(() => {
            const detected = detectPlatforms();
            detected.forEach(key => loadPlatform(key));
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
        log.debug('MutationObserver installed');
    }

    // ============================================================
    //  INIT
    // ============================================================
    function init() {
        setupConsoleSniffer();
        installGlobalTrap();
        setupObserver();

        const initial = detectPlatforms();
        initial.forEach(key => loadPlatform(key));

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                const later = detectPlatforms();
                later.forEach(key => loadPlatform(key));
            });
        }

        log.info('Ready, waiting for events...');
    }

    if (document.head) {
        init();
    } else {
        const obs = new MutationObserver(() => {
            if (document.head) {
                obs.disconnect();
                init();
            }
        });
        obs.observe(document.documentElement, { childList: true, subtree: true });
    }
})();