* GB: integrated SameBoy v0.12.1 by Lior Halphon
* SFC: added HG51B169 (Cx4) math tables into bsnes binary
This commit is contained in:
byuu 2019-07-17 21:11:46 +09:00
parent 382e192647
commit 903d1e4012
743 changed files with 1460 additions and 5327 deletions

View File

@ -32,7 +32,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "bsnes";
static const string Version = "107.7";
static const string Version = "107.8";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";

0
bsnes/filter/snes_ntsc/snes_ntsc.c Normal file → Executable file
View File

0
bsnes/filter/snes_ntsc/snes_ntsc.h Normal file → Executable file
View File

0
bsnes/filter/snes_ntsc/snes_ntsc_config.h Normal file → Executable file
View File

0
bsnes/filter/snes_ntsc/snes_ntsc_impl.h Normal file → Executable file
View File

View File

@ -506,6 +506,11 @@ void GB_apu_run(GB_gameboy_t *gb)
void GB_apu_init(GB_gameboy_t *gb)
{
memset(&gb->apu, 0, sizeof(gb->apu));
/* Restore the wave form */
for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) {
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4;
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF;
}
gb->apu.lf_div = 1;
/* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on,
the first DIV/APU event is skipped. */
@ -556,14 +561,14 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
{
if (!gb->apu.global_enable && reg != GB_IO_NR52 && (GB_is_cgb(gb) ||
(
reg != GB_IO_NR11 &&
reg != GB_IO_NR21 &&
reg != GB_IO_NR31 &&
reg != GB_IO_NR41
)
)) {
if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) ||
(
reg != GB_IO_NR11 &&
reg != GB_IO_NR21 &&
reg != GB_IO_NR31 &&
reg != GB_IO_NR41
)
)) {
return;
}

View File

@ -2172,7 +2172,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
unsigned bank, address;
char symbol[length];
if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) {
if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) {
bank &= 0x1FF;
if (!gb->bank_symbols[bank]) {
gb->bank_symbols[bank] = GB_map_alloc();

View File

@ -123,8 +123,8 @@ static void display_vblank(GB_gameboy_t *gb)
{
gb->vblank_just_occured = true;
/* TODO: Slow in trubo mode! */
if (GB_is_sgb(gb)) {
/* TODO: Slow in turbo mode! */
if (GB_is_hle_sgb(gb)) {
GB_sgb_render(gb);
}
@ -137,9 +137,8 @@ static void display_vblank(GB_gameboy_t *gb)
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
if (gb->sgb) {
uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? 0x3 : 0x0;
for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb->sgb->screen_buffer[i] = color;
gb->sgb->screen_buffer[i] = 0x0;
}
}
else {
@ -377,6 +376,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
}
}
uint8_t icd_pixel = 0;
{
uint8_t pixel = bg_enabled? fifo_item->pixel : 0;
if (pixel && bg_priority) {
@ -387,7 +388,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
}
if (gb->sgb) {
if (gb->current_lcd_line < LINES) {
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel;
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
}
}
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
//gb->icd_pixel_callback(gb, pixel);
}
}
else {
@ -403,7 +410,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
}
if (gb->sgb) {
if (gb->current_lcd_line < LINES) {
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel;
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
}
}
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
//gb->icd_pixel_callback(gb, pixel);
}
}
else {
@ -411,6 +424,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
}
}
/* byuu: gotta do this later so it's only done once ... you'll probably wanna refactor this somehow :/ */
if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
gb->icd_pixel_callback(gb, icd_pixel);
}
}
gb->position_in_line++;
}
@ -890,6 +910,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
}
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
gb->mode_for_interrupt = 2;
/* TODO: Can this timing even be verified? */
if (gb->icd_hreset_callback) {
gb->icd_hreset_callback(gb);
}
}
/* Lines 144 - 152 */
@ -961,7 +986,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->wy_diff = 0;
gb->window_disabled_while_active = false;
gb->current_line = 0;
gb->current_lcd_line = -1; // TODO: not the correct timing
// TODO: not the correct timing
gb->current_lcd_line = -1;
if (gb->icd_vreset_callback) {
gb->icd_vreset_callback(gb);
}
}
}

View File

@ -112,6 +112,11 @@ void GB_init(GB_gameboy_t *gb, GB_model_t model)
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
gb->clock_multiplier = 1.0;
if (model & GB_MODEL_NO_SFC_BIT) {
/* Disable time syncing. Timing should be done by the SFC emulator. */
gb->turbo = true;
}
GB_reset(gb);
}
@ -193,26 +198,27 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path)
}
gb->rom = malloc(gb->rom_size);
memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */
fread(gb->rom, gb->rom_size, 1, f);
fread(gb->rom, 1, gb->rom_size, f);
fclose(f);
GB_configure_cart(gb);
return 0;
}
/* byuu */
int GB_load_rom_from_buffer(GB_gameboy_t* gb, const unsigned char* buffer, size_t size) {
gb->rom_size = (size + 0x3fff) & ~0x3fff;
while(gb->rom_size & (gb->rom_size - 1)) {
gb->rom_size |= gb->rom_size >> 1;
gb->rom_size++;
}
if(gb->rom) free(gb->rom);
gb->rom = (uint8_t*)malloc(gb->rom_size);
memset(gb->rom, 0xff, gb->rom_size);
memcpy(gb->rom, buffer, size);
GB_configure_cart(gb);
return 0;
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
{
gb->rom_size = (size + 0x3fff) & ~0x3fff;
while (gb->rom_size & (gb->rom_size - 1)) {
gb->rom_size |= gb->rom_size >> 1;
gb->rom_size++;
}
if (gb->rom) {
free(gb->rom);
}
gb->rom = malloc(gb->rom_size);
memset(gb->rom, 0xff, gb->rom_size);
memcpy(gb->rom, buffer, size);
GB_configure_cart(gb);
}
typedef struct {
@ -548,6 +554,11 @@ bool GB_is_cgb(GB_gameboy_t *gb)
}
bool GB_is_sgb(GB_gameboy_t *gb)
{
return (gb->model & ~GB_MODEL_PAL_BIT & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB || (gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2;
}
bool GB_is_hle_sgb(GB_gameboy_t *gb)
{
return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2;
}
@ -586,6 +597,7 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NO_SFC:
for (unsigned i = 0; i < gb->ram_size; i++) {
gb->ram[i] = GB_random();
if (i & 0x100) {
@ -598,6 +610,7 @@ static void reset_ram(GB_gameboy_t *gb)
break;
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < gb->ram_size; i++) {
gb->ram[i] = 0x55;
gb->ram[i] ^= GB_random() & GB_random() & GB_random();
@ -630,7 +643,9 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < sizeof(gb->hram); i++) {
if (i & 1) {
gb->hram[i] = GB_random() | GB_random() | GB_random();
@ -651,9 +666,11 @@ static void reset_ram(GB_gameboy_t *gb)
break;
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_NTSC: /* Unverified */
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < 8; i++) {
if (i & 2) {
gb->oam[i] = GB_random() & GB_random() & GB_random();
@ -679,7 +696,9 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB2: {
case GB_MODEL_SGB_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC: {
uint8_t temp;
for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) {
if (i & 1) {
@ -760,7 +779,7 @@ void GB_reset(GB_gameboy_t *gb)
gb->accessed_oam_row = -1;
if (GB_is_sgb(gb)) {
if (GB_is_hle_sgb(gb)) {
if (!gb->sgb) {
gb->sgb = malloc(sizeof(*gb->sgb));
}
@ -894,17 +913,17 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb)
unsigned GB_get_screen_width(GB_gameboy_t *gb)
{
return GB_is_sgb(gb)? 256 : 160;
return GB_is_hle_sgb(gb)? 256 : 160;
}
unsigned GB_get_screen_height(GB_gameboy_t *gb)
{
return GB_is_sgb(gb)? 224 : 144;
return GB_is_hle_sgb(gb)? 224 : 144;
}
unsigned GB_get_player_count(GB_gameboy_t *gb)
{
return GB_is_sgb(gb)? gb->sgb->player_count : 1;
return GB_is_hle_sgb(gb)? gb->sgb->player_count : 1;
}
void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback)
@ -916,3 +935,24 @@ double GB_get_usual_frame_rate(GB_gameboy_t *gb)
{
return GB_get_clock_rate(gb) / (double)LCDC_PERIOD;
}
void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback)
{
gb->joyp_write_callback = callback;
}
void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback)
{
gb->icd_pixel_callback = callback;
}
void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback)
{
gb->icd_hreset_callback = callback;
}
void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback)
{
gb->icd_vreset_callback = callback;
}

View File

@ -23,13 +23,14 @@
#define GB_STRUCT_VERSION 13
#ifdef GB_INTERNAL
#define GB_MODEL_FAMILY_MASK 0xF00
#define GB_MODEL_DMG_FAMILY 0x000
#define GB_MODEL_MGB_FAMILY 0x100
#define GB_MODEL_CGB_FAMILY 0x200
#define GB_MODEL_PAL_BIT 0x1000
#define GB_MODEL_NO_SFC_BIT 0x2000
#ifdef GB_INTERNAL
#if __clang__
#define UNROLL _Pragma("unroll")
#elif __GNUC__
@ -67,9 +68,11 @@ typedef enum {
// GB_MODEL_DMG_C = 0x003,
GB_MODEL_SGB = 0x004,
GB_MODEL_SGB_NTSC = GB_MODEL_SGB,
GB_MODEL_SGB_PAL = 0x1004,
GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT,
GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT,
// GB_MODEL_MGB = 0x100,
GB_MODEL_SGB2 = 0x101,
GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT,
// GB_MODEL_CGB_0 = 0x200,
// GB_MODEL_CGB_A = 0x201,
// GB_MODEL_CGB_B = 0x202,
@ -239,6 +242,10 @@ typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on);
typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send);
typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value);
typedef void (*GB_icd_pixel_callback_t)(GB_gameboy_t *gb, uint8_t row);
typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb);
typedef struct {
bool state;
@ -444,7 +451,8 @@ struct GB_gameboy_internal_s {
/* The LCDC will skip the first frame it renders after turning it on.
On the CGB, a frame is not skipped if the previous frame was skipped as well.
See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */
/* TODO: Drop this and properly emulate the dropped vreset signal*/
/* TODO: Drop this and properly emulate the dropped vreset signal*/
enum {
GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state,
// on a CGB, the previous frame is repeated (which might be
@ -530,6 +538,10 @@ struct GB_gameboy_internal_s {
GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback;
GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback;
GB_update_input_hint_callback_t update_input_hint_callback;
GB_joyp_write_callback_t joyp_write_callback;
GB_icd_pixel_callback_t icd_pixel_callback;
GB_icd_vreset_callback_t icd_hreset_callback;
GB_icd_vreset_callback_t icd_vreset_callback;
/* IR */
long cycles_since_ir_change; // In 8MHz units
@ -617,13 +629,14 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
void GB_init(GB_gameboy_t *gb, GB_model_t model);
bool GB_is_inited(GB_gameboy_t *gb);
bool GB_is_cgb(GB_gameboy_t *gb);
bool GB_is_sgb(GB_gameboy_t *gb);
bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2
bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd
GB_model_t GB_get_model(GB_gameboy_t *gb);
void GB_free(GB_gameboy_t *gb);
void GB_reset(GB_gameboy_t *gb);
void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model);
/* Returns the time passed, in 4MHz ticks. */
/* Returns the time passed, in 8MHz ticks. */
uint8_t GB_run(GB_gameboy_t *gb);
/* Returns the time passed since the last frame, in nanoseconds */
uint64_t GB_run_frame(GB_gameboy_t *gb);
@ -654,7 +667,7 @@ void GB_set_user_data(GB_gameboy_t *gb, void *data);
int GB_load_boot_rom(GB_gameboy_t *gb, const char *path);
void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size);
int GB_load_rom(GB_gameboy_t *gb, const char *path);
int GB_load_rom_from_buffer(GB_gameboy_t* gb, const unsigned char* buffer, size_t size);
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size);
int GB_save_battery(GB_gameboy_t *gb, const char *path);
void GB_load_battery(GB_gameboy_t *gb, const char *path);
@ -689,6 +702,12 @@ void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data);
void GB_disconnect_serial(GB_gameboy_t *gb);
/* For integration with SFC/SNES emulators */
void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback);
void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback);
void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback);
void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback);
#ifdef GB_INTERNAL
uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
#endif

View File

@ -3,6 +3,8 @@
void GB_update_joyp(GB_gameboy_t *gb)
{
if (gb->model & GB_MODEL_SGB_NO_SFC) return;
uint8_t key_selection = 0;
uint8_t previous_state = 0;
@ -53,14 +55,27 @@ void GB_update_joyp(GB_gameboy_t *gb)
break;
}
/* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
/* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */
/* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */
gb->io_registers[GB_IO_IF] |= 0x10;
}
gb->io_registers[GB_IO_JOYP] |= 0xC0;
}
void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value)
{
uint8_t previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
gb->io_registers[GB_IO_JOYP] &= 0xF0;
gb->io_registers[GB_IO_JOYP] |= value & 0xF;
if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) {
gb->io_registers[GB_IO_IF] |= 0x10;
}
}
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed)
{
assert(index >= 0 && index < GB_KEY_MAX);

View File

@ -17,6 +17,7 @@ typedef enum {
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed);
void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed);
void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value);
#ifdef GB_INTERNAL
void GB_update_joyp(GB_gameboy_t *gb);

View File

@ -273,7 +273,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
;
}
}
@ -584,7 +586,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
break;
@ -663,7 +667,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
/* These are the states when LY changes, let the display routine call GB_STAT_update for use
so it correctly handles T-cycle accurate LYC writes */
if (!GB_is_cgb(gb) || (
gb->display_state != 6 &&
gb->display_state != 35 &&
gb->display_state != 26 &&
gb->display_state != 15 &&
gb->display_state != 16)) {
@ -737,8 +741,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
case GB_IO_JOYP:
GB_sgb_write(gb, value);
gb->io_registers[GB_IO_JOYP] = value & 0xF0;
GB_sgb_write(gb, value);
GB_update_joyp(gb);
return;

View File

@ -36,7 +36,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
if (!DUMP_SECTION(gb, f, rtc )) goto error;
if (!DUMP_SECTION(gb, f, video )) goto error;
if (GB_is_sgb(gb)) {
if (GB_is_hle_sgb(gb)) {
if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
}
@ -73,7 +73,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
+ GB_SECTION_SIZE(apu ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(rtc ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(video ) + sizeof(uint32_t)
+ (GB_is_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
+ (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
+ gb->mbc_ram_size
+ gb->ram_size
+ gb->vram_size;
@ -105,7 +105,7 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
DUMP_SECTION(gb, buffer, rtc );
DUMP_SECTION(gb, buffer, video );
if (GB_is_sgb(gb)) {
if (GB_is_hle_sgb(gb)) {
buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb));
}
@ -161,8 +161,8 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
return false;
}
if (GB_is_sgb(gb) != GB_is_sgb(save)) {
GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_sgb(save)? "" : "not ");
if (GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) {
GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_hle_sgb(save)? "" : "not ");
return false;
}
@ -223,7 +223,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
goto error;
}
if (GB_is_sgb(gb)) {
if (GB_is_hle_sgb(gb)) {
if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
}
@ -334,7 +334,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
return -1;
}
if (GB_is_sgb(gb)) {
if (GB_is_hle_sgb(gb)) {
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1;
}

View File

@ -295,7 +295,14 @@ static void command_ready(GB_gameboy_t *gb)
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
{
if (gb->joyp_write_callback) {
gb->joyp_write_callback(gb, value);
}
if (!GB_is_sgb(gb)) return;
if (!GB_is_hle_sgb(gb)) {
/* Notify via callback */
return;
}
if (gb->sgb->disable_commands) return;
if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return;

View File

@ -1,38 +1,24 @@
processors += sm83
objects += gb-interface gb-system
objects += gb-memory gb-cartridge
objects += gb-cpu gb-ppu gb-apu
obj/gb-interface.o: gb/interface/interface.cpp
obj/gb-system.o: gb/system/system.cpp
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp
obj/gb-memory.o: gb/memory/memory.cpp
obj/gb-cpu.o: gb/cpu/cpu.cpp
obj/gb-ppu.o: gb/ppu/ppu.cpp
obj/gb-apu.o: gb/apu/apu.cpp
flags += -DGB_INTERNAL -Wno-multichar -Dtypeof=__typeof__
options += -I../sameboy
objects += sb-apu sb-camera sb-debugger sb-display sb-gb sb-joypad sb-mbc
objects += sb-memory sb-printer sb-random sb-rewind sb-save_state sb-sgb
objects += sb-sm83_cpu sb-sm83_disassembler sb-symbol_hash sb-timing
objects += gb-apu gb-camera gb-debugger gb-display gb-gb gb-joypad gb-mbc
objects += gb-memory gb-printer gb-random gb-rewind gb-save_state gb-sgb
objects += gb-sm83_cpu gb-sm83_disassembler gb-symbol_hash gb-timing
obj/sb-apu.o: ../sameboy/Core/apu.c
obj/sb-camera.o: ../sameboy/Core/camera.c
obj/sb-debugger.o: ../sameboy/Core/debugger.c
obj/sb-display.o: ../sameboy/Core/display.c
obj/sb-gb.o: ../sameboy/Core/gb.c
obj/sb-joypad.o: ../sameboy/Core/joypad.c
obj/sb-mbc.o: ../sameboy/Core/mbc.c
obj/sb-memory.o: ../sameboy/Core/memory.c
obj/sb-printer.o: ../sameboy/Core/printer.c
obj/sb-random.o: ../sameboy/Core/random.c
obj/sb-rewind.o: ../sameboy/Core/rewind.c
obj/sb-save_state.o: ../sameboy/Core/save_state.c
obj/sb-sgb.o: ../sameboy/Core/sgb.c
obj/sb-sm83_cpu.o: ../sameboy/Core/sm83_cpu.c
obj/sb-sm83_disassembler.o: ../sameboy/Core/sm83_disassembler.c
obj/sb-symbol_hash.o: ../sameboy/Core/symbol_hash.c
obj/sb-timing.o: ../sameboy/Core/timing.c
obj/gb-apu.o: gb/Core/apu.c
obj/gb-camera.o: gb/Core/camera.c
obj/gb-debugger.o: gb/Core/debugger.c
obj/gb-display.o: gb/Core/display.c
obj/gb-gb.o: gb/Core/gb.c
obj/gb-joypad.o: gb/Core/joypad.c
obj/gb-mbc.o: gb/Core/mbc.c
obj/gb-memory.o: gb/Core/memory.c
obj/gb-printer.o: gb/Core/printer.c
obj/gb-random.o: gb/Core/random.c
obj/gb-rewind.o: gb/Core/rewind.c
obj/gb-save_state.o: gb/Core/save_state.c
obj/gb-sgb.o: gb/Core/sgb.c
obj/gb-sm83_cpu.o: gb/Core/sm83_cpu.c
obj/gb-sm83_disassembler.o: gb/Core/sm83_disassembler.c
obj/gb-symbol_hash.o: gb/Core/symbol_hash.c
obj/gb-timing.o: gb/Core/timing.c

View File

@ -1,106 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
#include "sequencer.cpp"
#include "square1.cpp"
#include "square2.cpp"
#include "wave.cpp"
#include "noise.cpp"
#include "serialization.cpp"
APU apu;
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
square1.run();
square2.run();
wave.run();
noise.run();
sequencer.run();
if(!Model::SuperGameBoy()) {
stream->sample(float(sequencer.left / 32768.0), float(sequencer.right / 32768.0));
} else {
float samples[] = {float(sequencer.left / 32768.0), float(sequencer.right / 32768.0)};
superGameBoy->audioSample(samples, 2);
}
if(cycle == 0) { //512hz
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
square1.clockLength();
square2.clockLength();
wave.clockLength();
noise.clockLength();
}
if(phase == 2 || phase == 6) { //128hz
square1.clockSweep();
}
if(phase == 7) { //64hz
square1.clockEnvelope();
square2.clockEnvelope();
noise.clockEnvelope();
}
phase++;
}
cycle++;
Thread::step(1);
synchronize(cpu);
}
auto APU::power() -> void {
create(Enter, 2 * 1024 * 1024);
if(!Model::SuperGameBoy()) {
stream = Emulator::audio.createStream(2, frequency());
stream->addHighPassFilter(20.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
}
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
square1.power();
square2.power();
wave.power();
noise.power();
sequencer.power();
phase = 0;
cycle = 0;
PRNG::PCG prng;
for(auto& n : wave.pattern) n = prng.random();
}
auto APU::readIO(uint16 addr) -> uint8 {
if(addr >= 0xff10 && addr <= 0xff14) return square1.read(addr);
if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr);
if(addr >= 0xff1f && addr <= 0xff23) return noise.read(addr);
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.read(addr);
if(addr >= 0xff30 && addr <= 0xff3f) return wave.read(addr);
return 0xff;
}
auto APU::writeIO(uint16 addr, uint8 data) -> void {
if(!sequencer.enable) {
bool valid = addr == 0xff26; //NR52
if(!Model::GameBoyColor()) {
//NRx1 length is writable only on DMG,SGB; not on CGB
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
if(addr == 0xff1b) valid = true; //NR31
if(addr == 0xff20) valid = true; //NR41
}
if(!valid) return;
}
if(addr >= 0xff10 && addr <= 0xff14) return square1.write(addr, data);
if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr, data);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr, data);
if(addr >= 0xff1f && addr <= 0xff23) return noise.write(addr, data);
if(addr >= 0xff24 && addr <= 0xff26) return sequencer.write(addr, data);
if(addr >= 0xff30 && addr <= 0xff3f) return wave.write(addr, data);
}
}

View File

@ -1,171 +0,0 @@
struct APU : Thread, MMIO {
shared_pointer<Emulator::Stream> stream;
static auto Enter() -> void;
auto main() -> void;
auto power() -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto serialize(serializer&) -> void;
//square1.cpp
struct Square1 {
auto dacEnable() const -> bool;
auto run() -> void;
auto sweep(bool update) -> void;
auto clockLength() -> void;
auto clockSweep() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint3 sweepFrequency;
bool sweepDirection;
uint3 sweepShift;
bool sweepNegate;
uint2 duty;
uint length;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint11 frequency;
bool counter;
int16 output;
bool dutyOutput;
uint3 phase;
uint period;
uint3 envelopePeriod;
uint3 sweepPeriod;
int frequencyShadow;
bool sweepEnable;
uint4 volume;
} square1;
//square2.cpp
struct Square2 {
auto dacEnable() const -> bool;
auto run() -> void;
auto clockLength() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint2 duty;
uint length;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint11 frequency;
bool counter;
int16 output;
bool dutyOutput;
uint3 phase;
uint period;
uint3 envelopePeriod;
uint4 volume;
} square2;
struct Wave {
auto getPattern(uint5 offset) const -> uint4;
auto run() -> void;
auto clockLength() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
bool dacEnable;
uint2 volume;
uint11 frequency;
bool counter;
uint8 pattern[16];
int16 output;
uint length;
uint period;
uint5 patternOffset;
uint4 patternSample;
uint patternHold;
} wave;
struct Noise {
auto dacEnable() const -> bool;
auto getPeriod() const -> uint;
auto run() -> void;
auto clockLength() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint4 frequency;
bool narrow;
uint3 divisor;
bool counter;
int16 output;
uint length;
uint3 envelopePeriod;
uint4 volume;
uint period;
uint15 lfsr;
} noise;
struct Sequencer {
auto run() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
bool leftEnable;
uint3 leftVolume;
bool rightEnable;
uint3 rightVolume;
struct Channel {
bool leftEnable;
bool rightEnable;
} square1, square2, wave, noise;
bool enable;
int16 center;
int16 left;
int16 right;
} sequencer;
uint3 phase; //high 3-bits of clock counter
uint12 cycle; //low 12-bits of clock counter
};
extern APU apu;

View File

@ -1,140 +0,0 @@
auto APU::Noise::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Noise::getPeriod() const -> uint {
static const uint table[] = {4, 8, 16, 24, 32, 40, 48, 56};
return table[divisor] << frequency;
}
auto APU::Noise::run() -> void {
if(period && --period == 0) {
period = getPeriod();
if(frequency < 14) {
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
lfsr = (lfsr >> 1) ^ (bit << (narrow ? 6 : 14));
}
}
uint4 sample = lfsr & 1 ? 0 : (uint)volume;
if(!enable) sample = 0;
output = sample;
}
auto APU::Noise::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Noise::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
auto APU::Noise::read(uint16 addr) -> uint8 {
if(addr == 0xff1f) { //NR40
return 0xff;
}
if(addr == 0xff20) { //NR41
return 0xff;
}
if(addr == 0xff21) { //NR42
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff22) { //NR43
return frequency << 4 | narrow << 3 | divisor;
}
if(addr == 0xff23) { //NR44
return 0x80 | counter << 6 | 0x3f;
}
return 0xff;
}
auto APU::Noise::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff20) { //NR41
length = 64 - bits(data,0-5);
}
if(addr == 0xff21) { //NR42
envelopeVolume = bits(data,4-7);
envelopeDirection = bit1(data,3);
envelopeFrequency = bits(data,0-2);
if(!dacEnable()) enable = false;
}
if(addr == 0xff22) { //NR43
frequency = bits(data,4-7);
narrow = bit1(data,3);
divisor = bits(data,0-2);
period = getPeriod();
}
if(addr == 0xff23) { //NR44
if(bit1(apu.phase,0) && !counter && bit1(data,6)) {
if(length && --length == 0) enable = false;
}
counter = bit1(data,6);
if(bit1(data,7)) {
enable = dacEnable();
lfsr = -1;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {
length = 64;
if(bit1(apu.phase,0) && counter) length--;
}
}
}
}
auto APU::Noise::power(bool initializeLength) -> void {
enable = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
narrow = 0;
divisor = 0;
counter = 0;
output = 0;
envelopePeriod = 0;
volume = 0;
period = 0;
lfsr = 0;
if(initializeLength) length = 64;
}
auto APU::Noise::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(narrow);
s.integer(divisor);
s.integer(counter);
s.integer(output);
s.integer(length);
s.integer(envelopePeriod);
s.integer(volume);
s.integer(period);
s.integer(lfsr);
}

View File

@ -1,142 +0,0 @@
auto APU::Sequencer::run() -> void {
if(enable == false) {
center = 0;
left = 0;
right = 0;
return;
}
int sample = 0;
sample += apu.square1.output;
sample += apu.square2.output;
sample += apu.wave.output;
sample += apu.noise.output;
center = (sample * 512) - 16384;
sample = 0;
if(square1.leftEnable) sample += apu.square1.output;
if(square2.leftEnable) sample += apu.square2.output;
if( wave.leftEnable) sample += apu.wave.output;
if( noise.leftEnable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (leftVolume + 1)) / 8;
left = sample;
sample = 0;
if(square1.rightEnable) sample += apu.square1.output;
if(square2.rightEnable) sample += apu.square2.output;
if( wave.rightEnable) sample += apu.wave.output;
if( noise.rightEnable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (rightVolume + 1)) / 8;
right = sample;
//reduce audio volume
center >>= 1;
left >>= 1;
right >>= 1;
}
auto APU::Sequencer::read(uint16 addr) -> uint8 {
if(addr == 0xff24) { //NR50
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
}
if(addr == 0xff25) { //NR51
return noise.leftEnable << 7
| wave.leftEnable << 6
| square2.leftEnable << 5
| square1.leftEnable << 4
| noise.rightEnable << 3
| wave.rightEnable << 2
| square2.rightEnable << 1
| square1.rightEnable << 0;
}
if(addr == 0xff26) { //NR52
return enable << 7 | 0x70
| apu.noise.enable << 3
| apu.wave.enable << 2
| apu.square2.enable << 1
| apu.square1.enable << 0;
}
return 0xff;
}
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff24) { //NR50
leftEnable = bit1(data,7);
leftVolume = bits(data,4-6);
rightEnable = bit1(data,3);
rightVolume = bits(data,0-2);
}
if(addr == 0xff25) { //NR51
noise.leftEnable = bit1(data,7);
wave.leftEnable = bit1(data,6);
square2.leftEnable = bit1(data,5);
square1.leftEnable = bit1(data,4);
noise.rightEnable = bit1(data,3);
wave.rightEnable = bit1(data,2);
square2.rightEnable = bit1(data,1);
square1.rightEnable = bit1(data,0);
}
if(addr == 0xff26) { //NR52
if(enable != bit1(data,7)) {
enable = bit1(data,7);
if(!enable) {
//power(bool) resets length counters when true (eg for CGB only)
apu.square1.power(Model::GameBoyColor());
apu.square2.power(Model::GameBoyColor());
apu.wave.power(Model::GameBoyColor());
apu.noise.power(Model::GameBoyColor());
power();
} else {
apu.phase = 0;
}
}
}
}
auto APU::Sequencer::power() -> void {
leftEnable = 0;
leftVolume = 0;
rightEnable = 0;
rightVolume = 0;
noise.leftEnable = 0;
wave.leftEnable = 0;
square2.leftEnable = 0;
square1.leftEnable = 0;
noise.rightEnable = 0;
wave.rightEnable = 0;
square2.rightEnable = 0;
square1.rightEnable = 0;
enable = 0;
center = 0;
left = 0;
right = 0;
}
auto APU::Sequencer::serialize(serializer& s) -> void {
s.integer(leftEnable);
s.integer(leftVolume);
s.integer(rightEnable);
s.integer(rightVolume);
s.integer(noise.leftEnable);
s.integer(wave.leftEnable);
s.integer(square2.leftEnable);
s.integer(square1.leftEnable);
s.integer(noise.rightEnable);
s.integer(wave.rightEnable);
s.integer(square2.rightEnable);
s.integer(square1.rightEnable);
s.integer(enable);
s.integer(center);
s.integer(left);
s.integer(right);
}

View File

@ -1,12 +0,0 @@
auto APU::serialize(serializer& s) -> void {
Thread::serialize(s);
square1.serialize(s);
square2.serialize(s);
wave.serialize(s);
noise.serialize(s);
sequencer.serialize(s);
s.integer(phase);
s.integer(cycle);
}

View File

@ -1,190 +0,0 @@
auto APU::Square1::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Square1::run() -> void {
if(period && --period == 0) {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: dutyOutput = (phase == 6); break; //______-_
case 1: dutyOutput = (phase >= 6); break; //______--
case 2: dutyOutput = (phase >= 4); break; //____----
case 3: dutyOutput = (phase <= 5); break; //------__
}
}
uint4 sample = dutyOutput ? (uint)volume : 0;
if(!enable) sample = 0;
output = sample;
}
auto APU::Square1::sweep(bool update) -> void {
if(!sweepEnable) return;
sweepNegate = sweepDirection;
uint delta = frequencyShadow >> sweepShift;
int freq = frequencyShadow + (sweepNegate ? -delta : delta);
if(freq > 2047) {
enable = false;
} else if(sweepShift && update) {
frequencyShadow = freq;
frequency = freq & 2047;
period = 2 * (2048 - frequency);
}
}
auto APU::Square1::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Square1::clockSweep() -> void {
if(--sweepPeriod == 0) {
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
if(sweepEnable && sweepFrequency) {
sweep(1);
sweep(0);
}
}
}
auto APU::Square1::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
auto APU::Square1::read(uint16 addr) -> uint8 {
if(addr == 0xff10) { //NR10
return 0x80 | sweepFrequency << 4 | sweepDirection << 3 | sweepShift;
}
if(addr == 0xff11) { //NR11
return duty << 6 | 0x3f;
}
if(addr == 0xff12) { //NR12
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff13) { //NR13
return 0xff;
}
if(addr == 0xff14) { //NR14
return 0x80 | counter << 6 | 0x3f;
}
return 0xff;
}
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff10) { //NR10
if(sweepEnable && sweepNegate && !bit1(data,3)) enable = false;
sweepFrequency = bits(data,4-6);
sweepDirection = bit1(data,3);
sweepShift = bits(data,0-2);
}
if(addr == 0xff11) { //NR11
duty = bits(data,6-7);
length = 64 - bits(data,0-5);
}
if(addr == 0xff12) { //NR12
envelopeVolume = bits(data,4-7);
envelopeDirection = bit1(data,3);
envelopeFrequency = bits(data,0-2);
if(!dacEnable()) enable = false;
}
if(addr == 0xff13) { //NR13
bits(frequency,0-7) = data;
}
if(addr == 0xff14) { //NR14
if(bit1(apu.phase,0) && !counter && bit1(data,6)) {
if(length && --length == 0) enable = false;
}
counter = bit1(data,6);
bits(frequency,8-10) = (uint)bits(data,0-2);
if(bit1(data,7)) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {
length = 64;
if(bit1(apu.phase,0) && counter) length--;
}
frequencyShadow = frequency;
sweepNegate = false;
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
sweepEnable = sweepPeriod || sweepShift;
if(sweepShift) sweep(0);
}
}
}
auto APU::Square1::power(bool initializeLength) -> void {
enable = 0;
sweepFrequency = 0;
sweepDirection = 0;
sweepShift = 0;
sweepNegate = 0;
duty = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
counter = 0;
output = 0;
dutyOutput = 0;
phase = 0;
period = 0;
envelopePeriod = 0;
sweepPeriod = 0;
frequencyShadow = 0;
sweepEnable = 0;
volume = 0;
if(initializeLength) length = 64;
}
auto APU::Square1::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(sweepFrequency);
s.integer(sweepDirection);
s.integer(sweepShift);
s.integer(sweepNegate);
s.integer(duty);
s.integer(length);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(dutyOutput);
s.integer(phase);
s.integer(period);
s.integer(envelopePeriod);
s.integer(sweepPeriod);
s.integer(frequencyShadow);
s.integer(sweepEnable);
s.integer(volume);
}

View File

@ -1,137 +0,0 @@
auto APU::Square2::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Square2::run() -> void {
if(period && --period == 0) {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: dutyOutput = (phase == 6); break; //______-_
case 1: dutyOutput = (phase >= 6); break; //______--
case 2: dutyOutput = (phase >= 4); break; //____----
case 3: dutyOutput = (phase <= 5); break; //------__
}
}
uint4 sample = dutyOutput ? (uint)volume : 0;
if(!enable) sample = 0;
output = sample;
}
auto APU::Square2::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Square2::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
auto APU::Square2::read(uint16 addr) -> uint8 {
if(addr == 0xff15) { //NR20
return 0xff;
}
if(addr == 0xff16) { //NR21
return duty << 6 | 0x3f;
}
if(addr == 0xff17) { //NR22
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff18) { //NR23
return 0xff;
}
if(addr == 0xff19) { //NR24
return 0x80 | counter << 6 | 0x3f;
}
return 0xff;
}
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff16) { //NR21
duty = bits(data,6-7);
length = 64 - bits(data,0-5);
}
if(addr == 0xff17) { //NR22
envelopeVolume = bits(data,4-7);
envelopeDirection = bit1(data,3);
envelopeFrequency = bits(data,0-2);
if(!dacEnable()) enable = false;
}
if(addr == 0xff18) { //NR23
bits(frequency,0-7) = data;
}
if(addr == 0xff19) { //NR24
if(bit1(apu.phase,0) && !counter && bit1(data,6)) {
if(length && --length == 0) enable = false;
}
counter = bit1(data,6);
bits(frequency,8-10) = (uint)bits(data,0-2);
if(bit1(data,7)) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {
length = 64;
if(bit1(apu.phase,0) && counter) length--;
}
}
}
}
auto APU::Square2::power(bool initializeLength) -> void {
enable = 0;
duty = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
counter = 0;
output = 0;
dutyOutput = 0;
phase = 0;
period = 0;
envelopePeriod = 0;
volume = 0;
if(initializeLength) length = 64;
}
auto APU::Square2::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(duty);
s.integer(length);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(dutyOutput);
s.integer(phase);
s.integer(period);
s.integer(envelopePeriod);
s.integer(volume);
}

View File

@ -1,156 +0,0 @@
auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
return pattern[offset >> 1] >> (offset & 1 ? 0 : 4);
}
auto APU::Wave::run() -> void {
if(patternHold) patternHold--;
if(period && --period == 0) {
period = 1 * (2048 - frequency);
patternSample = getPattern(++patternOffset);
patternHold = 1;
}
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
uint4 sample = patternSample >> shift[volume];
if(!enable) sample = 0;
output = sample;
}
auto APU::Wave::clockLength() -> void {
if(counter) {
if(length && --length == 0) enable = false;
}
}
auto APU::Wave::read(uint16 addr) -> uint8 {
if(addr == 0xff1a) { //NR30
return dacEnable << 7 | 0x7f;
}
if(addr == 0xff1b) { //NR31
return 0xff;
}
if(addr == 0xff1c) { //NR32
return 0x80 | volume << 5 | 0x1f;
}
if(addr == 0xff1d) { //NR33
return 0xff;
}
if(addr == 0xff1e) { //NR34
return 0x80 | counter << 6 | 0x3f;
}
if(addr >= 0xff30 && addr <= 0xff3f) {
if(enable) {
if(!Model::GameBoyColor() && !patternHold) return 0xff;
return pattern[patternOffset >> 1];
} else {
return pattern[addr & 15];
}
}
return 0xff;
}
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff1a) { //NR30
dacEnable = bit1(data,7);
if(!dacEnable) enable = false;
}
if(addr == 0xff1b) { //NR31
length = 256 - data;
}
if(addr == 0xff1c) { //NR32
volume = bits(data,5-6);
}
if(addr == 0xff1d) { //NR33
bits(frequency,0-7) = data;
}
if(addr == 0xff1e) { //NR34
if(bit1(apu.phase,0) && !counter && bit1(data,6)) {
if(length && --length == 0) enable = false;
}
counter = bit1(data,6);
bits(frequency,8-10) = (uint)bits(data,0-2);
if(bit1(data,7)) {
if(!Model::GameBoyColor() && patternHold) {
//DMG,SGB trigger while channel is being read corrupts wave RAM
if((patternOffset >> 1) <= 3) {
//if current pattern is with 0-3; only byte 0 is corrupted
pattern[0] = pattern[patternOffset >> 1];
} else {
//if current pattern is within 4-15; pattern&~3 is copied to pattern[0-3]
pattern[0] = pattern[((patternOffset >> 1) & ~3) + 0];
pattern[1] = pattern[((patternOffset >> 1) & ~3) + 1];
pattern[2] = pattern[((patternOffset >> 1) & ~3) + 2];
pattern[3] = pattern[((patternOffset >> 1) & ~3) + 3];
}
}
enable = dacEnable;
period = 1 * (2048 - frequency);
patternOffset = 0;
patternSample = 0;
patternHold = 0;
if(!length) {
length = 256;
if(bit1(apu.phase,0) && counter) length--;
}
}
}
if(addr >= 0xff30 && addr <= 0xff3f) {
if(enable) {
if(!Model::GameBoyColor() && !patternHold) return;
pattern[patternOffset >> 1] = data;
} else {
pattern[addr & 15] = data;
}
}
}
auto APU::Wave::power(bool initializeLength) -> void {
enable = 0;
dacEnable = 0;
volume = 0;
frequency = 0;
counter = 0;
output = 0;
period = 0;
patternOffset = 0;
patternSample = 0;
patternHold = 0;
if(initializeLength) length = 256;
}
auto APU::Wave::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(dacEnable);
s.integer(volume);
s.integer(frequency);
s.integer(counter);
s.array(pattern);
s.integer(output);
s.integer(length);
s.integer(period);
s.integer(patternOffset);
s.integer(patternSample);
s.integer(patternHold);
}

View File

@ -1,206 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
Cartridge cartridge;
#include "mbc0/mbc0.cpp"
#include "mbc1/mbc1.cpp"
#include "mbc1m/mbc1m.cpp"
#include "mbc2/mbc2.cpp"
#include "mbc3/mbc3.cpp"
#include "mbc5/mbc5.cpp"
#include "mbc6/mbc6.cpp"
#include "mbc7/mbc7.cpp"
#include "mmm01/mmm01.cpp"
#include "huc1/huc1.cpp"
#include "huc3/huc3.cpp"
#include "tama/tama.cpp"
#include "serialization.cpp"
auto Cartridge::Enter() -> void {
while(true) scheduler.synchronize(), cartridge.main();
}
auto Cartridge::main() -> void {
mapper->main();
}
auto Cartridge::step(uint clocks) -> void {
Thread::step(clocks);
synchronize(cpu);
}
auto Cartridge::load() -> bool {
information = {};
rom = {};
ram = {};
rtc = {};
mapper = &mbc0;
accelerometer = false;
rumble = false;
if(Model::GameBoy()) {
if(auto loaded = platform->load(ID::GameBoy, "Game Boy", "gb")) {
information.pathID = loaded.pathID;
} else return false;
}
if(Model::GameBoyColor()) {
if(auto loaded = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
information.pathID = loaded.pathID;
} else return false;
}
if(Model::SuperGameBoy()) {
if(auto loaded = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
information.pathID = loaded.pathID;
} else return false;
}
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
information.title = document["game/label"].text();
auto mapperID = document["game/board"].text();
if(mapperID == "MBC0" ) mapper = &mbc0;
if(mapperID == "MBC1" ) mapper = &mbc1;
if(mapperID == "MBC1M") mapper = &mbc1m;
if(mapperID == "MBC2" ) mapper = &mbc2;
if(mapperID == "MBC3" ) mapper = &mbc3;
if(mapperID == "MBC5" ) mapper = &mbc5;
if(mapperID == "MBC6" ) mapper = &mbc6;
if(mapperID == "MBC7" ) mapper = &mbc7;
if(mapperID == "MMM01") mapper = &mmm01;
if(mapperID == "HuC1" ) mapper = &huc1;
if(mapperID == "HuC3" ) mapper = &huc3;
if(mapperID == "TAMA" ) mapper = &tama;
accelerometer = (bool)document["game/board/accelerometer"];
rumble = (bool)document["game/board/rumble"];
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Program)"]}) {
rom.size = max(0x4000, (uint)memory.size);
rom.data = memory::allocate<uint8>(rom.size, 0xff);
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Required)) {
fp->read(rom.data, min(rom.size, fp->size()));
}
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
ram.size = memory.size;
ram.data = memory::allocate<uint8>(ram.size, 0xff);
if(memory.nonVolatile) {
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Optional)) {
fp->read(ram.data, min(ram.size, fp->size()));
}
}
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RTC,content=Time)"]}) {
rtc.size = memory.size;
rtc.data = memory::allocate<uint8>(rtc.size, 0xff);
if(memory.nonVolatile) {
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Optional)) {
fp->read(rtc.data, min(rtc.size, fp->size()));
}
}
}
information.sha256 = Hash::SHA256({rom.data, rom.size}).digest();
mapper->load(document);
return true;
}
auto Cartridge::save() -> void {
auto document = BML::unserialize(information.manifest);
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
if(memory.nonVolatile) {
if(auto fp = platform->open(pathID(), memory.name(), File::Write)) {
fp->write(ram.data, ram.size);
}
}
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RTC,content=Time)"]}) {
if(memory.nonVolatile) {
if(auto fp = platform->open(pathID(), memory.name(), File::Write)) {
fp->write(rtc.data, rtc.size);
}
}
}
mapper->save(document);
}
auto Cartridge::unload() -> void {
delete[] rom.data;
delete[] ram.data;
delete[] rtc.data;
rom = {};
ram = {};
rtc = {};
}
auto Cartridge::readIO(uint16 addr) -> uint8 {
if(addr == 0xff50) return 0xff;
if(bootromEnable) {
const uint8* data = nullptr;
if(Model::GameBoy()) data = system.bootROM.dmg;
if(Model::GameBoyColor()) data = system.bootROM.cgb;
if(Model::SuperGameBoy()) data = system.bootROM.sgb;
if(addr >= 0x0000 && addr <= 0x00ff) return data[addr];
if(addr >= 0x0200 && addr <= 0x08ff && Model::GameBoyColor()) return data[addr - 0x100];
}
return mapper->read(addr);
}
auto Cartridge::writeIO(uint16 addr, uint8 data) -> void {
if(bootromEnable && addr == 0xff50) {
bootromEnable = false;
return;
}
mapper->write(addr, data);
}
auto Cartridge::power() -> void {
create(Enter, 4 * 1024 * 1024);
for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this;
bus.mmio[0xff50] = this;
bootromEnable = true;
mapper->power();
}
auto Cartridge::second() -> void {
mapper->second();
}
auto Cartridge::Memory::read(uint address) const -> uint8 {
if(!size) return 0xff;
if(address >= size) address %= size;
return data[address];
}
auto Cartridge::Memory::write(uint address, uint8 byte) -> void {
if(!size) return;
if(address >= size) address %= size;
data[address] = byte;
}
//
auto Cartridge::Mapper::main() -> void {
cartridge.step(cartridge.frequency());
}
}

View File

@ -1,68 +0,0 @@
struct Cartridge : Thread, MMIO {
auto pathID() const -> uint { return information.pathID; }
auto hash() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
static auto Enter() -> void;
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
auto readIO(uint16 address) -> uint8;
auto writeIO(uint16 address, uint8 data) -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
auto second() -> void;
auto serialize(serializer&) -> void;
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;
} information;
struct Memory {
auto read(uint address) const -> uint8;
auto write(uint address, uint8 data) -> void;
uint8* data = nullptr;
uint size = 0;
} rom, ram, rtc;
bool bootromEnable = true;
private:
struct Mapper {
virtual auto load(Markup::Node document) -> void {}
virtual auto save(Markup::Node document) -> void {}
virtual auto main() -> void;
virtual auto second() -> void {}
virtual auto read(uint16 address) -> uint8 = 0;
virtual auto write(uint16 address, uint8 data) -> void = 0;
virtual auto power() -> void = 0;
virtual auto serialize(serializer&) -> void = 0;
};
Mapper* mapper = nullptr;
bool accelerometer = false;
bool rumble = false;
#include "mbc0/mbc0.hpp"
#include "mbc1/mbc1.hpp"
#include "mbc1m/mbc1m.hpp"
#include "mbc2/mbc2.hpp"
#include "mbc3/mbc3.hpp"
#include "mbc5/mbc5.hpp"
#include "mbc6/mbc6.hpp"
#include "mbc7/mbc7.hpp"
#include "mmm01/mmm01.hpp"
#include "huc1/huc1.hpp"
#include "huc3/huc3.hpp"
#include "tama/tama.hpp"
};
extern Cartridge cartridge;

View File

@ -1,54 +0,0 @@
auto Cartridge::HuC1::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram.read(io.ram.bank << 13 | bits(address,0-12));
}
return 0xff;
}
auto Cartridge::HuC1::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.writable = bits(data,0-3) == 0x0a;
return;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
return;
}
if((address & 0xe000) == 0x6000) { //$6000-7fff
io.model = data & 1;
return;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.writable) return;
return cartridge.ram.write(io.ram.bank << 13 | bits(address,0-12), data);
}
}
auto Cartridge::HuC1::power() -> void {
io = {};
}
auto Cartridge::HuC1::serialize(serializer& s) -> void {
s.integer(io.model);
s.integer(io.rom.bank);
s.integer(io.ram.writable);
s.integer(io.ram.bank);
}

View File

@ -1,17 +0,0 @@
struct HuC1 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
uint1 model;
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 writable;
uint8 bank;
} ram;
} io;
} huc1;

View File

@ -1,48 +0,0 @@
auto Cartridge::HuC3::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0x01; //does not return open collection
return cartridge.ram.read(io.ram.bank << 13 | bits(address,0-12));
}
return 0xff;
}
auto Cartridge::HuC3::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = bits(data,0-3) == 0x0a;
return;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
return;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
return;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank << 13 | bits(address,0-12), data);
}
}
auto Cartridge::HuC3::power() -> void {
io = {};
}
auto Cartridge::HuC3::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@ -1,16 +0,0 @@
struct HuC3 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
} io;
} huc3;

View File

@ -1,24 +0,0 @@
auto Cartridge::MBC0::read(uint16 address) -> uint8 {
if((address & 0x8000) == 0x0000) { //$0000-7fff
return cartridge.rom.read(bits(address,0-14));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram.read(bits(address,0-12));
}
return 0xff;
}
auto Cartridge::MBC0::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0xa000) { //$a000-bfff
cartridge.ram.write(bits(address,0-12), data);
return;
}
}
auto Cartridge::MBC0::power() -> void {
}
auto Cartridge::MBC0::serialize(serializer& s) -> void {
}

View File

@ -1,6 +0,0 @@
struct MBC0 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
} mbc0;

View File

@ -1,67 +0,0 @@
auto Cartridge::MBC1::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
if(io.mode == 0) {
return cartridge.rom.read(io.ram.bank << 19 | io.rom.bank << 14 | bits(address,0-13));
} else {
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
if(io.mode == 0) {
return cartridge.ram.read(bits(address,0-12));
} else {
return cartridge.ram.read(io.ram.bank << 13 | bits(address,0-12));
}
}
return 0xff;
}
auto Cartridge::MBC1::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = bits(data,0-3) == 0x0a;
return;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = bits(data,0-4);
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = bits(data,0-1);
return;
}
if((address & 0xe000) == 0x6000) { //$6000-7fff
io.mode = bit1(data,0);
return;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
if(io.mode == 0) {
return cartridge.ram.write(bits(address,0-12), data);
} else {
return cartridge.ram.write(io.ram.bank << 13 | bits(address,0-12), data);
}
}
}
auto Cartridge::MBC1::power() -> void {
io = {};
}
auto Cartridge::MBC1::serialize(serializer& s) -> void {
s.integer(io.mode);
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@ -1,17 +0,0 @@
struct MBC1 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer& s) -> void;
struct IO {
uint1 mode;
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
} io;
} mbc1;

View File

@ -1,43 +0,0 @@
auto Cartridge::MBC1M::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
if(io.mode == 0) return cartridge.rom.read(bits(address,0-13));
return cartridge.rom.read(bits(io.rom.bank,4-5) << 18 | bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
return cartridge.ram.read(bits(address,0-12));
}
return 0xff;
}
auto Cartridge::MBC1M::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x2000) { //$2000-3fff
bits(io.rom.bank,0-3) = bits(data,0-3);
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
bits(io.rom.bank,4-5) = bits(data,0-1);
}
if((address & 0xe000) == 0x6000) { //$6000-7fff
io.mode = bit1(data,0);
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
cartridge.ram.write(bits(address,0-13), data);
}
}
auto Cartridge::MBC1M::power() -> void {
io = {};
}
auto Cartridge::MBC1M::serialize(serializer& s) -> void {
s.integer(io.mode);
s.integer(io.rom.bank);
}

View File

@ -1,13 +0,0 @@
struct MBC1M : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
uint1 mode;
struct ROM {
uint6 bank = 0x01;
} rom;
} io;
} mbc1m;

View File

@ -1,61 +0,0 @@
auto Cartridge::MBC2::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xee01) == 0xa000) { //$a000-a1ff (even)
if(!io.ram.enable) return 0xff;
auto ram = cartridge.ram.read(bits(address,1-8));
return 0xf0 | bits(ram,0-3);
}
if((address & 0xee01) == 0xa001) { //$a000-a1ff (odd)
if(!io.ram.enable) return 0xff;
auto ram = cartridge.ram.read(bits(address,1-8));
return 0xf0 | bits(ram,4-7);
}
return 0xff;
}
auto Cartridge::MBC2::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
if(!bit1(address,8)) io.ram.enable = bits(data,0-3) == 0x0a;
return;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
if(bit1(address,8)) io.rom.bank = bits(data,0-3);
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((address & 0xee01) == 0xa000) { //$a000-a1ff (even)
if(!io.ram.enable) return;
auto ram = cartridge.ram.read(bits(address,1-8));
bits(ram,0-3) = (uint)bits(data,0-3);
cartridge.ram.write(bits(address,1-8), ram);
return;
}
if((address & 0xee01) == 0xa001) { //$a000-a1ff (odd)
if(!io.ram.enable) return;
auto ram = cartridge.ram.read(bits(address,1-8));
bits(ram,4-7) = (uint)bits(data,0-3);
cartridge.ram.write(bits(address,1-8), ram);
return;
}
}
auto Cartridge::MBC2::power() -> void {
io = {};
}
auto Cartridge::MBC2::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
}

View File

@ -1,15 +0,0 @@
struct MBC2 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable = 0;
} ram;
} io;
} mbc2;

View File

@ -1,113 +0,0 @@
auto Cartridge::MBC3::second() -> void {
if(io.rtc.halt) return;
if(++io.rtc.second >= 60) {
io.rtc.second = 0;
if(++io.rtc.minute >= 60) {
io.rtc.minute = 0;
if(++io.rtc.hour >= 24) {
io.rtc.hour = 0;
if(++io.rtc.day == 0) {
io.rtc.dayCarry = true;
}
}
}
}
}
auto Cartridge::MBC3::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
if(io.ram.bank <= 0x03) return cartridge.ram.read(io.ram.bank << 13 | bits(address,0-12));
if(io.ram.bank == 0x08) return io.rtc.latchSecond;
if(io.ram.bank == 0x09) return io.rtc.latchMinute;
if(io.ram.bank == 0x0a) return io.rtc.latchHour;
if(io.ram.bank == 0x0b) return io.rtc.latchDay;
if(io.ram.bank == 0x0c) return io.rtc.latchDayCarry << 7 | io.rtc.latchDay >> 8;
return 0xff;
}
return 0xff;
}
auto Cartridge::MBC3::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = bits(data,0-3) == 0x0a;
return;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = bits(data,0-6);
if(!io.rom.bank) io.rom.bank = 0x01;
return;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
return;
}
if((address & 0xe000) == 0x6000) { //$6000-7fff
if(io.rtc.latch == 0 && data == 1) {
io.rtc.latchSecond = io.rtc.second;
io.rtc.latchMinute = io.rtc.minute;
io.rtc.latchHour = io.rtc.hour;
io.rtc.latchDay = io.rtc.day;
io.rtc.latchDayCarry = io.rtc.dayCarry;
}
io.rtc.latch = data;
return;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
if(io.ram.bank <= 0x03) {
cartridge.ram.write(io.ram.bank << 13 | bits(address,0-12), data);
} else if(io.ram.bank == 0x08) {
if(data >= 60) data = 0;
io.rtc.second = data;
} else if(io.ram.bank == 0x09) {
if(data >= 60) data = 0;
io.rtc.minute = data;
} else if(io.ram.bank == 0x0a) {
if(data >= 24) data = 0;
io.rtc.hour = data;
} else if(io.ram.bank == 0x0b) {
bits(io.rtc.day,0-7) = bits(data,0-7);
} else if(io.ram.bank == 0x0c) {
bit1(io.rtc.day,8) = bit1(data,0);
io.rtc.halt = bit1(data,6);
io.rtc.dayCarry = bit1(data,7);
}
return;
}
}
auto Cartridge::MBC3::power() -> void {
io = {};
}
auto Cartridge::MBC3::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
s.integer(io.rtc.halt);
s.integer(io.rtc.latch);
s.integer(io.rtc.second);
s.integer(io.rtc.minute);
s.integer(io.rtc.hour);
s.integer(io.rtc.day);
s.integer(io.rtc.dayCarry);
s.integer(io.rtc.latchSecond);
s.integer(io.rtc.latchMinute);
s.integer(io.rtc.latchHour);
s.integer(io.rtc.latchDay);
s.integer(io.rtc.latchDayCarry);
}

View File

@ -1,33 +0,0 @@
struct MBC3 : Mapper {
auto second() -> void;
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer& s) -> void;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
struct RTC {
uint1 halt = true;
uint1 latch;
uint8 second;
uint8 minute;
uint8 hour;
uint9 day;
uint1 dayCarry;
uint8 latchSecond;
uint8 latchMinute;
uint8 latchHour;
uint9 latchDay;
uint1 latchDayCarry;
} rtc;
} io;
} mbc3;

View File

@ -1,54 +0,0 @@
auto Cartridge::MBC5::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank << 13 | bits(address,0-12));
}
return 0xff;
}
auto Cartridge::MBC5::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = bits(data,0-3) == 0x0a;
return;
}
if((address & 0xf000) == 0x2000) { //$2000-2fff
bits(io.rom.bank,0-7) = bits(data,0-7);
return;
}
if((address & 0xf000) == 0x3000) { //$3000-3fff
bit1(io.rom.bank,8) = bit1(data,0);
return;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
if(cartridge.rumble) platform->inputRumble(ID::Port::Cartridge, ID::Device::MBC5, 0, bit1(data,3));
io.ram.bank = bits(data,0-3);
return;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank << 13 | bits(address,0-12), data);
}
}
auto Cartridge::MBC5::power() -> void {
io = {};
}
auto Cartridge::MBC5::serialize(serializer& s) -> void {
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@ -1,16 +0,0 @@
struct MBC5 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
struct ROM {
uint9 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint4 bank;
} ram;
} io;
} mbc5;

View File

@ -1,74 +0,0 @@
auto Cartridge::MBC6::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
return cartridge.rom.read(io.rom.bank[0] << 13 | bits(address,0-12));
}
if((address & 0xe000) == 0x6000) { //$6000-7fff
return cartridge.rom.read(io.rom.bank[1] << 13 | bits(address,0-12));
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank[0] << 12 | bits(address,0-11));
}
if((address & 0xf000) == 0xb000) { //$b000-bfff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank[1] << 12 | bits(address,0-11));
}
return 0xff;
}
auto Cartridge::MBC6::write(uint16 address, uint8 data) -> void {
if((address & 0xfc00) == 0x0000) {
io.ram.enable = bits(data,0-3) == 0xa;
return;
}
if((address & 0xfc00) == 0x0400) {
io.ram.bank[0] = data;
return;
}
if((address & 0xfc00) == 0x0800) {
io.ram.bank[1] = data;
return;
}
if((address & 0xf800) == 0x2000) {
io.rom.bank[0] = data;
return;
}
if((address & 0xf800) == 0x3000) {
io.rom.bank[1] = data;
return;
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank[0] << 12 | bits(address,0-11), data);
}
if((address & 0xf000) == 0xb000) { //$b000-bfff
if(!io.ram.enable) return;
return cartridge.ram.write(io.ram.bank[1] << 12 | bits(address,0-11), data);
}
}
auto Cartridge::MBC6::power() -> void {
io = {};
}
auto Cartridge::MBC6::serialize(serializer& s) -> void {
s.integer(io.rom.bank[0]);
s.integer(io.rom.bank[1]);
s.integer(io.ram.enable);
s.integer(io.ram.bank[0]);
s.integer(io.ram.bank[1]);
}

View File

@ -1,16 +0,0 @@
struct MBC6 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
struct ROM {
uint8 bank[2];
} rom;
struct RAM {
uint1 enable;
uint8 bank[2];
} ram;
} io;
} mbc6;

View File

@ -1,241 +0,0 @@
//Microchip 93LCx6
// 93LC46 => 1024 cells => 128 x 8-bit or 64 x 16-bit
// 93LC56 => 2048 cells => 256 x 8-bit or 128 x 16-bit
// 93LC66 => 4096 cells => 512 x 8-bit or 256 x 16-bit
auto Cartridge::MBC7::EEPROM::load(Markup::Node document) -> void {
for(auto& byte : data) byte = 0xff;
size = 512; //EEPROM size is in bytes
width = 16; //16-bit configuration
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=EEPROM,content=Save)"]}) {
if(memory.size == 128) size = 128;
if(memory.size == 256) size = 256;
if(memory.size == 512) size = 512;
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read, File::Optional)) {
fp->read(data, min(fp->size(), sizeof(data)));
}
}
//note: the 93LC56 alone has an extra dummy address bit
if(size == 128) input.addressLength = width == 16 ? 6 : 7; //93LC46
if(size == 256) input.addressLength = width == 16 ? 8 : 9; //93LC56
if(size == 512) input.addressLength = width == 16 ? 8 : 9; //93LC66
input.dataLength = width;
}
auto Cartridge::MBC7::EEPROM::save(Markup::Node document) -> void {
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=EEPROM,content=Save)"]}) {
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Write)) {
fp->write(data, size);
}
}
}
auto Cartridge::MBC7::EEPROM::main() -> void {
//step by approximately one millisecond
cartridge.step(cartridge.frequency() / 1000);
//set during programming commands
if(busy) busy--;
}
auto Cartridge::MBC7::EEPROM::power() -> void {
select = 0;
clock = 0;
writable = 0;
busy = 0;
input.flush();
output.flush();
}
auto Cartridge::MBC7::EEPROM::readIO() -> uint8 {
uint8 data = 0b00'1111'00;
bit1(data,7) = select;
bit1(data,6) = clock;
bit1(data,1) = input.edge();
if(!select) {
bit1(data,0) = 1; //high-z when the chip is idle (not selected)
} else if(busy) {
bit1(data,0) = 0; //low when a programming command is in progress
} else if(output.count) {
bit1(data,0) = output.edge(); //shift register data during read commands
} else {
bit1(data,0) = 1; //high-z during all other commands
}
return data;
}
auto Cartridge::MBC7::EEPROM::writeIO(uint8 data) -> void {
//chip enters idle state on falling CS edge
if(select && !bit1(data,7)) return power();
//chip leaves idle state on rising CS edge
if(!(select = bit1(data,7))) return;
//input shift register clocks on rising edge
if(!clock.raise(bit1(data,6))) return;
//read mode
if(output.count && !bit1(data,1)) {
if(input.start() && *input.start() == 1) {
if(input.opcode() && *input.opcode() == 0b10) {
output.read();
if(output.count == 0) {
//sequential read mode
input.increment();
read();
}
}
}
return;
}
output.flush();
input.write(bit1(data,1));
//wait for start
if(!input.start()) return;
uint start = *input.start();
//start bit must be set
if(start != 1) return input.flush();
//wait for opcode
if(!input.opcode()) return;
uint opcode = *input.opcode();
//wait for address
if(!input.address()) return;
if(opcode == 0b00) {
auto mode = *input.mode();
if(mode == 0b00) return writeDisable();
if(mode == 0b01) return writeAll();
if(mode == 0b10) return eraseAll();
if(mode == 0b11) return writeEnable();
}
if(opcode == 0b01) return write();
if(opcode == 0b10) return read();
if(opcode == 0b11) return erase();
}
//
auto Cartridge::MBC7::EEPROM::read() -> void {
uint address = *input.address() << (width == 16) & size - 1;
output.flush();
for(uint4 index : range(width)) {
output.write(bit1(data[address + !bit1(index,3)],bits(index,0-2)));
}
output.write(0); //reads have an extra dummy data bit
}
auto Cartridge::MBC7::EEPROM::write() -> void {
if(!input.data()) return; //wait for data
if(!writable) return input.flush();
uint address = *input.address() << (width == 16) & size - 1;
for(uint4 index : range(width)) {
bit1(data[address + !bit1(index,3)],bits(index,0-2)) = input.read();
}
busy = 4; //milliseconds
return input.flush();
}
auto Cartridge::MBC7::EEPROM::erase() -> void {
if(!writable) return input.flush();
uint address = *input.address() << (width == 16) & size - 1;
for(uint index : range(width)) {
bit1(data[address + index / 8],index % 8) = 1;
}
busy = 4; //milliseconds
return input.flush();
}
auto Cartridge::MBC7::EEPROM::writeAll() -> void {
if(!input.data()) return; //wait for data
if(!writable) return input.flush();
auto word = *input.data();
for(uint address = 0; address < 512;) {
data[address++] = bit8(word,width == 16);
data[address++] = bit8(word,0);
}
busy = 16; //milliseconds
return input.flush();
}
auto Cartridge::MBC7::EEPROM::eraseAll() -> void {
if(!writable) return input.flush();
for(auto& byte : data) byte = 0xff;
busy = 8; //milliseconds
return input.flush();
}
auto Cartridge::MBC7::EEPROM::writeEnable() -> void {
writable = true;
return input.flush();
}
auto Cartridge::MBC7::EEPROM::writeDisable() -> void {
writable = false;
return input.flush();
}
//
auto Cartridge::MBC7::EEPROM::ShiftRegister::flush() -> void {
value = 0;
count = 0;
}
auto Cartridge::MBC7::EEPROM::ShiftRegister::edge() -> uint1 {
return value & 1;
}
auto Cartridge::MBC7::EEPROM::ShiftRegister::read() -> uint1 {
uint1 bit = value & 1;
value >>= 1;
count--;
return bit;
}
auto Cartridge::MBC7::EEPROM::ShiftRegister::write(uint1 bit) -> void {
value <<= 1;
bit1(value,0) = bit;
count++;
}
//
auto Cartridge::MBC7::EEPROM::InputShiftRegister::start() -> maybe<uint1> {
if(count < 1) return {};
return {value >> count - 1 & 1};
}
auto Cartridge::MBC7::EEPROM::InputShiftRegister::opcode() -> maybe<uint2> {
if(count < 1 + 2) return {};
return {value >> count - 3 & 3};
}
auto Cartridge::MBC7::EEPROM::InputShiftRegister::mode() -> maybe<uint2> {
if(count < 1 + 2 + addressLength) return {};
return {value >> count - 5 & 3};
}
auto Cartridge::MBC7::EEPROM::InputShiftRegister::address() -> maybe<uint9> {
if(count < 1 + 2 + addressLength) return {};
return {value >> count - (3 + addressLength) & (1 << addressLength) - 1};
}
auto Cartridge::MBC7::EEPROM::InputShiftRegister::data() -> maybe<uint16> {
if(count < 1 + 2 + addressLength + dataLength) return {};
uint16 data = value >> count - (3 + addressLength + dataLength) & (1 << dataLength) - 1;
return data;
}
auto Cartridge::MBC7::EEPROM::InputShiftRegister::increment() -> void {
uint mask = (1 << addressLength) - 1;
value = value & ~mask | (value + 1 & mask);
}

View File

@ -1,93 +0,0 @@
#include "eeprom.cpp"
#include "serialization.cpp"
auto Cartridge::MBC7::load(Markup::Node document) -> void {
eeprom.load(document);
}
auto Cartridge::MBC7::save(Markup::Node document) -> void {
eeprom.save(document);
}
auto Cartridge::MBC7::main() -> void {
eeprom.main();
}
auto Cartridge::MBC7::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable[0] || !io.ram.enable[1]) return 0xff;
switch(bits(address,4-7)) {
case 2: return bit8(io.accelerometer.x,0);
case 3: return bit8(io.accelerometer.x,1);
case 4: return bit8(io.accelerometer.y,0);
case 5: return bit8(io.accelerometer.y,1);
case 6: return 0x00; //z?
case 7: return 0xff; //z?
case 8: return eeprom.readIO();
}
return 0xff;
}
return 0xff;
}
auto Cartridge::MBC7::write(uint16 address, uint8 data) -> void {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable[0] = bits(data,0-3) == 0xa;
if(!io.ram.enable[0]) io.ram.enable[1] = false;
return;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
if(!io.rom.bank) io.rom.bank = 1;
return;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
if(!io.ram.enable[0]) return;
io.ram.enable[1] = data == 0x40;
}
if((address & 0xf000) == 0xa000) { //$a000-afff
if(!io.ram.enable[0] || !io.ram.enable[1]) return;
switch(bits(address,4-7)) {
case 0: {
if(data != 0x55) break;
io.accelerometer.x = Center;
io.accelerometer.y = Center;
break;
}
case 1: {
if(data != 0xaa) break;
io.accelerometer.x = Center - platform->inputPoll(ID::Port::Cartridge, ID::Device::MBC7, 0);
io.accelerometer.y = Center + platform->inputPoll(ID::Port::Cartridge, ID::Device::MBC7, 1);
break;
}
case 8: {
eeprom.writeIO(data);
break;
}
}
return;
}
}
auto Cartridge::MBC7::power() -> void {
eeprom.power();
io = {};
}

View File

@ -1,91 +0,0 @@
struct MBC7 : Mapper {
enum : uint { Center = 0x81d0 }; //not 0x8000
//mbc7.cpp
auto load(Markup::Node document) -> void override;
auto save(Markup::Node document) -> void override;
auto main() -> void override;
auto read(uint16 address) -> uint8 override;
auto write(uint16 address, uint8 data) -> void override;
auto power() -> void override;
//serialization.cpp
auto serialize(serializer&) -> void override;
struct EEPROM {
//eeprom.cpp
auto load(Markup::Node document) -> void;
auto save(Markup::Node document) -> void;
auto main() -> void;
auto power() -> void;
//Game Boy MBC7 interface
auto readIO() -> uint8;
auto writeIO(uint8 data) -> void;
//chip commands
auto read() -> void;
auto write() -> void;
auto erase() -> void;
auto writeAll() -> void;
auto eraseAll() -> void;
auto writeEnable() -> void;
auto writeDisable() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
//it is awkward no matter if data is uint1[4096], uint8[512], or uint16[256]
uint8 data[512]; //uint8 was chosen solely for easier serialization and saving
uint size; //in bytes
uint width; //8-bit (ORG=0) or 16-bit (ORG=1) configuration
boolean select; //CS
boolean clock; //CLK
boolean writable; //EWEN, EWDS
uint busy; //busy cycles in milliseconds remaining for programming (write) operations to complete
struct ShiftRegister {
auto flush() -> void;
auto edge() -> uint1;
auto read() -> uint1;
auto write(uint1 data) -> void;
uint32 value;
uint32 count;
};
struct InputShiftRegister : ShiftRegister {
auto start() -> maybe<uint1>;
auto opcode() -> maybe<uint2>;
auto mode() -> maybe<uint2>;
auto address() -> maybe<uint9>;
auto data() -> maybe<uint16>;
auto increment() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
uint32 addressLength;
uint32 dataLength;
} input;
struct OutputShiftRegister : ShiftRegister {
//serialization.cpp
auto serialize(serializer&) -> void;
} output;
} eeprom;
struct IO {
struct ROM {
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable[2];
} ram;
struct Accelerometer {
uint16 x = Center;
uint16 y = Center;
} accelerometer;
} io;
} mbc7;

View File

@ -1,32 +0,0 @@
auto Cartridge::MBC7::serialize(serializer& s) -> void {
eeprom.serialize(s);
s.integer(io.rom.bank);
s.integer(io.ram.enable[0]);
s.integer(io.ram.enable[1]);
s.integer(io.accelerometer.x);
s.integer(io.accelerometer.y);
}
auto Cartridge::MBC7::EEPROM::serialize(serializer& s) -> void {
s.array(data);
s.integer(size);
s.integer(width);
s.boolean(select);
s.boolean(clock);
s.boolean(writable);
s.integer(busy);
input.serialize(s);
output.serialize(s);
}
auto Cartridge::MBC7::EEPROM::InputShiftRegister::serialize(serializer& s) -> void {
s.integer(value);
s.integer(count);
s.integer(addressLength);
s.integer(dataLength);
}
auto Cartridge::MBC7::EEPROM::OutputShiftRegister::serialize(serializer& s) -> void {
s.integer(value);
s.integer(count);
}

View File

@ -1,65 +0,0 @@
auto Cartridge::MMM01::read(uint16 address) -> uint8 {
if(io.mode == 0) {
if((address & 0x8000) == 0x0000) { //$0000-7fff
return cartridge.rom.read(cartridge.rom.size - 0x8000 + bits(address,0-14));
}
return 0xff;
} else {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read((io.rom.base << 14) + bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read((io.rom.base << 14) + (io.rom.bank << 14) + bits(address,0-13));
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return 0xff;
return cartridge.ram.read(io.ram.bank << 13 | bits(address,0-12));
}
return 0xff;
}
}
auto Cartridge::MMM01::write(uint16 address, uint8 data) -> void {
if(io.mode == 0) {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.mode = 1;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.base = bits(data,0-5);
}
} else {
if((address & 0xe000) == 0x0000) { //$0000-1fff
io.ram.enable = bits(data,0-3) == 0x0a;
}
if((address & 0xe000) == 0x2000) { //$2000-3fff
io.rom.bank = data;
}
if((address & 0xe000) == 0x4000) { //$4000-5fff
io.ram.bank = data;
}
if((address & 0xe000) == 0xa000) { //$a000-bfff
if(!io.ram.enable) return;
cartridge.ram.write(io.ram.bank << 13 | bits(address,0-12), data);
}
}
}
auto Cartridge::MMM01::power() -> void {
io = {};
}
auto Cartridge::MMM01::serialize(serializer& s) -> void {
s.integer(io.mode);
s.integer(io.rom.base);
s.integer(io.rom.bank);
s.integer(io.ram.enable);
s.integer(io.ram.bank);
}

View File

@ -1,18 +0,0 @@
struct MMM01 : Mapper {
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer& s) -> void;
struct IO {
uint1 mode;
struct ROM {
uint6 base;
uint8 bank = 0x01;
} rom;
struct RAM {
uint1 enable;
uint8 bank;
} ram;
} io;
} mmm01;

View File

@ -1,9 +0,0 @@
auto Cartridge::serialize(serializer& s) -> void {
Thread::serialize(s);
if(ram.size) s.array(ram.data, ram.size);
if(rtc.size) s.array(rtc.data, rtc.size);
s.integer(bootromEnable);
mapper->serialize(s);
}

View File

@ -1,236 +0,0 @@
//U1: TAMA7: Mask ROM (512KB)
//U2: TAMA5: Game Boy cartridge connector interface
//U3: TAMA6: Toshiba TMP47C243M (4-bit MCU)
//U4: RTC: Toshiba TC8521AM
//note: the TMP47C243M's 2048 x 8-bit program ROM is currently undumped
//as such, high level emulation is used as a necessary evil
auto Cartridge::TAMA::second() -> void {
if(++rtc.second >= 60) {
rtc.second = 0;
if(++rtc.minute >= 60) {
rtc.minute = 0;
if(rtc.hourMode == 0 && ++rtc.hour >= 12) {
rtc.hour = 0;
rtc.meridian++;
}
if(rtc.hourMode == 1 && ++rtc.hour >= 24) {
rtc.hour = 0;
rtc.meridian = rtc.hour >= 12;
}
if((rtc.hourMode == 0 && rtc.hour == 0 && rtc.meridian == 0)
|| (rtc.hourMode == 1 && rtc.hour == 0)
) {
uint days[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
if(rtc.leapYear == 0) days[1] = 29; //extra day in February for leap years
if(++rtc.day > days[(rtc.month - 1) % 12]) {
rtc.day = 1;
if(++rtc.month > 12) {
rtc.month = 1;
rtc.leapYear++;
if(++rtc.year >= 100) {
rtc.year = 0;
}
}
}
}
}
}
}
auto Cartridge::TAMA::read(uint16 address) -> uint8 {
if((address & 0xc000) == 0x0000) { //$0000-3fff
return cartridge.rom.read(bits(address,0-13));
}
if((address & 0xc000) == 0x4000) { //$4000-7fff
return cartridge.rom.read(io.rom.bank << 14 | bits(address,0-13));
}
if((address & 0xe001) == 0xa000) { //$a000-bfff (even)
if(io.select == 0x0a) {
return 0xf0 | io.ready;
}
if(io.mode == 0 || io.mode == 1) {
if(io.select == 0x0c) {
return 0xf0 | bits(io.output,0-3);
}
if(io.select == 0x0d) {
return 0xf0 | bits(io.output,4-7);
}
}
if(io.mode == 2 || io.mode == 4) {
if(io.select == 0x0c || io.select == 0x0d) {
uint4 data;
if(rtc.index == 0) data = rtc.minute % 10;
if(rtc.index == 1) data = rtc.minute / 10;
if(rtc.index == 2) data = rtc.hour % 10;
if(rtc.index == 3) data = rtc.hour / 10;
if(rtc.index == 4) data = rtc.day / 10;
if(rtc.index == 5) data = rtc.day % 10;
if(rtc.index == 6) data = rtc.month / 10;
if(rtc.index == 7) data = rtc.month % 10;
rtc.index++;
return 0xf0 | data;
}
}
return 0xff;
}
if((address & 0xe001) == 0xa001) { //$a000-bfff (odd)
return 0xff;
}
return 0xff;
}
auto Cartridge::TAMA::write(uint16 address, uint8 data) -> void {
auto toBCD = [](uint8 data) -> uint8 { return (data / 10) * 16 + (data % 10); };
auto fromBCD = [](uint8 data) -> uint8 { return (data / 16) * 10 + (data % 16); };
if((address & 0xe001) == 0xa000) { //$a000-bfff (even)
if(io.select == 0x00) {
bits(io.rom.bank,0-3) = (uint)bits(data,0-3);
}
if(io.select == 0x01) {
bit1(io.rom.bank,4) = (uint)bit1(data,0);
}
if(io.select == 0x04) {
bits(io.input,0-3) = (uint)bits(data,0-3);
}
if(io.select == 0x05) {
bits(io.input,4-7) = (uint)bits(data,0-3);
}
if(io.select == 0x06) {
bit1(io.index,4) = (uint)bit1(data,0);
io.mode = bits(data,1-3);
}
if(io.select == 0x07) {
bits(io.index,0-3) = (uint)bits(data,0-3);
if(io.mode == 0) {
cartridge.ram.write(io.index, io.input);
}
if(io.mode == 1) {
io.output = cartridge.ram.read(io.index);
}
if(io.mode == 2 && io.index == 0x04) {
rtc.minute = fromBCD(io.input);
}
if(io.mode == 2 && io.index == 0x05) {
rtc.hour = fromBCD(io.input);
rtc.meridian = rtc.hour >= 12;
}
if(io.mode == 4 && io.index == 0x00 && bits(io.input,0-3) == 0x7) {
uint8 day = toBCD(rtc.day);
bits(day,0-3) = (uint)bits(io.input,4-7);
rtc.day = fromBCD(day);
}
if(io.mode == 4 && io.index == 0x00 && bits(io.input,0-3) == 0x8) {
uint8 day = toBCD(rtc.day);
bits(day,4-7) = (uint)bits(io.input,4-7);
rtc.day = fromBCD(day);
}
if(io.mode == 4 && io.index == 0x00 && bits(io.input,0-3) == 0x9) {
uint8 month = toBCD(rtc.month);
bits(month,0-3) = (uint)bits(io.input,4-7);
rtc.month = fromBCD(month);
}
if(io.mode == 4 && io.index == 0x00 && bits(io.input,0-3) == 0xa) {
uint8 month = toBCD(rtc.month);
bits(month,4-7) = (uint)bits(io.input,4-7);
rtc.month = fromBCD(month);
}
if(io.mode == 4 && io.index == 0x00 && bits(io.input,0-3) == 0xb) {
uint8 year = toBCD(rtc.year);
bits(year,0-3) = (uint)bits(io.input,4-7);
rtc.year = fromBCD(year);
}
if(io.mode == 4 && io.index == 0x00 && bits(io.input,0-3) == 0xc) {
uint8 year = toBCD(rtc.year);
bits(year,4-7) = (uint)bits(io.input,4-7);
rtc.year = fromBCD(year);
}
if(io.mode == 4 && io.index == 0x02 && bits(io.input,0-3) == 0xa) {
rtc.hourMode = bit1(io.input,4);
rtc.second = 0; //hack: unclear where this is really being set (if it is at all)
}
if(io.mode == 4 && io.index == 0x02 && bits(io.input,0-3) == 0xb) {
rtc.leapYear = bits(data,4-5);
}
if(io.mode == 4 && io.index == 0x02 && bits(io.input,0-3) == 0xe) {
rtc.test = bits(io.input,4-7);
}
if(io.mode == 2 && io.index == 0x06) {
rtc.index = 0;
}
}
return;
}
if((address & 0xe001) == 0xa001) { //$a000-bfff (odd)
io.select = bits(data,0-3);
if(io.select == 0x0a) {
io.ready = true;
}
return;
}
}
auto Cartridge::TAMA::power() -> void {
io = {};
}
auto Cartridge::TAMA::serialize(serializer& s) -> void {
s.integer(io.ready);
s.integer(io.select);
s.integer(io.mode);
s.integer(io.index);
s.integer(io.input);
s.integer(io.output);
s.integer(io.rom.bank);
s.integer(rtc.year);
s.integer(rtc.month);
s.integer(rtc.day);
s.integer(rtc.hour);
s.integer(rtc.minute);
s.integer(rtc.second);
s.integer(rtc.meridian);
s.integer(rtc.leapYear);
s.integer(rtc.hourMode);
s.integer(rtc.test);
}

View File

@ -1,33 +0,0 @@
struct TAMA : Mapper {
auto second() -> void;
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
struct IO {
uint1 ready;
uint4 select;
uint3 mode;
uint5 index;
uint8 input;
uint8 output;
struct ROM {
uint5 bank;
} rom;
} io;
struct RTC {
uint8 year; //0 - 99
uint8 month; //1 - 12
uint8 day; //1 - 31
uint8 hour; //0 - 23
uint8 minute; //0 - 59
uint8 second; //0 - 59
uint1 meridian; //0 = AM; 1 = PM
uint2 leapYear; //0 = leap year; 1-3 = non-leap year
uint1 hourMode; //0 = 12-hour; 1 = 24-hour
uint4 test;
uint8 index;
} rtc;
} tama;

View File

@ -1,129 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
#include "io.cpp"
#include "memory.cpp"
#include "timing.cpp"
#include "serialization.cpp"
CPU cpu;
auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
interruptTest();
instruction();
}
auto CPU::raise(CPU::Interrupt id) -> void {
if(id == Interrupt::Vblank) {
status.interruptRequestVblank = 1;
if(status.interruptEnableVblank) r.halt = false;
}
if(id == Interrupt::Stat) {
status.interruptRequestStat = 1;
if(status.interruptEnableStat) r.halt = false;
}
if(id == Interrupt::Timer) {
status.interruptRequestTimer = 1;
if(status.interruptEnableTimer) r.halt = false;
}
if(id == Interrupt::Serial) {
status.interruptRequestSerial = 1;
if(status.interruptEnableSerial) r.halt = false;
}
if(id == Interrupt::Joypad) {
status.interruptRequestJoypad = 1;
if(status.interruptEnableJoypad) r.halt = r.stop = false;
}
}
auto CPU::interruptTest() -> void {
if(!r.ime) return;
if(status.interruptRequestVblank && status.interruptEnableVblank) {
status.interruptRequestVblank = 0;
return interrupt(0x0040);
}
if(status.interruptRequestStat && status.interruptEnableStat) {
status.interruptRequestStat = 0;
return interrupt(0x0048);
}
if(status.interruptRequestTimer && status.interruptEnableTimer) {
status.interruptRequestTimer = 0;
return interrupt(0x0050);
}
if(status.interruptRequestSerial && status.interruptEnableSerial) {
status.interruptRequestSerial = 0;
return interrupt(0x0058);
}
if(status.interruptRequestJoypad && status.interruptEnableJoypad) {
status.interruptRequestJoypad = 0;
return interrupt(0x0060);
}
}
auto CPU::stop() -> bool {
if(status.speedSwitch) {
status.speedSwitch = 0;
status.speedDouble ^= 1;
if(status.speedDouble == 0) setFrequency(4 * 1024 * 1024);
if(status.speedDouble == 1) setFrequency(8 * 1024 * 1024);
return true;
}
return false;
}
auto CPU::power() -> void {
create(Enter, 4 * 1024 * 1024);
SM83::power();
for(uint n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
for(uint n = 0xe000; n <= 0xfdff; n++) bus.mmio[n] = this; //WRAM (mirror)
for(uint n = 0xff80; n <= 0xfffe; n++) bus.mmio[n] = this; //HRAM
bus.mmio[0xff00] = this; //JOYP
bus.mmio[0xff01] = this; //SB
bus.mmio[0xff02] = this; //SC
bus.mmio[0xff04] = this; //DIV
bus.mmio[0xff05] = this; //TIMA
bus.mmio[0xff06] = this; //TMA
bus.mmio[0xff07] = this; //TAC
bus.mmio[0xff0f] = this; //IF
bus.mmio[0xffff] = this; //IE
if(Model::GameBoyColor()) {
bus.mmio[0xff4d] = this; //KEY1
bus.mmio[0xff51] = this; //HDMA1
bus.mmio[0xff52] = this; //HDMA2
bus.mmio[0xff53] = this; //HDMA3
bus.mmio[0xff54] = this; //HDMA4
bus.mmio[0xff55] = this; //HDMA5
bus.mmio[0xff56] = this; //RP
bus.mmio[0xff6c] = this; //???
bus.mmio[0xff70] = this; //SVBK
bus.mmio[0xff72] = this; //???
bus.mmio[0xff73] = this; //???
bus.mmio[0xff74] = this; //???
bus.mmio[0xff75] = this; //???
bus.mmio[0xff76] = this; //???
bus.mmio[0xff77] = this; //???
}
for(auto& n : wram) n = 0x00;
for(auto& n : hram) n = 0x00;
status = {};
}
}

View File

@ -1,112 +0,0 @@
struct CPU : Processor::SM83, Thread, MMIO {
enum class Interrupt : uint { Vblank, Stat, Timer, Serial, Joypad };
static auto Enter() -> void;
auto main() -> void;
auto raise(Interrupt id) -> void;
auto interruptTest() -> void;
auto stop() -> bool;
auto power() -> void;
auto serialize(serializer&) -> void;
//io.cpp
auto wramAddress(uint16 addr) const -> uint;
auto joypPoll() -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
//memory.cpp
auto idle() -> void override;
auto read(uint16 addr) -> uint8 override;
auto write(uint16 addr, uint8 data) -> void override;
auto cycleEdge() -> void;
auto readDMA(uint16 addr) -> uint8;
auto writeDMA(uint16 addr, uint8 data) -> void;
auto readDebugger(uint16 addr) -> uint8 override;
//timing.cpp
auto step(uint clocks) -> void;
auto timer262144hz() -> void;
auto timer65536hz() -> void;
auto timer16384hz() -> void;
auto timer8192hz() -> void;
auto timer4096hz() -> void;
auto hblank() -> void;
struct Status {
uint22 clock;
//$ff00 JOYP
bool p15 = 0;
bool p14 = 0;
uint8 joyp;
//$ff01 SB
uint8 serialData;
uint serialBits = 0;
//$ff02 SC
bool serialTransfer = 0;
bool serialClock = 0;
//$ff04 DIV
uint16 div;
//$ff05 TIMA
uint8 tima;
//$ff06 TMA
uint8 tma;
//$ff07 TAC
bool timerEnable = 0;
uint timerClock = 0;
//$ff0f IF
bool interruptRequestJoypad = 0;
bool interruptRequestSerial = 0;
bool interruptRequestTimer = 0;
bool interruptRequestStat = 0;
bool interruptRequestVblank = 0;
//$ff4d KEY1
bool speedDouble = 0;
bool speedSwitch = 0;
//$ff51,$ff52 HDMA1,HDMA2
uint16 dmaSource;
//$ff53,$ff54 HDMA3,HDMA4
uint16 dmaTarget;
//$ff55 HDMA5
bool dmaMode = 0;
uint16 dmaLength;
bool dmaCompleted = 1;
//$ff6c ???
uint8 ff6c;
//$ff70 SVBK
uint3 wramBank = 1;
//$ff72-$ff75 ???
uint8 ff72;
uint8 ff73;
uint8 ff74;
uint8 ff75;
//$ffff IE
bool interruptEnableJoypad = 0;
bool interruptEnableSerial = 0;
bool interruptEnableTimer = 0;
bool interruptEnableStat = 0;
bool interruptEnableVblank = 0;
} status;
uint8 wram[32768]; //GB=8192, GBC=32768
uint8 hram[128];
};
extern CPU cpu;

View File

@ -1,283 +0,0 @@
auto CPU::wramAddress(uint16 addr) const -> uint {
addr &= 0x1fff;
if(addr < 0x1000) return addr;
auto bank = status.wramBank + (status.wramBank == 0);
return (bank * 0x1000) + (addr & 0x0fff);
}
auto CPU::joypPoll() -> void {
function<auto (uint, uint, uint) -> int16> inputPoll = {&Emulator::Platform::inputPoll, platform};
if(Model::SuperGameBoy()) inputPoll = {&SuperGameBoyInterface::inputPoll, superGameBoy};
uint button = 0;
button |= inputPoll(0, 0, (uint)Input::Start) << 3;
button |= inputPoll(0, 0, (uint)Input::Select) << 2;
button |= inputPoll(0, 0, (uint)Input::B) << 1;
button |= inputPoll(0, 0, (uint)Input::A) << 0;
uint dpad = 0;
dpad |= inputPoll(0, 0, (uint)Input::Down) << 3;
dpad |= inputPoll(0, 0, (uint)Input::Up) << 2;
dpad |= inputPoll(0, 0, (uint)Input::Left) << 1;
dpad |= inputPoll(0, 0, (uint)Input::Right) << 0;
if(!Model::SuperGameBoy()) {
//D-pad pivot makes it impossible to press opposing directions at the same time
//however, Super Game Boy BIOS is able to set these bits together
if(dpad & 4) dpad &= ~8; //disallow up+down
if(dpad & 2) dpad &= ~1; //disallow left+right
}
status.joyp = 0x0f;
if(status.p15 == 1 && status.p14 == 1 && Model::SuperGameBoy()) {
status.joyp = superGameBoy->joypRead();
}
if(status.p15 == 0) status.joyp &= button ^ 0x0f;
if(status.p14 == 0) status.joyp &= dpad ^ 0x0f;
if(status.joyp != 0x0f) raise(Interrupt::Joypad);
}
auto CPU::readIO(uint16 addr) -> uint8 {
if(addr >= 0xc000 && addr <= 0xfdff) return wram[wramAddress(addr)];
if(addr >= 0xff80 && addr <= 0xfffe) return hram[addr & 0x7f];
if(addr == 0xff00) { //JOYP
joypPoll();
return 0xc0
| (status.p15 << 5)
| (status.p14 << 4)
| (status.joyp << 0);
}
if(addr == 0xff01) { //SB
return 0x00;
}
if(addr == 0xff02) { //SC
return (status.serialTransfer << 7)
| 0x7e
| (status.serialClock << 0);
}
if(addr == 0xff04) { //DIV
return status.div >> 8;
}
if(addr == 0xff05) { //TIMA
return status.tima;
}
if(addr == 0xff06) { //TMA
return status.tma;
}
if(addr == 0xff07) { //TAC
return 0xf8
| (status.timerEnable << 2)
| (status.timerClock << 0);
}
if(addr == 0xff0f) { //IF
return 0xe0
| (status.interruptRequestJoypad << 4)
| (status.interruptRequestSerial << 3)
| (status.interruptRequestTimer << 2)
| (status.interruptRequestStat << 1)
| (status.interruptRequestVblank << 0);
}
if(addr == 0xff4d) { //KEY1
return (status.speedDouble << 7);
}
if(addr == 0xff55) { //HDMA5
return (status.dmaCompleted << 7)
| (((status.dmaLength / 16) - 1) & 0x7f);
}
if(addr == 0xff56) { //RP
return 0x02;
}
if(addr == 0xff6c) { //???
return 0xfe | status.ff6c;
}
if(addr == 0xff70) { //SVBK
return status.wramBank;
}
if(addr == 0xff72) { //???
return status.ff72;
}
if(addr == 0xff73) { //???
return status.ff73;
}
if(addr == 0xff74) { //???
return status.ff74;
}
if(addr == 0xff75) { //???
return 0x8f | status.ff75;
}
if(addr == 0xff76) { //???
return 0xff;
}
if(addr == 0xff77) { //???
return 0xff;
}
if(addr == 0xffff) { //IE
return 0xe0
| (status.interruptEnableJoypad << 4)
| (status.interruptEnableSerial << 3)
| (status.interruptEnableTimer << 2)
| (status.interruptEnableStat << 1)
| (status.interruptEnableVblank << 0);
}
return 0xff;
}
auto CPU::writeIO(uint16 addr, uint8 data) -> void {
if(addr >= 0xc000 && addr <= 0xfdff) { wram[wramAddress(addr)] = data; return; }
if(addr >= 0xff80 && addr <= 0xfffe) { hram[addr & 0x7f] = data; return; }
if(addr == 0xff00) { //JOYP
status.p15 = data & 0x20;
status.p14 = data & 0x10;
if(Model::SuperGameBoy()) superGameBoy->joypWrite(status.p15, status.p14);
return;
}
if(addr == 0xff01) { //SB
status.serialData = data;
return;
}
if(addr == 0xff02) { //SC
status.serialTransfer = data & 0x80;
status.serialClock = data & 0x01;
if(status.serialTransfer) status.serialBits = 8;
return;
}
if(addr == 0xff04) { //DIV
status.div = 0;
return;
}
if(addr == 0xff05) { //TIMA
status.tima = data;
return;
}
if(addr == 0xff06) { //TMA
status.tma = data;
return;
}
if(addr == 0xff07) { //TAC
status.timerEnable = data & 0x04;
status.timerClock = data & 0x03;
return;
}
if(addr == 0xff0f) { //IF
status.interruptRequestJoypad = data & 0x10;
status.interruptRequestSerial = data & 0x08;
status.interruptRequestTimer = data & 0x04;
status.interruptRequestStat = data & 0x02;
status.interruptRequestVblank = data & 0x01;
return;
}
if(addr == 0xff4d) { //KEY1
status.speedSwitch = data & 0x01;
return;
}
if(addr == 0xff51) { //HDMA1
status.dmaSource = (status.dmaSource & 0x00ff) | (data << 8);
return;
}
if(addr == 0xff52) { //HDMA2
status.dmaSource = (status.dmaSource & 0xff00) | (data & 0xf0);
return;
}
if(addr == 0xff53) { //HDMA3
status.dmaTarget = (status.dmaTarget & 0x00ff) | (data << 8);
return;
}
if(addr == 0xff54) { //HDMA4
status.dmaTarget = (status.dmaTarget & 0xff00) | (data & 0xf0);
return;
}
if(addr == 0xff55) { //HDMA5
status.dmaMode = data & 0x80;
status.dmaLength = ((data & 0x7f) + 1) * 16;
status.dmaCompleted = !status.dmaMode;
if(status.dmaMode == 0) {
do {
for(auto n : range(16)) {
writeDMA(status.dmaTarget++, readDMA(status.dmaSource++));
}
step(8 << status.speedDouble);
status.dmaLength -= 16;
} while(status.dmaLength);
}
return;
}
if(addr == 0xff56) { //RP
return;
}
if(addr == 0xff6c) { //???
status.ff6c = data & 0x01;
return;
}
if(addr == 0xff72) { //???
status.ff72 = data;
return;
}
if(addr == 0xff73) { //???
status.ff73 = data;
return;
}
if(addr == 0xff74) { //???
status.ff74 = data;
return;
}
if(addr == 0xff75) { //???
status.ff75 = data & 0x70;
return;
}
if(addr == 0xff70) { //SVBK
status.wramBank = data & 0x07;
return;
}
if(addr == 0xffff) { //IE
status.interruptEnableJoypad = data & 0x10;
status.interruptEnableSerial = data & 0x08;
status.interruptEnableTimer = data & 0x04;
status.interruptEnableStat = data & 0x02;
status.interruptEnableVblank = data & 0x01;
return;
}
}

View File

@ -1,41 +0,0 @@
auto CPU::idle() -> void {
cycleEdge();
step(4);
}
auto CPU::read(uint16 addr) -> uint8 {
cycleEdge();
step(4);
return bus.read(addr);
}
auto CPU::write(uint16 addr, uint8 data) -> void {
cycleEdge();
step(4);
bus.write(addr, data);
}
auto CPU::cycleEdge() -> void {
if(r.ei) {
r.ei = false;
r.ime = 1;
}
}
//VRAM DMA source can only be ROM or RAM
auto CPU::readDMA(uint16 addr) -> uint8 {
if(addr < 0x8000) return bus.read(addr); //0000-7fff
if(addr < 0xa000) return 0xff; //8000-9fff
if(addr < 0xe000) return bus.read(addr); //a000-dfff
return 0xff; //e000-ffff
}
//VRAM DMA target is always VRAM
auto CPU::writeDMA(uint16 addr, uint8 data) -> void {
addr = 0x8000 | (addr & 0x1fff); //8000-9fff
return bus.write(addr, data);
}
auto CPU::readDebugger(uint16 addr) -> uint8 {
return bus.read(addr);
}

View File

@ -1,55 +0,0 @@
auto CPU::serialize(serializer& s) -> void {
SM83::serialize(s);
Thread::serialize(s);
s.array(wram);
s.array(hram);
s.integer(status.clock);
s.integer(status.p15);
s.integer(status.p14);
s.integer(status.joyp);
s.integer(status.serialData);
s.integer(status.serialBits);
s.integer(status.serialTransfer);
s.integer(status.serialClock);
s.integer(status.div);
s.integer(status.tima);
s.integer(status.tma);
s.integer(status.timerEnable);
s.integer(status.timerClock);
s.integer(status.interruptRequestJoypad);
s.integer(status.interruptRequestSerial);
s.integer(status.interruptRequestTimer);
s.integer(status.interruptRequestStat);
s.integer(status.interruptRequestVblank);
s.integer(status.speedDouble);
s.integer(status.speedSwitch);
s.integer(status.dmaSource);
s.integer(status.dmaTarget);
s.integer(status.dmaMode);
s.integer(status.dmaLength);
s.integer(status.dmaCompleted);
s.integer(status.ff6c);
s.integer(status.wramBank);
s.integer(status.ff72);
s.integer(status.ff73);
s.integer(status.ff74);
s.integer(status.ff75);
s.integer(status.interruptEnableJoypad);
s.integer(status.interruptEnableSerial);
s.integer(status.interruptEnableTimer);
s.integer(status.interruptEnableStat);
s.integer(status.interruptEnableVblank);
}

View File

@ -1,84 +0,0 @@
//70224 clocks/frame
// 456 clocks/scanline
// 154 scanlines/frame
auto CPU::step(uint clocks) -> void {
for(auto n : range(clocks)) {
if(++status.clock == 0) {
cartridge.second();
}
//4MHz / N(hz) - 1 = mask
status.div++;
if((status.div & 15) == 0) timer262144hz();
if((status.div & 63) == 0) timer65536hz();
if((status.div & 255) == 0) timer16384hz();
if((status.div & 511) == 0) timer8192hz();
if((status.div & 1023) == 0) timer4096hz();
Thread::step(1);
synchronize(ppu);
synchronize(apu);
synchronize(cartridge);
}
if(Model::SuperGameBoy()) {
system._clocksExecuted += clocks;
scheduler.exit(Scheduler::Event::Step);
}
}
auto CPU::timer262144hz() -> void {
if(status.timerEnable && status.timerClock == 1) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
}
auto CPU::timer65536hz() -> void {
if(status.timerEnable && status.timerClock == 2) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
}
auto CPU::timer16384hz() -> void {
if(status.timerEnable && status.timerClock == 3) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
}
auto CPU::timer8192hz() -> void {
if(status.serialTransfer && status.serialClock) {
if(--status.serialBits == 0) {
status.serialTransfer = 0;
raise(Interrupt::Serial);
}
}
}
auto CPU::timer4096hz() -> void {
if(status.timerEnable && status.timerClock == 0) {
if(++status.tima == 0) {
status.tima = status.tma;
raise(Interrupt::Timer);
}
}
}
auto CPU::hblank() -> void {
if(status.dmaMode == 1 && status.dmaLength && ppu.status.ly < 144) {
for(auto n : range(16)) {
writeDMA(status.dmaTarget++, readDMA(status.dmaSource++));
status.dmaLength--;
if(n & 1) step(1 << status.speedDouble);
}
}
}

View File

@ -1,46 +0,0 @@
#pragma once
//license: GPLv3
//started: 2010-12-27
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/cheat.hpp>
#include <processor/sm83/sm83.hpp>
namespace GameBoy {
#define platform Emulator::platform
namespace File = Emulator::File;
using Scheduler = Emulator::Scheduler;
using Cheat = Emulator::Cheat;
extern Scheduler scheduler;
extern Cheat cheat;
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency);
scheduler.append(*this);
}
inline auto synchronize(Thread& thread) -> void {
if(clock() >= thread.clock()) scheduler.resume(thread);
}
};
struct Model {
inline static auto GameBoy() -> bool;
inline static auto GameBoyColor() -> bool;
inline static auto SuperGameBoy() -> bool;
};
#include <gb/memory/memory.hpp>
#include <gb/system/system.hpp>
#include <gb/cartridge/cartridge.hpp>
#include <gb/cpu/cpu.hpp>
#include <gb/ppu/ppu.hpp>
#include <gb/apu/apu.hpp>
}
#include <gb/interface/interface.hpp>

View File

@ -1,23 +0,0 @@
auto GameBoyColorInterface::information() -> Information {
Information information;
information.manufacturer = "Nintendo";
information.name = "Game Boy Color";
information.extension = "gbc";
return information;
}
auto GameBoyColorInterface::color(uint32 color) -> uint64 {
uint r = bits(color, 0- 4);
uint g = bits(color, 5- 9);
uint b = bits(color,10-14);
uint64 R = image::normalize(r, 5, 16);
uint64 G = image::normalize(g, 5, 16);
uint64 B = image::normalize(b, 5, 16);
return R << 32 | G << 16 | B << 0;
}
auto GameBoyColorInterface::load() -> bool {
return system.load(this, System::Model::GameBoyColor);
}

View File

@ -1,16 +0,0 @@
auto GameBoyInterface::information() -> Information {
Information information;
information.manufacturer = "Nintendo";
information.name = "Game Boy";
information.extension = "gb";
return information;
}
auto GameBoyInterface::color(uint32 color) -> uint64 {
uint64 L = image::normalize(3 - color, 2, 16);
return L << 32 | L << 16 | L << 0;
}
auto GameBoyInterface::load() -> bool {
return system.load(this, System::Model::GameBoy);
}

View File

@ -1,123 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
SuperGameBoyInterface* superGameBoy = nullptr;
#include "game-boy.cpp"
#include "game-boy-color.cpp"
auto Interface::display() -> Display {
Display display;
display.type = Display::Type::LCD;
display.colors = Model::GameBoyColor() ? 1 << 15 : 1 << 2;
display.width = 160;
display.height = 144;
display.internalWidth = 160;
display.internalHeight = 144;
display.aspectCorrection = 1.0;
return display;
}
auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::hashes() -> vector<string> {
return {cartridge.hash()};
}
auto Interface::manifests() -> vector<string> {
return {cartridge.manifest()};
}
auto Interface::titles() -> vector<string> {
return {cartridge.title()};
}
auto Interface::save() -> void {
system.save();
}
auto Interface::unload() -> void {
save();
system.unload();
}
auto Interface::ports() -> vector<Port> { return {
{ID::Port::Hardware, "Hardware"},
{ID::Port::Cartridge, "Cartridge"}};
}
auto Interface::devices(uint port) -> vector<Device> {
if(port == ID::Port::Hardware) return {
{ID::Device::Controls, "Controls"}
};
if(port == ID::Port::Cartridge) return {
{ID::Device::MBC5, "MBC5"},
{ID::Device::MBC7, "MBC7"}
};
return {};
}
auto Interface::inputs(uint device) -> vector<Input> {
using Type = Input::Type;
if(device == ID::Device::Controls) return {
{Type::Hat, "Up" },
{Type::Hat, "Down" },
{Type::Hat, "Left" },
{Type::Hat, "Right" },
{Type::Button, "B" },
{Type::Button, "A" },
{Type::Control, "Select"},
{Type::Control, "Start" }
};
if(device == ID::Device::MBC5) return {
{Type::Rumble, "Rumble"}
};
if(device == ID::Device::MBC7) return {
{Type::Axis, "Accelerometer - X-axis"},
{Type::Axis, "Accelerometer - Y-axis"}
};
return {};
}
auto Interface::power() -> void {
system.power();
}
auto Interface::run() -> void {
system.run();
}
auto Interface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto Interface::cheats(const vector<string>& list) -> void {
cheat.assign(list);
}
auto Interface::cap(const string& name) -> bool {
return false;
}
auto Interface::get(const string& name) -> any {
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
return false;
}
}

View File

@ -1,79 +0,0 @@
namespace GameBoy {
struct ID {
enum : uint {
System,
GameBoy,
SuperGameBoy,
GameBoyColor,
};
struct Port { enum : uint {
Hardware,
Cartridge,
};};
struct Device { enum : uint {
Controls,
MBC5,
MBC7,
};};
};
struct Interface : Emulator::Interface {
auto display() -> Display override;
auto loaded() -> bool override;
auto hashes() -> vector<string> override;
auto manifests() -> vector<string> override;
auto titles() -> vector<string> override;
auto save() -> void override;
auto unload() -> void override;
auto ports() -> vector<Port> override;
auto devices(uint port) -> vector<Device> override;
auto inputs(uint device) -> vector<Input> override;
auto power() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheats(const vector<string>&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
};
struct GameBoyInterface : Interface {
auto information() -> Information override;
auto color(uint32 color) -> uint64 override;
auto load() -> bool override;
};
struct GameBoyColorInterface : Interface {
auto information() -> Information override;
auto color(uint32 color) -> uint64 override;
auto load() -> bool override;
};
struct SuperGameBoyInterface {
virtual auto audioSample(const float* samples, uint channels) -> void = 0;
virtual auto inputPoll(uint port, uint device, uint id) -> int16 = 0;
virtual auto lcdScanline() -> void = 0;
virtual auto lcdOutput(uint2 color) -> void = 0;
virtual auto joypRead() -> uint4 = 0;
virtual auto joypWrite(bool p15, bool p14) -> void = 0;
};
extern SuperGameBoyInterface* superGameBoy;
}

View File

@ -1,56 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
Unmapped unmapped;
Bus bus;
Memory::~Memory() {
free();
}
auto Memory::operator[](uint addr) -> uint8& {
return data[addr];
}
auto Memory::allocate(uint size_) -> void {
free();
size = size_;
data = new uint8[size];
}
auto Memory::copy(const uint8* data_, unsigned size_) -> void {
free();
size = size_;
data = new uint8[size];
memcpy(data, data_, size);
}
auto Memory::free() -> void {
if(data) {
delete[] data;
data = nullptr;
}
}
//
auto Bus::read(uint16 addr) -> uint8 {
uint8 data = mmio[addr]->readIO(addr);
if(cheat) {
if(auto result = cheat.find(addr, data)) return result();
}
return data;
}
auto Bus::write(uint16 addr, uint8 data) -> void {
mmio[addr]->writeIO(addr, data);
}
auto Bus::power() -> void {
for(auto n : range(65536)) mmio[n] = &unmapped;
}
}

View File

@ -1,32 +0,0 @@
struct Memory {
~Memory();
auto operator[](uint addr) -> uint8&;
auto allocate(uint size) -> void;
auto copy(const uint8* data, uint size) -> void;
auto free() -> void;
uint8* data = nullptr;
uint size = 0;
};
struct MMIO {
virtual auto readIO(uint16 addr) -> uint8 = 0;
virtual auto writeIO(uint16 addr, uint8 data) -> void = 0;
};
struct Unmapped : MMIO {
auto readIO(uint16) -> uint8 { return 0xff; }
auto writeIO(uint16, uint8) -> void {}
};
struct Bus {
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power() -> void;
MMIO* mmio[65536];
};
extern Unmapped unmapped;
extern Bus bus;

View File

@ -1,158 +0,0 @@
//BG attributes:
//0x80: 0 = OAM priority, 1 = BG priority
//0x40: vertical flip
//0x20: horizontal flip
//0x08: VRAM bank#
//0x07: palette#
//OB attributes:
//0x80: 0 = OBJ above BG, 1 = BG above OBJ
//0x40: vertical flip
//0x20: horizontal flip
//0x08: VRAM bank#
//0x07: palette#
auto PPU::readTileCGB(bool select, uint x, uint y, uint& attr, uint& data) -> void {
uint tmaddr = 0x1800 + (select << 10);
tmaddr += (((y >> 3) << 5) + (x >> 3)) & 0x03ff;
uint tile = vram[0x0000 + tmaddr];
attr = vram[0x2000 + tmaddr];
uint tdaddr = attr & 0x08 ? 0x2000 : 0x0000;
if(status.bgTiledataSelect == 0) {
tdaddr += 0x1000 + ((int8)tile << 4);
} else {
tdaddr += 0x0000 + (tile << 4);
}
y &= 7;
if(attr & 0x40) y ^= 7;
tdaddr += y << 1;
data = vram[tdaddr++] << 0;
data |= vram[tdaddr++] << 8;
if(attr & 0x20) data = hflip(data);
}
auto PPU::scanlineCGB() -> void {
px = 0;
const uint Height = (status.obSize == 0 ? 8 : 16);
sprites = 0;
//find first ten sprites on this scanline
for(uint n = 0; n < 40 * 4; n += 4) {
Sprite& s = sprite[sprites];
s.y = oam[n + 0] - 16;
s.x = oam[n + 1] - 8;
s.tile = oam[n + 2] & ~status.obSize;
s.attr = oam[n + 3];
s.y = status.ly - s.y;
if(s.y >= Height) continue;
if(s.attr & 0x40) s.y ^= (Height - 1);
uint tdaddr = (s.attr & 0x08 ? 0x2000 : 0x0000) + (s.tile << 4) + (s.y << 1);
s.data = vram[tdaddr + 0] << 0;
s.data |= vram[tdaddr + 1] << 8;
if(s.attr & 0x20) s.data = hflip(s.data);
if(++sprites == 10) break;
}
}
auto PPU::runCGB() -> void {
ob.color = 0;
ob.palette = 0;
ob.priority = 0;
uint color = 0x7fff;
runBackgroundCGB();
if(status.windowDisplayEnable) runWindowCGB();
if(status.obEnable) runObjectsCGB();
if(ob.palette == 0) {
color = bg.color;
} else if(bg.palette == 0) {
color = ob.color;
} else if(status.bgEnable == 0) {
color = ob.color;
} else if(bg.priority) {
color = bg.color;
} else if(ob.priority) {
color = ob.color;
} else {
color = bg.color;
}
uint32* output = screen + status.ly * 160 + px++;
*output = color;
}
auto PPU::runBackgroundCGB() -> void {
uint scrolly = (status.ly + status.scy) & 255;
uint scrollx = (px + status.scx) & 255;
uint tx = scrollx & 7;
if(tx == 0 || px == 0) readTileCGB(status.bgTilemapSelect, scrollx, scrolly, background.attr, background.data);
uint index = 0;
index |= (background.data & (0x0080 >> tx)) ? 1 : 0;
index |= (background.data & (0x8000 >> tx)) ? 2 : 0;
uint palette = ((background.attr & 0x07) << 2) + index;
uint color = 0;
color |= bgpd[(palette << 1) + 0] << 0;
color |= bgpd[(palette << 1) + 1] << 8;
color &= 0x7fff;
bg.color = color;
bg.palette = index;
bg.priority = background.attr & 0x80;
}
auto PPU::runWindowCGB() -> void {
uint scrolly = status.ly - status.wy;
uint scrollx = px + 7 - status.wx;
if(scrolly >= 144u) return; //also matches underflow (scrolly < 0)
if(scrollx >= 160u) return; //also matches underflow (scrollx < 0)
uint tx = scrollx & 7;
if(tx == 0 || px == 0) readTileCGB(status.windowTilemapSelect, scrollx, scrolly, window.attr, window.data);
uint index = 0;
index |= (window.data & (0x0080 >> tx)) ? 1 : 0;
index |= (window.data & (0x8000 >> tx)) ? 2 : 0;
uint palette = ((window.attr & 0x07) << 2) + index;
uint color = 0;
color |= bgpd[(palette << 1) + 0] << 0;
color |= bgpd[(palette << 1) + 1] << 8;
color &= 0x7fff;
bg.color = color;
bg.palette = index;
bg.priority = window.attr & 0x80;
}
auto PPU::runObjectsCGB() -> void {
//render backwards, so that first sprite has priority
for(int n = sprites - 1; n >= 0; n--) {
Sprite& s = sprite[n];
int tx = px - s.x;
if(tx < 0 || tx > 7) continue;
uint index = 0;
index |= (s.data & (0x0080 >> tx)) ? 1 : 0;
index |= (s.data & (0x8000 >> tx)) ? 2 : 0;
if(index == 0) continue;
uint palette = ((s.attr & 0x07) << 2) + index;
uint color = 0;
color |= obpd[(palette << 1) + 0] << 0;
color |= obpd[(palette << 1) + 1] << 8;
color &= 0x7fff;
ob.color = color;
ob.palette = index;
ob.priority = !(s.attr & 0x80);
}
}

View File

@ -1,128 +0,0 @@
//OB attributes:
//0x80: 0 = OBJ above BG, 1 = BG above OBJ
//0x40: vertical flip
//0x20: horizontal flip
//0x10: palette#
auto PPU::readTileDMG(bool select, uint x, uint y, uint& data) -> void {
uint tmaddr = 0x1800 + (select << 10), tdaddr;
tmaddr += (((y >> 3) << 5) + (x >> 3)) & 0x03ff;
if(status.bgTiledataSelect == 0) {
tdaddr = 0x1000 + ((int8)vram[tmaddr] << 4);
} else {
tdaddr = 0x0000 + (vram[tmaddr] << 4);
}
tdaddr += (y & 7) << 1;
data = vram[tdaddr + 0] << 0;
data |= vram[tdaddr + 1] << 8;
}
auto PPU::scanlineDMG() -> void {
px = 0;
const uint Height = (status.obSize == 0 ? 8 : 16);
sprites = 0;
//find first ten sprites on this scanline
for(uint n = 0; n < 40 * 4; n += 4) {
Sprite& s = sprite[sprites];
s.y = oam[n + 0] - 16;
s.x = oam[n + 1] - 8;
s.tile = oam[n + 2] & ~status.obSize;
s.attr = oam[n + 3];
s.y = status.ly - s.y;
if(s.y >= Height) continue;
if(s.attr & 0x40) s.y ^= (Height - 1);
uint tdaddr = (s.tile << 4) + (s.y << 1);
s.data = vram[tdaddr + 0] << 0;
s.data |= vram[tdaddr + 1] << 8;
if(s.attr & 0x20) s.data = hflip(s.data);
if(++sprites == 10) break;
}
//sort by X-coordinate
for(uint lo = 0; lo < sprites; lo++) {
for(uint hi = lo + 1; hi < sprites; hi++) {
if(sprite[hi].x < sprite[lo].x) swap(sprite[lo], sprite[hi]);
}
}
}
auto PPU::runDMG() -> void {
bg.color = 0;
bg.palette = 0;
ob.color = 0;
ob.palette = 0;
uint color = 0;
if(status.bgEnable) runBackgroundDMG();
if(status.windowDisplayEnable) runWindowDMG();
if(status.obEnable) runObjectsDMG();
if(ob.palette == 0) {
color = bg.color;
} else if(bg.palette == 0) {
color = ob.color;
} else if(ob.priority) {
color = ob.color;
} else {
color = bg.color;
}
uint32* output = screen + status.ly * 160 + px++;
*output = color;
if(Model::SuperGameBoy()) superGameBoy->lcdOutput(color);
}
auto PPU::runBackgroundDMG() -> void {
uint scrolly = (status.ly + status.scy) & 255;
uint scrollx = (px + status.scx) & 255;
uint tx = scrollx & 7;
if(tx == 0 || px == 0) readTileDMG(status.bgTilemapSelect, scrollx, scrolly, background.data);
uint index = 0;
index |= (background.data & (0x0080 >> tx)) ? 1 : 0;
index |= (background.data & (0x8000 >> tx)) ? 2 : 0;
bg.color = bgp[index];
bg.palette = index;
}
auto PPU::runWindowDMG() -> void {
uint scrolly = status.ly - status.wy;
uint scrollx = px + 7 - status.wx;
if(scrolly >= 144u) return; //also matches underflow (scrolly < 0)
if(scrollx >= 160u) return; //also matches underflow (scrollx < 0)
uint tx = scrollx & 7;
if(tx == 0 || px == 0) readTileDMG(status.windowTilemapSelect, scrollx, scrolly, window.data);
uint index = 0;
index |= (window.data & (0x0080 >> tx)) ? 1 : 0;
index |= (window.data & (0x8000 >> tx)) ? 2 : 0;
bg.color = bgp[index];
bg.palette = index;
}
auto PPU::runObjectsDMG() -> void {
//render backwards, so that first sprite has priority
for(int n = sprites - 1; n >= 0; n--) {
Sprite& s = sprite[n];
int tx = px - s.x;
if(tx < 0 || tx > 7) continue;
uint index = 0;
index |= (s.data & (0x0080 >> tx)) ? 1 : 0;
index |= (s.data & (0x8000 >> tx)) ? 2 : 0;
if(index == 0) continue;
ob.color = obp[(bool)(s.attr & 0x10)][index];
ob.palette = index;
ob.priority = !(s.attr & 0x80);
}
}

View File

@ -1,240 +0,0 @@
auto PPU::vramAddress(uint16 addr) const -> uint {
return status.vramBank << 13 | (uint13)addr;
}
auto PPU::readIO(uint16 addr) -> uint8 {
if(addr >= 0x8000 && addr <= 0x9fff) {
return vram[vramAddress(addr)];
}
if(addr >= 0xfe00 && addr <= 0xfe9f) {
if(status.dmaActive && status.dmaClock >= 8) return 0xff;
return oam[addr & 0xff];
}
if(addr == 0xff40) { //LCDC
return (status.displayEnable << 7)
| (status.windowTilemapSelect << 6)
| (status.windowDisplayEnable << 5)
| (status.bgTiledataSelect << 4)
| (status.bgTilemapSelect << 3)
| (status.obSize << 2)
| (status.obEnable << 1)
| (status.bgEnable << 0);
}
if(addr == 0xff41) { //STAT
return (status.interruptLYC << 6)
| (status.interruptOAM << 5)
| (status.interruptVblank << 4)
| (status.interruptHblank << 3)
| ((status.ly == status.lyc) << 2)
| (status.mode << 0);
}
if(addr == 0xff42) { //SCY
return status.scy;
}
if(addr == 0xff43) { //SCX
return status.scx;
}
if(addr == 0xff44) { //LY
return status.ly;
}
if(addr == 0xff45) { //LYC
return status.lyc;
}
if(addr == 0xff47) { //BGP
return (bgp[3] << 6)
| (bgp[2] << 4)
| (bgp[1] << 2)
| (bgp[0] << 0);
}
if(addr == 0xff48) { //OBP0
return (obp[0][3] << 6)
| (obp[0][2] << 4)
| (obp[0][1] << 2)
| (obp[0][0] << 0);
}
if(addr == 0xff49) { //OBP1
return (obp[1][3] << 6)
| (obp[1][2] << 4)
| (obp[1][1] << 2)
| (obp[1][0] << 0);
}
if(addr == 0xff4a) { //WY
return status.wy;
}
if(addr == 0xff4b) { //WX
return status.wx;
}
if(addr == 0xff4f) { //VBK
return status.vramBank;
}
if(addr == 0xff68) { //BGPI
return status.bgpiIncrement << 7 | status.bgpi;
}
if(addr == 0xff69) { //BGPD
return bgpd[status.bgpi];
}
if(addr == 0xff6a) { //OBPI
return status.obpiIncrement << 7 | status.obpi;
}
if(addr == 0xff6b) { //OBPD
return obpd[status.obpi];
}
return 0xff; //should never occur
}
auto PPU::writeIO(uint16 addr, uint8 data) -> void {
if(addr >= 0x8000 && addr <= 0x9fff) {
vram[vramAddress(addr)] = data;
return;
}
if(addr >= 0xfe00 && addr <= 0xfe9f) {
if(status.dmaActive && status.dmaClock >= 8) return;
oam[addr & 0xff] = data;
return;
}
if(addr == 0xff40) { //LCDC
if(status.displayEnable && !(data & 0x80)) {
status.mode = 0;
status.ly = 0;
status.lx = 0;
//restart cothread to begin new frame
auto clock = Thread::clock();
create(Enter, 4 * 1024 * 1024);
Thread::setClock(clock);
}
status.displayEnable = data & 0x80;
status.windowTilemapSelect = data & 0x40;
status.windowDisplayEnable = data & 0x20;
status.bgTiledataSelect = data & 0x10;
status.bgTilemapSelect = data & 0x08;
status.obSize = data & 0x04;
status.obEnable = data & 0x02;
status.bgEnable = data & 0x01;
return;
}
if(addr == 0xff41) { //STAT
status.interruptLYC = data & 0x40;
status.interruptOAM = data & 0x20;
status.interruptVblank = data & 0x10;
status.interruptHblank = data & 0x08;
//hardware bug: writes to STAT on DMG,SGB during vblank triggers STAT IRQ
//note: this behavior isn't entirely correct; more research is needed ...
if(!Model::GameBoyColor() && status.mode == 1) {
cpu.raise(CPU::Interrupt::Stat);
}
return;
}
if(addr == 0xff42) { //SCY
status.scy = data;
return;
}
if(addr == 0xff43) { //SCX
status.scx = data;
return;
}
if(addr == 0xff44) { //LY
status.ly = 0;
return;
}
if(addr == 0xff45) { //LYC
status.lyc = data;
return;
}
if(addr == 0xff46) { //DMA
status.dmaActive = true;
status.dmaClock = 0;
status.dmaBank = data;
return;
}
if(addr == 0xff47) { //BGP
bgp[3] = (data >> 6) & 3;
bgp[2] = (data >> 4) & 3;
bgp[1] = (data >> 2) & 3;
bgp[0] = (data >> 0) & 3;
return;
}
if(addr == 0xff48) { //OBP0
obp[0][3] = (data >> 6) & 3;
obp[0][2] = (data >> 4) & 3;
obp[0][1] = (data >> 2) & 3;
obp[0][0] = (data >> 0) & 3;
return;
}
if(addr == 0xff49) { //OBP1
obp[1][3] = (data >> 6) & 3;
obp[1][2] = (data >> 4) & 3;
obp[1][1] = (data >> 2) & 3;
obp[1][0] = (data >> 0) & 3;
return;
}
if(addr == 0xff4a) { //WY
status.wy = data;
return;
}
if(addr == 0xff4b) { //WX
status.wx = data;
return;
}
if(addr == 0xff4f) { //VBK
status.vramBank = data & 1;
return;
}
if(addr == 0xff68) { //BGPI
status.bgpiIncrement = data & 0x80;
status.bgpi = data & 0x3f;
return;
}
if(addr == 0xff69) { //BGPD
bgpd[status.bgpi] = data;
if(status.bgpiIncrement) status.bgpi++;
return;
}
if(addr == 0xff6a) { //OBPI
status.obpiIncrement = data & 0x80;
status.obpi = data & 0x3f;
}
if(addr == 0xff6b) { //OBPD
obpd[status.obpi] = data;
if(status.obpiIncrement) status.obpi++;
}
}

View File

@ -1,182 +0,0 @@
#include <gb/gb.hpp>
namespace GameBoy {
PPU ppu;
#include "io.cpp"
#include "dmg.cpp"
#include "cgb.cpp"
#include "serialization.cpp"
auto PPU::Enter() -> void {
while(true) scheduler.synchronize(), ppu.main();
}
auto PPU::main() -> void {
if(!status.displayEnable) {
for(uint n : range(160 * 144)) screen[n] = Model::GameBoy() ? 0 : 0x7fff;
Thread::step(154 * 456);
synchronize(cpu);
scheduler.exit(Scheduler::Event::Frame);
return;
}
status.lx = 0;
if(Model::SuperGameBoy()) superGameBoy->lcdScanline();
if(status.ly <= 143) {
status.mode = 2;
scanline();
step(92);
status.mode = 3;
for(auto n : range(160)) {
run();
step(1);
}
status.mode = 0;
cpu.hblank();
step(204);
} else {
status.mode = 1;
step(456);
}
status.ly++;
if(status.ly == 144) {
cpu.raise(CPU::Interrupt::Vblank);
scheduler.exit(Scheduler::Event::Frame);
}
if(status.ly == 154) {
status.ly = 0;
}
}
auto PPU::stat() -> void {
bool irq = status.irq;
status.irq = status.interruptHblank && status.mode == 0;
status.irq |= status.interruptVblank && status.mode == 1;
status.irq |= status.interruptOAM && status.mode == 2;
status.irq |= status.interruptLYC && coincidence();
if(!irq && status.irq) cpu.raise(CPU::Interrupt::Stat);
}
auto PPU::coincidence() -> bool {
uint ly = status.ly;
if(ly == 153 && status.lx >= 92) ly = 0; //LYC=0 triggers early during LY=153
return ly == status.lyc;
}
auto PPU::refresh() -> void {
if(!Model::SuperGameBoy()) Emulator::video.refresh(screen, 160 * sizeof(uint32), 160, 144);
}
auto PPU::step(uint clocks) -> void {
while(clocks--) {
stat();
if(status.dmaActive) {
uint hi = status.dmaClock++;
uint lo = hi & (cpu.status.speedDouble ? 1 : 3);
hi >>= cpu.status.speedDouble ? 1 : 2;
if(lo == 0) {
if(hi == 0) {
//warm-up
} else if(hi == 161) {
//cool-down; disable
status.dmaActive = false;
} else {
oam[hi - 1] = bus.read(status.dmaBank << 8 | hi - 1);
}
}
}
status.lx++;
Thread::step(1);
synchronize(cpu);
}
}
auto PPU::hflip(uint data) const -> uint {
return (data & 0x8080) >> 7 | (data & 0x4040) >> 5
| (data & 0x2020) >> 3 | (data & 0x1010) >> 1
| (data & 0x0808) << 1 | (data & 0x0404) << 3
| (data & 0x0202) << 5 | (data & 0x0101) << 7;
}
auto PPU::power() -> void {
create(Enter, 4 * 1024 * 1024);
if(Model::GameBoyColor()) {
scanline = {&PPU::scanlineCGB, this};
run = {&PPU::runCGB, this};
} else {
scanline = {&PPU::scanlineDMG, this};
run = {&PPU::runDMG, this};
}
for(uint n = 0x8000; n <= 0x9fff; n++) bus.mmio[n] = this; //VRAM
for(uint n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM
bus.mmio[0xff40] = this; //LCDC
bus.mmio[0xff41] = this; //STAT
bus.mmio[0xff42] = this; //SCY
bus.mmio[0xff43] = this; //SCX
bus.mmio[0xff44] = this; //LY
bus.mmio[0xff45] = this; //LYC
bus.mmio[0xff46] = this; //DMA
bus.mmio[0xff47] = this; //BGP
bus.mmio[0xff48] = this; //OBP0
bus.mmio[0xff49] = this; //OBP1
bus.mmio[0xff4a] = this; //WY
bus.mmio[0xff4b] = this; //WX
if(Model::GameBoyColor()) {
bus.mmio[0xff4f] = this; //VBK
bus.mmio[0xff68] = this; //BGPI
bus.mmio[0xff69] = this; //BGPD
bus.mmio[0xff6a] = this; //OBPI
bus.mmio[0xff6b] = this; //OBPD
}
for(auto& n : vram) n = 0x00;
for(auto& n : oam) n = 0x00;
for(auto& n : bgp) n = 0x00;
for(auto& n : obp[0]) n = 3;
for(auto& n : obp[1]) n = 3;
for(auto& n : bgpd) n = 0x0000;
for(auto& n : obpd) n = 0x0000;
status = {};
for(auto& n : screen) n = 0;
bg.color = 0;
bg.palette = 0;
bg.priority = 0;
ob.color = 0;
ob.palette = 0;
ob.priority = 0;
for(auto& s : sprite) {
s.x = 0;
s.y = 0;
s.tile = 0;
s.attr = 0;
s.data = 0;
}
sprites = 0;
background.attr = 0;
background.data = 0;
window.attr = 0;
window.data = 0;
}
}

View File

@ -1,132 +0,0 @@
struct PPU : Thread, MMIO {
static auto Enter() -> void;
auto main() -> void;
auto stat() -> void;
auto coincidence() -> bool;
auto refresh() -> void;
auto step(uint clocks) -> void;
auto hflip(uint data) const -> uint;
//io.cpp
auto vramAddress(uint16 addr) const -> uint;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
//dmg.cpp
auto readTileDMG(bool select, uint x, uint y, uint& data) -> void;
auto scanlineDMG() -> void;
auto runDMG() -> void;
auto runBackgroundDMG() -> void;
auto runWindowDMG() -> void;
auto runObjectsDMG() -> void;
//cgb.cpp
auto readTileCGB(bool select, uint x, uint y, uint& attr, uint& data) -> void;
auto scanlineCGB() -> void;
auto runCGB() -> void;
auto runBackgroundCGB() -> void;
auto runWindowCGB() -> void;
auto runObjectsCGB() -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
uint8 vram[16384]; //GB = 8192, GBC = 16384
uint8 oam[160];
uint8 bgp[4];
uint8 obp[2][4];
uint8 bgpd[64];
uint8 obpd[64];
function<auto () -> void> scanline;
function<auto () -> void> run;
struct Status {
bool irq = 0; //STAT IRQ line
uint lx = 0;
//$ff40 LCDC
bool displayEnable = 0;
bool windowTilemapSelect = 0;
bool windowDisplayEnable = 0;
bool bgTiledataSelect = 0;
bool bgTilemapSelect = 0;
bool obSize = 0;
bool obEnable = 0;
bool bgEnable = 0;
//$ff41 STAT
bool interruptLYC = 0;
bool interruptOAM = 0;
bool interruptVblank = 0;
bool interruptHblank = 0;
uint2 mode;
//$ff42 SCY
uint8 scy;
//$ff43 SCX
uint8 scx;
//$ff44 LY
uint8 ly;
//$ff45 LYC
uint8 lyc;
//$ff46 DMA
bool dmaActive = 0;
uint dmaClock = 0;
uint8 dmaBank;
//$ff4a WY
uint8 wy;
//$ff4b WX
uint8 wx;
//$ff4f VBK
bool vramBank = 0;
//$ff68 BGPI
bool bgpiIncrement = 0;
uint6 bgpi;
//$ff6a OBPI
bool obpiIncrement = 0;
uint8 obpi;
} status;
uint32 screen[160 * 144];
struct Pixel {
uint16 color;
uint8 palette;
bool priority = 0;
};
Pixel bg;
Pixel ob;
struct Sprite {
uint x = 0;
uint y = 0;
uint tile = 0;
uint attr = 0;
uint data = 0;
};
Sprite sprite[10];
uint sprites = 0;
uint px = 0;
struct Background {
uint attr = 0;
uint data = 0;
};
Background background;
Background window;
};
extern PPU ppu;

View File

@ -1,74 +0,0 @@
auto PPU::serialize(serializer& s) -> void {
Thread::serialize(s);
s.array(vram);
s.array(oam);
s.array(bgp);
s.array(obp[0]);
s.array(obp[1]);
s.array(bgpd);
s.array(obpd);
s.integer(status.irq);
s.integer(status.lx);
s.integer(status.displayEnable);
s.integer(status.windowTilemapSelect);
s.integer(status.windowDisplayEnable);
s.integer(status.bgTiledataSelect);
s.integer(status.bgTilemapSelect);
s.integer(status.obSize);
s.integer(status.obEnable);
s.integer(status.bgEnable);
s.integer(status.interruptLYC);
s.integer(status.interruptOAM);
s.integer(status.interruptVblank);
s.integer(status.interruptHblank);
s.integer(status.mode);
s.integer(status.scy);
s.integer(status.scx);
s.integer(status.ly);
s.integer(status.lyc);
s.integer(status.dmaActive);
s.integer(status.dmaClock);
s.integer(status.dmaBank);
s.integer(status.wy);
s.integer(status.wx);
s.integer(status.vramBank);
s.integer(status.bgpiIncrement);
s.integer(status.bgpi);
s.integer(status.obpiIncrement);
s.integer(status.obpi);
s.array(screen);
s.integer(bg.color);
s.integer(bg.palette);
s.integer(bg.priority);
s.integer(ob.color);
s.integer(ob.palette);
s.integer(ob.priority);
for(auto& o : sprite) {
s.integer(o.x);
s.integer(o.y);
s.integer(o.tile);
s.integer(o.attr);
}
s.integer(sprites);
s.integer(background.attr);
s.integer(background.data);
s.integer(window.attr);
s.integer(window.data);
}

View File

@ -1,59 +0,0 @@
auto System::serialize() -> serializer {
serializer s(_serializeSize);
uint signature = 0x31545342;
char version[16] = {0};
char description[512] = {0};
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
s.integer(signature);
s.array(version);
s.array(description);
serializeAll(s);
return s;
}
auto System::unserialize(serializer& s) -> bool {
uint signature;
char version[16] = {0};
char description[512];
s.integer(signature);
s.array(version);
s.array(description);
if(signature != 0x31545342) return false;
if(string{version} != Emulator::SerializerVersion) return false;
power();
serializeAll(s);
return true;
}
auto System::serialize(serializer& s) -> void {
s.integer(_clocksExecuted);
}
auto System::serializeAll(serializer& s) -> void {
cartridge.serialize(s);
system.serialize(s);
cpu.serialize(s);
ppu.serialize(s);
apu.serialize(s);
}
auto System::serializeInit() -> void {
serializer s;
uint signature = 0;
char version[16] = {0};
char description[512] = {0};
s.integer(signature);
s.array(version);
s.array(description);
serializeAll(s);
_serializeSize = s.size();
}

Some files were not shown because too many files have changed in this diff Show More