Better (But imperfect) emulation of the wave RAM address bug glitch

This commit is contained in:
Lior Halphon 2021-10-19 01:53:24 +03:00
parent de16ab5d08
commit 94776fcf8c
4 changed files with 58 additions and 24 deletions

View File

@ -529,6 +529,16 @@ void GB_apu_div_event(GB_gameboy_t *gb)
if (gb->apu.wave_channel.length_enabled) { if (gb->apu.wave_channel.length_enabled) {
if (gb->apu.wave_channel.pulse_length) { if (gb->apu.wave_channel.pulse_length) {
if (!--gb->apu.wave_channel.pulse_length) { if (!--gb->apu.wave_channel.pulse_length) {
if (gb->apu.is_active[GB_WAVE] && gb->model == GB_MODEL_AGB) {
if (gb->apu.wave_channel.sample_countdown == 0) {
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (((gb->apu.wave_channel.current_sample_index + 1) & 0xF) >> 1)];
}
else if (gb->apu.wave_channel.sample_countdown == 9) {
// TODO: wtf?
gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START];
}
}
gb->apu.is_active[GB_WAVE] = false; gb->apu.is_active[GB_WAVE] = false;
update_sample(gb, GB_WAVE, 0, 0); update_sample(gb, GB_WAVE, 0, 0);
} }
@ -596,6 +606,12 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.apu_cycles = 0; gb->apu.apu_cycles = 0;
if (!cycles) return; if (!cycles) return;
if (unlikely(gb->apu.channel_3_delayed_bugged_read)) {
gb->apu.channel_3_delayed_bugged_read = false;
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)];
}
bool start_ch4 = false; bool start_ch4 = false;
if (likely(!gb->stopped || GB_is_cgb(gb))) { if (likely(!gb->stopped || GB_is_cgb(gb))) {
if (gb->apu.channel_4_dmg_delayed_start) { if (gb->apu.channel_4_dmg_delayed_start) {
@ -688,6 +704,23 @@ void GB_apu_run(GB_gameboy_t *gb)
gb->apu.wave_channel.wave_form_just_read = false; gb->apu.wave_channel.wave_form_just_read = false;
} }
} }
else if (gb->apu.wave_channel.enable && gb->apu.channel_3_pulsed && gb->model < GB_MODEL_AGB) {
uint8_t cycles_left = cycles;
while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) {
cycles_left -= gb->apu.wave_channel.sample_countdown + 1;
gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF;
if (cycles_left) {
gb->apu.wave_channel.current_sample_byte =
gb->io_registers[GB_IO_WAV_START + (gb->address_bus & 0xF)];
}
else {
gb->apu.channel_3_delayed_bugged_read = true;
}
}
if (cycles_left) {
gb->apu.wave_channel.sample_countdown -= cycles_left;
}
}
// The noise channel can step even if inactive on the DMG // The noise channel can step even if inactive on the DMG
if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) { if (gb->apu.is_active[GB_NOISE] || !GB_is_cgb(gb)) {
@ -1156,6 +1189,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR30: case GB_IO_NR30:
gb->apu.wave_channel.enable = value & 0x80; gb->apu.wave_channel.enable = value & 0x80;
if (!gb->apu.wave_channel.enable) { if (!gb->apu.wave_channel.enable) {
gb->apu.channel_3_pulsed = false;
if (gb->apu.is_active[GB_WAVE]) { if (gb->apu.is_active[GB_WAVE]) {
// Todo: I assume this happens on pre-CGB models; test this with an audible test // Todo: I assume this happens on pre-CGB models; test this with an audible test
if (gb->apu.wave_channel.sample_countdown == 0 && gb->model < GB_MODEL_AGB) { if (gb->apu.wave_channel.sample_countdown == 0 && gb->model < GB_MODEL_AGB) {
@ -1186,6 +1220,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
gb->apu.wave_channel.sample_length &= 0xFF; gb->apu.wave_channel.sample_length &= 0xFF;
gb->apu.wave_channel.sample_length |= (value & 7) << 8; gb->apu.wave_channel.sample_length |= (value & 7) << 8;
if (value & 0x80) { if (value & 0x80) {
gb->apu.channel_3_pulsed = true;
/* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
reads from it. */ reads from it. */
if (!GB_is_cgb(gb) && if (!GB_is_cgb(gb) &&

View File

@ -122,6 +122,7 @@ typedef struct
} noise_channel; } noise_channel;
/* Todo: merge these into their structs when breaking save state compatibility */
#define GB_SKIP_DIV_EVENT_INACTIVE 0 #define GB_SKIP_DIV_EVENT_INACTIVE 0
#define GB_SKIP_DIV_EVENT_SKIPPED 1 #define GB_SKIP_DIV_EVENT_SKIPPED 1
#define GB_SKIP_DIV_EVENT_SKIP 2 #define GB_SKIP_DIV_EVENT_SKIP 2
@ -136,6 +137,8 @@ typedef struct
GB_envelope_clock_t square_envelope_clock[2]; GB_envelope_clock_t square_envelope_clock[2];
GB_envelope_clock_t noise_envelope_clock; GB_envelope_clock_t noise_envelope_clock;
bool channel_3_pulsed;
bool channel_3_delayed_bugged_read;
} GB_apu_t; } GB_apu_t;
typedef enum { typedef enum {

View File

@ -434,6 +434,7 @@ struct GB_gameboy_internal_s {
int32_t ir_sensor; int32_t ir_sensor;
bool effective_ir_input; bool effective_ir_input;
uint16_t address_bus;
); );
/* DMA and HDMA */ /* DMA and HDMA */

View File

@ -83,6 +83,7 @@ static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr)
if (gb->pending_cycles) { if (gb->pending_cycles) {
GB_advance_cycles(gb, gb->pending_cycles); GB_advance_cycles(gb, gb->pending_cycles);
} }
gb->address_bus = addr;
uint8_t ret = GB_read_memory(gb, addr); uint8_t ret = GB_read_memory(gb, addr);
gb->pending_cycles = 4; gb->pending_cycles = 4;
return ret; return ret;
@ -93,10 +94,12 @@ static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr)
is both read be the CPU, modified by the ISR, and modified by an actual interrupt. is both read be the CPU, modified by the ISR, and modified by an actual interrupt.
If this timing proves incorrect, the ISR emulation must be updated so IF reads are If this timing proves incorrect, the ISR emulation must be updated so IF reads are
timed correctly. */ timed correctly. */
/* TODO: Does this affect the address bus? Verify. */
static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value) static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value)
{ {
assert(gb->pending_cycles); assert(gb->pending_cycles);
GB_advance_cycles(gb, gb->pending_cycles); GB_advance_cycles(gb, gb->pending_cycles);
gb->address_bus = 0xFF00 + GB_IO_IF;
uint8_t old = (gb->io_registers[GB_IO_IF]) & 0x1F; uint8_t old = (gb->io_registers[GB_IO_IF]) & 0x1F;
GB_write_memory(gb, 0xFF00 + GB_IO_IF, value); GB_write_memory(gb, 0xFF00 + GB_IO_IF, value);
gb->pending_cycles = 4; gb->pending_cycles = 4;
@ -125,19 +128,19 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, gb->pending_cycles); GB_advance_cycles(gb, gb->pending_cycles);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 4; gb->pending_cycles = 4;
return; break;
case GB_CONFLICT_READ_NEW: case GB_CONFLICT_READ_NEW:
GB_advance_cycles(gb, gb->pending_cycles - 1); GB_advance_cycles(gb, gb->pending_cycles - 1);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 5; gb->pending_cycles = 5;
return; break;
case GB_CONFLICT_WRITE_CPU: case GB_CONFLICT_WRITE_CPU:
GB_advance_cycles(gb, gb->pending_cycles + 1); GB_advance_cycles(gb, gb->pending_cycles + 1);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 3; gb->pending_cycles = 3;
return; break;
/* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */ /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */
case GB_CONFLICT_STAT_DMG: case GB_CONFLICT_STAT_DMG:
@ -155,7 +158,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, 1); GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 3; gb->pending_cycles = 3;
return; break;
case GB_CONFLICT_STAT_CGB: { case GB_CONFLICT_STAT_CGB: {
/* Todo: Verify this with SCX adjustments */ /* Todo: Verify this with SCX adjustments */
@ -166,7 +169,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, 1); GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 3; gb->pending_cycles = 3;
return; break;
} }
/* There is some "time travel" going on with these two values, as it appears /* There is some "time travel" going on with these two values, as it appears
@ -181,14 +184,14 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, 1); GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 5; gb->pending_cycles = 5;
return; break;
} }
case GB_CONFLICT_PALETTE_CGB: { case GB_CONFLICT_PALETTE_CGB: {
GB_advance_cycles(gb, gb->pending_cycles - 2); GB_advance_cycles(gb, gb->pending_cycles - 2);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 6; gb->pending_cycles = 6;
return; break;
} }
case GB_CONFLICT_DMG_LCDC: { case GB_CONFLICT_DMG_LCDC: {
@ -212,7 +215,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, 1); GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 5; gb->pending_cycles = 5;
return; break;
} }
case GB_CONFLICT_SGB_LCDC: { case GB_CONFLICT_SGB_LCDC: {
@ -226,7 +229,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, 1); GB_advance_cycles(gb, 1);
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 5; gb->pending_cycles = 5;
return; break;
} }
case GB_CONFLICT_WX: case GB_CONFLICT_WX:
@ -236,7 +239,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_advance_cycles(gb, 1); GB_advance_cycles(gb, 1);
gb->wx_just_changed = false; gb->wx_just_changed = false;
gb->pending_cycles = 3; gb->pending_cycles = 3;
return; break;
case GB_CONFLICT_CGB_LCDC: case GB_CONFLICT_CGB_LCDC:
if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) { if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) {
@ -265,7 +268,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 4; gb->pending_cycles = 4;
} }
return; break;
case GB_CONFLICT_NR10: case GB_CONFLICT_NR10:
/* Hack: Due to the coupling between DIV and the APU, GB_apu_run only runs at M-cycle /* Hack: Due to the coupling between DIV and the APU, GB_apu_run only runs at M-cycle
@ -285,9 +288,9 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
} }
GB_write_memory(gb, addr, value); GB_write_memory(gb, addr, value);
gb->pending_cycles = 4; gb->pending_cycles = 4;
return; break;
} }
gb->address_bus = addr;
} }
static void cycle_no_access(GB_gameboy_t *gb) static void cycle_no_access(GB_gameboy_t *gb)
@ -297,28 +300,20 @@ static void cycle_no_access(GB_gameboy_t *gb)
static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id) static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id)
{ {
if (GB_is_cgb(gb)) {
/* Slight optimization */
gb->pending_cycles += 4;
return;
}
if (gb->pending_cycles) { if (gb->pending_cycles) {
GB_advance_cycles(gb, gb->pending_cycles); GB_advance_cycles(gb, gb->pending_cycles);
} }
gb->address_bus = gb->registers[register_id];
GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */ GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */
gb->pending_cycles = 4; gb->pending_cycles = 4;
} }
static void cycle_oam_bug_pc(GB_gameboy_t *gb) static void cycle_oam_bug_pc(GB_gameboy_t *gb)
{ {
if (GB_is_cgb(gb)) {
/* Slight optimization */
gb->pending_cycles += 4;
return;
}
if (gb->pending_cycles) { if (gb->pending_cycles) {
GB_advance_cycles(gb, gb->pending_cycles); GB_advance_cycles(gb, gb->pending_cycles);
} }
gb->address_bus = gb->pc;
GB_trigger_oam_bug(gb, gb->pc); /* Todo: test T-cycle timing */ GB_trigger_oam_bug(gb, gb->pc); /* Todo: test T-cycle timing */
gb->pending_cycles = 4; gb->pending_cycles = 4;
} }