Hybrid detector loader with configurable debug flags
// ==UserScript==
// @name ZeroAd: Loader
// @namespace https://greasyfork.org/en/users/1483567-vujohn123
// @version 6.3.0
// @description Hybrid detector loader with configurable debug flags
// @author ZeroAd Team
// @match *://*/*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
if (window.__zaLoaderLoaded) return;
window.__zaLoaderLoaded = true;
// ============================================================
// CẤU HÌNH
// ============================================================
const DEFAULT_CONFIG = {
LOG_LEVEL: 'info',
SHOW_EMOJI: true,
ENABLE_CONSOLE_SNIFFER: true,
ENABLE_GLOBAL_TRAPS: true,
ENABLE_MUTATION_OBSERVER: true,
RETRY_DELAY: 100,
MAX_RETRIES: 5,
FALLBACK_PLATFORM: 'adinplay',
IGNORE_PLATFORMS: [],
DEBUG_PLATFORMS: [],
};
function getConfig() {
let config = { ...DEFAULT_CONFIG };
if (window.__zaConfig) config = { ...config, ...window.__zaConfig };
try {
const params = new URLSearchParams(window.location.search);
if (params.has('za_debug') || params.has('za_log_level')) {
config.LOG_LEVEL = params.get('za_log_level') || 'debug';
if (params.has('za_debug')) {
config.LOG_LEVEL = 'debug';
config.ENABLE_CONSOLE_SNIFFER = true;
config.ENABLE_GLOBAL_TRAPS = true;
config.ENABLE_MUTATION_OBSERVER = true;
}
if (params.has('za_no_sniffer')) config.ENABLE_CONSOLE_SNIFFER = false;
if (params.has('za_no_traps')) config.ENABLE_GLOBAL_TRAPS = false;
if (params.has('za_no_observer')) config.ENABLE_MUTATION_OBSERVER = false;
}
} catch(e) {}
return config;
}
const CONFIG = getConfig();
// ============================================================
// LOGGING
// ============================================================
const LOG_LEVELS = { 'none': 0, 'error': 1, 'warn': 2, 'info': 3, 'debug': 4 };
const currentLogLevel = LOG_LEVELS[CONFIG.LOG_LEVEL] || LOG_LEVELS.info;
const PREFIX = '[ZeroAd:Loader]';
const log = {
debug: (...args) => { if (currentLogLevel >= 4) console.log(PREFIX, '🐛', ...args); },
info: (...args) => { if (currentLogLevel >= 3) console.log(PREFIX, 'ℹ️', ...args); },
warn: (...args) => { if (currentLogLevel >= 2) console.warn(PREFIX, '⚠️', ...args); },
error: (...args) => { if (currentLogLevel >= 1) console.error(PREFIX, '❌', ...args); },
success: (...args) => { if (currentLogLevel >= 3) console.log(PREFIX, '✅', ...args); }
};
// ============================================================
// PLATFORM MAP — 🆕 UPDATED with PlayHop
// ============================================================
const SCRIPT_MAP = {
'poki': 579407,
'crazygames': 579404,
'adinplay': 579403,
'gamevui': 579405,
'unity': 583938,
'gamedistribution': 583939,
'playgama': 592446,
'yandex': 592456,
'y8': 592513,
'lagged': 592657,
'playhop': 592665, // 🆕 PlayHop support
'facebook': null,
'gamemonetize': null,
'gamepix': null,
'kongregate': null,
'gameflare': null,
'ezoic': null
};
const FALLBACK = CONFIG.FALLBACK_PLATFORM;
const loaded = new Set();
const pending = new Set();
// ============================================================
// DETECTION ENGINE — HYBRID
// ============================================================
function detectPlatforms() {
const detected = new Set();
const host = window.location.hostname.toLowerCase();
// 1. Hostname
const hostMap = {
'poki.com': 'poki',
'crazygames.com': 'crazygames',
'gamevui.vn': 'gamevui',
'gamedistribution.com': 'gamedistribution',
'playgama.com': 'playgama',
'yandex': 'yandex',
'games.yandex': 'yandex',
'y8.com': 'y8',
'y8games.com': 'y8',
'lagged.com': 'lagged',
'playhop.com': 'playhop', // 🆕
'gamemonetize.com': 'gamemonetize',
'gamepix.com': 'gamepix',
'kongregate.com': 'kongregate',
'gameflare.com': 'gameflare'
};
for (const [key, value] of Object.entries(hostMap)) {
if (host.includes(key)) detected.add(value);
}
// 2. Global objects
if (window.GD_OPTIONS || window.gdApi || window.GDSdk) detected.add('gamedistribution');
if (window.ID || window.ID?.ads || window.ID?.Event || window.y8 || window.y8?.sdk) detected.add('y8');
if (window.CrazyGames || window.CrazyGames?.SDK || window.CrazySDK || window.CrazygamesAds) detected.add('crazygames');
if (window.PokiSDK) detected.add('poki');
if (window.bridge || window.PlayGama || window.pg || window.PG) detected.add('playgama');
if (window.YaGames || window.ysdk) detected.add('yandex');
if (window.GVAdBreak) detected.add('gamevui');
if (window.FBInstant) detected.add('facebook');
if (window.KongregateAPI) detected.add('kongregate');
if (window.EzoicAd) detected.add('ezoic');
if (window.LaggedAPI) detected.add('lagged');
// 🆕 PlayHop detection
if (window._a || window.adv || window.Playhop || window.PlayhopSDK || window.__playhopAds) {
detected.add('playhop');
}
// 3. Unity
if (window.unityInstance || window.gameInstance || window.UnityLoader ||
document.querySelector('canvas.unity-canvas')) {
detected.add('unity');
}
// 4. Script src
const scripts = document.querySelectorAll('script[src]');
for (const s of scripts) {
const src = s.src.toLowerCase();
if (src.includes('poki')) detected.add('poki');
if (src.includes('crazygames')) detected.add('crazygames');
if (src.includes('gamevui')) detected.add('gamevui');
if (src.includes('gamedistribution') || src.includes('main.min.js')) detected.add('gamedistribution');
if (src.includes('playgama')) detected.add('playgama');
if (src.includes('yandex')) detected.add('yandex');
if (src.includes('y8') || src.includes('id.net') || src.includes('gamebreakbeta')) detected.add('y8');
if (src.includes('lagged')) detected.add('lagged');
if (src.includes('playhop')) detected.add('playhop'); // 🆕
if (src.includes('gamemonetize')) detected.add('gamemonetize');
if (src.includes('gamepix')) detected.add('gamepix');
if (src.includes('kongregate')) detected.add('kongregate');
if (src.includes('gameflare')) detected.add('gameflare');
if (src.includes('ezoic')) detected.add('ezoic');
if (src.includes('fbinstant') || src.includes('facebook')) detected.add('facebook');
}
// Filter
const result = new Set();
for (const platform of detected) {
if (CONFIG.IGNORE_PLATFORMS.includes(platform)) continue;
result.add(platform);
}
if (result.size === 0) result.add(FALLBACK);
return result;
}
// ============================================================
// CONSOLE SNIFFER
// ============================================================
function setupConsoleSniffer() {
if (!CONFIG.ENABLE_CONSOLE_SNIFFER) return;
const originalLog = console.log, originalWarn = console.warn, originalError = console.error;
const sniff = (args) => {
if (!args || args.length === 0) return;
const msg = args.join(' ');
if (typeof msg !== 'string') return;
const lower = msg.toLowerCase();
if (lower.includes('gamedistribution') || lower.includes('gd-api')) loadPlatform('gamedistribution');
if (lower.includes('y8') || lower.includes('id.net') || lower.includes('id.ads')) loadPlatform('y8');
if (lower.includes('crazygames')) loadPlatform('crazygames');
if (lower.includes('poki')) loadPlatform('poki');
if (lower.includes('playgama') || lower.includes('bridge')) loadPlatform('playgama');
if (lower.includes('yandex') || lower.includes('ysdk') || lower.includes('yagames')) loadPlatform('yandex');
if (lower.includes('unity') || lower.includes('unityengine')) loadPlatform('unity');
if (lower.includes('fbinstant') || lower.includes('facebook instant')) loadPlatform('facebook');
if (lower.includes('kongregate')) loadPlatform('kongregate');
if (lower.includes('ezoic')) loadPlatform('ezoic');
if (lower.includes('laggedapi') || lower.includes('lagged')) loadPlatform('lagged');
// 🆕 PlayHop console sniff
if (lower.includes('playhop') || lower.includes('sdk init')) loadPlatform('playhop');
};
console.log = function(...args) { sniff(args); return originalLog.apply(this, args); };
console.warn = function(...args) { sniff(args); return originalWarn.apply(this, args); };
console.error = function(...args) { sniff(args); return originalError.apply(this, args); };
log.debug('Console sniffer installed');
}
// ============================================================
// LOADER ENGINE
// ============================================================
function loadPlatform(platformKey) {
if (!platformKey || loaded.has(platformKey) || pending.has(platformKey)) return;
const scriptId = SCRIPT_MAP[platformKey];
if (!scriptId) { log.warn(`No script ID for: ${platformKey}`); return; }
pending.add(platformKey);
const url = `https://greasyfork.org/scripts/${scriptId}/code/${scriptId}.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 < CONFIG.MAX_RETRIES) {
const delay = CONFIG.RETRY_DELAY * Math.pow(2, attempt);
log.warn(`Retry ${platformKey} in ${delay}ms (${attempt+1}/${CONFIG.MAX_RETRIES})`);
setTimeout(() => tryLoad(attempt + 1), delay);
} else {
pending.delete(platformKey);
log.error(`Failed to load ${platformKey}`);
}
};
script.src = url;
(document.head || document.documentElement).appendChild(script);
};
tryLoad(0);
}
// ============================================================
// TRAPS & OBSERVER
// ============================================================
function installGlobalTrap() {
if (!CONFIG.ENABLE_GLOBAL_TRAPS) return;
const sdkNames = [
'GD_OPTIONS','gdApi','GDSdk','ID','y8','CrazyGames','CrazySDK','CrazygamesAds',
'PokiSDK','bridge','PlayGama','pg','PG','YaGames','ysdk','GVAdBreak',
'unityInstance','gameInstance','UnityLoader','FBInstant','KongregateAPI','EzoicAd',
'LaggedAPI',
// 🆕 PlayHop global objects
'_a','adv','Playhop','PlayhopSDK','__playhopAds'
];
sdkNames.forEach(name => {
let _value = window[name];
Object.defineProperty(window, name, {
configurable: true, enumerable: true,
get() { return _value; },
set(newVal) {
_value = newVal;
const detected = detectPlatforms();
detected.forEach(key => loadPlatform(key));
}
});
});
log.debug('Global traps installed');
}
function setupObserver() {
if (!CONFIG.ENABLE_MUTATION_OBSERVER) return;
const observer = new MutationObserver(() => {
const detected = detectPlatforms();
detected.forEach(key => loadPlatform(key));
});
observer.observe(document.documentElement, { childList: true, subtree: true });
log.debug('MutationObserver installed');
}
// ============================================================
// INIT
// ============================================================
function init() {
log.info(`Starting (log level: ${CONFIG.LOG_LEVEL})`);
setupConsoleSniffer();
installGlobalTrap();
setupObserver();
const initial = detectPlatforms();
initial.forEach(key => loadPlatform(key));
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
const later = detectPlatforms();
later.forEach(key => loadPlatform(key));
});
}
log.info('Ready, waiting for events...');
}
if (document.head) init();
else {
const obs = new MutationObserver(() => {
if (document.head) { obs.disconnect(); init(); }
});
obs.observe(document.documentElement, { childList: true, subtree: true });
}
})();