【React】 Scratch のフロントエンドをハックしよう
https://scratch.mit.edu/ は React で書かれています。これをハックしてみましょう。
外部から Scratch のフロントエンドの State にアクセスすることができるため、いろいろ遊べます。
_reactRootContainer
Scratch は React 16 を使用していますが、React にマウントされたルートの Element には _reactRootContainer
というプロパティがついています。これを使えば、React の内部状態にアクセスできます。
Scratch のプロジェクトページ では、ルートの Element の ID は app
です。これを使ってみましょう:
const app = document.getElementById('app')
const rootContainer = app._reactRootContainer
console.log(rootContainer)
ブラウザのコンソールで実行してみると、以下のような結果が得られます。
Fiber ノードを取得する
React は内部的に Fiber というグラフ構造のデータを使用して UI を管理しています。先ほど取得した rootContainer
から Fiber ノードを取得することができます。
const rootFiberNode = rootContainer._internalRoot.current
console.log(rootFiberNode)
しかし、これはルートの Fiber ノードであり、実際のコンポーネントの Fiber ノードはその子供にあります。これを取得するためには、rootFiberNode.child
を使います。
const appFiberNode = rootFiberNode.child
memoizedProps
というプロパティを使うことで、コンポーネントの Props にアクセスできます。
ルートのコンポーネントには、store
という props を使って Redux のストアを渡しています。これを取得して、Scratch のストアにアクセスしてみましょう。
const scratchState = appFiberNode.memoizedProps.store.getState()
console.log(scratchState)
いくつかのストアが組み合わさっていますが、重要なのは scratchGui
というストアです。これを使うことで、Scratch エディタにアクセスできます。
vm を使って遊ぼう
Scratch は、Scratch VM という仮想マシンを利用してプロジェクトを実行しています。この VM は、scratchGui
ストアの中に格納してあります。取り出してみましょう。
const vm = scratchState.scratchGui.vm
console.log(vm)
vm を取得すればいろいろなことができます。例えば、コンソールからターボモードを有効にすることができます。
vm.runtime.turboMode = true
また、このように変数を外部から変更できます。
ストアからアクセスできない値を変更する
ストアからアクセスできない値もあります。例えば、Scratch では自分以外が作成したプロジェクトで「中を見る」ボタンを押すと、「クラウド変数」と呼ばれる WebSocket を使ったユーザ共通の変数の通信が接続されます。この切断処理はストアからアクセスすることができません。
しかし、Fiber ノードを使い、任意のコンポーネントを取得することが可能です。
export function getSpecifiedFiber(
root: ReactFiber,
cond: (fiber: ReactFiber) => boolean,
) {
const stack = [root]
while (true) {
const fiber = stack.pop()
if (!fiber) {
return null
}
if (cond(fiber)) {
return fiber
}
if (fiber.child) {
stack.push(fiber.child)
}
if (fiber.sibling) {
stack.push(fiber.sibling)
}
}
}
この関数は、Fiber ノードを深さ優先探索で探索し、条件を満たすノードを返す関数です。これを使います。
クラウド変数の接続処理は、cloudManagerHOC
という HOC が CloudManager
というコンポーネントを UI に追加することで行われています:
この中に「中を見る」を押したときにの切断処理があります。
これを空の関数に置き換えることで、クラウド変数の接続を切断を無効化することができます!
まず、getSpecifiedFiber
を使って CloudManager
がマウントされている Fiber ノードを取得しましょう。
このように propTypes に canModifyCloudData
というプロパティがあるので、これを使って判定します。
const cloudManagerFiber = getSpecifiedFiber(root, (fiber) => {
if (typeof fiber.type === 'function') {
const propTypes = fiber.elementType.propTypes
if (propTypes && 'canModifyCloudData' in propTypes) {
return true
}
}
return false
});
こうして取得した Fiber ノードから、コンポーネントを取得しましょう。
const cloudManagerHOC = cloudManagerHOCFiber.elementType.prototype
このコンポーネントの disconnectFromCloud
メソッドを空の関数に置き換えます。
cloudManagerHOC.disconnectFromCloud = () => {}
これで「中を見る」を押したとしても、クラウド変数の接続が切断されなくなります。
このように、React の Fiber ノードを使うことで、ストアからアクセスできない値も変更することができます。
まとめ
- Scratch は React 16 を使用している
- Fiber ノードを使えば Scratch を外部からハックできる
- Redux ストアからアクセスできない値も Fiber ノードを使えば変更できる
みなさんも、Scratch のソースコードを読み、ハックしてみてください。Happy Hacking!
Discussion