ZeroAd: Unity WebGL

Production-grade Unity SDKManager callback interceptor - force rewards

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 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)

Advertisement:

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)

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

})();