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
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)
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:
The value this byte represents in a marker
Marker definitions MUST each end in 0x00
For example:
The marker definition for a note should be as follows:
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:
Time (in milliseconds, 4 bytes)
32 bit unsigned integer (0x00 00 00 00)
SSPMv2 Note Storage Type (1 byte)
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)