mirror of
https://github.com/libretro/bsnes-libretro.git
synced 2024-11-23 00:49:40 +00:00
v107.8
* GB: integrated SameBoy v0.12.1 by Lior Halphon * SFC: added HG51B169 (Cx4) math tables into bsnes binary
This commit is contained in:
parent
382e192647
commit
903d1e4012
@ -32,7 +32,7 @@ using namespace nall;
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "bsnes";
|
||||
static const string Version = "107.7";
|
||||
static const string Version = "107.8";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "https://byuu.org/";
|
||||
|
0
bsnes/filter/snes_ntsc/snes_ntsc.c
Normal file → Executable file
0
bsnes/filter/snes_ntsc/snes_ntsc.c
Normal file → Executable file
0
bsnes/filter/snes_ntsc/snes_ntsc.h
Normal file → Executable file
0
bsnes/filter/snes_ntsc/snes_ntsc.h
Normal file → Executable file
0
bsnes/filter/snes_ntsc/snes_ntsc_config.h
Normal file → Executable file
0
bsnes/filter/snes_ntsc/snes_ntsc_config.h
Normal file → Executable file
0
bsnes/filter/snes_ntsc/snes_ntsc_impl.h
Normal file → Executable file
0
bsnes/filter/snes_ntsc/snes_ntsc_impl.h
Normal file → Executable 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;
|
||||
}
|
||||
|
@ -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();
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
@ -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);
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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 {
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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]);
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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 = {};
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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 = {};
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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++;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342;
|
||||
char version[16] = {0};
|
||||
char description[512] = {0};
|
||||
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(description);
|
||||
|
||||
serializeAll(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
auto System::unserialize(serializer& s) -> bool {
|
||||
uint signature;
|
||||
char version[16] = {0};
|
||||
char description[512];
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(description);
|
||||
|
||||
if(signature != 0x31545342) return false;
|
||||
if(string{version} != Emulator::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
s.integer(_clocksExecuted);
|
||||
}
|
||||
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
cartridge.serialize(s);
|
||||
system.serialize(s);
|
||||
cpu.serialize(s);
|
||||
ppu.serialize(s);
|
||||
apu.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0;
|
||||
char version[16] = {0};
|
||||
char description[512] = {0};
|
||||
|
||||
s.integer(signature);
|
||||
s.array(version);
|
||||
s.array(description);
|
||||
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user