mirror of
https://github.com/raphnet/gc_n64_usb-v3
synced 2025-01-30 23:00:11 -05:00
Add command-line utilities for N64 mempak manipulation
This commit is contained in:
parent
ba6e9f9850
commit
5a7ae24bb9
33
tool/n64savetool/Makefile
Normal file
33
tool/n64savetool/Makefile
Normal 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
178
tool/n64savetool/mempak.c
Normal 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, ¬e_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, ¬e_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
28
tool/n64savetool/mempak.h
Normal 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__
|
||||||
|
|
38
tool/n64savetool/mempak_extract_note.c
Normal file
38
tool/n64savetool/mempak_extract_note.c
Normal 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;
|
||||||
|
}
|
28
tool/n64savetool/mempak_format.c
Normal file
28
tool/n64savetool/mempak_format.c
Normal 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
1254
tool/n64savetool/mempak_fs.c
Normal file
File diff suppressed because it is too large
Load Diff
80
tool/n64savetool/mempak_fs.h
Normal file
80
tool/n64savetool/mempak_fs.h
Normal 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
|
57
tool/n64savetool/mempak_ls.c
Normal file
57
tool/n64savetool/mempak_ls.c
Normal 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, ¬e_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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user