Skip to content

Commit

Permalink
NES: Halt cycle for an aborted DMA should not count as a read on inpu…
Browse files Browse the repository at this point in the history
…t ports if the CPU is halted on a 4016/4017 read

The CPU will read 4016/4017 for 2 consecutive cycles, which the controllers will see a single read (except on the original famicom), so skip the dummy read to reproduce this behavior
  • Loading branch information
SourMesen committed Sep 28, 2024
1 parent efdf18d commit 3860e39
Showing 1 changed file with 15 additions and 9 deletions.
24 changes: 15 additions & 9 deletions Core/NES/NesCpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -356,35 +356,41 @@ void NesCpu::ProcessPendingDma(uint16_t readAddress)
//The exact specifics of where the CPU reads instead aren't known yet - so just disable read side-effects entirely on PAL
bool isNtscInputBehavior = _console->GetRegion() != ConsoleRegion::Pal;

//"If this cycle is a read, hijack the read, discard the value, and prevent all other actions that occur on this cycle (PC not incremented, etc)"
//On Famicom, each dummy/idle read to 4016/4017 is intepreted as a read of the joypad registers
//On NES (or AV Famicom), only the first dummy/idle read causes side effects (e.g only a single bit is lost)
bool isNesBehavior = _console->GetNesConfig().ConsoleType != NesConsoleType::Hvc001;
bool skipDummyReads = !isNtscInputBehavior || (isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017));

_needHalt = false;

StartCpuCycle(true);
if(isNtscInputBehavior && !skipFirstInputClock) {
if(_abortDmcDma && isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017)) {
//Skip halt cycle dummy read on 4016/4017
//The DMA was aborted, and the CPU will read 4016/4017 next
//If 4016/4017 is read here, the controllers will see 2 separate reads
//even though they would only see a single read on hardware (except the original Famicom)
} else if(isNtscInputBehavior && !skipFirstInputClock) {
_memoryManager->Read(readAddress, MemoryOperationType::DmaRead);
}
EndCpuCycle(true);
_needHalt = false;

if(_abortDmcDma) {
_dmcDmaRunning = false;
_abortDmcDma = false;
_needDummyRead = false;
return;
}

uint16_t spriteDmaCounter = 0;
uint8_t spriteReadAddr = 0;
uint8_t readValue = 0;

//On Famicom, each dummy/idle read to 4016/4017 is intepreted as a read of the joypad registers
//On NES (or AV Famicom), only the first dummy/idle read causes side effects (e.g only a single bit is lost)
NesConsoleType type = _console->GetNesConfig().ConsoleType;
bool isNesBehavior = type != NesConsoleType::Hvc001;
bool skipDummyReads = !isNtscInputBehavior || (isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017));

auto processCycle = [this] {
//Sprite DMA cycles count as halt/dummy cycles for the DMC DMA when both run at the same time
if(_abortDmcDma) {
_dmcDmaRunning = false;
_abortDmcDma = false;
_needDummyRead = false;
_needHalt = false;
} else if(_needHalt) {
_needHalt = false;
Expand Down

0 comments on commit 3860e39

Please sign in to comment.