Extra metadata in .vvv

This is a draft, basically I think it'd be a good thing to make one standard format for extra metadata (like song titles and textual notes) in .vvv files.

For the old version of this document (which summarized more what the idea was and explained the rationale), click here.


The existing .vvv format

The .vvv format is normally structured in two parts: 128 song headers (despite only 16 being used), followed by all the songs concatenated.

Each of the 128 song headers is structured as follows in a C/C++ struct:

	struct resourceheader
	{
		char name[48];     /* 48 bytes */
		int start;         /* 4 bytes */
		int size;          /* 4 bytes */
		bool valid;        /* 1 byte, plus 3 bytes padding */
	};
	

For the songs in VVVVVV, the name attribute is always one of the following:

data/music/0levelcomplete.ogg
data/music/1pushingonwards.ogg
data/music/2positiveforce.ogg
data/music/3potentialforanything.ogg
data/music/4passionforexploring.ogg
data/music/5intermission.ogg
data/music/6presentingvvvvvv.ogg
data/music/7gamecomplete.ogg
data/music/8predestinedfate.ogg
data/music/9positiveforcereversed.ogg
data/music/10popularpotpourri.ogg
data/music/11pipedream.ogg
data/music/12pressurecooker.ogg
data/music/13pacedenergy.ogg
data/music/14piercingthesky.ogg
data/music/predestinedfatefinallevel.ogg

As far as I know the game relies on the name to match these expected values, and loads in song numbers by these filenames, and won't search for any other names than these. Most you could do by changing these is reorder songs, or get a song to not play in VVVVVV at all.

The start attribute is not used at all. In fact, it's not even initialized in Terry's original music packing code, so it may be completely random.

The size attribute is the size of the song in bytes. Note that this is stored in little-endian, so length 0x00112233 is stored as 33 22 11 00. But depending on the way you read/write music files, you may not even have to be aware about this. For invalid songs (see below), this can be uninitialized.

The valid attribute basically indicates how many songs there are; the first song marked invalid and all the ones after will not be loaded. In practice, songs 0-15 are valid and the rest invalid. The 2.0 music file used to mark song 15 as invalid as well, since the Predestined Fate remix didn't exist yet.
Because of alignment, three bytes after valid are used as padding, which may be uninitialized random bytes.

As said, the 128 headers are followed by the filedata (audio data) of all the songs.

The entire format can be represented more graphically as follows, assuming not all 128 songs are 'valid':

path [48] [4] A[0].size t [3] /* song header 0 */
path [48] [4] A[1].size t [3] /* song header 1 */
.
.
.
path [48] [4] A[n].size t [3] /* song header n, last valid (usually 15) */
null bytes [48] [4] [4] f [3] /* song header n+1, first invalid (usually 16) */
.
.
.
null bytes [48] [4] [4] f [3] /* song header 126 */
null bytes [48] [4] [4] f [3] /* song header 127 */

A[0]
[size 0..231-1, given in header 0 as size]
 

A[1]
[size 0..231-1, given in header 1 as size]
 
.
.
.

A[n]
[size 0..231-1, given in corresponding header as size]
 

Extra metadata

What would be cool to store is stuff like song names and textual notes, without breaking VVVVVV of course, and while keeping compatibility with existing music files (we want to reduce the chance of uninitialized garbage data being interpreted as data in the new format). That way, people could make .vvvs without having to keep separate text files or level notes. It would also be nice to have some fields for the music file in general, like album name or information about the level it was made for.


This document defines version 1.0 of the format (major.minor).

The format is modified by adding one dynamically sized block of metadata for the file as a whole (file metadata) and dynamically sized blocks with metadata for songs (song metadata) at the end of the file.

The song metadata blocks are all optional, and can thus have a size of 0. Only songs marked as 'valid' may have song metadata. The maximum value of any B[x].size and F.size is 16777215 (0xFFFFFF). The file metadata block is almost fully optional, and can have a size of at least 2 bytes. The structures of the song metadata and file metadata blocks will be detailed later.

The start field of a header for each valid song x is set to B[x].size. So, start indicates how large a song's metadata is, just like size indicates how large a song's audio data is.

In the header for the last invalid song, song 127, the start field is set to F.size, and the size field is set to S (the sum of all metadata sizes). The purpose of storing S is to distinguish uninitialized garbage memory in start fields from being interpreted as lengths of metadata blocks. When loading a .vvv file with extra metadata, a program must calculate S itself and check if this is equal to the value given in the size field of header 127. If it doesn't match, then the file should be considered to have no valid metadata. If any value of start is higher than 0xFFFFFF or is a negative value, the file can also be considered to have no valid metadata.

Here's what the format looks like with these modifications:

path [48] B[0].size A[0].size t [3] /* song header 0 */
path [48] B[1].size A[1].size t [3] /* song header 1 */
.
.
.
path [48] B[n].size A[n].size t [3] /* song header n, last valid (usually 15) */
null bytes [48] [4] [4] f [3] /* song header n+1, first invalid (usually 16) */
.
.
.
null bytes [48] [4] [4] f [3] /* song header 126 */
null bytes [48] F.size S f [3] /* song header 127 */

A[0]
[size 0..231-1, given in header 0 as size]
 
/* first song audio data */

A[1]
[size 0..231-1, given in header 1 as size]
 
.
.
.

A[n]
[size 0..231-1, given in corresponding header as size]
 
/* last song audio data */

F
[size 0..224-1, given in header 127 as size]
 
/* file metadata */

B[0]
[size 0..224-1, given in header 0 as start]
 
/* first song metadata */

B[1]
[size 0..224-1, given in header 1 as start]
 
.
.
.

B[n]
[size 0..224-1, given in corresponding header as start]
 
/* last song metadata */

Format of file metadata, F

The block of file metadata, F, can be structured in two ways, a short format with just the version number, and a long format.

If the file metadata is missing altogether (0 bytes), it means song metadata should be missing too. In other words, that way it's just a traditional .vvv without metadata.

The format version number is included in the file metadata. The version number should be understood as follows:

Short format

The short format is 2 bytes and is meant for cases where you don't really to store metadata about the file, but do want to use song metadata. It's structured as follows:

v v

This is the following:

Long format

The long format is as follows:

v v [2] time name 0 artist 0 notes 0

It's divided in the following parts:

All these string fields may be of any size (as long as the total size limit for a metadata block is not broken), and they may be empty, but the NUL bytes must always exist. Text should be encoded in UTF-8.

Format of song metadata, B[x]

A block of song metadata, B[x], is structured either as a 0-byte object, or as follows:

song name 0 filename 0 notes 0

It's divided in the following parts:

The strings may again be of any size, their NUL bytes must always exist, and text should be encoded in UTF-8.