LoginLogin

SmileBASIC file format

Root / Submissions / [.]

MasterR3C0RDCreated:
Quick note: I didn't figure out most of the format of SB files. Trinitro21 did most of the work figuring out the file structure, and the footer was thanks to code plutooo had for smilehax. I just wrote this document, which sums everything we know. DISCLAIMER: This is not 100% correct and may change at any time. By the way, me and Trinitro21 (triangle) wrote an API to download programs from the SmileBASIC servers. It can also display GRPs as PNGs. It's available at http://sbapi.me/ SmileBASIC has its own format for storing its files. There are 2 main types of files, TXT and DAT. Both have a common header and a footer, but DAT files have a secondary header after the common header to store metadata like how many dimensions it has, how large each dimension is, and what type of data it stores. All files are stored in SmileBASIC's ExtData archive stored on the SD card. The default folder is stored as ### in the ExtData, and filenames are prefixed with T or B, for Text (TXT) and Binary (DAT), respectively.

Common Header

The shared part of every SB file is the common header. This contains information such as the username of who wrote it, how large the data stored is, and when it was last modified. The common header is 80 bytes long on the 3DS and 112 bytes on Switch. All values are little-endian.
Offset | Size (bytes) | Description
-----------------------------------
&H00   | 2            | Version of SmileBASIC the file was created under (00 or 01 = 3DS/WiiU, 04 = Switch)
&H02   | 1            | File type (00 = TXT, 01 = DAT. Switch: 02 = GRP, 04 = META)
&H04   | 1            | Zlib compression (0 = no, 1 = yes)
&H06   | 1            | Icon shown in the project browser. For TXT files, 00 = TXT and 01 = PRG. For DAT, 00 = DAT and 02 = GRP
&H08   | 4            | 32-bit value storing the size of the file, not including the common header or the footer
&H0C   | 2            | 16-bit value storing the year the file was last modified
&H0E   | 1            | 8-bit value storing the month the file was last modified
&H0F   | 1            | 8-bit value storing the day of the month the file was last modified
&H10   | 1            | 8-bit value storing the hour the file was last modified
&H11   | 1            | 8-bit value storing the minute the file was last modified
&H12   | 1            | 8-bit value storing the second the file was last modified
&H13   | 1            | Unknown, might be part of date/time
A very important difference to note is that in SB4, uploader information is slightly longer. Offsets for 3DS:
&H14   | 18           | The first author's (the original uploader)  NNID. This one isn't shown in the project browser
&H26   | 18           | The second author's (the last editor) NNID. This one is displayed in the project browser
&H38   | 4            | The first author's user ID. Used for controlling the blacklist editable in the project download area
&H3C   | 4            | The second author's user ID
Offsets for Switch:
&H14   | 32           | The first author's (the original uploader)  NNID. This one isn't shown in the project browser
&H34   | 32           | The second author's (the last editor) NNID. This one is displayed in the project browser
&H54   | 4            | The first author's user ID. Used for controlling the blacklist editable in the project download area
&H58   | 4            | The second author's user ID
The header ends at &H50 on 3DS and &H70 on Switch.

DAT Secondary Header (Petit Computer BiNary)

TXT and PRG files just place the text after the footer. The DAT and GRP files need more information, which is why they have a secondary header. This secondary header stores information for SB to parse the file properly. (offset is the offset after the header, which changes depending on the version of SmileBASIC in use.)
Offset | Size (bytes) | Description
-----------------------------------
&H00   | 8            | Always the ASCII string "PCBN000n", where n is the device type (offset &H00)
&H08   | 1            | Data type (03 = 16 bit unsigned (colors as RGBA5551, used for GRPs), 04 = Signed 32 bit integers (int%), 05 = 64-bit double (real#)). GRPs in SB4 are stored as integer (data type &H04) DAT files since they use RGBA8888 encoding.
&H0A   | 1            | Number of dimensions (1-4)
&H0C   | 4            | 32-bit value storing the size of the first dimension
&H10   | 4            | 32-bit value storing the size of the second dimension if applicable
&H14   | 4            | 32-bit value storing the size of the third dimension if applicable
&H18   | 4            | 32-bit value storing the size of the fourth dimension if applicable
Afterward, the data is stored in row-major order (https://en.wikipedia.org/wiki/Row-_and_column-major_order).

META Project file (Petit Computer Project Metadata

META files are used in projects to store metadata about a project, including icon, name, and description. (offset is the offset after the header, which changes depending on the version of SmileBASIC in use.)
Offset | Size (bytes) | Description
-----------------------------------
&H00   | 8            | Always the ASCII string "PCPM0005"
&H08   | 48           | The project name (UCS-2)
&H38   | 4576         | The project description (UCS-2)
&H1218 | 4            | The width of the project icon
The data following is the icon's pixel data, encoded in BGRA8888. Icons are always square, so you can find the length of the icon data by squaring the icon width and multiplying by 4.

Projects

File type 2 in SB3 and file type 3 on SB4 are reserved for project files that are used for uploading/downloading projects from the server. They are unpacked by SB when downloaded into a proper file structure. As such, this file format should never be encountered unless you're talking to the SB servers yourself. If you're doing that, figuring out project file format is left as an exercise for you :)

Footer

The footer is a 20 byte HMAC-SHA1 hash of the header and data using this HMAC key: nqmby+e9S?{%U*-V]51n%^xZMk8>b{?x]&?(NmmV[,g85:%6Sqd"'U")/8u77UL2 The footer must be valid in order to download or upload a program/project, not having a valid footer will cause an error when doing either of these.

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)
I'm pretty sure we've proved that certain GRP files are decompressed when loaded (I think the actual compression flag might be 2, not 4) compression is probably the most important reason for figuring out the header (differently sized GRP files are nice, but we could already just save image data as DAT, so whatever)

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)
The GRPs on BIG are smaller than they should be normally so there is definitely compression of some form.

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)
Interesting thing i just discovered. Every sample demo file in the smilebasic main game file romfs is flagged 05. Every demo file in the 3.6.0 update romfs is 04, however. The update romfs .GRP files were actually compressed (flagged 04), but the text files were not. And in the main game romfs (flagged 05), nothing was compressed. Strange.

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)
Compression was seemingly introduced in an update

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)
text files most likely ignore the compression flag. edit: false, but because we didn't know where the REAL compression flag was

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)
Zlib compression is confirmed to be a different byte (offset &H04)

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)

Replying to:DrZog
The 20 byte footer at the end of smilebasic files is a sha1 hmac. The code to calculate it has been public for 3 years, right under our noses: https://github.com/plutooo/smilehax/blob/master/scripts/make_script.py I went ahead and adapted the script to more inline with what we'd want here (to fix the footer to something smilebasic would accept - without overwriting the header). https://gist.github.com/zoogie/ccae68b60d86b25ee8801f3fbc212301 Usage: python SMILEBASIC_FILENAME output_filename (needs python 3)
https://12me21.github.io/sbtools/ added compression support

It looks like icons are actually: 0 = DAT/TXT (depending on file type) 1 = PRG 2 = GRP

Replying to:12Me21
It looks like icons are actually: 0 = DAT/TXT (depending on file type) 1 = PRG 2 = GRP
What happens when a DAT has a PRG icon or a TXT has a GRP icon?

Replying to:12Me21
It looks like icons are actually: 0 = DAT/TXT (depending on file type) 1 = PRG 2 = GRP
it just shows that icon, nothing special happens

Replying to:12Me21
It looks like icons are actually: 0 = DAT/TXT (depending on file type) 1 = PRG 2 = GRP
oh not sure what i expecting but that was underwhelming

I'm a bit late, but could &H13 be milliseconds? That's the only thing I can think of for it.

Replying to:HTV04
I'm a bit late, but could &H13 be milliseconds? That's the only thing I can think of for it.
I'd glance at the real-time clock 3dbrew reference for some guidance. Milliseconds wouldn't make a lot of sense. Briefly glancing at the RTC reference, I don't even think it goes down to sub-second precision. My guess is that it uses something similar to year-month-day-hour, whereas each value is one byte long. That's pure guessing, though. Link: https://www.3dbrew.org/wiki/MCU_Services#MCU_service_.22mcu::RTC.22

Replying to:HTV04
I'm a bit late, but could &H13 be milliseconds? That's the only thing I can think of for it.
I'm really late in the game, aren't I? lmao, I knew that, never bothered to actually tell. I'm a shame I guess. That I waited 2 years to tell, makes this more bitter :sweat_smile: It's actually the weekday (0=Sunday through 6=Saturday) No idea why they put that in, as the file browser doesn't use it on any version, but there you go. You can verify by keep changing the day in System Settings (or hacky by changing RTC from GodMode9) and save a new file on each day. Then check that value. It's unnecessary in my opinion, but might be a nice addition for external SmileBASIC file explorers.

See how byte 0x04 of the common header is Zlib compression flag? This Uint16 is actually a bitmask, even if only the lowest 2 bits are meaning anything. Bit 0: Zlib Compression Bit 1: "Protected resource" (GRP only)
Protected what?If you didn't know, SB3 has japanese-exclusive DLC, which is Content IP, stuff from Namco arcade games, such as Mappy and Pacman. The GRPs in it are not only compressed and have a non-standard size, they have Bit 1 set on byte 0x4, so SB3 refuses to let you read from the slot the GRP is loaded in with commands like GSPOIT, GSAVE or GCOPY, including SAVE. However, that blocking is sorta broken: GCLS'ing, otherwise editing the GRP works fine but the slot is still considered "protected". That internal flag can only be gotten rid of by using ACLS. Also, DAT files, or loading a GRP as an array, with that flag throw an error on LOAD, so you can make DAT files useless lol