Greasy Fork is available in English.
Hybrid detection loader with console log reading & multi-platform support
目前為
// ==UserScript==
// @name ZeroAd: Loader
// @namespace https://greasyfork.org/en/users/1483567-vujohn123
// @version 5.0.0
// @description Hybrid detection loader with console log reading & multi-platform support
// @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: 200, // ms giữa các lần quét
MAX_DETECTION_ATTEMPTS: 30, // ~6 giây
};
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;
// ============================================================
// LOAD SCRIPT VỚI RETRY
// ============================================================
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 – Hybrid
// ============================================================
function detectPlatforms() {
const detected = new Set();
const host = window.location.hostname.toLowerCase();
const doc = document;
// ----- 1. Hostname-based -----
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');
// ----- 2. Global objects / classes -----
if (window.PokiSDK) detected.add('poki');
if (window.CrazyGames || window.CrazygamesAds) 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');
// ----- 3. Unity detection (by object or canvas) -----
if (window.unityInstance || window.gameInstance || window.UnityLoader) {
detected.add('unity');
} else {
// Tìm canvas có class unity-canvas hoặc WebGL context
const canvases = doc.querySelectorAll('canvas');
for (const canvas of canvases) {
if (canvas.classList.contains('unity-canvas')) {
detected.add('unity');
break;
}
try {
const ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (ctx) {
// Nếu canvas có WebGL context và không phải là canvas của quảng cáo
detected.add('unity');
break;
}
} catch(e) {}
}
}
// ----- 4. Script src scanning -----
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');
if (src.includes('unity')) detected.add('unity');
}
// ----- 5. Meta tags / 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);
}
// ----- 6. Console log reader (đã được tích hợp bên dưới) -----
// Console logs sẽ được xử lý qua sự kiện, sẽ gọi detectPlatforms() lại nếu cần
// Nếu không có gì -> fallback
if (detected.size === 0) {
detected.add(FALLBACK_PLATFORM);
}
return detected;
}
// ============================================================
// CONSOLE LOG READER – Lắng nghe logs để phát hiện platform
// ============================================================
let consoleLogDetected = new Set();
function setupConsoleReader() {
// Lưu trữ các hàm console gốc
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();
// Các pattern đặc trưng
if (msg.includes('poki') || msg.includes('poki sdk')) extra.add('poki');
if (msg.includes('crazygames') || msg.includes('crazygames sdk')) 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('unity webgl')) extra.add('unity');
if (msg.includes('adinplay')) extra.add('adinplay');
// Nếu có phát hiện mới, trigger re-detection
if (extra.size > 0) {
for (const p of extra) {
if (!consoleLogDetected.has(p)) {
consoleLogDetected.add(p);
log.debug(`Console log detected: ${p}`);
// Kích hoạt quét lại ngay
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);
};
// Cũng có thể dùng sự kiện nếu có (một số trình duyệt hỗ trợ)
// Không cần vì đã override hàm
log.debug('Console reader installed');
}
// ============================================================
// DEBOUNCED DETECTION
// ============================================================
let detectionTimer = null;
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++;
// Nếu đã quét đủ số lần và vẫn không thấy platform mới -> dừng polling
if (detectionCount >= CONFIG.MAX_DETECTION_ATTEMPTS) {
log.debug('Max detection attempts reached, stopping periodic checks');
if (window.__zaDetectionInterval) {
clearInterval(window.__zaDetectionInterval);
window.__zaDetectionInterval = null;
}
}
}
// ============================================================
// MUTATION OBSERVER
// ============================================================
function setupObserver() {
const observer = new MutationObserver(() => {
scheduleDetection();
});
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: false });
log.debug('MutationObserver installed');
}
// ============================================================
// GLOBAL TRAPS
// ============================================================
function installTraps() {
const sdkNames = [
'PokiSDK', 'CrazyGames', 'CrazygamesAds', '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');
}
// ============================================================
// PERIODIC SCAN
// ============================================================
function startPeriodicScan() {
if (window.__zaDetectionInterval) return;
window.__zaDetectionInterval = setInterval(() => {
// Chỉ quét nếu chưa đạt max attempts
if (detectionCount < CONFIG.MAX_DETECTION_ATTEMPTS) {
runDetection();
} else {
// Nếu đã quét đủ mà không có gì mới, có thể dừng
// Nhưng vẫn để cho các trap và observer kích hoạt lại nếu cần
}
}, CONFIG.DETECTION_INTERVAL);
log.debug(`Periodic scan started (every ${CONFIG.DETECTION_INTERVAL}ms)`);
}
// ============================================================
// INIT
// ============================================================
function init() {
installTraps();
setupConsoleReader();
setupObserver();
startPeriodicScan();
// Quét ngay lập tức
setTimeout(() => runDetection(), 10);
// Khi DOMContentLoaded, quét lại để bắt các script tải muộn
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
log.debug('DOMContentLoaded – re-detecting');
runDetection();
});
} else {
// Nếu đã sẵn sàng, quét lại sau 500ms
setTimeout(runDetection, 500);
}
log.info('Hybrid Loader initialized');
}
// Chờ head tồn tại
if (document.head) {
init();
} else {
const headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect();
init();
}
});
headObserver.observe(document.documentElement, { childList: true, subtree: true });
}
})();