summaryrefslogtreecommitdiff
path: root/src/saturn-mkboot.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/saturn-mkboot.c')
-rw-r--r--src/saturn-mkboot.c277
1 files changed, 277 insertions, 0 deletions
diff --git a/src/saturn-mkboot.c b/src/saturn-mkboot.c
new file mode 100644
index 0000000..63e7bb6
--- /dev/null
+++ b/src/saturn-mkboot.c
@@ -0,0 +1,277 @@
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "bootinfo.h"
+#include "symbols.h"
+
+static struct systemid sysid = {
+ .magic = "SEGA SEGASATURN ",
+ .maker = "SEGA TP T-000 ",
+ .product = "T-000000G ",
+ .version = "V0.001",
+ .reldate = "20000101",
+ .device = "CD-1/1 ",
+ .regions = "JTUBKAEL ",
+ .peripherals = "J ",
+ .padding1 = " ",
+ .title = "TEST TITLE ",
+ .bootsize = 0xe00,
+ .stack_master = 0,
+ .stack_slave = 0,
+ .load_addr = 0x06004000,
+ .load_size = 0,
+};
+
+static char ipbuf[0x7200];
+static char *ipfile, *outfile;
+
+static void serialize_region_code(char *out, const struct symbolname *region)
+{
+ static const char defcode[32] = "\xa0\x0e\x00\x09" "For ";
+ size_t namelen = region ? strlen(region->name) : 0;
+
+ memcpy(out, defcode, sizeof defcode);
+ if (namelen) memcpy(out+8, region->name, namelen);
+ out[8+namelen] = '.';
+}
+
+#define WRITE(f,p,s) fwrite(p,s,1,f)
+#define WRITE32(f,v) fwrite((char[]){(v)>>24,(v)>>16,(v)>>8,(v)},4,1,f)
+
+static int write_output(FILE *fp)
+{
+ extern const unsigned char securitycode[];
+ extern const size_t securitycode_size;
+
+ /* system id - 0x100 bytes */
+ WRITE (fp, &sysid, offsetof(struct systemid, bootsize));
+ WRITE32(fp, sysid.bootsize);
+ WRITE32(fp, 0); /* reserved bytes */
+ WRITE32(fp, sysid.stack_master);
+ WRITE32(fp, sysid.stack_slave);
+ WRITE32(fp, sysid.load_addr);
+ WRITE32(fp, sysid.load_size);
+ WRITE32(fp, 0); /* reserved bytes */
+ WRITE32(fp, 0); /* reserved bytes */
+
+ /* security code */
+ WRITE(fp, securitycode, securitycode_size);
+
+ /* initial program */
+ WRITE(fp, ipbuf, sysid.bootsize - 0xe00);
+ return ferror(fp) ? -1 : 0;
+}
+
+static size_t default_ip(char *out)
+{
+ size_t size = 0;
+
+ out[size++] = 0xd0; /* mov.l addr, r0 */
+ out[size++] = 0x01;
+ out[size++] = 0x40; /* jmp @r0 */
+ out[size++] = 0x2b;
+ out[size++] = 0x00; /* nop */
+ out[size++] = 0x09;
+ out[size++] = 0x00; /* nop */
+ out[size++] = 0x09;
+ out[size++] = sysid.load_addr >> 24 & 0xff; /* addr: .long <sysid.load_addr> */
+ out[size++] = sysid.load_addr >> 16 & 0xff;
+ out[size++] = sysid.load_addr >> 8 & 0xff;
+ out[size++] = sysid.load_addr >> 0 & 0xff;
+
+ return size;
+}
+
+static size_t load_ip(char *filename, char *out, size_t maxsize)
+{
+ FILE *fp;
+ size_t size;
+ const char *errprefix = "Error loading initial program";
+
+ if (!(fp = fopen(filename, "rb"))) {
+ goto fail_perror;
+ }
+ if ((size = fread(out, 1, maxsize, fp)) < maxsize) {
+ /* read fewer than requested amount. determine if we hit error or eof */
+ if (ferror(fp)) {
+ goto fail_perror;
+ }
+ } else {
+ /* successfully read the requested amount. verify we reached eof */
+ if (fgetc(fp) != EOF) {
+ fprintf(stderr, "%s: exceeds maximum size\n", errprefix);
+ goto fail;
+ }
+ }
+
+ fclose(fp);
+ return size;
+
+fail_perror:
+ perror(errprefix);
+fail:
+ if (fp) fclose(fp);
+ return -1;
+}
+
+static void print_symbols(int width, const struct symbolname *symbols)
+{
+ for (; symbols->symbol; symbols++) {
+ printf("\t%*s%c: %s\n", width+4, "", symbols->symbol, symbols->name);
+ }
+}
+
+static void usage(const char *progname)
+{
+ const int width = 20;
+
+ printf("usage: %s -h\n", progname);
+ printf(" %s -o[iprms]\n\n", progname);
+ printf("\t%-*s%s\n", width, "-h", "Show this help text");
+ printf("\t%-*s%s\n", width, "-o output", "Output file");
+ printf("\t%-*s%s\n", width, "-i ip.bin", "Initial program code file");
+ printf("\t%-*s%s\n", width, "-p peripherals", "Supported peripherals (default: control pad):");
+ print_symbols(width, peripheraldefs);
+ printf("\t%-*s%s\n", width, "-r regions", "Geographical regions (default: all):");
+ print_symbols(width, regiondefs);
+ printf("\t%-*s%s\n", width, "-m master_stack", "Master stack address (default 0x06002000)");
+ printf("\t%-*s%s\n", width, "-s slave_stack", "Slave stack address (default 0x06001000)");
+
+ exit(0);
+}
+
+static int process_symbols(const char *progname, char *list, size_t size, const char *arg)
+{
+ while (size-- && *arg) *list++ = *arg++;
+ while (size--) *list++ = ' ';
+ return *arg;
+}
+
+static int process_address(const char *progname, uint32_t *addr, const char *arg)
+{
+ char *end;
+ errno = 0;
+ unsigned long val = strtoul(arg, &end, 16);
+ if (end == arg || *end || errno || val > (uint32_t)-1) return 1;
+ *addr = val;
+ return 0;
+}
+
+#define FLAG_o (1U << 3)
+
+static int process_args(int argc, char **argv)
+{
+ uint32_t seen = 0;
+ int opt, fail = 0;
+
+ extern char *optarg;
+ extern int optind, optopt;
+ static const char *optpat = "hi:o:p:r:m:s:";
+ while ((opt = getopt(argc, argv, optpat)) != -1) {
+ uint32_t flag = opt == '?' ? 0 : 1U << strchr(optpat, opt) - optpat;
+ if (seen & flag) {
+ fprintf(stderr, "%s: Duplicate option -%c\n", argv[0], opt);
+ fail = 1;
+ continue;
+ }
+ seen |= flag;
+
+ switch (opt) {
+ case 'h':
+ usage(argv[0]);
+
+ case 'i':
+ ipfile = optarg;
+ break;
+
+ case 'o':
+ outfile = optarg;
+ break;
+
+ case 'm':
+ if (process_address(argv[0], &sysid.stack_master, optarg)) {
+ fprintf(stderr, "%s: Invalid master stack\n", argv[0]);
+ fail = 1;
+ }
+ break;
+
+ case 's':
+ if (process_address(argv[0], &sysid.stack_slave, optarg)) {
+ fprintf(stderr, "%s: Invalid slave stack\n", argv[0]);
+ fail = 1;
+ }
+ break;
+
+ case 'p':
+ if (process_symbols(argv[0], sysid.peripherals, sizeof sysid.peripherals, optarg)) {
+ fprintf(stderr, "%s: Too many peripherals specified (max 16)\n", argv[0]);
+ fail = 1;
+ }
+ break;
+
+ case 'r':
+ if (process_symbols(argv[0], sysid.regions, sizeof sysid.regions, optarg)) {
+ fprintf(stderr, "%s: Too many regions specified (max 10)\n", argv[0]);
+ fail = 1;
+ }
+ break;
+
+ default:
+ fail = 1;
+ }
+ }
+
+ if (!(seen & FLAG_o)) {
+ fprintf(stderr, "%s: Missing required option -o\n", argv[0]);
+ fail = 1;
+ }
+ if (argv[optind]) {
+ fprintf(stderr, "%s: Unknown option '%s'\n", argv[0], argv[optind]);
+ fail = 1;
+ }
+ if (fail) fprintf(stderr, "\nUse -h option for help on correct usage.\n");
+
+ return fail;
+}
+
+int main(int argc, char **argv)
+{
+ FILE *outfp = NULL;
+ char *ipout = ipbuf;
+ size_t ipsize;
+
+ if (process_args(argc, argv)) return 1;
+
+ for (int i = 0; i < 8; i++) {
+ serialize_region_code(ipout, find_symbol(regiondefs, sysid.regions[i]));
+ ipout += 32;
+ sysid.bootsize += 32;
+ }
+
+ ipsize = ipfile
+ ? load_ip(ipfile, ipout, sizeof ipbuf - (ipout - ipbuf))
+ : default_ip(ipout);
+ if (ipsize == -1) goto fail;
+ sysid.bootsize += ipsize;
+
+ if (!(outfp = fopen(outfile, "wb"))) {
+ perror("Error opening output file");
+ goto fail;
+ }
+
+ if (write_output(outfp)) {
+ perror("Error writing output");
+ goto fail;
+ }
+
+ fclose(outfp);
+ return 0;
+
+fail:
+ if (outfp) fclose(outfp);
+ return 1;
+}