Greasy Fork is available in English.

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://greasyfork.org/vi/users/1195312-renji-yuusei
// @version      2024.12.23.2
// @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
    }
  };

  // Configuration
  const config = {
    debugPatterns: {
      basic: /[;\s]*(?:debugger|debug(?:ger)?|breakpoint|console\.[a-z]+)[\s;]*/gi,
      advanced: /(?:debugger|debug(?:ger)?|breakpoint|devtools?)(?:\s*\{[\s\S]*?\}|\s*\([^)]*\)|\s*;|\s*$)/gi,
      timing: /(?:performance(?:\.timing)?|Date)\.(?:now|getTime)\(\)|new\s+Date(?:\s*\(\s*\))?\s*\.getTime\(\)/gi,
      eval: /(?:eval|Function|setTimeout|setInterval)\s*\(\s*(?:[`'"].*?(?:debugger|debug|breakpoint).*?[`'"]|\{[\s\S]*?(?:debugger|debug|breakpoint)[\s\S]*?\})\s*\)/gi,
      devtools: /(?:isDevTools?|devtools?|debugMode|debug_mode|debugEnabled)\s*[=:]\s*(?:true|1|!0|yes)/gi,
      consoleCheck: /console\.(?:log|warn|error|info|debug|trace|dir|table)\s*\([^)]*\)/gi,
      functionDebug: /function\s*[^(]*\([^)]*\)\s*\{(?:\s|.)*?(?:debugger|debug|breakpoint)(?:\s|.)*?\}/gi,
      sourceMap: /\/\/[#@]\s*source(?:Mapping)?URL\s*=\s*(?:data:|https?:)?\/\/.*?$/gm,
      debugStrings: /(['"`])(?:(?!\1).)*?(?:debug|debugger|breakpoint|devtools?)(?:(?!\1).)*?\1/gi,
      debugComments: /\/\*[\s\S]*?(?:debug|debugger|breakpoint|devtools?)[\s\S]*?\*\/|\/\/.*(?:debug|debugger|breakpoint|devtools?).*$/gim
    },
    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
    },
    logging: {
      enabled: true,
      prefix: CONSTANTS.PREFIX,
      levels: Object.values(CONSTANTS.LOG_LEVELS),
      detailedErrors: true
    },
    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 isPresent() {
      try {
        const cacheKey = 'debugger_check';
        const cached = this.#detectionCache.get(cacheKey);
        if (cached && Date.now() - cached.timestamp < 500) {
          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;
        const frames = stack.split('\n');
        const uniqueFrames = new Set(frames);

        return {
          depth: frames.length,
          hasDebugKeywords: frames.some(frame => 
            Object.values(config.debugPatterns).some(pattern => pattern.test(frame))
          ),
          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 || 0
      };
    }
  }

  // Helper functions
  const BypassHelpers = {
    disableAntiDebugging: {
      patchTimingChecks() {
        const originalNow = Date.now;
        Date.now = function() {
          return originalNow.call(this) - Math.random() * 100;
        };
      },

      patchStackTraces() {
        Error.prepareStackTrace = (_, stack) => 
          stack.filter(frame => !frame.toString().includes('debugger'));
      },

      patchDebugChecks() {
        const noop = () => {};
        window.debug = noop;
        window.debugger = noop;
        window.isDebuggerEnabled = false;
      }
    },

    debugTools: {
      monitorAPICalls() {
        const originalFetch = window.fetch;
        window.fetch = async (...args) => {
          console.log('[API Call]', ...args);
          return originalFetch.apply(this, args);
        };
      },

      monitorDOMEvents() {
        new MutationObserver(mutations => {
          mutations.forEach(mutation => {
            console.log('[DOM Change]', mutation);
          });
        }).observe(document.body, {
          childList: true,
          subtree: true
        });
      }
    }
  };

  // Protection class
  class Protection {
    static applyAll() {
      this.#protectTimers();
      this.#protectTiming();
      this.#protectFunction();
      this.#protectStack();
      this.#protectEval();
      this.#protectConsole();
      this.#setupMutationObserver();
      this.#protectDevToolsKeys();
      this.#protectRightClick();
      this.#protectViewSource();
      this.#protectNetwork();
      this.#protectStorage();
      this.#protectClipboard();
      this.#protectPrinting();
      this.#protectWebWorkers();
      this.#enableDebuggingHelpers();
    }

    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;

      const errorHandler = {
        get(target, prop) {
          if (prop === 'stack') {
            return Protection.#cleanCode(target.stack);
          }
          return target[prop];
        }
      };

      const originalErrorPrototype = Error.prototype;
      const proxyErrorPrototype = Object.create(originalErrorPrototype);

      Object.defineProperty(proxyErrorPrototype, 'stack', {
        get() {
          return Protection.#cleanCode(new Error().stack);
        },
        configurable: true
      });

      try {
        Error.prototype = proxyErrorPrototype;
      } 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 => {
          if (mutation.type === 'childList') {
            mutation.addedNodes.forEach(node => {
              if (node.tagName === 'SCRIPT') {
                const originalContent = node.textContent;
                const cleanedContent = Protection.#cleanCode(originalContent);
                if (originalContent !== cleanedContent) {
                  node.textContent = cleanedContent;
                }
              }
            });
          }
        });
      }).observe(document, {
        childList: true,
        subtree: true
      });
    }

    static #protectDevToolsKeys() {
      if (!config.protection.preventDevToolsKeys) return;

      const handler = e => {
        const { keyCode, ctrlKey, shiftKey, altKey } = e;
        if (
          keyCode === 123 || // F12
          (ctrlKey && shiftKey && keyCode === 73) || // Ctrl+Shift+I
          (ctrlKey && shiftKey && keyCode === 74) || // Ctrl+Shift+J
          (ctrlKey && keyCode === 85) || // Ctrl+U
          (altKey && keyCode === 68) // Alt+D
        ) {
          e.preventDefault();
          e.stopPropagation();
          return false;
        }
      };

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

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

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

    static #protectViewSource() {
      if (!config.protection.preventViewSource) return;

      const handler = e => {
        if (
          (e.ctrlKey && (e.key === 'u' || e.key === 's')) || // Ctrl+U, Ctrl+S
          (e.ctrlKey && e.shiftKey && e.key === 'i') || // Ctrl+Shift+I
          (e.ctrlKey && e.shiftKey && e.key === 'j') || // Ctrl+Shift+J
          (e.ctrlKey && e.shiftKey && e.key === 'c') || // Ctrl+Shift+C
          e.key === 'F12'
        ) {
          e.preventDefault();
          e.stopPropagation();
          return false;
        }
      };

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

      window.addEventListener('beforeunload', e => {
        if (window.location.protocol === 'view-source:') {
          e.preventDefault();
          return false;
        }
      });
    }

    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() {
      if (!config.protection.preventCopy) return;

      document.addEventListener('copy', e => {
        e.preventDefault();
      }, true);

      document.addEventListener('cut', e => {
        e.preventDefault();
      }, true);

      if (config.protection.preventPaste) {
        document.addEventListener('paste', e => {
          e.preventDefault();
        }, 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 #enableDebuggingHelpers() {
      BypassHelpers.disableAntiDebugging.patchTimingChecks();
      BypassHelpers.disableAntiDebugging.patchStackTraces();
      BypassHelpers.debugTools.monitorAPICalls();
      BypassHelpers.debugTools.monitorDOMEvents();
    }

    static #cleanCode(code) {
      if (typeof code !== 'string') return code;

      let cleanCode = code
        .replace(/\/\/[#@]\s*source(?:Mapping)?URL\s*=.*$/gm, '')
        .replace(/(['"`])(?:(?!\1).)*?(?:debug|debugger|devtools?)(?:(?!\1).)*?\1/gi, match => `'${btoa(match)}'`)
        .replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, '')
        .replace(/debugger|debug\s*\(|console\.[a-z]+/gi, '');

      Object.values(config.debugPatterns).forEach(pattern => {
        cleanCode = cleanCode.replace(pattern, '');
      });

      return cleanCode
        .replace(/function\s*\(/g, 'function /*debug*/(')
        .replace(/return\s+/g, 'return /*debug*/ ');
    }
  }

  // Main class
  class DevToolsBypass {
    static init() {
      try {
        Protection.applyAll();
        logger.info('DevTools Bypass initialized successfully');
      } catch (e) {
        logger.error('Failed to initialize DevTools Bypass:', e);
      }
    }
  }

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