Aus Das deutschsprachige Scratch-Wiki

Diese Seite enthält Tipps und Anleitungen die über die normale Scratch-Verwendung hinausgehen. Sei deshalb vorsichtig und mach eine Kopie deiner Projekte bevor du etwas ausprobierst. Die Verwendung erfolgt auf eigene Gefahr.

Das Tutorial basiert auf diesen Foren-Threads:

Was ist JSON Hacking überhaupt und wofür benötige ich es?

JSON (JavaScript Object Notation) ist das Datenformat, in dem der Code und weitere Informationen von Scratch 2.0 Projekten gespeichert wird (siehe auch JSON.org). Durch Bearbeiten der Datei mittels eines Texteditors kann man den Code des Scratch-Projekts verändern und dabei bestimmte Scratch-seitige Beschränkungen umgehen (z.B. Drop-Down-Menüs in eigenen Blöcken verwenden, Variablen in bestimmte Drop-Down-Menüs einfügen etc.).

Nehmen wir also einmal dieses Skript als Beispiel:

Definiere füge (Text) (n) mal zu Liste (Liste) hinzu
wiederhole (n) mal
füge (Text) zu (Liste) hinzu
end

füge [Lorem ipsum] (5) mal zu Liste [Blindtext v] hinzu

Im Scratch-Editor selbst wäre das aus zwei Gründen nicht zu realisieren:

  1. Man kann keine funktionierenden Drop-Down-Menüs in eigenen Blöcken verwenden.
  2. Man kann keine Wertblöcke in Listen-Drop-Down-Menüs einfügen.

Mithilfe von JSON Hacking kann man sich dennoch ein solches Skript zusammenbasteln.

Wie JSON-Hacking funktioniert

Hinweis: Fertige in jedem Fall eine Sicherheitskopie von Projekten an, die du hackst, denn bei fehlerhafter Durchführung lässt sich das Projekt eventuell nicht mehr öffnen.
Hinweis: Verwende den Onlineeditor. Die Benutzung der Offlineversion kann zu Abweichungen und Fehlfunktionen führen.
Hinweis: Dieses Tutorial funktioniert auf Windows-Systemen.

Schritt 1: Setup

Lege ein neues Projekt an und erstelle folgendes Skript im Skriptbereich von Sprite1 (beachte, auch eine Liste Test zu erstellen, um auf die Listenblöcke zugreifen zu können):

Definiere füge (Text) (n) mal zu Liste (Liste) hinzu
wiederhole (n) mal
füge (Text) zu [Test v] hinzu
end

Der Block sollte in der Blockliste so aussehen:

füge [] (1) mal zu Liste [] hinzu :: custom

Schritt 2: Projekt herunterladen

Bitte unbedingt diese Reihenfolge beibehalten.

  1. Klicke im Editor auf Datei → Herunterladen auf deinen Computer.
  2. Wähle in dem erscheinenden Fenster bei Dateityp: Alle Dateien aus.
  3. Gib bei Dateiname: Projekt.zip ein. Du kannst auch jeden beliebigen anderen Dateinamen verwenden, wichtig ist das .zip am Ende.

ZIP-Download.PNG

  1. Klicke auf Speichern.
  2. Entpacke das ZIP-Archiv mit Rechtsklick → Alle extrahieren... → Extrahieren und öffne den so erstellten Ordner.
  3. Lösche die ursprüngliche .zip-Datei (Optional).
  4. Du siehst jetzt einige Grafik- und Sounddateien sowie eine project.json. Öffne Letztere in einem Texteditor deiner Wahl (ich persönlich verwende Notepad++).

Schritt 3: Code verändern

Der Code, den du jetzt vor dir hast, sieht in etwa so aus (evtl. gibt es bei einigen Zahlen leichte Abweichungen, das ist für uns jedoch nicht relevant):

Bitte nicht schreiend weglaufen! Das wirkt jetzt erst einmal alles ein wenig chaotisch, ist aber nur halb so schlimm.

{
	"objName": "Stage",
	"lists": [{
			"listName": "Test",
			"contents": [],
			"isPersistent": false,
			"x": 5,
			"y": 5,
			"width": 102,
			"height": 202,
			"visible": true
		}],
	"sounds": [{
			"soundName": "pop",
			"soundID": 1,
			"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
			"sampleCount": 258,
			"rate": 11025,
			"format": ""
		}],
	"costumes": [{
			"costumeName": "backdrop1",
			"baseLayerID": 3,
			"baseLayerMD5": "739b5e2a2435f6e1ec2993791b423146.png",
			"bitmapResolution": 1,
			"rotationCenterX": 240,
			"rotationCenterY": 180
		}],
	"currentCostumeIndex": 0,
	"penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png",
	"penLayerID": 0,
	"tempoBPM": 60,
	"videoAlpha": 0.5,
	"children": [{
			"objName": "Sprite1",
			"scripts": [[10,
					10,
					[["procDef", "füge %s %n -mal zu Liste %s hinzu", ["Text", "n", "Liste"], ["", 1, ""], false],
						["doRepeat", ["getParam", "n", "r"], [["append:toList:", ["getParam", "Text", "r"], "Test"]]]]]],
			"sounds": [{
					"soundName": "meow",
					"soundID": 0,
					"md5": "83c36d806dc92327b9e7049a565c6bff.wav",
					"sampleCount": 18688,
					"rate": 22050,
					"format": ""
				}],
			"costumes": [{
					"costumeName": "costume1",
					"baseLayerID": 1,
					"baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg",
					"bitmapResolution": 1,
					"rotationCenterX": 47,
					"rotationCenterY": 55
				},
				{
					"costumeName": "costume2",
					"baseLayerID": 2,
					"baseLayerMD5": "6e8bd9ae68fdb02b7e1e3df656a75635.svg",
					"bitmapResolution": 1,
					"rotationCenterX": 47,
					"rotationCenterY": 55
				}],
			"currentCostumeIndex": 0,
			"scratchX": 0,
			"scratchY": 0,
			"scale": 1,
			"direction": 90,
			"rotationStyle": "normal",
			"isDraggable": false,
			"indexInLibrary": 1,
			"visible": true,
			"spriteInfo": {
			}
		},
		{
			"listName": "Test",
			"contents": [],
			"isPersistent": false,
			"x": 5,
			"y": 5,
			"width": 102,
			"height": 202,
			"visible": true
		}],
	"info": {
		"flashVersion": "WIN 14,0,0,145",
		"scriptCount": 1,
		"videoOn": false,
		"spriteCount": 1,
		"projectID": "25066069",
		"swfVersion": "v421",
		"userAgent": "Mozilla\/5.0 (Windows NT 6.1; WOW64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/35.0.1916.153 Safari\/537.36"
	}
}

Das ist also ein Scratch-Projekt in Textform. Auf den ersten Blick eine riesige kryptische Textwüste. Auf den zweiten vermutlich auch. Aber wie gesagt, ich erkläre die wichtigen Teile.

Interessant wird es für uns ab Zeile 35:

			"objName": "Sprite1",
			"scripts": [[10,
					10,
					[["procDef", "füge %s %n -mal zu Liste %s hinzu", ["Text", "n", "Liste"], ["", 1, ""], false],
						["doRepeat", ["getParam", "n", "r"], [["append:toList:", ["getParam", "Text", "r"], "Test"]]]]]],
			"sounds": [{
					"soundName": "meow",
					"soundID": 0,
					"md5": "83c36d806dc92327b9e7049a565c6bff.wav",
					"sampleCount": 18688,
					"rate": 22050,
					"format": ""
				}],
			"costumes": [{
					"costumeName": "costume1",
					"baseLayerID": 1,
					"baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg",
					"bitmapResolution": 1,
					"rotationCenterX": 47,
					"rotationCenterY": 55
				},
				{
					"costumeName": "costume2",
					"baseLayerID": 2,
					"baseLayerMD5": "6e8bd9ae68fdb02b7e1e3df656a75635.svg",
					"bitmapResolution": 1,
					"rotationCenterX": 47,
					"rotationCenterY": 55
				}],
			"currentCostumeIndex": 0,
			"scratchX": 0,
			"scratchY": 0,
			"scale": 1,
			"direction": 90,
			"rotationStyle": "normal",
			"isDraggable": false,
			"indexInLibrary": 1,
			"visible": true,
			"spriteInfo": {
			}
		},
		{
			"listName": "Test",
			"contents": [],
			"isPersistent": false,
			"x": 5,
			"y": 5,
			"width": 102,
			"height": 202,
			"visible": true
		}],

Neben einigen Basisinformationen werden alle Objekte und die Bühne mit allem, was man irgendwie daran verändern kann, untereinander gelistet. Der Abschnitt oben stellt die Figur Sprite1 dar. Zu den Informationen, die dort gelistet werden, gehören Skripte, Kostüme, Klänge, Position, Drehung, Sichtbarkeit und so weiter. Interessant sind für uns die Skripte dieser Figur. Und die finden wir schon relativ weit oben, nämlich von Zeile 36 bis einschließlich Zeile 39:

"scripts": [[10,
	10,
	[["procDef", "füge %s %n -mal zu Liste %s hinzu", ["Text", "n", "Liste"], ["", 1, ""], false],
		["doRepeat", ["getParam", "n", "r"], [["append:toList:", ["getParam", "Text", "r"], "Test"]]]]]],

Das sind die eben erstellten Blöcke in Textform. Darauf basierend kann man sich recht einfach herleiten, was hier welcher Block ist. Beginnen wir also mit dem Modifizieren des Codes.

[["procDef", "füge %s %n -mal zu Liste %s hinzu", ["Text", "n", "Liste"], ["", 1, ""], false],

Hier haben wir den Beschriftungstext des eigenen Blocks mit den entsprechenden Inputs. %s ist ein Text-Input, %n ein Input für numerische Werte. Eine Liste aller Inputs gibt es hier.

Den dritten Input wollen wir ja zu einem Listen-Input machen. Und das funktioniert mit %m.list. Der Code sieht dann folgendermaßen aus

[["procDef", "füge %s %n -mal zu Liste %m.list hinzu", ["Text", "n", "Liste"], ["", 1, ""], false],

Und das war es auch schon. Jetzt machen wir damit weiter, den Parameter in den Listen-Input des Listenblocks darunter einzufügen. Dazu müssen wir zuerst einmal folgende Codezeile auseinander nehmen:

["doRepeat", ["getParam", "n", "r"], [["append:toList:", ["getParam", "Text", "r"], "Test"]]]]]],

Das doRepeat und das getParam' am Anfang gehören zu der Schleife um den Block. Der Block selbst ist also dieser Schnipsel:

[["append:toList:", ["getParam", "Text", "r"], "Test"]]

Der Aufbau der Blöcke ist folgender: append:toList: legt fest, um welchen Block es sich handelt. Dahinter werden, durch Kommata getrennt, die einzelnen Parameter angegeben. Der Block

füge [] zu [ v] hinzu

besitzt zwei solcher Parameter (Text und Liste). Den ersten haben wir auf den Block-Input Text festgelegt, der zweite ist die Liste Test. Der zweite Parameter soll aber der Block-Input Liste sein. Und dafür bedienen wir uns Copy&Paste und ersetzen erst einmal den zweiten Parameter "Test" durch den ersten:

[["append:toList:", ["getParam", "Text", "r"], ["getParam", "Text", "r"]]]

Bei solchen Parametern ergibt sich wieder der gleiche Aufbau: getParam legt fest, dass es sich hier um einen Parameter aus eigenen Blöcken handelt. Dann folgen die Attribute: Text spezifiziert, um welchen Parameter es sich handelt, und r besagt, dass der Parameter ein Wertblock (Reporter) ist.

Letztendlich müssen wir also hier Text in Liste ändern. Damit sieht der Block-Code so aus:

[["append:toList:", ["getParam", "Text", "r"], ["getParam", "Liste", "r"]]]

Und der gesamte Projektcode:

{
	"objName": "Stage",
	"lists": [{
			"listName": "Test",
			"contents": [],
			"isPersistent": false,
			"x": 5,
			"y": 5,
			"width": 102,
			"height": 202,
			"visible": true
		}],
	"sounds": [{
			"soundName": "pop",
			"soundID": 1,
			"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
			"sampleCount": 258,
			"rate": 11025,
			"format": ""
		}],
	"costumes": [{
			"costumeName": "backdrop1",
			"baseLayerID": 3,
			"baseLayerMD5": "739b5e2a2435f6e1ec2993791b423146.png",
			"bitmapResolution": 1,
			"rotationCenterX": 240,
			"rotationCenterY": 180
		}],
	"currentCostumeIndex": 0,
	"penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png",
	"penLayerID": 0,
	"tempoBPM": 60,
	"videoAlpha": 0.5,
	"children": [{
			"objName": "Sprite1",
			"scripts": [[10,
					10,
					[["procDef", "füge %s %n -mal zu Liste %m.list hinzu", ["Text", "n", "Liste"], ["", 1, ""], false],
						["doRepeat", ["getParam", "n", "r"], [["append:toList:", ["getParam", "Text", "r"], ["getParam", "Liste", "r"]]]]]]],
			"sounds": [{
					"soundName": "meow",
					"soundID": 0,
					"md5": "83c36d806dc92327b9e7049a565c6bff.wav",
					"sampleCount": 18688,
					"rate": 22050,
					"format": ""
				}],
			"costumes": [{
					"costumeName": "costume1",
					"baseLayerID": 1,
					"baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg",
					"bitmapResolution": 1,
					"rotationCenterX": 47,
					"rotationCenterY": 55
				},
				{
					"costumeName": "costume2",
					"baseLayerID": 2,
					"baseLayerMD5": "6e8bd9ae68fdb02b7e1e3df656a75635.svg",
					"bitmapResolution": 1,
					"rotationCenterX": 47,
					"rotationCenterY": 55
				}],
			"currentCostumeIndex": 0,
			"scratchX": 0,
			"scratchY": 0,
			"scale": 1,
			"direction": 90,
			"rotationStyle": "normal",
			"isDraggable": false,
			"indexInLibrary": 1,
			"visible": true,
			"spriteInfo": {
			}
		},
		{
			"listName": "Test",
			"contents": [],
			"isPersistent": false,
			"x": 5,
			"y": 5,
			"width": 102,
			"height": 202,
			"visible": true
		}],
	"info": {
		"flashVersion": "WIN 14,0,0,145",
		"scriptCount": 1,
		"videoOn": false,
		"spriteCount": 1,
		"projectID": "25066069",
		"swfVersion": "v421",
		"userAgent": "Mozilla\/5.0 (Windows NT 6.1; WOW64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/35.0.1916.153 Safari\/537.36"
	}
}

Schritt 4: Projekt wieder hochladen

Speichere die project.json ab, packe den Ordner wieder (Rechtsklick → Senden an → ZIP-komprimierter Ordner), nenne die endung des zip's ordner in ".sb2" um und lade ihn anschließend über den Scratch-Editor hoch (Datei → Hochladen von deinem Computer).

Screenshot (319).png Screenshot (320).png

Wenn du alles richtig gemacht hast, sollten die Skripte so aussehen:

Definiere füge (Text) (n) mal zu Liste (Liste) hinzu
wiederhole (n) mal
füge (Text) zu (Liste) hinzu
end

Und der Block sollte funktionieren. Damit wären wir fertig. :)

Schlusswort

Abschließend bleibt noch zu sagen: JSON Hacking ist zwar eine feine Sache, die in vielen Situationen praktisch ist, benutze diese Methode jedoch sparsam und versuche, sie möglichst zu umgehen. Denn das Scratch-Team unterstützt diese Methode nicht und niemand kann sagen, ob JSON Hacking auch in Zukunft noch so funktionieren wird. Zudem kann JSON Hacking viele Leute verwirren.

"This is a cool hack, but my concern is that when anyone who doesn't know what you've done (or how you've done it) tries to view your project, they won't be able to understand your scripts - they won't even make sense.
I wouldn't mind at all if this was being done only with downloaded, local projects. But these are public, shared ones, that people are likely to "see inside"." - Ligthnin, Scratch Team.

Code zum Einbinden ins Forum:
[wiki=de:JSON Hacking]JSON Hacking[/wiki]