diff --git a/Core/gb.h b/Core/gb.h index 3a322c8..f29f914 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -297,7 +297,6 @@ typedef enum { #define SGB_NTSC_FREQUENCY (21477272 / 5) #define SGB_PAL_FREQUENCY (21281370 / 5) #define DIV_CYCLES (0x100) -#define INTERNAL_DIV_CYCLES (0x40000) #if !defined(MIN) #define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) @@ -532,6 +531,8 @@ struct GB_gameboy_internal_s { uint16_t serial_length; uint8_t double_speed_alignment; uint8_t serial_count; + int32_t speed_switch_halt_countdown; + uint8_t speed_switch_freeze; // Solely for realigning the PPU, should be removed when the odd modes are implemented ); /* APU */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index ec908e7..0388e4f 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -357,6 +357,9 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode) static void enter_stop_mode(GB_gameboy_t *gb) { GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); + if (!gb->ime) { // TODO: I don't trust this if, + gb->div_cycles = -4; // Emulate the CPU-side DIV-reset signal being held + } gb->stopped = true; gb->oam_ppu_blocked = !gb->oam_read_blocked; gb->vram_ppu_blocked = !gb->vram_read_blocked; @@ -369,53 +372,56 @@ static void leave_stop_mode(GB_gameboy_t *gb) gb->oam_ppu_blocked = false; gb->vram_ppu_blocked = false; gb->cgb_palettes_ppu_blocked = false; - /* The CPU takes more time to wake up then the other components */ - for (unsigned i = 0x1FFF; i--;) { - GB_advance_cycles(gb, 0x10); - } - GB_advance_cycles(gb, gb->cgb_double_speed? 0x10 : 0xC); - GB_write_memory(gb, 0xFF00 + GB_IO_DIV, 0); } static void stop(GB_gameboy_t *gb, uint8_t opcode) { - if (gb->io_registers[GB_IO_KEY1] & 0x1) { + flush_pending_cycles(gb); + bool exit_by_joyp = ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF); + bool speed_switch = (gb->io_registers[GB_IO_KEY1] & 0x1) && !exit_by_joyp; + bool immediate_exit = speed_switch || exit_by_joyp; + bool interrupt_pending = (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F); + // When entering with IF&IE, the 2nd byte of STOP is actually executed + if (!exit_by_joyp) { + enter_stop_mode(gb); + } + + if (!interrupt_pending) { + /* Todo: is PC being actually read? */ + cycle_read_inc_oam_bug(gb, gb->pc++); + } + + /* Todo: speed switching takes a fractional number of M-cycles. It make + every active component (APU, PPU) unaligned with the CPU. */ + if (speed_switch) { flush_pending_cycles(gb); - bool needs_alignment = false; - GB_advance_cycles(gb, 0x4); - /* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */ - - if (gb->double_speed_alignment & 7) { - GB_advance_cycles(gb, 0x4); - needs_alignment = true; + if (gb->io_registers[GB_IO_LCDC] & 0x80) { GB_log(gb, "ROM triggered PPU odd mode, which is currently not supported. Reverting to even-mode.\n"); + if (gb->double_speed_alignment & 7) { + gb->speed_switch_freeze = 6; + } + else { + gb->speed_switch_freeze = 4; + } } - + gb->cgb_double_speed ^= true; gb->io_registers[GB_IO_KEY1] = 0; - enter_stop_mode(gb); - leave_stop_mode(gb); - - if (!needs_alignment) { - GB_advance_cycles(gb, 0x4); - } - + gb->speed_switch_halt_countdown = 0x20008; } - else { - GB_timing_sync(gb); - if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { - /* TODO: HW Bug? When STOP is executed while a button is down, the CPU enters halt - mode instead. Fine details not confirmed yet. */ + + if (immediate_exit) { + leave_stop_mode(gb); + if (!interrupt_pending) { gb->halted = true; + gb->just_halted = true; } else { - enter_stop_mode(gb); + gb->speed_switch_halt_countdown = 0; } } - /* Todo: is PC being actually read? */ - gb->pc++; } /* Operand naming conventions for functions: @@ -1603,11 +1609,13 @@ void GB_cpu_run(GB_gameboy_t *gb) /* Wake up from HALT mode without calling interrupt code. */ if (gb->halted && !effective_ime && interrupt_queue) { gb->halted = false; + gb->speed_switch_halt_countdown = 0; } /* Call interrupt */ else if (effective_ime && interrupt_queue) { gb->halted = false; + gb->speed_switch_halt_countdown = 0; uint16_t call_addr = gb->pc; gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++); diff --git a/Core/timing.c b/Core/timing.c index 7b79b72..69b301a 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -145,7 +145,6 @@ static void increase_tima(GB_gameboy_t *gb) static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) { /* TIMA increases when a specific high-bit becomes a low-bit. */ - value &= INTERNAL_DIV_CYCLES - 1; uint16_t triggers = gb->div_counter & ~value; if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { increase_tima(gb); @@ -356,7 +355,24 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode } + if (gb->speed_switch_halt_countdown) { + gb->speed_switch_halt_countdown -= cycles; + if (gb->speed_switch_halt_countdown <= 0) { + gb->speed_switch_halt_countdown = 0; + gb->halted = false; + } + } + gb->debugger_ticks += cycles; + + if (gb->speed_switch_freeze) { + if (gb->speed_switch_freeze >= cycles) { + gb->speed_switch_freeze -= cycles; + return; + } + cycles -= gb->speed_switch_freeze; + gb->speed_switch_freeze = 0; + } if (!gb->cgb_double_speed) { cycles <<= 1; diff --git a/Tester/main.c b/Tester/main.c index b0f1b31..912563c 100755 --- a/Tester/main.c +++ b/Tester/main.c @@ -27,7 +27,7 @@ static FILE *log_file; static void replace_extension(const char *src, size_t length, char *dest, const char *ext); static bool push_start_a, start_is_not_first, a_is_bad, b_is_confirm, push_faster, push_slower, do_not_stop, push_a_twice, start_is_bad, allow_weird_sp_values, large_stack, push_right, - semi_random, limit_start, pointer_control; + semi_random, limit_start, pointer_control, unsafe_speed_switch; static unsigned int test_length = 60 * 40; GB_gameboy_t gb; @@ -60,6 +60,9 @@ static char *async_input_callback(GB_gameboy_t *gb) static void handle_buttons(GB_gameboy_t *gb) { + if (!gb->cgb_double_speed && unsafe_speed_switch) { + return; + } /* Do not press any buttons during the last two seconds, this might cause a screenshot to be taken while the LCD is off if the press makes the game load graphics. */ @@ -129,7 +132,7 @@ static void vblank(GB_gameboy_t *gb) gb->registers[GB_REGISTER_SP], gb->backtrace_size); frames = test_length - 1; } - if (gb->halted && !gb->interrupt_enable) { + if (gb->halted && !gb->interrupt_enable && gb->speed_switch_halt_countdown == 0) { GB_log(gb, "The game is deadlocked.\n"); frames = test_length - 1; } @@ -265,9 +268,7 @@ static void replace_extension(const char *src, size_t length, char *dest, const int main(int argc, char **argv) { -#define str(x) #x -#define xstr(x) str(x) - fprintf(stderr, "SameBoy Tester v" xstr(VERSION) "\n"); + fprintf(stderr, "SameBoy Tester v" GB_VERSION "\n"); if (argc == 1) { fprintf(stderr, "Usage: %s [--dmg] [--start] [--length seconds] [--sav] [--boot path to boot ROM]" @@ -406,7 +407,8 @@ int main(int argc, char **argv) strcmp((const char *)(gb.rom + 0x134), "ONI 5") == 0; b_is_confirm = strcmp((const char *)(gb.rom + 0x134), "ELITE SOCCER") == 0 || strcmp((const char *)(gb.rom + 0x134), "SOCCER") == 0 || - strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0; + strcmp((const char *)(gb.rom + 0x134), "GEX GECKO") == 0 || + strcmp((const char *)(gb.rom + 0x134), "BABE") == 0; push_faster = strcmp((const char *)(gb.rom + 0x134), "MOGURA DE PON!") == 0 || strcmp((const char *)(gb.rom + 0x134), "HUGO2 1/2") == 0 || strcmp((const char *)(gb.rom + 0x134), "HUGO") == 0; @@ -445,6 +447,11 @@ int main(int argc, char **argv) pointer_control = memcmp((const char *)(gb.rom + 0x134), "LEGO ATEAM BLPP", strlen("LEGO ATEAM BLPP")) == 0; push_faster |= pointer_control; + /* Games that perform an unsafe speed switch, don't input until in double speed */ + unsafe_speed_switch = strcmp((const char *)(gb.rom + 0x134), "GBVideo") == 0 || // lulz this is my fault + strcmp((const char *)(gb.rom + 0x134), "POKEMONGOLD 2") == 0; // Pokemon Adventure + + /* Run emulation */ running = true; gb.turbo = gb.turbo_dont_skip = gb.disable_rendering = true;