From 58540ff2f5c92c01a55793a8deabc37231eff8e2 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Wed, 12 Jul 2017 00:51:43 -0400 Subject: [PATCH] Add support for BIOS. --- src/config.c | 69 ++++++++++++++++++++++++++++++++++++---------------------- src/config.h | 3 ++- src/emulator.c | 8 +++++++ src/gamegear.c | 16 +++++++++++--- src/gamegear.h | 1 + src/io.c | 17 +++++++++++---- src/io.h | 6 +++-- src/mmu.c | 21 ++++++++++++++++++ src/mmu.h | 4 ++++ src/rom.c | 53 +++++++++++++++++++++++++++++++++++++++++++- src/rom.h | 10 ++++++++- 11 files changed, 170 insertions(+), 38 deletions(-) diff --git a/src/config.c b/src/config.c index 1ddd446..98d6e5e 100644 --- a/src/config.c +++ b/src/config.c @@ -25,23 +25,25 @@ typedef struct { */ static void print_help(const char *arg1) { - printf("%s [-h] [-v] [-f] [-s ] [] ...\n" + printf("%s [-h] [-v] [-f] [-s | -n] [] ...\n" "\n" "basic options:\n" " -h, --help show this help message and exit\n" " -v, --version show crater's version number and exit\n" " -f, --fullscreen enable fullscreen mode\n" +" -s, --save save cartridge RAM (\"battery save\") to the given file\n" +" (defaults to .sav)\n" +" -n, --no-save disable saving cartridge RAM entirely\n" " path to the rom file to execute; if not given, will look\n" " in the roms/ directory and prompt the user\n" "\n" "advanced options:\n" " -g, --debug show logging information while running; add twice (-gg)\n" " to show more detailed logs, including an emulator trace\n" +" -b, --bios load BIOS from the given ROM file (no default;\n" +" the Game Gear does not usually require BIOS)\n" " -x, --scale scale the game screen by an integer factor\n" " (applies to windowed mode only; defaults to 4)\n" -" -s, --save save cartridge RAM (\"battery save\") to the given file\n" -" (defaults to .sav)\n" -" -n, --no-save disable saving cartridge RAM entirely\n" " -a, --assemble []\n" " convert z80 assembly source code into a binary file that\n" " can be run by crater\n" @@ -230,19 +232,6 @@ static int parse_opt_arg(Config *config, Arguments *args, const char *arg) else if (arg_check(arg, "f", "fullscreen")) { config->fullscreen = true; } - else if (arg_check(arg, "x", "scale")) { - const char *next = consume_next(args); - if (!next) { - ERROR("the scale option requires an argument") - return CONFIG_EXIT_FAILURE; - } - long scale = strtol(next, NULL, 10); - if (scale <= 0 || scale > SCALE_MAX) { - ERROR("scale factor of %s is not an integer or is out of range", next) - return CONFIG_EXIT_FAILURE; - } - config->scale = scale; - } else if (arg_check(arg, "s", "save")) { const char *next = consume_next(args); if (!next) { @@ -258,6 +247,28 @@ static int parse_opt_arg(Config *config, Arguments *args, const char *arg) else if (arg_check(arg, "g", "debug")) { config->debug++; } + else if (arg_check(arg, "b", "bios")) { + const char *next = consume_next(args); + if (!next) { + ERROR("the bios option requires an argument") + return CONFIG_EXIT_FAILURE; + } + free(config->bios_path); + config->bios_path = cr_strdup(next); + } + else if (arg_check(arg, "x", "scale")) { + const char *next = consume_next(args); + if (!next) { + ERROR("the scale option requires an argument") + return CONFIG_EXIT_FAILURE; + } + long scale = strtol(next, NULL, 10); + if (scale <= 0 || scale > SCALE_MAX) { + ERROR("scale factor of %s is not an integer or is out of range", next) + return CONFIG_EXIT_FAILURE; + } + config->scale = scale; + } else if (arg_check(arg, "a", "assemble")) { if (args->paths_read >= 1) { config->src_path = config->rom_path; @@ -347,7 +358,10 @@ static bool sanity_check(Config *config) { bool assembler = config->assemble || config->disassemble; - if (config->fullscreen && config->scale) { + if (config->sav_path && config->no_saving) { + ERROR("cannot use a save game file if saving is disabled") + return false; + } else if (config->fullscreen && config->scale) { ERROR("cannot specify a scale in fullscreen mode") return false; } else if (config->assemble && config->disassemble) { @@ -408,13 +422,14 @@ int config_create(Config** config_ptr, int argc, char* argv[]) config->assemble = false; config->disassemble = false; config->fullscreen = false; + config->no_saving = false; config->scale = 0; config->rom_path = NULL; config->sav_path = NULL; + config->bios_path = NULL; config->src_path = NULL; config->dst_path = NULL; config->overwrite = false; - config->no_saving = false; retval = parse_args(config, argc, argv); if (retval == CONFIG_OK && !(sanity_check(config) && set_defaults(config))) @@ -435,6 +450,7 @@ void config_destroy(Config *config) { free(config->rom_path); free(config->sav_path); + free(config->bios_path); free(config->src_path); free(config->dst_path); free(config); @@ -448,14 +464,15 @@ void config_dump_args(const Config* config) { DEBUG("Dumping arguments:") DEBUG("- debug: %d", config->debug) - DEBUG("- assemble: %s", config->assemble ? "true" : "false") + DEBUG("- assemble: %s", config->assemble ? "true" : "false") DEBUG("- disassemble: %s", config->disassemble ? "true" : "false") - DEBUG("- fullscreen: %s", config->fullscreen ? "true" : "false") + DEBUG("- fullscreen: %s", config->fullscreen ? "true" : "false") + DEBUG("- no_saving: %s", config->no_saving ? "true" : "false") DEBUG("- scale: %d", config->scale) - DEBUG("- rom_path: %s", config->rom_path ? config->rom_path : "(null)") - DEBUG("- sav_path: %s", config->sav_path ? config->sav_path : "(null)") - DEBUG("- src_path: %s", config->src_path ? config->src_path : "(null)") - DEBUG("- dst_path: %s", config->dst_path ? config->dst_path : "(null)") + DEBUG("- rom_path: %s", config->rom_path ? config->rom_path : "(null)") + DEBUG("- sav_path: %s", config->sav_path ? config->sav_path : "(null)") + DEBUG("- bios_path: %s", config->bios_path ? config->bios_path : "(null)") + DEBUG("- src_path: %s", config->src_path ? config->src_path : "(null)") + DEBUG("- dst_path: %s", config->dst_path ? config->dst_path : "(null)") DEBUG("- overwrite: %s", config->overwrite ? "true" : "false") - DEBUG("- no_saving: %s", config->no_saving ? "true" : "false") } diff --git a/src/config.h b/src/config.h index e266948..0d8a8be 100644 --- a/src/config.h +++ b/src/config.h @@ -25,13 +25,14 @@ typedef struct { bool assemble; bool disassemble; bool fullscreen; + bool no_saving; unsigned scale; char *rom_path; char *sav_path; + char *bios_path; char *src_path; char *dst_path; bool overwrite; - bool no_saving; } Config; /* Functions */ diff --git a/src/emulator.c b/src/emulator.c index f2c04eb..89373da 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -183,6 +183,12 @@ void emulate(ROM *rom, Config *config) return; } + BIOS *bios = NULL; + if (config->bios_path) { + if (!(bios = bios_open(config->bios_path))) + return; + } + emu.gg = gamegear_create(); signal(SIGINT, handle_sigint); setup_graphics(config->fullscreen, config->scale); @@ -190,6 +196,8 @@ void emulate(ROM *rom, Config *config) gamegear_attach_callback(emu.gg, frame_callback); gamegear_attach_display(emu.gg, emu.pixels); gamegear_load_rom(emu.gg, rom); + if (bios) + gamegear_load_bios(emu.gg, bios); if (!config->no_saving) gamegear_load_save(emu.gg, &save); diff --git a/src/gamegear.c b/src/gamegear.c index bf7436a..23b9013 100644 --- a/src/gamegear.c +++ b/src/gamegear.c @@ -30,7 +30,7 @@ GameGear* gamegear_create() mmu_init(&gg->mmu); vdp_init(&gg->vdp); psg_init(&gg->psg); - io_init(&gg->io, &gg->vdp, &gg->psg); + io_init(&gg->io, &gg->mmu, &gg->vdp, &gg->psg); z80_init(&gg->cpu, &gg->mmu, &gg->io); gg->powered = false; @@ -63,11 +63,22 @@ void gamegear_load_rom(GameGear *gg, const ROM *rom) { if (gg->powered) return; - mmu_load_rom(&gg->mmu, rom->data, rom->size); } /* + Load BIOS into the GameGear object. + + The same rules with gamegear_load_rom() apply here. +*/ +void gamegear_load_bios(GameGear *gg, const BIOS *bios) +{ + if (gg->powered) + return; + mmu_load_bios(&gg->mmu, bios->data); +} + +/* Load a game save into the GameGear object. The same rules with gamegear_load_rom() apply here. @@ -76,7 +87,6 @@ void gamegear_load_save(GameGear *gg, Save *save) { if (gg->powered) return; - mmu_load_save(&gg->mmu, save); } diff --git a/src/gamegear.h b/src/gamegear.h index a8c71a4..420c058 100644 --- a/src/gamegear.h +++ b/src/gamegear.h @@ -50,6 +50,7 @@ typedef enum { GameGear* gamegear_create(); void gamegear_destroy(GameGear*); void gamegear_load_rom(GameGear*, const ROM*); +void gamegear_load_bios(GameGear*, const BIOS*); void gamegear_load_save(GameGear*, Save*); void gamegear_simulate(GameGear*); void gamegear_input(GameGear*, GGButton, bool); diff --git a/src/io.c b/src/io.c index 27e39e6..16b7823 100644 --- a/src/io.c +++ b/src/io.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2016 Ben Kurtovic +/* Copyright (C) 2014-2017 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ #include "io.h" @@ -7,9 +7,10 @@ /* Initialize an IO object. */ -void io_init(IO *io, VDP *vdp, PSG *psg) +void io_init(IO *io, MMU *mmu, VDP *vdp, PSG *psg) { io->vdp = vdp; + io->mmu = mmu; io->psg = psg; } @@ -92,6 +93,14 @@ static void write_system_port(IO *io, uint8_t port, uint8_t value) } /* + Write to the memory control port, $3E. +*/ +static void write_memory_control(IO *io, uint8_t value) +{ + io->mmu->bios_enabled = !(value & 0x08); +} + +/* Read and return a byte from the given port. */ uint8_t io_port_read(IO *io, uint8_t port) @@ -111,7 +120,7 @@ uint8_t io_port_read(IO *io, uint8_t port) else if (port == 0xCD || port == 0xDC) return io->buttons; else if (port == 0xC1 || port == 0xDD) - return 0xFF; // TODO + return 0xFF; // B/Misc port, always set (unless in SMS mode?) else return 0xFF; } @@ -124,7 +133,7 @@ void io_port_write(IO *io, uint8_t port, uint8_t value) if (port <= 0x06) write_system_port(io, port, value); else if (port <= 0x3F && !(port % 2)) - return; // TODO: Write to memory control register + write_memory_control(io, value); else if (port <= 0x3F) return; // TODO: Write to I/O control register else if (port <= 0x7F) diff --git a/src/io.h b/src/io.h index 52c9633..3b41ff6 100644 --- a/src/io.h +++ b/src/io.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2016 Ben Kurtovic +/* Copyright (C) 2014-2017 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ #pragma once @@ -6,12 +6,14 @@ #include #include +#include "mmu.h" #include "psg.h" #include "vdp.h" /* Structs */ typedef struct { + MMU *mmu; VDP *vdp; PSG *psg; uint8_t ports[6]; @@ -21,7 +23,7 @@ typedef struct { /* Functions */ -void io_init(IO*, VDP*, PSG*); +void io_init(IO*, MMU*, VDP*, PSG*); void io_power(IO*); bool io_check_irq(IO*); void io_set_button(IO*, uint8_t, bool); diff --git a/src/mmu.c b/src/mmu.c index 377c0e4..df67a68 100644 --- a/src/mmu.c +++ b/src/mmu.c @@ -17,8 +17,10 @@ void mmu_init(MMU *mmu) mmu->system_ram = cr_malloc(sizeof(uint8_t) * MMU_SYSTEM_RAM_SIZE); mmu->cart_ram = NULL; mmu->cart_ram_slot = NULL; + mmu->bios_rom = NULL; mmu->cart_ram_mapped = false; mmu->cart_ram_external = false; + mmu->bios_enabled = false; mmu->save = NULL; for (size_t slot = 0; slot < MMU_NUM_SLOTS; slot++) @@ -89,6 +91,20 @@ void mmu_load_rom(MMU *mmu, const uint8_t *data, size_t size) } /* + Load BIOS into the MMU. + + The BIOS must be exactly 1KB. (Extra bytes will be ignored; if it is too + small, the system will crash.) The BIOS will automatically be enabled when + the MMU is powered on. + + Pass in NULL to disable the BIOS. +*/ +void mmu_load_bios(MMU *mmu, const uint8_t *data) +{ + mmu->bios_rom = data; +} + +/* Load a save into the MMU. If the save has valid cartridge RAM from a previous game, we will load that @@ -133,6 +149,9 @@ void mmu_power(MMU *mmu) map_rom_slot(mmu, slot, slot); memset(mmu->system_ram, 0xFF, MMU_SYSTEM_RAM_SIZE); + + if (mmu->bios_rom) + mmu->bios_enabled = true; } /* @@ -153,6 +172,8 @@ static inline uint8_t bank_byte_read(const uint8_t* bank, uint16_t addr) uint8_t mmu_read_byte(const MMU *mmu, uint16_t addr) { if (addr < 0x0400) { // First kilobyte is unpaged, for interrupt handlers + if (mmu->bios_enabled && mmu->bios_rom) + return mmu->bios_rom[addr]; return bank_byte_read(mmu->rom_banks[0], addr); } else if (addr < 0x4000) { // Slot 0 (0x0400 - 0x3FFF) return bank_byte_read(mmu->rom_slots[0], addr); diff --git a/src/mmu.h b/src/mmu.h index ea482b2..77d0da7 100644 --- a/src/mmu.h +++ b/src/mmu.h @@ -23,7 +23,9 @@ typedef struct { const uint8_t *rom_slots[MMU_NUM_SLOTS]; const uint8_t *rom_banks[MMU_NUM_ROM_BANKS]; uint8_t *cart_ram_slot; + const uint8_t *bios_rom; bool cart_ram_mapped, cart_ram_external; + bool bios_enabled; Save *save; } MMU; @@ -32,8 +34,10 @@ typedef struct { void mmu_init(MMU*); void mmu_free(MMU*); void mmu_load_rom(MMU*, const uint8_t*, size_t); +void mmu_load_bios(MMU*, const uint8_t*); void mmu_load_save(MMU*, Save*); void mmu_power(MMU*); + uint8_t mmu_read_byte(const MMU*, uint16_t); uint16_t mmu_read_double(const MMU*, uint16_t); uint32_t mmu_read_quad(const MMU*, uint16_t); diff --git a/src/rom.c b/src/rom.c index d2e9215..fde92d2 100644 --- a/src/rom.c +++ b/src/rom.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2016 Ben Kurtovic +/* Copyright (C) 2014-2017 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ #include @@ -263,3 +263,54 @@ const char* rom_region(const ROM *rom) { return region_code_to_string(rom->region_code); } + +/* + Open a BIOS ROM from the given path. + + Return a BIOS pointer if successful, or NULL on error. An error message + will be printed. The BIOS must be deallocated with bios_close(). +*/ +BIOS* bios_open(const char *path) +{ + FILE *fp; + struct stat st; + + if (!(fp = fopen(path, "rb"))) { + ERROR("couldn't load BIOS '%s': fopen(): %s", path, strerror(errno)); + return NULL; + } + if (fstat(fileno(fp), &st)) { + ERROR("couldn't load BIOS '%s': fstat(): %s", path, strerror(errno)); + fclose(fp); + return NULL; + } + if (!(st.st_mode & S_IFREG)) { + ERROR("couldn't load BIOS '%s': not a regular file", path); + fclose(fp); + return NULL; + } + if (st.st_size != BIOS_SIZE) { + ERROR("couldn't load BIOS '%s': incorrect size", path); + fclose(fp); + return NULL; + } + + BIOS *bios = cr_malloc(sizeof(BIOS)); + if (!(fread(bios->data, BIOS_SIZE, 1, fp))) { + ERROR("couldn't load BIOS '%s': fread(): %s", path, strerror(errno)); + fclose(fp); + bios_close(bios); + return NULL; + } + + fclose(fp); + return bios; +} + +/* + Deallocate a BIOS object previously returned by bios_open(). +*/ +void bios_close(BIOS *bios) +{ + free(bios); +} diff --git a/src/rom.h b/src/rom.h index 7c356b0..0b65a16 100644 --- a/src/rom.h +++ b/src/rom.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2015 Ben Kurtovic +/* Copyright (C) 2014-2017 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ #pragma once @@ -9,6 +9,8 @@ #define ROM_SIZE_MIN (32 << 10) // 32 KB #define ROM_SIZE_MAX ( 1 << 20) // 1 MB +#define BIOS_SIZE 1024 + /* Header info */ #define HEADER_SIZE 16 @@ -40,9 +42,15 @@ typedef struct { uint8_t declared_size; } ROM; +typedef struct { + uint8_t data[BIOS_SIZE]; +} BIOS; + /* Functions */ const char* rom_open(ROM*, const char*); void rom_close(ROM*); const char* rom_product(const ROM*); const char* rom_region(const ROM*); +BIOS* bios_open(const char*); +void bios_close(BIOS*);