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.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) &&
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue