/* 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 handler. */ #include #include #include #include "global.h" #include "expr.h" #include "codepage.h" #include "gbout.h" /* ---------------------------------------- MACROS & TYPES */ enum option_t { OPT_COLOUR, OPT_SUPER, OPT_CART_RAM, OPT_CART_TYPE, OPT_IRQ }; static const ValueTable option_set[] = { {"gameboy-colour", OPT_COLOUR}, {"gameboy-color", OPT_COLOUR}, {"gameboy-super", OPT_SUPER}, {"gameboy-cart-ram", OPT_CART_RAM}, {"gameboy-cart-type", OPT_CART_TYPE}, {"gameboy-irq", OPT_IRQ}, {NULL} }; typedef enum { IRQ_VBLANK, IRQ_LCD, IRQ_TIMER, IRQ_SERIAL, IRQ_JOYPAD } IRQ_Type; static ValueTable irq_table[] = { {"vbl", IRQ_VBLANK}, {"lcd", IRQ_LCD}, {"timer", IRQ_TIMER}, {"serial", IRQ_SERIAL}, {"joypad", IRQ_JOYPAD}, {NULL} }; static struct { int is_colour; int is_super; int cart_ram; int cart_type; int irq_vector[5]; } option = { FALSE, FALSE, -1, -1, {-1, -1, -1, -1, -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) { while(*str && maxlen--) { addr = PokeB(mem, addr, CodeFromNative(CP_ASCII, *str++)); } return addr; } static int PokeCode(Byte *mem, int addr, const int *code) { while(*code != -1) { addr = PokeB(mem, addr, *code++); } return addr; } /* ---------------------------------------- INTERFACES */ const ValueTable *GBOutputOptions(void) { return option_set; } CommandStatus GBOutputSetOption(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_COLOUR: option.is_colour = ParseTrueFalse(argv[0], TRUE); break; case OPT_SUPER: option.is_super = ParseTrueFalse(argv[0], TRUE); break; case OPT_CART_RAM: CMD_EXPR(argv[0], option.cart_ram); break; case OPT_CART_TYPE: CMD_EXPR(argv[0], option.cart_type); 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; default: break; } return CMD_OK; } int GBOutput(const char *filename, const char *filename_bank, MemoryBank **bank, int count, char *error, size_t error_size) { static const int logo[] = { 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, -1 }; FILE *fp = fopen(filename, "wb"); int f; int offset; int rom_size; Word global_csum = 0; Byte hdr_csum = 0; Byte *mem; if (!fp) { snprintf(error, error_size, "Failed to create %s", filename); return FALSE; } /* Check bank sizes and layouts */ if (count == 1 && (bank[0]->min_address_used < 0x150 || bank[0]->max_address_used > 0x7fff)) { snprintf(error, error_size, "A simple ROM must be in the address " "space 0x150 to 0x7fff"); return FALSE; } if (count > 1 && (bank[0]->min_address_used < 0x150 || bank[0]->max_address_used > 0x3fff)) { snprintf(error, error_size, "Bank zero of a banked ROM must be in the " "address space 0x150 to 0x3fff"); return FALSE; } for(f = 1 ; f < count; f++) { if (bank[f]->min_address_used < 0x4000 || bank[f]->max_address_used > 0x7fff) { snprintf(error, error_size, "Bank %u must be in the address space " "0x4000 to 0x7fff", bank[f]->number); return FALSE; } } if (option.cart_type == -1) { if (count == 1) { option.cart_type = 0; } else { option.cart_type = 1; } if (option.cart_ram != -1) { option.cart_type = 3; } } if (count == 1) { rom_size = 0; } else { rom_size = (count / 4) + 1; } mem = bank[0]->memory; /* Create the log */ for(f = 0; logo[f] != -1; f++) { PokeB(mem, 0x104 + f, logo[f]); } /* Create the RST handlers */ for(f = 0; f < 8; f++) { int addr = f * 8; addr = PokeB(mem, addr, 0xc3); addr = PokeW(mem, addr, 0x100); } /* Create the IRQ handlers */ for(f = 0; f < 5; f++) { int addr = (f * 8) + 0x40; if (option.irq_vector[f] == -1) { addr = PokeB(mem, addr, 0xd9); } else { addr = PokeB(mem, addr, 0xc3); addr = PokeW(mem, addr, option.irq_vector[f]); } } /* Create the entry point */ PokeB(mem, 0x100, 0); PokeB(mem, 0x101, 0xc3); PokeW(mem, 0x102, bank[0]->min_address_used); /* Title (new smaller size) */ PokeS(mem, 0x134, filename, 11); /* GBC flag */ if (option.is_colour) { PokeB(mem, 0x143, 0xc0); } /* Super Gameboy flag */ if (option.is_super) { PokeB(mem, 0x146, 3); } else { PokeB(mem, 0x146, 0); } /* Type/ROM size */ PokeB(mem, 0x147, option.cart_type); PokeB(mem, 0x148, rom_size); /* RAM size */ switch(option.cart_ram) { case 2: PokeB(mem, 0x149, 1); break; case 8: PokeB(mem, 0x149, 2); break; case 32: PokeB(mem, 0x149, 3); break; case 128: PokeB(mem, 0x149, 4); break; case 64: PokeB(mem, 0x149, 5); break; default: PokeB(mem, 0x149, 0); break; } /* Non-Japanese ROM */ PokeB(mem, 0x14a, 1); /* Old licensee */ if (option.is_super) { PokeB(mem, 0x14b, 0x33); } else { PokeB(mem, 0x14b, 0); } /* Header checksum */ hdr_csum = 0; for(f = 0x134 ; f < 0x14d; f++) { hdr_csum = hdr_csum - mem[f] - 1u; } PokeB(mem, 0x14d, hdr_csum); /* Global checksum */ global_csum = 0; if (count == 1) { for(f = 0; f < 0x14e; f++) { global_csum += mem[f]; } for(f = 0x150; f < 0x8000; f++) { global_csum += mem[f]; } } else { int r; for(f = 0; f < 0x14e; f++) { global_csum += mem[f]; } for(f = 0x150; f < 0x4000; f++) { global_csum += mem[f]; } for(r = 1; r < count; r++) { for(f = 0x4000; f < 0x8000; f++) { global_csum += bank[r]->memory[f]; } } } PokeB(mem, 0x14e, global_csum >> 8); PokeB(mem, 0x14f, global_csum); /* Output the ROM contents */ if (count == 1) { fwrite(mem, 0x8000, 1, fp); } else { int r; fwrite(mem, 0x4000, 1, fp); for(r = 1; r < count; r++) { for(f = 0x4000; f < 0x8000; f++) { fwrite(bank[r]->memory + 0x4000, 0x4000, 1, fp); } } } fclose(fp); return TRUE; } /* vim: ai sw=4 ts=8 expandtab */