Scratch In-Editor Obfuscator

Scratchのエディタ上で直接、変数・リスト・定義名をランダム化する

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu betiği yüklemek için bir betik yöneticisi eklentisi yüklemeniz gerekecektir.

(Zaten bir betik yöneticim var, hadi yükleyelim!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Scratch In-Editor Obfuscator
// @namespace    https://scratch.mit.edu/
// @version      1.0
// @description  Scratchのエディタ上で直接、変数・リスト・定義名をランダム化する
// @author       You
// @match        https://scratch.mit.edu/projects/*
// @grant        none
// ==/UserScript==

(() => {
    'use strict';

    const makeRandomName = () => '_0x' + Math.random().toString(36).substring(2, 8);

    const obfuscateCurrentProject = () => {
        // ScratchのVM(Virtual Machine)を取得
        const vm = window.vm || (document.querySelector('canvas') && document.querySelector('canvas').__reactInternalInstance$?.return?.stateNode?.props?.vm);
        if (!vm) {
            alert('Scratchのエディタデータが見つかりませんでした。');
            return;
        }

        const nameMap = new Map();
        const getNewName = (oldName) => {
            if (oldName.startsWith('☁')) return oldName; // クラウド変数は維持
            if (!nameMap.has(oldName)) {
                nameMap.set(oldName, makeRandomName());
            }
            return nameMap.get(oldName);
        };

        // 1. 変数・リスト・メッセージの書き換え
        vm.runtime.targets.forEach(target => {
            // 変数名
            for (const id in target.variables) {
                target.variables[id].name = getNewName(target.variables[id].name);
            }
            // リスト名
            for (const id in target.lists) {
                target.lists[id].name = getNewName(target.lists[id].name);
            }
            // メッセージ名
            for (const id in target.broadcasts) {
                target.broadcasts[id] = getNewName(target.broadcasts[id]);
            }
        });

        // 2. カスタムブロック(定義)の書き換え
        vm.runtime.targets.forEach(target => {
            for (const blockId in target.blocks._blocks) {
                const block = target.blocks._blocks[blockId];
                if (block.mutation && block.mutation.proccode) {
                    const parts = block.mutation.proccode.split(/(%[sbnb])/);
                    parts[0] = getNewName(parts[0]);
                    block.mutation.proccode = parts.join('');
                }
            }
        });

        // 3. 画面(ワークスペース)を再描画して表示を更新
        vm.emitWorkspaceUpdate();
        alert('画面上の変数名や定義名をランダム化しました!');
    };

    // UIボタンの作成
    const addButton = () => {
        if (document.getElementById('direct-obfuscate-btn')) return;

        const btn = document.createElement('button');
        btn.id = 'direct-obfuscate-btn';
        btn.innerText = '⚡ 画面上の名前を難読化';
        btn.style.cssText = `
            position: fixed;
            top: 10px;
            right: 180px;
            z-index: 99999;
            padding: 6px 12px;
            background: #e91e63;
            color: white;
            border: none;
            border-radius: 4px;
            font-weight: bold;
            cursor: pointer;
            box-shadow: 0 2px 5px rgba(0,0,0,0.3);
        `;

        btn.addEventListener('click', obfuscateCurrentProject);
        document.body.appendChild(btn);
    };

    const timer = setInterval(() => {
        if (document.querySelector('.gui_menu-bar_2300M') || document.querySelector('canvas')) {
            addButton();
        }
    }, 1000);
})();