GameData: Difference between revisions
GalaxyMaster (talk | contribs) (import page from https://wiibrew.org/wiki/Super_Mario_Galaxy_savefile) |
GalaxyMaster (talk | contribs) m (some fixes) |
||
Line 1: | Line 1: | ||
The | The games save progress data into a file named ''GameData.bin'' stored within the game's NAND save. Most of the functions that handle save game functionality also have ''GameData'' in the name. | ||
The GameData.bin file consists of a header, followed by an index and some data sections. | The GameData.bin file consists of a header, followed by an index and some data sections. | ||
Line 35: | Line 35: | ||
The checksum is calculated as follows, with buf pointing to offset 0x04 in the file, and len being the length of GameData.bin minus 0x04: | The checksum is calculated as follows, with buf pointing to offset 0x04 in the file, and len being the length of GameData.bin minus 0x04: | ||
< | <syntaxhighlight lang="c"> | ||
uint32_t generate_checksum(void *buf, int len) | uint32_t generate_checksum(void *buf, int len) | ||
{ | { | ||
Line 49: | Line 49: | ||
return (c2 & 0xFFFF) | ((c1 & 0xFFFF) << 16); | return (c2 & 0xFFFF) | ((c1 & 0xFFFF) << 16); | ||
} | } | ||
</ | </syntaxhighlight> | ||
=== Index === | === Index === |
Latest revision as of 09:27, 21 February 2023
The games save progress data into a file named GameData.bin stored within the game's NAND save. Most of the functions that handle save game functionality also have GameData in the name.
The GameData.bin file consists of a header, followed by an index and some data sections.
Header
Start | End | Length | Description |
0x000 | 0x003 | 4 | Checksum |
0x004 | 0x007 | 4 | ?? Version = 2 |
0x008 | 0x00B | 4 | number of entries |
0x00C | 0x00F | 4 | size of GameData.bin |
The checksum is calculated as follows, with buf pointing to offset 0x04 in the file, and len being the length of GameData.bin minus 0x04:
uint32_t generate_checksum(void *buf, int len)
{
uint16_t *data = (uint16_t*)buf;
uint32_t c1 = 0, c2 = 0;
for (int i = 0; i < len / sizeof(uint16_t); i++)
{
c1 = (c1 + data[i]) & 0xFFFF;
c2 = (c2 + ~data[i]) & 0xFFFF;
}
return (c2 & 0xFFFF) | ((c1 & 0xFFFF) << 16);
}
Index
The index has as many entries as specified in the header. Index entries are stored consecutively. The format of an index entry is specified in the following table.
Start | End | Length | Description |
0x000 | 0x00B | 12 | entry name, null padded (i.e. 'mario1', 'luigi1', ...) |
0x00C | 0x00F | 4 | offset of entry data in GameData.bin |
Data
There are at least 4 types of data entries:
- Player data is stored in PLAY data entries
- 'mario%1d' and 'luigi%1d' entries have a length of 0xF80 bytes
- Configuration data is stored in CONF data entries
- 'config%1d' entries have a length of 0x60 bytes
- System configuration is stored in SYSC data entries (there is usually only one SYSC data entry)
- 'sysconf' entries have a length of 0x80 bytes
Start | End | Length | Description |
0x000 | 0x003 | 4 | ?? entry type code (0x01010000=SYSC, 0x01030000=CONF, 0x01060000=PLAY) |
0x004 | 0x007 | 4 | entry type name ('SYSC', 'CONF', 'PLAY') |
... | ... | ... | ... |
Vulnerabilities
Super Mario Galaxy (and its sequel, Super Mario Galaxy 2) are known to have an unexploitable vulnerability in parsing GameData.bin. The entire file is loaded into memory at once, and after being verified for integrity (checksum, version, etc.), its index entries are extracted. The game keeps an in-memory template of an empty savefile, with the index entries being copied into the template. A function is used (on both GameData.bin and the template) to get the data pointer and length of an entry.
This function calculates the length of an index entry by subtracting the current entry's offset from the next entry's offset. Unfortunately, the length from GameData.bin is not used for the memcpy() into the template: the length from the template is used. However, once the memcpy() completes, the game checks if the amount of data specified by GameData.bin is less than the amount copied. This is done by checking whether template_size - reported_size > 0. It is possible to make reported_size negative (>= 0x80000000), which would cause that check to return true. If true, it memset()s that amount of 0 bytes after the copied data.
Unfortunately, the layout of the heap and the fact that it is all 0 bytes makes this vulnerability apparently useless.