Skip to content

Instantly share code, notes, and snippets.

@jonbarrow
Last active January 9, 2024 18:06
Show Gist options
  • Save jonbarrow/d448bf0f2cb6bf500091dfaf606d9fbf to your computer and use it in GitHub Desktop.
Save jonbarrow/d448bf0f2cb6bf500091dfaf606d9fbf to your computer and use it in GitHub Desktop.
Documentation on the Miiverse "AppData" used in Mario vs. Donkey Kong: Tipping Stars

App Data format

Miiverse AppData for Mario vs. Donkey Kong Tipping Stars has 3 different main types:

  1. Community Info
  2. Post Info
  3. Comment Info

Each type starts with a different 4 byte string denoting which info type it is

Community Info

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

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

Binary buffers

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

Binary Buffer:

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

Checksum algorithm:

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

Map Header:

Offset Size Name
0x0 0x20 UTF-16 Map name
0x20 0x1 Map Width
0x21 0x1 Map Height

Buffer Data

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

Map buffer data

Thumbnail buffer data

Map Settings

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

Camera Intro

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

Help Data

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

Thumbnail Settings

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

Thumbnail Slopes

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

Tile Grid

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

Entities List

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

Rivet Entity (type ID 1)

Total bytes: 2

Bits Name Notes
7 PosX
6 PosY
1 Enabled

Editable Line Entity (type ID 5)

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 Entity (type ID 6)

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

Cannon Kong Entity (type ID 8)

Total bytes: 2

Bits Name Notes
7 PosX
6 PosY
1 Facing direction
2 Kong facing direction

Mini Toy Entity (type ID 16)

Total bytes: 2

Bits Name Notes
7 PosX
6 PosY
1 Facing direction
1 Key bit
1 Capsule bit

Comment Info

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 (??)")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment