mirror of
https://github.com/libretro/bsnes-libretro.git
synced 2026-11-07 10:15:59 -10: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:
1 parent
382e192647
commit
903d1e4012
743 files changed
+1460
-5327
No files matched your search
@@ -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/";
|
||||
|
||||
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
@@ -506,6 +506,11 @@ void GB_apu_run(GB_gameboy_t *gb)
|
||||
void GB_apu_init(GB_gameboy_t *gb)
|
||||
{
|
||||
memset(&gb->apu, 0, sizeof(gb->apu));
|
||||
/* Restore the wave form */
|
||||
for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) {
|
||||
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4;
|
||||
gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF;
|
||||
}
|
||||
gb->apu.lf_div = 1;
|
||||
/* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on,
|
||||
the first DIV/APU event is skipped. */
|
||||
@@ -556,14 +561,14 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
|
||||
|
||||
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
||||
{
|
||||
if (!gb->apu.global_enable && reg != GB_IO_NR52 && (GB_is_cgb(gb) ||
|
||||
(
|
||||
reg != GB_IO_NR11 &&
|
||||
reg != GB_IO_NR21 &&
|
||||
reg != GB_IO_NR31 &&
|
||||
reg != GB_IO_NR41
|
||||
)
|
||||
)) {
|
||||
if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) ||
|
||||
(
|
||||
reg != GB_IO_NR11 &&
|
||||
reg != GB_IO_NR21 &&
|
||||
reg != GB_IO_NR31 &&
|
||||
reg != GB_IO_NR41
|
||||
)
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2172,7 +2172,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
|
||||
unsigned bank, address;
|
||||
char symbol[length];
|
||||
|
||||
if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) {
|
||||
if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) {
|
||||
bank &= 0x1FF;
|
||||
if (!gb->bank_symbols[bank]) {
|
||||
gb->bank_symbols[bank] = GB_map_alloc();
|
||||
File renamed without changes.
@@ -123,23 +123,22 @@ static void display_vblank(GB_gameboy_t *gb)
|
||||
{
|
||||
gb->vblank_just_occured = true;
|
||||
|
||||
/* TODO: Slow in trubo mode! */
|
||||
if (GB_is_sgb(gb)) {
|
||||
/* TODO: Slow in turbo mode! */
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
GB_sgb_render(gb);
|
||||
}
|
||||
|
||||
|
||||
if (gb->turbo) {
|
||||
if (GB_timing_sync_turbo(gb)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
|
||||
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
|
||||
if (gb->sgb) {
|
||||
uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? 0x3 : 0x0;
|
||||
for (unsigned i = 0; i < WIDTH * LINES; i++) {
|
||||
gb->sgb->screen_buffer[i] = color;
|
||||
gb->sgb->screen_buffer[i] = 0x0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -376,6 +375,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
|
||||
bg_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t icd_pixel = 0;
|
||||
|
||||
{
|
||||
uint8_t pixel = bg_enabled? fifo_item->pixel : 0;
|
||||
@@ -387,7 +388,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
|
||||
}
|
||||
if (gb->sgb) {
|
||||
if (gb->current_lcd_line < LINES) {
|
||||
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel;
|
||||
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
|
||||
}
|
||||
}
|
||||
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
|
||||
if (gb->icd_pixel_callback) {
|
||||
icd_pixel = pixel;
|
||||
//gb->icd_pixel_callback(gb, pixel);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -403,13 +410,26 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
|
||||
}
|
||||
if (gb->sgb) {
|
||||
if (gb->current_lcd_line < LINES) {
|
||||
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = pixel;
|
||||
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
|
||||
}
|
||||
}
|
||||
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
|
||||
if (gb->icd_pixel_callback) {
|
||||
icd_pixel = pixel;
|
||||
//gb->icd_pixel_callback(gb, pixel);
|
||||
}
|
||||
}
|
||||
else {
|
||||
gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
|
||||
}
|
||||
}
|
||||
|
||||
/* byuu: gotta do this later so it's only done once ... you'll probably wanna refactor this somehow :/ */
|
||||
if (gb->model & GB_MODEL_NO_SFC_BIT) {
|
||||
if (gb->icd_pixel_callback) {
|
||||
gb->icd_pixel_callback(gb, icd_pixel);
|
||||
}
|
||||
}
|
||||
|
||||
gb->position_in_line++;
|
||||
}
|
||||
@@ -890,6 +910,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
}
|
||||
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
|
||||
gb->mode_for_interrupt = 2;
|
||||
|
||||
/* TODO: Can this timing even be verified? */
|
||||
if (gb->icd_hreset_callback) {
|
||||
gb->icd_hreset_callback(gb);
|
||||
}
|
||||
}
|
||||
|
||||
/* Lines 144 - 152 */
|
||||
@@ -961,7 +986,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||
gb->wy_diff = 0;
|
||||
gb->window_disabled_while_active = false;
|
||||
gb->current_line = 0;
|
||||
gb->current_lcd_line = -1; // TODO: not the correct timing
|
||||
// TODO: not the correct timing
|
||||
gb->current_lcd_line = -1;
|
||||
if (gb->icd_vreset_callback) {
|
||||
gb->icd_vreset_callback(gb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -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
|
||||
File renamed without changes.
@@ -3,6 +3,8 @@
|
||||
|
||||
void GB_update_joyp(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->model & GB_MODEL_SGB_NO_SFC) return;
|
||||
|
||||
uint8_t key_selection = 0;
|
||||
uint8_t previous_state = 0;
|
||||
|
||||
@@ -53,14 +55,27 @@ void GB_update_joyp(GB_gameboy_t *gb)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */
|
||||
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
|
||||
/* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */
|
||||
/* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */
|
||||
gb->io_registers[GB_IO_IF] |= 0x10;
|
||||
}
|
||||
|
||||
gb->io_registers[GB_IO_JOYP] |= 0xC0;
|
||||
}
|
||||
|
||||
void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value)
|
||||
{
|
||||
uint8_t previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
|
||||
gb->io_registers[GB_IO_JOYP] &= 0xF0;
|
||||
gb->io_registers[GB_IO_JOYP] |= value & 0xF;
|
||||
|
||||
if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) {
|
||||
gb->io_registers[GB_IO_IF] |= 0x10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed)
|
||||
{
|
||||
assert(index >= 0 && index < GB_KEY_MAX);
|
||||
@@ -17,6 +17,7 @@ typedef enum {
|
||||
|
||||
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed);
|
||||
void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed);
|
||||
void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value);
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
void GB_update_joyp(GB_gameboy_t *gb);
|
||||
File renamed without changes.
File renamed without changes.
@@ -273,7 +273,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
||||
case GB_MODEL_DMG_B:
|
||||
case GB_MODEL_SGB_NTSC:
|
||||
case GB_MODEL_SGB_PAL:
|
||||
case GB_MODEL_SGB_NO_SFC:
|
||||
case GB_MODEL_SGB2:
|
||||
case GB_MODEL_SGB2_NO_SFC:
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -584,7 +586,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
case GB_MODEL_DMG_B:
|
||||
case GB_MODEL_SGB_NTSC:
|
||||
case GB_MODEL_SGB_PAL:
|
||||
case GB_MODEL_SGB_NO_SFC:
|
||||
case GB_MODEL_SGB2:
|
||||
case GB_MODEL_SGB2_NO_SFC:
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB:
|
||||
break;
|
||||
@@ -663,7 +667,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
/* These are the states when LY changes, let the display routine call GB_STAT_update for use
|
||||
so it correctly handles T-cycle accurate LYC writes */
|
||||
if (!GB_is_cgb(gb) || (
|
||||
gb->display_state != 6 &&
|
||||
gb->display_state != 35 &&
|
||||
gb->display_state != 26 &&
|
||||
gb->display_state != 15 &&
|
||||
gb->display_state != 16)) {
|
||||
@@ -737,8 +741,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
return;
|
||||
|
||||
case GB_IO_JOYP:
|
||||
GB_sgb_write(gb, value);
|
||||
gb->io_registers[GB_IO_JOYP] = value & 0xF0;
|
||||
GB_sgb_write(gb, value);
|
||||
GB_update_joyp(gb);
|
||||
return;
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -36,7 +36,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
|
||||
if (!DUMP_SECTION(gb, f, rtc )) goto error;
|
||||
if (!DUMP_SECTION(gb, f, video )) goto error;
|
||||
|
||||
if (GB_is_sgb(gb)) {
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
|
||||
+ GB_SECTION_SIZE(apu ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(rtc ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(video ) + sizeof(uint32_t)
|
||||
+ (GB_is_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
|
||||
+ (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
|
||||
+ gb->mbc_ram_size
|
||||
+ gb->ram_size
|
||||
+ gb->vram_size;
|
||||
@@ -105,7 +105,7 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
|
||||
DUMP_SECTION(gb, buffer, rtc );
|
||||
DUMP_SECTION(gb, buffer, video );
|
||||
|
||||
if (GB_is_sgb(gb)) {
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb));
|
||||
}
|
||||
|
||||
@@ -161,8 +161,8 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GB_is_sgb(gb) != GB_is_sgb(save)) {
|
||||
GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_sgb(save)? "" : "not ");
|
||||
if (GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) {
|
||||
GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_hle_sgb(save)? "" : "not ");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (GB_is_sgb(gb)) {
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (GB_is_sgb(gb)) {
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1;
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -295,7 +295,14 @@ static void command_ready(GB_gameboy_t *gb)
|
||||
|
||||
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
|
||||
{
|
||||
if (gb->joyp_write_callback) {
|
||||
gb->joyp_write_callback(gb, value);
|
||||
}
|
||||
if (!GB_is_sgb(gb)) return;
|
||||
if (!GB_is_hle_sgb(gb)) {
|
||||
/* Notify via callback */
|
||||
return;
|
||||
}
|
||||
if (gb->sgb->disable_commands) return;
|
||||
if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return;
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+20
-34
@@ -1,38 +1,24 @@
|
||||
processors += sm83
|
||||
|
||||
objects += gb-interface gb-system
|
||||
objects += gb-memory gb-cartridge
|
||||
objects += gb-cpu gb-ppu gb-apu
|
||||
|
||||
obj/gb-interface.o: gb/interface/interface.cpp
|
||||
obj/gb-system.o: gb/system/system.cpp
|
||||
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp
|
||||
obj/gb-memory.o: gb/memory/memory.cpp
|
||||
obj/gb-cpu.o: gb/cpu/cpu.cpp
|
||||
obj/gb-ppu.o: gb/ppu/ppu.cpp
|
||||
obj/gb-apu.o: gb/apu/apu.cpp
|
||||
|
||||
flags += -DGB_INTERNAL -Wno-multichar -Dtypeof=__typeof__
|
||||
options += -I../sameboy
|
||||
|
||||
objects += sb-apu sb-camera sb-debugger sb-display sb-gb sb-joypad sb-mbc
|
||||
objects += sb-memory sb-printer sb-random sb-rewind sb-save_state sb-sgb
|
||||
objects += sb-sm83_cpu sb-sm83_disassembler sb-symbol_hash sb-timing
|
||||
objects += gb-apu gb-camera gb-debugger gb-display gb-gb gb-joypad gb-mbc
|
||||
objects += gb-memory gb-printer gb-random gb-rewind gb-save_state gb-sgb
|
||||
objects += gb-sm83_cpu gb-sm83_disassembler gb-symbol_hash gb-timing
|
||||
|
||||
obj/sb-apu.o: ../sameboy/Core/apu.c
|
||||
obj/sb-camera.o: ../sameboy/Core/camera.c
|
||||
obj/sb-debugger.o: ../sameboy/Core/debugger.c
|
||||
obj/sb-display.o: ../sameboy/Core/display.c
|
||||
obj/sb-gb.o: ../sameboy/Core/gb.c
|
||||
obj/sb-joypad.o: ../sameboy/Core/joypad.c
|
||||
obj/sb-mbc.o: ../sameboy/Core/mbc.c
|
||||
obj/sb-memory.o: ../sameboy/Core/memory.c
|
||||
obj/sb-printer.o: ../sameboy/Core/printer.c
|
||||
obj/sb-random.o: ../sameboy/Core/random.c
|
||||
obj/sb-rewind.o: ../sameboy/Core/rewind.c
|
||||
obj/sb-save_state.o: ../sameboy/Core/save_state.c
|
||||
obj/sb-sgb.o: ../sameboy/Core/sgb.c
|
||||
obj/sb-sm83_cpu.o: ../sameboy/Core/sm83_cpu.c
|
||||
obj/sb-sm83_disassembler.o: ../sameboy/Core/sm83_disassembler.c
|
||||
obj/sb-symbol_hash.o: ../sameboy/Core/symbol_hash.c
|
||||
obj/sb-timing.o: ../sameboy/Core/timing.c
|
||||
obj/gb-apu.o: gb/Core/apu.c
|
||||
obj/gb-camera.o: gb/Core/camera.c
|
||||
obj/gb-debugger.o: gb/Core/debugger.c
|
||||
obj/gb-display.o: gb/Core/display.c
|
||||
obj/gb-gb.o: gb/Core/gb.c
|
||||
obj/gb-joypad.o: gb/Core/joypad.c
|
||||
obj/gb-mbc.o: gb/Core/mbc.c
|
||||
obj/gb-memory.o: gb/Core/memory.c
|
||||
obj/gb-printer.o: gb/Core/printer.c
|
||||
obj/gb-random.o: gb/Core/random.c
|
||||
obj/gb-rewind.o: gb/Core/rewind.c
|
||||
obj/gb-save_state.o: gb/Core/save_state.c
|
||||
obj/gb-sgb.o: gb/Core/sgb.c
|
||||
obj/gb-sm83_cpu.o: gb/Core/sm83_cpu.c
|
||||
obj/gb-sm83_disassembler.o: gb/Core/sm83_disassembler.c
|
||||
obj/gb-symbol_hash.o: gb/Core/symbol_hash.c
|
||||
obj/gb-timing.o: gb/Core/timing.c
|
||||
File renamed without changes.
@@ -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();
|
||||
}
|
||||
Loaded 100 of 743 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user