From ecbdb9e95e52407a6ba50fd141f38572f102d7d5 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 25 Nov 2023 21:24:46 +0200 Subject: [PATCH] Emulate several more pre-CGB-D channel 1 glitches --- AppleCommon/GBViewBase.h | 3 +- Core/apu.c | 200 ++++++++++++++++++++++----------------- Core/apu.h | 2 +- Core/gb.h | 1 + Core/memory.c | 2 + 5 files changed, 118 insertions(+), 90 deletions(-) diff --git a/AppleCommon/GBViewBase.h b/AppleCommon/GBViewBase.h index df8f39923..94474cb57 100644 --- a/AppleCommon/GBViewBase.h +++ b/AppleCommon/GBViewBase.h @@ -1,5 +1,4 @@ #import -#import #if TARGET_OS_IPHONE #define NSView UIView @@ -8,6 +7,8 @@ #import #endif +#import + typedef enum { GB_FRAME_BLENDING_MODE_DISABLED, GB_FRAME_BLENDING_MODE_SIMPLE, diff --git a/Core/apu.c b/Core/apu.c index e88c775e2..9bde1b5b1 100644 --- a/Core/apu.c +++ b/Core/apu.c @@ -462,6 +462,22 @@ static void tick_noise_envelope(GB_gameboy_t *gb) } } +static void sweep_calculation_done(GB_gameboy_t *gb, unsigned cycles) +{ + /* APU bug: sweep frequency is checked after adding the sweep delta twice */ + if (gb->apu.channel_1_restart_hold == 0) { + gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; + } + if (gb->io_registers[GB_IO_NR10] & 8) { + gb->apu.sweep_length_addend ^= 0x7FF; + } + if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) { + gb->apu.is_active[GB_SQUARE_1] = false; + update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown * 2 - cycles); + } + gb->apu.channel1_completed_addend = gb->apu.sweep_length_addend; +} + static void trigger_sweep_calculation(GB_gameboy_t *gb) { if ((gb->io_registers[GB_IO_NR10] & 0x70) && gb->apu.square_sweep_countdown == 7) { @@ -477,9 +493,16 @@ static void trigger_sweep_calculation(GB_gameboy_t *gb) /* Recalculation and overflow check only occurs after a delay */ gb->apu.square_sweep_calculate_countdown = gb->io_registers[GB_IO_NR10] & 0x7; - gb->apu.square_sweep_calculate_countdown_reload_timer = 2; + // TODO: this is a hack because DIV write timing is inaccurate. Will probably break on odd mode. + gb->apu.square_sweep_calculate_countdown_reload_timer = 1 + gb->apu.lf_div; + if (!gb->cgb_double_speed && gb->during_div_write) { + gb->apu.square_sweep_calculate_countdown_reload_timer = 1; + } gb->apu.unshifted_sweep = !(gb->io_registers[GB_IO_NR10] & 0x7); gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; + if (gb->apu.square_sweep_calculate_countdown == 0) { + gb->apu.square_sweep_instant_calculation_done = true; + } } } @@ -615,22 +638,6 @@ static void step_lfsr(GB_gameboy_t *gb, unsigned cycles_offset) } } -static void sweep_calculation_done(GB_gameboy_t *gb, unsigned cycles) -{ - /* APU bug: sweep frequency is checked after adding the sweep delta twice */ - if (gb->apu.channel_1_restart_hold == 0) { - gb->apu.shadow_sweep_sample_length = gb->apu.square_channels[GB_SQUARE_1].sample_length; - } - if (gb->io_registers[GB_IO_NR10] & 8) { - gb->apu.sweep_length_addend ^= 0x7FF; - } - if (gb->apu.shadow_sweep_sample_length + gb->apu.sweep_length_addend > 0x7FF && !(gb->io_registers[GB_IO_NR10] & 8)) { - gb->apu.is_active[GB_SQUARE_1] = false; - update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown * 2 - cycles); - } - gb->apu.channel1_completed_addend = gb->apu.sweep_length_addend; -} - void GB_apu_run(GB_gameboy_t *gb, bool force) { uint32_t clock_rate = GB_get_clock_rate(gb) * 2; @@ -689,34 +696,28 @@ void GB_apu_run(GB_gameboy_t *gb, bool force) if ((cycles & 1) && !gb->apu.lf_div) { sweep_cycles++; } - - if (gb->apu.square_sweep_calculate_countdown_reload_timer == 0 && !gb->cgb_double_speed) { - gb->apu.square_sweep_calculate_countdown = 0; - } - gb->apu.square_sweep_countdown_just_reloaded = false; + if (gb->apu.square_sweep_calculate_countdown_reload_timer > sweep_cycles) { gb->apu.square_sweep_calculate_countdown_reload_timer -= sweep_cycles; sweep_cycles = 0; } else { - if (gb->apu.square_sweep_calculate_countdown_reload_timer && !gb->apu.square_sweep_calculate_countdown && gb->model > GB_MODEL_CGB_C) { + if (gb->apu.square_sweep_calculate_countdown_reload_timer && !gb->apu.square_sweep_calculate_countdown && gb->apu.square_sweep_instant_calculation_done) { sweep_calculation_done(gb, cycles); } - gb->apu.square_sweep_countdown_just_reloaded = gb->apu.square_sweep_calculate_countdown_reload_timer == sweep_cycles && sweep_cycles; + gb->apu.square_sweep_instant_calculation_done = false; sweep_cycles -= gb->apu.square_sweep_calculate_countdown_reload_timer; gb->apu.square_sweep_calculate_countdown_reload_timer = 0; } - + if (gb->apu.square_sweep_calculate_countdown && - !gb->apu_output.square_sweep_disable_stepping && - (((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.unshifted_sweep) || - gb->apu.square_sweep_calculate_countdown <= 1)) { // Calculation is paused if the lower bits are 0 + (((gb->io_registers[GB_IO_NR10] & 7) || gb->apu.unshifted_sweep))) { // Calculation is paused if the lower bits are 0 if (gb->apu.square_sweep_calculate_countdown > sweep_cycles) { gb->apu.square_sweep_calculate_countdown -= sweep_cycles; } else { - sweep_calculation_done(gb, cycles); gb->apu.square_sweep_calculate_countdown = 0; + sweep_calculation_done(gb, cycles); } } @@ -1035,6 +1036,76 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb) return effective_counter; } +static noinline void nr10_write_glitch(GB_gameboy_t *gb, uint8_t value) +{ + // TODO: Check all of these in APU odd mode + if (gb->model <= GB_MODEL_CGB_C) { + if (gb->apu.square_sweep_calculate_countdown_reload_timer == 1 && !gb->apu.lf_div) { + if (gb->cgb_double_speed) { + /* This is some instance-specific data corruption. It might also be affect by revision. + At least for my CGB-0 (haven't tested any other CGB-0s), the '3' case is non-deterministic. */ + static const uint8_t corruption[8] = {7, 7, 5, 7, 3, 3, 5, 7}; // Two of my CGB-Cs, CGB-A + // static const uint8_t corruption[8] = {7, 7, 1, 3, 3, 3, 5, 7}; // My other CGB-C, Coffee Bat's CGB-C + // static const uint8_t corruption[8] = {7, 1, 1, 3, 3, 5, 5, 7}; // My CGB-B + // static const uint8_t corruption[8] = {7, 7, 1, *, 3, 3, 5, 7}; // My CGB-0 + + // static const uint8_t corruption[8] = {7, 5, 1, 3, 3, 1, 5, 7}; // PinoBatch's CGB-B + // static const uint8_t corruption[8] = {7, 5, 1, 3, 3, *, 5, 7}; // GenericHeroGuy CGB-C + + + // TODO: How does this affect actual frequency calculation? + + gb->apu.square_sweep_calculate_countdown = corruption[gb->apu.square_sweep_calculate_countdown & 7]; + /* TODO: the value of 1 needs special handling, but it doesn't occur with the instance I'm emulating here */ + } + } + else if (gb->apu.square_sweep_calculate_countdown_reload_timer > 1) { + if (gb->cgb_double_speed) { + // TODO: How does this affect actual frequency calculation? + gb->apu.square_sweep_calculate_countdown = value & 7; + } + } + else if (gb->apu.square_sweep_calculate_countdown) { + // No clue why 1 is a special case here + bool should_zombie_step = false; + if (!(gb->io_registers[GB_IO_NR10] & 7)) { + should_zombie_step = gb->apu.lf_div ^ gb->cgb_double_speed; + } + else if (gb->cgb_double_speed && gb->apu.square_sweep_calculate_countdown == 1) { + should_zombie_step = true; + } + + if (should_zombie_step) { + gb->apu.square_sweep_calculate_countdown--; + if (gb->apu.square_sweep_calculate_countdown <= 1) { + gb->apu.square_sweep_calculate_countdown = 0; + sweep_calculation_done(gb, 0); + } + } + } + } + else { + if (gb->apu.square_sweep_calculate_countdown_reload_timer == 2) { + // Countdown just reloaded, re-reload it + gb->apu.square_sweep_calculate_countdown = value & 0x7; + if (!gb->apu.square_sweep_calculate_countdown) { + gb->apu.square_sweep_calculate_countdown_reload_timer = 0; + } + else { + // TODO: How does this affect actual frequency calculation? + } + } + if ((value & 7) && !(gb->io_registers[GB_IO_NR10] & 7) && !gb->apu.lf_div && gb->apu.square_sweep_calculate_countdown > 1) { + // TODO: Another odd glitch? Ditto + gb->apu.square_sweep_calculate_countdown--; + if (!gb->apu.square_sweep_calculate_countdown) { + sweep_calculation_done(gb, 0); + } + } + } + +} + void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) { GB_apu_run(gb, true); @@ -1102,62 +1173,14 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) /* Square channels */ case GB_IO_NR10: { - bool old_negate = gb->io_registers[GB_IO_NR10] & 8; - // TODO: Check all of these in APU odd mode - if (gb->apu.square_sweep_calculate_countdown_reload_timer || gb->apu.square_sweep_countdown_just_reloaded) { - GB_log(gb, "%04x: Glitch NR10 write at %d.%d (%d, %d)\n", gb->de, gb->apu.square_sweep_calculate_countdown, gb->apu.lf_div, gb->apu.square_sweep_calculate_countdown_reload_timer, gb->apu.square_sweep_countdown_just_reloaded); + if (unlikely(gb->apu.square_sweep_calculate_countdown || gb->apu.square_sweep_calculate_countdown_reload_timer)) { + nr10_write_glitch(gb, value); } + bool old_negate = gb->io_registers[GB_IO_NR10] & 8; + gb->io_registers[GB_IO_NR10] = value; if (gb->model <= GB_MODEL_CGB_C) { - if (gb->apu.square_sweep_calculate_countdown_reload_timer) { - if (gb->cgb_double_speed) { - // TODO: How does this affect actual frequency calculation? - gb->apu.square_sweep_calculate_countdown = value & 7; - } - } - else if (gb->apu.square_sweep_countdown_just_reloaded) { - GB_log(gb, "%04x: Glitch NR10 write at %d.%d (%d, %d)\n", gb->de, gb->apu.square_sweep_calculate_countdown, gb->apu.lf_div, gb->apu.square_sweep_calculate_countdown_reload_timer, gb->apu.square_sweep_countdown_just_reloaded); - /* This is some instance-specific data corruption. It might also be affect by revision. - At least for my CGB-0 (haven't tested any other CGB-0s), the '3' case is non-deterministic. */ - static const uint8_t corruption[8] = {7, 7, 5, 7, 3, 3, 5, 7}; // Two of my CGB-Cs, CGB-A - // static const uint8_t corruption[8] = {7, 7, 1, 3, 3, 3, 5, 7}; // My other CGB-C - // static const uint8_t corruption[8] = {7, 1, 1, 3, 3, 5, 5, 7}; // My CGB-B - // static const uint8_t corruption[8] = {7, 7, 1, *, 3, 3, 5, 7}; // My CGB-0 - - // TODO: How does this affect actual frequency calculation? - - gb->apu.square_sweep_calculate_countdown = corruption[gb->apu.square_sweep_calculate_countdown & 7]; - } - else { - if (!(gb->io_registers[GB_IO_NR10] & 7) && - (gb->apu.lf_div ^ gb->cgb_double_speed) && - gb->apu.square_sweep_calculate_countdown) { - gb->apu.square_sweep_calculate_countdown--; - if (!gb->apu.square_sweep_calculate_countdown) { - sweep_calculation_done(gb, 0); - } - } - } + old_negate = true; } - else { - if (gb->apu.square_sweep_calculate_countdown_reload_timer == 2) { - // Countdown just reloaded, re-reload it - gb->apu.square_sweep_calculate_countdown = value & 0x7; - if (!gb->apu.square_sweep_calculate_countdown) { - gb->apu.square_sweep_calculate_countdown_reload_timer = 0; - } - else { - // TODO: How does this affect actual frequency calculation? - } - } - if ((value & 7) && !(gb->io_registers[GB_IO_NR10] & 7) && !gb->apu.lf_div && gb->apu.square_sweep_calculate_countdown > 1) { - // TODO: Another odd glitch? Ditto - gb->apu.square_sweep_calculate_countdown--; - if (!gb->apu.square_sweep_calculate_countdown) { - sweep_calculation_done(gb, 0); - } - } - } - gb->io_registers[GB_IO_NR10] = value; if (gb->apu.shadow_sweep_sample_length + gb->apu.channel1_completed_addend + old_negate > 0x7FF && !(value & 8)) { gb->apu.is_active[GB_SQUARE_1] = false; @@ -1285,12 +1308,18 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) } if (index == GB_SQUARE_1) { + gb->apu.square_sweep_instant_calculation_done = false; gb->apu.shadow_sweep_sample_length = 0; gb->apu.channel1_completed_addend = 0; if (gb->io_registers[GB_IO_NR10] & 7) { /* APU bug: if shift is nonzero, overflow check also occurs on trigger */ gb->apu.square_sweep_calculate_countdown = gb->io_registers[GB_IO_NR10] & 0x7; - gb->apu.square_sweep_calculate_countdown_reload_timer = 2; + if ((gb->apu.lf_div ^ !gb->cgb_double_speed) && gb->model <= GB_MODEL_CGB_C) { + gb->apu.square_sweep_calculate_countdown_reload_timer = 3; + } + else { + gb->apu.square_sweep_calculate_countdown_reload_timer = 2; + } gb->apu.unshifted_sweep = false; if (!was_active) { gb->apu.square_sweep_calculate_countdown_reload_timer++; @@ -1302,11 +1331,6 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) gb->apu.sweep_length_addend = 0; } gb->apu.channel_1_restart_hold = 2 - gb->apu.lf_div + (GB_is_cgb(gb) && gb->model != GB_MODEL_CGB_D) * 2; - /* - if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) { - // TODO: This if makes channel_1_sweep_restart_2 fail on CGB-C mode - gb->apu.channel_1_restart_hold += 2; - }*/ gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7) ^ 7; } } diff --git a/Core/apu.h b/Core/apu.h index 24b9878de..c8700c806 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -75,7 +75,7 @@ typedef struct uint16_t sweep_length_addend; uint16_t shadow_sweep_sample_length; bool unshifted_sweep; - bool square_sweep_countdown_just_reloaded; + bool square_sweep_instant_calculation_done; uint8_t channel_1_restart_hold; uint16_t channel1_completed_addend; diff --git a/Core/gb.h b/Core/gb.h index f887fd0f8..79dcdd1f3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -824,6 +824,7 @@ struct GB_gameboy_internal_s { bool hdma_in_progress; bool returned_open_bus; uint16_t addr_for_hdma_conflict; + bool during_div_write; /* Thread safety (debug only) */ void *running_thread_id; diff --git a/Core/memory.c b/Core/memory.c index ad0875dd7..0b73700eb 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1529,7 +1529,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; case GB_IO_DIV: + gb->during_div_write = true; GB_set_internal_div_counter(gb, 0); + gb->during_div_write = false; /* Reset the div state machine */ gb->div_state = 0; gb->div_cycles = 0;