ZeroAd: CrazyGames SDK

Ad bypass with singleton SDK instance, event, and Unity support

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         ZeroAd: CrazyGames SDK
// @namespace    https://greasyfork.org/users/YOUR_USER_ID
// @version      4.4.1
// @description  Ad bypass with singleton SDK instance, event, and Unity support
// @author       ZeroAd Team
// @match        *://*.crazygames.com/*
// @match        *://crazygames.com/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

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

    const CONFIG = {
        DEBUG: true,
        REWARD_DELAY_MS: 500,   // Half a second feels natural
        MAX_HOOK_ATTEMPTS: 100,
        HOOK_RETRY_INTERVAL_MS: 200,
        AD_PLAYING_DURATION_MS: 5,
        SIMULATE_AD_ERROR: false
    };

    const log = (...args) => CONFIG.DEBUG && console.log('[ZeroAd]', ...args);
    const warn = (...args) => CONFIG.DEBUG && console.warn('[ZeroAd]', ...args);

    const hookState = {
        crazyGamesSDK: false,
        constructSDK: false,
        crazygamesAds: false,
        unityGameInstance: false,
        genericCrazySDK: false,
        crazySDKInstance: false,
        interceptedCalls: 0
    };

    // Singleton fake instance (created once)
    let fakeCrazySDKInstance = null;

    // ═══════════════════════════════════════════════════════════════
    // Fake CrazySDK Instance (singleton)
    // ═══════════════════════════════════════════════════════════════
    function createFakeCrazySDKInstance() {
        const listeners = {};
        let initCalled = false;
        let requestInProgress = false;

        function fireEvent(eventName, data = {}) {
            if (listeners[eventName]) {
                listeners[eventName].forEach(cb => {
                    try { cb(data); } catch (e) { warn(`Error in ${eventName} listener:`, e); }
                });
            }
        }

        return {
            init() {
                log('CrazySDK instance: init()');
                initCalled = true;
                setTimeout(() => {
                    fireEvent('initialized', {
                        userInfo: {
                            countryCode: "US",
                            browser: { name: "Chrome", version: "120" },
                            os: { name: "Windows", version: "10" },
                            device: { type: "desktop" }
                        }
                    });
                }, 50);
            },

            addEventListener(event, callback) {
                if (!listeners[event]) listeners[event] = [];
                listeners[event].push(callback);
                log(`CrazySDK instance: added listener for "${event}"`);
                if (event === 'initialized' && initCalled) {
                    setTimeout(() => callback({
                        userInfo: {
                            countryCode: "US",
                            browser: { name: "Chrome", version: "120" },
                            os: { name: "Windows", version: "10" },
                            device: { type: "desktop" }
                        }
                    }), 10);
                }
            },
            removeEventListener(event, callback) {
                if (listeners[event]) {
                    listeners[event] = listeners[event].filter(cb => cb !== callback);
                }
            },

            async requestAd(type = "midgame") {
                hookState.interceptedCalls++;
                log(`✓ INTERCEPTED: CrazySDK.requestAd("${type}")`);

                if (CONFIG.SIMULATE_AD_ERROR) {
                    fireEvent('adError', { error: 'Simulated error' });
                    return;
                }

                requestInProgress = true;
                fireEvent('adStarted', { adType: type });
                log('  → adStarted fired');

                return new Promise(resolve => {
                    setTimeout(() => {
                        requestInProgress = false;
                        fireEvent('adFinished', { adType: type });
                        log('  → adFinished fired');
                        log(`  → ✅ Reward granted for "${type}"`);
                        resolve();
                    }, CONFIG.REWARD_DELAY_MS);
                });
            },

            gameplayStart() { log('CrazySDK instance: gameplayStart() (stub)'); },
            gameplayStop() { log('CrazySDK instance: gameplayStop() (stub)'); },
            happytime() { log('CrazySDK instance: happytime() (stub)'); },
            sdkGameLoadingStart() { log('CrazySDK instance: sdkGameLoadingStart() (stub)'); },
            sdkGameLoadingStop() { log('CrazySDK instance: sdkGameLoadingStop() (stub)'); },

            async requestBanner(containers) {
                log('CrazySDK instance: requestBanner() (stub)', containers);
                if (Array.isArray(containers)) {
                    containers.forEach(c => {
                        setTimeout(() => fireEvent('bannerRendered', { containerId: c.containerId }), 50);
                    });
                }
            },
            async requestResponsiveBanner(containers) {
                log('CrazySDK instance: requestResponsiveBanner() (stub)', containers);
                if (Array.isArray(containers)) {
                    containers.forEach(c => {
                        setTimeout(() => fireEvent('bannerRendered', { containerId: c.id || c.containerId }), 50);
                    });
                }
            },
            clearBanner(containerId) { log(`CrazySDK instance: clearBanner("${containerId}") (stub)`); },
            clearAllBanners() { log('CrazySDK instance: clearAllBanners() (stub)'); },

            get hasAdblock() { return false; },
            adblockDetectionExecuted: true,

            inviteLink(params = {}) {
                log('CrazySDK instance: inviteLink() (stub)');
                const baseUrl = window.location.href.split('?')[0];
                const queryParams = new URLSearchParams(params);
                return `${baseUrl}?czy_invite=true&utm_source=invite&${queryParams.toString()}`;
            },

            get requestInProgress() { return requestInProgress; },
            set requestInProgress(val) { requestInProgress = val; }
        };
    }

    // ═══════════════════════════════════════════════════════════════
    // HOOK: CrazyGames.CrazySDK.getInstance() – SINGLETON FIX
    // ═══════════════════════════════════════════════════════════════
    function hookCrazySDKInstance() {
        if (hookState.crazySDKInstance) return true;

        // Create the singleton instance immediately
        fakeCrazySDKInstance = createFakeCrazySDKInstance();

        try {
            if (window.CrazyGames?.CrazySDK?.getInstance) {
                const originalGetInstance = window.CrazyGames.CrazySDK.getInstance;
                window.CrazyGames.CrazySDK.getInstance = function() {
                    log('✓ INTERCEPTED: CrazyGames.CrazySDK.getInstance()');
                    return fakeCrazySDKInstance;
                };
                hookState.crazySDKInstance = true;
                log('✓✓ CrazySDK.getInstance() HOOKED (existing singleton)');
                return true;
            }

            let _crazyGames = window.CrazyGames;
            Object.defineProperty(window, 'CrazyGames', {
                configurable: true,
                enumerable: true,
                get() { return _crazyGames; },
                set(newValue) {
                    log('CrazyGames object assigned');
                    _crazyGames = newValue;
                    if (newValue?.CrazySDK?.getInstance && !hookState.crazySDKInstance) {
                        const originalGetInstance = newValue.CrazySDK.getInstance;
                        newValue.CrazySDK.getInstance = function() {
                            log('✓ INTERCEPTED: CrazyGames.CrazySDK.getInstance() (deferred)');
                            return fakeCrazySDKInstance;
                        };
                        hookState.crazySDKInstance = true;
                        log('✓✓ CrazySDK.getInstance() HOOKED (deferred singleton)');
                    }
                }
            });

            log('✓ CrazySDK.getInstance() singleton trap installed');
            return false;
        } catch (error) {
            warn('Failed to hook CrazySDK.getInstance():', error);
            return false;
        }
    }

    // ═══════════════════════════════════════════════════════════════
    // Legacy hooks (unchanged, kept for compatibility)
    // ═══════════════════════════════════════════════════════════════
    function createFakeAdModule() {
        let _adPlaying = false;
        let _playingTimer = null;

        function dispatchRewardEvents() {
            const events = [
                'rewardedComplete', 'rewardGranted', 'adComplete',
                'crazy_ad_finished', 'adReward', 'adFinished'
            ];
            events.forEach(ev => {
                try { window.dispatchEvent(new CustomEvent(ev)); } catch (e) {}
            });
        }

        return {
            get isAdPlaying() { log(`isAdPlaying checked → ${_adPlaying}`); return _adPlaying; },
            get requestInProgress() { log('requestInProgress checked → false'); return false; },
            get hasAdblock() { log('hasAdblock checked → false'); return false; },
            async prefetchAd(type) { hookState.interceptedCalls++; log(`✓ INTERCEPTED: prefetchAd("${type}")`); return Promise.resolve(); },
            async requestAd(type, callbacks = {}) {
                hookState.interceptedCalls++; log(`✓ INTERCEPTED: requestAd("${type}")`);
                _adPlaying = true;
                if (_playingTimer) clearTimeout(_playingTimer);
                _playingTimer = setTimeout(() => { _adPlaying = false; }, CONFIG.AD_PLAYING_DURATION_MS);

                if (CONFIG.SIMULATE_AD_ERROR && typeof callbacks.adError === 'function') {
                    try { callbacks.adError({ code: 'INTERNAL_ERROR', message: 'Simulated error' }); log('  → adError() called'); } catch(e) {}
                    _adPlaying = false; clearTimeout(_playingTimer); return;
                }
                if (typeof callbacks.adStarted === 'function') { try { callbacks.adStarted(); log('  → adStarted() called'); } catch(e) {} }
                return new Promise(resolve => {
                    setTimeout(() => {
                        _adPlaying = false; clearTimeout(_playingTimer);
                        if (typeof callbacks.adFinished === 'function') { try { callbacks.adFinished(); log('  → adFinished() called'); } catch(e) {} }
                        dispatchRewardEvents();
                        log(`  → ✅ Reward granted for "${type}"`);
                        resolve();
                    }, CONFIG.REWARD_DELAY_MS);
                });
            },
            async hasAdblockEnabled() { hookState.interceptedCalls++; log('✓ INTERCEPTED: hasAdblockEnabled() → false'); return false; },
            addAdblockPopupListener() { log('addAdblockPopupListener (stub)'); },
            removeAdblockPopupListener() { log('removeAdblockPopupListener (stub)'); }
        };
    }

    function hookCrazyGamesSDK() {
        if (hookState.crazyGamesSDK) return true;
        try {
            if (window.CrazyGames?.SDK) {
                const fakeAd = createFakeAdModule();
                Object.defineProperty(window.CrazyGames.SDK, 'ad', { configurable: true, enumerable: true, get: () => fakeAd });
                hookState.crazyGamesSDK = true; log('✓✓ CrazyGames.SDK.ad HOOKED (existing)'); return true;
            }
            let _crazyGames = window.CrazyGames;
            Object.defineProperty(window, 'CrazyGames', {
                configurable: true, enumerable: true, get() { return _crazyGames; },
                set(newValue) { _crazyGames = newValue; if (newValue?.SDK && !hookState.crazyGamesSDK) { const fakeAd = createFakeAdModule(); Object.defineProperty(newValue.SDK, 'ad', { configurable: true, enumerable: true, get: () => fakeAd }); hookState.crazyGamesSDK = true; log('✓✓ CrazyGames.SDK.ad HOOKED (deferred)'); } }
            });
            log('✓ CrazyGames trap installed'); return false;
        } catch (error) { warn('Failed to hook CrazyGames.SDK:', error); return false; }
    }

    function hookConstructSDK() {
        if (hookState.constructSDK) return true;
        try {
            if (window.ConstructCrazySDK) {
                const fakeAd = createFakeAdModule();
                Object.defineProperty(window.ConstructCrazySDK, 'ad', { configurable: true, enumerable: true, get: () => fakeAd });
                hookState.constructSDK = true; log('✓✓ ConstructCrazySDK.ad HOOKED (existing)'); return true;
            }
            let _construct = window.ConstructCrazySDK;
            Object.defineProperty(window, 'ConstructCrazySDK', {
                configurable: true, enumerable: true, get() { return _construct; },
                set(newValue) { _construct = newValue; if (newValue && !hookState.constructSDK) { const fakeAd = createFakeAdModule(); Object.defineProperty(newValue, 'ad', { configurable: true, enumerable: true, get: () => fakeAd }); hookState.constructSDK = true; log('✓✓ ConstructCrazySDK.ad HOOKED (deferred)'); } }
            });
            log('✓ ConstructCrazySDK trap installed'); return false;
        } catch (error) { warn('Failed to hook ConstructCrazySDK:', error); return false; }
    }

    function hookGenericCrazySDK() {
        if (hookState.genericCrazySDK) return true;
        const target = window.CrazySDK || window.crazysdk;
        if (target) {
            const fakeAd = createFakeAdModule();
            if (target.ad) { Object.defineProperty(target, 'ad', { configurable: true, enumerable: true, get: () => fakeAd }); log('✓✓ CrazySDK.ad HOOKED'); }
            if (target.Ad) { Object.defineProperty(target, 'Ad', { configurable: true, enumerable: true, get: () => fakeAd }); log('✓✓ CrazySDK.Ad HOOKED'); }
            hookState.genericCrazySDK = true; return true;
        }
        ['CrazySDK', 'crazysdk'].forEach(propName => {
            if (hookState.genericCrazySDK) return;
            let _val = window[propName];
            Object.defineProperty(window, propName, {
                configurable: true, enumerable: true, get() { return _val; },
                set(newValue) { _val = newValue; if (newValue && !hookState.genericCrazySDK) { const fakeAd = createFakeAdModule(); if (newValue.ad) Object.defineProperty(newValue, 'ad', { configurable: true, enumerable: true, get: () => fakeAd }); if (newValue.Ad) Object.defineProperty(newValue, 'Ad', { configurable: true, enumerable: true, get: () => fakeAd }); hookState.genericCrazySDK = true; log(`✓✓ ${propName} HOOKED (deferred)`); } }
            });
        });
        log('✓ Generic CrazySDK trap installed'); return false;
    }

    function hookUnitySendMessage() {
        if (hookState.unityGameInstance) return true;
        const candidates = Object.keys(window).filter(key => { try { const val = window[key]; return val && typeof val === 'object' && typeof val.SendMessage === 'function'; } catch(e) { return false; } });
        for (const key of candidates) {
            const instance = window[key]; if (instance.__zaHooked) continue;
            const originalSend = instance.SendMessage;
            instance.SendMessage = function(gameObject, method, param) {
                if (method === 'AdEvent' && (param === 'adStarted' || param === 'adFinished' || param === 'adError')) { log(`✓ INTERCEPTED: Unity SendMessage`); hookState.interceptedCalls++; if (param === 'adStarted') { setTimeout(() => { try { originalSend.call(this, gameObject, method, 'adFinished'); } catch(e) {} }, CONFIG.REWARD_DELAY_MS); } return; }
                return originalSend.apply(this, arguments);
            };
            instance.__zaHooked = true; hookState.unityGameInstance = true; log(`✓✓ Unity SendMessage HOOKED`); return true;
        }
        return false;
    }

    function hookCrazygamesAds() {
        if (hookState.crazygamesAds) return true;
        const AD_METHODS = [
            'requestAd', 'requestAds', 'showAd', 'showRewardedAd',
            'displayAd', 'displayRewardedAd', 'displayMidrollAd',
            'requestRewardedAd', 'showRewardedVideo', 'requestRewardedVideo',
            'showInterstitial', 'requestOnly', 'render', 'preloadAd'
        ];
        function isAdMethod(prop) { return AD_METHODS.includes(prop); }
        function isAdblockMethod(prop) {
            const lower = prop.toLowerCase();
            return lower === 'hasadblock' || lower === 'hasadblockenabled' || lower === 'isadblockactive';
        }
        function createAdsProxy(target) {
            if (!target || target.__zaProxied) return target;
            const handler = {
                get(obj, prop) {
                    const original = obj[prop];
                    if (isAdMethod(prop) && typeof original === 'function') {
                        return function(...args) {
                            hookState.interceptedCalls++;
                            log(`✓ INTERCEPTED: CrazygamesAds.${prop}()`);
                            let onStarted = null, onFinished = null;
                            for (const arg of args) {
                                if (arg && typeof arg === 'object') {
                                    onStarted = arg.adStarted || arg.onAdStarted || arg.onStart;
                                    onFinished = arg.adFinished || arg.onAdFinished || arg.onComplete || arg.onReward;
                                }
                            }
                            if (typeof onStarted === 'function') { try { onStarted(); } catch(e) { warn('onStarted error:', e); } }
                            if (prop === 'requestOnly' || prop === 'preloadAd') { return Promise.resolve({ success: true }); }
                            return new Promise(resolve => {
                                setTimeout(() => {
                                    if (typeof onFinished === 'function') { try { onFinished(); } catch(e) { warn('onFinished error:', e); } }
                                    const events = ['rewardedComplete', 'adComplete', 'crazy_ad_finished'];
                                    events.forEach(ev => { try { window.dispatchEvent(new CustomEvent(ev)); } catch(e) {} });
                                    resolve({ success: true, completed: true });
                                }, CONFIG.REWARD_DELAY_MS);
                            });
                        };
                    }
                    if (isAdblockMethod(prop) && typeof original === 'function') {
                        return async () => { log(`✓ INTERCEPTED: CrazygamesAds.${prop}() → false`); return false; };
                    }
                    return original;
                }
            };
            const proxied = new Proxy(target, handler);
            proxied.__zaProxied = true;
            return proxied;
        }
        try {
            if (window.CrazygamesAds) {
                window.CrazygamesAds = createAdsProxy(window.CrazygamesAds);
                hookState.crazygamesAds = true;
                log('✓✓ CrazygamesAds HOOKED (existing)');
                return true;
            }
            let _ads = window.CrazygamesAds;
            Object.defineProperty(window, 'CrazygamesAds', {
                configurable: true, enumerable: true,
                get() { return _ads; },
                set(newValue) {
                    log('CrazygamesAds object assigned');
                    _ads = newValue ? createAdsProxy(newValue) : newValue;
                    if (_ads && !hookState.crazygamesAds) {
                        hookState.crazygamesAds = true;
                        log('✓✓ CrazygamesAds HOOKED (deferred)');
                    }
                }
            });
            log('✓ CrazygamesAds trap installed');
            return false;
        } catch (error) {
            warn('Failed to hook CrazygamesAds:', error);
            return false;
        }
    }

    // ═══════════════════════════════════════════════════════════════
    // ORCHESTRATION
    // ═══════════════════════════════════════════════════════════════
    let hookAttempts = 0;
    let retryTimer = null;
    let observer = null;

    function allHooksInstalled() {
        return hookState.crazySDKInstance ||
               (hookState.crazyGamesSDK && hookState.constructSDK && hookState.crazygamesAds &&
                hookState.genericCrazySDK && hookState.unityGameInstance);
    }

    function attemptHooks() {
        hookAttempts++;

        // NEW: Primary hook for the event-listener pattern
        hookCrazySDKInstance();

        // Legacy hooks
        hookCrazyGamesSDK();
        hookConstructSDK();
        hookCrazygamesAds();
        hookGenericCrazySDK();
        hookUnitySendMessage();

        if (allHooksInstalled()) {
            log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
            log('✅ ALL HOOKS INSTALLED SUCCESSFULLY');
            log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
            cleanup();
            return true;
        }

        if (hookAttempts >= CONFIG.MAX_HOOK_ATTEMPTS) {
            warn('⚠️  Hook installation incomplete after max attempts');
            warn('Hooked:', hookState);
            cleanup();
            return false;
        }
        return false;
    }

    function cleanup() {
        if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
        if (observer) { observer.disconnect(); observer = null; log('✓ MutationObserver disconnected'); }
    }

    function setupMutationObserver() {
        if (!CONFIG.MUTATION_OBSERVER_ENABLED) return;
        observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.tagName === 'SCRIPT') {
                        const src = node.src || '';
                        if (src.includes('crazygames') || src.includes('sdk')) {
                            log(`Script detected: ${src.substring(0, 80)}...`);
                            node.addEventListener('load', () => {
                                log('SDK script loaded, re-attempting hooks');
                                setTimeout(attemptHooks, 50);
                            });
                        }
                    }
                }
            }
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
        log('✓ MutationObserver watching for SDK scripts');
    }

    function initialize() {
        log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        log('ZeroAd v4.4.0 - Full CrazySDK Instance & Event Support');
        log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

        attemptHooks();
        setupMutationObserver();

        retryTimer = setInterval(() => {
            if (!allHooksInstalled() && hookAttempts < CONFIG.MAX_HOOK_ATTEMPTS) {
                attemptHooks();
                log(`Hook attempt ${hookAttempts}/${CONFIG.MAX_HOOK_ATTEMPTS}`);
            } else {
                cleanup();
            }
        }, CONFIG.HOOK_RETRY_INTERVAL_MS);

        setTimeout(() => {
            log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
            log('Status Report:');
            log('  Hooks installed:', hookState);
            log('  Intercepted calls:', hookState.interceptedCalls);
            log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        }, 5000);
    }

    initialize();

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', attemptHooks);
    }

})();