CrazyGames Ad Bypass Helper

Bypasses CrazyGames ad wait/loading stages via SDK faking, network blocking, and DOM cleanup

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         CrazyGames Ad Bypass Helper
// @namespace    http://tampermonkey.net/
// @version      3.4.0
// @description  Bypasses CrazyGames ad wait/loading stages via SDK faking, network blocking, and DOM cleanup
// @match        https://www.crazygames.com/game/*
// @match        https://*.crazygames.com/*
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  // Guard against double-injection (e.g. multiple @match patterns firing)
  if (unsafeWindow.__cgBypassInstalled) return;
  unsafeWindow.__cgBypassInstalled = true;

  const win = unsafeWindow || window;
  const LOG = '[CG-Bypass]';
  const log  = (...a) => console.log(LOG, ...a);
  const warn = (...a) => console.warn(LOG, ...a);

  // -----------------------------------------------------------
  // CONFIG
  // -----------------------------------------------------------
  const CFG = {
    REWARD_DELAY_MS:   50,  // fake ad play duration before callbacks fire
    MAX_HOOK_ATTEMPTS: 100,
    HOOK_INTERVAL_MS:  200,
  };

  // Native timer references used internally (no overrides)
  const _setTimeout  = win.setTimeout.bind(win);
  const _setInterval = win.setInterval.bind(win);

  // -----------------------------------------------------------
  // 1. AD URL PATTERN LIST
  // -----------------------------------------------------------
  const AD_PATTERNS = [
    'googlesyndication', 'doubleclick', 'googleads',
    'adservice', 'pagead', 'adserver',
    'prebid', 'amazon-adsystem', 'ad-delivery',
    'imasdk.googleapis.com', 'securepubads',
  ];

  function isAdUrl(url) {
    const u = String(url);
    return AD_PATTERNS.some(p => u.includes(p));
  }

  // -----------------------------------------------------------
  // 2. NETWORK BLOCKING  (fetch + XHR)
  // -----------------------------------------------------------
  const _fetch = win.fetch.bind(win);
  win.fetch = function (input, options) {
    const url = (input instanceof Request) ? input.url : String(input);
    if (isAdUrl(url)) {
      log('Blocked fetch:', url.substring(0, 100));
      return Promise.resolve(new Response('{}', {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      }));
    }
    return _fetch(input, options);
  };

  const _xhrOpen = XMLHttpRequest.prototype.open;
  const _xhrSend = XMLHttpRequest.prototype.send;

  XMLHttpRequest.prototype.open = function (method, url, ...args) {
    this._cgBlocked = isAdUrl(String(url));
    if (this._cgBlocked) log('Blocked XHR:', String(url).substring(0, 100));
    return _xhrOpen.call(this, method, url, ...args);
  };

  XMLHttpRequest.prototype.send = function (...args) {
    if (this._cgBlocked) {
      Object.defineProperty(this, 'readyState',  { value: 4,    configurable: true });
      Object.defineProperty(this, 'status',       { value: 200,  configurable: true });
      Object.defineProperty(this, 'responseText', { value: '{}', configurable: true });
      this.dispatchEvent(new Event('load'));
      return;
    }
    return _xhrSend.apply(this, args);
  };

  // -----------------------------------------------------------
  // 3. SDK FAKING
  // -----------------------------------------------------------

  const hookState = {
    instance:  false,
    sdkAd:     false,
    adsModule: false,
    calls:     0,
  };

  // -- 3a. Singleton fake instance (event-listener pattern) ----
  let _fakeInstance = null;

  function getFakeInstance() {
    if (_fakeInstance) return _fakeInstance;

    const listeners = {};

    function fireEvent(name, data = {}) {
      (listeners[name] || []).forEach(cb => {
        try { cb(data); } catch (e) { warn(`Listener error [${name}]:`, e); }
      });
    }

    let _initDone = false;

    _fakeInstance = {
      init() {
        log('instance.init()');
        _initDone = true;
        _setTimeout(() => fireEvent('initialized', {
          userInfo: {
            countryCode: 'US',
            browser: { name: 'Chrome', version: '124' },
            os:      { name: 'Windows', version: '10' },
            device:  { type: 'desktop' },
          },
        }), 50);
      },

      addEventListener(event, cb) {
        if (!listeners[event]) listeners[event] = [];
        listeners[event].push(cb);
        if (event === 'initialized' && _initDone) {
          _setTimeout(() => cb({
            userInfo: {
              countryCode: 'US',
              browser: { name: 'Chrome', version: '124' },
              os:      { name: 'Windows', version: '10' },
              device:  { type: 'desktop' },
            },
          }), 10);
        }
      },

      removeEventListener(event, cb) {
        if (listeners[event])
          listeners[event] = listeners[event].filter(fn => fn !== cb);
      },

      async requestAd(type = 'midgame') {
        hookState.calls++;
        log(`instance.requestAd("${type}")`);
        fireEvent('adStarted', { adType: type });
        return new Promise(resolve => {
          _setTimeout(() => {
            fireEvent('adFinished', { adType: type });
            log(`adFinished for "${type}"`);
            resolve();
          }, CFG.REWARD_DELAY_MS);
        });
      },

      gameplayStart()       {},
      gameplayStop()        {},
      happytime()           {},
      sdkGameLoadingStart() {},
      sdkGameLoadingStop()  {},

      async requestBanner(containers) {
        (containers || []).forEach(c =>
          _setTimeout(() => fireEvent('bannerRendered', { containerId: c.containerId }), 50));
      },
      async requestResponsiveBanner(containers) {
        (containers || []).forEach(c =>
          _setTimeout(() => fireEvent('bannerRendered', { containerId: c.id || c.containerId }), 50));
      },
      clearBanner()     {},
      clearAllBanners() {},

      get hasAdblock() { return false; },
      adblockDetectionExecuted: true,

      inviteLink(params = {}) {
        const base = win.location.href.split('?')[0];
        return `${base}?czy_invite=true&utm_source=invite&${new URLSearchParams(params)}`;
      },
    };

    return _fakeInstance;
  }

  // -- 3b. Legacy callback-based ad module ---------------------
  function createFakeAdModule() {
    const REWARD_EVENTS = [
      'rewardedComplete', 'rewardGranted', 'adComplete',
      'crazy_ad_finished', 'adReward', 'adFinished',
    ];

    function dispatchRewardEvents() {
      REWARD_EVENTS.forEach(ev => {
        try { win.dispatchEvent(new CustomEvent(ev)); } catch (_) {}
      });
    }

    function falsyFn() { return Promise.resolve(false); }
    falsyFn.valueOf  = () => false;
    falsyFn.toString = () => 'false';

    return {
      get isAdPlaying()       { return false; },
      get requestInProgress() { return false; },

      hasAdblock:        falsyFn,
      hasAdblockEnabled: falsyFn,
      isAdblockActive:   falsyFn,

      async prefetchAd(type) {
        hookState.calls++;
        log(`prefetchAd("${type}")`);
      },

      async requestAd(type = 'midgame', cbs = {}) {
        hookState.calls++;
        log(`requestAd("${type}")`);
        try { cbs.adStarted?.(); } catch (_) {}
        return new Promise(resolve => {
          _setTimeout(() => {
            try { cbs.adFinished?.(); } catch (_) {}
            dispatchRewardEvents();
            log(`reward granted for "${type}"`);
            resolve();
          }, CFG.REWARD_DELAY_MS);
        });
      },

      addAdblockPopupListener()    {},
      removeAdblockPopupListener() {},
    };
  }

  // -- 3c. Hook helper -----------------------------------------
  function trapProperty(obj, prop, onSet) {
    if (obj[prop]) { onSet(obj[prop]); return; }
    let _val;
    Object.defineProperty(obj, prop, {
      configurable: true,
      enumerable: true,
      get() { return _val; },
      set(v) { _val = v; if (v) onSet(v); },
    });
  }

  // -- 3d. Individual SDK hooks --------------------------------

  function hookInstance() {
    if (hookState.instance) return;
    const apply = (sdk) => {
      if (!sdk?.CrazySDK) return;
      sdk.CrazySDK.getInstance = () => {
        log('CrazyGames.CrazySDK.getInstance() intercepted');
        return getFakeInstance();
      };
      hookState.instance = true;
      log('CrazySDK.getInstance() hooked');
    };
    if (win.CrazyGames) { apply(win.CrazyGames); return; }
    trapProperty(win, 'CrazyGames', apply);
  }

  function hookSdkAd() {
    if (hookState.sdkAd) return;
    const apply = (cg) => {
      if (!cg?.SDK || hookState.sdkAd) return;
      const fakeAd = createFakeAdModule();
      Object.defineProperty(cg.SDK, 'ad', { configurable: true, enumerable: true, get: () => fakeAd });
      hookState.sdkAd = true;
      log('CrazyGames.SDK.ad hooked');
    };
    if (win.CrazyGames) { apply(win.CrazyGames); return; }
    trapProperty(win, 'CrazyGames', apply);
  }

  function hookAdsModule() {
    if (hookState.adsModule) return;

    const AD_METHODS = [
      'requestAd', 'requestAds', 'showAd', 'showRewardedAd',
      'displayAd', 'displayRewardedAd', 'displayMidrollAd',
      'requestRewardedAd', 'showRewardedVideo', 'requestRewardedVideo',
      'showInterstitial', 'requestOnly', 'render', 'preloadAd',
    ];

    const apply = (target) => {
      if (!target || hookState.adsModule) return;
      const proxy = new Proxy(target, {
        get(obj, prop) {
          const orig = obj[prop];
          if (AD_METHODS.includes(prop) && typeof orig === 'function') {
            return function (...args) {
              hookState.calls++;
              log(`CrazygamesAds.${prop}()`);
              let onStart = null, onFinish = null;
              for (const a of args) {
                if (a && typeof a === 'object') {
                  onStart  = a.adStarted  || a.onAdStarted  || a.onStart;
                  onFinish = a.adFinished || a.onAdFinished || a.onComplete || a.onReward;
                }
              }
              try { onStart?.(); } catch (_) {}
              if (prop === 'requestOnly' || prop === 'preloadAd')
                return Promise.resolve({ success: true });
              return new Promise(resolve => {
                _setTimeout(() => {
                  try { onFinish?.(); } catch (_) {}
                  ['rewardedComplete', 'adComplete', 'crazy_ad_finished'].forEach(ev => {
                    try { win.dispatchEvent(new CustomEvent(ev)); } catch (_) {}
                  });
                  resolve({ success: true, completed: true });
                }, CFG.REWARD_DELAY_MS);
              });
            };
          }
          const lower = String(prop).toLowerCase();
          if (['hasadblock', 'hasadblockenabled', 'isadblockactive'].includes(lower)) {
            return async () => false;
          }
          return orig;
        },
      });
      hookState.adsModule = true;
      return proxy;
    };

    if (win.CrazygamesAds) {
      win.CrazygamesAds = apply(win.CrazygamesAds);
      log('CrazygamesAds hooked (existing)');
      return;
    }

    let _ads;
    Object.defineProperty(win, 'CrazygamesAds', {
      configurable: true, enumerable: true,
      get()  { return _ads; },
      set(v) { _ads = apply(v) || v; if (_ads) log('CrazygamesAds hooked (deferred)'); },
    });
  }

  // -- 3e. Orchestration ---------------------------------------
  let _hookAttempts = 0;
  let _hookTimer    = null;

  function attemptAllHooks() {
    _hookAttempts++;
    hookInstance();
    hookSdkAd();
    hookAdsModule();

    const done = hookState.instance || (hookState.sdkAd && hookState.adsModule);

    if (done || _hookAttempts >= CFG.MAX_HOOK_ATTEMPTS) {
      if (_hookTimer) { clearInterval(_hookTimer); _hookTimer = null; }
      log(`Hooks finalised after ${_hookAttempts} attempt(s). State:`, hookState);
    }
  }

  _hookTimer = _setInterval(attemptAllHooks, CFG.HOOK_INTERVAL_MS);
  attemptAllHooks();

  // -----------------------------------------------------------
  // 4. DOM AD OVERLAY REMOVAL + AUTO-SKIP
  // -----------------------------------------------------------
  function removeAdOverlays() {
    const SELECTORS = [
      '[class*="ad-overlay"]', '[class*="ad-container"]', '[class*="ad-wrapper"]',
      '[class*="preroll"]', '[class*="midroll"]', '[id*="ad-container"]',
      '[class*="loading-ad"]', '[class*="ad-loading"]', '.ad-blocker-message',
      '[class*="rewarded"]',
    ];

    const obs = new MutationObserver(() => {
      SELECTORS.forEach(sel => {
        document.querySelectorAll(sel).forEach(el => {
          const s = getComputedStyle(el);
          const isOverlay = (s.position === 'fixed' || s.position === 'absolute')
                          && parseFloat(s.zIndex) > 100;
          if (isOverlay) { el.style.display = 'none'; log('Hid overlay:', sel); }
        });
      });
      document.querySelectorAll('button, [class*="skip"], [class*="close"]').forEach(btn => {
        const text = (btn.textContent || '').toLowerCase();
        if (['skip', 'close', 'continue'].some(w => text.includes(w)) && btn.offsetParent) {
          btn.click();
          log('Auto-clicked:', btn.textContent.trim());
        }
      });
    });
    obs.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
  }

  // -----------------------------------------------------------
  // 5. SUPPRESS AD ERROR postMessages
  // -----------------------------------------------------------
  win.addEventListener('message', function (e) {
    try {
      const d = (typeof e.data === 'string') ? JSON.parse(e.data) : e.data;
      if (d?.type === 'adError' || d?.event === 'adError') {
        log('Suppressed adError postMessage');
        e.stopImmediatePropagation();
      }
    } catch (_) {}
  }, true);

  // -----------------------------------------------------------
  // INIT
  // -----------------------------------------------------------
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', removeAdOverlays);
  } else {
    removeAdOverlays();
  }

  log('v3.4.0 loaded — all modules active');
})();