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

No files matched your search

+1 -1
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/";
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
+13 -8
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;
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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();
File renamed without changes.
@@ -123,23 +123,22 @@ 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);
}
if (gb->turbo) {
if (GB_timing_sync_turbo(gb)) {
return;
}
}
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 {
@@ -376,6 +375,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
bg_enabled = false;
}
}
uint8_t icd_pixel = 0;
{
uint8_t pixel = bg_enabled? fifo_item->pixel : 0;
@@ -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,13 +410,26 @@ 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 {
gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
}
}
/* 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);
}
}
}
File renamed without changes.
+60 -20
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;
}
+26 -7
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);
@@ -688,7 +701,13 @@ bool GB_serial_get_data_bit(GB_gameboy_t *gb);
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
File renamed without changes.
@@ -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);
@@ -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);
File renamed without changes.
File renamed without changes.
@@ -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;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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;
}
File renamed without changes.
@@ -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;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+20 -34
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
File renamed without changes.
-106
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);
}
}
-171
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;
-140
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);
}
-142
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);
}
-12
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);
}
-190
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);
}
-137
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);
}
-156
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);
}
-206
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());
}
}
-68
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;
-54
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);
}
-17
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;
-48
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);
}
-16
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;
-24
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 {
}
-6
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;
-67
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);
}
-17
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;
-43
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);
}
-13
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;
-61
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);
}
-15
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;
-113
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);
}
-33
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;
-54
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);
}
-16
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;
-74
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]);
}
-16
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;
-241
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);
}
-93
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 = {};
}
-91
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;
-32
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);
}
-65
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);
}
-18
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;
-9
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);
}
-236
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);
}
-33
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;
-129
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 = {};
}
}
-112
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;
-283
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;
}
}
-41
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);
}
-55
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);
}
-84
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);
}
}
}
-46
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>
-23
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);
}
-16
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);
}
-123
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;
}
}
-79
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;
}
-56
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;
}
}
-32
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;
-158
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);
}
}
-128
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);
}
}
-240
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++;
}
}
-182
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;
}
}
-132
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;
-74
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);
}
-59
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();
}
Loaded 100 of 743 files, more files were not shown because too many files have changed in this diff. Show more