Save data management on the Controller Pak is facilitated through a simple proprietary filesystem. Most games that support this Pak have a menu that can be accessed by holding START while turning on the N64. In all official and unofficial Controller Paks, there are 32,768 bytes of SRAM available, which is split into 256-byte sectors referred to as "pages". This allows for a maximum capacity of 128 pages, 5 of which are reserved for the filesystem data, and the remaining 123 for user software. A software can create one or more save files, up to a total of 16 files, referred to as "notes".
Filesystem data format
|ID Sector - critical sanity and identification
|Index Table - allocation of user data sectors
|Backup Index Table - exact copy of Index Table
|Note Table - allocation of Note entries
|Note data sectors
Page 0: ID Sector
The ID sector primarily houses basic identifying and system information for the file system and the specific Controller Pak.
|ID sector structure (256 bytes)
|ID Block (Primary)
|ID Block (Backup 1)
|ID Block (Backup 2)
|ID Block (Backup 3)
The first 32-bytes of the ID Sector is known as the Label. It was never officially used nor were its specifications ever supplied to developers. It is implied to be a sort of text-based label or tag. In practice, only garbage data is ever written here during a repair operation, or as a side-effect from example code being copy-pasted to produce the numbers 0 to 32.
The 32-byte ID block provides system information such as the serial number, device ID, the capacity of the media, and two protective checksums. In total, four identical copies of the ID block are stored in this sector in case one or more become corrupted.
|ID Block structure (32 bytes)
|unused (always 0)
|Device ID (always 1)
|Bank size (always 1)
|unused (always 0)
The serial number uniquely identifies the particular controller pak. It can be filled with random bytes or any other value that is unique across all extant controller paks.
The device ID field identifies the type of device. This is always set to 1 indicating a controller pak.
The bank size describes the capacity of the controller pak. This is always set to 1 to indicate 256 kilobits which is the only size ever produced.
The first checksum is a 16-bit big endian word computed by summing the first fourteen 16-bit words in the structure. That is every word in the block that is not either of the checksums.
The second checksum is also a 16-bit word computed by subtracting the first checksum from the value 0xFFF2.
Page 1+2: Index Table
The Index Table is used as the allocation table for the file system utilizing a sector chaining strategy similar to FAT16. The table consists of one full page representing an array of 128 two-byte words, known as I-Nodes, where the index of the word in the array corresponds to the page with the same index number on the controller pak.
|Index Table structure (256 bytes)
|unused (always 0)
|unused (always 0)
|I-Nodes for pages 5-127
The first 5 I-Nodes are reserved because the first 5 pages on the pak are reserved and never part of any note.
All of the bytes for the five reserved I-Nodes are set to zero except for the lower-byte of the I-Node at index 0 which stores the (8-bit) checksum for the entire table. The checksum is computed by summing all the bytes for all of the 123 remaining I-Nodes (i.e. starting at byte offset 0x0A through the end of the page).
Otherwise, the I-Node contains a value indicating that it is unallocated (not used by a note), a value indicating it is the last in a chain (the last page at the end of a note), or the index of the next page in a multiple page chain. The first page for a note is always stored in the note entry data structure and the entire note can then be found by walking the chain in the index table until the end value is encountered.
|Indicates this is the last node in sequence.
|Indicates this node is unallocated free space.
|Indicates the next node in the sequence
Two identical copies of the Index Table are stored in case of data corruption. The primary is stored in page 1 and the backup is stored in page 2.
Page 3+4: Note Table
The Note Table is simply a list of 16 Note entries. Each entry consists of 32 bytes and identifies the start page, game code and other data. The positions in the list correspond to the slot numbers displayed in the memory card manager. Unused entries are zeroed out.
|Note entry format (32 bytes)
|Game Code - ASCII, e.g. NSME
|Publisher Code - ASCII, e.g. 01
|Start page - Index number of the first page of the note
|Status - Bit field of flags
|Reserved - Unused, contains garbage data?
|"Data sum" - Always zero. Unimplemented checksum?
|File extension - Text using custom encoding
|File name - Text using custom encoding
The game code is a short ASCII encoded string that identifies the specific game that created and owns the note. This is the same code as the media format code that appears in the ROM header for each game. These codes are specific not only to the particular game but also to the type of media (e.g. cartridge) and geographic region.
The publisher code are unique codes that were assigned by Nintendo to their licensed partners. The publisher codes and are also typically ASCII characters but do not appear in the ROM header and so are more difficult to catalogue.
The status field contains bit flags. The flag 0x02 should always be set.
The file extension is optional and uses the same custom character encoding used by the file name. Four characters are reserved but the stock controller pak manager used by most games only displays the first character. Some games, such as Ogre Battle 64, which implement their own manager do display the extended characters when present. Unused characters should be padded with NUL.
The file name appears as the primary text string describing the contents of the note in the slot when the note is displayed in the controller pak manager of any game. The note text is encoded using a custom character encoding specific to the controller pak file system that includes punctuation plus basic Latin and Japanese characters. Unused characters should be padded with NUL.
|Note name text encoding