ZeroAd: Unity WebGL

Production-grade Unity SDKManager callback interceptor - force rewards

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Advertisement:

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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

})();