You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
843 lines
23 KiB
C
843 lines
23 KiB
C
/*
|
|
n64tool V1.0, a program used to append an n64 header to an arbitrary
|
|
sized Nintendo64 binary.
|
|
Copyright (C) 2009 DragonMinded (dragonminded@dragonminded.com)
|
|
|
|
This tool is part of the Libdragon SDK.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
|
*/
|
|
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "polyfill.h"
|
|
#include "crc32.c"
|
|
|
|
typedef intptr_t ssize_t;
|
|
|
|
// Default header to use if none is specified
|
|
#include "ipl3.h"
|
|
|
|
#define ROUND_UP(n, d) ({ \
|
|
typeof(n) _n = n; typeof(d) _d = d; \
|
|
(((_n) + (_d) - 1) / (_d) * (_d)); \
|
|
})
|
|
|
|
// strlcpy() is not available on all platforms, so we provide a simple implementation
|
|
#ifndef strlcpy
|
|
size_t __strlcpy(char * restrict dst, const char * restrict src, size_t dstsize)
|
|
{
|
|
strncpy(dst, src, dstsize - 1);
|
|
dst[dstsize - 1] = '\0';
|
|
return strlen(dst);
|
|
}
|
|
#define strlcpy __strlcpy
|
|
#endif
|
|
|
|
// Minimum ROM size alignment, used by default. We currently know of two constraints:
|
|
// * 64drive firmware has a bug and can only transfer chunks of 512 bytes. Some
|
|
// tools like UNFloader and g64drive work around this bug by padding ROMs,
|
|
// but others (like the official one) don't. So it's better in general to
|
|
// pad to 512 bytes.
|
|
// * EverDrive64 also requires ROMs to be transferred in blocks of 512 bytes,
|
|
// which means that the ROM has to be padded.
|
|
// * iQue player only allows loading ROMs which are multiple of 16 KiB in size.
|
|
//
|
|
// To allow the maximum compatibility, we pad to 16 KiB by default. Users can still
|
|
// force a specific length with --size, if they need to.
|
|
// We also allow changing the padding alignment with --padding.
|
|
int pad_align = 16384;
|
|
|
|
#define WRITE_SIZE (1024 * 1024)
|
|
|
|
#define TITLE_OFFSET 0x20
|
|
#define TITLE_SIZE 20
|
|
|
|
#define CATEGORY_OFFSET 0x3B
|
|
#define REGION_OFFSET 0x3E
|
|
|
|
#define IQUE_ENTRYPOINT_OFFSET 0x8
|
|
|
|
#define STATUS_OK 0
|
|
#define STATUS_ERROR 1
|
|
#define STATUS_BADUSAGE 2
|
|
|
|
#define TOC_SIZE 1024
|
|
#define TOC_ALIGN 16
|
|
#define TOC_ENTRY_SIZE 64
|
|
#define TOC_MAX_ENTRIES ((TOC_SIZE - 16) / 64)
|
|
|
|
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
#define SWAPLONG(i) (i)
|
|
#define SWAPSHORT(i) (i)
|
|
#else
|
|
#define SWAPLONG(i) (((uint32_t)((i) & 0xFF000000) >> 24) | ((uint32_t)((i) & 0x00FF0000) >> 8) | ((uint32_t)((i) & 0x0000FF00) << 8) | ((uint32_t)((i) & 0x000000FF) << 24))
|
|
#define SWAPSHORT(i) (((uint16_t)((i) & 0xFF00) >> 8) | ((uint16_t)((i) & 0x00FF) << 8))
|
|
#endif
|
|
|
|
static const unsigned char zero[1024] = {0};
|
|
static char * tmp_output = NULL;
|
|
static uint32_t elf_loadpoint = 0xFFFFFFFF;
|
|
|
|
// The TOC cookie could be just a 32-bit random number; its goal is just to
|
|
// differentiate different ROMs, to detect if a ROM was "swapped" by a flashcart
|
|
// during runtime.
|
|
// However we don't want to use a random number as we want ROMs to be reproducible,
|
|
// so we will instead hash the various file contents to generate a fixed-but-random
|
|
// cookie.
|
|
static uint32_t toc_cookie = 0xFFFFFFFF;
|
|
|
|
struct toc_s {
|
|
char magic_version[4];
|
|
uint32_t cookie;
|
|
uint32_t toc_size;
|
|
uint16_t entry_size;
|
|
uint16_t num_entries;
|
|
struct {
|
|
uint32_t offset;
|
|
uint32_t size;
|
|
char name[TOC_ENTRY_SIZE - 8];
|
|
} files[TOC_MAX_ENTRIES];
|
|
} toc = {
|
|
.magic_version = "TOC0",
|
|
.toc_size = TOC_SIZE,
|
|
.entry_size = TOC_ENTRY_SIZE,
|
|
.num_entries = 0,
|
|
};
|
|
|
|
_Static_assert(sizeof(toc) <= TOC_SIZE, "invalid table size");
|
|
|
|
int print_usage(const char * prog_name)
|
|
{
|
|
fprintf(stderr, "Usage: %s [flags] [file-flags] <file> [[file-flags] <file> ...]\n\n", prog_name);
|
|
fprintf(stderr, "This program creates an N64 ROM from a header and a list of files,\n");
|
|
fprintf(stderr, "the first being an Nintendo 64 binary and the rest arbitrary data.\n");
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "General flags (to be used before any file):\n");
|
|
fprintf(stderr, "\t-t, --title <title> Title of ROM (max %d characters).\n", TITLE_SIZE);
|
|
fprintf(stderr, "\t-l, --size <size> Force ROM output file size to <size> (min 1 mebibyte).\n");
|
|
fprintf(stderr, "\t-P, --padding <size> Padding alignment for the final ROM (default: 16 KiB).\n");
|
|
fprintf(stderr, "\t-h, --header <file> Use <file> as IPL3 header (default: use libdragon IPL3).\n");
|
|
fprintf(stderr, "\t-o, --output <file> Save output ROM to <file>.\n");
|
|
fprintf(stderr, "\t-C, --category <cat> N64 Media Category Code (default: 'N' - N64 Game Pak).\n");
|
|
fprintf(stderr, "\t-R, --region <reg> Specify ROM region (default: 'E' - North America).\n");
|
|
fprintf(stderr, "\t-T, --toc Create a table of contents in the ROM.\n");
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "File flags (to be used before each file):\n");
|
|
fprintf(stderr, "\t-a, --align <align> Next file is aligned at <align> bytes from top of memory (default: 16).\n");
|
|
fprintf(stderr, "\t-s, --offset <offset> Next file starts at <offset> from top of memory. Offset must be 4-byte aligned.\n");
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "Binary byte size/offset suffix notation:\n");
|
|
fprintf(stderr, "\tB for bytes.\n");
|
|
fprintf(stderr, "\tK for kibibytes (KiB) [1024 bytes].\n");
|
|
fprintf(stderr, "\tM for mebibytes (MiB) [1024 kibibytes].\n");
|
|
return STATUS_BADUSAGE;
|
|
}
|
|
|
|
bool check_flag(const char * arg, const char * shortFlag, const char * longFlag)
|
|
{
|
|
return !strcmp(arg, shortFlag) || !strcmp(arg, longFlag);
|
|
}
|
|
|
|
size_t get_file_size(FILE * fp)
|
|
{
|
|
size_t restore = ftell(fp);
|
|
size_t end = 0;
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
end = ftell(fp);
|
|
fseek(fp, restore, SEEK_SET);
|
|
|
|
return end;
|
|
}
|
|
|
|
ssize_t copy_file(FILE * dest, const char * file)
|
|
{
|
|
FILE *read_file = fopen(file, "rb");
|
|
uint8_t *buffer;
|
|
|
|
if(!read_file)
|
|
{
|
|
fprintf(stderr, "ERROR: Cannot open %s for reading!\n", file);
|
|
return -1;
|
|
}
|
|
|
|
ssize_t fsize = get_file_size(read_file);
|
|
ssize_t rsize = fsize;
|
|
|
|
/* Should probably make this read in increments, but whatever */
|
|
buffer = malloc(WRITE_SIZE);
|
|
|
|
if(!buffer)
|
|
{
|
|
fprintf(stderr, "ERROR: Out of memory!\n");
|
|
|
|
fclose(read_file);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Weird Windows bug fix, plus this is the right way to do things */
|
|
while(fsize > 0)
|
|
{
|
|
/* Max 1K chunk */
|
|
ssize_t write_size = (fsize > WRITE_SIZE) ? WRITE_SIZE : fsize;
|
|
fsize -= write_size;
|
|
|
|
fread(buffer, 1, write_size, read_file);
|
|
fwrite(buffer, 1, write_size, dest);
|
|
|
|
/* Update the TOC cookie with file contents. */
|
|
toc_cookie = crc32_update(toc_cookie, buffer, write_size);
|
|
}
|
|
|
|
free(buffer);
|
|
fclose(read_file);
|
|
|
|
return rsize;
|
|
}
|
|
|
|
ssize_t output_zeros(FILE * dest, ssize_t amount)
|
|
{
|
|
if(amount < 0)
|
|
{
|
|
/* Can't backward seek */
|
|
return -1;
|
|
}
|
|
|
|
while (amount > 0) {
|
|
int sz = amount;
|
|
if (sz > sizeof(zero))
|
|
sz = sizeof(zero);
|
|
fwrite(zero, 1, sz, dest);
|
|
toc_cookie = crc32_update(toc_cookie, zero, sz);
|
|
amount -= sz;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t parse_bytes(const char * arg)
|
|
{
|
|
size_t arg_len = strlen(arg);
|
|
|
|
if(arg_len < 2)
|
|
{
|
|
/* Need at least 2 characters to parse */
|
|
return -1;
|
|
}
|
|
|
|
char * suffix;
|
|
size_t size = strtol(arg, &suffix, 10);
|
|
|
|
/* Multiply by the suffix magnitude */
|
|
switch(suffix[0])
|
|
{
|
|
case 'm':
|
|
case 'M':
|
|
size *= 1024;
|
|
case 'k':
|
|
case 'K':
|
|
size *= 1024;
|
|
case 'b':
|
|
case 'B':
|
|
default:
|
|
return size;
|
|
}
|
|
}
|
|
|
|
void remove_tmp_file(void)
|
|
{
|
|
if(tmp_output)
|
|
remove(tmp_output);
|
|
}
|
|
|
|
static uint32_t fread32be_at(FILE *fp, int offset)
|
|
{
|
|
uint8_t buf[4];
|
|
fseek(fp, offset, SEEK_SET);
|
|
fread(buf, 1, 4, fp);
|
|
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
|
}
|
|
|
|
static uint16_t fread16be_at(FILE *fp, int offset)
|
|
{
|
|
uint8_t buf[2];
|
|
fseek(fp, offset, SEEK_SET);
|
|
fread(buf, 1, 2, fp);
|
|
return (buf[0] << 8) | buf[1];
|
|
}
|
|
|
|
int parse_elf_loadpoint(const char *elf_fn, uint32_t *loadpoint)
|
|
{
|
|
FILE *elf = fopen(elf_fn, "rb");
|
|
|
|
if(!elf)
|
|
{
|
|
fprintf(stderr, "ERROR: Cannot open %s for reading!\n", elf_fn);
|
|
return -1;
|
|
}
|
|
|
|
char id[4];
|
|
fread(id, 1, 4, elf);
|
|
if (memcmp(id, "\x7F" "ELF", 4) != 0) {
|
|
fprintf(stderr, "WARNING: %s is not an ELF file, boot may fail\n", elf_fn);
|
|
fclose(elf);
|
|
return -1;
|
|
}
|
|
|
|
bool elf64 = fgetc(elf) == 2;
|
|
if (fgetc(elf) == 1) {
|
|
fprintf(stderr, "WARNING: %s is a little-endian ELF file, boot may fail\n", elf_fn);
|
|
fclose(elf);
|
|
return -1;
|
|
}
|
|
|
|
int phoff = fread32be_at(elf, (elf64 ? 0x20+4 : 0x1C));
|
|
int phnum = fread16be_at(elf, (elf64 ? 0x38 : 0x2C));
|
|
|
|
enum { PF_N64_COMPRESSED = 0x1000 };
|
|
enum { PT_LOAD = 0x1 };
|
|
|
|
bool found_loadpoint = false;
|
|
for (int i=0; i<phnum; i++) {
|
|
uint32_t type = fread32be_at(elf, phoff + 0);
|
|
uint32_t vaddr = fread32be_at(elf, phoff + (elf64 ? 5 : 2)*4);
|
|
uint32_t paddr = fread32be_at(elf, phoff + (elf64 ? 7 : 3)*4);
|
|
uint32_t flags = fread32be_at(elf, phoff + (elf64 ? 1 : 6)*4);
|
|
|
|
if (flags & PF_N64_COMPRESSED)
|
|
vaddr = paddr;
|
|
|
|
if (type == PT_LOAD && !(vaddr >= 0x80000000 && vaddr < 0x80000400)) {
|
|
*loadpoint = vaddr;
|
|
found_loadpoint = true;
|
|
break;
|
|
}
|
|
|
|
phoff += elf64 ? 0x38 : 0x20;
|
|
}
|
|
|
|
if (!found_loadpoint) {
|
|
fprintf(stderr, "WARNING: No suitable loading point found in %s, boot may fail on iQue\n", elf_fn);
|
|
fclose(elf);
|
|
return -1;
|
|
}
|
|
|
|
fclose(elf);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
winconsole_utf8();
|
|
FILE * write_file = NULL;
|
|
const char * header = NULL;
|
|
const char * output = NULL;
|
|
size_t declared_size = 0;
|
|
size_t total_bytes_written = 0;
|
|
char title[TITLE_SIZE + 1] = { 0, };
|
|
bool create_toc = false;
|
|
size_t toc_offset = 0;
|
|
int header_size = 0;
|
|
int align_next = 16;
|
|
|
|
char category = 'N';
|
|
// Some flashcarts (at least Everdrive X7) seem to automatically set the TV type based on the region field.
|
|
// As a result, some users might not be able to play the ROM because their TV or capture device doesn't
|
|
// support either PAL or NTSC. If the field is 0, the flashcart seems to not overwrite the console's region,
|
|
// so we use it as default.
|
|
char region = 0;
|
|
|
|
|
|
if(argc <= 1)
|
|
{
|
|
/* No way we can have just one argument or less */
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
int i = 1;
|
|
const char * arg;
|
|
|
|
while(i < argc)
|
|
{
|
|
arg = argv[i++];
|
|
if(check_flag(arg, "-b", "--byteswap"))
|
|
{
|
|
/* Invalid usage */
|
|
fprintf(stderr, "ERROR: The byteswap option is no longer supported. ");
|
|
fprintf(stderr, "Use another tool to convert the output of this program.\n");
|
|
fprintf(stderr, " For example: dd conv=swab if=rom.z64 of=rom.v64\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
if(check_flag(arg, "-h", "--header"))
|
|
{
|
|
if(header)
|
|
{
|
|
/* Invalid usage */
|
|
fprintf(stderr, "ERROR: The header can only be set once\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
if(i >= argc)
|
|
{
|
|
/* Invalid usage */
|
|
fprintf(stderr, "ERROR: Expected an argument to header flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
header = argv[i++];
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-o", "--output"))
|
|
{
|
|
if(output)
|
|
{
|
|
/* Invalid usage */
|
|
fprintf(stderr, "ERROR: The output can only be set once\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
if(i >= argc)
|
|
{
|
|
/* Invalid usage */
|
|
fprintf(stderr, "ERROR: Expected an argument to output flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
output = argv[i++];
|
|
unsigned int needed_size = snprintf(0, 0, "%s.tmp", output) + 1;
|
|
tmp_output = calloc(needed_size, 1);
|
|
snprintf(tmp_output, needed_size, "%s.tmp", output);
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-l", "--size"))
|
|
{
|
|
if(i >= argc)
|
|
{
|
|
/* Expected another argument */
|
|
fprintf(stderr, "ERROR: Expected an argument to size flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
ssize_t size = parse_bytes(argv[i++]);
|
|
|
|
if (size % 4 != 0)
|
|
{
|
|
/* Invalid size */
|
|
fprintf(stderr, "ERROR: Invalid size argument; must be a multiple of 4 bytes\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
if (size % 512 != 0)
|
|
{
|
|
fprintf(stderr, "WARNING: Sizes which are not multiple of 512 bytes might have problems being loaded with a 64drive\n\n");
|
|
}
|
|
|
|
declared_size = size;
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-P", "--padding"))
|
|
{
|
|
if(i >= argc)
|
|
{
|
|
/* Expected another argument */
|
|
fprintf(stderr, "ERROR: Expected an argument to padding flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
ssize_t size = parse_bytes(argv[i++]);
|
|
|
|
if (size < 0)
|
|
{
|
|
/* Invalid size */
|
|
fprintf(stderr, "ERROR: Invalid padding argument; must be positive\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
pad_align = size;
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-T", "--toc"))
|
|
{
|
|
if(total_bytes_written)
|
|
{
|
|
fprintf(stderr, "ERROR: -T / --toc must be specified before any input file\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
create_toc = true;
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-s", "--offset"))
|
|
{
|
|
if(!output)
|
|
{
|
|
fprintf(stderr, "ERROR: Need output flag before offset\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
if(!total_bytes_written)
|
|
{
|
|
fprintf(stderr, "ERROR: The first file cannot have an offset\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
if(i >= argc)
|
|
{
|
|
/* Expected another argument */
|
|
fprintf(stderr, "ERROR: Expected an argument to offset flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
ssize_t offset = parse_bytes(argv[i++]);
|
|
|
|
if(offset <= 0)
|
|
{
|
|
/* Invalid offset */
|
|
fprintf(stderr, "ERROR: Invalid offset argument\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
if((offset % 4) != 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Invalid offset argument (must be multiple of 4)\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
/* Write out needed number of zeros */
|
|
ssize_t num_zeros = offset - total_bytes_written;
|
|
|
|
if(output_zeros(write_file, num_zeros))
|
|
{
|
|
fprintf(stderr, "ERROR: Invalid offset %zd to seek to in %s!\n", offset, output);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/* Same as total_bytes_written = offset */
|
|
total_bytes_written += num_zeros;
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-a", "--align"))
|
|
{
|
|
if(!output)
|
|
{
|
|
fprintf(stderr, "ERROR: Need output flag before alignment\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
if(i >= argc)
|
|
{
|
|
/* Expected another argument */
|
|
fprintf(stderr, "ERROR: Expected an argument to align flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
int align = atoi(argv[i++]);
|
|
if (align < 4)
|
|
{
|
|
fprintf(stderr, "ERROR: Minimum alignment is 4 bytes\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
align_next = align;
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-t", "--title"))
|
|
{
|
|
if(i >= argc)
|
|
{
|
|
/* Expected another argument */
|
|
fprintf(stderr, "ERROR: Expected an argument to title flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
const char * title_arg = argv[i++];
|
|
size_t title_len = strlen(title_arg);
|
|
|
|
if(title_len > TITLE_SIZE)
|
|
{
|
|
fprintf(stderr, "WARNING: Title will be truncated to %d characters\n", TITLE_SIZE);
|
|
title_len = TITLE_SIZE;
|
|
}
|
|
|
|
memcpy(title, title_arg, title_len);
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-C", "--category"))
|
|
{
|
|
if(i >= argc)
|
|
{
|
|
/* Expected another argument */
|
|
fprintf(stderr, "ERROR: Expected an argument to category flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
const char * category_arg = argv[i++];
|
|
if(strlen(category_arg) != 1)
|
|
{
|
|
fprintf(stderr, "ERROR: Category must be a single character\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
category = category_arg[0];
|
|
continue;
|
|
}
|
|
if(check_flag(arg, "-R", "--region"))
|
|
{
|
|
if(i >= argc)
|
|
{
|
|
/* Expected another argument */
|
|
fprintf(stderr, "ERROR: Expected an argument to region flag\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
const char * region_arg = argv[i++];
|
|
if (strlen(region_arg) != 1)
|
|
{
|
|
fprintf(stderr, "ERROR: Region must be a single character\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
region = region_arg[0];
|
|
continue;
|
|
}
|
|
|
|
/* Argument is not a flag; treat it as an input file */
|
|
|
|
/* Can't copy input file unless header and output set */
|
|
if(!output)
|
|
{
|
|
fprintf(stderr, "ERROR: Need output flag before first file\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
/* If this is the first input file, open the output file and write the header */
|
|
if(!write_file)
|
|
{
|
|
/* Create or completely overwrite the output file */
|
|
write_file = fopen(tmp_output, "wb");
|
|
|
|
if(!write_file)
|
|
{
|
|
fprintf(stderr, "ERROR: Cannot open '%s' for writing\n", output);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/* Try to clean up the temporary file if we exit */
|
|
atexit(remove_tmp_file);
|
|
|
|
/* Copy over the ROM header */
|
|
if (header)
|
|
{
|
|
header_size = copy_file(write_file, header);
|
|
}
|
|
else
|
|
{
|
|
header_size = sizeof(default_ipl3);
|
|
fwrite(default_ipl3, 1, header_size, write_file);
|
|
if (parse_elf_loadpoint(arg, &elf_loadpoint) < 0)
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
if(header_size < 0x100)
|
|
{
|
|
fprintf(stderr, "ERROR: Header file '%s' is too small (minimum is 4096 bytes)\n", header);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/* HACK. Unfortunately, n64tool handles both --align and --offset with respect
|
|
to file positions/sizes *excluding* the header. The header used to be of
|
|
fixed size (4096 bytes), but that's now just a minimum. For full backward
|
|
compatibility, we still consider the header to always be 4096 bytes, and
|
|
just offset from there. */
|
|
total_bytes_written += header_size - 4096;
|
|
header_size = 4096;
|
|
|
|
/* Leave space for the table, if asked to do so. */
|
|
if(create_toc && !toc_offset)
|
|
{
|
|
if (total_bytes_written % TOC_ALIGN)
|
|
{
|
|
ssize_t num_zeros = TOC_ALIGN - (total_bytes_written % TOC_ALIGN);
|
|
output_zeros(write_file, num_zeros);
|
|
total_bytes_written += num_zeros;
|
|
}
|
|
|
|
toc_offset = ftell(write_file);
|
|
output_zeros(write_file, TOC_SIZE);
|
|
total_bytes_written += TOC_SIZE;
|
|
}
|
|
}
|
|
|
|
if (align_next)
|
|
{
|
|
if (total_bytes_written % align_next)
|
|
{
|
|
ssize_t num_zeros = align_next - (total_bytes_written % align_next);
|
|
|
|
if(output_zeros(write_file, num_zeros))
|
|
{
|
|
fprintf(stderr, "ERROR: Invalid alignment %d to seek to in %s!\n", align_next, output);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
total_bytes_written += num_zeros;
|
|
}
|
|
}
|
|
|
|
/* Reset to 16 for next file (default) */
|
|
align_next = 16;
|
|
|
|
size_t offset = ftell(write_file);
|
|
|
|
/* Copy the input file into the output file */
|
|
ssize_t bytes_copied = copy_file(write_file, arg);
|
|
|
|
if(bytes_copied < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Unable to copy file from '%s' to '%s'\n", arg, output);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
if (toc.num_entries < TOC_MAX_ENTRIES)
|
|
{
|
|
/* Add the file to the toc */
|
|
toc.files[toc.num_entries].offset = offset;
|
|
toc.files[toc.num_entries].size = bytes_copied;
|
|
|
|
const char *basename = strrchr(arg, '/');
|
|
if (!basename) basename = strrchr(arg, '\\');
|
|
if (!basename) basename = arg;
|
|
if (basename[0] == '/' || basename[0] == '\\') basename++;
|
|
strlcpy(toc.files[toc.num_entries].name, basename, sizeof(toc.files[toc.num_entries].name));
|
|
toc.num_entries++;
|
|
}
|
|
else
|
|
{
|
|
if (create_toc)
|
|
{
|
|
fprintf(stderr, "ERROR: Too many files to add to table.\n");
|
|
return STATUS_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
/* Keep track to be sure we align properly when they request a memory alignment */
|
|
total_bytes_written += bytes_copied;
|
|
}
|
|
|
|
if(!total_bytes_written)
|
|
{
|
|
/* Didn't write anything! */
|
|
printf("ERROR: No input files, nothing written\n\n");
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
/* If the declared size is too small, error out */
|
|
if(declared_size && declared_size < total_bytes_written)
|
|
{
|
|
fprintf(stderr, "ERROR: Couldn't fit ROM in %zu bytes as requested.\n", declared_size);
|
|
return print_usage(argv[0]);
|
|
}
|
|
|
|
/* Pad the output file to the declared size (not including the IPL3 header) */
|
|
if(!declared_size) {
|
|
/* If the user didn't specify a size, initialize this to the minimum
|
|
size that is padded to the correct alignment. Notice that this variable
|
|
declares the size WITHOUT header, but the padding refers to the final
|
|
ROM and so it must be calculated with the header. */
|
|
if (pad_align)
|
|
declared_size = ROUND_UP(header_size, pad_align)-header_size;
|
|
}
|
|
if(declared_size > total_bytes_written)
|
|
{
|
|
ssize_t num_zeros = declared_size - total_bytes_written;
|
|
|
|
if(output_zeros(write_file, num_zeros))
|
|
{
|
|
fprintf(stderr, "ERROR: Couldn't pad %zu bytes to %zu bytes.\n", total_bytes_written, declared_size);
|
|
return print_usage(argv[0]);
|
|
}
|
|
}
|
|
else if(pad_align && ((total_bytes_written+header_size) % pad_align) != 0)
|
|
{
|
|
/* Pad size as required. */
|
|
ssize_t num_zeros = pad_align - ((total_bytes_written+header_size) % pad_align);
|
|
if(output_zeros(write_file, num_zeros))
|
|
{
|
|
fprintf(stderr, "ERROR: Couldn't pad %zu bytes to %zu bytes.\n", total_bytes_written, total_bytes_written+num_zeros);
|
|
return print_usage(argv[0]);
|
|
}
|
|
}
|
|
|
|
/* Set title in header */
|
|
fseek(write_file, TITLE_OFFSET, SEEK_SET);
|
|
fwrite(title, 1, TITLE_SIZE, write_file);
|
|
|
|
/* Set category in header */
|
|
fseek(write_file, CATEGORY_OFFSET, SEEK_SET);
|
|
fwrite(&category, 1, 1, write_file);
|
|
|
|
/* Set region in header */
|
|
fseek(write_file, REGION_OFFSET, SEEK_SET);
|
|
fwrite(®ion, 1, 1, write_file);
|
|
|
|
/* If we are using libdragon's IPL3, set the entrypoint in the header
|
|
for iQue to match the first valid loadpoint found in the ELF. This make
|
|
sure that the iQue OS, in its initial flat-binary loading, will use
|
|
the same memory region as the ELF. */
|
|
if (elf_loadpoint != 0xFFFFFFFF)
|
|
{
|
|
elf_loadpoint = SWAPLONG(elf_loadpoint);
|
|
fseek(write_file, IQUE_ENTRYPOINT_OFFSET, SEEK_SET);
|
|
fwrite(&elf_loadpoint, 1, 4, write_file);
|
|
}
|
|
|
|
/* Write table of contents */
|
|
if(create_toc)
|
|
{
|
|
for (int i=0; i<toc.num_entries; i++) {
|
|
toc.files[i].offset = SWAPLONG(toc.files[i].offset);
|
|
toc.files[i].size = SWAPLONG(toc.files[i].size);
|
|
}
|
|
toc.toc_size = SWAPLONG(toc.toc_size);
|
|
toc.num_entries = SWAPSHORT(toc.num_entries);
|
|
toc.entry_size = SWAPSHORT(toc.entry_size);
|
|
|
|
toc_cookie = ~crc32_update(toc_cookie, (uint8_t *)&toc, sizeof(toc));
|
|
toc.cookie = SWAPLONG(toc_cookie);
|
|
|
|
fseek(write_file, toc_offset, SEEK_SET);
|
|
fwrite(&toc, 1, sizeof(toc), write_file);
|
|
}
|
|
|
|
/* Sync and close the output file */
|
|
fclose(write_file);
|
|
|
|
/* Rename to the final name */
|
|
#ifdef _WIN32
|
|
/* Windows doesn't support atomic renames, so we have to delete the old file first */
|
|
remove(output);
|
|
#endif
|
|
if(rename(tmp_output, output) != 0) {
|
|
fprintf(stderr, "Couldn't rename temporary output file '%s' to '%s': %s", tmp_output, output, strerror(errno));
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
return STATUS_OK;
|
|
}
|