PPU: Simulate the PPU's address bus a bit better (fixes Touhou Rououmu homebrew that detected Mesen as an emulator because of it)

This commit is contained in:
Sour 2018-06-02 22:12:00 -04:00
parent 76ad3946c0
commit aad8b2bd47
6 changed files with 90 additions and 65 deletions

View file

@ -686,6 +686,11 @@ void BaseMapper::SetNametable(uint8_t index, uint8_t nametableIndex)
_nametableIndexes[index] = nametableIndex;
SetPpuMemoryMapping(0x2000 + index * 0x400, 0x2000 + (index + 1) * 0x400 - 1, GetNametable(nametableIndex));
//Mirror $2000-$2FFF to $3000-$3FFF, while keeping a distinction between the addresses
//Previously, $3000-$3FFF was being "redirected" to $2000-$2FFF to avoid MMC3 IRQ issues (which is incorrect)
//More info here: https://forums.nesdev.com/viewtopic.php?p=132145#p132145
SetPpuMemoryMapping(0x3000 + index * 0x400, 0x3000 + (index + 1) * 0x400 - 1, GetNametable(nametableIndex));
}
void BaseMapper::SetNametables(uint8_t nametable1Index, uint8_t nametable2Index, uint8_t nametable3Index, uint8_t nametable4Index)
@ -790,16 +795,6 @@ void BaseMapper::WritePrgRam(uint16_t addr, uint8_t value)
}
}
void BaseMapper::ProcessVramAccess(uint16_t &addr)
{
addr &= 0x3FFF;
if(addr >= 0x3000) {
//Need to mirror 0x3000 writes to 0x2000, this appears to be how hardware behaves
//Required for proper MMC3 IRQ timing in Burai Fighter
addr -= 0x1000;
}
}
void BaseMapper::NotifyVRAMAddressChange(uint16_t addr)
{
//This is called when the VRAM addr on the PPU memory bus changes
@ -818,7 +813,7 @@ uint8_t BaseMapper::InternalReadVRAM(uint16_t addr)
uint8_t BaseMapper::DebugReadVRAM(uint16_t addr, bool disableSideEffects)
{
ProcessVramAccess(addr);
addr &= 0x3FFF;
if(!disableSideEffects) {
NotifyVRAMAddressChange(addr);
}
@ -832,7 +827,7 @@ uint8_t BaseMapper::MapperReadVRAM(uint16_t addr, MemoryOperationType operationT
void BaseMapper::DebugWriteVRAM(uint16_t addr, uint8_t value, bool disableSideEffects)
{
ProcessVramAccess(addr);
addr &= 0x3FFF;
if(disableSideEffects) {
if(_chrPages[addr >> 8]) {
//Always allow writes when side-effects are disabled
@ -848,9 +843,7 @@ void BaseMapper::DebugWriteVRAM(uint16_t addr, uint8_t value, bool disableSideEf
void BaseMapper::WriteVRAM(uint16_t addr, uint8_t value)
{
ProcessVramAccess(addr);
Debugger::ProcessVramWriteOperation(addr, value);
NotifyVRAMAddressChange(addr);
if(_chrPageAccessType[addr >> 8] & MemoryAccessType::Write) {
_chrPages[addr >> 8][(uint8_t)addr] = value;

View file

@ -185,9 +185,6 @@ public:
__forceinline uint8_t ReadVRAM(uint16_t addr, MemoryOperationType type = MemoryOperationType::PpuRenderingRead)
{
ProcessVramAccess(addr);
NotifyVRAMAddressChange(addr);
uint8_t value = MapperReadVRAM(addr, type);
Debugger::ProcessVramReadOperation(type, addr, value);
return value;
@ -196,7 +193,6 @@ public:
void DebugWriteVRAM(uint16_t addr, uint8_t value, bool disableSideEffects = true);
void WriteVRAM(uint16_t addr, uint8_t value);
__forceinline void ProcessVramAccess(uint16_t &addr);
uint8_t DebugReadVRAM(uint16_t addr, bool disableSideEffects = true);
static void InitializeRam(void* data, uint32_t length);

View file

@ -228,6 +228,7 @@ protected:
for(int i = 0; i < 4; i++) {
if(_disableNtRam || (_ntLowRegs[i] & 0x80) != (_ntRamSelectBit & 0x80)) {
SetPpuMemoryMapping(0x2000 + 0x400 * i, 0x23FF + 0x400 * i, _ntLowRegs[i] | (_ntHighRegs[i] << 8), ChrMemoryType::ChrRom);
SetPpuMemoryMapping(0x3000 + 0x400 * i, 0x33FF + 0x400 * i, _ntLowRegs[i] | (_ntHighRegs[i] << 8), ChrMemoryType::ChrRom);
} else {
SetNametable(i, _ntLowRegs[i] & 0x01);
}

View file

@ -60,6 +60,7 @@ void PPU::Reset()
_currentTile = {};
_nextTile = {};
_ppuBusAddress = 0;
_intensifyColorBits = 0;
_paletteRamMask = 0x3F;
_lastUpdatedPixel = -1;
@ -170,11 +171,11 @@ void PPU::SetState(PPUDebugState &state)
void PPU::UpdateVideoRamAddr()
{
if(_scanline >= 240 || !IsRenderingEnabled()) {
_state.VideoRamAddr += _flags.VerticalWrite ? 32 : 1;
_state.VideoRamAddr = (_state.VideoRamAddr + (_flags.VerticalWrite ? 32 : 1)) & 0x7FFF;
//Trigger memory read when setting the vram address - needed by MMC3 IRQ counter
//"Should be clocked when A12 changes to 1 via $2007 read/write"
_mapper->ReadVRAM(_state.VideoRamAddr, MemoryOperationType::Read);
SetBusAddress(_state.VideoRamAddr & 0x3FFF);
} else {
//"During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled), "
//it will update v in an odd way, triggering a coarse X increment and a Y increment simultaneously"
@ -268,7 +269,7 @@ uint8_t PPU::ReadRAM(uint16_t addr)
openBusMask = 0xFF;
} else {
returnValue = _memoryReadBuffer;
_memoryReadBuffer = _mapper->ReadVRAM(_state.VideoRamAddr, MemoryOperationType::Read);
_memoryReadBuffer = ReadVram(_ppuBusAddress & 0x3FFF, MemoryOperationType::Read);
if((_state.VideoRamAddr & 0x3FFF) >= 0x3F00 && !EmulationSettings::CheckFlag(EmulationFlags::DisablePaletteRead)) {
returnValue = ReadPaletteRAM(_state.VideoRamAddr) | (_openBus & 0xC0);
@ -360,7 +361,7 @@ void PPU::WriteRAM(uint16_t addr, uint8_t value)
WritePaletteRAM(_state.VideoRamAddr, value);
Debugger::ProcessVramWriteOperation(_state.VideoRamAddr & 0x3FFF, value);
} else {
_mapper->WriteVRAM(_state.VideoRamAddr, value);
_mapper->WriteVRAM(_ppuBusAddress & 0x3FFF, value);
}
UpdateVideoRamAddr();
break;
@ -541,6 +542,24 @@ uint16_t PPU::GetAttributeAddr()
return 0x23C0 | (_state.VideoRamAddr & 0x0C00) | ((_state.VideoRamAddr >> 4) & 0x38) | ((_state.VideoRamAddr >> 2) & 0x07);
}
void PPU::SetBusAddress(uint16_t addr)
{
_ppuBusAddress = addr;
_mapper->NotifyVRAMAddressChange(addr);
}
uint8_t PPU::ReadVram(uint16_t addr, MemoryOperationType type)
{
SetBusAddress(addr);
return _mapper->ReadVRAM(addr, type);
}
void PPU::WriteVram(uint16_t addr, uint8_t value)
{
SetBusAddress(addr);
_mapper->WriteVRAM(addr, value);
}
void PPU::LoadTileInfo()
{
if(IsRenderingEnabled()) {
@ -552,7 +571,7 @@ void PPU::LoadTileInfo()
_state.LowBitShift |= _nextTile.LowByte;
_state.HighBitShift |= _nextTile.HighByte;
uint8_t tileIndex = _mapper->ReadVRAM(GetNameTableAddr());
uint8_t tileIndex = ReadVram(GetNameTableAddr());
_nextTile.TileAddr = (tileIndex << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr;
_nextTile.OffsetY = _state.VideoRamAddr >> 12;
break;
@ -560,17 +579,17 @@ void PPU::LoadTileInfo()
case 2: {
uint8_t shift = ((_state.VideoRamAddr >> 4) & 0x04) | (_state.VideoRamAddr & 0x02);
_nextTile.PaletteOffset = ((_mapper->ReadVRAM(GetAttributeAddr()) >> shift) & 0x03) << 2;
_nextTile.PaletteOffset = ((ReadVram(GetAttributeAddr()) >> shift) & 0x03) << 2;
break;
}
case 3:
_nextTile.LowByte = _mapper->ReadVRAM(_nextTile.TileAddr);
_nextTile.LowByte = ReadVram(_nextTile.TileAddr);
_nextTile.AbsoluteTileAddr = _mapper->ToAbsoluteChrAddress(_nextTile.TileAddr);
break;
case 5:
_nextTile.HighByte = _mapper->ReadVRAM(_nextTile.TileAddr + 8);
_nextTile.HighByte = ReadVram(_nextTile.TileAddr + 8);
break;
}
}
@ -609,8 +628,8 @@ void PPU::LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uin
info.HighByte = _mapper->DebugReadVRAM(tileAddr + 8);
} else {
fetchLastSprite = false;
info.LowByte = _mapper->ReadVRAM(tileAddr);
info.HighByte = _mapper->ReadVRAM(tileAddr + 8);
info.LowByte = ReadVram(tileAddr);
info.HighByte = ReadVram(tileAddr + 8);
}
info.TileAddr = tileAddr;
info.AbsoluteTileAddr = _mapper->ToAbsoluteChrAddress(tileAddr);
@ -635,8 +654,8 @@ void PPU::LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uin
tileAddr = ((tileIndex << 4) | _flags.SpritePatternAddr) + lineOffset;
}
_mapper->ReadVRAM(tileAddr);
_mapper->ReadVRAM(tileAddr + 8);
ReadVram(tileAddr);
ReadVram(tileAddr + 8);
}
_spriteIndex++;
@ -841,10 +860,10 @@ void PPU::ProcessScanline()
LoadSpriteTileInfo();
} else if((_cycle - 257) % 8 == 0) {
//Garbage NT sprite fetch (257, 265, 273, etc.) - Required for proper MC-ACC IRQs (MMC3 clone)
_mapper->ReadVRAM(GetNameTableAddr());
ReadVram(GetNameTableAddr());
} else if((_cycle - 259) % 8 == 0) {
//Garbage AT sprite fetch
_mapper->ReadVRAM(GetAttributeAddr());
ReadVram(GetAttributeAddr());
}
if(_scanline == -1 && _cycle >= 280 && _cycle <= 304) {
@ -868,7 +887,7 @@ void PPU::ProcessScanline()
}
} else if(_cycle == 337 || _cycle == 339) {
if(IsRenderingEnabled()) {
_mapper->ReadVRAM(GetNameTableAddr());
ReadVram(GetNameTableAddr());
if(_scanline == -1 && _nesModel == NesModel::NTSC && _cycle == 339 && (_frameCount & 0x01)) {
//This behavior is NTSC-specific - PAL frames are always the same number of cycles
@ -1158,9 +1177,13 @@ void PPU::UpdateState()
if(_updateVramAddrDelay == 0) {
_state.VideoRamAddr = _updateVramAddr;
//Trigger memory read when setting the vram address - needed by MMC3 IRQ counter
//"4) Should be clocked when A12 changes to 1 via $2006 write"
_mapper->ReadVRAM(_state.VideoRamAddr, MemoryOperationType::Read);
if(_scanline >= 240 || !IsRenderingEnabled()) {
//Only set the VRAM address on the bus if the PPU is rendering
//More info here: https://forums.nesdev.com/viewtopic.php?p=132145#p132145
//Trigger bus address change when setting the vram address - needed by MMC3 IRQ counter
//"4) Should be clocked when A12 changes to 1 via $2006 write"
SetBusAddress(_state.VideoRamAddr & 0x3FFF);
}
} else {
_needStateUpdate = true;
}
@ -1228,7 +1251,8 @@ void PPU::StreamState(bool saving)
_nextTile.PaletteOffset, _nextTile.TileAddr, _previousTile.LowByte, _previousTile.HighByte, _previousTile.PaletteOffset, _spriteIndex, _spriteCount,
_secondaryOAMAddr, _sprite0Visible, _oamCopybuffer, _spriteInRange, _sprite0Added, _spriteAddrH, _spriteAddrL, _oamCopyDone, _nesModel, unusedSpriteDmaAddr,
unusedSpriteDmaCounter, _prevRenderingEnabled, _renderingEnabled, _openBus, _ignoreVramRead, unusedSkipScrollIncrement, paletteRam, spriteRam, secondarySpriteRam,
openBusDecayStamp, _cyclesNeeded, disablePpu2004Reads, disablePaletteRead, disableOamAddrBug, _overflowBugCounter, _updateVramAddr, _updateVramAddrDelay, _needStateUpdate);
openBusDecayStamp, _cyclesNeeded, disablePpu2004Reads, disablePaletteRead, disableOamAddrBug, _overflowBugCounter, _updateVramAddr, _updateVramAddrDelay,
_needStateUpdate, _ppuBusAddress);
for(int i = 0; i < 64; i++) {
Stream(_spriteTiles[i].SpriteX, _spriteTiles[i].LowByte, _spriteTiles[i].HighByte, _spriteTiles[i].PaletteOffset, _spriteTiles[i].HorizontalMirror, _spriteTiles[i].BackgroundPriority);

View file

@ -63,6 +63,7 @@ class PPU : public IMemoryHandler, public Snapshotable
SpriteInfo *_lastSprite; //used by HD ppu
uint16_t _ppuBusAddress;
TileInfo _currentTile;
TileInfo _nextTile;
TileInfo _previousTile;
@ -156,6 +157,10 @@ class PPU : public IMemoryHandler, public Snapshotable
}
}
__forceinline void SetBusAddress(uint16_t addr);
__forceinline uint8_t ReadVram(uint16_t addr, MemoryOperationType type = MemoryOperationType::PpuRenderingRead);
__forceinline void WriteVram(uint16_t addr, uint8_t value);
void StreamState(bool saving) override;
public:

View file

@ -54,6 +54,12 @@ protected:
_audio.Clock();
}
void SetPpuMapping(uint8_t bank, uint8_t page)
{
SetPpuMemoryMapping(0x2000 + bank * 0x400, 0x23FF + bank * 0x400, page);
SetPpuMemoryMapping(0x3000 + bank * 0x400, 0x33FF + bank * 0x400, page);
}
void UpdatePpuBanking()
{
uint8_t mask = (_bankingMode & 0x20) ? 0xFE : 0xFF;
@ -99,34 +105,34 @@ protected:
switch(_bankingMode & 0x2F) {
case 0x20:
case 0x27:
SetPpuMemoryMapping(0x2000, 0x23FF, _chrRegisters[6] & 0xFE);
SetPpuMemoryMapping(0x2400, 0x27FF, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMemoryMapping(0x2800, 0x2BFF, _chrRegisters[7] & 0xFE);
SetPpuMemoryMapping(0x2C00, 0x2FFF, (_chrRegisters[7] & 0xFE) | 1);
SetPpuMapping(0, _chrRegisters[6] & 0xFE);
SetPpuMapping(1, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMapping(2, _chrRegisters[7] & 0xFE);
SetPpuMapping(3, (_chrRegisters[7] & 0xFE) | 1);
break;
case 0x23:
case 0x24:
SetPpuMemoryMapping(0x2000, 0x23FF, (_chrRegisters[6] & 0xFE));
SetPpuMemoryMapping(0x2400, 0x27FF, (_chrRegisters[7] & 0xFE));
SetPpuMemoryMapping(0x2800, 0x2BFF, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMemoryMapping(0x2C00, 0x2FFF, (_chrRegisters[7] & 0xFE) | 1);
SetPpuMapping(0, (_chrRegisters[6] & 0xFE));
SetPpuMapping(1, (_chrRegisters[7] & 0xFE));
SetPpuMapping(2, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMapping(3, (_chrRegisters[7] & 0xFE) | 1);
break;
case 0x28:
case 0x2F:
SetPpuMemoryMapping(0x2000, 0x23FF, _chrRegisters[6] & 0xFE);
SetPpuMemoryMapping(0x2400, 0x27FF, _chrRegisters[6] & 0xFE);
SetPpuMemoryMapping(0x2800, 0x2BFF, _chrRegisters[7] & 0xFE);
SetPpuMemoryMapping(0x2C00, 0x2FFF, _chrRegisters[7] & 0xFE);
SetPpuMapping(0, _chrRegisters[6] & 0xFE);
SetPpuMapping(1, _chrRegisters[6] & 0xFE);
SetPpuMapping(2, _chrRegisters[7] & 0xFE);
SetPpuMapping(3, _chrRegisters[7] & 0xFE);
break;
case 0x2B:
case 0x2C:
SetPpuMemoryMapping(0x2000, 0x23FF, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMemoryMapping(0x2400, 0x27FF, (_chrRegisters[7] & 0xFE) | 1);
SetPpuMemoryMapping(0x2800, 0x2BFF, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMemoryMapping(0x2C00, 0x2FFF, (_chrRegisters[7] & 0xFE) | 1);
SetPpuMapping(0, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMapping(1, (_chrRegisters[7] & 0xFE) | 1);
SetPpuMapping(2, (_chrRegisters[6] & 0xFE) | 1);
SetPpuMapping(3, (_chrRegisters[7] & 0xFE) | 1);
break;
default:
@ -134,27 +140,27 @@ protected:
case 0:
case 6:
case 7:
SetPpuMemoryMapping(0x2000, 0x23FF, _chrRegisters[6]);
SetPpuMemoryMapping(0x2400, 0x27FF, _chrRegisters[6]);
SetPpuMemoryMapping(0x2800, 0x2BFF, _chrRegisters[7]);
SetPpuMemoryMapping(0x2C00, 0x2FFF, _chrRegisters[7]);
SetPpuMapping(0, _chrRegisters[6]);
SetPpuMapping(1, _chrRegisters[6]);
SetPpuMapping(2, _chrRegisters[7]);
SetPpuMapping(3, _chrRegisters[7]);
break;
case 1:
case 5:
SetPpuMemoryMapping(0x2000, 0x23FF, _chrRegisters[4]);
SetPpuMemoryMapping(0x2400, 0x27FF, _chrRegisters[5]);
SetPpuMemoryMapping(0x2800, 0x2BFF, _chrRegisters[6]);
SetPpuMemoryMapping(0x2C00, 0x2FFF, _chrRegisters[7]);
SetPpuMapping(0, _chrRegisters[4]);
SetPpuMapping(1, _chrRegisters[5]);
SetPpuMapping(2, _chrRegisters[6]);
SetPpuMapping(3, _chrRegisters[7]);
break;
case 2:
case 3:
case 4:
SetPpuMemoryMapping(0x2000, 0x23FF, _chrRegisters[6]);
SetPpuMemoryMapping(0x2400, 0x27FF, _chrRegisters[7]);
SetPpuMemoryMapping(0x2800, 0x2BFF, _chrRegisters[6]);
SetPpuMemoryMapping(0x2C00, 0x2FFF, _chrRegisters[7]);
SetPpuMapping(0, _chrRegisters[6]);
SetPpuMapping(1, _chrRegisters[7]);
SetPpuMapping(2, _chrRegisters[6]);
SetPpuMapping(3, _chrRegisters[7]);
break;
}
break;