// BSON DATA
{
"origin": {
"majorVersion": integer SAVE_VERSION, // current TPT version that matters to the save server, eg 83
"minorVersion": integer MINOR_VERSION, // TPT minor version - after the dot, eg 0
"buildNum": integer BUILD_NUM, // TPT build version, always goes up, eg 272
"snapshotId": integer SNAPSHOT_ID, // TPT snapshot version (rarely used), eg 1346881831
"releaseType": integer IDENT_PLATFORM, //TPT release type eg "WIN32"
"builtType": integer IDENT_BUILD // Build type eg "SSE3"
},
"waterEEnabled": boolean waterEEnabled, // Water equalisation
"legacyEnable": boolean legacyEnable, // Legacy mode - no heat?
"gravityEnable": boolean gravityEnable, // Newtonian Gravity
"aheat_enable": boolean aheat_enable, // Ambient Heat
"paused": boolean paused, // paused?
"gravityMode": integer gravityMode, // Vertical/Radial/No gravity
"airMode": integer airMode, // Off/No pressure/No velocity/No update/All on
// "leftSelectedElement": integer sl,
// "rightSelectedElement": integer sr,
// "activeMenu": int active_menu,
// following may or may not appear on the save file:
// https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L1833-L1995
// Everything relating to particles. Make sure to check out the code!
"parts": binary,
// https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L1819-L1831
// Particle stacking array, check out the code here too
"partsPos": binary,
// https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L1743-L1783
// Wall map. Check out the code.
"wallMap": binary,
// https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L1761-L1771
// A special wall map for special fans
"fanMap": binary,
// https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L1997-L2042
// Even more special map for soap connection points. Make sure to read the code.
"soapLinks": binary,
// https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L2077-L2085
// Element Name/ID pairs as PaletteItems
"<element name>": <element ID>,
// https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L2086-L2111
// Signs
"signs": [
// (each sign adds an object)
{
"text": string signs[i].text.c_str(),
"justification": int signs[i].ju,
"x": int signs[i].x,
"y": int signs[i].y
}
// ...
]
}
// SAVE BYTES
// Length of file: BSON data dump size * 2 + 12 bytes
[0]-[3]: 'OPS1',
[4]: SAVE_VERSION,
[5]: CELL, // 4
[6]: blockW,
[7]: blockH,
[8]: finalDataLen // size of the BSON data structure up above
[9]: finalDataLen >> 8
[10]: finalDataLen >> 16
[11]: finalDataLen >> 24
[12]-: bzip2-compressed dump of the BSON object
/*
BZ2_bzBuffToBuffCompress( (char*)(outputData+12),
&outputDataLen,
(char*) finalData,
bson_size(&b),
9,
0,
0)
*/
To translate the uncompressed bytes into the format given by @boxmein (View Post), see http://bsonspec.org/#/specification
To understand that, you'll need to know how to read BNF specifications, which might not be verbose English but are a detailed explanation of how to interpret data, and are pretty easy to comprehend once you're familiar with them.
I'm not going to give a detailed explanation in English of BSON. However, to help you check your understanding of the BNF for it, here is part of the hexadecimal you posted, interpreted according to the specification (compare it to the structure shown in boxmein's post):
C5 E4 0D 00 : int32, length of document
03 : start of embedded document
6F 72 69 67 69 6E 00 : name of embedded document as a 0 terminated string ("origin").
82 00 00 00 : int32, length of embedded document
followed by the elements in the embedded document
10 : type of first element = int32
6D 61 6A 6F 72 56 65 72 73 69 6F 6E 00 : name of first element as a 0 terminated string ("majorVersion").
59 00 00 00: value of first element = 89
10 : type of second element = int32
6D 69 6E 6F 72 56 65 72 73 69 6F 6E 00 : name of second element as a 0 terminated string ("minorVersion").
00 00 00 00 : value of second element = 0
Interpretation of the binary data in parts and partsPos would be a nice thing to have documented, though personally I find the readOPS function pretty straightforward to follow (https://github.com/simtr/The-Powder-Toy/blob/master/src/client/GameSave.cpp#L698-L1033 as opposed to serialiseOPS in the links from boxmein and mniip, since the read function is probably more useful if you want to know how to read a save file).
A binary element has the form:
"\x05" e_name binary
= (05) (70 61 72 74 73 00) (B6 21 03 00 80 ....)
The 'binary' part of that has the form:
binary ::= int32 subtype (byte*)
= (B6 21 03 00) (80) (....)
The 0x80 byte is therefore the 'subtype'. The (byte*) that follows is the actual data ((byte*) in BNF means 0 or more bytes, not a pointer as it might in C/C++).
subtype ::= "\x00" Binary / Generic
| "\x01" Function
| "\x02" Binary (Old)
| "\x03" UUID (Old)
| "\x04" UUID
| "\x05" MD5
| "\x80" User defined
subtype is a description of the binary data, so if storing a MD5 hash as a binary element, subtype could be 0x05. Some subtypes have an effect on how the subsequent bytes should be interpreted (hover mouse over the (i) symbols in the specification), but 0x80 means "the structure of the binary data can be anything". So yes, the 0x80 is part of the header and the subsequent bytes are the data.