From 45fd0ca73ec84a9b605f918a7b1b3497d2a25b33 Mon Sep 17 00:00:00 2001 From: Ian C Date: Fri, 20 May 2016 15:23:00 +0100 Subject: First pass at NES ROM support. --- .gitignore | 2 + doc/casm.html | 87 ++++++++++++- src/Makefile | 15 ++- src/casm.c | 2 + src/example/Makefile | 5 +- src/example/nes.asm | 82 ++++++++++++ src/nesout.c | 352 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nesout.h | 56 ++++++++ src/output.c | 8 +- src/output.h | 1 + 10 files changed, 601 insertions(+), 9 deletions(-) create mode 100644 src/example/nes.asm create mode 100644 src/nesout.c create mode 100644 src/nesout.h diff --git a/.gitignore b/.gitignore index fd6c1df..7b374ef 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,7 @@ src/casm *.tap *.t64 *.p +*.sfc +*.nes src/A-Hdrive src/output diff --git a/doc/casm.html b/doc/casm.html index 7696136..5581eba 100644 --- a/doc/casm.html +++ b/doc/casm.html @@ -8,13 +8,25 @@ h1 { padding-top: 20px; - padding-top: 10px; + padding-bottom: 10px; } h2 { padding-top: 20px; - padding-top: 10px; + padding-bottom: 10px; +} + +h3 +{ + padding-top: 20px; + padding-bottom: 10px; +} + +h4 +{ + padding-top: 20px; + padding-bottom: 10px; } body @@ -983,6 +995,13 @@ A simple library format that allows the importing of pre-built memory and labels. + +nes + + +A NES ROM file. + + @@ -1323,6 +1342,70 @@ gfx.lib: gfx.asm +

NES ROM File Output Format

+ +

+Generates a ROM file for a NES emulator or hardware. Note that large ROM +sizes have not been extensively checked and verified. +

+ +

It searches for banks with code in the range 0x8000 to 0xffff, or just in the +range 0xc000 to 0xffff if there is just one ROM code bank. Video ROM (VROM) +banks are identified by just using addresses up to 0x1fff.

+ +

The output driver will output the appropriate number of ROM and VROM chunks +to the file depending on these, and will error if there are no ROM banks +or banks using illegal addresses.

+ +

NES ROM Output Format options

+ +

The NES output driver supports the following settings that can be set via +an option command. +

+ + + + + + + + + + + + + + + +
OptionDescription
+option nes-vector, vector, address + +Specifies an address where an IRQ routine is stored, and sets the appropriate +vectors in the appropriate place in memory. irq can either by +reset, nmi or brk.

+ +reset is for the reset vector (i.e. where the ROM runs from). If +undefined a warning is displayed and the vector will automatically be set to +either 0x8000 or 0xc000 according to the ROM size.

+ +nmi is the NMI vector used for the vertical blanking interrupt. If +undefined a warning is issued.

+ +brk is the vector used for software interrupts from the brk command. +This can be left undefined if not needed. +
+option nes-mapper, number + +Sets the mapper type in the output file header. If undefined left at zero, +which is the correct one if you have 16 or 32K of code ROM and 8KB of video +ROM. +
+option nes-tv-format, pal|ntsc + +Defines the TV format stored in the ROM header, defaulting to pal. +
+ +

Listing

diff --git a/src/Makefile b/src/Makefile index fe9fc6d..e95a5c8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -48,7 +48,8 @@ SOURCE = casm.c \ zx81out.c \ gbout.c \ snesout.c \ - libout.c + libout.c \ + nesout.c OBJECTS = casm.o \ expr.o \ @@ -74,7 +75,8 @@ OBJECTS = casm.o \ zx81out.o \ gbout.o \ snesout.o \ - libout.o + libout.o \ + nesout.o $(TARGET): $(OBJECTS) $(CC) $(CLAGS) -o $(TARGET) $(OBJECTS) @@ -89,8 +91,8 @@ clean: alias.o: alias.c global.h basetype.h util.h state.h alias.h casm.o: casm.c global.h basetype.h util.h state.h expr.h label.h macro.h \ cmd.h parse.h codepage.h stack.h listing.h alias.h output.h rawout.h \ - specout.h t64out.h zx81out.h gbout.h snesout.h libout.h z80.h 6502.h \ - gbcpu.h 65c816.h spc700.h + specout.h t64out.h zx81out.h gbout.h snesout.h libout.h nesout.h z80.h \ + 6502.h gbcpu.h 65c816.h spc700.h codepage.o: codepage.c global.h basetype.h util.h state.h codepage.h \ parse.h cmd.h expr.o: expr.c global.h basetype.h util.h state.h expr.h label.h @@ -106,8 +108,11 @@ listing.o: listing.c global.h basetype.h util.h state.h label.h macro.h \ cmd.h parse.h expr.h varchar.h listing.h macro.o: macro.c global.h basetype.h util.h state.h codepage.h parse.h \ cmd.h varchar.h macro.h +nesout.o: nesout.c global.h basetype.h util.h state.h expr.h codepage.h \ + parse.h cmd.h nesout.h output.o: output.c global.h basetype.h util.h state.h output.h parse.h \ - cmd.h rawout.h specout.h t64out.h zx81out.h gbout.h snesout.h libout.h + cmd.h rawout.h specout.h t64out.h zx81out.h gbout.h snesout.h libout.h \ + nesout.h parse.o: parse.c global.h basetype.h util.h state.h codepage.h parse.h \ cmd.h rawout.o: rawout.c global.h basetype.h util.h state.h rawout.h parse.h \ diff --git a/src/casm.c b/src/casm.c index f1de63b..c85c578 100644 --- a/src/casm.c +++ b/src/casm.c @@ -691,6 +691,8 @@ int main(int argc, char *argv[]) PushValTableHandler(ZX81OutputOptions(), ZX81OutputSetOption); PushValTableHandler(GBOutputOptions(), GBOutputSetOption); PushValTableHandler(SNESOutputOptions(), SNESOutputSetOption); + PushValTableHandler(LibOutputOptions(), LibOutputSetOption); + PushValTableHandler(NESOutputOptions(), NESOutputSetOption); ClearState(); diff --git a/src/example/Makefile b/src/example/Makefile index 3c57e50..e92b2ec 100644 --- a/src/example/Makefile +++ b/src/example/Makefile @@ -20,7 +20,7 @@ # Makefile for examples # -ALL = spectrum.tap c64.t64 zx81.p gb.gb vcs.bin snes.sfc +ALL = spectrum.tap c64.t64 zx81.p gb.gb vcs.bin snes.sfc nes.nes CASM = ../casm all: $(ALL) $(CASM) @@ -48,5 +48,8 @@ vcs.bin: vcs.asm $(CASM) snes.sfc: snes.asm $(CASM) $(CASM) snes.asm +nes.nes: nes.asm $(CASM) + $(CASM) nes.asm + clean: rm -f $(ALL) diff --git a/src/example/nes.asm b/src/example/nes.asm new file mode 100644 index 0000000..c10ca66 --- /dev/null +++ b/src/example/nes.asm @@ -0,0 +1,82 @@ + processor 6502 + + option output-file,"nes.nes" + option output-format,nes + + option nes-vector,reset,start + option nes-vector,nmi,nmi + option nes-vector,brk,nmi + +start: org $c000 + + ; + ; Code taken from NES example + ; + + sei ; disable IRQs + cld ; disable decimal mode + ldx #$40 + stx $4017 ; dsiable APU frame IRQ + ldx #$ff ; Set up stack + txs ; . + inx ; now X = 0 + stx $2000 ; disable NMI + stx $2001 ; disable rendering + stx $4010 ; disable DMC IRQs + + ;; first wait for vblank to make sure PPU is ready +vblankwait1: + bit $2002 + bpl vblankwait1 + +clear_memory: + lda #$00 + sta $0000, x + sta $0100, x + sta $0200, x + sta $0300, x + sta $0400, x + sta $0500, x + sta $0600, x + sta $0700, x + inx + bne clear_memory + + ;; second wait for vblank, PPU is ready after this +vblankwait2: + bit $2002 + bpl vblankwait2 + +clear_palette: + ;; Need clear both palettes to $00. Needed for Nestopia. Not + ;; needed for FCEU* as they're already $00 on powerup. + lda $2002 ; Read PPU status to reset PPU address + lda #$3f ; Set PPU address to BG palette RAM ($3F00) + sta $2006 + lda #$00 + sta $2006 + + ldx #$20 ; Loop $20 times (up to $3F20) + lda #$00 ; Set each entry to $00 +.loop + sta $2007 + dex + bne loop + + lda #%01000000 ; intensify blues + sta $2001 + +forever: + jmp forever + +nmi: + rti + + +; +; Dummy VROM +; + org 0 + bank 1 + + db 0 diff --git a/src/nesout.c b/src/nesout.c new file mode 100644 index 0000000..7877e12 --- /dev/null +++ b/src/nesout.c @@ -0,0 +1,352 @@ +/* + + 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 +}; + +static const ValueTable option_set[] = +{ + {"nes-vector", OPT_VECTOR}, + {"nes-tv-format", OPT_TV_FORMAT}, + {"nes-mapper", OPT_MAPPER}, + {NULL} +}; + +typedef enum +{ + VECTOR_RESET, + VECTOR_NMI, + VECTOR_BRK +} VectorType; + +static ValueTable vector_table[] = +{ + {"reset", VECTOR_RESET}, + {"nmi", VECTOR_NMI}, + {"brk", VECTOR_BRK}, + {NULL} +}; + +typedef enum +{ + PAL = 1, + NTSC = 0 +} TVFormat; + +static ValueTable format_table[] = +{ + {"pal", PAL}, + {"NTSC", NTSC}, + {NULL} +}; + + +static struct +{ + int vector[3]; + TVFormat tv_format; + int mapper; +} option = +{ + {-1, -1, -1}, PAL, 0 +}; + + +/* ---------------------------------------- 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; + + 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, fp); + fputc(option.mapper & 0xf0, fp); + + fputc(0, fp); + + fputc(option.tv_format, fp); + + for(f = 0; f < 5; 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 +*/ diff --git a/src/nesout.h b/src/nesout.h new file mode 100644 index 0000000..722db02 --- /dev/null +++ b/src/nesout.h @@ -0,0 +1,56 @@ +/* + + 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 . + + ------------------------------------------------------------------------- + + Gameboy ROM output + +*/ + +#ifndef CASM_NESOUT_H +#define CASM_NESOUT_H + +#include "parse.h" +#include "state.h" +#include "cmd.h" + +/* ---------------------------------------- INTERFACES +*/ + + +/* NES Output options +*/ +const ValueTable *NESOutputOptions(void); + +CommandStatus NESOutputSetOption(int opt, int argc, char *argv[], + int quoted[], char *error, + size_t error_size); + + +/* NES ROM output of assembly. Returns TRUE if OK, FALSE for failure. +*/ +int NESOutput(const char *filename, const char *filename_bank, + MemoryBank **bank, int count, + char *error, size_t error_size); + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/output.c b/src/output.c index ac05216..1067b96 100644 --- a/src/output.c +++ b/src/output.c @@ -54,7 +54,8 @@ typedef enum ZX81, GAMEBOY, SNES, - LIBRARY + LIBRARY, + NES } Format; static char output[4096] = "output"; @@ -71,6 +72,7 @@ static ValueTable format_table[] = {"gameboy", GAMEBOY}, {"snes", SNES}, {"lib", LIBRARY}, + {"nes", NES}, {NULL} }; @@ -160,6 +162,10 @@ int OutputCode(void) return LibOutput(output, output_bank, bank, count, error, sizeof error); + case NES: + return NESOutput(output, output_bank, bank, count, + error, sizeof error); + default: break; } diff --git a/src/output.h b/src/output.h index 03131e4..b2e208c 100644 --- a/src/output.h +++ b/src/output.h @@ -38,6 +38,7 @@ #include "gbout.h" #include "snesout.h" #include "libout.h" +#include "nesout.h" /* ---------------------------------------- INTERFACES */ -- cgit v1.2.3