DevTools Bypass

Bypass for website restrictions on DevTools with enhanced protection

// ==UserScript==
// @name         DevTools Bypass
// @name:vi      Bỏ Qua Chặn DevTools
// @name:zh-CN   开发工具限制绕过
// @name:ru      Разблокировка DevTools
// @namespace    https://gf.zukizuki.org/vi/users/1195312-renji-yuusei
// @version      2025.06.10.1
// @description  Bypass for website restrictions on DevTools with enhanced protection
// @description:vi Bỏ qua các hạn chế của trang web về DevTools với bảo vệ nâng cao
// @description:zh-CN 绕过网站对开发工具的限制,具有增强的保护功能
// @description:ru   Разблокировка DevTools с усиленной защитой
// @author       Yuusei
// @match        *://*/*
// @grant        unsafeWindow
// @run-at       document-start
// @license      GPL-3.0-only
// ==/UserScript==

(() => {
  'use strict';

  // Constants
  const CONSTANTS = {
    PREFIX: '[DevTools Bypass]',
    LOG_LEVELS: {
      INFO: 'info',
      WARN: 'warn', 
      ERROR: 'error',
      DEBUG: 'debug'
    },
    TIME_THRESHOLDS: {
      DEBUGGER: 80,
      CACHE: 30000
    },
    CACHE_TTL: {
        DEBUGGER_CHECK: 500
    }
  };

  // Configuration
  const config = {
    antiDebugRegex: new RegExp([
        // debugger, debug(), etc.
        /[;\s]*(?:debugger|debug(?:ger)?|breakpoint)[\s;]*/,
        // new Function('debugger')(), etc.
        /(?:eval|Function|setTimeout|setInterval)\s*\(\s*['"`].*?debugger.*?['"`]\s*\)/,
        // devtools checks
        /(?:isDevTools?|devtools?|debugMode|debug_enabled)\s*[=:]\s*(?:true|1|!0|yes)/,
        // console checks
        /console\.(?:log|warn|error|info|debug|trace|dir|table)/,
        // source map urls
        /\/\/[#@]\s*source(?:Mapping)?URL\s*=.*/,
        // Known anti-debug library patterns
        /FuckDevTools|devtools-detector/
    ].map(r => r.source).join('|'), 'gi'),

    consoleProps: ['log', 'warn', 'error', 'info', 'debug', 'trace', 'dir', 'dirxml', 'table', 'profile', 'group', 'groupEnd', 'time', 'timeEnd'],
    cutoffs: {
      debugger: { amount: 30, within: CONSTANTS.TIME_THRESHOLDS.CACHE },
      debuggerThrow: { amount: 30, within: CONSTANTS.TIME_THRESHOLDS.CACHE }
    },
    bypassTriggers: {
      timeThreshold: CONSTANTS.TIME_THRESHOLDS.DEBUGGER,
      stackDepth: 30,
      recursionLimit: 50
    },
    debuggerDetector: {
        cacheTTL: CONSTANTS.CACHE_TTL.DEBUGGER_CHECK,
        historyCleanupInterval: 60000 // 1 minute
    },
    logging: {
      enabled: true,
      prefix: CONSTANTS.PREFIX,
      levels: Object.values(CONSTANTS.LOG_LEVELS),
      detailedErrors: true,
      monitorAPI: false,
      monitorDOM: false
    },
    protection: {
      preventDevToolsKeys: true,
      hideStackTraces: true,
      sanitizeErrors: true,
      obfuscateTimers: true,
      preventRightClick: true,
      preventViewSource: true,
      preventCopy: true,
      preventPaste: true,
      preventPrint: true,
      preventSave: true
    }
  };

  // Logger class
  class Logger {
    static #instance;
    #lastLog = 0;
    #logCount = 0;
    #logBuffer = [];

    constructor() {
      if (Logger.#instance) {
        return Logger.#instance;
      }
      Logger.#instance = this;
      this.#setupBufferFlush();
    }

    #setupBufferFlush() {
      setInterval(() => {
        if (this.#logBuffer.length) {
          this.#flushBuffer();
        }
      }, 1000);
    }

    #flushBuffer() {
      this.#logBuffer.forEach(({level, args}) => {
        console[level](config.logging.prefix, ...args);
      });
      this.#logBuffer = [];
    }

    #shouldLog() {
      const now = Date.now();
      if (now - this.#lastLog > 1000) {
        this.#logCount = 0;
      }
      this.#lastLog = now;
      return ++this.#logCount <= 10;
    }

    #log(level, ...args) {
      if (!config.logging.enabled || !this.#shouldLog()) return;
      this.#logBuffer.push({ level, args });
    }

    info(...args) { this.#log(CONSTANTS.LOG_LEVELS.INFO, ...args); }
    warn(...args) { this.#log(CONSTANTS.LOG_LEVELS.WARN, ...args); }
    error(...args) { this.#log(CONSTANTS.LOG_LEVELS.ERROR, ...args); }
    debug(...args) { this.#log(CONSTANTS.LOG_LEVELS.DEBUG, ...args); }
  }

  // Original functions store
  const OriginalFunctions = {
    defineProperty: Object.defineProperty,
    getOwnPropertyDescriptor: Object.getOwnPropertyDescriptor,
    setTimeout: window.setTimeout,
    setInterval: window.setInterval,
    Date: window.Date,
    now: Date.now,
    performance: window.performance,
    Function: window.Function,
    eval: window.eval,
    console: {},
    toString: Function.prototype.toString,
    preventDefault: Event.prototype.preventDefault,
    getComputedStyle: window.getComputedStyle,
    addEventListener: window.addEventListener,
    removeEventListener: window.removeEventListener,
    fetch: window.fetch,
    XMLHttpRequest: window.XMLHttpRequest,

    initConsole() {
      config.consoleProps.forEach(prop => {
        if (console[prop]) {
          this.console[prop] = console[prop].bind(console);
        }
      });
    }
  };

  OriginalFunctions.initConsole();

  // Debugger detector
  class DebuggerDetector {
    static #detectionCache = new Map();
    static #detectionHistory = [];
    static #historyCleanupTimer = null;

    static isPresent() {
      try {
        const cacheKey = 'debugger_check';
        const cached = this.#detectionCache.get(cacheKey);
        if (cached && Date.now() - cached.timestamp < config.debuggerDetector.cacheTTL) {
          return cached.result;
        }

        const startTime = OriginalFunctions.now.call(Date);
        new Function('debugger;')();
        const timeDiff = OriginalFunctions.now.call(Date) - startTime;

        const result = timeDiff > config.bypassTriggers.timeThreshold;
        this.#detectionCache.set(cacheKey, {
          result,
          timestamp: Date.now()
        });

        this.#detectionHistory.push({
          timestamp: Date.now(),
          result,
          timeDiff
        });

        // Keep history for 5 minutes
        const fiveMinutesAgo = Date.now() - 300000;
        this.#detectionHistory = this.#detectionHistory.filter(entry => entry.timestamp > fiveMinutesAgo);

        return result;
      } catch {
        return false;
      }
    }

    static analyzeStack() {
      try {
        const stack = new Error().stack;
        if (!stack) return { depth: 0, hasDebugKeywords: false, isRecursive: false, suspiciousPatterns: [], stackHash: '' };
        
        const frames = stack.split('\n');
        const uniqueFrames = new Set(frames);

        return {
          depth: frames.length,
          hasDebugKeywords: config.antiDebugRegex.test(stack),
          isRecursive: uniqueFrames.size < frames.length,
          suspiciousPatterns: this.#detectSuspiciousPatterns(stack),
          stackHash: this.#generateStackHash(stack)
        };
      } catch {
        return {
          depth: 0,
          hasDebugKeywords: false,
          isRecursive: false,
          suspiciousPatterns: [],
          stackHash: ''
        };
      }
    }

    static #detectSuspiciousPatterns(stack) {
      const patterns = [
        /eval.*?\(/g,
        /Function.*?\(/g,
        /debugger/g,
        /debug/g,
        /DevTools/g,
        /console\./g,
        /chrome-extension/g
      ];
      return patterns.filter(pattern => pattern.test(stack));
    }

    static #generateStackHash(stack) {
      return Array.from(stack).reduce((hash, char) => {
        hash = ((hash << 5) - hash) + char.charCodeAt(0);
        return hash & hash;
      }, 0).toString(36);
    }

    static getDetectionStats() {
      const now = Date.now();
      const recentDetections = this.#detectionHistory.filter(entry => 
        entry.timestamp > now - 60000
      );

      return {
        total: recentDetections.length,
        positive: recentDetections.filter(entry => entry.result).length,
        averageTime: recentDetections.reduce((acc, curr) => 
          acc + curr.timeDiff, 0) / (recentDetections.length || 1)
      };
    }

    static startHistoryCleanup() {
        if (this.#historyCleanupTimer) return;
        this.#historyCleanupTimer = setInterval(() => {
            const fiveMinutesAgo = Date.now() - 300000;
            this.#detectionHistory = this.#detectionHistory.filter(entry => entry.timestamp > fiveMinutesAgo);
        }, config.debuggerDetector.historyCleanupInterval);
    }
  }

  // Protection class
  class Protection {
    static #combinedPattern = null;
    
    static applyAll() {
      this.#patchGlobalDebugVariables();
      this.#protectTimers();
      this.#protectTiming();
      this.#protectFunction();
      this.#protectStack();
      this.#protectEval();
      this.#protectConsole();
      this.#setupMutationObserver();
      this.#protectDeveloperKeys();
      this.#protectRightClick();
      this.#protectNetwork();
      this.#protectStorage();
      this.#protectClipboard();
      this.#protectPrinting();
      this.#protectWebWorkers();
      this.#applyDeveloperHelpers();
    }

    static #protectTimers() {
      const wrapTimer = original => {
        return function(handler, timeout, ...args) {
          if (typeof handler !== 'function') {
            return original.apply(this, arguments);
          }

          const wrappedHandler = function() {
            try {
              if (DebuggerDetector.isPresent()) return;
              return handler.apply(this, arguments);
            } catch (e) {
              if (e.message?.includes('debugger')) return;
              throw e;
            }
          };

          if (config.protection.obfuscateTimers) {
            timeout = Math.max(1, timeout + (Math.random() * 20 - 10));
          }

          return original.call(this, wrappedHandler, timeout, ...args);
        };
      };

      window.setTimeout = wrapTimer(OriginalFunctions.setTimeout);
      window.setInterval = wrapTimer(OriginalFunctions.setInterval);
    }

    static #protectTiming() {
      const timeOffset = Math.random() * 25;
      const safeNow = () => OriginalFunctions.now.call(Date) + timeOffset;

      Object.defineProperty(Date, 'now', {
        value: safeNow,
        configurable: false,
        writable: false
      });

      if (window.performance?.now) {
        Object.defineProperty(window.performance, 'now', {
          value: safeNow,
          configurable: false,
          writable: false
        });
      }
    }

    static #protectFunction() {
      const handler = {
        apply(target, thisArg, args) {
          if (typeof args[0] === 'string') {
            args[0] = Protection.#cleanCode(args[0]);
          }
          return Reflect.apply(target, thisArg, args);
        },
        construct(target, args) {
          if (typeof args[0] === 'string') {
            args[0] = Protection.#cleanCode(args[0]);
          }
          return Reflect.construct(target, args);
        }
      };

      window.Function = new Proxy(OriginalFunctions.Function, handler);
      if (typeof unsafeWindow !== 'undefined') {
        unsafeWindow.Function = window.Function;
      }
    }

    static #protectStack() {
      if (!config.protection.hideStackTraces) return;

      // V8-specific API for stack trace customization
      if ('prepareStackTrace' in Error) {
        Error.prepareStackTrace = (error, stack) => {
          const stackString = [error.toString(), ...stack.map(frame => `    at ${frame}`)].join('\n');
          return Protection.#cleanCode(stackString);
        };
        return;
      }
      
      // Standard-based approach
      try {
        const originalStackDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
        if (originalStackDescriptor?.get) {
          Object.defineProperty(Error.prototype, 'stack', {
            get() {
              const originalStack = originalStackDescriptor.get.call(this);
              return Protection.#cleanCode(originalStack);
            },
            configurable: true,
          });
        }
      } catch (e) {
        logger.error('Failed to protect stack traces:', e);
      }
    }

    static #protectEval() {
      const safeEval = function(code) {
        if (typeof code === 'string') {
          if (DebuggerDetector.isPresent()) return;
          return OriginalFunctions.eval.call(this, Protection.#cleanCode(code));
        }
        return OriginalFunctions.eval.apply(this, arguments);
      };

      Object.defineProperty(window, 'eval', {
        value: safeEval,
        configurable: false,
        writable: false
      });

      if (typeof unsafeWindow !== 'undefined') {
        unsafeWindow.eval = safeEval;
      }
    }

    static #protectConsole() {
      const consoleHandler = {
        get(target, prop) {
          if (!config.consoleProps.includes(prop)) return target[prop];

          return function(...args) {
            if (DebuggerDetector.isPresent()) return;
            return OriginalFunctions.console[prop]?.apply(console, args);
          };
        },
        set(target, prop, value) {
          if (config.consoleProps.includes(prop)) return true;
          target[prop] = value;
          return true;
        }
      };

      window.console = new Proxy(console, consoleHandler);
    }

    static #setupMutationObserver() {
      new MutationObserver(mutations => {
        mutations.forEach(mutation => {
          mutation.addedNodes.forEach(node => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              // Clean script content
              if (node.tagName === 'SCRIPT') {
                const originalContent = node.textContent;
                const cleanedContent = Protection.#cleanCode(originalContent);
                if (originalContent !== cleanedContent) {
                  node.textContent = cleanedContent;
                }
              }
              // Clean attributes
              for (const attr of node.attributes) {
                if (attr.name.startsWith('on') && Protection.#cleanCode(attr.value) !== attr.value) {
                    node.removeAttribute(attr.name);
                }
              }
            }
          });
        });
      }).observe(document.documentElement, {
        childList: true,
        subtree: true,
      });
    }

    static #protectDeveloperKeys() {
      if (!config.protection.preventDevToolsKeys && !config.protection.preventViewSource) return;

      const handler = e => {
        const key = e.key.toUpperCase();
        const ctrl = e.ctrlKey;
        const shift = e.shiftKey;
        const alt = e.altKey;

        const isDevToolsKey =
          key === 'F12' ||
          (ctrl && shift && (key === 'I' || key === 'J' || key === 'C')) ||
          (ctrl && key === 'U');
        
        if (isDevToolsKey) {
          e.preventDefault();
          e.stopPropagation();
        }
      };

      window.addEventListener('keydown', handler, true);
    }

    static #protectRightClick() {
      if (!config.protection.preventRightClick) return;

      window.addEventListener('contextmenu', e => {
        e.preventDefault();
        e.stopPropagation();
        return false;
      }, true);
    }

    static #protectNetwork() {
      window.fetch = async function(...args) {
        if (DebuggerDetector.isPresent()) {
          throw new Error('Network request blocked');
        }
        return OriginalFunctions.fetch.apply(this, args);
      };

      window.XMLHttpRequest = function() {
        const xhr = new OriginalFunctions.XMLHttpRequest();
        const originalOpen = xhr.open;
        xhr.open = function(...args) {
          if (DebuggerDetector.isPresent()) {
            throw new Error('Network request blocked');
          }
          return originalOpen.apply(xhr, args);
        };
        return xhr;
      };
    }

    static #protectStorage() {
      const storageHandler = {
        get(target, prop) {
          if (DebuggerDetector.isPresent()) return null;
          return target[prop];
        },
        set(target, prop, value) {
          if (DebuggerDetector.isPresent()) return true;
          target[prop] = value;
          return true;
        }
      };

      window.localStorage = new Proxy(window.localStorage, storageHandler);
      window.sessionStorage = new Proxy(window.sessionStorage, storageHandler);
    }

    static #protectClipboard() {
      const events = [];
      if (config.protection.preventCopy) events.push('copy', 'cut');
      if (config.protection.preventPaste) events.push('paste');

      if (events.length) {
        events.forEach(eventName => {
          document.addEventListener(eventName, e => {
            e.preventDefault();
            e.stopPropagation();
          }, true);
        });
      }
    }

    static #protectPrinting() {
      if (!config.protection.preventPrint) return;

      window.addEventListener('beforeprint', e => {
        e.preventDefault();
      }, true);

      window.addEventListener('afterprint', e => {
        e.preventDefault();
      }, true);
    }

    static #protectWebWorkers() {
      window.Worker = function(scriptURL, options) {
        console.log('[Worker Created]', scriptURL);
        return new OriginalFunctions.Worker(scriptURL, options);
      };
    }

    static #applyDeveloperHelpers() {
      if (config.logging.monitorAPI) {
        this.#monitorAPICalls();
      }
      if (config.logging.monitorDOM) {
        this.#monitorDOMEvents();
      }
    }
    
    static #patchGlobalDebugVariables() {
        const noop = () => {};
        Object.defineProperties(window, {
            'debug': { value: noop, configurable: false, writable: false },
            'debugger': { value: noop, configurable: false, writable: false },
            'isDebuggerEnabled': { value: false, configurable: false, writable: false }
        });
    }
    
    static #monitorAPICalls() {
      const originalFetch = window.fetch;
      window.fetch = async (...args) => {
        logger.debug('[API Call]', ...args);
        return originalFetch.apply(this, args);
      };
    }

    static #monitorDOMEvents() {
      new MutationObserver(mutations => {
        mutations.forEach(mutation => {
          logger.debug('[DOM Change]', mutation);
        });
      }).observe(document.documentElement, {
        childList: true,
        subtree: true,
        attributes: true,
        characterData: true
      });
    }

    static #cleanCode(code) {
      if (typeof code !== 'string') return code;
      return code.replace(config.antiDebugRegex, '');
    }
  }
  // Main class
  class DevToolsBypass {
    static init() {
      try {
        DebuggerDetector.startHistoryCleanup();
        Protection.applyAll();
        logger.info('DevTools Bypass initialized successfully');
      } catch (e) {
        logger.error('Failed to initialize DevTools Bypass:', e);
      }
    }
  }

  // Initialize
  DevToolsBypass.init();
})();