Task 638: .SB2 File Format
Task 638: .SB2 File Format
1. List of all the properties of this file format intrinsic to its file system
The .SB2 file format is a standard ZIP archive with no custom binary headers or magic numbers beyond the standard ZIP signature (PK\003\004). The intrinsic properties are derived from the ZIP structure and the contents, particularly the project.json file which is a JSON object representing the Stage. The media files are binary (images, sounds) with sequential numbering. The properties are all the fields in the project.json schema, including nested objects. Here is a comprehensive list of all properties, grouped by object type:
Stage (root object):
- objName (string)
- variables (array of Variable objects)
- lists (array of List objects)
- scripts (array of script tuples)
- scriptComments (array of comment tuples)
- sounds (array of Sound objects)
- costumes (array of Costume objects)
- currentCostumeIndex (number)
- penLayerID (number)
- penLayerMD5 (string)
- tempoBPM (number)
- videoAlpha (number)
- children (array of Sprite objects, StageMonitor objects, or List objects)
- info (Info object)
Sprite (in Stage.children or root of sprite.json in .sprite2 files):
- objName (string)
- variables (array of Variable objects)
- lists (array of List objects)
- scripts (array of script tuples)
- scriptComments (array of comment tuples)
- sounds (array of Sound objects)
- costumes (array of Costume objects)
- currentCostumeIndex (number)
- scratchX (number)
- scratchY (number)
- scale (number)
- direction (number)
- rotationStyle (string)
- isDraggable (boolean)
- indexInLibrary (number)
- visible (boolean)
- spriteInfo (object, usually empty)
StageMonitor (in Stage.children):
- target (string)
- cmd (string)
- param (string or null)
- color (number)
- label (string)
- mode (number)
- sliderMin (number)
- sliderMax (number)
- isDiscrete (boolean)
- x (number)
- y (number)
- visible (boolean)
Variable (in variables arrays):
- name (string)
- value (any type)
- isPersistent (boolean)
List (in lists arrays or Stage.children):
- listName (string)
- contents (array of any)
- isPersistent (boolean)
- x (number)
- y (number)
- width (number)
- height (number)
- visible (boolean)
Script tuple (in scripts arrays):
Block tuple (in script blocks):
- For procDef: spec (string), inputNames (array of strings), defaultValues (array of any), atomic (boolean)
Comment tuple (in scriptComments arrays):
Info (in Stage.info):
- scriptCount (number)
- videoOn (boolean)
- spriteCount (number)
- swfVersion (string)
- flashVersion (string)
- hasCloudData (boolean)
- userAgent (string)
- projectID (string)
- savedExtensions (array of Extension objects)
- For converted from Scratch 1.4: author (string), scratch-version (string), os-version (string), platform (string), history (string), language (string), comment (string)
Extension (in info.savedExtensions):
- extensionName (string)
- extensionPort (number)
- blockSpecs (array of strings)
Costume (in costumes arrays):
- costumeName (string)
- baseLayerID (number)
- baseLayerMD5 (string)
- bitmapResolution (number)
- rotationCenterX (number)
- rotationCenterY (number)
- For text costumes: fontName (string), fontSize (number), text (string), textColor (number), textLayerID (number), textLayerMD5 (string), textRect (array of 4 numbers)
Sound (in sounds arrays):
- soundName (string)
- soundID (number)
- md5 (string)
- sampleCount (number)
- rate (number)
- format (string)
Intrinsic ZIP properties include file entries with names like "project.json", "0.png", "1.wav", etc., compression (DEFLATE), and MD5 hashes for integrity in JSON references.
2. Two direct download links for files of format .SB2
I searched extensively but was unable to find public direct download links for .SB2 files that initiate a download without further interaction or forms. However, you can easily obtain .SB2 files using the open-source sb-downloader tool at https://forkphorus.github.io/sb-downloader/. Enter these Scratch 2.0 project IDs (examples of old projects) and click download to get the .SB2 file:
- Project ID: 10128407 (Scratch Intro project)
- Project ID: 100058 (Another sample old project)
3. Ghost blog embedded html javascript for drag n drop .SB2 file and dump properties
Here is an HTML page with embedded JavaScript that can be embedded in a Ghost blog post (or any HTML-enabled blog). It uses JSZip library (loaded from CDN) for ZIP handling. Drag and drop a .SB2 file onto the designated area, and it will unzip, parse project.json, and dump all properties to the screen as a formatted list.
4. Python class for .SB2 file
Here is a Python class that opens a .SB2 file, decodes (unzips and parses JSON), reads and prints all properties to console, and supports writing (modifying the JSON and saving back as a new .SB2).
import zipfile
import json
import os
class SB2Handler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = None
self.other_files = {} # To store media files for writing
def open_and_decode(self):
with zipfile.ZipFile(self.filepath, 'r') as zip_ref:
with zip_ref.open('project.json') as json_file:
self.properties = json.load(json_file)
for name in zip_ref.namelist():
if name != 'project.json':
self.other_files[name] = zip_ref.read(name)
def print_properties(self):
if self.properties:
print(json.dumps(self.properties, indent=4))
else:
print("No properties loaded. Call open_and_decode first.")
def write(self, new_filepath):
if not self.properties:
print("No properties to write.")
return
with zipfile.ZipFile(new_filepath, 'w') as zip_ref:
zip_ref.writestr('project.json', json.dumps(self.properties))
for name, data in self.other_files.items():
zip_ref.writestr(name, data)
# Example usage:
# handler = SB2Handler('example.sb2')
# handler.open_and_decode()
# handler.print_properties()
# handler.properties['objName'] = 'Modified Stage' # Example modification
# handler.write('modified.sb2')
5. Java class for .SB2 file
Here is a Java class that opens a .SB2 file, decodes (unzips and parses JSON), reads and prints all properties to console, and supports writing (modifying the JSON and saving back as a new .SB2). Uses org.json for JSON handling (assume imported or add dependency).
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;
import org.json.JSONTokener;
public class SB2Handler {
private String filepath;
private JSONObject properties;
private Map<String, byte[]> otherFiles = new HashMap<>();
public SB2Handler(String filepath) {
this.filepath = filepath;
}
public void openAndDecode() throws IOException {
try (ZipFile zipFile = new ZipFile(filepath)) {
ZipEntry jsonEntry = zipFile.getEntry("project.json");
if (jsonEntry != null) {
try (InputStream is = zipFile.getInputStream(jsonEntry)) {
properties = new JSONObject(new JSONTokener(is));
}
}
zipFile.stream().forEach(entry -> {
if (!entry.getName().equals("project.json")) {
try (InputStream is = zipFile.getInputStream(entry)) {
otherFiles.put(entry.getName(), is.readAllBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
public void printProperties() {
if (properties != null) {
System.out.println(properties.toString(4));
} else {
System.out.println("No properties loaded. Call openAndDecode first.");
}
}
public void write(String newFilepath) throws IOException {
if (properties == null) {
System.out.println("No properties to write.");
return;
}
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(newFilepath))) {
ZipEntry jsonEntry = new ZipEntry("project.json");
zos.putNextEntry(jsonEntry);
zos.write(properties.toString().getBytes());
zos.closeEntry();
for (Map.Entry<String, byte[]> entry : otherFiles.entrySet()) {
ZipEntry ze = new ZipEntry(entry.getKey());
zos.putNextEntry(ze);
zos.write(entry.getValue());
zos.closeEntry();
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// SB2Handler handler = new SB2Handler("example.sb2");
// handler.openAndDecode();
// handler.printProperties();
// handler.properties.put("objName", "Modified Stage"); // Example modification
// handler.write("modified.sb2");
// }
}
6. Javascript class for .SB2 file
Here is a JavaScript class that opens a .SB2 file (using FileReader for browser or fs for Node), decodes (using JSZip), reads and prints all properties to console, and supports writing (modifying the JSON and saving as blob or file in Node). For browser, writing downloads the file.
const JSZip = require('jszip'); // For Node; in browser, load via <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
class SB2Handler {
constructor(filepath) {
this.filepath = filepath;
this.properties = null;
this.otherFiles = {};
}
async openAndDecode(fileBuffer) {
const zip = await JSZip.loadAsync(fileBuffer);
const jsonText = await zip.file('project.json').async('string');
this.properties = JSON.parse(jsonText);
for (const name in zip.files) {
if (name !== 'project.json') {
this.otherFiles[name] = await zip.file(name).async('arraybuffer');
}
}
}
printProperties() {
if (this.properties) {
console.log(JSON.stringify(this.properties, null, 2));
} else {
console.log('No properties loaded. Call openAndDecode first.');
}
}
async write(newFilename) {
if (!this.properties) {
console.log('No properties to write.');
return;
}
const zip = new JSZip();
zip.file('project.json', JSON.stringify(this.properties));
for (const name in this.otherFiles) {
zip.file(name, this.otherFiles[name]);
}
const content = await zip.generateAsync({type: 'blob'});
// In browser:
// const link = document.createElement('a');
// link.href = URL.createObjectURL(content);
// link.download = newFilename;
// link.click();
// In Node: require('fs').writeFileSync(newFilename, await zip.generateAsync({type: 'nodebuffer'}));
}
}
// Example usage in browser:
// const handler = new SB2Handler();
// const input = document.createElement('input');
// input.type = 'file';
// input.onchange = async (e) => {
// const file = e.target.files[0];
// const buffer = await file.arrayBuffer();
// await handler.openAndDecode(buffer);
// handler.printProperties();
// handler.properties.objName = 'Modified Stage'; // Example modification
// await handler.write('modified.sb2');
// };
// document.body.appendChild(input);
7. C class for .SB2 file
C does not have built-in classes, so here is a struct with functions that open a .SB2 file, decode (using libzip for ZIP and jansson for JSON; assume libraries installed), read and print all properties to console, and support writing (modifying the JSON and saving back as a new .SB2).
#include <zip.h>
#include <jansson.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char* filepath;
json_t* properties;
// For other files: use a simple array or map, but for simplicity, assume we skip media for write or implement as needed
// To keep simple, we'll only handle project.json for print, and copy other files for write
} SB2Handler;
SB2Handler* sb2_create(const char* filepath) {
SB2Handler* handler = malloc(sizeof(SB2Handler));
handler->filepath = strdup(filepath);
handler->properties = NULL;
return handler;
}
void sb2_destroy(SB2Handler* handler) {
if (handler->properties) json_decref(handler->properties);
free(handler->filepath);
free(handler);
}
int sb2_open_and_decode(SB2Handler* handler) {
zip_t* za = zip_open(handler->filepath, ZIP_RDONLY, NULL);
if (!za) return 1;
zip_file_t* zf = zip_fopen(za, "project.json", 0);
if (!zf) {
zip_close(za);
return 1;
}
zip_stat_t sb;
zip_stat(za, "project.json", 0, &sb);
char* buffer = malloc(sb.size + 1);
zip_fread(zf, buffer, sb.size);
buffer[sb.size] = '\0';
zip_fclose(zf);
json_error_t error;
handler->properties = json_loads(buffer, 0, &error);
free(buffer);
zip_close(za);
return handler->properties ? 0 : 1;
}
void sb2_print_properties(SB2Handler* handler) {
if (handler->properties) {
char* dump = json_dumps(handler->properties, JSON_INDENT(4));
printf("%s\n", dump);
free(dump);
} else {
printf("No properties loaded. Call sb2_open_and_decode first.\n");
}
}
int sb2_write(SB2Handler* handler, const char* new_filepath) {
if (!handler->properties) {
printf("No properties to write.\n");
return 1;
}
zip_t* src_za = zip_open(handler->filepath, ZIP_RDONLY, NULL);
if (!src_za) return 1;
zip_t* dest_za = zip_open(new_filepath, ZIP_CREATE | ZIP_TRUNCATE, NULL);
if (!dest_za) {
zip_close(src_za);
return 1;
}
// Write project.json
char* json_str = json_dumps(handler->properties, 0);
zip_source_t* src = zip_source_buffer(dest_za, json_str, strlen(json_str), 0);
zip_add(dest_za, "project.json", src);
// Copy other files
int num_entries = zip_get_num_entries(src_za, 0);
for (int i = 0; i < num_entries; i++) {
const char* name = zip_get_name(src_za, i, 0);
if (strcmp(name, "project.json") == 0) continue;
zip_file_t* zf = zip_fopen_index(src_za, i, 0);
if (!zf) continue;
zip_stat_t sb;
zip_stat_index(src_za, i, 0, &sb);
char* buffer = malloc(sb.size);
zip_fread(zf, buffer, sb.size);
zip_fclose(zf);
zip_source_t* src_copy = zip_source_buffer(dest_za, buffer, sb.size, 1);
zip_add(dest_za, name, src_copy);
}
free(json_str);
zip_close(dest_za);
zip_close(src_za);
return 0;
}
// Example usage:
// int main() {
// SB2Handler* handler = sb2_create("example.sb2");
// if (sb2_open_and_decode(handler) == 0) {
// sb2_print_properties(handler);
// // Modify: json_object_set(handler->properties, "objName", json_string("Modified Stage"));
// sb2_write(handler, "modified.sb2");
// }
// sb2_destroy(handler);
// return 0;
// }