| @@ -25,23 +25,25 @@ typedef struct { | |||||
| */ | */ | ||||
| static void print_help(const char *arg1) | static void print_help(const char *arg1) | ||||
| { | { | ||||
| printf("%s [-h] [-v] [-f] [-s <n>] [<rom_path>] ...\n" | |||||
| printf("%s [-h] [-v] [-f] [-s <save_path> | -n] [<rom_path>] ...\n" | |||||
| "\n" | "\n" | ||||
| "basic options:\n" | "basic options:\n" | ||||
| " -h, --help show this help message and exit\n" | " -h, --help show this help message and exit\n" | ||||
| " -v, --version show crater's version number and exit\n" | " -v, --version show crater's version number and exit\n" | ||||
| " -f, --fullscreen enable fullscreen mode\n" | " -f, --fullscreen enable fullscreen mode\n" | ||||
| " -s, --save <path> save cartridge RAM (\"battery save\") to the given file\n" | |||||
| " (defaults to <rom_path>.sav)\n" | |||||
| " -n, --no-save disable saving cartridge RAM entirely\n" | |||||
| " <rom_path> path to the rom file to execute; if not given, will look\n" | " <rom_path> path to the rom file to execute; if not given, will look\n" | ||||
| " in the roms/ directory and prompt the user\n" | " in the roms/ directory and prompt the user\n" | ||||
| "\n" | "\n" | ||||
| "advanced options:\n" | "advanced options:\n" | ||||
| " -g, --debug show logging information while running; add twice (-gg)\n" | " -g, --debug show logging information while running; add twice (-gg)\n" | ||||
| " to show more detailed logs, including an emulator trace\n" | " to show more detailed logs, including an emulator trace\n" | ||||
| " -b, --bios <path> load BIOS from the given ROM file (no default;\n" | |||||
| " the Game Gear does not usually require BIOS)\n" | |||||
| " -x, --scale <n> scale the game screen by an integer factor\n" | " -x, --scale <n> scale the game screen by an integer factor\n" | ||||
| " (applies to windowed mode only; defaults to 4)\n" | " (applies to windowed mode only; defaults to 4)\n" | ||||
| " -s, --save <path> save cartridge RAM (\"battery save\") to the given file\n" | |||||
| " (defaults to <rom_path>.sav)\n" | |||||
| " -n, --no-save disable saving cartridge RAM entirely\n" | |||||
| " -a, --assemble <in> [<out>]\n" | " -a, --assemble <in> [<out>]\n" | ||||
| " convert z80 assembly source code into a binary file that\n" | " convert z80 assembly source code into a binary file that\n" | ||||
| " can be run by crater\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")) { | else if (arg_check(arg, "f", "fullscreen")) { | ||||
| config->fullscreen = true; | 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")) { | else if (arg_check(arg, "s", "save")) { | ||||
| const char *next = consume_next(args); | const char *next = consume_next(args); | ||||
| if (!next) { | 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")) { | else if (arg_check(arg, "g", "debug")) { | ||||
| config->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")) { | else if (arg_check(arg, "a", "assemble")) { | ||||
| if (args->paths_read >= 1) { | if (args->paths_read >= 1) { | ||||
| config->src_path = config->rom_path; | config->src_path = config->rom_path; | ||||
| @@ -347,7 +358,10 @@ static bool sanity_check(Config *config) | |||||
| { | { | ||||
| bool assembler = config->assemble || config->disassemble; | 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") | ERROR("cannot specify a scale in fullscreen mode") | ||||
| return false; | return false; | ||||
| } else if (config->assemble && config->disassemble) { | } else if (config->assemble && config->disassemble) { | ||||
| @@ -408,13 +422,14 @@ int config_create(Config** config_ptr, int argc, char* argv[]) | |||||
| config->assemble = false; | config->assemble = false; | ||||
| config->disassemble = false; | config->disassemble = false; | ||||
| config->fullscreen = false; | config->fullscreen = false; | ||||
| config->no_saving = false; | |||||
| config->scale = 0; | config->scale = 0; | ||||
| config->rom_path = NULL; | config->rom_path = NULL; | ||||
| config->sav_path = NULL; | config->sav_path = NULL; | ||||
| config->bios_path = NULL; | |||||
| config->src_path = NULL; | config->src_path = NULL; | ||||
| config->dst_path = NULL; | config->dst_path = NULL; | ||||
| config->overwrite = false; | config->overwrite = false; | ||||
| config->no_saving = false; | |||||
| retval = parse_args(config, argc, argv); | retval = parse_args(config, argc, argv); | ||||
| if (retval == CONFIG_OK && !(sanity_check(config) && set_defaults(config))) | 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->rom_path); | ||||
| free(config->sav_path); | free(config->sav_path); | ||||
| free(config->bios_path); | |||||
| free(config->src_path); | free(config->src_path); | ||||
| free(config->dst_path); | free(config->dst_path); | ||||
| free(config); | free(config); | ||||
| @@ -448,14 +464,15 @@ void config_dump_args(const Config* config) | |||||
| { | { | ||||
| DEBUG("Dumping arguments:") | DEBUG("Dumping arguments:") | ||||
| DEBUG("- debug: %d", config->debug) | 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("- 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("- 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("- overwrite: %s", config->overwrite ? "true" : "false") | ||||
| DEBUG("- no_saving: %s", config->no_saving ? "true" : "false") | |||||
| } | } | ||||
| @@ -25,13 +25,14 @@ typedef struct { | |||||
| bool assemble; | bool assemble; | ||||
| bool disassemble; | bool disassemble; | ||||
| bool fullscreen; | bool fullscreen; | ||||
| bool no_saving; | |||||
| unsigned scale; | unsigned scale; | ||||
| char *rom_path; | char *rom_path; | ||||
| char *sav_path; | char *sav_path; | ||||
| char *bios_path; | |||||
| char *src_path; | char *src_path; | ||||
| char *dst_path; | char *dst_path; | ||||
| bool overwrite; | bool overwrite; | ||||
| bool no_saving; | |||||
| } Config; | } Config; | ||||
| /* Functions */ | /* Functions */ | ||||
| @@ -183,6 +183,12 @@ void emulate(ROM *rom, Config *config) | |||||
| return; | return; | ||||
| } | } | ||||
| BIOS *bios = NULL; | |||||
| if (config->bios_path) { | |||||
| if (!(bios = bios_open(config->bios_path))) | |||||
| return; | |||||
| } | |||||
| emu.gg = gamegear_create(); | emu.gg = gamegear_create(); | ||||
| signal(SIGINT, handle_sigint); | signal(SIGINT, handle_sigint); | ||||
| setup_graphics(config->fullscreen, config->scale); | 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_callback(emu.gg, frame_callback); | ||||
| gamegear_attach_display(emu.gg, emu.pixels); | gamegear_attach_display(emu.gg, emu.pixels); | ||||
| gamegear_load_rom(emu.gg, rom); | gamegear_load_rom(emu.gg, rom); | ||||
| if (bios) | |||||
| gamegear_load_bios(emu.gg, bios); | |||||
| if (!config->no_saving) | if (!config->no_saving) | ||||
| gamegear_load_save(emu.gg, &save); | gamegear_load_save(emu.gg, &save); | ||||
| @@ -30,7 +30,7 @@ GameGear* gamegear_create() | |||||
| mmu_init(&gg->mmu); | mmu_init(&gg->mmu); | ||||
| vdp_init(&gg->vdp); | vdp_init(&gg->vdp); | ||||
| psg_init(&gg->psg); | 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); | z80_init(&gg->cpu, &gg->mmu, &gg->io); | ||||
| gg->powered = false; | gg->powered = false; | ||||
| @@ -63,11 +63,22 @@ void gamegear_load_rom(GameGear *gg, const ROM *rom) | |||||
| { | { | ||||
| if (gg->powered) | if (gg->powered) | ||||
| return; | return; | ||||
| mmu_load_rom(&gg->mmu, rom->data, rom->size); | 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. | Load a game save into the GameGear object. | ||||
| The same rules with gamegear_load_rom() apply here. | The same rules with gamegear_load_rom() apply here. | ||||
| @@ -76,7 +87,6 @@ void gamegear_load_save(GameGear *gg, Save *save) | |||||
| { | { | ||||
| if (gg->powered) | if (gg->powered) | ||||
| return; | return; | ||||
| mmu_load_save(&gg->mmu, save); | mmu_load_save(&gg->mmu, save); | ||||
| } | } | ||||
| @@ -50,6 +50,7 @@ typedef enum { | |||||
| GameGear* gamegear_create(); | GameGear* gamegear_create(); | ||||
| void gamegear_destroy(GameGear*); | void gamegear_destroy(GameGear*); | ||||
| void gamegear_load_rom(GameGear*, const ROM*); | void gamegear_load_rom(GameGear*, const ROM*); | ||||
| void gamegear_load_bios(GameGear*, const BIOS*); | |||||
| void gamegear_load_save(GameGear*, Save*); | void gamegear_load_save(GameGear*, Save*); | ||||
| void gamegear_simulate(GameGear*); | void gamegear_simulate(GameGear*); | ||||
| void gamegear_input(GameGear*, GGButton, bool); | void gamegear_input(GameGear*, GGButton, bool); | ||||
| @@ -1,4 +1,4 @@ | |||||
| /* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| /* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
| #include "io.h" | #include "io.h" | ||||
| @@ -7,9 +7,10 @@ | |||||
| /* | /* | ||||
| Initialize an IO object. | 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->vdp = vdp; | ||||
| io->mmu = mmu; | |||||
| io->psg = psg; | 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. | Read and return a byte from the given port. | ||||
| */ | */ | ||||
| uint8_t io_port_read(IO *io, uint8_t 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) | else if (port == 0xCD || port == 0xDC) | ||||
| return io->buttons; | return io->buttons; | ||||
| else if (port == 0xC1 || port == 0xDD) | else if (port == 0xC1 || port == 0xDD) | ||||
| return 0xFF; // TODO | |||||
| return 0xFF; // B/Misc port, always set (unless in SMS mode?) | |||||
| else | else | ||||
| return 0xFF; | return 0xFF; | ||||
| } | } | ||||
| @@ -124,7 +133,7 @@ void io_port_write(IO *io, uint8_t port, uint8_t value) | |||||
| if (port <= 0x06) | if (port <= 0x06) | ||||
| write_system_port(io, port, value); | write_system_port(io, port, value); | ||||
| else if (port <= 0x3F && !(port % 2)) | else if (port <= 0x3F && !(port % 2)) | ||||
| return; // TODO: Write to memory control register | |||||
| write_memory_control(io, value); | |||||
| else if (port <= 0x3F) | else if (port <= 0x3F) | ||||
| return; // TODO: Write to I/O control register | return; // TODO: Write to I/O control register | ||||
| else if (port <= 0x7F) | else if (port <= 0x7F) | ||||
| @@ -1,4 +1,4 @@ | |||||
| /* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| /* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
| #pragma once | #pragma once | ||||
| @@ -6,12 +6,14 @@ | |||||
| #include <stdbool.h> | #include <stdbool.h> | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include "mmu.h" | |||||
| #include "psg.h" | #include "psg.h" | ||||
| #include "vdp.h" | #include "vdp.h" | ||||
| /* Structs */ | /* Structs */ | ||||
| typedef struct { | typedef struct { | ||||
| MMU *mmu; | |||||
| VDP *vdp; | VDP *vdp; | ||||
| PSG *psg; | PSG *psg; | ||||
| uint8_t ports[6]; | uint8_t ports[6]; | ||||
| @@ -21,7 +23,7 @@ typedef struct { | |||||
| /* Functions */ | /* Functions */ | ||||
| void io_init(IO*, VDP*, PSG*); | |||||
| void io_init(IO*, MMU*, VDP*, PSG*); | |||||
| void io_power(IO*); | void io_power(IO*); | ||||
| bool io_check_irq(IO*); | bool io_check_irq(IO*); | ||||
| void io_set_button(IO*, uint8_t, bool); | void io_set_button(IO*, uint8_t, bool); | ||||
| @@ -17,8 +17,10 @@ void mmu_init(MMU *mmu) | |||||
| mmu->system_ram = cr_malloc(sizeof(uint8_t) * MMU_SYSTEM_RAM_SIZE); | mmu->system_ram = cr_malloc(sizeof(uint8_t) * MMU_SYSTEM_RAM_SIZE); | ||||
| mmu->cart_ram = NULL; | mmu->cart_ram = NULL; | ||||
| mmu->cart_ram_slot = NULL; | mmu->cart_ram_slot = NULL; | ||||
| mmu->bios_rom = NULL; | |||||
| mmu->cart_ram_mapped = false; | mmu->cart_ram_mapped = false; | ||||
| mmu->cart_ram_external = false; | mmu->cart_ram_external = false; | ||||
| mmu->bios_enabled = false; | |||||
| mmu->save = NULL; | mmu->save = NULL; | ||||
| for (size_t slot = 0; slot < MMU_NUM_SLOTS; slot++) | 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. | Load a save into the MMU. | ||||
| If the save has valid cartridge RAM from a previous game, we will load that | 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); | map_rom_slot(mmu, slot, slot); | ||||
| memset(mmu->system_ram, 0xFF, MMU_SYSTEM_RAM_SIZE); | 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) | uint8_t mmu_read_byte(const MMU *mmu, uint16_t addr) | ||||
| { | { | ||||
| if (addr < 0x0400) { // First kilobyte is unpaged, for interrupt handlers | 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); | return bank_byte_read(mmu->rom_banks[0], addr); | ||||
| } else if (addr < 0x4000) { // Slot 0 (0x0400 - 0x3FFF) | } else if (addr < 0x4000) { // Slot 0 (0x0400 - 0x3FFF) | ||||
| return bank_byte_read(mmu->rom_slots[0], addr); | return bank_byte_read(mmu->rom_slots[0], addr); | ||||
| @@ -23,7 +23,9 @@ typedef struct { | |||||
| const uint8_t *rom_slots[MMU_NUM_SLOTS]; | const uint8_t *rom_slots[MMU_NUM_SLOTS]; | ||||
| const uint8_t *rom_banks[MMU_NUM_ROM_BANKS]; | const uint8_t *rom_banks[MMU_NUM_ROM_BANKS]; | ||||
| uint8_t *cart_ram_slot; | uint8_t *cart_ram_slot; | ||||
| const uint8_t *bios_rom; | |||||
| bool cart_ram_mapped, cart_ram_external; | bool cart_ram_mapped, cart_ram_external; | ||||
| bool bios_enabled; | |||||
| Save *save; | Save *save; | ||||
| } MMU; | } MMU; | ||||
| @@ -32,8 +34,10 @@ typedef struct { | |||||
| void mmu_init(MMU*); | void mmu_init(MMU*); | ||||
| void mmu_free(MMU*); | void mmu_free(MMU*); | ||||
| void mmu_load_rom(MMU*, const uint8_t*, size_t); | 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_load_save(MMU*, Save*); | ||||
| void mmu_power(MMU*); | void mmu_power(MMU*); | ||||
| uint8_t mmu_read_byte(const MMU*, uint16_t); | uint8_t mmu_read_byte(const MMU*, uint16_t); | ||||
| uint16_t mmu_read_double(const MMU*, uint16_t); | uint16_t mmu_read_double(const MMU*, uint16_t); | ||||
| uint32_t mmu_read_quad(const MMU*, uint16_t); | uint32_t mmu_read_quad(const MMU*, uint16_t); | ||||
| @@ -1,4 +1,4 @@ | |||||
| /* Copyright (C) 2014-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| /* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
| #include <ctype.h> | #include <ctype.h> | ||||
| @@ -263,3 +263,54 @@ const char* rom_region(const ROM *rom) | |||||
| { | { | ||||
| return region_code_to_string(rom->region_code); | 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); | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| /* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| /* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
| #pragma once | #pragma once | ||||
| @@ -9,6 +9,8 @@ | |||||
| #define ROM_SIZE_MIN (32 << 10) // 32 KB | #define ROM_SIZE_MIN (32 << 10) // 32 KB | ||||
| #define ROM_SIZE_MAX ( 1 << 20) // 1 MB | #define ROM_SIZE_MAX ( 1 << 20) // 1 MB | ||||
| #define BIOS_SIZE 1024 | |||||
| /* Header info */ | /* Header info */ | ||||
| #define HEADER_SIZE 16 | #define HEADER_SIZE 16 | ||||
| @@ -40,9 +42,15 @@ typedef struct { | |||||
| uint8_t declared_size; | uint8_t declared_size; | ||||
| } ROM; | } ROM; | ||||
| typedef struct { | |||||
| uint8_t data[BIOS_SIZE]; | |||||
| } BIOS; | |||||
| /* Functions */ | /* Functions */ | ||||
| const char* rom_open(ROM*, const char*); | const char* rom_open(ROM*, const char*); | ||||
| void rom_close(ROM*); | void rom_close(ROM*); | ||||
| const char* rom_product(const ROM*); | const char* rom_product(const ROM*); | ||||
| const char* rom_region(const ROM*); | const char* rom_region(const ROM*); | ||||
| BIOS* bios_open(const char*); | |||||
| void bios_close(BIOS*); | |||||