/* casm - Simple, portable assembler Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.co.uk) 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 3 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, see . ------------------------------------------------------------------------- NES ROM output handler. Notes: * ROM is 0x8000 to 0xffff (just 0xc000 to 0xffff if 16K ROM) * VROM is at 0x0000 to 0x1fff. */ #include #include #include #include "global.h" #include "expr.h" #include "codepage.h" #include "nesout.h" /* ---------------------------------------- MACROS & TYPES */ enum option_t { OPT_VECTOR, OPT_TV_FORMAT, OPT_MAPPER, OPT_MIRROR, OPT_BATTERY_RAM }; static const ValueTable option_set[] = { {"nes-vector", OPT_VECTOR}, {"nes-tv-format", OPT_TV_FORMAT}, {"nes-mapper", OPT_MAPPER}, {"nes-mirror", OPT_MIRROR}, {"nes-battery-ram", OPT_BATTERY_RAM}, {NULL} }; typedef enum { VECTOR_RESET, VECTOR_NMI, VECTOR_BRK } VectorType; typedef enum { MIRROR_HORIZONTAL = 0x00, MIRROR_VERTICAL = 0x01, MIRROR_VRAM = 0x08, } MirrorType; typedef enum { PAL = 1, NTSC = 0 } TVFormat; static ValueTable vector_table[] = { {"reset", VECTOR_RESET}, {"nmi", VECTOR_NMI}, {"brk", VECTOR_BRK}, {NULL} }; static ValueTable mirror_table[] = { {"horizontal", MIRROR_HORIZONTAL}, {"vertical", MIRROR_VERTICAL}, {"vram", MIRROR_VRAM}, {NULL} }; static ValueTable format_table[] = { {"pal", PAL}, {"NTSC", NTSC}, {NULL} }; static struct { int vector[3]; TVFormat tv_format; int mapper; MirrorType mirror; int battery_ram; } option = { {-1, -1, -1}, PAL, 0, MIRROR_HORIZONTAL, FALSE }; /* ---------------------------------------- PRIVATE FUNCTIONS */ static int PokeB(Byte *mem, int addr, Byte b) { mem[addr++] = b; return (addr % 0x10000); } static int PokeW(Byte *mem, int addr, int w) { addr = PokeB(mem, addr, w & 0xff); return PokeB(mem, addr, (w & 0xff00) >> 8); } static int PokeS(Byte *mem, int addr, const char *str, int maxlen, char pad) { while(*str && maxlen--) { addr = PokeB(mem, addr, CodeFromNative(CP_ASCII, *str++)); } while(maxlen-- > 0) { addr = PokeB(mem, addr, CodeFromNative(CP_ASCII, pad)); } return addr; } static unsigned CalcChecksum(Byte *p, int len, unsigned csum) { while(len-- > 0) { csum += *p++; } return csum & 0xffff; } /* ---------------------------------------- INTERFACES */ const ValueTable *NESOutputOptions(void) { return option_set; } CommandStatus NESOutputSetOption(int opt, int argc, char *argv[], int quoted[], char *err, size_t errsize) { const ValueTable *val; int f; CMD_ARGC_CHECK(1); switch(opt) { case OPT_VECTOR: CMD_ARGC_CHECK(2); CMD_TABLE(argv[0], vector_table, val); CMD_EXPR(argv[1], f); option.vector[val->value] = f; break; case OPT_TV_FORMAT: CMD_TABLE(argv[0], format_table, val); option.tv_format = val->value; break; case OPT_MAPPER: CMD_EXPR(argv[1], f); option.mapper = f; break; case OPT_MIRROR: CMD_TABLE(argv[0], mirror_table, val); option.mirror = val->value; break; case OPT_BATTERY_RAM: option.battery_ram = ParseTrueFalse(argv[0], FALSE); break; default: break; } return CMD_OK; } int NESOutput(const char *filename, const char *filename_bank, MemoryBank **bank, int count, char *error, size_t error_size) { FILE *fp; Byte *mem; int num_rom; int num_vrom; int is_16k; int first_code; int start; int f; /* Go through the banks and see which are code (ROM), and which char (VROM) */ num_rom = 0; num_vrom = 0; is_16k = FALSE; first_code = -1; for(f = 0; f < count; f++) { if (bank[f]->max_address_used < 0x2000) { num_vrom++; } else if (bank[f]->min_address_used >= 0xc000) { if (first_code == -1) { first_code = f; } is_16k = TRUE; num_rom++; } else if (bank[f]->min_address_used >= 0x8000) { if (first_code == -1) { first_code = f; } is_16k = FALSE; num_rom++; } else { snprintf(error, error_size, "Banks should use memory in the range 0x0000 - 0x1fff " "to indicate they are\nvideo ROM, 0x8000 - 0xffff to " "indicate they are program ROM or 0xc000 - 0xffff\n" "to indicate a single 16K ROM segment."); return FALSE; } } if (first_code == -1) { snprintf(error, error_size, "No ROM code banks present;\n" "Banks should use memory in the range 0x0000 - 0x1fff " "to indicate they are\nvideo ROM, 0x8000 - 0xffff to " "indicate they are program ROM or 0xc000 - 0xffff\n" "to indicate a single 16K ROM segment."); return FALSE; } if (!(fp = fopen(filename, "wb"))) { snprintf(error, error_size, "Failed to create %s", filename); return FALSE; } /* Setup vectors */ mem = bank[first_code]->memory; start = option.vector[VECTOR_RESET]; if (start == -1) { start = is_16k ? 0xc000 : 0x8000; fprintf(stderr, "WARNING: No reset vector provided; assuming 0x%4.4x\n", start); } PokeW(mem, 0xfffc, start); if (option.vector[VECTOR_NMI] != -1) { PokeW(mem, 0xfffa, option.vector[VECTOR_NMI]); } else { fprintf(stderr, "WARNING: NMI vector not set\n"); } if (option.vector[VECTOR_BRK] != -1) { PokeW(mem, 0xfffe, option.vector[VECTOR_BRK]); } /* Output header */ fputs("NES", fp); fputc(26, fp); if (is_16k) { fputc(1, fp); } else { fputc(num_rom * 2, fp); } fputc(num_vrom, fp); fputc(((option.mapper & 0x0f) << 4) | option.mirror | (option.battery_ram ? 0x02 : 0x00), fp); fputc(option.mapper & 0xf0, fp); fputc(0, fp); fputc(option.tv_format, fp); for(f = 0; f < 6; f++) { fputc(0, fp); } /* Output ROM contents, first code segments */ for(f = 0; f < count; f++) { if (bank[f]->min_address_used > 0x2000) { if (is_16k) { fwrite(bank[f]->memory + 0xc000, 0x4000, 1, fp); } else { fwrite(bank[f]->memory + 0x8000, 0x8000, 1, fp); } } } /* Then video ROM banks */ for(f = 0; f < count; f++) { if (bank[f]->min_address_used < 0x2000) { fwrite(bank[f]->memory, 0x2000, 1, fp); } } fclose(fp); return TRUE; } /* vim: ai sw=4 ts=8 expandtab */