scratchファイル(.sb3)を分析＆解説してみた　

# やろうと思ったきっかけ
友達がsb3ファイルを書き換えてscratchにないブロックを作ったり没ブロックを出したりしているのを見て、自分も挑戦してみたいと思った。
しかし友達はサイトに載っている方法を見ただけで、詳しいことはよく分からないとのことだったので、自分で調べてみることにした。

ちなみに詳しく調べすぎて長くなっています。（なので二つに分けています）
# sb3ファイルの概要　by[scratch wiki](https://ja.scratch-wiki.info/wiki/Scratchプロジェクトのダウンロード時のファイル形式?utm_source=chatgpt.com)

* SB3は "ZIP形式で圧縮されている" ので一度ZIPに変換してから解凍すると中身が見られる。
* 中にはコスチュームと音、プロジェクトの内容が書かれているproject.jsonが入っている。
* project.jsonにブロックの情報や変数などが格納されているのでそこを編集することでscratchにないブロックを作ったり没ブロックを出したりできる。
![スクリーンショット 2024-12-25 12.41.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3965914/514c09f7-0dc7-2eed-90e1-121214a285f6.png)
(ちなみに
83a9787d4cb6f3b7632b4ddfebf74367.wav　ーステージにある「ポップ」と言う効果音
83c36d806dc92327b9e7049a565c6bff.wav ースプライトにある「ニャー」と言う鳴き声
だった)

# 解説
大体3つのパーツに分かれている
1. ステージ情報
2. スプライト情報(ブロックコード)
3. メタ情報

<details><summary>全部見る（132行 一部変更）</summary>

```json:project.json
{
	"targets": [
		{
			"isStage": true,
			"name": "Stage",
			"variables": {
				"`jEk@4|i[#Fk?(8x)AV.-my variable": [
					"変数",
					0
				]
			},
			"lists": {},
			"broadcasts": {},
			"blocks": {},
			"comments": {},
			"currentCostume": 0,
			"costumes": [
				{
					"name": "背景1",
					"dataFormat": "svg",
					"assetId": "cd21514d0531fdffb22204e0ec5ed84a",
					"md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg",
					"rotationCenterX": 240,
					"rotationCenterY": 180
				}
			],
			"sounds": [
				{
					"name": "ポップ",
					"assetId": "83a9787d4cb6f3b7632b4ddfebf74367",
					"dataFormat": "wav",
					"format": "",
					"rate": 48000,
					"sampleCount": 1123,
					"md5ext": "83a9787d4cb6f3b7632b4ddfebf74367.wav"
				}
			],
			"volume": 100,
			"layerOrder": 0,
			"tempo": 60,
			"videoTransparency": 50,
			"videoState": "on",
			"textToSpeechLanguage": null
		},
		{
			"isStage": false,
			"name": "スプライト1",
			"variables": {},
			"lists": {},
			"broadcasts": {},
			"blocks": {
				";2N|+XZ23R-XYd`u-Eo?": {
					"opcode": "looks_say",
					"next": null,
					"parent": "BJHdBztzfK`B_=RZ7qE$",
					"inputs": {
						"MESSAGE": [
							1,
							[
								10,
								"hello wold"
							]
						]
					},
					"fields": {},
					"shadow": false,
					"topLevel": false
				},
				"BJHdBztzfK`B_=RZ7qE$": {
					"opcode": "event_whenflagclicked",
					"next": ";2N|+XZ23R-XYd`u-Eo?",
					"parent": null,
					"inputs": {},
					"fields": {},
					"shadow": false,
					"topLevel": true,
					"x": 482,
					"y": 102
				}
			},
			"comments": {},
			"currentCostume": 0,
			"costumes": [
				{
					"name": "コスチューム1",
					"bitmapResolution": 1,
					"dataFormat": "svg",
					"assetId": "bcf454acf82e4504149f7ffe07081dbc",
					"md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
					"rotationCenterX": 48,
					"rotationCenterY": 50
				},
				{
					"name": "コスチューム2",
					"bitmapResolution": 1,
					"dataFormat": "svg",
					"assetId": "0fb9be3e8397c983338cb71dc84d0b25",
					"md5ext": "0fb9be3e8397c983338cb71dc84d0b25.svg",
					"rotationCenterX": 46,
					"rotationCenterY": 53
				}
			],
			"sounds": [
				{
					"name": "ニャー",
					"assetId": "83c36d806dc92327b9e7049a565c6bff",
					"dataFormat": "wav",
					"format": "",
					"rate": 48000,
					"sampleCount": 40681,
					"md5ext": "83c36d806dc92327b9e7049a565c6bff.wav"
				}
			],
			"volume": 100,
			"layerOrder": 1,
			"visible": true,
			"x": 0,
			"y": 0,
			"size": 100,
			"direction": 90,
			"draggable": false,
			"rotationStyle": "all around"
		}
	],
	"monitors": [ ],
	"extensions": [ ],
	"meta": {
		"semver": "3.0.0",
		"vm": "5.0.40",
		"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
	}
}
```
</details>

### jsonコード(1. ステージ情報)


```json:project.json

{
	"targets": [
		{
			"isStage": true,
			"name": "Stage",
			"variables": {
				"`jEk@4|i[#Fk?(8x)AV.-my variable": [
					"変数",
					0
				]
			},
			"lists": {},
			"broadcasts": {},
			"blocks": {},
			"comments": {},
			"currentCostume": 0, 
			"costumes": [
				{
					"name": "背景1",
					"dataFormat": "svg",
					"assetId": "cd21514d0531fdffb22204e0ec5ed84a",
					"md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg",
					"rotationCenterX": 240,
					"rotationCenterY": 180
				}
			],
		
```
</details>


少しずつ見ていこう。
まずはこの部分
```json
"isStage": true,
			"name": "Stage",
			"variables": {
				"`jEk@4|i[#Fk?(8x)AV.-my variable": [
					"変数",
					0
				]
```
```"isStage": true,```でステージであることを示している。
```"name": "Stage",```名前はstage 

次にこれだ

```json
"variables": {
				"`jEk@4|i[#Fk?(8x)AV.-my variable": [
					"変数",
					0
				]
```
変数(variables)の「変数」を初期値0で定義。
jEk@4|i[#Fk?(8x)AV.-my variable　の意味は調べても出てこなかったのでAIに聞いてみた。
>この文字列（jEk@4|i[#Fk?(8x)AV.-my variable）は、Scratchプロジェクト内部で一意に変数を識別するためのランダムに生成されたIDのようなもので、通常は人間にとって意味のある名前ではありません。

だそうです。
要は判別用のIDってことですね
___
次にこの部分
```json
"lists": {},
			"broadcasts": {},
			"blocks": {},
			"comments": {},
			"currentCostume": 0, 
			"costumes": [
				{
					"name": "背景1",
					"dataFormat": "svg",
					"assetId": "cd21514d0531fdffb22204e0ec5ed84a",
					"md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg",
					"rotationCenterX": 240,
					"rotationCenterY": 180
				}
			],
```
```lists": {}```そのステージやスプライトに関連付けられたリストを格納する場所。


<details><summary>リストの中身がある場合はこうなります</summary>


```json
"lists": {
				"|zx~o5mY:B.%Aa^+w[7$": [
					"リストの名前",
					[
						"なにか",
						"なにか",
						"なにか",
						"なにか",
						"なにか",
						"なにか",
						"なにか",
						"なにか",
						"なにか",
						"なにか"
					]
				]
			}
   ```

</details>

___

```"broadcasts": {},```　メッセージ（イベント）を格納する場所。

```blocks": {},```　コードブロックを格納する（何も書かれていないのでブロックがないとわかる）


```"comments": {},```ブロックについてるコメント
<details><summary>サンプルコード</summary>

```json
"zC:P[rT^=P{EuI9Gy3Z_": {
					"blockId": ";2N|+XZ23R-XYd`u-Eo?",
					"x": 473.3009262084961,
					"y": 172,
					"width": 200,
					"height": 200,
					"minimized": false,
					"text": "コメント"
				}
			},
   ```

```"blockId": ";2N|+XZ23R-XYd`u-Eo?",```どのブロックについてるか(この場合;2N|+XZ23R-XYd`u-Eo? のブロックについている)

```"minimized": false,```コメントをたたむか

```"text": "コメント"```コメントのテキスト
</details>

 ___
```currentCostume": 0```　現在選択されている背景（スプライトの場合はコスチューム＆0なので一番最初のコスチュームが使用されているとわかる）

```"costumes": [ ]```このターゲットで使用できるコスチューム（または背景）のリスト
<details><summary>要素一覧</summary>

```"name": "背景1,"```コスチュームの名前。

```"dataFormat": "svg",```ファイル形式（SVG形式のベクター画像）

```"assetId": "cd21514d0531fdffb22204e0ec5ed84a",```　MD5ハッシュ　（ファイルのデータ内容を基に計算された固定長の文字列で、ファイルが改変されていないかを確認するために使われる）

```"md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg",```　ファイルの名前（MD5ハッシュ＋拡張子）

```"rotationCenterX": 240,"rotationCenterY": 180```画像の回転中心の座標

</details>

2は長いので今度解説します。
書き方は大体同じです。

### jsonコード(3. メタ情報)

ここではプロジェクトの技術的な詳細を示している。

```json:project.json
"monitors": [ ],
	"extensions": [ ],
	"meta": {
		"semver": "3.0.0",
		"vm": "5.0.40",
		"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
	}
```

```"monitors": [ ]```　ステージに変数を表示させている時の情報





<details><summary>サンプルコードと解説</summary>

```json
	"monitors": [
		{
			"id": "`jEk@4|i[#Fk?(8x)AV.-my variable",
			"mode": "default",
			"opcode": "data_variable",
			"params": {
				"VARIABLE": "変数"
			},
			"spriteName": null,
			"value": 0,
			"width": 0,
			"height": 0,
			"x": 5,
			"y": 5,
			"visible": true,
			"sliderMin": 0,
			"sliderMax": 100,
			"isDiscrete": true
		}
	],
```

```"id": "(ID)",```　ID

```"mode": "(MODE)",```モニターの表示モード（他にはスライダー、大きな表示がある）

```"opcode": "(op)",```モニターが表示する対象の種類（リストか変数）

```"params": {},```変数パラメーター

``` "spriteName": null,```モニターが表示される対象となるスプライト名を指定

```"value": 0,```モニターに表示されている変数の値

```"width": 0,"height": 0,```モニターの表示される幅と高さを指定

```"x": 5,"y": 5,```　モニターがステージ上に表示される位置の座標

```"visible": true,```　モニターが表示されるかどうか

```"sliderMin": 0,"sliderMax": 100,```スライダーを使用する場合の最小値と最大値を設定

```"isDiscrete": true```スライダーが整数値のみで動作するかどうかを指定
</details>

___





```"extensions": [ ]```　使用している拡張機能の一覧
<details><summary>主な拡張機能名</summary>

```
"music",
"pen",
"videoSensing",
"text2speech",
"translate"
  ```
</details>

```"semver": "3.0.0"```scratchのバージョン

```"vm": "5.0.40"　```　Scratchの仮想マシンのバージョン。
___
Scratch 仮想マシンとは？
と思い調べてみたがどの記事も難しくてわからなかった（ﾖｼ!AIの出番だ）
>Scratch 仮想マシンはブロック構造を解析し、デバイスやOSに依存せず一貫して動作する環境を提供します。
リアルタイムでスクリプトを解釈し、スプライトやサウンドを即座に実行したり、並列処理が可能で、複数のスクリプトを同時に動作させることで複雑な表現を実現します。
安全性を重視し、直接ハードウェアに触れない仮想環境内でコードを実行できます。
>### 関連情報
>Scratch VMは、オープンソースプロジェクトとしてGitHubで公開されており、興味がある場合はコードを確認したり、独自の拡張を作成したりできます。

要はScratchプロジェクトを実行するためのプログラムだということらしい（間違ってたらごめんなさい）

___
```"agent": "Mozilla/...（略）"```　プロジェクトがどのデバイスやブラウザで作成されたか
この場合は
* デバイス: Mac（Macintosh; Intel Mac OS X 10_15_7）
* ブラウザ: Google Chrome バージョン 131
* ブラウザエンジン: WebKit（Safari）

となる


### 2.スプライト情報(ブロックコード)

まずはスプライト情報
```json:project.json

{
			"isStage": false,
			"name": "スプライト1",
			"variables": {},
			"lists": {},
			"broadcasts": {},
			"blocks": {
				"BJHdBztzfK`B_=RZ7qE$": {
					"opcode": "event_whenflagclicked",
					"next": ";2N|+XZ23R-XYd`u-Eo?",
					"parent": null,
					"inputs": {},
					"fields": {},
					"shadow": false,
					"topLevel": true,
					"x": 288,
					"y": 116
				},
				";2N|+XZ23R-XYd`u-Eo?": {
					"opcode": "looks_say",
					"next": null,
					"parent": "BJHdBztzfK`B_=RZ7qE$",
					"inputs": {
						"MESSAGE": [
							1,
							[
								10,
								"hello wold"
							]
						]
					},
					"fields": {},
					"shadow": false,
					"topLevel": false
				}
			},
			"comments": {},
			"currentCostume": 0,
			"costumes": [
				{
					"name": "コスチューム1",
					"bitmapResolution": 1,
					"dataFormat": "svg",
					"assetId": "bcf454acf82e4504149f7ffe07081dbc",
					"md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
					"rotationCenterX": 48,
					"rotationCenterY": 50
				},
				{
					"name": "コスチューム2",
					"bitmapResolution": 1,
					"dataFormat": "svg",
					"assetId": "0fb9be3e8397c983338cb71dc84d0b25",
					"md5ext": "0fb9be3e8397c983338cb71dc84d0b25.svg",
					"rotationCenterX": 46,
					"rotationCenterY": 53
				}
			],
			"sounds": [
				{
					"name": "ニャー",
					"assetId": "83c36d806dc92327b9e7049a565c6bff",
					"dataFormat": "wav",
					"format": "",
					"rate": 48000,
					"sampleCount": 40681,
					"md5ext": "83c36d806dc92327b9e7049a565c6bff.wav"
				}
			],
			"volume": 100,
			"layerOrder": 1,
			"visible": true,
			"x": 0,
			"y": 0,
			"size": 100,
			"direction": 90,
			"draggable": false,
			"rotationStyle": "all around"
		}
	],
 ```
___
``` "isStage": false,```　ステージか？（falseなので違う）

```"name": "スプライト1",```スプライト名

```"variables": {},```変数一覧

```"lists": {},```リスト

```"broadcasts": {},```メッセージ一覧

```"blocks": {}```ブロックコードが書かれている

<details><summary>blocks要素一覧</summary>
 
```"comments": {},```ブロックについてるコメント(詳しくは1.ステージ情報にあります)
```"currentCostume": 0,```今のコスチューム

</details>

____

"costumes": [ ]
<details><summary>costumes要素一覧</summary>

```"name": "コスチューム1",```

```"bitmapResolution": 1,```コスチュームがビットマップ画像の場合、その解像度を示す。
**1: 通常の解像度（1ピクセル = 1 Scratch単位）。**
**2: 高解像度（画像が2倍に拡大されて表示される）。**

```"dataFormat": "svg",```コスチュームのファイル形式。

```"assetId": "bcf454acf82e4504149f7ffe07081dbc",```画像のID。

```"md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",```ID＋拡張子

```"rotationCenterX": 48,"rotationCenterY": 50``` コスチュームの回転の中心点のX,Y座標（画像の左上を原点とする）



</details>

___
"sounds": [ ]
<details><summary>sounds要素一覧</summary>

```"name": "ニャー",```表示名

```"assetId": "83c36d806dc92327b9e7049a565c6bff",```ID

```"dataFormat": "wav",```ファイル形式

```"format": "",```ファイル形式の追加情報

```"rate": 48000,```音声のサンプリングレート（Hz単位）

```"sampleCount": 40681,```音声データのサンプル数

:::note info
音声データは、連続した音波を一定間隔でデジタル化したデータ。この「サンプル数」は、音声データが持つ時間的な解像度を示す
:::




```"md5ext": "83c36d806dc92327b9e7049a565c6bff.wav"```
</details>

___

```"volume": 100,```スプライトの音声再生時の音量(%)

```"layerOrder": 1,```スプライトのレイヤーの順序(1層手前に出す などで使われる)

```"visible": true,```スプライトが表示されているか

```"x": 0,"y": 0,```スプライトの座標位置

```"size": 100,```スプライトの大きさ

```"direction": 90,```スプライトの向き

```"draggable": false,```スプライトがドラッグ可能か

```"rotationStyle": "all around"```スプライトの回転スタイル。
**"all around": どの方向にも回転可能**
**"left-right": 左右にのみ反転**
**"don't rotate": 回転しない**

# まとめ
プロジェクトはjsonを使ってブロックを呼び出している

jsonは
・ステージ情報
・スプライト情報(ブロックコード)
・メタ情報
の3つの部分から成り立っている

そのうち今回は
・ステージ情報
・メタ情報
を解説した

# 最後に

初めて書いた記事なのでわかりにくい部分もあったと思いますが、最後まで見てくださってありがとうございました。