ZeroAd: Unity WebGL

Production-grade Unity SDKManager callback interceptor - force rewards

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

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

})();