ZeroAd: Unity WebGL

Production-grade Unity SDKManager callback interceptor - force rewards

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Advertisement:

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

Advertisement:

// ==UserScript==
// @name         ZeroAd: Unity WebGL
// @namespace    https://greasyfork.org/users/zeroadv4
// @version      4.0.0
// @description  Production-grade Unity SDKManager callback interceptor - force rewards
// @author       ZeroAd Team
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // ═══════════════════════════════════════════════════════════════
    // CONFIGURATION
    // ═══════════════════════════════════════════════════════════════
    const CONFIG = {
        DEBUG: true,
        SCAN_INTERVAL_MS: 2000,
        MAX_SCANS: 30 // 60 seconds total
    };

    const METRICS = {
        patchedInstances: 0,
        interceptedCalls: 0,
        forcedRewards: 0
    };

    // ═══════════════════════════════════════════════════════════════
    // UNITY SDK METHOD TARGETS
    // ═══════════════════════════════════════════════════════════════
    const SDK_METHODS = [
        'OnVideoAdEnded',
        'OnVideoAdError',
        'OnVideoAdSkipped',
        'OnRewardedAdComplete',
        'OnRewardedAdFailed',
        'OnAdComplete',
        'OnAdError',
        'OnAdSkipped',
        'AdComplete',
        'AdFailed',
        'AdSkipped',
        'RewardGranted',
        'RewardFailed'
    ];

    const SDK_OBJECTS = [
        'SDKManager',
        'AdsManager',
        'AdManager',
        'RewardManager',
        'VideoAdManager'
    ];

    // ═══════════════════════════════════════════════════════════════
    // UTILITY LAYER
    // ═══════════════════════════════════════════════════════════════
    const log = (...args) => CONFIG.DEBUG && console.log('[ZeroAd:Unity]', ...args);
    const warn = (...args) => CONFIG.DEBUG && console.warn('[ZeroAd:Unity]', ...args);
    const error = (...args) => console.error('[ZeroAd:Unity]', ...args);

    // ═══════════════════════════════════════════════════════════════
    // HOOK LAYER
    // ═══════════════════════════════════════════════════════════════

    /**
     * Patch Unity instance SendMessage to force reward callbacks
     */
    function patchUnityInstance(instance) {
        if (!instance || instance.__zaPatched || typeof instance.SendMessage !== 'function') {
            return false;
        }

        try {
            const originalSendMessage = instance.SendMessage;

            instance.SendMessage = function(objectName, methodName, param) {
                METRICS.interceptedCalls++;

                // Check if this is an SDK callback
                const isSDKObject = SDK_OBJECTS.includes(objectName);
                const isSDKMethod = SDK_METHODS.includes(methodName);

                if (isSDKObject && isSDKMethod) {
                    // Check if parameter indicates failure
                    const isFalse = param === 'false' ||
                                   param === false ||
                                   param === '0' ||
                                   param === 0;

                    if (isFalse) {
                        log(`${objectName}.${methodName}("${param}") → forcing "true"`);
                        param = 'true';
                        METRICS.forcedRewards++;
                    } else {
                        log(`${objectName}.${methodName}("${param}") → pass through`);
                    }
                }

                // Call original with potentially modified parameter
                return originalSendMessage.call(this, objectName, methodName, param);
            };

            instance.__zaPatched = true;
            METRICS.patchedInstances++;
            log('✓ Unity instance patched');
            return true;

        } catch (e) {
            error('Failed to patch Unity instance:', e);
            return false;
        }
    }

    /**
     * Scan for Unity instances across multiple possible locations
     */
    function scanAndPatchInstances() {
        let foundAny = false;

        // Check window.unityInstance (most common)
        if (window.unityInstance && !window.unityInstance.__zaPatched) {
            if (patchUnityInstance(window.unityInstance)) {
                foundAny = true;
            }
        }

        // Check window.gameInstance (alternative naming)
        if (window.gameInstance && !window.gameInstance.__zaPatched) {
            if (patchUnityInstance(window.gameInstance)) {
                foundAny = true;
            }
        }

        // Check window.unity (another variant)
        if (window.unity && typeof window.unity.SendMessage === 'function' && !window.unity.__zaPatched) {
            if (patchUnityInstance(window.unity)) {
                foundAny = true;
            }
        }

        // Check for instances in UnityLoader namespace
        if (window.UnityLoader?.instances) {
            Object.values(window.UnityLoader.instances).forEach(instance => {
                if (instance && !instance.__zaPatched) {
                    if (patchUnityInstance(instance)) {
                        foundAny = true;
                    }
                }
            });
        }

        return foundAny;
    }

    // ═══════════════════════════════════════════════════════════════
    // PROPERTY TRAPS
    // ═══════════════════════════════════════════════════════════════

    /**
     * Install property trap for window.unityInstance
     */
    function installUnityInstanceTrap() {
        let _unityInstance = window.unityInstance;

        Object.defineProperty(window, 'unityInstance', {
            configurable: true,
            enumerable: true,
            get() {
                return _unityInstance;
            },
            set(newValue) {
                if (newValue && !newValue.__zaPatched) {
                    log('unityInstance assigned, patching...');
                    patchUnityInstance(newValue);
                }
                _unityInstance = newValue;
            }
        });

        log('✓ unityInstance trap installed');
    }

    /**
     * Install property trap for window.gameInstance
     */
    function installGameInstanceTrap() {
        let _gameInstance = window.gameInstance;

        Object.defineProperty(window, 'gameInstance', {
            configurable: true,
            enumerable: true,
            get() {
                return _gameInstance;
            },
            set(newValue) {
                if (newValue && !newValue.__zaPatched) {
                    log('gameInstance assigned, patching...');
                    patchUnityInstance(newValue);
                }
                _gameInstance = newValue;
            }
        });

        log('✓ gameInstance trap installed');
    }

    // ═══════════════════════════════════════════════════════════════
    // ORCHESTRATION LAYER
    // ═══════════════════════════════════════════════════════════════
    let scanCount = 0;
    let scanInterval = null;

    /**
     * Periodic scan for Unity instances
     */
    function startPeriodicScan() {
        scanInterval = setInterval(() => {
            scanCount++;

            const foundNew = scanAndPatchInstances();

            if (foundNew) {
                log(`Found and patched instance(s) on scan ${scanCount}`);
            }

            // Stop scanning after max attempts
            if (scanCount >= CONFIG.MAX_SCANS) {
                log(`Stopping periodic scan after ${scanCount} attempts`);
                clearInterval(scanInterval);
            }

        }, CONFIG.SCAN_INTERVAL_MS);

        log('✓ Periodic scan started');
    }

    /**
     * Setup mutation observer to detect Unity loader scripts
     */
    function setupMutationObserver() {
        const 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('unity') ||
                            src.includes('Build') ||
                            src.includes('loader.js')) {
                            log('Unity script detected:', src.substring(0, 80));

                            node.addEventListener('load', () => {
                                log('Unity script loaded, scanning for instances');
                                setTimeout(scanAndPatchInstances, 100);
                            });
                        }
                    }

                    // Check for canvas elements (Unity render target)
                    if (node.tagName === 'CANVAS') {
                        log('Canvas element added, scanning for Unity instances');
                        setTimeout(scanAndPatchInstances, 100);
                    }
                }
            }
        });

        if (document.documentElement) {
            observer.observe(document.documentElement, {
                childList: true,
                subtree: true
            });
        }

        log('✓ MutationObserver active');
    }

    // ═══════════════════════════════════════════════════════════════
    // INITIALIZATION
    // ═══════════════════════════════════════════════════════════════
    function initialize() {
        log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
        log('ZeroAd: Unity WebGL Reward Fix v4.0.0');
        log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

        // Install property traps
        installUnityInstanceTrap();
        installGameInstanceTrap();

        // Immediate scan
        scanAndPatchInstances();

        // Start periodic scanning
        startPeriodicScan();

        // Setup mutation observer
        setupMutationObserver();

        // Expose metrics
        window._zaMetrics = window._zaMetrics || {};
        window._zaMetrics.unity = METRICS;

        // Status report
        setTimeout(() => {
            log('━━━ Status Report ━━━');
            log('Metrics:', METRICS);
            log('━━━━━━━━━━━━━━━━━━━━━');
        }, 10000);
    }

    // Run immediately
    initialize();

    // Defensive: re-run on DOM ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', scanAndPatchInstances);
    }

})();