DevTools Unlocked

Neutralizes devtools detection and blocking by libraries like disable-devtool and devtools-detector.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         DevTools Unlocked
// @namespace    groknt
// @version      3.6.2
// @description  Neutralizes devtools detection and blocking by libraries like disable-devtool and devtools-detector.
// @author       groknt
// @license      MIT
// @match        *://*/*
// @match        file:///*
// @inject-into  auto
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const BLOCKED_EVENTS = ["contextmenu", "selectstart", "copy", "cut", "paste"];

  const DEBUGGER_BODY_RE = /^\s*debugger\s*;?\s*$/;
  const DEVTOOL_WARN_RE = /permission.+DEVTOOL/i;

  function coreSetup(env) {
    let detectorPresent = false;
    let recentTrap = false;
    let recentTrapTimer = 0;

    const onTrapDetected = () => {
      detectorPresent = true;
      recentTrap = true;
      env.clearTimeout(recentTrapTimer);
      recentTrapTimer = env.setTimeout(() => {
        recentTrap = false;
      }, 100);
    };

    const hasOwn = (obj, prop) =>
      env.apply(env.ObjectProto.hasOwnProperty, obj, [prop]);

    function isTrap(value, depth) {
      if (value == null) return false;
      const type = typeof value;
      if (type !== "object" && type !== "function") return false;
      try {
        if (
          hasOwn(value, "toString") &&
          (env.isDate(value) || env.isRegExp(value) || type === "function")
        ) {
          return true;
        }
        if (env.isElement(value)) {
          const desc = env.desc(value, "id");
          if (desc && typeof desc.get === "function") return true;
        }
        if (env.isArray(value) && value.length >= 50 && value[0] === value[1]) {
          const first = value[0];
          if (
            first != null &&
            typeof first === "object" &&
            !env.isArray(first) &&
            env.keys(first).length >= 100
          ) {
            return true;
          }
        }
        if (
          !depth &&
          type === "object" &&
          !env.isArray(value) &&
          !env.isElement(value)
        ) {
          const keys = env.keys(value);
          if (keys.length > 0 && keys.length <= 10) {
            for (let i = 0; i < keys.length; i++) {
              const desc = env.desc(value, keys[i]);
              if (desc && "value" in desc && isTrap(desc.value, 1)) return true;
            }
          }
        }
      } catch {
        return true;
      }
      return false;
    }

    function hasTrap(args) {
      for (let i = 0; i < args.length; i++) {
        if (isTrap(args[i])) return true;
      }
      return false;
    }

    const isDetectorNoise = (args) =>
      detectorPresent &&
      args.length === 1 &&
      args[0] != null &&
      typeof args[0] === "object" &&
      !env.isArray(args[0]) &&
      env.getProto(args[0]) === env.ObjectProto &&
      env.keys(args[0]).length === 0;

    const nativeConsole = {
      log: env.console.log,
      table: env.console.table,
      warn: env.console.warn,
      clear: env.console.clear,
      dir: env.console.dir,
      dirxml: env.console.dirxml,
    };

    const deferNative = (nativeFn, args) =>
      env.setTimeout(() => env.apply(nativeFn, env.console, args), 0);

    env.console.log = env.func(function log(...args) {
      if (hasTrap(args)) return onTrapDetected();
      if (isDetectorNoise(args)) return;
      deferNative(nativeConsole.log, args);
    }, "log");

    env.console.table = env.func(function table(...args) {
      if (hasTrap(args)) return onTrapDetected();
      if (isDetectorNoise(args)) return;
      deferNative(nativeConsole.table, args);
    }, "table");

    env.console.warn = env.func(function warn(...args) {
      if (
        args.length &&
        typeof args[0] === "string" &&
        DEVTOOL_WARN_RE.test(args[0])
      ) {
        detectorPresent = true;
        return;
      }
      deferNative(nativeConsole.warn, args);
    }, "warn");

    env.console.clear = env.func(function clear() {
      if (recentTrap || detectorPresent) return;
      deferNative(nativeConsole.clear, []);
    }, "clear");

    if (nativeConsole.dir) {
      env.console.dir = env.func(function dir(...args) {
        if (hasTrap(args)) return onTrapDetected();
        if (isDetectorNoise(args)) return;
        deferNative(nativeConsole.dir, args);
      }, "dir");
    }

    if (nativeConsole.dirxml) {
      env.console.dirxml = env.func(function dirxml(...args) {
        if (hasTrap(args)) return onTrapDetected();
        if (isDetectorNoise(args)) return;
        deferNative(nativeConsole.dirxml, args);
      }, "dirxml");
    }

    const isTrapFormatter = (formatter) =>
      formatter != null &&
      typeof formatter.header === "function" &&
      formatter.header.length === 0 &&
      typeof formatter.hasBody !== "function";

    let realFormatters = env.win.devtoolsFormatters;
    try {
      env.def(env.win, "devtoolsFormatters", {
        configurable: true,
        get: env.wrap(() => realFormatters),
        set: env.wrap((val) => {
          if (env.isArray(val)) {
            const clean = env.newArray();
            let trapped = false;
            for (let i = 0; i < val.length; i++) {
              if (isTrapFormatter(val[i])) trapped = true;
              else clean.push(val[i]);
            }
            if (trapped) detectorPresent = true;
            val = clean;

            const origPush = val.push;
            val.push = env.func(function push(...items) {
              const cleanItems = env.newArray();
              for (let i = 0; i < items.length; i++) {
                if (isTrapFormatter(items[i])) detectorPresent = true;
                else cleanItems.push(items[i]);
              }
              return cleanItems.length > 0
                ? env.apply(origPush, this, cleanItems)
                : this.length;
            }, "push");
          }
          realFormatters = val;
        }),
      });
    } catch {}

    if (env.patchDescriptor) {
      env.patchDescriptor("devtoolsFormatters", () => realFormatters);
    }

    try {
      const outerWidthDesc =
        env.desc(env.win, "outerWidth") ||
        env.desc(env.WindowProto, "outerWidth");
      const outerHeightDesc =
        env.desc(env.win, "outerHeight") ||
        env.desc(env.WindowProto, "outerHeight");

      const getReal = (desc) =>
        desc?.get ? env.apply(desc.get, env.win, []) : 0;

      env.def(
        env.win,
        "outerWidth",
        env.getter(function outerWidth() {
          const real = getReal(outerWidthDesc);
          const inner = env.win.innerWidth;
          if (real - inner > 100) return Math.round(inner + 16);
          return real;
        }, "get outerWidth"),
      );

      env.def(
        env.win,
        "outerHeight",
        env.getter(function outerHeight() {
          const real = getReal(outerHeightDesc);
          const inner = env.win.innerHeight;
          if (real - inner > 100) return Math.round(inner + 16);
          return real;
        }, "get outerHeight"),
      );
    } catch {}

    function isDevToolKey(e) {
      const keyCode = e.keyCode || e.which;
      if (keyCode === 123) return true;
      const ctrl = e.ctrlKey,
        meta = e.metaKey,
        shift = e.shiftKey,
        alt = e.altKey;
      return (
        (ctrl && shift && (keyCode === 73 || keyCode === 74)) ||
        (meta && alt && (keyCode === 73 || keyCode === 74)) ||
        (ctrl && keyCode === 85) ||
        (meta && alt && keyCode === 85)
      );
    }

    env.on(
      env.win,
      "keydown",
      env.wrap((e) => {
        if (isDevToolKey(e)) env.stop(e);
      }),
      true,
    );

    const origFunction = env.win.Function;
    const isDebuggerBody = (body) =>
      typeof body === "string" && DEBUGGER_BODY_RE.test(body);
    const noopFn = env.func(() => {}, "anonymous");

    const funcHandler = env.newObject();
    funcHandler.apply = env.wrap((target, thisArg, args) => {
      if (args.length && isDebuggerBody(args[args.length - 1])) return noopFn;
      return env.reflectApply(target, thisArg, args);
    });
    funcHandler.construct = env.wrap((target, args, newTarget) => {
      if (args.length && isDebuggerBody(args[args.length - 1])) return noopFn;
      return env.reflectConstruct(target, args, newTarget);
    });

    const FunctionProxy = env.proxy(origFunction, funcHandler, "Function");
    env.win.Function = FunctionProxy;
    origFunction.prototype.constructor = FunctionProxy;

    const nativeAddListener = env.EventTargetProto.addEventListener;
    const nativeRemoveListener = env.EventTargetProto.removeEventListener;
    const blockedEvents = env.newSet(env.blockedEvents);
    const listenerMap = env.newWeakMap();

    const isWindow = (obj) => {
      try {
        return obj.self === obj;
      } catch {
        return false;
      }
    };

    env.EventTargetProto.addEventListener = env.func(function addEventListener(
      type,
      listener,
      options,
    ) {
      if (
        !blockedEvents.has(type) ||
        typeof listener !== "function" ||
        !isWindow(this)
      ) {
        return env.apply(nativeAddListener, this, [type, listener, options]);
      }
      let wrapped = listenerMap.get(listener);
      if (!wrapped) {
        wrapped = env.wrap(function (e) {
          if (!detectorPresent) return env.apply(listener, this, [e]);
          const realPreventDefault = e.preventDefault;
          env.def(e, "preventDefault", {
            value: env.func(() => {}, "preventDefault"),
            writable: true,
            configurable: true,
          });
          const returnValueDesc = env.desc(e, "returnValue");
          env.def(e, "returnValue", {
            get: env.wrap(() => true),
            set: env.wrap(() => {}),
            configurable: true,
          });
          try {
            return env.apply(listener, this, [e]);
          } finally {
            env.def(e, "preventDefault", {
              value: realPreventDefault,
              writable: true,
              configurable: true,
            });
            if (returnValueDesc) env.def(e, "returnValue", returnValueDesc);
            else delete e.returnValue;
          }
        });
        listenerMap.set(listener, wrapped);
      }
      return env.apply(nativeAddListener, this, [type, wrapped, options]);
    }, "addEventListener");

    env.EventTargetProto.removeEventListener = env.func(
      function removeEventListener(type, listener, options) {
        if (
          blockedEvents.has(type) &&
          typeof listener === "function" &&
          isWindow(this)
        ) {
          const wrapped = listenerMap.get(listener);
          if (wrapped)
            return env.apply(nativeRemoveListener, this, [
              type,
              wrapped,
              options,
            ]);
        }
        return env.apply(nativeRemoveListener, this, [type, listener, options]);
      },
      "removeEventListener",
    );

    const nativeQuerySelector = env.DocumentProto.querySelector;
    env.DocumentProto.querySelector = env.func(function querySelector(
      selector,
    ) {
      if (selector === "[disable-devtool-auto]") {
        detectorPresent = true;
        return null;
      }
      return env.apply(nativeQuerySelector, this, [selector]);
    }, "querySelector");
  }

  function hasXray() {
    try {
      const page = window.wrappedJSObject;
      if (!page || page === window) return false;
      if (typeof exportFunction !== "function") return false;
      const fn = exportFunction(() => true, page);
      page.__xtest = fn;
      const success = page.__xtest() === true;
      delete page.__xtest;
      return success;
    } catch {
      return false;
    }
  }

  if (hasXray()) xraySetup();
  else pageSetup();

  function xraySetup() {
    const page = window.wrappedJSObject;
    const exportFn = (fn) => exportFunction(fn, page);
    const pageObj = () => new page.Object();
    const pageArr = () => new page.Array();

    coreSetup({
      blockedEvents: BLOCKED_EVENTS,
      win: page,
      doc: page.document,
      console: page.console,
      WindowProto: page.Window.prototype,
      DocumentProto: page.Document.prototype,
      EventTargetProto: page.EventTarget.prototype,
      ObjectProto: page.Object.prototype,
      apply: (fn, ctx, args) => Reflect.apply(fn, ctx, args),
      clearTimeout: (id) => page.clearTimeout(id),
      setTimeout: (fn, delay) => page.setTimeout(exportFn(fn), delay),
      isDate: (val) => val instanceof page.Date,
      isRegExp: (val) => val instanceof page.RegExp,
      isElement: (val) => val instanceof page.Element,
      isArray: (val) => page.Array.isArray(val),
      keys: (obj) => page.Object.keys(obj),
      desc: (obj, prop) => page.Object.getOwnPropertyDescriptor(obj, prop),
      def: (obj, prop, descriptor) =>
        page.Object.defineProperty(obj, prop, descriptor),
      getProto: (obj) => page.Object.getPrototypeOf(obj),
      newArray: pageArr,
      newObject: pageObj,
      newSet: (items) => new Set(items),
      newWeakMap: () => new WeakMap(),
      wrap: exportFn,
      func: exportFn,
      getter: (fn) => {
        const descriptor = pageObj();
        descriptor.get = exportFn(fn);
        descriptor.configurable = true;
        descriptor.enumerable = true;
        return descriptor;
      },
      proxy: (target, handler) => new page.Proxy(target, handler),
      reflectApply: (target, ctx, args) => Reflect.apply(target, ctx, args),
      reflectConstruct: (target, args, newTarget) =>
        Reflect.construct(target, args, newTarget),
      stop: (e) => e.stopImmediatePropagation(),
      on: (target, evt, fn, capture) =>
        target.addEventListener(evt, fn, capture),
      patchDescriptor: (prop, getValue) => {
        const nativeDesc = (obj, key) =>
          page.Object.getOwnPropertyDescriptor(obj, key);
        const patched = exportFn(function getOwnPropertyDescriptor(obj, key) {
          const descriptor = nativeDesc(obj, key);
          if (
            descriptor &&
            (descriptor.get || descriptor.set) &&
            key === prop
          ) {
            return {
              value: getValue(),
              writable: true,
              configurable: true,
              enumerable: true,
            };
          }
          return descriptor;
        });
        page.Object.getOwnPropertyDescriptor = patched;
        page.Reflect.getOwnPropertyDescriptor = patched;
      },
    });
  }

  function pageSetup() {
    const nativeStrings = new Map();
    const origToString = Function.prototype.toString;
    const nativeTemplate = origToString.call(document.getElementById);
    const toStringHandler = {
      apply(target, thisArg, args) {
        return nativeStrings.has(thisArg)
          ? nativeStrings.get(thisArg)
          : Reflect.apply(target, thisArg, args);
      },
    };
    const toStringProxy = new Proxy(origToString, toStringHandler);
    Function.prototype.toString = toStringProxy;
    nativeStrings.set(
      toStringProxy,
      nativeTemplate.replace("getElementById", "toString"),
    );

    const markNative = (fn, name) => {
      nativeStrings.set(
        fn,
        nativeTemplate.replace("getElementById", name || ""),
      );
      return fn;
    };

    coreSetup({
      blockedEvents: BLOCKED_EVENTS,
      win: window,
      doc: document,
      console: console,
      WindowProto: Window.prototype,
      DocumentProto: Document.prototype,
      EventTargetProto: EventTarget.prototype,
      ObjectProto: Object.prototype,
      apply: Reflect.apply,
      clearTimeout: clearTimeout.bind(window),
      setTimeout: setTimeout.bind(window),
      isDate: (val) => val instanceof Date,
      isRegExp: (val) => val instanceof RegExp,
      isElement: (val) => val instanceof Element,
      isArray: Array.isArray,
      keys: Object.keys,
      desc: Object.getOwnPropertyDescriptor,
      def: Object.defineProperty,
      getProto: Object.getPrototypeOf,
      newArray: () => [],
      newObject: () => ({}),
      newSet: (items) => new Set(items),
      newWeakMap: () => new WeakMap(),
      wrap: (fn) => fn,
      func: (fn, name) => markNative(fn, name),
      getter: (fn, name) => ({
        get: markNative(fn, name),
        configurable: true,
        enumerable: true,
      }),
      proxy: (target, handler, name) => {
        const proxy = new Proxy(target, handler);
        if (name) markNative(proxy, name);
        return proxy;
      },
      reflectApply: Reflect.apply,
      reflectConstruct: Reflect.construct,
      stop: (e) => e.stopImmediatePropagation(),
      on: (target, evt, fn, capture) =>
        EventTarget.prototype.addEventListener.call(target, evt, fn, capture),
      patchDescriptor: (prop, getValue) => {
        const nativeDesc = Object.getOwnPropertyDescriptor;
        const patched = markNative(function getOwnPropertyDescriptor(obj, key) {
          const descriptor = nativeDesc(obj, key);
          if (
            descriptor &&
            (descriptor.get || descriptor.set) &&
            key === prop
          ) {
            return {
              value: getValue(),
              writable: true,
              configurable: true,
              enumerable: true,
            };
          }
          return descriptor;
        }, "getOwnPropertyDescriptor");
        Object.getOwnPropertyDescriptor = patched;
        Reflect.getOwnPropertyDescriptor = patched;
      },
    });
  }
})();