From 17c8294f5d921753973ae82ee520591b79b0cd7c Mon Sep 17 00:00:00 2001 From: Sour Date: Sun, 19 Jan 2020 14:45:04 -0500 Subject: [PATCH] MMC5: Improved accuracy (scanline counter, split screen mode, etc.) --- Core/Core.vcxproj | 1 + Core/Core.vcxproj.filters | 3 + Core/MMC5.h | 286 +++++++++++++++++++++----------------- Core/MMC5MemoryHandler.h | 31 +++++ Core/MemoryManager.cpp | 7 + Core/MemoryManager.h | 1 + Core/PPU.cpp | 19 +-- Core/PPU.h | 3 +- 8 files changed, 213 insertions(+), 138 deletions(-) create mode 100644 Core/MMC5MemoryHandler.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 72f47999..1ce9afd2 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -579,6 +579,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 8990c5e8..f98ce50c 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1496,6 +1496,9 @@ Nes\RomLoader + + Nes\Mappers\MMC + diff --git a/Core/MMC5.h b/Core/MMC5.h index 499d83e6..b6dac916 100644 --- a/Core/MMC5.h +++ b/Core/MMC5.h @@ -3,6 +3,7 @@ #include "BaseMapper.h" #include "PPU.h" #include "MMC5Audio.h" +#include "MMC5MemoryHandler.h" class MMC5 : public BaseMapper { @@ -13,6 +14,7 @@ private: static constexpr uint8_t NtFillModeIndex = 3; unique_ptr _audio; + unique_ptr _mmc5MemoryHandler; uint8_t _prgRamProtect1; uint8_t _prgRamProtect2; @@ -50,18 +52,19 @@ private: uint8_t _chrUpperBits; uint16_t _chrBanks[12]; uint16_t _lastChrReg; - bool _spriteFetch; - bool _largeSprites; + bool _prevChrA; //IRQ counter related fields uint8_t _irqCounterTarget; bool _irqEnabled; - int16_t _previousScanline; - uint8_t _irqCounter; + uint8_t _scanlineCounter; bool _irqPending; - bool _ppuInFrame; - MemoryOperationType _lastVramOperationType; + bool _needInFrame; + bool _ppuInFrame; + uint8_t _ppuIdleCounter; + uint16_t _lastPpuReadAddr; + uint8_t _ntReadCounter; void SwitchPrgBank(uint16_t reg, uint8_t value) { @@ -173,26 +176,29 @@ private: void SwitchChrBank(uint16_t reg, uint8_t value) { - _chrBanks[reg - 0x5120] = value | (_chrUpperBits << 8); - - if(_largeSprites) { + uint16_t newValue = value | (_chrUpperBits << 8); + if(newValue != _chrBanks[reg - 0x5120] || _lastChrReg != reg) { + _chrBanks[reg - 0x5120] = newValue; _lastChrReg = reg; - } else { - //Using 8x8 sprites resets the last written to bank logic - //Unsure about this part (hasn't been tested specifically, but would make sense) - _lastChrReg = 0; + UpdateChrBanks(true); } - UpdateChrBanks(); } - void UpdateChrBanks() + void UpdateChrBanks(bool forceUpdate) { - if(!_largeSprites) { + bool largeSprites = (_mmc5MemoryHandler->GetReg(0x2000) & 0x20) != 0; + + if(!largeSprites) { //Using 8x8 sprites resets the last written to bank logic _lastChrReg = 0; } - bool chrA = !_largeSprites || (_largeSprites && _spriteFetch) || (_lastVramOperationType != MemoryOperationType::PpuRenderingRead && _lastChrReg <= 0x5127); + bool chrA = !largeSprites || (_splitTileNumber >= 32 && _splitTileNumber < 40) || (!_ppuInFrame && _lastChrReg <= 0x5127); + if(!forceUpdate && chrA == _prevChrA) { + return; + } + _prevChrA = chrA; + if(_chrMode == 0) { SelectChrPage8x(0, _chrBanks[chrA ? 0x07 : 0x0B] << 3); } else if(_chrMode == 1) { @@ -218,36 +224,14 @@ private: void ProcessCpuClock() override { _audio->Clock(); - } - virtual void NotifyVRAMAddressChange(uint16_t addr) override - { - PPU* ppu = _console->GetPpu(); - if(ppu->GetControlFlags().BackgroundEnabled || ppu->GetControlFlags().SpritesEnabled) { - int16_t currentScanline = ppu->GetCurrentScanline(); - if(currentScanline != _previousScanline) { - if(currentScanline >= 239 || currentScanline < 0) { - _ppuInFrame = false; - } else { - if(!_ppuInFrame) { - _ppuInFrame = true; - _irqCounter = 0; - _irqPending = false; - _console->GetCpu()->ClearIrqSource(IRQSource::External); - } else { - _irqCounter++; - if(_irqCounter == _irqCounterTarget) { - _irqPending = true; - if(_irqEnabled) { - _console->GetCpu()->SetIrqSource(IRQSource::External); - } - } - } - } - _previousScanline = currentScanline; + if(_ppuIdleCounter) { + _ppuIdleCounter--; + if(_ppuIdleCounter == 0) { + //"The "in-frame" flag is cleared when the PPU is no longer rendering. This is detected when 3 CPU cycles pass without a PPU read having occurred (PPU /RD has not been low during the last 3 M2 rises)." + _ppuInFrame = false; + UpdateChrBanks(true); } - } else { - _ppuInFrame = false; } } @@ -312,11 +296,6 @@ private: memset(GetNametable(NtFillModeIndex) + 32 * 30, attributeByte, 64); //Attribute table is 64 bytes } - bool IsSpriteFetch() - { - return _console->GetPpu()->GetCurrentCycle() >= 257 && _console->GetPpu()->GetCurrentCycle() < 321; - } - protected: virtual uint16_t GetPRGPageSize() override { return 0x2000; } virtual uint16_t GetCHRPageSize() override { return 0x400; } @@ -369,8 +348,18 @@ protected: virtual void InitMapper() override { + AddRegisterRange(0xFFFA, 0xFFFB, MemoryOperation::Read); + _audio.reset(new MMC5Audio(_console)); + //Override the 2000-2007 registers to catch all writes to the PPU registers (but not their mirrors) + _mmc5MemoryHandler.reset(new MMC5MemoryHandler(_console.get())); + + _ppuIdleCounter = 0; + _lastPpuReadAddr = 0; + _ntReadCounter = 0; + _prevChrA = false; + _chrMode = 0; _prgRamProtect1 = 0; _prgRamProtect2 = 0; @@ -388,8 +377,6 @@ protected: _chrUpperBits = 0; memset(_chrBanks, 0, sizeof(_chrBanks)); _lastChrReg = 0; - _spriteFetch = false; - _largeSprites = false; _exAttrLastFetchCounter = 0; _exAttributeLastNametableFetch = 0; @@ -397,12 +384,10 @@ protected: _irqPending = false; _irqCounterTarget = 0; - _irqCounter = 0; + _scanlineCounter = 0; _irqEnabled = false; - _previousScanline = -1; _ppuInFrame = false; - - _lastVramOperationType = MemoryOperationType::Read; + _needInFrame = false; _splitInSplitRegion = false; _splitVerticalScroll = 0; @@ -418,12 +403,23 @@ protected: //"Games seem to expect $5117 to be $FF on powerup (last PRG page swapped in)." WriteRegister(0x5117, 0xFF); + + UpdateChrBanks(true); + } + + void Reset(bool softReset) override + { + _console->GetMemoryManager()->RegisterWriteHandler(_mmc5MemoryHandler.get(), 0x2000, 0x2007); } void StreamState(bool saving) override { BaseMapper::StreamState(saving); + int16_t unusedPreviousScanline = 0; + bool unusedSpriteFetch = false; + bool unusedLargeSprites = false; + ArrayInfo prgBanks = { _prgBanks, 5 }; ArrayInfo chrBanks = { _chrBanks, 12 }; SnapshotInfo audio{ _audio.get() }; @@ -431,8 +427,8 @@ protected: _verticalSplitDelimiterTile, _verticalSplitScroll, _verticalSplitBank, _multiplierValue1, _multiplierValue2, _nametableMapping, _extendedRamMode, _exAttributeLastNametableFetch, _exAttrLastFetchCounter, _exAttrSelectedChrBank, _prgMode, prgBanks, _chrMode, _chrUpperBits, chrBanks, _lastChrReg, - _spriteFetch, _largeSprites, _irqCounterTarget, _irqEnabled, _previousScanline, _irqCounter, _irqPending, _ppuInFrame, audio, - _splitInSplitRegion, _splitVerticalScroll, _splitTile, _splitTileNumber, _lastVramOperationType); + unusedSpriteFetch, unusedLargeSprites, _irqCounterTarget, _irqEnabled, unusedPreviousScanline, _scanlineCounter, _irqPending, _ppuInFrame, audio, + _splitInSplitRegion, _splitVerticalScroll, _splitTile, _splitTileNumber, _needInFrame); if(!saving) { UpdatePrgBanks(); @@ -442,103 +438,127 @@ protected: virtual void WriteRAM(uint16_t addr, uint8_t value) override { - if(addr >= 0x5C00 && addr <= 0x5FFF && _extendedRamMode <= 1) { - PPUControlFlags flags = _console->GetPpu()->GetControlFlags(); - if(!flags.BackgroundEnabled && !flags.SpritesEnabled) { - //Expansion RAM ($5C00-$5FFF, read/write) - //Mode 0/1 - Not readable (returns open bus), can only be written while the PPU is rendering (otherwise, 0 is written) - value = 0; - } + if(addr >= 0x5C00 && addr <= 0x5FFF && _extendedRamMode <= 1 && !_ppuInFrame) { + //Expansion RAM ($5C00-$5FFF, read/write) + //Mode 0/1 - Not readable (returns open bus), can only be written while the PPU is rendering (otherwise, 0 is written) + value = 0; } BaseMapper::WriteRAM(addr, value); } + void DetectScanlineStart(uint16_t addr) + { + if(addr >= 0x2000 && addr <= 0x2FFF) { + if(_lastPpuReadAddr == addr) { + //Count consecutive identical reads + _ntReadCounter++; + } else { + _ntReadCounter = 0; + } + + if(_ntReadCounter >= 2) { + if(!_ppuInFrame && !_needInFrame) { + _needInFrame = true; + _scanlineCounter = 0; + } else { + _scanlineCounter++; + if(_irqCounterTarget == _scanlineCounter) { + _irqPending = true; + if(_irqEnabled) { + _console->GetCpu()->SetIrqSource(IRQSource::External); + } + } + } + _splitTileNumber = 0; + } + } else { + _ntReadCounter = 0; + } + } + virtual uint8_t MapperReadVRAM(uint16_t addr, MemoryOperationType memoryOperationType) override { - PPU* ppu = _console->GetPpu(); - if(_spriteFetch != IsSpriteFetch() || _largeSprites != ppu->GetControlFlags().LargeSprites || _lastVramOperationType != memoryOperationType) { - _lastVramOperationType = memoryOperationType; - _spriteFetch = IsSpriteFetch(); - _largeSprites = ppu->GetControlFlags().LargeSprites; - UpdateChrBanks(); + bool isNtFetch = addr >= 0x2000 && addr <= 0x2FFF && (addr & 0x3FF) < 0x3C0; + if(isNtFetch) { + //Nametable data, not an attribute fetch + _splitInSplitRegion = false; + _splitTileNumber++; + + if(_ppuInFrame) { + UpdateChrBanks(false); + } else if(_needInFrame) { + _needInFrame = false; + _ppuInFrame = true; + UpdateChrBanks(false); + } } + DetectScanlineStart(addr); - if(_extendedRamMode <= 1 && _verticalSplitEnabled && memoryOperationType == MemoryOperationType::PpuRenderingRead) { - uint32_t cycle = ppu->GetCurrentCycle(); - int32_t scanline = ppu->GetCurrentScanline(); - if(cycle == 321) { - _splitTileNumber = -1; - if(scanline == -1) { - _splitVerticalScroll = _verticalSplitScroll; - } else if(scanline < 240) { - _splitVerticalScroll++; - } - if(_splitVerticalScroll >= 240) { - _splitVerticalScroll -= 240; - } - } + _ppuIdleCounter = 3; + _lastPpuReadAddr = addr; - if((cycle - 1) % 8 == 0 && cycle != 337) { - _splitTileNumber++; - } - - if(cycle < 256 || cycle >= 321) { + if(_extendedRamMode <= 1 && _ppuInFrame) { + if(_verticalSplitEnabled) { + uint16_t verticalSplitScroll = (_verticalSplitScroll + _scanlineCounter) % 240; if(addr >= 0x2000) { - if((addr & 0x3FF) < 0x3C0) { - if((_verticalSplitRightSide && _splitTileNumber >= _verticalSplitDelimiterTile) || (!_verticalSplitRightSide && _splitTileNumber < _verticalSplitDelimiterTile)) { - //Split region + if(isNtFetch) { + uint8_t tileNumber = (_splitTileNumber + 2) % 42; + if(tileNumber <= 32 && (_verticalSplitRightSide && tileNumber >= _verticalSplitDelimiterTile) || (!_verticalSplitRightSide && tileNumber < _verticalSplitDelimiterTile)) { + //Split region (for next 3 fetches, attribute + 2x tile data) _splitInSplitRegion = true; - _splitTile = ((_splitVerticalScroll & 0xF8) << 2) | _splitTileNumber; + _splitTile = ((verticalSplitScroll & 0xF8) << 2) | tileNumber; return InternalReadRam(0x5C00 + _splitTile); } else { - //Regular data, result can get modified by ex ram mode code below + //Outside of split region (or sprite data), result can get modified by ex ram mode code below _splitInSplitRegion = false; } } else if(_splitInSplitRegion) { - return InternalReadRam(0x5FC0 + ((_splitTile >> 4) & ~0x07) + ((_splitTile & 0x3F) >> 2)); + return InternalReadRam(0x5FC0 | ((_splitTile & 0x380) >> 4) | ((_splitTile & 0x1F) >> 2)); } } else if(_splitInSplitRegion) { - return _chrRom[(_verticalSplitBank % (GetCHRPageCount() / 4)) * 0x1000 + (((addr & ~0x07) | (_splitVerticalScroll & 0x07)) & 0xFFF)]; + //CHR tile fetches for split region + return _chrRom[(_verticalSplitBank % (GetCHRPageCount() / 4)) * 0x1000 + (((addr & ~0x07) | (verticalSplitScroll & 0x07)) & 0xFFF)]; } } - } - if(_extendedRamMode == 1 && !IsSpriteFetch() && memoryOperationType == MemoryOperationType::PpuRenderingRead) { - //"In Mode 1, nametable fetches are processed normally, and can come from CIRAM nametables, fill mode, or even Expansion RAM, but attribute fetches are replaced by data from Expansion RAM." - //"Each byte of Expansion RAM is used to enhance the tile at the corresponding address in every nametable" + if(_extendedRamMode == 1 && (_splitTileNumber < 32 || _splitTileNumber >= 40)) { + //"In Mode 1, nametable fetches are processed normally, and can come from CIRAM nametables, fill mode, or even Expansion RAM, but attribute fetches are replaced by data from Expansion RAM." + //"Each byte of Expansion RAM is used to enhance the tile at the corresponding address in every nametable" - //When fetching NT data, we set a flag and then alter the VRAM values read by the PPU on the following 3 cycles (palette, tile low/high byte) - if(addr >= 0x2000 && (addr & 0x3FF) < 0x3C0) { - //Nametable fetches - _exAttributeLastNametableFetch = addr & 0x03FF; - _exAttrLastFetchCounter = 3; - } else if(_exAttrLastFetchCounter > 0) { - //Attribute fetches - _exAttrLastFetchCounter--; - switch(_exAttrLastFetchCounter) { - case 2: - { - //PPU palette fetch - //Check work ram (expansion ram) to see which tile/palette to use - //Use InternalReadRam to bypass the fact that the ram is supposed to be write-only in mode 0/1 - uint8_t value = InternalReadRam(0x5C00 + _exAttributeLastNametableFetch); + //When fetching NT data, we set a flag and then alter the VRAM values read by the PPU on the following 3 cycles (palette, tile low/high byte) + if(isNtFetch) { + //Nametable fetches + _exAttributeLastNametableFetch = addr & 0x03FF; + _exAttrLastFetchCounter = 3; + } else if(_exAttrLastFetchCounter > 0) { + //Attribute fetches + _exAttrLastFetchCounter--; + switch(_exAttrLastFetchCounter) { + case 2: + { + //PPU palette fetch + //Check work ram (expansion ram) to see which tile/palette to use + //Use InternalReadRam to bypass the fact that the ram is supposed to be write-only in mode 0/1 + uint8_t value = InternalReadRam(0x5C00 + _exAttributeLastNametableFetch); - //"The pattern fetches ignore the standard CHR banking bits, and instead use the top two bits of $5130 and the bottom 6 bits from Expansion RAM to choose a 4KB bank to select the tile from." - _exAttrSelectedChrBank = ((value & 0x3F) | (_chrUpperBits << 6)) % (_chrRomSize / 0x1000); + //"The pattern fetches ignore the standard CHR banking bits, and instead use the top two bits of $5130 and the bottom 6 bits from Expansion RAM to choose a 4KB bank to select the tile from." + _exAttrSelectedChrBank = ((value & 0x3F) | (_chrUpperBits << 6)) % (_chrRomSize / 0x1000); - //Return a byte containing the same palette 4 times - this allows the PPU to select the right palette no matter the shift value - uint8_t palette = (value & 0xC0) >> 6; - return palette | palette << 2 | palette << 4 | palette << 6; + //Return a byte containing the same palette 4 times - this allows the PPU to select the right palette no matter the shift value + uint8_t palette = (value & 0xC0) >> 6; + return palette | palette << 2 | palette << 4 | palette << 6; + } + + case 1: + case 0: + //PPU tile data fetch (high byte & low byte) + return _chrRom[_exAttrSelectedChrBank * 0x1000 + (addr & 0xFFF)]; } - - case 1: - case 0: - //PPU tile data fetch (high byte & low byte) - return _chrRom[_exAttrSelectedChrBank * 0x1000 + (addr & 0xFFF)]; } } } - return BaseMapper::MapperReadVRAM(addr, memoryOperationType); + + return InternalReadVRAM(addr); } void WriteRegister(uint16_t addr, uint8_t value) override @@ -554,7 +574,7 @@ protected: break; case 0x5100: _prgMode = value & 0x03; UpdatePrgBanks(); break; - case 0x5101: _chrMode = value & 0x03; UpdateChrBanks(); break; + case 0x5101: _chrMode = value & 0x03; UpdateChrBanks(true); break; case 0x5102: _prgRamProtect1 = value & 0x03; UpdatePrgBanks(); break; case 0x5103: _prgRamProtect2 = value & 0x03; UpdatePrgBanks(); break; case 0x5104: SetExtendedRamMode(value & 0x03); break; @@ -603,6 +623,16 @@ protected: case 0x5205: return (_multiplierValue1*_multiplierValue2) & 0xFF; case 0x5206: return (_multiplierValue1*_multiplierValue2) >> 8; + + case 0xFFFA: + case 0xFFFB: + _ppuInFrame = false; + UpdateChrBanks(true); + _lastPpuReadAddr = 0; + _scanlineCounter = 0; + _irqPending = false; + _console->GetCpu()->ClearIrqSource(IRQSource::External); + return DebugReadRAM(addr); } return _console->GetMemoryManager()->GetOpenBus(); diff --git a/Core/MMC5MemoryHandler.h b/Core/MMC5MemoryHandler.h new file mode 100644 index 00000000..946ee1ca --- /dev/null +++ b/Core/MMC5MemoryHandler.h @@ -0,0 +1,31 @@ +#pragma once +#include "stdafx.h" +#include "IMemoryHandler.h" +#include "Console.h" + +class MMC5MemoryHandler : public IMemoryHandler +{ + Console* _console; + uint8_t _ppuRegs[8]; + +public: + MMC5MemoryHandler(Console* console) + { + _console = console; + memset(_ppuRegs, 0, sizeof(_ppuRegs)); + } + + uint8_t GetReg(uint16_t addr) + { + return _ppuRegs[addr & 0x07]; + } + + void GetMemoryRanges(MemoryRanges& ranges) override {} + uint8_t ReadRAM(uint16_t addr) override { return 0; } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + _console->GetPpu()->WriteRAM(addr, value); + _ppuRegs[addr & 0x07] = value; + } +}; diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 1c55b4e3..809d8b0d 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -63,6 +63,13 @@ void MemoryManager::RegisterIODevice(IMemoryHandler *handler) InitializeMemoryHandlers(_ramWriteHandlers, handler, ranges.GetRAMWriteAddresses(), ranges.GetAllowOverride()); } +void MemoryManager::RegisterWriteHandler(IMemoryHandler* handler, uint32_t start, uint32_t end) +{ + for(uint32_t i = start; i < end; i++) { + _ramWriteHandlers[i] = handler; + } +} + void MemoryManager::UnregisterIODevice(IMemoryHandler *handler) { MemoryRanges ranges; diff --git a/Core/MemoryManager.h b/Core/MemoryManager.h index e142e38e..2d88aaac 100644 --- a/Core/MemoryManager.h +++ b/Core/MemoryManager.h @@ -40,6 +40,7 @@ class MemoryManager : public Snapshotable void Reset(bool softReset); void RegisterIODevice(IMemoryHandler *handler); + void RegisterWriteHandler(IMemoryHandler* handler, uint32_t start, uint32_t end); void UnregisterIODevice(IMemoryHandler *handler); uint8_t DebugRead(uint16_t addr, bool disableSideEffects = true); diff --git a/Core/PPU.cpp b/Core/PPU.cpp index ed155ea2..c474f290 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -82,7 +82,6 @@ void PPU::Reset() _spriteCount = 0; _secondaryOAMAddr = 0; _sprite0Visible = false; - _overflowSpriteAddr = 0; _spriteIndex = 0; _openBus = 0; memset(_openBusDecayStamp, 0, sizeof(_openBusDecayStamp)); @@ -783,7 +782,7 @@ void PPU::LoadExtraSprites() } if(loadExtraSprites) { - for(uint32_t i = _overflowSpriteAddr; i < 0x100; i += 4) { + for(uint32_t i = (_lastVisibleSpriteAddr + 4) & 0xFF; i != _firstVisibleSpriteAddr; i = (i + 4) & 0xFF) { uint8_t spriteY = _spriteRAM[i]; if(_scanline >= spriteY && _scanline < spriteY + (_flags.LargeSprites ? 16 : 8)) { LoadSprite(spriteY, _spriteRAM[i + 1], _spriteRAM[i + 2], _spriteRAM[i + 3], true); @@ -975,19 +974,22 @@ void PPU::ProcessScanline() } } } else if(_cycle >= 321 && _cycle <= 336) { - LoadTileInfo(); if(_cycle == 321) { if(IsRenderingEnabled()) { LoadExtraSprites(); _oamCopybuffer = _secondarySpriteRAM[0]; } + LoadTileInfo(); if(_scanline == -1) { _console->DebugSetLastFramePpuScroll(_state.VideoRamAddr, _state.XScroll, false); } } else if(_prevRenderingEnabled && (_cycle == 328 || _cycle == 336)) { + LoadTileInfo(); _state.LowBitShift <<= 8; _state.HighBitShift <<= 8; IncHorizontalScrolling(); + } else { + LoadTileInfo(); } } else if(_cycle == 337 || _cycle == 339) { if(IsRenderingEnabled()) { @@ -1014,12 +1016,15 @@ void PPU::ProcessSpriteEvaluation() _sprite0Added = false; _spriteInRange = false; _secondaryOAMAddr = 0; - _overflowSpriteAddr = 0; + _overflowBugCounter = 0; _oamCopyDone = false; _spriteAddrH = (_state.SpriteRamAddr >> 2) & 0x3F; _spriteAddrL = _state.SpriteRamAddr & 0x03; + + _firstVisibleSpriteAddr = _spriteAddrH * 4; + _lastVisibleSpriteAddr = _firstVisibleSpriteAddr; } else if(_cycle == 256) { _sprite0Visible = _sprite0Added; _spriteCount = (_secondaryOAMAddr >> 2); @@ -1056,6 +1061,7 @@ void PPU::ProcessSpriteEvaluation() //Done copying all 4 bytes _spriteInRange = false; _spriteAddrL = 0; + _lastVisibleSpriteAddr = _spriteAddrH * 4; _spriteAddrH = (_spriteAddrH + 1) & 0x3F; if(_spriteAddrH == 0) { _oamCopyDone = true; @@ -1073,11 +1079,6 @@ void PPU::ProcessSpriteEvaluation() _oamCopybuffer = _secondarySpriteRAM[_secondaryOAMAddr & 0x1F]; //8 sprites have been found, check next sprite for overflow + emulate PPU bug - if(_overflowSpriteAddr == 0) { - //Used to remove sprite limit - _overflowSpriteAddr = _spriteAddrH * 4; - } - if(_spriteInRange) { //Sprite is visible, consider this to be an overflow _statusFlags.SpriteOverflow = true; diff --git a/Core/PPU.h b/Core/PPU.h index 3c466f66..ea391190 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -75,7 +75,8 @@ class PPU : public IMemoryHandler, public Snapshotable uint32_t _secondaryOAMAddr; bool _sprite0Visible; - uint32_t _overflowSpriteAddr; + uint8_t _firstVisibleSpriteAddr; + uint8_t _lastVisibleSpriteAddr; uint32_t _spriteIndex; uint8_t _openBus;