MMC5: Improved accuracy (scanline counter, split screen mode, etc.)
This commit is contained in:
parent
ae4a2f29d3
commit
17c8294f5d
8 changed files with 213 additions and 138 deletions
|
@ -579,6 +579,7 @@
|
||||||
<ClInclude Include="MMC3_198.h" />
|
<ClInclude Include="MMC3_198.h" />
|
||||||
<ClInclude Include="MMC3_208.h" />
|
<ClInclude Include="MMC3_208.h" />
|
||||||
<ClInclude Include="MMC3_224.h" />
|
<ClInclude Include="MMC3_224.h" />
|
||||||
|
<ClInclude Include="MMC5MemoryHandler.h" />
|
||||||
<ClInclude Include="MovieRecorder.h" />
|
<ClInclude Include="MovieRecorder.h" />
|
||||||
<ClInclude Include="AsciiTurboFile.h" />
|
<ClInclude Include="AsciiTurboFile.h" />
|
||||||
<ClInclude Include="NESHeader.h" />
|
<ClInclude Include="NESHeader.h" />
|
||||||
|
|
|
@ -1496,6 +1496,9 @@
|
||||||
<ClInclude Include="StudyBoxLoader.h">
|
<ClInclude Include="StudyBoxLoader.h">
|
||||||
<Filter>Nes\RomLoader</Filter>
|
<Filter>Nes\RomLoader</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="MMC5MemoryHandler.h">
|
||||||
|
<Filter>Nes\Mappers\MMC</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="stdafx.cpp">
|
<ClCompile Include="stdafx.cpp">
|
||||||
|
|
286
Core/MMC5.h
286
Core/MMC5.h
|
@ -3,6 +3,7 @@
|
||||||
#include "BaseMapper.h"
|
#include "BaseMapper.h"
|
||||||
#include "PPU.h"
|
#include "PPU.h"
|
||||||
#include "MMC5Audio.h"
|
#include "MMC5Audio.h"
|
||||||
|
#include "MMC5MemoryHandler.h"
|
||||||
|
|
||||||
class MMC5 : public BaseMapper
|
class MMC5 : public BaseMapper
|
||||||
{
|
{
|
||||||
|
@ -13,6 +14,7 @@ private:
|
||||||
static constexpr uint8_t NtFillModeIndex = 3;
|
static constexpr uint8_t NtFillModeIndex = 3;
|
||||||
|
|
||||||
unique_ptr<MMC5Audio> _audio;
|
unique_ptr<MMC5Audio> _audio;
|
||||||
|
unique_ptr<MMC5MemoryHandler> _mmc5MemoryHandler;
|
||||||
|
|
||||||
uint8_t _prgRamProtect1;
|
uint8_t _prgRamProtect1;
|
||||||
uint8_t _prgRamProtect2;
|
uint8_t _prgRamProtect2;
|
||||||
|
@ -50,18 +52,19 @@ private:
|
||||||
uint8_t _chrUpperBits;
|
uint8_t _chrUpperBits;
|
||||||
uint16_t _chrBanks[12];
|
uint16_t _chrBanks[12];
|
||||||
uint16_t _lastChrReg;
|
uint16_t _lastChrReg;
|
||||||
bool _spriteFetch;
|
bool _prevChrA;
|
||||||
bool _largeSprites;
|
|
||||||
|
|
||||||
//IRQ counter related fields
|
//IRQ counter related fields
|
||||||
uint8_t _irqCounterTarget;
|
uint8_t _irqCounterTarget;
|
||||||
bool _irqEnabled;
|
bool _irqEnabled;
|
||||||
int16_t _previousScanline;
|
uint8_t _scanlineCounter;
|
||||||
uint8_t _irqCounter;
|
|
||||||
bool _irqPending;
|
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)
|
void SwitchPrgBank(uint16_t reg, uint8_t value)
|
||||||
{
|
{
|
||||||
|
@ -173,26 +176,29 @@ private:
|
||||||
|
|
||||||
void SwitchChrBank(uint16_t reg, uint8_t value)
|
void SwitchChrBank(uint16_t reg, uint8_t value)
|
||||||
{
|
{
|
||||||
_chrBanks[reg - 0x5120] = value | (_chrUpperBits << 8);
|
uint16_t newValue = value | (_chrUpperBits << 8);
|
||||||
|
if(newValue != _chrBanks[reg - 0x5120] || _lastChrReg != reg) {
|
||||||
if(_largeSprites) {
|
_chrBanks[reg - 0x5120] = newValue;
|
||||||
_lastChrReg = reg;
|
_lastChrReg = reg;
|
||||||
} else {
|
UpdateChrBanks(true);
|
||||||
//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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
//Using 8x8 sprites resets the last written to bank logic
|
||||||
_lastChrReg = 0;
|
_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) {
|
if(_chrMode == 0) {
|
||||||
SelectChrPage8x(0, _chrBanks[chrA ? 0x07 : 0x0B] << 3);
|
SelectChrPage8x(0, _chrBanks[chrA ? 0x07 : 0x0B] << 3);
|
||||||
} else if(_chrMode == 1) {
|
} else if(_chrMode == 1) {
|
||||||
|
@ -218,36 +224,14 @@ private:
|
||||||
void ProcessCpuClock() override
|
void ProcessCpuClock() override
|
||||||
{
|
{
|
||||||
_audio->Clock();
|
_audio->Clock();
|
||||||
}
|
|
||||||
|
|
||||||
virtual void NotifyVRAMAddressChange(uint16_t addr) override
|
if(_ppuIdleCounter) {
|
||||||
{
|
_ppuIdleCounter--;
|
||||||
PPU* ppu = _console->GetPpu();
|
if(_ppuIdleCounter == 0) {
|
||||||
if(ppu->GetControlFlags().BackgroundEnabled || ppu->GetControlFlags().SpritesEnabled) {
|
//"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)."
|
||||||
int16_t currentScanline = ppu->GetCurrentScanline();
|
_ppuInFrame = false;
|
||||||
if(currentScanline != _previousScanline) {
|
UpdateChrBanks(true);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
_ppuInFrame = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,11 +296,6 @@ private:
|
||||||
memset(GetNametable(NtFillModeIndex) + 32 * 30, attributeByte, 64); //Attribute table is 64 bytes
|
memset(GetNametable(NtFillModeIndex) + 32 * 30, attributeByte, 64); //Attribute table is 64 bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsSpriteFetch()
|
|
||||||
{
|
|
||||||
return _console->GetPpu()->GetCurrentCycle() >= 257 && _console->GetPpu()->GetCurrentCycle() < 321;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual uint16_t GetPRGPageSize() override { return 0x2000; }
|
virtual uint16_t GetPRGPageSize() override { return 0x2000; }
|
||||||
virtual uint16_t GetCHRPageSize() override { return 0x400; }
|
virtual uint16_t GetCHRPageSize() override { return 0x400; }
|
||||||
|
@ -369,8 +348,18 @@ protected:
|
||||||
|
|
||||||
virtual void InitMapper() override
|
virtual void InitMapper() override
|
||||||
{
|
{
|
||||||
|
AddRegisterRange(0xFFFA, 0xFFFB, MemoryOperation::Read);
|
||||||
|
|
||||||
_audio.reset(new MMC5Audio(_console));
|
_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;
|
_chrMode = 0;
|
||||||
_prgRamProtect1 = 0;
|
_prgRamProtect1 = 0;
|
||||||
_prgRamProtect2 = 0;
|
_prgRamProtect2 = 0;
|
||||||
|
@ -388,8 +377,6 @@ protected:
|
||||||
_chrUpperBits = 0;
|
_chrUpperBits = 0;
|
||||||
memset(_chrBanks, 0, sizeof(_chrBanks));
|
memset(_chrBanks, 0, sizeof(_chrBanks));
|
||||||
_lastChrReg = 0;
|
_lastChrReg = 0;
|
||||||
_spriteFetch = false;
|
|
||||||
_largeSprites = false;
|
|
||||||
|
|
||||||
_exAttrLastFetchCounter = 0;
|
_exAttrLastFetchCounter = 0;
|
||||||
_exAttributeLastNametableFetch = 0;
|
_exAttributeLastNametableFetch = 0;
|
||||||
|
@ -397,12 +384,10 @@ protected:
|
||||||
|
|
||||||
_irqPending = false;
|
_irqPending = false;
|
||||||
_irqCounterTarget = 0;
|
_irqCounterTarget = 0;
|
||||||
_irqCounter = 0;
|
_scanlineCounter = 0;
|
||||||
_irqEnabled = false;
|
_irqEnabled = false;
|
||||||
_previousScanline = -1;
|
|
||||||
_ppuInFrame = false;
|
_ppuInFrame = false;
|
||||||
|
_needInFrame = false;
|
||||||
_lastVramOperationType = MemoryOperationType::Read;
|
|
||||||
|
|
||||||
_splitInSplitRegion = false;
|
_splitInSplitRegion = false;
|
||||||
_splitVerticalScroll = 0;
|
_splitVerticalScroll = 0;
|
||||||
|
@ -418,12 +403,23 @@ protected:
|
||||||
|
|
||||||
//"Games seem to expect $5117 to be $FF on powerup (last PRG page swapped in)."
|
//"Games seem to expect $5117 to be $FF on powerup (last PRG page swapped in)."
|
||||||
WriteRegister(0x5117, 0xFF);
|
WriteRegister(0x5117, 0xFF);
|
||||||
|
|
||||||
|
UpdateChrBanks(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset(bool softReset) override
|
||||||
|
{
|
||||||
|
_console->GetMemoryManager()->RegisterWriteHandler(_mmc5MemoryHandler.get(), 0x2000, 0x2007);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamState(bool saving) override
|
void StreamState(bool saving) override
|
||||||
{
|
{
|
||||||
BaseMapper::StreamState(saving);
|
BaseMapper::StreamState(saving);
|
||||||
|
|
||||||
|
int16_t unusedPreviousScanline = 0;
|
||||||
|
bool unusedSpriteFetch = false;
|
||||||
|
bool unusedLargeSprites = false;
|
||||||
|
|
||||||
ArrayInfo<uint8_t> prgBanks = { _prgBanks, 5 };
|
ArrayInfo<uint8_t> prgBanks = { _prgBanks, 5 };
|
||||||
ArrayInfo<uint16_t> chrBanks = { _chrBanks, 12 };
|
ArrayInfo<uint16_t> chrBanks = { _chrBanks, 12 };
|
||||||
SnapshotInfo audio{ _audio.get() };
|
SnapshotInfo audio{ _audio.get() };
|
||||||
|
@ -431,8 +427,8 @@ protected:
|
||||||
_verticalSplitDelimiterTile, _verticalSplitScroll, _verticalSplitBank, _multiplierValue1, _multiplierValue2,
|
_verticalSplitDelimiterTile, _verticalSplitScroll, _verticalSplitBank, _multiplierValue1, _multiplierValue2,
|
||||||
_nametableMapping, _extendedRamMode, _exAttributeLastNametableFetch, _exAttrLastFetchCounter, _exAttrSelectedChrBank,
|
_nametableMapping, _extendedRamMode, _exAttributeLastNametableFetch, _exAttrLastFetchCounter, _exAttrSelectedChrBank,
|
||||||
_prgMode, prgBanks, _chrMode, _chrUpperBits, chrBanks, _lastChrReg,
|
_prgMode, prgBanks, _chrMode, _chrUpperBits, chrBanks, _lastChrReg,
|
||||||
_spriteFetch, _largeSprites, _irqCounterTarget, _irqEnabled, _previousScanline, _irqCounter, _irqPending, _ppuInFrame, audio,
|
unusedSpriteFetch, unusedLargeSprites, _irqCounterTarget, _irqEnabled, unusedPreviousScanline, _scanlineCounter, _irqPending, _ppuInFrame, audio,
|
||||||
_splitInSplitRegion, _splitVerticalScroll, _splitTile, _splitTileNumber, _lastVramOperationType);
|
_splitInSplitRegion, _splitVerticalScroll, _splitTile, _splitTileNumber, _needInFrame);
|
||||||
|
|
||||||
if(!saving) {
|
if(!saving) {
|
||||||
UpdatePrgBanks();
|
UpdatePrgBanks();
|
||||||
|
@ -442,103 +438,127 @@ protected:
|
||||||
|
|
||||||
virtual void WriteRAM(uint16_t addr, uint8_t value) override
|
virtual void WriteRAM(uint16_t addr, uint8_t value) override
|
||||||
{
|
{
|
||||||
if(addr >= 0x5C00 && addr <= 0x5FFF && _extendedRamMode <= 1) {
|
if(addr >= 0x5C00 && addr <= 0x5FFF && _extendedRamMode <= 1 && !_ppuInFrame) {
|
||||||
PPUControlFlags flags = _console->GetPpu()->GetControlFlags();
|
//Expansion RAM ($5C00-$5FFF, read/write)
|
||||||
if(!flags.BackgroundEnabled && !flags.SpritesEnabled) {
|
//Mode 0/1 - Not readable (returns open bus), can only be written while the PPU is rendering (otherwise, 0 is written)
|
||||||
//Expansion RAM ($5C00-$5FFF, read/write)
|
value = 0;
|
||||||
//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);
|
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
|
virtual uint8_t MapperReadVRAM(uint16_t addr, MemoryOperationType memoryOperationType) override
|
||||||
{
|
{
|
||||||
PPU* ppu = _console->GetPpu();
|
bool isNtFetch = addr >= 0x2000 && addr <= 0x2FFF && (addr & 0x3FF) < 0x3C0;
|
||||||
if(_spriteFetch != IsSpriteFetch() || _largeSprites != ppu->GetControlFlags().LargeSprites || _lastVramOperationType != memoryOperationType) {
|
if(isNtFetch) {
|
||||||
_lastVramOperationType = memoryOperationType;
|
//Nametable data, not an attribute fetch
|
||||||
_spriteFetch = IsSpriteFetch();
|
_splitInSplitRegion = false;
|
||||||
_largeSprites = ppu->GetControlFlags().LargeSprites;
|
_splitTileNumber++;
|
||||||
UpdateChrBanks();
|
|
||||||
|
if(_ppuInFrame) {
|
||||||
|
UpdateChrBanks(false);
|
||||||
|
} else if(_needInFrame) {
|
||||||
|
_needInFrame = false;
|
||||||
|
_ppuInFrame = true;
|
||||||
|
UpdateChrBanks(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
DetectScanlineStart(addr);
|
||||||
|
|
||||||
if(_extendedRamMode <= 1 && _verticalSplitEnabled && memoryOperationType == MemoryOperationType::PpuRenderingRead) {
|
_ppuIdleCounter = 3;
|
||||||
uint32_t cycle = ppu->GetCurrentCycle();
|
_lastPpuReadAddr = addr;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if((cycle - 1) % 8 == 0 && cycle != 337) {
|
if(_extendedRamMode <= 1 && _ppuInFrame) {
|
||||||
_splitTileNumber++;
|
if(_verticalSplitEnabled) {
|
||||||
}
|
uint16_t verticalSplitScroll = (_verticalSplitScroll + _scanlineCounter) % 240;
|
||||||
|
|
||||||
if(cycle < 256 || cycle >= 321) {
|
|
||||||
if(addr >= 0x2000) {
|
if(addr >= 0x2000) {
|
||||||
if((addr & 0x3FF) < 0x3C0) {
|
if(isNtFetch) {
|
||||||
if((_verticalSplitRightSide && _splitTileNumber >= _verticalSplitDelimiterTile) || (!_verticalSplitRightSide && _splitTileNumber < _verticalSplitDelimiterTile)) {
|
uint8_t tileNumber = (_splitTileNumber + 2) % 42;
|
||||||
//Split region
|
if(tileNumber <= 32 && (_verticalSplitRightSide && tileNumber >= _verticalSplitDelimiterTile) || (!_verticalSplitRightSide && tileNumber < _verticalSplitDelimiterTile)) {
|
||||||
|
//Split region (for next 3 fetches, attribute + 2x tile data)
|
||||||
_splitInSplitRegion = true;
|
_splitInSplitRegion = true;
|
||||||
_splitTile = ((_splitVerticalScroll & 0xF8) << 2) | _splitTileNumber;
|
_splitTile = ((verticalSplitScroll & 0xF8) << 2) | tileNumber;
|
||||||
return InternalReadRam(0x5C00 + _splitTile);
|
return InternalReadRam(0x5C00 + _splitTile);
|
||||||
} else {
|
} 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;
|
_splitInSplitRegion = false;
|
||||||
}
|
}
|
||||||
} else if(_splitInSplitRegion) {
|
} else if(_splitInSplitRegion) {
|
||||||
return InternalReadRam(0x5FC0 + ((_splitTile >> 4) & ~0x07) + ((_splitTile & 0x3F) >> 2));
|
return InternalReadRam(0x5FC0 | ((_splitTile & 0x380) >> 4) | ((_splitTile & 0x1F) >> 2));
|
||||||
}
|
}
|
||||||
} else if(_splitInSplitRegion) {
|
} 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) {
|
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."
|
//"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"
|
//"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)
|
//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) {
|
if(isNtFetch) {
|
||||||
//Nametable fetches
|
//Nametable fetches
|
||||||
_exAttributeLastNametableFetch = addr & 0x03FF;
|
_exAttributeLastNametableFetch = addr & 0x03FF;
|
||||||
_exAttrLastFetchCounter = 3;
|
_exAttrLastFetchCounter = 3;
|
||||||
} else if(_exAttrLastFetchCounter > 0) {
|
} else if(_exAttrLastFetchCounter > 0) {
|
||||||
//Attribute fetches
|
//Attribute fetches
|
||||||
_exAttrLastFetchCounter--;
|
_exAttrLastFetchCounter--;
|
||||||
switch(_exAttrLastFetchCounter) {
|
switch(_exAttrLastFetchCounter) {
|
||||||
case 2:
|
case 2:
|
||||||
{
|
{
|
||||||
//PPU palette fetch
|
//PPU palette fetch
|
||||||
//Check work ram (expansion ram) to see which tile/palette to use
|
//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
|
//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);
|
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."
|
//"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);
|
_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
|
//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;
|
uint8_t palette = (value & 0xC0) >> 6;
|
||||||
return palette | palette << 2 | palette << 4 | palette << 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
|
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||||
|
@ -554,7 +574,7 @@ protected:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x5100: _prgMode = value & 0x03; UpdatePrgBanks(); 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 0x5102: _prgRamProtect1 = value & 0x03; UpdatePrgBanks(); break;
|
||||||
case 0x5103: _prgRamProtect2 = value & 0x03; UpdatePrgBanks(); break;
|
case 0x5103: _prgRamProtect2 = value & 0x03; UpdatePrgBanks(); break;
|
||||||
case 0x5104: SetExtendedRamMode(value & 0x03); break;
|
case 0x5104: SetExtendedRamMode(value & 0x03); break;
|
||||||
|
@ -603,6 +623,16 @@ protected:
|
||||||
|
|
||||||
case 0x5205: return (_multiplierValue1*_multiplierValue2) & 0xFF;
|
case 0x5205: return (_multiplierValue1*_multiplierValue2) & 0xFF;
|
||||||
case 0x5206: return (_multiplierValue1*_multiplierValue2) >> 8;
|
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();
|
return _console->GetMemoryManager()->GetOpenBus();
|
||||||
|
|
31
Core/MMC5MemoryHandler.h
Normal file
31
Core/MMC5MemoryHandler.h
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -63,6 +63,13 @@ void MemoryManager::RegisterIODevice(IMemoryHandler *handler)
|
||||||
InitializeMemoryHandlers(_ramWriteHandlers, handler, ranges.GetRAMWriteAddresses(), ranges.GetAllowOverride());
|
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)
|
void MemoryManager::UnregisterIODevice(IMemoryHandler *handler)
|
||||||
{
|
{
|
||||||
MemoryRanges ranges;
|
MemoryRanges ranges;
|
||||||
|
|
|
@ -40,6 +40,7 @@ class MemoryManager : public Snapshotable
|
||||||
|
|
||||||
void Reset(bool softReset);
|
void Reset(bool softReset);
|
||||||
void RegisterIODevice(IMemoryHandler *handler);
|
void RegisterIODevice(IMemoryHandler *handler);
|
||||||
|
void RegisterWriteHandler(IMemoryHandler* handler, uint32_t start, uint32_t end);
|
||||||
void UnregisterIODevice(IMemoryHandler *handler);
|
void UnregisterIODevice(IMemoryHandler *handler);
|
||||||
|
|
||||||
uint8_t DebugRead(uint16_t addr, bool disableSideEffects = true);
|
uint8_t DebugRead(uint16_t addr, bool disableSideEffects = true);
|
||||||
|
|
19
Core/PPU.cpp
19
Core/PPU.cpp
|
@ -82,7 +82,6 @@ void PPU::Reset()
|
||||||
_spriteCount = 0;
|
_spriteCount = 0;
|
||||||
_secondaryOAMAddr = 0;
|
_secondaryOAMAddr = 0;
|
||||||
_sprite0Visible = false;
|
_sprite0Visible = false;
|
||||||
_overflowSpriteAddr = 0;
|
|
||||||
_spriteIndex = 0;
|
_spriteIndex = 0;
|
||||||
_openBus = 0;
|
_openBus = 0;
|
||||||
memset(_openBusDecayStamp, 0, sizeof(_openBusDecayStamp));
|
memset(_openBusDecayStamp, 0, sizeof(_openBusDecayStamp));
|
||||||
|
@ -783,7 +782,7 @@ void PPU::LoadExtraSprites()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(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];
|
uint8_t spriteY = _spriteRAM[i];
|
||||||
if(_scanline >= spriteY && _scanline < spriteY + (_flags.LargeSprites ? 16 : 8)) {
|
if(_scanline >= spriteY && _scanline < spriteY + (_flags.LargeSprites ? 16 : 8)) {
|
||||||
LoadSprite(spriteY, _spriteRAM[i + 1], _spriteRAM[i + 2], _spriteRAM[i + 3], true);
|
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) {
|
} else if(_cycle >= 321 && _cycle <= 336) {
|
||||||
LoadTileInfo();
|
|
||||||
if(_cycle == 321) {
|
if(_cycle == 321) {
|
||||||
if(IsRenderingEnabled()) {
|
if(IsRenderingEnabled()) {
|
||||||
LoadExtraSprites();
|
LoadExtraSprites();
|
||||||
_oamCopybuffer = _secondarySpriteRAM[0];
|
_oamCopybuffer = _secondarySpriteRAM[0];
|
||||||
}
|
}
|
||||||
|
LoadTileInfo();
|
||||||
if(_scanline == -1) {
|
if(_scanline == -1) {
|
||||||
_console->DebugSetLastFramePpuScroll(_state.VideoRamAddr, _state.XScroll, false);
|
_console->DebugSetLastFramePpuScroll(_state.VideoRamAddr, _state.XScroll, false);
|
||||||
}
|
}
|
||||||
} else if(_prevRenderingEnabled && (_cycle == 328 || _cycle == 336)) {
|
} else if(_prevRenderingEnabled && (_cycle == 328 || _cycle == 336)) {
|
||||||
|
LoadTileInfo();
|
||||||
_state.LowBitShift <<= 8;
|
_state.LowBitShift <<= 8;
|
||||||
_state.HighBitShift <<= 8;
|
_state.HighBitShift <<= 8;
|
||||||
IncHorizontalScrolling();
|
IncHorizontalScrolling();
|
||||||
|
} else {
|
||||||
|
LoadTileInfo();
|
||||||
}
|
}
|
||||||
} else if(_cycle == 337 || _cycle == 339) {
|
} else if(_cycle == 337 || _cycle == 339) {
|
||||||
if(IsRenderingEnabled()) {
|
if(IsRenderingEnabled()) {
|
||||||
|
@ -1014,12 +1016,15 @@ void PPU::ProcessSpriteEvaluation()
|
||||||
_sprite0Added = false;
|
_sprite0Added = false;
|
||||||
_spriteInRange = false;
|
_spriteInRange = false;
|
||||||
_secondaryOAMAddr = 0;
|
_secondaryOAMAddr = 0;
|
||||||
_overflowSpriteAddr = 0;
|
|
||||||
_overflowBugCounter = 0;
|
_overflowBugCounter = 0;
|
||||||
|
|
||||||
_oamCopyDone = false;
|
_oamCopyDone = false;
|
||||||
_spriteAddrH = (_state.SpriteRamAddr >> 2) & 0x3F;
|
_spriteAddrH = (_state.SpriteRamAddr >> 2) & 0x3F;
|
||||||
_spriteAddrL = _state.SpriteRamAddr & 0x03;
|
_spriteAddrL = _state.SpriteRamAddr & 0x03;
|
||||||
|
|
||||||
|
_firstVisibleSpriteAddr = _spriteAddrH * 4;
|
||||||
|
_lastVisibleSpriteAddr = _firstVisibleSpriteAddr;
|
||||||
} else if(_cycle == 256) {
|
} else if(_cycle == 256) {
|
||||||
_sprite0Visible = _sprite0Added;
|
_sprite0Visible = _sprite0Added;
|
||||||
_spriteCount = (_secondaryOAMAddr >> 2);
|
_spriteCount = (_secondaryOAMAddr >> 2);
|
||||||
|
@ -1056,6 +1061,7 @@ void PPU::ProcessSpriteEvaluation()
|
||||||
//Done copying all 4 bytes
|
//Done copying all 4 bytes
|
||||||
_spriteInRange = false;
|
_spriteInRange = false;
|
||||||
_spriteAddrL = 0;
|
_spriteAddrL = 0;
|
||||||
|
_lastVisibleSpriteAddr = _spriteAddrH * 4;
|
||||||
_spriteAddrH = (_spriteAddrH + 1) & 0x3F;
|
_spriteAddrH = (_spriteAddrH + 1) & 0x3F;
|
||||||
if(_spriteAddrH == 0) {
|
if(_spriteAddrH == 0) {
|
||||||
_oamCopyDone = true;
|
_oamCopyDone = true;
|
||||||
|
@ -1073,11 +1079,6 @@ void PPU::ProcessSpriteEvaluation()
|
||||||
_oamCopybuffer = _secondarySpriteRAM[_secondaryOAMAddr & 0x1F];
|
_oamCopybuffer = _secondarySpriteRAM[_secondaryOAMAddr & 0x1F];
|
||||||
|
|
||||||
//8 sprites have been found, check next sprite for overflow + emulate PPU bug
|
//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) {
|
if(_spriteInRange) {
|
||||||
//Sprite is visible, consider this to be an overflow
|
//Sprite is visible, consider this to be an overflow
|
||||||
_statusFlags.SpriteOverflow = true;
|
_statusFlags.SpriteOverflow = true;
|
||||||
|
|
|
@ -75,7 +75,8 @@ class PPU : public IMemoryHandler, public Snapshotable
|
||||||
uint32_t _secondaryOAMAddr;
|
uint32_t _secondaryOAMAddr;
|
||||||
bool _sprite0Visible;
|
bool _sprite0Visible;
|
||||||
|
|
||||||
uint32_t _overflowSpriteAddr;
|
uint8_t _firstVisibleSpriteAddr;
|
||||||
|
uint8_t _lastVisibleSpriteAddr;
|
||||||
uint32_t _spriteIndex;
|
uint32_t _spriteIndex;
|
||||||
|
|
||||||
uint8_t _openBus;
|
uint8_t _openBus;
|
||||||
|
|
Loading…
Add table
Reference in a new issue