Discuss Scratch

Lirex
Scratcher
500+ posts

JSON Hacking - Listen Inputs etc.

Hinweis:
Bei diesem Tutorial handelt es sich um kein offizielles Feature von Scratch.
Die Benutzung des Tutorials erfolgt auf eigene Gefahr.
Ich übernehme keinerlei Haftung für Schäden an deinen Projekten, deiner Scratch-Installation oder deinem Betriebssystem, die möglicherweise durch die Benutzung dieses Tutorials entstehen könnten.

Längster Post im deutschsprachigen Scratch-Forum? ^^

Hallo,
nachdem Dadiwiki in diesem Thread das Thema Listeninputs in eigenen Blöcken angesprochen hatte, habe ich beschlossen, basierend auf diesem Thread eine Anleitung zum “Hacken” von Scratch-Projekten zu verfassen.

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

Dieser Post dient als Vorlage für einen Artikel für das deutschsprachige Scratch-Wiki. Es wäre schön, wenn wir ihn zusammen verbessern und erweitern könnten.
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: Ich empfehle, den Onlineeditor zu nutzen. Die Benutzung der Offlineversion kann zu Abweichungen und Fehlfunktionen führen.
Hinweis: Dieses Tutorial funktioniert auf Windows-Systemen (Hat jemand ein anderes Betriebssystem und kann diese Anleitung erweitern?).

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.



  4. Klicke auf Speichern.
  5. Entpacke das ZIP-Archiv mit Rechtsklick → Alle extrahieren… → Extrahieren und öffne den so erstellten Ordner.
  6. Lösche die ursprüngliche .zip-Datei (Optional).
  7. 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"
	}
}
Die farbliche Darstellung (Syntax-Highlighting) wird vermutlich abweichen oder nicht vorhanden sein, das ist aber nicht schlimm.

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) und lade ihn anschließend über den Scratch-Editor hoch (Datei → Hochladen von deinem Computer). Sollte der Ordner nicht auftauchen, versuche, Alle Dateien statt Benutzerdefinierte Dateien auszuwählen.



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

Das war also der erste Entwurf einer Anleitung zum Hacken von Blöcken. Falls ihr Feedback, Fragen, Anregungen, Verbesserungsvorschläge etc. habt oder irgendetwas nicht funktioniert, postet es hier! :)

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.

Lightnin wrote:

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”.

Last edited by Lirex (Sept. 6, 2014 19:05:28)


Scratch-DACH-Wiki author


;




Yay, 500+ posts! (05/25/2014)
LiFaytheGoblin
Forum Moderator
1000+ posts

JSON Hacking - Listen Inputs etc.

DANKE


Deutsch, English, Français, Español, Nederlands
DACH Scratch WikiEN Scratch WikiAll Scratch WikisA Scratch Wiki in your native language

Dadiwiki
Scratcher
100+ posts

JSON Hacking - Listen Inputs etc.

Perfekt! Vielen Dank
Dadiwiki
Scratcher
100+ posts

JSON Hacking - Listen Inputs etc.

Aber eine Frage hab ich noch: Ist es möglich, ein Dropdown-Menü in einen Block zu implementieren, in dem man Elemente einer bestimmten Liste auswählen kann? Oder ist das zu kompliziert?
TM_
Scratcher
1000+ posts

JSON Hacking - Listen Inputs etc.

Dadiwiki wrote:

Aber eine Frage hab ich noch: Ist es möglich, ein Dropdown-Menü in einen Block zu implementieren, in dem man Elemente einer bestimmten Liste auswählen kann? Oder ist das zu kompliziert?

Ich denke mal da es nirgendswo in Scratch existiert ist es auch nicht möglich. Man könnte bestimmt die dort genutzte Programmiersprache verwenden, um sowas zu realisieren, aber ob es dann noch mit der Datenstrukturdie Scratch nutzt kompatibel ist bezweifel ich mal. Und selbst wenn es funktionieren würde, wäre es immer noch unsicher, da so etwas nie beabsichtigt wurde.


My YouTube-Account: TM_ (TMtheScratcher)
Join the Google+ Community “Persist” and test the latest Alpha-versions and get news about the desktop-game! Persist
Lirex
Scratcher
500+ posts

JSON Hacking - Listen Inputs etc.

LiFaytheGoblin wrote:

DANKE

Dadiwiki wrote:

Perfekt! Vielen Dank
Bitte, Bitte!

Dadiwiki wrote:

Aber eine Frage hab ich noch: Ist es möglich, ein Dropdown-Menü in einen Block zu implementieren, in dem man Elemente einer bestimmten Liste auswählen kann? Oder ist das zu kompliziert?
Falls du den ersten Dropdown aus diesem Block meinst:
(Element ( v) von [  v])
Kannst du den, wenn ich mich nicht irre, mit d.listItem oder d.listDeleteItem erzeugen. Anderenfalls schau einfach mal hier nach - vielleicht findest du dort das, wonach du suchst.

Last edited by Lirex (July 29, 2014 21:07:02)


Scratch-DACH-Wiki author


;




Yay, 500+ posts! (05/25/2014)
TM_
Scratcher
1000+ posts

JSON Hacking - Listen Inputs etc.

Lirex wrote:

Dadiwiki wrote:

Aber eine Frage hab ich noch: Ist es möglich, ein Dropdown-Menü in einen Block zu implementieren, in dem man Elemente einer bestimmten Liste auswählen kann? Oder ist das zu kompliziert?
Falls du den ersten Dropdown aus diesem Block meinst:
(Element ( v) von [  v])
Kannst du den, wenn ich mich nicht irre, mit d.listItem oder d.listDeleteItem erzeugen. Anderenfalls schau einfach mal hier nach - vielleicht findest du dort das, wonach du suchst.

Ich glaube er will als einzelne Menüpunkte des Drop-Downs die Listenelemente haben, quasi die komplette Liste als Drop-Down-Menü, oder?

Falls es doch so gemeint war wie Lirex es verstanden hat könntest du zur Not ja auch einfach den Listeninput wie in der Anleitung erklärt einbauen und einen numerischen Parameter einbauen, den du dann in das erste Feld reinziehst

Und Lirex: Auch von mir gibt's ein DANKE! ^^ hab ich wohl irgendwie vergessen

Last edited by TM_ (July 29, 2014 22:41:40)



My YouTube-Account: TM_ (TMtheScratcher)
Join the Google+ Community “Persist” and test the latest Alpha-versions and get news about the desktop-game! Persist
Dadiwiki
Scratcher
100+ posts

JSON Hacking - Listen Inputs etc.

TM_ wrote:

Lirex wrote:

Dadiwiki wrote:

Aber eine Frage hab ich noch: Ist es möglich, ein Dropdown-Menü in einen Block zu implementieren, in dem man Elemente einer bestimmten Liste auswählen kann? Oder ist das zu kompliziert?
Falls du den ersten Dropdown aus diesem Block meinst:
(Element ( v) von [  v])
Kannst du den, wenn ich mich nicht irre, mit d.listItem oder d.listDeleteItem erzeugen. Anderenfalls schau einfach mal hier nach - vielleicht findest du dort das, wonach du suchst.

Ich glaube er will als einzelne Menüpunkte des Drop-Downs die Listenelemente haben, quasi die komplette Liste als Drop-Down-Menü, oder?

Falls es doch so gemeint war wie Lirex es verstanden hat könntest du zur Not ja auch einfach den Listeninput wie in der Anleitung erklärt einbauen und einen numerischen Parameter einbauen, den du dann in das erste Feld reinziehst

Und Lirex: Auch von mir gibt's ein DANKE! ^^ hab ich wohl irgendwie vergessen
Ja ich meinte das so wie TM_ aber das ist ja wie es aussieht unmöglich…
Man müsste bestimmt den Source-Code verändern, damit das sicher funktioniert.
TM_
Scratcher
1000+ posts

JSON Hacking - Listen Inputs etc.

Dadiwiki wrote:


Man müsste bestimmt den Source-Code verändern, damit das sicher funktioniert.

Ja, denn die Drop-Down-Menüs sind ja inputs. Die Werte der Listen sind aber im Grunde nur outputs. Man müsste also die Datenstruktur im Sourcecode verändern bzw mehr Zugriff auf sie haben.


My YouTube-Account: TM_ (TMtheScratcher)
Join the Google+ Community “Persist” and test the latest Alpha-versions and get news about the desktop-game! Persist
Lirex
Scratcher
500+ posts

JSON Hacking - Listen Inputs etc.

Der Post ist jetzt auch als Wiki-Artikel verfügbar.

Scratch-DACH-Wiki author


;




Yay, 500+ posts! (05/25/2014)

Powered by DjangoBB