oot/tools/makeromfs.c
2020-03-17 00:31:30 -04:00

343 lines
7.8 KiB
C

#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "n64chksum.h"
#include "util.h"
#define ROM_SIZE 0x02000000
enum InputObjType
{
OBJ_NULL,
OBJ_FILE,
OBJ_TABLE,
};
struct InputFile
{
enum InputObjType type;
const char *name;
uint8_t *data;
size_t size;
unsigned int valign;
uint32_t virtStart;
uint32_t virtEnd;
uint32_t physStart;
uint32_t physEnd;
};
static struct InputFile *g_inputFiles = NULL;
static int g_inputFilesCount = 0;
static unsigned int round_up(unsigned int num, unsigned int multiple)
{
num += multiple - 1;
return num / multiple * multiple;
}
static bool is_yaz0_header(const uint8_t *data)
{
return data[0] == 'Y'
&& data[1] == 'a'
&& data[2] == 'z'
&& data[3] == '0';
}
static void compute_offsets(void)
{
size_t physOffset = 0;
size_t virtOffset = 0;
int i;
for (i = 0; i < g_inputFilesCount; i++)
{
bool compressed = false;
if (g_inputFiles[i].type == OBJ_FILE)
{
if (is_yaz0_header(g_inputFiles[i].data))
compressed = true;
}
else if (g_inputFiles[i].type == OBJ_TABLE)
{
g_inputFiles[i].size = g_inputFilesCount * 16;
}
virtOffset = round_up(virtOffset, g_inputFiles[i].valign);
if (g_inputFiles[i].type == OBJ_NULL)
{
g_inputFiles[i].virtStart = 0;
g_inputFiles[i].virtEnd = 0;
g_inputFiles[i].physStart = 0;
g_inputFiles[i].physEnd = 0;
}
else if (compressed)
{
size_t compSize = round_up(g_inputFiles[i].size, 16);
size_t uncompSize = util_read_uint32_be(g_inputFiles[i].data + 4);
g_inputFiles[i].virtStart = virtOffset;
g_inputFiles[i].virtEnd = virtOffset + uncompSize;
g_inputFiles[i].physStart = physOffset;
g_inputFiles[i].physEnd = physOffset + compSize;
physOffset += compSize;
virtOffset += uncompSize;
}
else
{
size_t size = g_inputFiles[i].size;
g_inputFiles[i].virtStart = virtOffset;
g_inputFiles[i].virtEnd = virtOffset + size;
g_inputFiles[i].physStart = physOffset;
g_inputFiles[i].physEnd = 0;
physOffset += size;
virtOffset += size;
}
}
}
static void build_rom(const char *filename)
{
uint8_t *romData = calloc(ROM_SIZE, 1);
size_t pos = 0;
int i;
int j;
uint32_t chksum[2];
FILE *outFile;
for (i = 0; i < g_inputFilesCount; i++)
{
size_t size = g_inputFiles[i].size;
if (pos + round_up(size, 16) > ROM_SIZE)
util_fatal_error("size exceeds max ROM size of 32 KiB");
assert(pos % 16 == 0);
switch (g_inputFiles[i].type)
{
case OBJ_FILE:
// write file data
memcpy(romData + pos, g_inputFiles[i].data, size);
pos += round_up(size, 16);
free(g_inputFiles[i].data);
break;
case OBJ_TABLE:
for (j = 0; j < g_inputFilesCount; j++)
{
util_write_uint32_be(romData + pos + 0, g_inputFiles[j].virtStart);
util_write_uint32_be(romData + pos + 4, g_inputFiles[j].virtEnd);
util_write_uint32_be(romData + pos + 8, g_inputFiles[j].physStart);
util_write_uint32_be(romData + pos + 12, g_inputFiles[j].physEnd);
pos += 16;
}
break;
case OBJ_NULL:
break;
}
}
// Pad the rest of the ROM
while (pos < ROM_SIZE)
{
// This is such a weird thing to pad with. Whatever, Nintendo.
romData[pos] = pos & 0xFF;
pos++;
}
// calculate checksum
n64chksum_calculate(romData, 6105, chksum);
util_write_uint32_be(romData + 0x10, chksum[0]);
util_write_uint32_be(romData + 0x14, chksum[1]);
// write file
outFile = fopen(filename, "wb");
if (outFile == NULL)
util_fatal_error("failed to open file '%s' for writing", filename);
fwrite(romData, ROM_SIZE, 1, outFile);
fclose(outFile);
free(romData);
}
static struct InputFile *new_file(void)
{
int index = g_inputFilesCount;
g_inputFilesCount++;
g_inputFiles = realloc(g_inputFiles, g_inputFilesCount * sizeof(*g_inputFiles));
g_inputFiles[index].valign = 1;
return &g_inputFiles[index];
}
// null terminates the current token and returns a pointer to the next token
static char *token_split(char *str)
{
while (!isspace(*str))
{
if (*str == 0)
return str; // end of string
str++;
}
*str = 0; // terminate token
str++;
// skip remaining whitespace
while (isspace(*str))
str++;
return str;
}
// null terminates the current line and returns a pointer to the next line
static char *line_split(char *str)
{
while (*str != '\n')
{
if (*str == 0)
return str; // end of string
str++;
}
*str = 0; // terminate line
return str + 1;
}
static void parse_line(char *line, int lineNum)
{
char *token = line;
int i = 0;
char *filename = NULL;
enum InputObjType type = -1;
int valign = 1;
struct InputFile *file;
// iterate through each token
while (token[0] != 0)
{
char *nextToken = token_split(token);
if (token[0] == '#') // comment - ignore rest of line
return;
switch (i)
{
case 0:
if (strcmp(token, "file") == 0)
type = OBJ_FILE;
else if (strcmp(token, "filetable") == 0)
type = OBJ_TABLE;
else if (strcmp(token, "null") == 0)
type = OBJ_NULL;
else
util_fatal_error("unknown object type '%s' on line %i", token, lineNum);
break;
case 1:
filename = token;
break;
case 2:
{
int n;
if (sscanf(token, "align(%i)", &n) == 1)
valign = n;
else
goto junk;
}
break;
default:
junk:
util_fatal_error("junk '%s' on line %i", token, lineNum);
break;
}
token = nextToken;
i++;
}
if (i == 0) // empty line
return;
file = new_file();
file->valign = valign;
switch (type)
{
case OBJ_FILE:
if (filename == NULL)
util_fatal_error("no filename specified on line %i", lineNum);
file->type = OBJ_FILE;
file->data = util_read_whole_file(filename, &file->size);
break;
case OBJ_TABLE:
file->type = OBJ_TABLE;
break;
case OBJ_NULL:
file->type = OBJ_NULL;
file->size = 0;
break;
}
}
static void parse_list(char *list)
{
char *line = list;
int lineNum = 1;
// iterate through each line
while (line[0] != 0)
{
char *nextLine = line_split(line);
parse_line(line, lineNum);
line = nextLine;
lineNum++;
}
}
static void usage(const char *execName)
{
printf("usage: %s FILE_LIST OUTPUT_FILE\n"
"where FILE_LIST is a list of files to include\n"
"and OUTPUT_FILE is the name of the output ROM\n"
"note that 'dmadata' refers to the file list itself and not an external file\n",
execName);
}
int main(int argc, char **argv)
{
char *list;
if (argc != 3)
{
puts("invalid args");
usage(argv[0]);
return 1;
}
list = util_read_whole_file(argv[1], NULL);
parse_list(list);
compute_offsets();
build_rom(argv[2]);
free(list);
return 0;
}