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