Difference between revisions of "Hey! Pikmin save file"

From Pikmin Technical Knowledge Base
Jump to navigation Jump to search
(→‎Checksum: add info on the checksum algorithm)
(Identified some fields, especially the log entry ones.)
(5 intermediate revisions by 2 users not shown)
Line 3: Line 3:
  
 
== Format ==
 
== Format ==
The save data is split into several blocks that start with 4-byte magic words.
+
The save data is split into several blocks that start with 4-byte magic words. The blocks are ordered in the same order they are presented in this article.
  
 
=== SAVE ===
 
=== SAVE ===
Line 11: Line 11:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0000</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>SAVE</code>.
+
| <code>0x0000</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>SAVE</code>.
 
|-
 
|-
| <code>000C</code> || <code>000C</code> || 4 bytes || Save data checksum || {{unsure|Calculated using an unknown algorithm.}}
+
| <code>0x000C</code> || <code>0x000C</code> || 4 bytes || Save data checksum || Calculated using [[#Checksum|this method]].
 
|}
 
|}
  
 
=== NEWS ===
 
=== NEWS ===
 +
What "news" the player has seen. "News" are the cutscenes that take place inside the S.S. Dolphin II, where the ship explains something to Olimar.
 
{| class="wikitable sortable"
 
{| class="wikitable sortable"
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
Line 22: Line 23:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0010</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>NEWS</code>.
+
| <code>0x0010</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>NEWS</code>.
 
|-
 
|-
 +
| <code>0x0014</code> || <code>0x0004</code> || 4 bytes || Unknown ||
 +
|-
 +
| <code>0x0018</code> || <code>0x0008</code> || 4 bytes || Unknown news controller 1 || 0 when the player hasn't heard the news about 1-A yet, 1 when they have.
 +
|-
 +
| <code>0x001C</code> || <code>0x000C</code> || 4 bytes || Unknown news controller 2 || 0 when the player hasn't heard the news about 1-A yet, 8 when they have.
 
|}
 
|}
  
 
=== OPTI ===
 
=== OPTI ===
 +
The player's preferences in the options menu.
 
{| class="wikitable sortable"
 
{| class="wikitable sortable"
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
Line 32: Line 39:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0020</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>OPTI</code>.
+
| <code>0x0020</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>OPTI</code>.
 +
|-
 +
| <code>0x0024</code> || <code>0x0004</code> || 4 bytes || {{unknown|Unknown}} ||
 +
|-
 +
| <code>0x0028</code> || <code>0x0008</code> || 1 byte || Music volume || 0 to 3.
 
|-
 
|-
| <code>0028</code> || <code>0008</code> || 1 byte || Music volume || <code>01</code>, <code>02</code>, or <code>03</code>.
+
| <code>0x0029</code> || <code>0x0009</code> || 1 byte || Sound effects volume || 0 to 3.
 
|-
 
|-
 +
| <code>0x002A</code> || <code>0x000A</code> || 2 bytes || {{unknown|Unknown}} ||
 
|}
 
|}
  
 
=== PBUF ===
 
=== PBUF ===
 +
Which log entries the player has unlocked, and which they have read.
 
{| class="wikitable sortable"
 
{| class="wikitable sortable"
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
Line 44: Line 57:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>002C</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>PBUF</code>.
+
| <code>0x002C</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>PBUF</code>.
 
|-
 
|-
 +
| <code>0x0030</code> || <code>0x0004</code> || 4 bytes || Unknown || Messing with it can make the save file be recognized as empty in the title screen.
 +
|-
 +
| <code>0x0034</code> || <code>0x0008</code> || 36 bytes || Unlocked entries bitfield || The first 11 bits correspond to the Pikmin Logs category (blue pellet, red pellet, rock pellet, winged pellet, yellow pellet, Onion, Red Pikmin, Blue Pikmin, Yellow Pikmin, Rock Pikmin, Winged Pikmin). After that come the enemies, normal treasures, and amiibo treasures, but their order is all over the place.
 +
|-
 +
| <code>0x0058</code> || <code>0x002C</code> || 4 bytes || Read "Pikmin Logs" entries bitfield ||
 +
|-
 +
| <code>0x005C</code> || <code>0x0030</code> || 36 bytes || Read entries from the other categories bitfield ||
 
|}
 
|}
  
Line 54: Line 74:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0080</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>PARK</code>.
+
| <code>0x0080</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>PARK</code>.
 
|-
 
|-
 
|}
 
|}
Line 64: Line 84:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>04A4</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>MINI</code>.
+
| <code>0x04A4</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>MINI</code>.
 
|-
 
|-
 
|}
 
|}
Line 74: Line 94:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0588</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>CRNT</code>.
+
| <code>0x0588</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>CRNT</code>.
 
|-
 
|-
 
|}
 
|}
Line 84: Line 104:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>09A8</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>GAME</code>.
+
| <code>0x09A8</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>GAME</code>.
 +
|-
 +
| <code>0x09AC</code> || <code>0x0004</code> || 4 bytes || {{unknown|Unknown}} ||
 +
|-
 +
| <code>0x09B0</code> || <code>0x0008</code> || 4 bytes || Sparklium total || Values over 99999 will make the game assume it's just 99999
 +
|-
 +
| <code>0x09B4</code> || <code>0x000C</code> || 44 bytes || {{unknown|Unknown}} ||
 
|-
 
|-
 
|}
 
|}
Line 94: Line 120:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>09E0</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>EVNT</code>.
+
| <code>0x09E0</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>EVNT</code>.
 
|-
 
|-
 
|}
 
|}
Line 104: Line 130:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0AE8</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>HELP</code>.
+
| <code>0x0AE8</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>HELP</code>.
 
|-
 
|-
 
|}
 
|}
Line 114: Line 140:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0BF0</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>RINF</code>.
+
| <code>0x0BF0</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>0xRINF</code>.
 
|-
 
|-
 
|}
 
|}
Line 124: Line 150:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0C2C</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>RSLT</code>.
+
| <code>0x0C2C</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>RSLT</code>.
 
|-
 
|-
 
|}
 
|}
Line 134: Line 160:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0C38</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>SPER</code>.
+
| <code>0x0C38</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>SPER</code>.
 
|-
 
|-
 
|}
 
|}
Line 144: Line 170:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0C48</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>STRE</code>.
+
| <code>0x0C48</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>STRE</code>.
 
|-
 
|-
 
|}
 
|}
Line 154: Line 180:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0C90</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>PLRP</code>.
+
| <code>0x0C90</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>PLRP</code>.
 
|-
 
|-
 
|}
 
|}
  
 
=== DATE ===
 
=== DATE ===
 +
Time at which the save was made.
 +
 
{| class="wikitable sortable"
 
{| class="wikitable sortable"
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
 
! colspan="2" | Offset || rowspan="2" | Length || rowspan="2" | Field || rowspan="2" | Notes
Line 164: Line 192:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0CE0</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>DATE</code>.
+
| <code>0x0CE0</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>DATE</code>.
 
|-
 
|-
 +
| <code>0x0CE4</code> || <code>0x0004</code> || 4 bytes || {{unknown|Unknown}} ||
 +
|-
 +
| <code>0x0CE8</code> || <code>0x0008</code> || 4 bytes || Year || Human format. For instance, 2019 has the actual value "2019".
 +
|-
 +
| <code>0x0CEC</code> || <code>0x000C</code> || 1 byte || Month || 1 to 12.
 +
|-
 +
| <code>0x0CED</code> || <code>0x000D</code> || 1 byte || Day || 1 to 31.
 +
|-
 +
| <code>0x0CEE</code> || <code>0x000E</code> || 1 byte || Hours || 0 to 23.
 +
|-
 +
| <code>0x0CEF</code> || <code>0x000F</code> || 1 byte || Minutes || 0 to 59.
 +
|-
 +
| <code>0x0CF0</code> || <code>0x0010</code> || 1 byte || Seconds || 0 to 59.
 +
|-
 +
| <code>0x0CF1</code> || <code>0x0011</code> || 11 bytes || {{unknown|Unknown}} ||
 
|}
 
|}
  
Line 174: Line 217:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0CFC</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>TTDS</code>.
+
| <code>0x0CFC</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>TTDS</code>.
 
|-
 
|-
 
|}
 
|}
Line 184: Line 227:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0D08</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>WMAP</code>.
+
| <code>0x0D08</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>WMAP</code>.
 
|-
 
|-
 
|}
 
|}
Line 194: Line 237:
 
! File || Block
 
! File || Block
 
|-
 
|-
| <code>0D80</code> || <code>0000</code> || 4 bytes || Block magic word || Always <code>SAMI</code>.
+
| <code>0x0D80</code> || <code>0x0000</code> || 4 bytes || Block magic word || Always <code>SAMI</code>.
 
|-
 
|-
 
|}
 
|}
Line 202: Line 245:
  
 
The following python code can recalculate the checksum if the data after the checksum is modified:
 
The following python code can recalculate the checksum if the data after the checksum is modified:
import struct
+
<pre>
from zlib import crc32
+
import struct
+
from zlib import crc32
filepath = "radish0.sav"  # put the script in the same folder as the save file
+
 
with open(filepath, "rb") as f:
+
filepath = "radish0.sav"  # put the script in the same folder as the save file
    header = f.read(0xC)
+
with open(filepath, "rb") as f:
    chksum = struct.unpack("I", f.read(4))[0]
+
    header = f.read(0xC)
   
+
    chksum = struct.unpack("I", f.read(4))[0]
    data = f.read()
+
   
+
    data = f.read()
calculated_chksum = crc32(data[0:0xd87])
+
 
with open(filepath, "wb") as f:
+
calculated_chksum = crc32(data[0:0xd87])
    f.write(header)
+
with open(filepath, "wb") as f:
    f.write(struct.pack("I", calculated_chksum))
+
    f.write(header)
    f.write(data)
+
    f.write(struct.pack("I", calculated_chksum))
 +
    f.write(data)
 +
</pre>
 +
 
 +
== Tools ==
 +
* [https://github.com/Espyo/Misc_Pikmin_tools/tree/master/hp_save_editor Hey! Pikmin save editor]
  
 
[[Category:File formats]]
 
[[Category:File formats]]
 
[[Category:Hey! Pikmin]]
 
[[Category:Hey! Pikmin]]

Revision as of 16:50, 5 March 2019

This article is a stub.

The file format for a Hey! Pikmin saved game. Note: this comes from analyses of the .sav files generated by Citra.

Format

The save data is split into several blocks that start with 4-byte magic words. The blocks are ordered in the same order they are presented in this article.

SAVE

Offset Length Field Notes
File Block
0x0000 0x0000 4 bytes Block magic word Always SAVE.
0x000C 0x000C 4 bytes Save data checksum Calculated using this method.

NEWS

What "news" the player has seen. "News" are the cutscenes that take place inside the S.S. Dolphin II, where the ship explains something to Olimar.

Offset Length Field Notes
File Block
0x0010 0x0000 4 bytes Block magic word Always NEWS.
0x0014 0x0004 4 bytes Unknown
0x0018 0x0008 4 bytes Unknown news controller 1 0 when the player hasn't heard the news about 1-A yet, 1 when they have.
0x001C 0x000C 4 bytes Unknown news controller 2 0 when the player hasn't heard the news about 1-A yet, 8 when they have.

OPTI

The player's preferences in the options menu.

Offset Length Field Notes
File Block
0x0020 0x0000 4 bytes Block magic word Always OPTI.
0x0024 0x0004 4 bytes Unknown[unsure]
0x0028 0x0008 1 byte Music volume 0 to 3.
0x0029 0x0009 1 byte Sound effects volume 0 to 3.
0x002A 0x000A 2 bytes Unknown[unsure]

PBUF

Which log entries the player has unlocked, and which they have read.

Offset Length Field Notes
File Block
0x002C 0x0000 4 bytes Block magic word Always PBUF.
0x0030 0x0004 4 bytes Unknown Messing with it can make the save file be recognized as empty in the title screen.
0x0034 0x0008 36 bytes Unlocked entries bitfield The first 11 bits correspond to the Pikmin Logs category (blue pellet, red pellet, rock pellet, winged pellet, yellow pellet, Onion, Red Pikmin, Blue Pikmin, Yellow Pikmin, Rock Pikmin, Winged Pikmin). After that come the enemies, normal treasures, and amiibo treasures, but their order is all over the place.
0x0058 0x002C 4 bytes Read "Pikmin Logs" entries bitfield
0x005C 0x0030 36 bytes Read entries from the other categories bitfield

PARK

Offset Length Field Notes
File Block
0x0080 0x0000 4 bytes Block magic word Always PARK.

MINI

Offset Length Field Notes
File Block
0x04A4 0x0000 4 bytes Block magic word Always MINI.

CRNT

Offset Length Field Notes
File Block
0x0588 0x0000 4 bytes Block magic word Always CRNT.

GAME

Offset Length Field Notes
File Block
0x09A8 0x0000 4 bytes Block magic word Always GAME.
0x09AC 0x0004 4 bytes Unknown[unsure]
0x09B0 0x0008 4 bytes Sparklium total Values over 99999 will make the game assume it's just 99999
0x09B4 0x000C 44 bytes Unknown[unsure]

EVNT

Offset Length Field Notes
File Block
0x09E0 0x0000 4 bytes Block magic word Always EVNT.

HELP

Offset Length Field Notes
File Block
0x0AE8 0x0000 4 bytes Block magic word Always HELP.

RINF

Offset Length Field Notes
File Block
0x0BF0 0x0000 4 bytes Block magic word Always 0xRINF.

RSLT

Offset Length Field Notes
File Block
0x0C2C 0x0000 4 bytes Block magic word Always RSLT.

SPER

Offset Length Field Notes
File Block
0x0C38 0x0000 4 bytes Block magic word Always SPER.

STRE

Offset Length Field Notes
File Block
0x0C48 0x0000 4 bytes Block magic word Always STRE.

PLRP

Offset Length Field Notes
File Block
0x0C90 0x0000 4 bytes Block magic word Always PLRP.

DATE

Time at which the save was made.

Offset Length Field Notes
File Block
0x0CE0 0x0000 4 bytes Block magic word Always DATE.
0x0CE4 0x0004 4 bytes Unknown[unsure]
0x0CE8 0x0008 4 bytes Year Human format. For instance, 2019 has the actual value "2019".
0x0CEC 0x000C 1 byte Month 1 to 12.
0x0CED 0x000D 1 byte Day 1 to 31.
0x0CEE 0x000E 1 byte Hours 0 to 23.
0x0CEF 0x000F 1 byte Minutes 0 to 59.
0x0CF0 0x0010 1 byte Seconds 0 to 59.
0x0CF1 0x0011 11 bytes Unknown[unsure]

TTDS

Offset Length Field Notes
File Block
0x0CFC 0x0000 4 bytes Block magic word Always TTDS.

WMAP

Offset Length Field Notes
File Block
0x0D08 0x0000 4 bytes Block magic word Always WMAP.

SAMI

Offset Length Field Notes
File Block
0x0D80 0x0000 4 bytes Block magic word Always SAMI.

Checksum

The checksum seems to be a CRC32 of the 0xd87 bytes that follow after the checksum (i.e. just one byte short of the entire remaining data), with crc32 as implemented by e.g. zlib.

The following python code can recalculate the checksum if the data after the checksum is modified:

import struct
from zlib import crc32

filepath = "radish0.sav"  # put the script in the same folder as the save file
with open(filepath, "rb") as f:
    header = f.read(0xC)
    chksum = struct.unpack("I", f.read(4))[0]
    
    data = f.read()

calculated_chksum = crc32(data[0:0xd87])
with open(filepath, "wb") as f:
    f.write(header)
    f.write(struct.pack("I", calculated_chksum))
    f.write(data)

Tools