Miiverse AppData for Mario vs. Donkey Kong Tipping Stars has 3 different main types:
Each type starts with a different 4 byte string denoting which info type it is
Community info AppData only contains the type string and community type
Offset | Size | Name |
---|---|---|
0x00 | 0x4 | Info type (MVMI) |
0x04 | 0x1 | Community type (0x0 = "UserLevels", 0x1 = "NintendoLevels") |
Post info AppData is comprised of a header containing metadata about the platform and NEX data followed by a binary buffer containing either map data or thumbnail data
Offset | Size | Name |
---|---|---|
0x00 | 0x4 | Info type (MVPI) |
0x04 | 0x2 | Current data (app?) version (always 1) |
0x06 | 0x2 | Platform (0x1 = WiiU, 0x2 = 3DS, 0x3 = "official platform (??)") |
0x08 | 0x8 | DataStore primary ID |
0x10 | 0x8 | DataStore secondary ID (only set if the level has secondary map buffer, else 0) |
0x18 | Binary buffer |
A binary buffer can be either map data or thumbnail data, but each starts with the same 10 byte main header followed by a 34 byte secondary "map" header, and then finally followed by the buffer data
Offset | Size | Name |
---|---|---|
0x00 | 0x4 | Checksum |
0x04 | 0x4 | Binary type (string) (MAPD = map data, THMD = thumbnail data) |
0x08 | 0x2 | Map version (even for thumbnails) |
0x0A | 0x22 | Map Header |
0x2C | Buffer data |
The checksum is calculated using the entire Binary Buffer (not just the buffer data). The checksum algorithm assumes that the input buffer includes the 4 checksum bytes, but set to null
// * Example data minus the 4 expected NULL bytes
const data = Buffer.from([
0x54, 0x48, 0x4D, 0x44, 0x00, 0x0E, 0x00, 0x47, 0x00, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x18, 0x03, 0x03, 0x0A, 0x08, 0x00, 0x10, 0x00, 0x00,
0x01, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
0x01, 0x01, 0x08, 0x01, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x04, 0x0F, 0x04, 0x0F, 0x01, 0x04, 0x13, 0x04, 0x13, 0x0A, 0x04, 0x02, 0x02, 0x00, 0x01,
0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x36, 0x03, 0x00, 0x03, 0x02, 0x02, 0x00, 0x05, 0x00, 0x05,
0x02, 0x00, 0x00, 0x01, 0x02, 0x03, 0x08, 0x09, 0x4D, 0x2C, 0x03, 0x08, 0xC9, 0x4B, 0x1A, 0x0C,
0x03, 0x02, 0x01, 0x04, 0x31, 0x00, 0x00, 0x01, 0x00, 0x0E, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00,
0x01, 0xC7, 0x54, 0x26, 0x0A, 0x01, 0x00, 0x00, 0x0F, 0x01, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00
]);
function calculateChecksum(data) {
// * Add the NULL checksum bytes if don't exist
if (data.readUInt32LE() !== 0) {
data = Buffer.concat([
Buffer.from([ 0x00, 0x00, 0x00, 0x00 ]),
data
]);
}
let i; // * "i" is used in multiple loops, define here to reuse
const startOffset = 4; // * First round happens skipping the NULL checksum bytes
const size = data.length;
const end = size - 4;
let checksum = 0;
// * Round 1
for (i = startOffset; i <= end; i += 4) {
checksum += data.readUInt32BE(i);
}
// * Round 2
for (; i < size; i++) {
checksum += data.readUInt8(i);
}
checksum += (size - startOffset);
checksum %= 0xFFFFFFFF;
return checksum;
}
const expected = 0xDF3F7A63;
const calculated = calculateChecksum(data);
console.log(expected === calculated); // * true
Offset | Size | Name |
---|---|---|
0x0 | 0x20 | UTF-16 Map name |
0x20 | 0x1 | Map Width |
0x21 | 0x1 | Map Height |
The different buffer data types share some structures. Each buffer type will be documented as just a list of structures, in order, with the individual structures having the detailed documentation
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Map theme |
0x1 | 0x1 | Map background |
0x2 | 0x1 | Background music |
0x3 | 0x1 | Thumbnail X |
0x4 | 0x1 | Thumbnail Y |
0x5 | 0x4 | Goal score |
0x9 | 0x1 | Pipe skin |
0xA | 0x1 | Rivet skin |
Offset | Size | Name |
---|---|---|
0x0 | 0x2 | Keyframe count |
0x2 | 0x4 * Keyframe count | Keyframes |
Keyframes
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | X |
0x1 | 0x1 | Y |
0x2 | 0x2 | Time |
Offset | Size | Name |
---|---|---|
0x0 | 0x2 | Help data count |
0x2 | 0x4 * Help data count | Help datas |
Help datas
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Object type |
0x1 | 0x1 | Object X |
0x2 | 0x1 | Object Y |
0x3 | 0x1 | Order |
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Map theme |
0x1 | 0x1 | Map background |
0x2 | 0x1 | Thumbnail width in tiles |
0x3 | 0x1 | Thumbnail height in tiles |
0x4 | 0x1 | Offset X (?) |
0x5 | 0x1 | Offset Y (?) |
0x6 | 0x1 | Pipe skin |
0x7 | 0x1 | Rivet skin |
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Slope count |
0x1 | Slope count * 4 | Slopes |
Slopes:
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Slope top |
0x1 | 0x1 | Slope left |
0x2 | 0x1 | Slope bottom |
0x3 | 0x1 | Slope right |
Offset | Size | Name |
---|---|---|
0x0 | W*H | Buffer of single byte tiles starting from the top left of the input tile map. For thumbnails W and H are the width and height from the Thumbnail Settings. For maps this is the width and height from the Map Header |
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Entity Count |
0x1 | Entities |
Entities
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Entity type |
0x1 | Subtypes list |
Subtypes list
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Subtypes Count |
0x1 | Subtypes |
Subtypes
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Subtype type |
0x1 | Instance datas list |
Instance datas list
Offset | Size | Name |
---|---|---|
0x0 | 0x1 | Instance Datas Count |
0x1 | Instance Datas |
Instance Datas
Instance datas are tightly packed bit streams whos content depends on what the entity type (not entity subtype) is. Not all bytes will be full
Total bytes: 2
Bits | Name | Notes |
---|---|---|
7 | PosX | |
6 | PosY | |
1 | Enabled |
Total bytes: 3
Bits | Name | Notes |
---|---|---|
6 | Start X | posx >> 1 |
5 | Start Y | posy >> 1 |
6 | End X | endx >> 1 |
5 | End y | endy >> 1 |
Pipe entities have a variable number of bytes due to them having a variable number of points. Each pipe entity starts with a header and is followed by point bits
Bits | Name | Notes |
---|---|---|
1 | Split bit | |
1 | Opaque bit | |
4 | Color | |
4 | Group ID | |
5 | Point count | |
(6 + 5) * Point count | Points |
Point
Bits | Name | Notes |
---|---|---|
6 | Point X | posx >> 1 |
5 | Point Y | posy >> 1 |
Total bytes: 2
Bits | Name | Notes |
---|---|---|
7 | PosX | |
6 | PosY | |
1 | Facing direction | |
2 | Kong facing direction |
Total bytes: 2
Bits | Name | Notes |
---|---|---|
7 | PosX | |
6 | PosY | |
1 | Facing direction | |
1 | Key bit | |
1 | Capsule bit |
Comment info AppData contains only the type string, version number, and platform type
Offset | Size | Name |
---|---|---|
0x00 | 0x4 | Info type (MVCI) |
0x04 | 0x2 | Current data (app?) version (always 1) |
0x06 | 0x2 | Platform (0x1 = WiiU, 0x2 = 3DS, 0x3 = "official platform (??)") |