Merge pull request #37 from vailkyte/backport2

Backport Mesen2 fixes
This commit is contained in:
LibretroAdmin 2023-05-21 04:24:57 +02:00 committed by GitHub
commit d25d60fc19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 181 additions and 97 deletions

View File

@ -154,18 +154,13 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
uint32_t pageCount = 0;
uint32_t pageSize = 0;
uint8_t defaultAccessType = MemoryAccessType::Read;
if(type == ChrMemoryType::Default) {
type = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
}
switch(type) {
case ChrMemoryType::Default:
pageSize = InternalGetChrPageSize();
if(pageSize == 0) {
return;
}
pageCount = GetCHRPageCount();
if(_onlyChrRam) {
defaultAccessType |= MemoryAccessType::Write;
}
break;
case ChrMemoryType::ChrRom:
pageSize = InternalGetChrPageSize();
if(pageSize == 0)
@ -194,7 +189,6 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
pageNumber = pageNumber % pageCount;
if((uint16_t)(endAddr - startAddr) >= pageSize) {
uint32_t addr = startAddr;
while(addr <= endAddr - pageSize + 1) {
SetPpuMemoryMapping(addr, addr + pageSize - 1, type, pageNumber * pageSize, accessType);
@ -209,17 +203,22 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, ChrMemoryType type, uint32_t sourceOffset, int8_t accessType)
{
uint8_t* sourceMemory = nullptr;
if(type == ChrMemoryType::Default) {
type = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
}
switch(type) {
default:
case ChrMemoryType::Default:
sourceMemory = _onlyChrRam ? _chrRam : _chrRom;
type = _onlyChrRam ? ChrMemoryType::ChrRam : ChrMemoryType::ChrRom;
case ChrMemoryType::ChrRom:
sourceMemory = _chrRom;
break;
case ChrMemoryType::ChrRom: sourceMemory = _chrRom; break;
case ChrMemoryType::ChrRam: sourceMemory = _chrRam; break;
case ChrMemoryType::NametableRam: sourceMemory = _nametableRam; break;
}
int firstSlot = startAddr >> 8;
int slotCount = (endAddr - startAddr + 1) >> 8;
for(int i = 0; i < slotCount; i++) {
@ -323,6 +322,9 @@ void BaseMapper::SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memor
if(memoryType == ChrMemoryType::NametableRam) {
pageSize = BaseMapper::NametableSize;
} else {
if(memoryType == ChrMemoryType::Default) {
memoryType = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
}
pageSize = memoryType == ChrMemoryType::ChrRam ? InternalGetChrRamPageSize() : InternalGetChrPageSize();
}
@ -415,7 +417,7 @@ bool BaseMapper::HasChrRam()
bool BaseMapper::HasChrRom()
{
return !_onlyChrRam;
return _chrRomSize > 0;
}
void BaseMapper::AddRegisterRange(uint16_t startAddr, uint16_t endAddr, MemoryOperation operation)
@ -516,6 +518,7 @@ void BaseMapper::Initialize(RomData &romData)
_prgRom = new uint8_t[_prgSize];
_chrRom = new uint8_t[_chrRomSize];
memcpy(_prgRom, romData.PrgRom.data(), _prgSize);
if(_chrRomSize > 0) {
memcpy(_chrRom, romData.ChrRom.data(), _chrRomSize);
@ -554,12 +557,10 @@ void BaseMapper::Initialize(RomData &romData)
if(_chrRomSize == 0) {
//Assume there is CHR RAM if no CHR ROM exists
_onlyChrRam = true;
InitializeChrRam(romData.ChrRamSize);
//Map CHR RAM to 0x0000-0x1FFF by default when no CHR ROM exists
SetPpuMemoryMapping(0x0000, 0x1FFF, 0, ChrMemoryType::ChrRam);
_chrRomSize = _chrRamSize;
} else if(romData.ChrRamSize >= 0) {
InitializeChrRam(romData.ChrRamSize);
} else if(GetChrRamSize()) {
@ -852,7 +853,7 @@ uint32_t BaseMapper::GetMemorySize(DebugMemoryType type)
{
switch(type) {
default: return 0;
case DebugMemoryType::ChrRom: return _onlyChrRam ? 0 : _chrRomSize;
case DebugMemoryType::ChrRom: return _chrRomSize;
case DebugMemoryType::ChrRam: return _chrRamSize;
case DebugMemoryType::NametableRam: return _nametableCount * BaseMapper::NametableSize;
case DebugMemoryType::SaveRam: return _saveRamSize;
@ -1016,7 +1017,7 @@ int32_t BaseMapper::ToAbsoluteChrRomAddress(uint16_t addr)
int32_t BaseMapper::FromAbsoluteChrAddress(uint32_t addr)
{
uint8_t* ptrAddress = (_onlyChrRam ? _chrRam : _chrRom) + (addr & 0x3FFF);
uint8_t* ptrAddress = _chrRom + (addr & 0x3FFF);
for(int i = 0; i < 64; i++) {
uint8_t* pageAddress = _chrPages[i];
@ -1095,14 +1096,14 @@ CartridgeState BaseMapper::GetState()
state.HasBattery = _romInfo.HasBattery;
state.PrgRomSize = _prgSize;
state.ChrRomSize = _onlyChrRam ? 0 : _chrRomSize;
state.ChrRomSize = _chrRomSize;
state.ChrRamSize = _chrRamSize;
state.PrgPageCount = GetPRGPageCount();
state.PrgPageSize = InternalGetPrgPageSize();
state.ChrPageCount = GetCHRPageCount();
state.ChrPageSize = InternalGetChrPageSize();
state.ChrRamPageSize = _onlyChrRam ? InternalGetChrPageSize() : InternalGetChrRamPageSize();
state.ChrRamPageSize = InternalGetChrRamPageSize();
for(int i = 0; i < 0x100; i++) {
state.PrgMemoryOffset[i] = _prgMemoryOffset[i];
state.PrgType[i] = _prgMemoryType[i];
@ -1157,20 +1158,16 @@ void BaseMapper::GetRomFileData(vector<uint8_t> &out, bool asIpsFile, uint8_t* h
vector<uint8_t> BaseMapper::GetPrgChrCopy()
{
vector<uint8_t> data;
data.resize(_prgSize + (_onlyChrRam ? 0 : _chrRomSize));
data.resize(_prgSize + _chrRomSize);
memcpy(data.data(), _prgRom, _prgSize);
if(!_onlyChrRam) {
memcpy(data.data() + _prgSize, _chrRom, _chrRomSize);
}
memcpy(data.data() + _prgSize, _chrRom, _chrRomSize);
return data;
}
void BaseMapper::RestorePrgChrBackup(vector<uint8_t> &backupData)
{
memcpy(_prgRom, backupData.data(), _prgSize);
if(!_onlyChrRam) {
memcpy(_chrRom, backupData.data() + _prgSize, _chrRomSize);
}
memcpy(_chrRom, backupData.data() + _prgSize, _chrRomSize);
}
void BaseMapper::RevertPrgChrChanges()
@ -1198,8 +1195,6 @@ void BaseMapper::CopyPrgChrRom(shared_ptr<BaseMapper> mapper)
{
if(_prgSize == mapper->_prgSize && _chrRomSize == mapper->_chrRomSize) {
memcpy(_prgRom, mapper->_prgRom, _prgSize);
if(!_onlyChrRam) {
memcpy(_chrRom, mapper->_chrRom, _chrRomSize);
}
memcpy(_chrRom, mapper->_chrRom, _chrRomSize);
}
}

View File

@ -26,7 +26,6 @@ private:
uint8_t *_nametableRam = nullptr;
uint8_t _nametableCount = 2;
bool _onlyChrRam = false;
bool _hasBusConflicts = false;
bool _allowRegisterRead = false;
@ -78,7 +77,7 @@ protected:
bool IsNes20();
virtual uint16_t GetChrRamPageSize() { return 0x2000; }
virtual uint16_t GetChrRamPageSize() { return GetCHRPageSize(); }
//Save ram is battery backed and saved to disk
virtual uint32_t GetSaveRamSize() { return HasBattery() ? 0x2000 : 0; }

View File

@ -6,6 +6,7 @@
#include "APU.h"
#include "DeltaModulationChannel.h"
#include "MemoryManager.h"
#include "ControlManager.h"
#include "Console.h"
CPU::CPU(shared_ptr<Console> console)
@ -306,14 +307,26 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
return;
}
uint16_t prevReadAddress = readAddress;
bool enableInternalRegReads = (readAddress & 0xFFE0) == 0x4000;
bool skipFirstInputClock = false;
if(enableInternalRegReads && _dmcDmaRunning && (readAddress == 0x4016 || readAddress == 0x4017)) {
uint16_t dmcAddress = _console->GetApu()->GetDmcReadAddress();
if((dmcAddress & 0x1F) == (readAddress & 0x1F)) {
//DMC will cause a read on the same address as the CPU was reading from
//This will hide the reads from the controllers because /OE will be active the whole time
skipFirstInputClock = true;
}
}
//On PAL, the dummy/idle reads done by the DMA don't appear to be done on the
//address that the CPU was about to read. This prevents the 2+x reads on registers issues.
//The exact specifics of where the CPU reads instead aren't known yet - so just disable read side-effects entirely on PAL
bool allowReadSideEffects = _console->GetModel() != NesModel::PAL;
bool isNtscInputBehavior = _console->GetModel() != NesModel::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)"
StartCpuCycle(true);
if(allowReadSideEffects) {
if(isNtscInputBehavior && !skipFirstInputClock) {
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
}
EndCpuCycle(true);
@ -327,7 +340,7 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
//On NES (or AV Famicom), only the first dummy/idle read causes side effects (e.g only a single bit is lost)
ConsoleType type = _console->GetSettings()->GetConsoleType();
bool isNesBehavior = type != ConsoleType::Famicom;
bool skipDummyReads = !allowReadSideEffects || (isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017));
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
@ -345,14 +358,14 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
if(_dmcDmaRunning && !_needHalt && !_needDummyRead) {
//DMC DMA is ready to read a byte (both halt and dummy read cycles were performed before this)
processCycle();
readValue = _memoryManager->Read(_console->GetApu()->GetDmcReadAddress(), MemoryOperationType::DmcRead);
readValue = ProcessDmaRead(_console->GetApu()->GetDmcReadAddress(), prevReadAddress, enableInternalRegReads, isNesBehavior);
EndCpuCycle(true);
_console->GetApu()->SetDmcReadBuffer(readValue);
_dmcDmaRunning = false;
} else if(_spriteDmaTransfer) {
//DMC DMA is not running, or not ready, run sprite DMA
processCycle();
readValue = _memoryManager->Read(_spriteDmaOffset * 0x100 + spriteReadAddr);
readValue = ProcessDmaRead(_spriteDmaOffset * 0x100 + spriteReadAddr, prevReadAddress, enableInternalRegReads, isNesBehavior);
EndCpuCycle(true);
spriteReadAddr++;
spriteDmaCounter++;
@ -361,7 +374,7 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
assert(_needHalt || _needDummyRead);
processCycle();
if(!skipDummyReads) {
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
ProcessDmaRead(readAddress, prevReadAddress, enableInternalRegReads, isNesBehavior);
}
EndCpuCycle(true);
}
@ -379,7 +392,7 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
//Align to read cycle before starting sprite DMA (or align to perform DMC read)
processCycle();
if(!skipDummyReads) {
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
ProcessDmaRead(readAddress, prevReadAddress, enableInternalRegReads, isNesBehavior);
}
EndCpuCycle(true);
}
@ -387,6 +400,77 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
}
}
uint8_t CPU::ProcessDmaRead(uint16_t addr, uint16_t& prevReadAddress, bool enableInternalRegReads, bool isNesBehavior)
{
//This is to reproduce a CPU bug that can occur during DMA which can cause the 2A03 to read from
//its internal registers (4015, 4016, 4017) at the same time as the DMA unit reads a byte from
//the bus. This bug occurs if the CPU is halted while it's reading a value in the $4000-$401F range.
//
//This has a number of side effects:
// -It can cause a read of $4015 to occur without the program's knowledge, which would clear the frame counter's IRQ flag
// -It can cause additional bit deletions while reading the input (e.g more than the DMC glitch usually causes)
// -It can also *prevent* bit deletions from occurring at all in another scenario
// -It can replace/corrupt the byte that the DMA is reading, causing DMC to play the wrong sample
uint8_t val;
if(!enableInternalRegReads) {
if(addr >= 0x4000 && addr <= 0x401F) {
//Nothing will respond on $4000-$401F on the external bus - return open bus value
val = _memoryManager->GetOpenBus();
} else {
val = _memoryManager->Read(addr, MemoryOperationType::DmcRead);
}
prevReadAddress = addr;
return val;
} else {
//This glitch causes the CPU to read from the internal APU/Input registers
//regardless of the address the DMA unit is trying to read
uint16_t internalAddr = 0x4000 | (addr & 0x1F);
bool isSameAddress = internalAddr == addr;
switch(internalAddr) {
case 0x4015:
val = _memoryManager->Read(internalAddr, MemoryOperationType::DmcRead);
if(!isSameAddress) {
//Also trigger a read from the actual address the CPU was supposed to read from (external bus)
_memoryManager->Read(addr, MemoryOperationType::DmcRead);
}
break;
case 0x4016:
case 0x4017:
if(_console->GetModel() == NesModel::PAL || (isNesBehavior && prevReadAddress == internalAddr)) {
//Reading from the same input register twice in a row, skip the read entirely to avoid
//triggering a bit loss from the read, since the controller won't react to this read
//Return the same value as the last read, instead
//On PAL, the behavior is unknown - for now, don't cause any bit deletions
val = _memoryManager->GetOpenBus();
} else {
val = _memoryManager->Read(internalAddr, MemoryOperationType::DmcRead);
}
if(!isSameAddress) {
//The DMA unit is reading from a different address, read from it too (external bus)
uint8_t obMask = _console->GetControlManager()->GetOpenBusMask(internalAddr - 0x4016);
uint8_t externalValue = _memoryManager->Read(addr, MemoryOperationType::DmcRead);
//Merge values, keep the external value for all open bus pins on the 4016/4017 port
//AND all other bits together (bus conflict)
val = (externalValue & obMask) | ((val & ~obMask) & (externalValue & ~obMask));
}
break;
default:
val = _memoryManager->Read(addr, MemoryOperationType::DmcRead);
break;
}
prevReadAddress = internalAddr;
return val;
}
}
void CPU::RunDMATransfer(uint8_t offsetValue)
{
_spriteDmaTransfer = true;

View File

@ -80,6 +80,7 @@ private:
__forceinline void StartCpuCycle(bool forRead);
__forceinline void ProcessPendingDma(uint16_t readAddress);
uint8_t ProcessDmaRead(uint16_t addr, uint16_t& prevReadAddress, bool enableInternalRegReads, bool isNesBehavior);
__forceinline uint16_t FetchOperand();
__forceinline void EndCpuCycle(bool forRead);
void IRQ();

View File

@ -39,12 +39,13 @@ protected:
virtual void StreamState(bool saving) override;
virtual ControllerType GetControllerType(uint8_t port);
virtual void RemapControllerButtons();
virtual uint8_t GetOpenBusMask(uint8_t port);
public:
ControlManager(std::shared_ptr<Console> console, std::shared_ptr<BaseControlDevice> systemActionManager, std::shared_ptr<BaseControlDevice> mapperControlDevice);
virtual ~ControlManager();
virtual uint8_t GetOpenBusMask(uint8_t port);
virtual void UpdateControlDevices();
void UpdateInputState();

View File

@ -983,19 +983,16 @@ void PPU::ProcessScanline()
}
}
} else if(_cycle >= 321 && _cycle <= 336) {
LoadTileInfo();
if(_cycle == 321) {
if(IsRenderingEnabled()) {
LoadExtraSprites();
_oamCopybuffer = _secondarySpriteRAM[0];
}
LoadTileInfo();
} 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()) {
@ -1247,52 +1244,8 @@ void PPU::ProcessOamCorruption()
void PPU::Exec()
{
if(_cycle > 339) {
_cycle = 0;
if(++_scanline > _vblankEnd) {
_lastUpdatedPixel = -1;
_scanline = -1;
//Force prerender scanline sprite fetches to load the dummy $FF tiles (fixes shaking in Ninja Gaiden 3 stage 1 after beating boss)
_spriteCount = 0;
if(_renderingEnabled) {
ProcessOamCorruption();
}
UpdateMinimumDrawCycles();
}
UpdateApuStatus();
if(_scanline == _settings->GetInputPollScanline()) {
_console->GetControlManager()->UpdateInputState();
}
//Cycle = 0
if(_scanline < 240) {
if(_scanline == -1) {
_statusFlags.SpriteOverflow = false;
_statusFlags.Sprite0Hit = false;
//Switch to alternate output buffer (VideoDecoder may still be decoding the last frame buffer)
_currentOutputBuffer = (_currentOutputBuffer == _outputBuffers[0]) ? _outputBuffers[1] : _outputBuffers[0];
} else if(_prevRenderingEnabled) {
if(_scanline > 0 || (!(_frameCount & 0x01) || _nesModel != NesModel::NTSC || _settings->GetPpuModel() != PpuModel::Ppu2C02)) {
//Set bus address to the tile address calculated from the unused NT fetches at the end of the previous scanline
//This doesn't happen on scanline 0 if the last dot of the previous frame was skipped
SetBusAddress((_nextTile.TileAddr << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr);
}
}
} else if(_scanline == 240) {
//At the start of vblank, the bus address is set back to VideoRamAddr.
//According to Visual NES, this occurs on scanline 240, cycle 1, but is done here on cycle for performance reasons
SetBusAddress(_state.VideoRamAddr);
SendFrame();
_frameCount++;
}
} else {
//Cycle > 0
if(_cycle < 340) {
//Process cycles 1 to 340
_cycle++;
if(_scanline < 240) {
@ -1314,6 +1267,9 @@ void PPU::Exec()
_state.SpriteRamAddr = 0;
}
}
} else {
//Process cycle 0
ProcessScanlineFirstCycle();
}
if(_needStateUpdate) {
@ -1321,6 +1277,53 @@ void PPU::Exec()
}
}
void PPU::ProcessScanlineFirstCycle()
{
_cycle = 0;
if(++_scanline > _vblankEnd) {
_lastUpdatedPixel = -1;
_scanline = -1;
//Force prerender scanline sprite fetches to load the dummy $FF tiles (fixes shaking in Ninja Gaiden 3 stage 1 after beating boss)
_spriteCount = 0;
if(_renderingEnabled) {
ProcessOamCorruption();
}
UpdateMinimumDrawCycles();
}
UpdateApuStatus();
if(_scanline == _settings->GetInputPollScanline()) {
_console->GetControlManager()->UpdateInputState();
}
//Cycle = 0
if(_scanline < 240) {
if(_scanline == -1) {
_statusFlags.SpriteOverflow = false;
_statusFlags.Sprite0Hit = false;
//Switch to alternate output buffer (VideoDecoder may still be decoding the last frame buffer)
_currentOutputBuffer = (_currentOutputBuffer == _outputBuffers[0]) ? _outputBuffers[1] : _outputBuffers[0];
} else if(_prevRenderingEnabled) {
if(_scanline > 0 || (!(_frameCount & 0x01) || _nesModel != NesModel::NTSC || _settings->GetPpuModel() != PpuModel::Ppu2C02)) {
//Set bus address to the tile address calculated from the unused NT fetches at the end of the previous scanline
//This doesn't happen on scanline 0 if the last dot of the previous frame was skipped
SetBusAddress((_nextTile.TileAddr << 4) | (_state.VideoRamAddr >> 12) | _flags.BackgroundPatternAddr);
}
}
} else if(_scanline == 240) {
//At the start of vblank, the bus address is set back to VideoRamAddr.
//According to Visual NES, this occurs on scanline 240, cycle 1, but is done here on cycle for performance reasons
SetBusAddress(_state.VideoRamAddr);
SendFrame();
_frameCount++;
}
}
void PPU::UpdateState()
{
_needStateUpdate = false;

View File

@ -129,16 +129,17 @@ class PPU : public IMemoryHandler, public Snapshotable
uint16_t GetNameTableAddr();
uint16_t GetAttributeAddr();
void ProcessScanlineFirstCycle();
__forceinline void ProcessScanline();
__forceinline void ProcessSpriteEvaluation();
void BeginVBlank();
void TriggerNmi();
void LoadTileInfo();
void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite);
void LoadSpriteTileInfo();
void LoadExtraSprites();
__forceinline void LoadTileInfo();
__forceinline void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite);
__forceinline void LoadSpriteTileInfo();
__forceinline void LoadExtraSprites();
__forceinline void ShiftTileRegisters();
__forceinline uint8_t ReadSpriteRam(uint8_t addr);

View File

@ -73,8 +73,8 @@ private:
double _previousTargetRate;
double GetChannelOutput(AudioChannel channel, bool forRightChannel);
int16_t GetOutputVolume(bool forRightChannel);
__forceinline double GetChannelOutput(AudioChannel channel, bool forRightChannel);
__forceinline int16_t GetOutputVolume(bool forRightChannel);
void EndFrame(uint32_t time);
void UpdateRates(bool forceUpdate);