gc_n64_usb-v3/tools/mempak_fs.c

1256 lines
34 KiB
C

/**
* @file mempak.c
* @brief Mempak Filesystem Routine
* @ingroup mempak
*/
#include <string.h>
#include "mempak.h"
/**
* @defgroup mempak Mempak Filesystem Routines
* @ingroup controller
* @brief Managed mempak interface.
*
* The mempak system is a subsystem of the @ref controller. Before attempting to
* read from or write to a mempak, be sure you have initialized the controller system
* with #controller_init and verified that you have a mempak in the correct controller
* using #identify_accessory.
*
* To read and write to the mempak in an organized way compatible with official software,
* first check that the mempak is valid using #validate_mempak. If the mempack is
* invalid, it will need to be formatted using #format_mempak. Once the mempak is
* considered valid, existing notes can be enumerated using #get_mempak_entry. To
* read the data associated with a note, use #read_mempak_entry_data. To write a
* new note to the mempak, use #write_mempak_entry_data. Note that there is no append
* functionality so if a note is being updated, ensure you have deleted the old note
* first using #delete_mempak_entry. Code should be careful to check how many blocks
* are free before writing using #get_mempak_free_space.
*
* @{
*/
/**
* @name Inode values
* @{
*/
/** @brief This block is empty */
#define BLOCK_EMPTY 0x03
/** @brief This is the last block in the note */
#define BLOCK_LAST 0x01
/** @brief First valid block that can contain user data */
#define BLOCK_VALID_FIRST 0x05
/** @brief Last valid block that can contain user data */
#define BLOCK_VALID_LAST 0x7F
/** @} */
/**
* @brief Read a sector from a mempak
*
* This will read a sector from a mempak. Sectors on mempaks are always 256 bytes
* in size.
*
* @param[in] controller
* The controller (0-3) to read a sector from
* @param[in] sector
* The sector (0-127) to read from
* @param[out] sector_data
* Buffer to place 256 read bytes of data
*
* @retval 0 if reading was successful
* @retval -1 if the sector was out of bounds or sector_data was null
* @retval -2 if there was an error reading part of a sector
*/
int read_mempak_sector( mempak_structure_t *pak, int sector, uint8_t *sector_data )
{
if( sector < 0 || sector >= 128 ) { return -1; }
if( sector_data == 0 ) { return -1; }
memcpy(sector_data, pak->data + sector * MEMPAK_BLOCK_SIZE, MEMPAK_BLOCK_SIZE);
#if 0
/* Sectors are 256 bytes, a mempak reads 32 bytes at a time */
for( int i = 0; i < 8; i++ )
{
if( read_mempak_address( controller, (sector * MEMPAK_BLOCK_SIZE) + (i * 32), sector_data + (i * 32) ) )
{
/* Failed to read a block */
return -2;
}
}
#endif
return 0;
}
/**
* @brief Write a sector to a mempak
*
* This will write a sector to a mempak. Sectors on mempaks are always 256 bytes
* in size.
*
* @param[in] controller
* The controller (0-3) to write a sector to
* @param[in] sector
* The sector (0-127) to write to
* @param[out] sector_data
* Buffer containing 256 bytes of data to write to sector
*
* @retval 0 if writing was successful
* @retval -1 if the sector was out of bounds or sector_data was null
* @retval -2 if there was an error writing part of a sector
*/
int write_mempak_sector( mempak_structure_t *pak, int sector, uint8_t *sector_data )
{
if( sector < 0 || sector >= 128 ) { return -1; }
if( sector_data == 0 ) { return -1; }
memcpy(pak->data + sector * MEMPAK_BLOCK_SIZE, sector_data, 256);
#if 0
/* Sectors are 256 bytes, a mempak writes 32 bytes at a time */
for( int i = 0; i < 8; i++ )
{
if( write_mempak_address( controller, (sector * MEMPAK_BLOCK_SIZE) + (i * 32), sector_data + (i * 32) ) )
{
/* Failed to read a block */
return -2;
}
}
#endif
return 0;
}
/**
* @brief Check a mempak header for validity
*
* @param[in] sector
* A sector containing a mempak header
*
* @retval 0 if the header is valid
* @retval -1 if the header is invalid
*/
static int __validate_header( uint8_t *sector )
{
if( !sector ) { return -1; }
/* Header is first sector */
if( memcmp( &sector[0x20], &sector[0x60], 16 ) != 0 ) { return -1; }
if( memcmp( &sector[0x80], &sector[0xC0], 16 ) != 0 ) { return -1; }
if( memcmp( &sector[0x20], &sector[0x80], 16 ) != 0 ) { return -1; }
return 0;
}
/**
* @brief Calculate the checksum over a TOC sector
*
* @param[in] sector
* A sector containing a TOC
*
* @return The 8 bit checksum over the TOC
*/
static uint8_t __get_toc_checksum( uint8_t *sector )
{
uint32_t sum = 0;
/* Rudimentary checksum */
for( int i = 5; i < 128; i++ )
{
sum += sector[(i << 1) + 1];
}
return sum & 0xFF;
}
/**
* @brief Check a mempak TOC sector for validity
*
* @param[in] sector
* A sector containing a TOC
*
* @retval 0 if the TOC is valid
* @retval -1 if the TOC is invalid
*/
static int __validate_toc( uint8_t *sector )
{
/* True checksum is sum % 256 */
if( __get_toc_checksum( sector ) == sector[1] )
{
return 0;
}
else
{
return -1;
}
return -1;
}
/**
* @brief Convert a N64 mempak character to ASCII
*
* @param[in] c
* A character read from a mempak entry title
*
* @return ASCII equivalent of character read
*/
static char __n64_to_ascii( char c )
{
/* Miscelaneous chart */
switch( c )
{
case 0x00:
return 0;
case 0x0F:
return ' ';
case 0x34:
return '!';
case 0x35:
return '\"';
case 0x36:
return '#';
case 0x37:
return '`';
case 0x38:
return '*';
case 0x39:
return '+';
case 0x3A:
return ',';
case 0x3B:
return '-';
case 0x3C:
return '.';
case 0x3D:
return '/';
case 0x3E:
return ':';
case 0x3F:
return '=';
case 0x40:
return '?';
case 0x41:
return '@';
}
/* Numbers */
if( c >= 0x10 && c <= 0x19 )
{
return '0' + (c - 0x10);
}
/* Uppercase ASCII */
if( c >= 0x1A && c <= 0x33 )
{
return 'A' + (c - 0x1A);
}
/* Default to space for unprintables */
return ' ';
}
/**
* @brief Convert an ASCII character to a N64 mempak character
*
* If the character passed in is one that the N64 mempak doesn't support, this
* function will default to a space.
*
* @param[in] c
* An ASCII character
*
* @return A N64 mempak character equivalent to the ASCII character passed in
*/
static char __ascii_to_n64( char c )
{
/* Miscelaneous chart */
switch( c )
{
case 0:
return 0x00;
case ' ':
return 0x0F;
case '!':
return 0x34;
case '\"':
return 0x35;
case '#':
return 0x36;
case '`':
return 0x37;
case '*':
return 0x38;
case '+':
return 0x39;
case ',':
return 0x3A;
case '-':
return 0x3B;
case '.':
return 0x3C;
case '/':
return 0x3D;
case ':':
return 0x3E;
case '=':
return 0x3F;
case '?':
return 0x40;
case '@':
return 0x41;
}
/* Numbers */
if( c >= '0' && c <= '9' )
{
return 0x10 + (c - '0');
}
/* Uppercase ASCII */
if( c >= 'A' && c <= 'Z' )
{
return 0x1A + (c - 'A');
}
/* Default to space for unprintables */
return 0x0F;
}
/**
* @brief Check a region read from a mempak entry for validity
*
* @param[in] region
* A region read from a mempak entry
*
* @retval 0 if the region is valid
* @retval -1 if the region is invalid
*/
static int __validate_region( uint8_t region )
{
switch( region )
{
case 0x00:
case 0x37:
case 0x41:
case 0x44:
case 0x45:
case 0x46:
case 0x49:
case 0x4A:
case 0x50:
case 0x53:
case 0x55:
case 0x58:
case 0x59:
/* Acceptible region */
return 0;
}
/* Invalid region */
return -3;
}
/**
* @brief Parse a note structure from a TOC
*
* Given a note block read from a mempak TOC, parse and return a structure
* representing the data.
*
* @param[in] tnote
* 32 bytes read from a mempak TOC
* @param[out] note
* Parsed note structure
*
* @retval 0 note was parsed properly
* @retval -1 parameters were invalid
* @retval -2 note inode out of bounds
* @retval -3 note region invalid
*/
//static int __read_note( uint8_t *tnote, entry_structure_t *note )
int mempak_parse_entry( const uint8_t *tnote, entry_structure_t *note )
{
if( !tnote || !note ) { return -1; }
/* Easy stuff */
note->vendor = (tnote[0] << 16) | (tnote[1] << 8) | (tnote[2]);
note->region = tnote[3];
memcpy(note->raw_data, tnote, 32);
/* Important stuff */
note->game_id = (tnote[4] << 8) | (tnote[5]);
note->inode = (tnote[6] << 8) | (tnote[7]);
/* Don't know number of blocks */
note->blocks = 0;
note->valid = 0;
note->entry_id = 255;
/* Translate n64 to ascii */
memset( note->name, 0, sizeof( note->name ) );
for( int i = 0; i < 16; i++ )
{
note->name[i] = __n64_to_ascii( tnote[0x10 + i] );
}
/* Find the last position */
for( int i = 0; i < 17; i++ )
{
if( note->name[i] == 0 )
{
/* Here it is! */
note->name[i] = '.';
note->name[i+1] = __n64_to_ascii( tnote[0xC] );
break;
}
}
/* Validate entries */
if( note->inode < BLOCK_VALID_FIRST || note->inode > BLOCK_VALID_LAST )
{
/* Invalid inode */
return -2;
}
if( __validate_region( note->region ) )
{
/* Invalid region */
return -3;
}
/* Checks out */
note->valid = 1;
return 0;
}
/**
* @brief Create a note structure for a mempak TOC
*
* Given a valid note structure, format it for writing to a mempak TOC
*
* @param[in] note
* Valid note structure to convert
* @param[out] out_note
* 32 bytes ready to be written to a mempak TOC
*
* @retval 0 if the note was converted properly
* @retval -1 if the parameters were invalid
*/
static int __write_note( entry_structure_t *note, uint8_t *out_note )
{
char tname[19];
if( !out_note || !note ) { return -1; }
/* Start with baseline */
memset( out_note, 0, 32 );
/* Easy stuff */
out_note[0] = (note->vendor >> 16) & 0xFF;
out_note[1] = (note->vendor >> 8) & 0xFF;
out_note[2] = note->vendor & 0xFF;
out_note[3] = note->region;
/* Important stuff */
out_note[4] = (note->game_id >> 8) & 0xFF;
out_note[5] = note->game_id & 0xFF;
out_note[6] = (note->inode >> 8) & 0xFF;
out_note[7] = note->inode & 0xFF;
/* Fields that generally stay the same on official saves */
out_note[8] = 0x02;
out_note[9] = 0x03;
/* Translate ascii to n64 */
memcpy( tname, note->name, sizeof( note->name ) );
for( int i = 18; i >= 0; i-- )
{
if( tname[i] == '.' )
{
/* Found extension */
out_note[0xC] = __ascii_to_n64( tname[i+1] );
/* Erase these as they have been taken care of */
tname[i] = 0;
tname[i+1] = 0;
}
}
for( int i = 0; i < 16; i++ )
{
out_note[0x10 + i] = __ascii_to_n64( tname[i] );
}
return 0;
}
/**
* @brief Return number of pages a note occupies
*
* Given a starting inode and a TOC sector, walk the linked list for a note
* and return the number of pages/blocks/sectors a note occupies.
*
* @param[in] sector
* A TOC sector
* @param[in] inode
* A starting inode
*
* @retval -2 The file contained free blocks
* @retval -3 The filesystem was invalid
* @return The number of blocks in a note
*/
static int __get_num_pages( uint8_t *sector, int inode )
{
if( inode < BLOCK_VALID_FIRST || inode > BLOCK_VALID_LAST ) { return -1; }
int tally = 0;
int last = inode;
int rcount = 0;
/* If we go over this, something is wrong */
while( rcount < 123 )
{
switch( sector[(last << 1) + 1] )
{
case BLOCK_LAST:
/* Last block */
return tally + 1;
case BLOCK_EMPTY:
/* Error, can't have free blocks! */
return -2;
default:
last = sector[(last << 1) + 1];
tally++;
/* Failed to point to valid next block */
if( last < BLOCK_VALID_FIRST || last > BLOCK_VALID_LAST ) { return -3; }
break;
}
rcount++;
}
/* Invalid filesystem */
return -3;
}
/**
* @brief Get number of free blocks on a mempak
*
* @param[in] sector
* A valid TOC block to examine
*
* @return The number of free blocks
*/
static int __get_free_space( uint8_t *sector )
{
int space = 0;
for( int i = BLOCK_VALID_FIRST; i <= BLOCK_VALID_LAST; i++ )
{
if( sector[(i << 1) + 1] == BLOCK_EMPTY )
{
space++;
}
}
return space;
}
/**
* @brief Get the inode of the n'th block in a note
*
* @param[in] sector
* A valid TOC sector
* @param[in] inode
* The starting inode of the note
* @param[in] block
* The block offset (starting from 0) to retrieve
*
* @retval -2 if there were free blocks in the file
* @retval -3 if the filesystem was invalid
* @return The inode of the n'th block
*/
static int __get_note_block( uint8_t *sector, int inode, int block )
{
if( inode < BLOCK_VALID_FIRST || inode > BLOCK_VALID_LAST ) { return -1; }
if( block < 0 || block > 123 ) { return -1; }
int tally = block + 1;
int last = inode;
/* Only going through until we hit the requested node */
while( tally > 0 )
{
/* Count down to zero, when zero is hit, this is the node we want */
tally--;
if( !tally )
{
return last;
}
else
{
switch( sector[(last << 1) + 1] )
{
case BLOCK_LAST:
/* Last block, couldn't find block number */
return -2;
case BLOCK_EMPTY:
/* Error, can't have free blocks! */
return -2;
default:
last = sector[(last << 1) + 1];
/* Failed to point to valid next block */
if( last < 5 || last >= 128 ) { return -3; }
break;
}
}
}
/* Invalid filesystem */
return -3;
}
/**
* @brief Retrieve the sector number of the first valid TOC found
*
* @param[in] controller
* The controller (0-3) to inspect for a valid TOC
*
* @retval -2 the mempak was not inserted or was bad
* @retval -3 the mempak was unformatted or the header was invalid
* @retval 1 the first sector has a valid TOC
* @retval 2 the second sector has a valid TOC
*/
static int __get_valid_toc( mempak_structure_t *mpk )
{
/* We will need only one sector at a time */
uint8_t data[MEMPAK_BLOCK_SIZE];
/* First check to see that the header block is valid */
if( read_mempak_sector( mpk, 0, data ) )
{
/* Couldn't read header */
return -2;
}
if( __validate_header( data ) )
{
/* Header is invalid or unformatted */
return -3;
}
/* Try to read the first TOC */
if( read_mempak_sector( mpk, 1, data ) )
{
/* Couldn't read header */
return -2;
}
if( __validate_toc( data ) )
{
/* First TOC is bad. Maybe the second works? */
if( read_mempak_sector( mpk, 2, data ) )
{
/* Couldn't read header */
return -2;
}
if( __validate_toc( data ) )
{
/* Second TOC is bad, nothing good on this memcard */
return -3;
}
else
{
/* Found a good TOC! */
return 2;
}
}
else
{
/* Found a good TOC! */
return 1;
}
}
/**
* @brief Return whether a mempak is valid
*
* This function will return whether the mempak in a particular controller
* is formatted and valid.
*
* @param[in] controller
* The controller (0-3) to validate
*
* @retval 0 if the mempak is valid and ready to be used
* @retval -2 if the mempak is not present or couldn't be read
* @retval -3 if the mempak is bad or unformatted
*/
int validate_mempak( mempak_structure_t *mpk )
{
int toc = __get_valid_toc( mpk );
if( toc == 1 || toc == 2 )
{
/* Found a valid TOC */
return 0;
}
else
{
/* Pass on return code */
return toc;
}
}
/**
* @brief Read an entry on a mempak
*
* Given an entry index (0-15), return the entry as found on the mempak. If
* the entry is blank or invalid, the valid flag is cleared.
*
* @param[in] controller
* The controller (0-3) from which the entry should be read
* @param[in] entry
* The entry index (0-15) to read
* @param[out] entry_data
* Structure containing information on the entry
*
* @retval 0 if the entry was read successfully
* @retval -1 if the entry is out of bounds or entry_data is null
* @retval -2 if the mempak is bad or not present
*/
int get_mempak_entry( mempak_structure_t *mpk, int entry, entry_structure_t *entry_data )
{
uint8_t data[MEMPAK_BLOCK_SIZE];
int toc;
if( entry < 0 || entry > 15 ) { return -1; }
if( entry_data == 0 ) { return -1; }
/* Make sure mempak is valid */
if( (toc = __get_valid_toc( mpk )) <= 0 )
{
/* Bad mempak or was removed, return */
return -2;
}
/* Entries are spread across two sectors, but we can luckly grab just one
with a single mempak read */
memcpy(data, mpk->data + (3 * MEMPAK_BLOCK_SIZE) + (entry * 32), 32);
#if 0
if( read_mempak_address( mpk, (3 * MEMPAK_BLOCK_SIZE) + (entry * 32), data ) )
{
/* Couldn't read note database */
return -2;
}
#endif
if( mempak_parse_entry( data, entry_data ) )
{
/* Note is most likely empty, don't bother getting length */
return 0;
}
/* Grab the TOC sector */
if( read_mempak_sector( mpk, toc, data ) )
{
/* Couldn't read TOC */
return -2;
}
/* Get the length of the entry */
int blocks = __get_num_pages( data, entry_data->inode );
if( blocks > 0 )
{
/* Valid entry */
entry_data->blocks = blocks;
entry_data->entry_id = entry;
return 0;
}
else
{
/* Invalid TOC */
entry_data->valid = 0;
return 0;
}
}
/**
* @brief Return the number of free blocks on a mempak
*
* Note that a block is identical in size to a sector. To calculate the number of
* bytes free, multiply the return of this function by #MEMPAK_BLOCK_SIZE.
*
* @param[in] controller
* The controller (0-3) to read the free space from
*
* @return The number of blocks free on the memory card or a negative number on failure
*/
int get_mempak_free_space( mempak_structure_t *mpk )
{
uint8_t data[MEMPAK_BLOCK_SIZE];
int toc;
/* Make sure mempak is valid */
if( (toc = __get_valid_toc( mpk )) <= 0 )
{
/* Bad mempak or was removed, return */
return -2;
}
/* Grab the valid TOC to get free space */
if( read_mempak_sector( mpk, toc, data ) )
{
/* Couldn't read TOC */
return -2;
}
return __get_free_space( data );
}
/**
* @brief Format a mempak
*
* Formats a mempak. Should only be done to wipe a mempak or to initialize
* the filesystem in case of a blank or corrupt mempak.
*
* @param[in] controller
* The controller (0-3) to format the mempak on
*
* @retval 0 if the mempak was formatted successfully
* @retval -2 if the mempak was not present or couldn't be formatted
*/
int format_mempak( mempak_structure_t *mpk )
{
/* Many mempak dumps exist online for users of emulated games to get
saves that have all unlocks. Every beginning sector on all these
was the same, so this is the data I use to initialize the first
sector */
uint8_t sector[MEMPAK_BLOCK_SIZE] = { 0x81,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
0xff,0xff,0xff,0xff,0x05,0x1a,0x5f,0x13,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0x01,0xff,0x66,0x25,0x99,0xcd,
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,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0x05,0x1a,0x5f,0x13,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0x01,0xff,0x66,0x25,0x99,0xcd,
0xff,0xff,0xff,0xff,0x05,0x1a,0x5f,0x13,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0x01,0xff,0x66,0x25,0x99,0xcd,
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,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0x05,0x1a,0x5f,0x13,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0x01,0xff,0x66,0x25,0x99,0xcd,
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,0x00,0x00,0x00,0x00 };
if( write_mempak_sector( mpk, 0, sector ) )
{
/* Couldn't write initial sector */
return -2;
}
/* Write out entry sectors, which can safely be zero */
memset( sector, 0x0, MEMPAK_BLOCK_SIZE );
if( write_mempak_sector( mpk, 3, sector ) ||
write_mempak_sector( mpk, 4, sector ) )
{
/* Couldn't write entry sectors */
return -2;
}
/* Go through, insert 'empty sector' marking on all entries */
for( int i = 0; i < 128; i++ )
{
sector[(i << 1) + 1] = BLOCK_EMPTY;
}
/* Fix the checksum */
sector[1] = __get_toc_checksum( sector );
/* Write out */
if( write_mempak_sector( mpk, 1, sector ) ||
write_mempak_sector( mpk, 2, sector ) )
{
/* Couldn't write TOC sectors */
return -2;
}
return 0;
}
/**
* @brief Read the data associated with an entry on a mempak
*
* Given a valid mempak entry fetched by get_mempak_entry, retrieves the contents
* of the entry. The calling function must ensure that enough room is available in
* the passed in buffer for the entire entry. The entry structure itself contains
* the number of blocks used to store the data which can be multiplied by
* #MEMPAK_BLOCK_SIZE to calculate the size of the buffer needed.
*
* @param[in] controller
* The controller (0-3) to read the entry data from
* @param[in] entry
* The entry structure associated with the data to be read. An entry
* structure can be fetched based on index using #get_mempak_entry
* @param[out] data
* The data associated with an entry
*
* @retval 0 if the entry was successfully read
* @retval -1 if input parameters were out of bounds or the entry was corrupted somehow
* @retval -2 if the mempak was not present or bad
* @retval -3 if the data couldn't be read
*/
int read_mempak_entry_data( mempak_structure_t *mpk, entry_structure_t *entry, uint8_t *data )
{
int toc;
uint8_t tocdata[MEMPAK_BLOCK_SIZE];
/* Some serious sanity checking */
if( entry == 0 || data == 0 ) { return -1; }
if( entry->valid == 0 ) { return -1; }
if( entry->blocks == 0 || entry->blocks > 123 ) { return -1; }
if( entry->inode < BLOCK_VALID_FIRST || entry->inode > BLOCK_VALID_LAST ) { return -1; }
/* Grab the TOC sector so we can get to the individual blocks the data comprises of */
if( (toc = __get_valid_toc( mpk )) <= 0 )
{
/* Bad mempak or was removed, return */
return -2;
}
/* Grab the valid TOC to get free space */
if( read_mempak_sector( mpk, toc, tocdata ) )
{
/* Couldn't read TOC */
return -2;
}
/* Now loop through blocks and grab each one */
for( int i = 0; i < entry->blocks; i++ )
{
int block = __get_note_block( tocdata, entry->inode, i );
if( read_mempak_sector( mpk, block, data + (i * MEMPAK_BLOCK_SIZE) ) )
{
/* Couldn't read a sector */
return -3;
}
}
/* Fetched all blocks successfully */
return 0;
}
/**
* @brief Write associated data to a mempak entry
*
* Given a mempak entry structure with a valid region, name and block count, writes the
* entry and associated data to the mempak. This function will not overwrite any existing
* user data. To update an existing entry, use #delete_mempak_entry followed by
* #write_mempak_entry_data with the same entry structure.
*
* @param[in] controller
* The controller (0-3) to write the entry and data to
* @param[in] entry
* The entry structure containing a region, name and block count
* @param[in] data
* The associated data to write to to the created entry
*
* @retval 0 if the entry was created and written successfully
* @retval -1 if the parameters were invalid or the note has no length
* @retval -2 if the mempak wasn't present or was bad
* @retval -3 if there was an error writing to the mempak
* @retval -4 if there wasn't enough space to store the note
* @retval -5 if there is no room in the TOC to add a new entry
*/
int write_mempak_entry_data( mempak_structure_t *mpk, entry_structure_t *entry, uint8_t *data )
{
uint8_t sector[MEMPAK_BLOCK_SIZE];
uint8_t tmp_data[32];
int toc;
/* Sanity checking on input data */
if( !entry || !data ) { return -1; }
if( entry->blocks < 1 ) { return -1; }
if( __validate_region( entry->region ) ) { return -1; }
if( strlen( entry->name ) == 0 ) { return -1; }
/* Grab valid TOC */
if( (toc = __get_valid_toc( mpk )) <= 0 )
{
/* Bad mempak or was removed, return */
return -2;
}
/* Grab the valid TOC to get free space */
if( read_mempak_sector( mpk, toc, sector ) )
{
/* Couldn't read TOC */
return -2;
}
/* Verify that we have enough free space */
if( __get_free_space( sector ) < entry->blocks )
{
/* Not enough space for note */
return -4;
}
/* Find blocks in TOC to allocate */
int tally = entry->blocks;
uint8_t last = BLOCK_LAST;
for( int i = BLOCK_VALID_FIRST; i <= BLOCK_VALID_LAST; i++ )
{
if( sector[(i << 1) + 1] == BLOCK_EMPTY )
{
/* We can use this block */
tally--;
/* Point this towards the next block */
sector[(i << 1) + 1] = last;
/* This block is now the last block */
last = i;
}
/* If we found all blocks, we can exit out early */
if( !tally ) { break; }
}
if( tally > 0 )
{
/* Even though we had free space, couldn't get all blocks? */
return -4;
}
else
{
/* Last now contains our inode */
entry->inode = last;
entry->valid = 0;
entry->vendor = 0;
/* A value observed in most games */
entry->game_id = 0x4535;
}
/* Loop through allocated blocks and write data to sectors */
for( int i = 0; i < entry->blocks; i++ )
{
int block = __get_note_block( sector, entry->inode, i );
if( write_mempak_sector( mpk, block, data + (i * MEMPAK_BLOCK_SIZE) ) )
{
/* Couldn't write a sector */
return -3;
}
}
/* Find an empty entry to store to */
for( int i = 0; i < 16; i++ )
{
entry_structure_t tmp_entry;
memcpy(tmp_data, mpk->data + (3 * MEMPAK_BLOCK_SIZE) + (i * 32), 32);
#if 0
if( read_mempak_address( mpk, (3 * MEMPAK_BLOCK_SIZE) + (i * 32), tmp_data ) )
{
/* Couldn't read note database */
return -2;
}
#endif
/* See if we can write to this note */
mempak_parse_entry( tmp_data, &tmp_entry );
if( tmp_entry.valid == 0 )
{
entry->entry_id = i;
entry->valid = 1;
break;
}
}
if( entry->valid == 0 )
{
/* Couldn't find an entry */
return -5;
}
/* Update CRC on newly updated TOC */
sector[1] = __get_toc_checksum( sector );
/* Write back to alternate TOC first before erasing the known valid one */
if( write_mempak_sector( mpk, ( toc == 1 ) ? 2 : 1, sector ) )
{
/* Failed to write alternate TOC */
return -2;
}
/* Write back to good TOC now that alternate is updated */
if( write_mempak_sector( mpk, toc, sector ) )
{
/* Failed to write alternate TOC */
return -2;
}
/* Convert entry structure to proper entry data */
__write_note( entry, tmp_data );
/* Store entry to empty slot on mempak */
memcpy(mpk->data + (3 * MEMPAK_BLOCK_SIZE) + (entry->entry_id * 32), tmp_data, 32);
#if 0
if( write_mempak_address( mpk, (3 * MEMPAK_BLOCK_SIZE) + (entry->entry_id * 32), tmp_data ) )
{
/* Couldn't update note database */
return -2;
}
#endif
return 0;
}
/**
* @brief Delete a mempak entry and associated data
*
* Given a valid mempak entry fetched by #get_mempak_entry, removes the entry and frees
* all associated blocks.
*
* @param[in] controller
* The controller (0-3) to delete the note from
* @param[in] entry
* The entry structure that is to be deleted from the mempak
*
* @retval 0 if the entry was deleted successfully
* @retval -1 if the entry was invalid
* @retval -2 if the mempak was bad or not present
*/
int delete_mempak_entry( mempak_structure_t *mpk, entry_structure_t *entry )
{
entry_structure_t tmp_entry;
uint8_t data[MEMPAK_BLOCK_SIZE];
int toc;
/* Some serious sanity checking */
if( entry == 0 ) { return -1; }
if( entry->valid == 0 ) { return -1; }
if( entry->entry_id > 15 ) { return -1; }
if( entry->inode < BLOCK_VALID_FIRST || entry->inode > BLOCK_VALID_LAST ) { return -1; }
/* Ensure that the entry passed in matches what's on the mempak */
memcpy(data, mpk->data + 3 * MEMPAK_BLOCK_SIZE + entry->entry_id * 32, 32);
#if 0
if( read_mempak_address( mpk, (3 * MEMPAK_BLOCK_SIZE) + (entry->entry_id * 32), data ) )
{
/* Couldn't read note database */
return -2;
}
#endif
if( mempak_parse_entry( data, &tmp_entry ) )
{
/* Couldn't parse entry, can't be valid */
return -2;
}
if( tmp_entry.inode != entry->inode )
{
/* inodes don't match, not the same entry! */
return -2;
}
/* The entry matches, so blank it */
memset( mpk->data + (3 * MEMPAK_BLOCK_SIZE) + (entry->entry_id * 32), 0, 32 );
#if 0
memset( data, 0, 32 );
if( write_mempak_address( mpk, (3 * MEMPAK_BLOCK_SIZE) + (entry->entry_id * 32), data ) )
{
/* Couldn't update note database */
return -2;
}
#endif
/* Grab the first valid TOC entry */
if( (toc = __get_valid_toc( mpk )) <= 0 )
{
/* Bad mempak or was removed, return */
return -2;
}
/* Grab the valid TOC to erase sectors */
if( read_mempak_sector( mpk, toc, data ) )
{
/* Couldn't read TOC */
return -2;
}
/* Erase all blocks out of the TOC */
int tally = 0;
int done = 0;
int last = entry->inode;
/* Don't want to recurse forever if filesystem is corrupt */
while( tally <= 123 && !done )
{
tally++;
switch( data[(last << 1) + 1] )
{
case BLOCK_LAST:
/* Last block, we are done */
data[(last << 1) + 1] = BLOCK_EMPTY;
done = 1;
break;
case BLOCK_EMPTY:
/* Error, can't have free blocks! */
return -2;
default:
{
int next = data[(last << 1) + 1];
data[(last << 1) + 1] = BLOCK_EMPTY;
last = next;
/* Failed to point to valid next block */
if( last < 5 || last >= 128 ) { return -3; }
break;
}
}
}
/* Update CRC on newly updated TOC */
data[1] = __get_toc_checksum( data );
/* Write back to alternate TOC first before erasing the known valid one */
if( write_mempak_sector( mpk, ( toc == 1 ) ? 2 : 1, data ) )
{
/* Failed to write alternate TOC */
return -2;
}
/* Write back to good TOC now that alternate is updated */
if( write_mempak_sector( mpk, toc, data ) )
{
/* Failed to write alternate TOC */
return -2;
}
return 0;
}
/** @} */ /* controller */