icon picker
SSPMv2 (Binary)

This format is usually regarded as one of the most hated formats of all the clones because of how obnoxiously complex it is, but it is the most used and future-proof unlike SSPMv1.
Please read this before continuing since there are new terminology you will not understand otherwise.
A marker is a weird concept, but I will try my best to explain it. It is an object, not a note. This means it can have a custom function for mod chart support and other things. Although, it CAN be a note, so if your map is just a plain basic map, just think of markers as a fancy definition of a note.

Now, pointers. These are for showing the parser where the Custom Data (for modcharts), Audio Data, Cover Data, Marker definitions, and Markers are. This is because basil decided not to have them in its own section like SSPMv1 with their respective length, so we need pointers.

Header

Header Binary Format
Bytes
Example in Hex
Description
4
0x53532B6D (SS+m)
Magic Number
2
0x0200 (2, for SSPMv2)
Version Number
4
0x00000000
Reserved Space
There are no rows in this table

Object Metadata

SHA-1 Hash of the markers, and marker definitions (20 bytes)

This isn’t really used for anything parsing wise, so you can just skip it. The main use of this is for verifying the SSPMv2 file integrity, so it is useless otherwise.

Millisecond of the very last marker (4 bytes)

32 bit unsigned integer (00 00 00 00)

Total Note Count (4 bytes)

32 bit unsigned integer (00 00 00 00)

Total Marker Count (4 bytes) (this includes notes since they are markers too)

32 bit unsigned integer (00 00 00 00)
SSPMv2 Map Difficulty (1 byte)
Byte
Difficulty Name
0x00
N/A
0x01
Easy
0x02
Medium
0x03
Hard
0x04
Logic
0x05
Tasukete
There are no rows in this table

Star Rating (2 bytes)

16 bit unsigned integer (00 00)
This was supposed to be for online, but ended up never being used (since calculations for star rating are done externally)

Audio Exists Bool (1 byte)

00 or 01

Cover Exists Bool (1 byte)

00 or 01

Mod Exists Bool (1 byte)

00 or 01
This is basically if it contains modchart data, so if its just a plain map then always leave this at 0

Pointers

Byte offset of the Custom Data section (8 bytes)

00 00 00 00 00 00 00 00

Byte length of the Custom Data section (8 bytes)

00 00 00 00 00 00 00 00

Byte offset of the Audio Data section (8 bytes)

00 00 00 00 00 00 00 00

Byte length of the Audio Data section (8 bytes)

00 00 00 00 00 00 00 00

Byte offset of the Cover Data section (8 bytes)

00 00 00 00 00 00 00 00

Byte length of the Cover Data section (8 bytes)

00 00 00 00 00 00 00 00

Byte offset of the Marker Definition section (8 bytes)

00 00 00 00 00 00 00 00

Byte length of the Marker Definition section (8 bytes)

00 00 00 00 00 00 00 00

Byte offset of the Marker section (8 bytes)

00 00 00 00 00 00 00 00

Byte length of the Marker section (8 bytes)

00 00 00 00 00 00 00 00

Song Metadata

Map ID Length (2 bytes)

16 bit unsigned integer (00 00)

Map ID

UTF-8 String converted to hexadecimal

Map Name Length (2 bytes)

16 bit unsigned integer (00 00)

Map Name (Artist - Title)

UTF-8 String converted to hexadecimal

Song Name Length (2 bytes)

16 bit unsigned integer (00 00)

Song Name

UTF-8 String converted to hexadecimal
I literally can not find a reason why song name is ever different from map name, my best guess is for a romanized title of Japanese songs, but other than that, just skip this and use the Map Name section instead
- fogsaturate

Number of Mappers (2 bytes)

16 bit unsigned integer (00 00)

(Array) Mapper

Mapper Name Length (2 bytes)

16 bit unsigned integer (00 00)

Mapper Name

UTF-8 String converted to hexadecimal

Custom Data

Number of fields (2 bytes)

16 bit unsigned integer (00 00)

Custom Data Objects:

Length of ID of the field as a UTF-8 string (has to be unique) (2 bytes)

16 bit unsigned integer (00 00)

Data type ID (1 byte) (Refer to the Data type Table)

00
If data type is 0x0C (which is an array):
Custom Data goes after the Data Type

Audio

If the Audio Exists Bool is true (if byte 0x2E is 0x01):
Audio Data (Must contain a full MP3 or OGG audio)

Cover

If the Cover Exists Bool is true:
PNG Data (Must contain a full PNG image)

Marker Definitions

Thus begins the most confusing part about this format! A “marker definition” is just an array with a string and a few bytes to tell the format how exactly to deal with an object, but there’s quite a bit going on with those few bytes. This is how markers actually know what type of data type to use.

Number of definitions (1 byte)

0x00

For each definition:

Length-prefixed UTF-8 string (2 bytes for length, then the string’s bytes after)

0x00 00 ...
For notes, this string should be “ssp_note”

Number of values that each marker with this definition will have (1 byte)

0x00

For each value:

Data type of this value (1 byte)

The options for this are listed below, since it can’t just be any value. When reading an object, for each of the data types listed in its definition, a parser should read a value denoted by the right column of this table:
Data type options
Byte
The value this byte represents in a marker
0x01
8 bit unsigned integer (1 byte)
0x02
16 bit unsigned integer (2 bytes)
0x03
32 bit unsigned integer (4 bytes)
0x04
64 bit unsigned integer (8 bytes)
0x05
32 bit floating point number (4 bytes)
0x06
64 bit floating point number (8 bytes)
0x07
x|y position, used for notes and will be described for marker data later
0x08
Buffer (2 bytes for length, then the buffer’s bytes after)
0x09
UTF-8 String (2 bytes for length, then the string’s bytes after)
0x0a
Long Buffer (4 bytes for length, then the buffer’s bytes after)
0x0b
Long UTF-8 String (4 bytes for length, then the string’s bytes after)
There are no rows in this table
Marker definitions MUST each end in 0x00

For example:

The marker definition for a note should be as follows:
Table
Bytes
Meaning
0x08 00
8 character string
0x73 73 70 5f 6e 6f 74 65
“ssp_note” in bytes
0x01
One value in data for this definition
0x07
Data type: 07 (x|y position)
0x00
End of definition (required)
There are no rows in this table
So, the bytes in a valid SSPMv2 file for this definition will be:
0x08 00 73 73 70 5f 6e 6f 74 65 01 07 00

If this seems like a lot just to define a note, don’t worry, there’s more soon!

Note Data

Note that this is for every note in every note marker. After the end, the next note is written in the same format as shown below.

For the X and Y position, they are on a 1 incremented plane as well, but the origin is at 1,1
2 on the X axis is to the right, and 0 is to the left
The Y axis is actually flipped, so 0 on the Y axis is up, and 2 is down

Note Matrix:

0,0
1,0
2,0
0,1
1, 1
2,1
0,2
1,2
2,2
There are no rows in this table

Time (in milliseconds, 4 bytes)

32 bit unsigned integer (0x00 00 00 00)
SSPMv2 Note Storage Type (1 byte)
Byte
Difficulty Name
0x00
Integer (non-quantum)
0x01
Quantum
There are no rows in this table
If the storage type is 0x00 (Integer):

X position (1 byte)

8 bit unsigned integer (0x00)

Y position (1 byte)

8 bit unsigned integer (0x00)
If the storage type is 0x01 (Quantum):

X position (4 bytes)

32 bit signed float (0x00 00 00 00)

Y position (4 bytes)

32 bit signed float (0x00 00 00 00)

Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.