From a395d7bf01456620806ae521878a60bdad8dc6e5 Mon Sep 17 00:00:00 2001 From: Ian C Date: Wed, 4 May 2016 13:48:43 +0100 Subject: First pass at SNES ROM output. Still to do: * ROM type * ROM checksums --- src/Makefile | 10 +- src/casm.c | 117 +++++++++++++------- src/example/Makefile | 5 +- src/example/snes.asm | 127 ++++++++++++++++++++++ src/output.c | 8 +- src/output.h | 1 + src/snesout.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/snesout.h | 56 ++++++++++ 8 files changed, 575 insertions(+), 44 deletions(-) create mode 100644 src/example/snes.asm create mode 100644 src/snesout.c create mode 100644 src/snesout.h (limited to 'src') diff --git a/src/Makefile b/src/Makefile index 4f7ec33..af7befa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -45,7 +45,8 @@ SOURCE = casm.c \ specout.c \ t64out.c \ zx81out.c \ - gbout.c + gbout.c \ + snesout.c OBJECTS = casm.o \ expr.o \ @@ -68,7 +69,8 @@ OBJECTS = casm.o \ specout.o \ t64out.o \ zx81out.o \ - gbout.o + gbout.o \ + snesout.o $(TARGET): $(OBJECTS) $(CC) $(CLAGS) -o $(TARGET) $(OBJECTS) @@ -83,7 +85,7 @@ 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 z80.h 6502.h gbcpu.h + specout.h t64out.h zx81out.h gbout.h z80.h 6502.h gbcpu.h 65c816.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 @@ -103,6 +105,8 @@ 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 \ cmd.h +snesout.o: snesout.c global.h basetype.h util.h state.h expr.h codepage.h \ + parse.h cmd.h snesout.h specout.o: specout.c global.h basetype.h util.h state.h specout.h parse.h \ cmd.h stack.o: stack.c global.h basetype.h util.h state.h stack.h diff --git a/src/casm.c b/src/casm.c index 2643a02..fb50532 100644 --- a/src/casm.c +++ b/src/casm.c @@ -86,6 +86,16 @@ typedef struct } CPU; +/* Defines a static value table handler +*/ +typedef struct +{ + const ValueTable *table; + CommandStatus (*handler)(int o, int ac, char *av[], + int q[], char *e, size_t es); +} ValTableHandler; + + /* ---------------------------------------- GLOBALS */ static const CPU cpu_table[]= @@ -123,6 +133,10 @@ static const CPU cpu_table[]= static const CPU *cpu = cpu_table; +static ValTableHandler *valtable_handler; +static int valtable_count; + + /* ---------------------------------------- PROTOS */ static void CheckLimits(void); @@ -133,6 +147,45 @@ static void RunPass(const char *name, FILE *, int depth); static void ProduceOutput(void); +/* ---------------------------------------- STATIC VALUE TABLE HANDLING +*/ +static void PushValTableHandler(const ValueTable *t, + CommandStatus (*h)(int o, int ac, char *av[], + int q[], char *e, size_t es)) +{ + if (t) + { + valtable_handler = Realloc(valtable_handler, + (sizeof *valtable_handler) * + (++valtable_count)); + + valtable_handler[valtable_count - 1].table = t; + valtable_handler[valtable_count - 1].handler = h; + } +} + + +static CommandStatus CheckValTableHandlers(const char *opt, int ac, + char *args[], int q[], + char *err, size_t errsize) +{ + const ValueTable *entry; + int f; + + for(f = 0; f < valtable_count; f++) + { + if ((entry = ParseTable(opt, valtable_handler[f].table))) + { + return valtable_handler[f].handler + (entry->value, ac, args, q, err, errsize); + } + } + + return CMD_NOT_KNOWN; +} + + + /* ---------------------------------------- INTERNAL COMMAND HANDLING */ @@ -384,6 +437,7 @@ static CommandStatus OPTION(const char *label, int argc, char *argv[], char **args; int ac; char *opt; + CommandStatus status = CMD_NOT_KNOWN; if (*argv[1] == '+' || *argv[1] == '-') { @@ -412,51 +466,25 @@ static CommandStatus OPTION(const char *label, int argc, char *argv[], opt = argv[1]; } - /* TODO: There should be someway to make this better - */ - if ((entry = ParseTable(opt, ListOptions()))) - { - return ListSetOption(entry->value, ac, args, q, err, errsize); - } - else if ((entry = ParseTable(opt, MacroOptions()))) - { - return MacroSetOption(entry->value, ac, args, q, err, errsize); - } - else if ((entry = ParseTable(opt, CodepageOptions()))) - { - return CodepageSetOption(entry->value, ac, args, q, err, errsize); - } - else if ((entry = ParseTable(opt, OutputOptions()))) - { - return OutputSetOption(entry->value, ac, args, q, err, errsize); - } - else if ((entry = ParseTable(opt, RawOutputOptions()))) - { - return RawOutputSetOption(entry->value, ac, args, q, err, errsize); - } - else if ((entry = ParseTable(opt, SpecTAPOutputOptions()))) - { - return SpecTAPOutputSetOption(entry->value, ac, args, q, err, errsize); - } - else if ((entry = ParseTable(opt, T64OutputOptions()))) - { - return T64OutputSetOption(entry->value, ac, args, q, err, errsize); - } - else if ((entry = ParseTable(opt, ZX81OutputOptions()))) + status = CheckValTableHandlers(opt, ac, args, q, err, errsize); + + if (status == CMD_NOT_KNOWN) { - return ZX81OutputSetOption(entry->value, ac, args, q, err, errsize); + if ((entry = ParseTable(opt, cpu->options()))) + { + status = cpu->set_option(entry->value, ac, args, q, err, errsize); + } } - else if ((entry = ParseTable(opt, GBOutputOptions()))) + + if (status == CMD_NOT_KNOWN) { - return GBOutputSetOption(entry->value, ac, args, q, err, errsize); + snprintf(err, errsize, "%s: unknown option %s", argv[0], opt); + return CMD_FAILED; } - else if ((entry = ParseTable(opt, cpu->options()))) + else { - return cpu->set_option(entry->value, ac, args, q, err, errsize); + return status; } - - snprintf(err, errsize, "%s: unknown option %s", argv[0], opt); - return CMD_FAILED; } @@ -576,6 +604,17 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } + PushValTableHandler(ListOptions(), ListSetOption); + PushValTableHandler(MacroOptions(), MacroSetOption); + PushValTableHandler(CodepageOptions(), CodepageSetOption); + PushValTableHandler(OutputOptions(), OutputSetOption); + PushValTableHandler(RawOutputOptions(), RawOutputSetOption); + PushValTableHandler(SpecTAPOutputOptions(), SpecTAPOutputSetOption); + PushValTableHandler(T64OutputOptions(), T64OutputSetOption); + PushValTableHandler(ZX81OutputOptions(), ZX81OutputSetOption); + PushValTableHandler(GBOutputOptions(), GBOutputSetOption); + PushValTableHandler(SNESOutputOptions(), SNESOutputSetOption); + ClearState(); SetPC(0); diff --git a/src/example/Makefile b/src/example/Makefile index f83d006..3c57e50 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 +ALL = spectrum.tap c64.t64 zx81.p gb.gb vcs.bin snes.sfc CASM = ../casm all: $(ALL) $(CASM) @@ -45,5 +45,8 @@ gb.gb: gb.asm $(CASM) vcs.bin: vcs.asm $(CASM) $(CASM) vcs.asm +snes.sfc: snes.asm $(CASM) + $(CASM) snes.asm + clean: rm -f $(ALL) diff --git a/src/example/snes.asm b/src/example/snes.asm new file mode 100644 index 0000000..bea3235 --- /dev/null +++ b/src/example/snes.asm @@ -0,0 +1,127 @@ + processor 65c816 + + option output-file,snes.sfc + option output-format,snes + + option snes-irq,vbl,handler + option snes-irq,irq,handler + + ; Initialise SNES + ; + org $8000 + + sei + clc + xce + ldx #$1fff + txs + + sep #$30 ; X,Y,A are 8 bit numbers + lda #$8F ; screen off, full brightness + sta $2100 ; brightness + screen enable register + stz $2101 ; Sprite register (size + address in VRAM) + stz $2102 ; Sprite registers (address of sprite memory [OAM]) + stz $2103 ; "" "" + stz $2105 ; Mode 0, = Graphic mode register + stz $2106 ; noplanes, no mosaic, = Mosaic register + stz $2107 ; Plane 0 map VRAM location + stz $2108 ; Plane 1 map VRAM location + stz $2109 ; Plane 2 map VRAM location + stz $210A ; Plane 3 map VRAM location + stz $210B ; Plane 0+1 Tile data location + stz $210C ; Plane 2+3 Tile data location + stz $210D ; Plane 0 scroll x (first 8 bits) + stz $210D ; Plane 0 scroll x (last 3 bits) #$0 - #$07ff + lda #$FF ; The top pixel drawn on the screen isn't the top one in the tilemap, it's the one above that. + sta $210E ; Plane 0 scroll y (first 8 bits) + sta $2110 ; Plane 1 scroll y (first 8 bits) + sta $2112 ; Plane 2 scroll y (first 8 bits) + sta $2114 ; Plane 3 scroll y (first 8 bits) + lda #$07 ; Since this could get quite annoying, it's better to edit the scrolling registers to fix this. + sta $210E ; Plane 0 scroll y (last 3 bits) #$0 - #$07ff + sta $2110 ; Plane 1 scroll y (last 3 bits) #$0 - #$07ff + sta $2112 ; Plane 2 scroll y (last 3 bits) #$0 - #$07ff + sta $2114 ; Plane 3 scroll y (last 3 bits) #$0 - #$07ff + stz $210F ; Plane 1 scroll x (first 8 bits) + stz $210F ; Plane 1 scroll x (last 3 bits) #$0 - #$07ff + stz $2111 ; Plane 2 scroll x (first 8 bits) + stz $2111 ; Plane 2 scroll x (last 3 bits) #$0 - #$07ff + stz $2113 ; Plane 3 scroll x (first 8 bits) + stz $2113 ; Plane 3 scroll x (last 3 bits) #$0 - #$07ff + lda #$80 ; increase VRAM address after writing to $2119 + sta $2115 ; VRAM address increment register + stz $2116 ; VRAM address low + stz $2117 ; VRAM address high + stz $211A ; Initial Mode 7 setting register + stz $211B ; Mode 7 matrix parameter A register (low) + lda #$01 + sta $211B ; Mode 7 matrix parameter A register (high) + stz $211C ; Mode 7 matrix parameter B register (low) + stz $211C ; Mode 7 matrix parameter B register (high) + stz $211D ; Mode 7 matrix parameter C register (low) + stz $211D ; Mode 7 matrix parameter C register (high) + stz $211E ; Mode 7 matrix parameter D register (low) + sta $211E ; Mode 7 matrix parameter D register (high) + stz $211F ; Mode 7 center position X register (low) + stz $211F ; Mode 7 center position X register (high) + stz $2120 ; Mode 7 center position Y register (low) + stz $2120 ; Mode 7 center position Y register (high) + stz $2121 ; Color number register ($0-ff) + stz $2123 ; BG1 & BG2 Window mask setting register + stz $2124 ; BG3 & BG4 Window mask setting register + stz $2125 ; OBJ & Color Window mask setting register + stz $2126 ; Window 1 left position register + stz $2127 ; Window 2 left position register + stz $2128 ; Window 3 left position register + stz $2129 ; Window 4 left position register + stz $212A ; BG1, BG2, BG3, BG4 Window Logic register + stz $212B ; OBJ, Color Window Logic Register (or,and,xor,xnor) + sta $212C ; Main Screen designation (planes, sprites enable) + stz $212D ; Sub Screen designation + stz $212E ; Window mask for Main Screen + stz $212F ; Window mask for Sub Screen + lda #$30 + sta $2130 ; Color addition & screen addition init setting + stz $2131 ; Add/Sub sub designation for screen, sprite, color + lda #$E0 + sta $2132 ; color data for addition/subtraction + stz $2133 ; Screen setting (interlace x,y/enable SFX data) + stz $4200 ; Enable V-blank, interrupt, Joypad register + lda #$FF + sta $4201 ; Programmable I/O port + stz $4202 ; Multiplicand A + stz $4203 ; Multiplier B + stz $4204 ; Multiplier C + stz $4205 ; Multiplicand C + stz $4206 ; Divisor B + stz $4207 ; Horizontal Count Timer + stz $4208 ; Horizontal Count Timer MSB (most significant bit) + stz $4209 ; Vertical Count Timer + stz $420A ; Vertical Count Timer MSB + stz $420B ; General DMA enable (bits 0-7) + stz $420C ; Horizontal DMA (HDMA) enable (bits 0-7) + stz $420D ; Access cycle designation (slow/fast rom) + + cli ; Enable interrupts + + ; Set the background color to green. + sep #$20 ; Set the A register to 8-bit. + lda #%10000000 ; Force VBlank by turning off the screen. + sta $2100 + lda #%11100000 ; Load the low byte of the green color. + sta $2122 + lda #%00000000 ; Load the high byte of the green color. + sta $2122 + lda #%00001111 ; End VBlank, setting brightness to 15 (100%). + sta $2100 + + ; Loop forever. +Forever: + jmp Forever + + +; IRQ vectors +; + +handler: + rti diff --git a/src/output.c b/src/output.c index 7d42565..71eb786 100644 --- a/src/output.c +++ b/src/output.c @@ -52,7 +52,8 @@ typedef enum TAP, T64, ZX81, - GAMEBOY + GAMEBOY, + SNES } Format; static char output[4096] = "output"; @@ -67,6 +68,7 @@ static ValueTable format_table[] = {"t64", T64}, {"zx81", ZX81}, {"gameboy", GAMEBOY}, + {"snes", SNES}, {NULL} }; @@ -148,6 +150,10 @@ int OutputCode(void) return GBOutput(output, output_bank, bank, count, error, sizeof error); + case SNES: + return SNESOutput(output, output_bank, bank, count, + error, sizeof error); + default: break; } diff --git a/src/output.h b/src/output.h index 1069fed..b894e88 100644 --- a/src/output.h +++ b/src/output.h @@ -36,6 +36,7 @@ #include "t64out.h" #include "zx81out.h" #include "gbout.h" +#include "snesout.h" /* ---------------------------------------- INTERFACES */ diff --git a/src/snesout.c b/src/snesout.c new file mode 100644 index 0000000..1612a6b --- /dev/null +++ b/src/snesout.c @@ -0,0 +1,295 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.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 . + + ------------------------------------------------------------------------- + + SNES ROM output handler. + +*/ +#include +#include +#include + +#include "global.h" +#include "expr.h" +#include "codepage.h" +#include "snesout.h" + + +/* ---------------------------------------- MACROS & TYPES +*/ + +enum option_t +{ + OPT_ROM_TYPE, + OPT_IRQ, + OPT_NAME, + OPT_START, + OPT_RAM_SIZE, + OPT_ROM_SIZE, +}; + +static const ValueTable option_set[] = +{ + {"snes-rom-type", OPT_ROM_TYPE}, + {"snes-irq", OPT_IRQ}, + {"snes-name", OPT_NAME}, + {"snes-start", OPT_START}, + {"snes-ram-size", OPT_RAM_SIZE}, + {"snes-rom-size", OPT_ROM_SIZE}, + {NULL} +}; + +typedef enum +{ + ROM_LOROM = 0x00, + ROM_HIROM = 0x01, + ROM_LOROM_FAST = 0x30 | ROM_LOROM, + ROM_HIROM_FAST = 0x30 | ROM_HIROM +} ROM_Type; + +static ValueTable rom_table[] = +{ + {"lorom", ROM_LOROM}, + {"hirom", ROM_HIROM}, + {"lorom-fast", ROM_LOROM_FAST}, + {"hirom-fast", ROM_HIROM_FAST}, + {NULL} +}; + +typedef enum +{ + IRQ_VBLANK, + IRQ_IRQ +} IRQ_Type; + +static ValueTable irq_table[] = +{ + {"vbl", IRQ_VBLANK}, + {"irq", IRQ_IRQ}, + {NULL} +}; + + +static struct +{ + ROM_Type rom_type; + int irq_vector[2]; + char name[22]; + int start; + int ram_size; + int rom_size; +} option = +{ + ROM_LOROM, {-1, -1}, "NONAME", 0x8000, 0, -1 +}; + + +/* ---------------------------------------- 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; +} + + +/* ---------------------------------------- INTERFACES +*/ +const ValueTable *SNESOutputOptions(void) +{ + return option_set; +} + +CommandStatus SNESOutputSetOption(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_ROM_TYPE: + CMD_TABLE(argv[0], rom_table, val); + option.rom_type = val->value; + break; + + case OPT_IRQ: + CMD_ARGC_CHECK(2); + CMD_TABLE(argv[0], irq_table, val); + CMD_EXPR(argv[1], f); + option.irq_vector[val->value] = f; + break; + + case OPT_NAME: + CopyStr(option.name, argv[1], sizeof option.name); + break; + + case OPT_START: + CMD_EXPR(argv[0], f); + option.start = f; + break; + + case OPT_ROM_SIZE: + CMD_EXPR(argv[0], f); + option.rom_size = f; + break; + + case OPT_RAM_SIZE: + CMD_EXPR(argv[0], f); + option.ram_size = f; + break; + + default: + break; + } + + return CMD_OK; + +} + +int SNESOutput(const char *filename, const char *filename_bank, + MemoryBank **bank, int count, char *error, size_t error_size) +{ + FILE *fp; + Byte *mem; + int base; + int len; + int f; + + /* If the ROM type is LOROM then we assume each bank holds 32Kb. Otherwise + each bank is a full 64Kb. + */ + if (option.rom_type == ROM_LOROM || option.rom_type == ROM_LOROM_FAST) + { + for(f = 0; f < count; f++) + { + if (bank[f]->min_address_used < 0x8000) + { + snprintf(error, error_size, "Bank %u uses memory below 0x8000", + bank[f]->number); + return FALSE; + } + } + + base = 0x8000; + len = 0x8000; + } + else + { + base = 0; + len = 0x10000; + } + + if (!(fp = fopen(filename, "wb"))) + { + snprintf(error, error_size, "Failed to create %s", filename); + return FALSE; + } + + /* Setup ROM header + */ + mem = bank[0]->memory; + + PokeS(mem, 0xffc0, option.name, 21, ' '); + + PokeB(mem, 0xffd5, option.rom_type); + + PokeW(mem, 0xfffc, option.start); + + if (option.irq_vector[IRQ_VBLANK] != -1) + { + PokeW(mem, 0xffea, option.irq_vector[IRQ_VBLANK]); + PokeW(mem, 0xfffa, option.irq_vector[IRQ_VBLANK]); + } + else + { + fprintf(stderr, "WARNING: VBLANK IRQ not set\n"); + } + + if (option.irq_vector[IRQ_IRQ] != -1) + { + PokeW(mem, 0xffee, option.irq_vector[IRQ_IRQ]); + PokeW(mem, 0xfffe, option.irq_vector[IRQ_IRQ]); + } + + /* TODO: What goes in 0xffd6 - ROM type? */ + + if (option.rom_size == -1) + { + if (option.rom_type == ROM_LOROM || option.rom_type == ROM_LOROM_FAST) + { + PokeB(mem, 0xffd7, count * 32); + } + else + { + PokeB(mem, 0xffd7, count * 64); + } + } + else + { + PokeB(mem, 0xffd7, option.rom_size); + } + + PokeB(mem, 0xffd8, option.ram_size); + + /* TODO: Need checksums? */ + + /* Output ROM contents + */ + for(f = 0; f < count; f++) + { + fwrite(bank[f]->memory + base, len, 1, fp); + } + + fclose(fp); + + return TRUE; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/snesout.h b/src/snesout.h new file mode 100644 index 0000000..ecc4c9e --- /dev/null +++ b/src/snesout.h @@ -0,0 +1,56 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.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_SNESOUT_H +#define CASM_SNESOUT_H + +#include "parse.h" +#include "state.h" +#include "cmd.h" + +/* ---------------------------------------- INTERFACES +*/ + + +/* SNES Output options +*/ +const ValueTable *SNESOutputOptions(void); + +CommandStatus SNESOutputSetOption(int opt, int argc, char *argv[], + int quoted[], char *error, + size_t error_size); + + +/* SNES ROM output of assembly. Returns TRUE if OK, FALSE for failure. +*/ +int SNESOutput(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 +*/ -- cgit v1.2.3