Browse Source

Add support for BIOS.

master
Ben Kurtovic 6 years ago
parent
commit
58540ff2f5
11 changed files with 170 additions and 38 deletions
  1. +43
    -26
      src/config.c
  2. +2
    -1
      src/config.h
  3. +8
    -0
      src/emulator.c
  4. +13
    -3
      src/gamegear.c
  5. +1
    -0
      src/gamegear.h
  6. +13
    -4
      src/io.c
  7. +4
    -2
      src/io.h
  8. +21
    -0
      src/mmu.c
  9. +4
    -0
      src/mmu.h
  10. +52
    -1
      src/rom.c
  11. +9
    -1
      src/rom.h

+ 43
- 26
src/config.c View File

@@ -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")
} }

+ 2
- 1
src/config.h View File

@@ -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 */


+ 8
- 0
src/emulator.c View File

@@ -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);




+ 13
- 3
src/gamegear.c View File

@@ -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);
} }




+ 1
- 0
src/gamegear.h View File

@@ -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);


+ 13
- 4
src/io.c View File

@@ -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)


+ 4
- 2
src/io.h View File

@@ -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);


+ 21
- 0
src/mmu.c View File

@@ -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);


+ 4
- 0
src/mmu.h View File

@@ -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);


+ 52
- 1
src/rom.c View File

@@ -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);
}

+ 9
- 1
src/rom.h View File

@@ -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*);

Loading…
Cancel
Save