Skip to content

QEM (Binary)

QEM, or Quantum Editor Map, is an internal format developed for SSQE. While currently unfinished, it will be used to save maps more efficiently and in a standardized location compared to the current TXT/INI system, which is restrictive, unstandardized, and not reasonably scalable.

This format is partly based on SSPMv2, but has been greatly simplified to make reading and writing easier. However, this format does not contain audio data - that will be delegated to an upcoming QEMZ format which relies on QEM files for object data. QEMZ will be a lossless export option for sharing maps in one file, regardless of what game or format you intend to map for, and could be a reasonable option for any future derivatives of Sound Space who don’t want to develop an entirely new format.

All integer values below should be assumed to be SIGNED unless otherwise stated. (This just makes parsing slightly easier and no map would reasonably need an unsigned integer for those values)

Similar to SSPM, QEM uses a lookup table for data types to determine how to read a following set of bytes. Every entry in the format marked with (*) is an example of one of these types, which will always come before a value of that type. These data types are different from what SSPM offers, and are listed below:

Data Types
Type ID
Hex Code
Type description
0
0x00
Byte
1
0x01
16-bit signed integer
2
0x02
16-bit unsigned integer
3
0x03
32-bit signed integer
4
0x04
32-bit unsigned integer
5
0x05
64-bit signed integer
6
0x06
64-bit unsigned integer
7
0x07
32-bit floating point number
8
0x08
64-bit floating point number
9
0x09
8-bit boolean (true/false)
10
0x0A
Variable length UTF-8 string, starting with a 16-bit signed integer for the number of bytes in the string
11
0x0B
Variable length UTF-8 string, starting with a 32-bit signed integer for the number of bytes in the string
12
0x0C
Variable length buffer, starting with a 16-bit signed integer for the number of bytes in the buffer
13
0x0D
Variable length buffer, starting with a 32-bit signed integer for the number of bytes in the buffer
14
0x0E
Variable length array, starting with a 16-bit signed integer for the number of elements in the array, and one byte for the data type contained in this array (nested data structures are allowed)
15
0x0F
Variable length array, starting with a 32-bit signed integer for the number of elements in the array, and one byte for the data type contained in this array (nested data structures are allowed)
16
0x10
Variable length dictionary, starting with a 16-bit signed integer for the number of elements in the dictionary, one byte for the data type of the key used by this dictionary, and one byte for the data type of the value used by this dictionary (nested data structures are allowed)
Each entry in this dictionary consists of the entry’s key immediately followed by the entry’s value (i.e., key → value → key → value → ...)
17
0x11
Variable length dictionary, starting with a 32-bit signed integer for the number of elements in the dictionary, one byte for the data type of the key used by this dictionary, and one byte for the data type of the value used by this dictionary (nested data structures are allowed)
Each entry in this dictionary consists of the entry’s key immediately followed by the entry’s value (i.e., key → value → key → value → ...)
18
0x12
Variable length tuple, starting with one byte for the number of types in the tuple, followed by each of this tuple’s entries (nested data structures are allowed)
Each entry in this tuple consists of the entry’s data type as one byte, immediately followed by the entry’s value of that type (i.e., type → value → type → value → ...)
There are no rows in this table
Note: a “buffer” is a raw block of bytes, with no inherent information on how it is encoded. It will not be written by SSQE, but should be accounted for in case you want to use and/or modify this format for your own purposes. This can be used to store another file within QEM, although this is not recommended (see QEMZ).


Now we get to the actual byte layout of the format, starting with the header. Note that a block marked with (static) must be present in every QEM file and will only occur once in that file.
Header (static)
Bytes
Example
Description
4
0x4951454D (IQEM)
File Signature
2
0x0000
Version Number (Just in case, should be 0)
There are no rows in this table
After the header is a path block with zero or more string keys mapped to file paths (also strings). These are files the map will need to load correctly, and may be absolute or relative paths. If relative, this path will be relative to the directory the QEM file is in. Always check if a file referenced by this block exists before trying to use it, especially if it’s an absolute path! The path block has the following layout:
Path Block (static)
Bytes
Example
Description
2
0x0000
Number of path fields
[x]
[...]
Path fields (layout below)
There are no rows in this table
Path Field
Bytes
Example
Description
2
0x0500
Byte length of the field’s identifier
[x]
“audio”
Field identifier as a UTF-8 string
2
0x0900
Byte length of the field’s value
[x]
"audio.mp3”
Field value as a UTF-8 string
There are no rows in this table
Below are all the paths SSQE may write into a QEM file. None of these fields are required, and there may be additional fields you must be able to account for if you intend to process QEM files for your own purposes.
Possible Path Fields
Identifier
Description
audio
The path to the audio for this map, which is not restricted to a certain set of file types
cover
The path to the cover for this map, which should be a valid PNG file
video
The path to the video for this map, which should be a valid MP4 file
There are no rows in this table

Following the path block will be a metadata block with zero or more metadata fields. Each field is represented by a UTF-8 string identifier and a value, with this layout:
Metadata Block (static)
Bytes
Example
Description
2
0x0000
Number of metadata fields
[x]
[...]
Metadata fields (layout below)
There are no rows in this table

Metadata Field
Bytes
Example
Description
2
0x0500
Byte length of the field’s identifier
[x]
“title”
Field identifier as a UTF-8 string
1
0
(*)Type of the field’s value
[x]
0x0000000000000000
Field value
There are no rows in this table

Below are all the fields written by SSQE into metadata, along with their default values if not present. A QEM file cannot be expected to contain all of these fields, and it may contain more fields than are listed here, so all present fields must be handled appropriately. Additionally, a field could have a different type than what is listed here, which is not recommended but must be accounted for.
Possible Metadata Fields
Identifier
(*) Data type
Description
Default
title
0x0A
UTF-8 song title
”"
romanizedTitle
0x0A
ASCII song title
”"
artists
0x0E[0x0A]
Array of UTF-8 artist names
[]
romanizedArtists
0x0E[0x0A]
Array of ASCII artist names
[]
mappers
0x0E[0x0A]
Array of UTF-8 mapper names
[]
difficulty
0x00
Standardized integer-based difficulty rating
[0]: N/A
[1]: Easy
[2]: Medium
[3]: Hard
[4]: LOGIC?/Expert
[5]: BRRR/Tasukete/Insane
255
difficultyStars
0x07
A more flexible star-based difficulty rating
(No scale defined for SSQE)
0
difficultyName
0x0A
UTF-8 custom difficulty name
”"
songLinks
0x0E[0x0A]
Array of UTF-8 links to this song on external platforms
{}
artistLinks
0x10[0x0A,0x0A]
Dictionary of UTF-8 artist names mapped to UTF-8 links for each artist on external platforms
{}
mapperIds
0x10[0x0A,0x05]
Dictionary of UTF-8 mapper names mapped to signed 64-bit IDs for each mapper
{}
mapId
0x0A
ASCII map ID
There are no rows in this table

Next up in the format are object blocks, or sets of what SSPMv2 considers to be “markers” - notes, timing points, etc.
Object Blocks (static)
Bytes
Example in Hex
Description
1
0x02
Number of object blocks
[x]
[...]
Object blocks (layout below)
There are no rows in this table

Object Block
Bytes
Example in Hex
Description
1
0x00
Numeric object ID
2
0x0000
Length of UTF-8 object name in bytes
[x]
“”
UTF-8 object name (must be blank if you intend these objects to be read by SSQE, which means the object’s types must align)
1
0x05
Number of types present in each object (excluding timestamp)
[x]
0x07, 0x07, 0x09, 0x00, 0x00
(*)The list of types present in all objects
4
0x00000000
Number of objects in this block as a 32-bit signed integer
There are no rows in this table

Object Data
Bytes
Example in Hex
Description
8
0x0000000000000000
Timestamp as a 64-bit floating point number, in milliseconds
[x]
[...]
Object data as defined in the object block’s types
There are no rows in this table

Below are the possible object IDs and type lists recognized by SSQE. These must be accounted for even if you don’t use all of them. If you intend to create a new object type for your own purposes, it’s recommended to use IDs descending from 255 instead of ascending from where these IDs stop.
Possible Object Data
Object ID
Name
(*) Data Types
Defaults
Game of Origin
0
Note
0x07: X position
0x07: Y position
0x09: Uses a tweened approach rate
0x00: Easing style
0x00: Easing direction
0
0
false
0
0
-
-
Novastra
Novastra
Novastra
1
Timing Point (BPM)
0x07: BPM
0x00: Time signature top
0x00: Time signature bottom
0
4
4
-
-
-
2-11
RESERVED
N/A
N/A
N/A
12
Beat
-
-
Novastra
13
Glide
0x00: Glide direction
4
Novastra
14
Mine
0x07: X position
0x07: Y position
0
0
Novastra
Novastra
15
Lyric
0x0A: Text
0x09: Fades in
0x09: Fades out
“”
false
false
Novastra
Novastra
Novastra
16
Fever
0x08: Duration in milliseconds
0
Novastra
17
Bookmark
0x08: Duration in milliseconds
0x0A: Text
0
“”
-
-
There are no rows in this table
Keep in mind that an object block may identify more or less types than are listed here. An object will always have as many types as the block defines, but these types aren’t guaranteed to be identical to what your parser can understand. If an object has less types than you expect, you should use the defaults listed next to each type here as a placeholder. If an object has more types than you expect, you can safely discard them after reading them.
A type listed for any object will never be deleted or altered for another purpose, or have its type changed. While you cannot assume the types in an object block are identical to these types in a valid parser implementation, such a case where a type of an SSQE-compatible object conflicts with a type listed here or is not in this list would mean the object block is invalid and can safely be discarded after being read.

Miscellaneous lookup tables:
Easing Styles
Index
Value
0
Linear
1
Sine
2
Back
3
Quad
4
Quart
5
Quint
6
Bounce
7
Elastic
8
Exponential
9
Circular
10
Cubic
There are no rows in this table
Easing Directions
Index
Value
0
In
1
Out
2
InOut
There are no rows in this table
Glide Directions
Index
Value
0
Up
1
Right
2
Down
3
Left
4
None
There are no rows in this table

Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.