mirror of https://github.com/bsnes-emu/bsnes.git
Better (But imperfect) emulation of the wave RAM address bug glitch
This commit is contained in:
parent
de16ab5d08
commit
94776fcf8c
35
Core/apu.c
35
Core/apu.c
|
@ -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.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;
|
||||
update_sample(gb, GB_WAVE, 0, 0);
|
||||
}
|
||||
|
@ -596,6 +606,12 @@ void GB_apu_run(GB_gameboy_t *gb)
|
|||
gb->apu.apu_cycles = 0;
|
||||
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;
|
||||
if (likely(!gb->stopped || GB_is_cgb(gb))) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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
|
||||
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:
|
||||
gb->apu.wave_channel.enable = value & 0x80;
|
||||
if (!gb->apu.wave_channel.enable) {
|
||||
gb->apu.channel_3_pulsed = false;
|
||||
if (gb->apu.is_active[GB_WAVE]) {
|
||||
// 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) {
|
||||
|
@ -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 |= (value & 7) << 8;
|
||||
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
|
||||
reads from it. */
|
||||
if (!GB_is_cgb(gb) &&
|
||||
|
|
|
@ -122,6 +122,7 @@ typedef struct
|
|||
|
||||
} 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_SKIPPED 1
|
||||
#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 noise_envelope_clock;
|
||||
bool channel_3_pulsed;
|
||||
bool channel_3_delayed_bugged_read;
|
||||
} GB_apu_t;
|
||||
|
||||
typedef enum {
|
||||
|
|
|
@ -434,6 +434,7 @@ struct GB_gameboy_internal_s {
|
|||
|
||||
int32_t ir_sensor;
|
||||
bool effective_ir_input;
|
||||
uint16_t address_bus;
|
||||
);
|
||||
|
||||
/* DMA and HDMA */
|
||||
|
|
|
@ -83,6 +83,7 @@ static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr)
|
|||
if (gb->pending_cycles) {
|
||||
GB_advance_cycles(gb, gb->pending_cycles);
|
||||
}
|
||||
gb->address_bus = addr;
|
||||
uint8_t ret = GB_read_memory(gb, addr);
|
||||
gb->pending_cycles = 4;
|
||||
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.
|
||||
If this timing proves incorrect, the ISR emulation must be updated so IF reads are
|
||||
timed correctly. */
|
||||
/* TODO: Does this affect the address bus? Verify. */
|
||||
static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value)
|
||||
{
|
||||
assert(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;
|
||||
GB_write_memory(gb, 0xFF00 + GB_IO_IF, value);
|
||||
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_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 4;
|
||||
return;
|
||||
break;
|
||||
|
||||
case GB_CONFLICT_READ_NEW:
|
||||
GB_advance_cycles(gb, gb->pending_cycles - 1);
|
||||
GB_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 5;
|
||||
return;
|
||||
break;
|
||||
|
||||
case GB_CONFLICT_WRITE_CPU:
|
||||
GB_advance_cycles(gb, gb->pending_cycles + 1);
|
||||
GB_write_memory(gb, addr, value);
|
||||
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 */
|
||||
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_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 3;
|
||||
return;
|
||||
break;
|
||||
|
||||
case GB_CONFLICT_STAT_CGB: {
|
||||
/* 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_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 3;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 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_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 5;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
case GB_CONFLICT_PALETTE_CGB: {
|
||||
GB_advance_cycles(gb, gb->pending_cycles - 2);
|
||||
GB_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 6;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
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_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 5;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
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_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 5;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
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->wx_just_changed = false;
|
||||
gb->pending_cycles = 3;
|
||||
return;
|
||||
break;
|
||||
|
||||
case GB_CONFLICT_CGB_LCDC:
|
||||
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->pending_cycles = 4;
|
||||
}
|
||||
return;
|
||||
break;
|
||||
|
||||
case GB_CONFLICT_NR10:
|
||||
/* 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->pending_cycles = 4;
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
gb->address_bus = addr;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (GB_is_cgb(gb)) {
|
||||
/* Slight optimization */
|
||||
gb->pending_cycles += 4;
|
||||
return;
|
||||
}
|
||||
if (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->pending_cycles = 4;
|
||||
}
|
||||
|
||||
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) {
|
||||
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->pending_cycles = 4;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue