Hybrid detection loader – optimized, avoids Unity if CrazyGames detected
Från och med
// ==UserScript==
// @name ZeroAd: Loader
// @namespace https://greasyfork.org/en/users/1483567-vujohn123
// @version 5.2.0
// @description Hybrid detection loader – optimized, avoids Unity if CrazyGames detected
// @author ZeroAd Team
// @match *://*/*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
if (window.__zaLoaderLoaded) return;
window.__zaLoaderLoaded = true;
const CONFIG = {
DEBUG: true,
RETRY_DELAY: 100,
MAX_RETRIES: 5,
DETECTION_INTERVAL: 300,
MAX_DETECTION_ATTEMPTS: 25,
};
const PREFIX = '[ZeroAd:Loader]';
const log = {
info: (...args) => console.log(PREFIX, 'ℹ️', ...args),
debug: (...args) => CONFIG.DEBUG && console.log(PREFIX, '🐛', ...args),
warn: (...args) => console.warn(PREFIX, '⚠️', ...args),
error: (...args) => console.error(PREFIX, '❌', ...args),
success: (...args) => console.log(PREFIX, '✅', ...args),
};
// ============================================================
// PLATFORM MAP
// ============================================================
const SCRIPT_MAP = {
'poki': 579407,
'crazygames': 579404,
'adinplay': 579403,
'gamevui': 579405,
'unity': 583938,
'gamedistribution': 583939,
'playgama': 592446,
'yandex': 592456,
};
const FALLBACK_PLATFORM = 'adinplay';
const loaded = new Set();
const pending = new Set();
let detectionCount = 0;
let detectionTimer = null;
// ============================================================
// LOAD SCRIPT
// ============================================================
function loadScriptWithRetry(platformKey, retries = CONFIG.MAX_RETRIES, delay = CONFIG.RETRY_DELAY) {
const id = SCRIPT_MAP[platformKey];
if (!id || loaded.has(platformKey) || pending.has(platformKey)) return;
pending.add(platformKey);
const url = `https://greasyfork.org/scripts/${id}/code/${id}.user.js`;
const script = document.createElement('script');
const tryLoad = (attempt) => {
script.onload = () => {
loaded.add(platformKey);
pending.delete(platformKey);
log.success(`Loaded ${platformKey}`);
};
script.onerror = () => {
if (attempt < retries) {
const nextDelay = delay * Math.pow(2, attempt);
log.warn(`Retry ${platformKey} in ${nextDelay}ms (attempt ${attempt+1}/${retries})`);
setTimeout(() => tryLoad(attempt + 1), nextDelay);
} else {
pending.delete(platformKey);
log.error(`Failed to load ${platformKey} after ${retries} attempts`);
}
};
script.src = url;
(document.head || document.documentElement).appendChild(script);
};
tryLoad(0);
}
// ============================================================
// DETECTION ENGINE – với logic loại trừ Unity khi có CrazyGames
// ============================================================
function detectPlatforms() {
const detected = new Set();
const host = window.location.hostname.toLowerCase();
const doc = document;
// Hostname
if (host.includes('poki.com')) detected.add('poki');
if (host.includes('crazygames.com')) detected.add('crazygames');
if (host.includes('gamevui.vn')) detected.add('gamevui');
if (host.includes('gamedistribution.com')) detected.add('gamedistribution');
if (host.includes('playgama.com')) detected.add('playgama');
if (host.includes('yandex') || host.includes('games.yandex')) detected.add('yandex');
// Global objects
if (window.PokiSDK) detected.add('poki');
if (window.CrazyGames || window.CrazygamesAds || window.CrazyGamesSDK) detected.add('crazygames');
if (window.GVAdBreak) detected.add('gamevui');
if (window.GDSdk || window.gdApi) detected.add('gamedistribution');
if (window.PlayGama || window.playgama || window.pg || window.PG) detected.add('playgama');
if (window.ysdk || window.YaGames) detected.add('yandex');
// Unity detection (but avoid if crazygames already detected)
let unityDetected = false;
if (window.unityInstance || window.gameInstance || window.UnityLoader) {
unityDetected = true;
} else {
const canvases = doc.querySelectorAll('canvas');
for (const canvas of canvases) {
if (canvas.classList.contains('unity-canvas')) {
unityDetected = true;
break;
}
try {
const ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (ctx) {
unityDetected = true;
break;
}
} catch(e) {}
}
}
// Chỉ thêm Unity nếu chưa có CrazyGames (tránh xung đột)
if (unityDetected && !detected.has('crazygames')) {
detected.add('unity');
}
// Script src
const scripts = doc.querySelectorAll('script[src]');
for (const script of scripts) {
const src = script.src.toLowerCase();
if (src.includes('poki.com/sdk')) detected.add('poki');
if (src.includes('sdk.crazygames.com')) detected.add('crazygames');
if (src.includes('gamevui.vn')) detected.add('gamevui');
if (src.includes('gamedistribution.com')) detected.add('gamedistribution');
if (src.includes('playgama.com')) detected.add('playgama');
if (src.includes('yandex') || src.includes('games.yandex')) detected.add('yandex');
// Chỉ thêm Unity nếu chưa có CrazyGames
if (src.includes('unity') && !detected.has('crazygames')) {
detected.add('unity');
}
}
// Meta / window properties
const metaPlatform = doc.querySelector('meta[name="platform"]');
if (metaPlatform) {
const p = metaPlatform.getAttribute('content');
if (p && SCRIPT_MAP[p]) detected.add(p);
}
if (window.gamePlatform && SCRIPT_MAP[window.gamePlatform]) {
detected.add(window.gamePlatform);
}
if (detected.size === 0) detected.add(FALLBACK_PLATFORM);
// Log if Unity was excluded
if (unityDetected && !detected.has('unity')) {
log.debug('Unity detected but excluded because CrazyGames is present');
}
return detected;
}
// ============================================================
// CONSOLE LOG READER (giữ nguyên, có thể thêm pattern)
// ============================================================
let consoleLogDetected = new Set();
function setupConsoleReader() {
const originalLog = console.log;
const originalWarn = console.warn;
const originalError = console.error;
const checkLog = (args) => {
if (!args || args.length === 0) return;
const msg = args.join(' ').toLowerCase();
let extra = new Set();
if (msg.includes('poki')) extra.add('poki');
if (msg.includes('crazygames') || msg.includes('crazy sdk') || msg.includes('html5 sdk crazygames') || msg.includes('adsmanager: crazygames') || msg.includes('[crazysdk]')) extra.add('crazygames');
if (msg.includes('gamevui')) extra.add('gamevui');
if (msg.includes('gamedistribution')) extra.add('gamedistribution');
if (msg.includes('playgama')) extra.add('playgama');
if (msg.includes('yandex') || msg.includes('ysdk') || msg.includes('yagames')) extra.add('yandex');
if (msg.includes('unity') || msg.includes('unitywebgl') || msg.includes('unitycache') || msg.includes('unitymemory') || msg.includes('physics::module') || msg.includes('initialize engine version') || msg.includes('creating webgl')) extra.add('unity');
if (msg.includes('adinplay')) extra.add('adinplay');
if (msg.includes('[gameframe]')) extra.add('crazygames');
if (extra.size > 0) {
for (const p of extra) {
if (!consoleLogDetected.has(p)) {
consoleLogDetected.add(p);
log.debug(`Console log detected: ${p}`);
scheduleDetection();
}
}
}
};
console.log = function(...args) { originalLog.apply(console, args); checkLog(args); };
console.warn = function(...args) { originalWarn.apply(console, args); checkLog(args); };
console.error = function(...args) { originalError.apply(console, args); checkLog(args); };
log.debug('Console reader installed');
}
// ============================================================
// DEBOUNCED DETECTION + PERIODIC SCAN
// ============================================================
function scheduleDetection() {
if (detectionTimer) clearTimeout(detectionTimer);
detectionTimer = setTimeout(() => {
detectionTimer = null;
runDetection();
}, 100);
}
function runDetection() {
const platforms = detectPlatforms();
log.debug('Detection result:', [...platforms]);
for (const key of platforms) {
if (!loaded.has(key) && !pending.has(key)) {
loadScriptWithRetry(key);
}
}
detectionCount++;
if (detectionCount >= CONFIG.MAX_DETECTION_ATTEMPTS) {
log.debug('Max detection attempts reached, stopping periodic checks');
if (window.__zaDetectionInterval) {
clearInterval(window.__zaDetectionInterval);
window.__zaDetectionInterval = null;
}
}
}
function startPeriodicScan() {
if (window.__zaDetectionInterval) return;
window.__zaDetectionInterval = setInterval(() => {
if (detectionCount < CONFIG.MAX_DETECTION_ATTEMPTS) runDetection();
}, CONFIG.DETECTION_INTERVAL);
log.debug(`Periodic scan started (every ${CONFIG.DETECTION_INTERVAL}ms)`);
}
// ============================================================
// MUTATION OBSERVER
// ============================================================
function setupObserver() {
const observer = new MutationObserver((mutations) => {
let shouldCheck = false;
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType === 1) {
if (node.tagName === 'SCRIPT' || node.tagName === 'CANVAS') {
shouldCheck = true; break;
}
if (node.querySelector && node.querySelector('canvas, script')) {
shouldCheck = true; break;
}
}
}
if (shouldCheck) break;
}
if (shouldCheck) scheduleDetection();
});
observer.observe(document.documentElement, { childList: true, subtree: true });
log.debug('MutationObserver installed');
}
// ============================================================
// GLOBAL TRAPS
// ============================================================
function installTraps() {
const sdkNames = [
'PokiSDK', 'CrazyGames', 'CrazygamesAds', 'CrazyGamesSDK', 'GVAdBreak',
'GDSdk', 'gdApi', 'unityInstance', 'gameInstance', 'UnityLoader',
'PlayGama', 'playgama', 'pg', 'PG',
'ysdk', 'YaGames'
];
sdkNames.forEach(name => {
let _value = window[name];
Object.defineProperty(window, name, {
configurable: true,
enumerable: true,
get() { return _value; },
set(newVal) {
_value = newVal;
log.debug(`Trap: window.${name} assigned`);
scheduleDetection();
}
});
});
log.debug('Global traps installed');
}
// ============================================================
// INIT
// ============================================================
function init() {
installTraps();
setupConsoleReader();
setupObserver();
startPeriodicScan();
setTimeout(() => runDetection(), 10);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
log.debug('DOMContentLoaded – re-detecting');
runDetection();
});
} else {
setTimeout(runDetection, 500);
}
log.info('Hybrid Loader v5.2.0 initialized');
}
if (document.head) {
init();
} else {
const headObserver = new MutationObserver(() => {
if (document.head) { headObserver.disconnect(); init(); }
});
headObserver.observe(document.documentElement, { childList: true, subtree: true });
}
})();