Add command-line utilities for N64 mempak manipulation

This commit is contained in:
Raphael Assenat 2015-10-25 01:57:10 -04:00
parent ba6e9f9850
commit 5a7ae24bb9
8 changed files with 1696 additions and 0 deletions

33
tool/n64savetool/Makefile Normal file
View File

@ -0,0 +1,33 @@
CC=gcc
LD=$(CC)
CFLAGS=-Wall -g --std=c99
LDFLAGS=-g
PREFIX=/usr/local
PROGRAMS=mempak_ls mempak_format mempak_extract_note
MEMPAKLIB_OBJS=mempak.o mempak_fs.o
.PHONY : clean install
all: $(PROGRAMS)
mempak_extract_note: mempak_extract_note.o $(MEMPAKLIB_OBJS)
$(LD) $^ $(LDFLAGS) -o $@
mempak_ls: mempak_ls.o $(MEMPAKLIB_OBJS)
$(LD) $^ $(LDFLAGS) -o $@
mempak_format: mempak_format.o $(MEMPAKLIB_OBJS)
$(LD) $^ $(LDFLAGS) -o $@
%.o: %.c %.h
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o $(PROGRAMS)
install:
@echo "Install not done yet. Sorry"

178
tool/n64savetool/mempak.c Normal file
View File

@ -0,0 +1,178 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mempak.h"
#define DEXDRIVE_DATA_OFFSET 0x1040
mempak_structure_t *mempak_new(void)
{
mempak_structure_t *mpk;
mpk = calloc(1, sizeof(mempak_structure_t));
if (!mpk) {
perror("calloc");
return NULL;
}
format_mempak(mpk);
return mpk;
}
int mempak_exportNote(mempak_structure_t *mpk, int note_id, const char *dst_filename)
{
FILE *fptr;
entry_structure_t note_header;
unsigned char databuf[0x10000];
if (!mpk)
return -1;
if (0 != get_mempak_entry(mpk, note_id, &note_header)) {
fprintf(stderr, "Error accessing note\n");
return -1;
}
if (!note_header.valid) {
fprintf(stderr, "Invaid note\n");
return -1;
}
if (0 != read_mempak_entry_data(mpk, &note_header, databuf)) {
fprintf(stderr, "Error accessing note data\n");
return -1;
}
fptr = fopen(dst_filename, "w");
if (!fptr) {
perror("fopen");
return -1;
}
/* For compatibility with bryc's javascript mempak editor[1], I set
* the inode number to 0xCAFE.
*
* [1] https://github.com/bryc/mempak
*/
note_header.raw_data[0x07] = 0xCA;
note_header.raw_data[0x08] = 0xFE;
fwrite(note_header.raw_data, 32, 1, fptr);
fwrite(databuf, MEMPAK_BLOCK_SIZE, note_header.blocks, fptr);
fclose(fptr);
return 0;
}
int mempak_saveToFile(mempak_structure_t *mpk, const char *dst_filename, unsigned char format)
{
FILE *fptr;
if (!mpk)
return -1;
fptr = fopen(dst_filename, "w");
if (!fptr) {
perror("fopen");
return -1;
}
switch(format)
{
default:
fclose(fptr);
return -1;
case MPK_SAVE_FORMAT_MPK:
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
break;
case MPK_SAVE_FORMAT_MPK4:
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
break;
case MPK_SAVE_FORMAT_N64:
// Note: This should work well for files that will
// be imported by non-official software which typically
// only look for the 123-456-STD header and then
// seek to the data.
//
// Real .N64 files contain more info. TODO: Support it
fprintf(fptr, "123-456-STD");
fseek(fptr, DEXDRIVE_DATA_OFFSET, SEEK_SET);
fwrite(mpk->data, sizeof(mpk->data), 1, fptr);
break;
}
return 0;
}
mempak_structure_t *mempak_loadFromFile(const char *filename)
{
FILE *fptr;
long file_size;
int i;
int num_images = -1;
long offset = 0;
mempak_structure_t *mpk;
mpk = calloc(1, sizeof(mempak_structure_t));
if (!mpk) {
perror("calloc");
return NULL;
}
fptr = fopen(filename, "rb");
if (!fptr) {
perror("fopen");
free(mpk);
return NULL;
}
fseek(fptr, 0, SEEK_END);
file_size = ftell(fptr);
fseek(fptr, 0, SEEK_SET);
printf("File size: %ld bytes\n", file_size);
/* Raw binary images. Those can contain more than one card's data. For
* instance, Mupen64 seems to contain four saves. (I suppose each 32kB block is
* for the virtual mempak of one controller) */
for (i=0; i<4; i++) {
if (file_size == 0x8000*i) {
num_images = i+1;
printf("MPK file Contains %d image(s)\n", num_images);
mpk->source = MPK_SRC_RAW_IMAGE;
}
}
if (num_images < 0) {
char header[11];
char *magic = "123-456-STD";
/* If the size is not a fixed multiple, it could be a .N64 file */
fread(header, 11, 1, fptr);
if (0 == memcmp(header, magic, sizeof(header))) {
printf(".N64 file detected\n");
// TODO : Extract comments and other info. from the header
offset = DEXDRIVE_DATA_OFFSET; // Thanks to N-Rage`s Dinput8 Plugin sources
mpk->source = MPK_SRC_DEX_IMAGE;
}
}
fseek(fptr, offset, SEEK_SET);
fread(mpk->data, sizeof(mpk->data), 1, fptr);
fclose(fptr);
return mpk;
}
void mempak_free(mempak_structure_t *mpk)
{
if (mpk)
free(mpk);
}

28
tool/n64savetool/mempak.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef _mempak_h__
#define _mempak_h__
#define MEMPAK_NUM_NOTES 16
#define MPK_SRC_RAW_IMAGE 0
#define MPK_SRC_DEX_IMAGE 1
typedef struct mempak_structure
{
unsigned char data[0x8000];
unsigned char source;
} mempak_structure_t;
mempak_structure_t *mempak_new(void);
mempak_structure_t *mempak_loadFromFile(const char *filename);
#define MPK_SAVE_FORMAT_MPK 0
#define MPK_SAVE_FORMAT_MPK4 1 // MPK + 3 times 32kB padding
#define MPK_SAVE_FORMAT_N64 2
int mempak_saveToFile(mempak_structure_t *mpk, const char *dst_filename, unsigned char format);
int mempak_exportNote(mempak_structure_t *mpk, int note_id, const char *dst_filename);
void mempak_free(mempak_structure_t *mpk);
#include "mempak_fs.h"
#endif // _mempak_h__

View File

@ -0,0 +1,38 @@
#include <stdio.h>
#include <stdlib.h>
#include "mempak.h"
int main(int argc, char **argv)
{
const char *infile;
const char *outfile;
int note_id;
mempak_structure_t *mpk;
if (argc < 4) {
printf("Usage: ./mempak_extract_note in_file note_id out_file\n");
printf("\n");
printf("Where:\n");
printf(" in_file The input file (full mempak image .mpk/.n64)\n");
printf(" note_id The id of the note (as shown by mempak_ls)\n");
printf(" out_file The output filename (eg: abc.note)\n");
return 1;
}
infile = argv[1];
note_id = atoi(argv[2]);
outfile = argv[3];
mpk = mempak_loadFromFile(infile);
if (!mpk) {
return 1;
}
if (mempak_exportNote(mpk, note_id, outfile)) {
fprintf(stderr, "could not export note\n");
}
mempak_free(mpk);
return 0;
}

View File

@ -0,0 +1,28 @@
#include <stdio.h>
#include "mempak.h"
int main(int argc, char **argv)
{
mempak_structure_t *mpk;
const char *outfile;
if (argc < 2) {
printf("Usage: ./mempak_ls file\n");
printf("\n");
printf("Raw files (.MPK, .BIN) and Dexdrive (.N64) formats accepted.\n");
return 1;
}
outfile = argv[1];
mpk = mempak_new();
if (!mpk) {
return 1;
}
mempak_saveToFile(mpk, outfile, MPK_SAVE_FORMAT_N64);
mempak_free(mpk);
return 0;
}

1254
tool/n64savetool/mempak_fs.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
/**
* @file mempak.h
* @brief Mempak Filesystem Routines
* @ingroup mempak
*/
#ifndef __LIBDRAGON_MEMPAK_H
#define __LIBDRAGON_MEMPAK_H
#include "mempak.h"
#include <stdint.h>
/**
* @addtogroup mempak
* @{
*/
/** @brief Size in bytes of a Mempak block */
#define MEMPAK_BLOCK_SIZE 256
/**
* @brief Structure representing a save entry in a mempak
*/
typedef struct entry_structure
{
/** @brief Vendor ID */
uint32_t vendor;
/** @brief Game ID */
uint16_t game_id;
/** @brief Inode pointer */
uint16_t inode;
/** @brief Intended region */
uint8_t region;
/** @brief Number of blocks used by this entry.
* @see MEMPAK_BLOCK_SIZE */
uint8_t blocks;
/** @brief Validity of this entry. */
uint8_t valid;
/** @brief ID of this entry */
uint8_t entry_id;
/**
* @brief Name of this entry
*
* The complete list of valid ASCII characters in a note name is:
*
* <pre>
* ABCDEFGHIJKLMNOPQRSTUVWXYZ!"#`*+,-./:=?\@
* </pre>
*
* The space character is also allowed. Any other character will be
* converted to a space before writing to the mempak.
*
* @see #__n64_to_ascii and #__ascii_to_n64
*/
char name[19];
/** @brief A copy of the raw note data (from the note table). */
unsigned char raw_data[32];
} entry_structure_t;
#ifdef __cplusplus
extern "C" {
#endif
int read_mempak_sector( mempak_structure_t *pak, int sector, uint8_t *sector_data );
int write_mempak_sector( mempak_structure_t *pak, int sector, uint8_t *sector_data );
int validate_mempak( mempak_structure_t *pak );
int get_mempak_free_space( mempak_structure_t *pak );
int get_mempak_entry( mempak_structure_t *pak, int entry, entry_structure_t *entry_data );
int format_mempak( mempak_structure_t *pak );
int read_mempak_entry_data( mempak_structure_t *pak, entry_structure_t *entry, uint8_t *data );
int write_mempak_entry_data( mempak_structure_t *pak, entry_structure_t *entry, uint8_t *data );
int delete_mempak_entry( mempak_structure_t *pak, entry_structure_t *entry );
#ifdef __cplusplus
}
#endif
/** @} */ /* mempak */
#endif

View File

@ -0,0 +1,57 @@
#include <stdio.h>
#include "mempak.h"
int main(int argc, char **argv)
{
const char *infile;
mempak_structure_t *mpk;
int note;
int res;
if (argc < 2) {
printf("Usage: ./mempak_ls file\n");
printf("\n");
printf("Raw files (.MPK, .BIN) and Dexdrive (.N64) formats accepted.\n");
return 1;
}
infile = argv[1];
mpk = mempak_loadFromFile(infile);
if (!mpk) {
return 1;
}
printf("Mempak image loaded. Image type %d\n", mpk->source);
if (0 != validate_mempak(mpk)) {
printf("Mempak invalid (not formatted or corrupted)\n");
goto done;
}
printf("Mempak content is valid\n");
for (note = 0; note<MEMPAK_NUM_NOTES; note++) {
entry_structure_t note_data;
printf("Note %d: ", note);
res = get_mempak_entry(mpk, note, &note_data);
if (res) {
printf("Error!\n");
} else {
if (note_data.valid) {
printf("%s (%d blocks) ", note_data.name, note_data.blocks);
printf("%08x ", note_data.vendor);
printf("%04x ", note_data.game_id);
printf("%02x ", note_data.region);
printf("\n");
} else {
printf("Invalid\n");
}
}
}
done:
mempak_free(mpk);
return 0;
}