mirror of
https://github.com/libretro/Mesen.git
synced 2024-11-23 09:09:45 +00:00
commit
d25d60fc19
@ -154,18 +154,13 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
|
|||||||
uint32_t pageCount = 0;
|
uint32_t pageCount = 0;
|
||||||
uint32_t pageSize = 0;
|
uint32_t pageSize = 0;
|
||||||
uint8_t defaultAccessType = MemoryAccessType::Read;
|
uint8_t defaultAccessType = MemoryAccessType::Read;
|
||||||
|
|
||||||
|
if(type == ChrMemoryType::Default) {
|
||||||
|
type = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
|
||||||
|
}
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case ChrMemoryType::Default:
|
case ChrMemoryType::Default:
|
||||||
pageSize = InternalGetChrPageSize();
|
|
||||||
if(pageSize == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pageCount = GetCHRPageCount();
|
|
||||||
if(_onlyChrRam) {
|
|
||||||
defaultAccessType |= MemoryAccessType::Write;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChrMemoryType::ChrRom:
|
case ChrMemoryType::ChrRom:
|
||||||
pageSize = InternalGetChrPageSize();
|
pageSize = InternalGetChrPageSize();
|
||||||
if(pageSize == 0)
|
if(pageSize == 0)
|
||||||
@ -194,7 +189,6 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
|
|||||||
pageNumber = pageNumber % pageCount;
|
pageNumber = pageNumber % pageCount;
|
||||||
|
|
||||||
if((uint16_t)(endAddr - startAddr) >= pageSize) {
|
if((uint16_t)(endAddr - startAddr) >= pageSize) {
|
||||||
|
|
||||||
uint32_t addr = startAddr;
|
uint32_t addr = startAddr;
|
||||||
while(addr <= endAddr - pageSize + 1) {
|
while(addr <= endAddr - pageSize + 1) {
|
||||||
SetPpuMemoryMapping(addr, addr + pageSize - 1, type, pageNumber * pageSize, accessType);
|
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)
|
void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, ChrMemoryType type, uint32_t sourceOffset, int8_t accessType)
|
||||||
{
|
{
|
||||||
uint8_t* sourceMemory = nullptr;
|
uint8_t* sourceMemory = nullptr;
|
||||||
|
|
||||||
|
if(type == ChrMemoryType::Default) {
|
||||||
|
type = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
|
||||||
|
}
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
default:
|
default:
|
||||||
case ChrMemoryType::Default:
|
case ChrMemoryType::Default:
|
||||||
sourceMemory = _onlyChrRam ? _chrRam : _chrRom;
|
case ChrMemoryType::ChrRom:
|
||||||
type = _onlyChrRam ? ChrMemoryType::ChrRam : ChrMemoryType::ChrRom;
|
sourceMemory = _chrRom;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ChrMemoryType::ChrRom: sourceMemory = _chrRom; break;
|
|
||||||
case ChrMemoryType::ChrRam: sourceMemory = _chrRam; break;
|
case ChrMemoryType::ChrRam: sourceMemory = _chrRam; break;
|
||||||
case ChrMemoryType::NametableRam: sourceMemory = _nametableRam; break;
|
case ChrMemoryType::NametableRam: sourceMemory = _nametableRam; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int firstSlot = startAddr >> 8;
|
int firstSlot = startAddr >> 8;
|
||||||
int slotCount = (endAddr - startAddr + 1) >> 8;
|
int slotCount = (endAddr - startAddr + 1) >> 8;
|
||||||
for(int i = 0; i < slotCount; i++) {
|
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) {
|
if(memoryType == ChrMemoryType::NametableRam) {
|
||||||
pageSize = BaseMapper::NametableSize;
|
pageSize = BaseMapper::NametableSize;
|
||||||
} else {
|
} else {
|
||||||
|
if(memoryType == ChrMemoryType::Default) {
|
||||||
|
memoryType = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam;
|
||||||
|
}
|
||||||
pageSize = memoryType == ChrMemoryType::ChrRam ? InternalGetChrRamPageSize() : InternalGetChrPageSize();
|
pageSize = memoryType == ChrMemoryType::ChrRam ? InternalGetChrRamPageSize() : InternalGetChrPageSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,7 +417,7 @@ bool BaseMapper::HasChrRam()
|
|||||||
|
|
||||||
bool BaseMapper::HasChrRom()
|
bool BaseMapper::HasChrRom()
|
||||||
{
|
{
|
||||||
return !_onlyChrRam;
|
return _chrRomSize > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseMapper::AddRegisterRange(uint16_t startAddr, uint16_t endAddr, MemoryOperation operation)
|
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];
|
_prgRom = new uint8_t[_prgSize];
|
||||||
_chrRom = new uint8_t[_chrRomSize];
|
_chrRom = new uint8_t[_chrRomSize];
|
||||||
|
|
||||||
memcpy(_prgRom, romData.PrgRom.data(), _prgSize);
|
memcpy(_prgRom, romData.PrgRom.data(), _prgSize);
|
||||||
if(_chrRomSize > 0) {
|
if(_chrRomSize > 0) {
|
||||||
memcpy(_chrRom, romData.ChrRom.data(), _chrRomSize);
|
memcpy(_chrRom, romData.ChrRom.data(), _chrRomSize);
|
||||||
@ -554,12 +557,10 @@ void BaseMapper::Initialize(RomData &romData)
|
|||||||
|
|
||||||
if(_chrRomSize == 0) {
|
if(_chrRomSize == 0) {
|
||||||
//Assume there is CHR RAM if no CHR ROM exists
|
//Assume there is CHR RAM if no CHR ROM exists
|
||||||
_onlyChrRam = true;
|
|
||||||
InitializeChrRam(romData.ChrRamSize);
|
InitializeChrRam(romData.ChrRamSize);
|
||||||
|
|
||||||
//Map CHR RAM to 0x0000-0x1FFF by default when no CHR ROM exists
|
//Map CHR RAM to 0x0000-0x1FFF by default when no CHR ROM exists
|
||||||
SetPpuMemoryMapping(0x0000, 0x1FFF, 0, ChrMemoryType::ChrRam);
|
SetPpuMemoryMapping(0x0000, 0x1FFF, 0, ChrMemoryType::ChrRam);
|
||||||
_chrRomSize = _chrRamSize;
|
|
||||||
} else if(romData.ChrRamSize >= 0) {
|
} else if(romData.ChrRamSize >= 0) {
|
||||||
InitializeChrRam(romData.ChrRamSize);
|
InitializeChrRam(romData.ChrRamSize);
|
||||||
} else if(GetChrRamSize()) {
|
} else if(GetChrRamSize()) {
|
||||||
@ -852,7 +853,7 @@ uint32_t BaseMapper::GetMemorySize(DebugMemoryType type)
|
|||||||
{
|
{
|
||||||
switch(type) {
|
switch(type) {
|
||||||
default: return 0;
|
default: return 0;
|
||||||
case DebugMemoryType::ChrRom: return _onlyChrRam ? 0 : _chrRomSize;
|
case DebugMemoryType::ChrRom: return _chrRomSize;
|
||||||
case DebugMemoryType::ChrRam: return _chrRamSize;
|
case DebugMemoryType::ChrRam: return _chrRamSize;
|
||||||
case DebugMemoryType::NametableRam: return _nametableCount * BaseMapper::NametableSize;
|
case DebugMemoryType::NametableRam: return _nametableCount * BaseMapper::NametableSize;
|
||||||
case DebugMemoryType::SaveRam: return _saveRamSize;
|
case DebugMemoryType::SaveRam: return _saveRamSize;
|
||||||
@ -1016,7 +1017,7 @@ int32_t BaseMapper::ToAbsoluteChrRomAddress(uint16_t addr)
|
|||||||
|
|
||||||
int32_t BaseMapper::FromAbsoluteChrAddress(uint32_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++) {
|
for(int i = 0; i < 64; i++) {
|
||||||
uint8_t* pageAddress = _chrPages[i];
|
uint8_t* pageAddress = _chrPages[i];
|
||||||
@ -1095,14 +1096,14 @@ CartridgeState BaseMapper::GetState()
|
|||||||
state.HasBattery = _romInfo.HasBattery;
|
state.HasBattery = _romInfo.HasBattery;
|
||||||
|
|
||||||
state.PrgRomSize = _prgSize;
|
state.PrgRomSize = _prgSize;
|
||||||
state.ChrRomSize = _onlyChrRam ? 0 : _chrRomSize;
|
state.ChrRomSize = _chrRomSize;
|
||||||
state.ChrRamSize = _chrRamSize;
|
state.ChrRamSize = _chrRamSize;
|
||||||
|
|
||||||
state.PrgPageCount = GetPRGPageCount();
|
state.PrgPageCount = GetPRGPageCount();
|
||||||
state.PrgPageSize = InternalGetPrgPageSize();
|
state.PrgPageSize = InternalGetPrgPageSize();
|
||||||
state.ChrPageCount = GetCHRPageCount();
|
state.ChrPageCount = GetCHRPageCount();
|
||||||
state.ChrPageSize = InternalGetChrPageSize();
|
state.ChrPageSize = InternalGetChrPageSize();
|
||||||
state.ChrRamPageSize = _onlyChrRam ? InternalGetChrPageSize() : InternalGetChrRamPageSize();
|
state.ChrRamPageSize = InternalGetChrRamPageSize();
|
||||||
for(int i = 0; i < 0x100; i++) {
|
for(int i = 0; i < 0x100; i++) {
|
||||||
state.PrgMemoryOffset[i] = _prgMemoryOffset[i];
|
state.PrgMemoryOffset[i] = _prgMemoryOffset[i];
|
||||||
state.PrgType[i] = _prgMemoryType[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> BaseMapper::GetPrgChrCopy()
|
||||||
{
|
{
|
||||||
vector<uint8_t> data;
|
vector<uint8_t> data;
|
||||||
data.resize(_prgSize + (_onlyChrRam ? 0 : _chrRomSize));
|
data.resize(_prgSize + _chrRomSize);
|
||||||
memcpy(data.data(), _prgRom, _prgSize);
|
memcpy(data.data(), _prgRom, _prgSize);
|
||||||
if(!_onlyChrRam) {
|
memcpy(data.data() + _prgSize, _chrRom, _chrRomSize);
|
||||||
memcpy(data.data() + _prgSize, _chrRom, _chrRomSize);
|
|
||||||
}
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseMapper::RestorePrgChrBackup(vector<uint8_t> &backupData)
|
void BaseMapper::RestorePrgChrBackup(vector<uint8_t> &backupData)
|
||||||
{
|
{
|
||||||
memcpy(_prgRom, backupData.data(), _prgSize);
|
memcpy(_prgRom, backupData.data(), _prgSize);
|
||||||
if(!_onlyChrRam) {
|
memcpy(_chrRom, backupData.data() + _prgSize, _chrRomSize);
|
||||||
memcpy(_chrRom, backupData.data() + _prgSize, _chrRomSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseMapper::RevertPrgChrChanges()
|
void BaseMapper::RevertPrgChrChanges()
|
||||||
@ -1198,8 +1195,6 @@ void BaseMapper::CopyPrgChrRom(shared_ptr<BaseMapper> mapper)
|
|||||||
{
|
{
|
||||||
if(_prgSize == mapper->_prgSize && _chrRomSize == mapper->_chrRomSize) {
|
if(_prgSize == mapper->_prgSize && _chrRomSize == mapper->_chrRomSize) {
|
||||||
memcpy(_prgRom, mapper->_prgRom, _prgSize);
|
memcpy(_prgRom, mapper->_prgRom, _prgSize);
|
||||||
if(!_onlyChrRam) {
|
memcpy(_chrRom, mapper->_chrRom, _chrRomSize);
|
||||||
memcpy(_chrRom, mapper->_chrRom, _chrRomSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ private:
|
|||||||
uint8_t *_nametableRam = nullptr;
|
uint8_t *_nametableRam = nullptr;
|
||||||
uint8_t _nametableCount = 2;
|
uint8_t _nametableCount = 2;
|
||||||
|
|
||||||
bool _onlyChrRam = false;
|
|
||||||
bool _hasBusConflicts = false;
|
bool _hasBusConflicts = false;
|
||||||
|
|
||||||
bool _allowRegisterRead = false;
|
bool _allowRegisterRead = false;
|
||||||
@ -78,7 +77,7 @@ protected:
|
|||||||
|
|
||||||
bool IsNes20();
|
bool IsNes20();
|
||||||
|
|
||||||
virtual uint16_t GetChrRamPageSize() { return 0x2000; }
|
virtual uint16_t GetChrRamPageSize() { return GetCHRPageSize(); }
|
||||||
|
|
||||||
//Save ram is battery backed and saved to disk
|
//Save ram is battery backed and saved to disk
|
||||||
virtual uint32_t GetSaveRamSize() { return HasBattery() ? 0x2000 : 0; }
|
virtual uint32_t GetSaveRamSize() { return HasBattery() ? 0x2000 : 0; }
|
||||||
|
98
Core/CPU.cpp
98
Core/CPU.cpp
@ -6,6 +6,7 @@
|
|||||||
#include "APU.h"
|
#include "APU.h"
|
||||||
#include "DeltaModulationChannel.h"
|
#include "DeltaModulationChannel.h"
|
||||||
#include "MemoryManager.h"
|
#include "MemoryManager.h"
|
||||||
|
#include "ControlManager.h"
|
||||||
#include "Console.h"
|
#include "Console.h"
|
||||||
|
|
||||||
CPU::CPU(shared_ptr<Console> console)
|
CPU::CPU(shared_ptr<Console> console)
|
||||||
@ -306,14 +307,26 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
|||||||
return;
|
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
|
//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.
|
//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
|
//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)"
|
//"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);
|
StartCpuCycle(true);
|
||||||
if(allowReadSideEffects) {
|
if(isNtscInputBehavior && !skipFirstInputClock) {
|
||||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
||||||
}
|
}
|
||||||
EndCpuCycle(true);
|
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)
|
//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();
|
ConsoleType type = _console->GetSettings()->GetConsoleType();
|
||||||
bool isNesBehavior = type != ConsoleType::Famicom;
|
bool isNesBehavior = type != ConsoleType::Famicom;
|
||||||
bool skipDummyReads = !allowReadSideEffects || (isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017));
|
bool skipDummyReads = !isNtscInputBehavior || (isNesBehavior && (readAddress == 0x4016 || readAddress == 0x4017));
|
||||||
|
|
||||||
auto processCycle = [this] {
|
auto processCycle = [this] {
|
||||||
//Sprite DMA cycles count as halt/dummy cycles for the DMC DMA when both run at the same time
|
//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) {
|
if(_dmcDmaRunning && !_needHalt && !_needDummyRead) {
|
||||||
//DMC DMA is ready to read a byte (both halt and dummy read cycles were performed before this)
|
//DMC DMA is ready to read a byte (both halt and dummy read cycles were performed before this)
|
||||||
processCycle();
|
processCycle();
|
||||||
readValue = _memoryManager->Read(_console->GetApu()->GetDmcReadAddress(), MemoryOperationType::DmcRead);
|
readValue = ProcessDmaRead(_console->GetApu()->GetDmcReadAddress(), prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||||
EndCpuCycle(true);
|
EndCpuCycle(true);
|
||||||
_console->GetApu()->SetDmcReadBuffer(readValue);
|
_console->GetApu()->SetDmcReadBuffer(readValue);
|
||||||
_dmcDmaRunning = false;
|
_dmcDmaRunning = false;
|
||||||
} else if(_spriteDmaTransfer) {
|
} else if(_spriteDmaTransfer) {
|
||||||
//DMC DMA is not running, or not ready, run sprite DMA
|
//DMC DMA is not running, or not ready, run sprite DMA
|
||||||
processCycle();
|
processCycle();
|
||||||
readValue = _memoryManager->Read(_spriteDmaOffset * 0x100 + spriteReadAddr);
|
readValue = ProcessDmaRead(_spriteDmaOffset * 0x100 + spriteReadAddr, prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||||
EndCpuCycle(true);
|
EndCpuCycle(true);
|
||||||
spriteReadAddr++;
|
spriteReadAddr++;
|
||||||
spriteDmaCounter++;
|
spriteDmaCounter++;
|
||||||
@ -361,7 +374,7 @@ void CPU::ProcessPendingDma(uint16_t readAddress)
|
|||||||
assert(_needHalt || _needDummyRead);
|
assert(_needHalt || _needDummyRead);
|
||||||
processCycle();
|
processCycle();
|
||||||
if(!skipDummyReads) {
|
if(!skipDummyReads) {
|
||||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
ProcessDmaRead(readAddress, prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||||
}
|
}
|
||||||
EndCpuCycle(true);
|
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)
|
//Align to read cycle before starting sprite DMA (or align to perform DMC read)
|
||||||
processCycle();
|
processCycle();
|
||||||
if(!skipDummyReads) {
|
if(!skipDummyReads) {
|
||||||
_memoryManager->Read(readAddress, MemoryOperationType::DummyRead);
|
ProcessDmaRead(readAddress, prevReadAddress, enableInternalRegReads, isNesBehavior);
|
||||||
}
|
}
|
||||||
EndCpuCycle(true);
|
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)
|
void CPU::RunDMATransfer(uint8_t offsetValue)
|
||||||
{
|
{
|
||||||
_spriteDmaTransfer = true;
|
_spriteDmaTransfer = true;
|
||||||
|
@ -80,6 +80,7 @@ private:
|
|||||||
|
|
||||||
__forceinline void StartCpuCycle(bool forRead);
|
__forceinline void StartCpuCycle(bool forRead);
|
||||||
__forceinline void ProcessPendingDma(uint16_t readAddress);
|
__forceinline void ProcessPendingDma(uint16_t readAddress);
|
||||||
|
uint8_t ProcessDmaRead(uint16_t addr, uint16_t& prevReadAddress, bool enableInternalRegReads, bool isNesBehavior);
|
||||||
__forceinline uint16_t FetchOperand();
|
__forceinline uint16_t FetchOperand();
|
||||||
__forceinline void EndCpuCycle(bool forRead);
|
__forceinline void EndCpuCycle(bool forRead);
|
||||||
void IRQ();
|
void IRQ();
|
||||||
|
@ -39,12 +39,13 @@ protected:
|
|||||||
virtual void StreamState(bool saving) override;
|
virtual void StreamState(bool saving) override;
|
||||||
virtual ControllerType GetControllerType(uint8_t port);
|
virtual ControllerType GetControllerType(uint8_t port);
|
||||||
virtual void RemapControllerButtons();
|
virtual void RemapControllerButtons();
|
||||||
virtual uint8_t GetOpenBusMask(uint8_t port);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ControlManager(std::shared_ptr<Console> console, std::shared_ptr<BaseControlDevice> systemActionManager, std::shared_ptr<BaseControlDevice> mapperControlDevice);
|
ControlManager(std::shared_ptr<Console> console, std::shared_ptr<BaseControlDevice> systemActionManager, std::shared_ptr<BaseControlDevice> mapperControlDevice);
|
||||||
virtual ~ControlManager();
|
virtual ~ControlManager();
|
||||||
|
|
||||||
|
virtual uint8_t GetOpenBusMask(uint8_t port);
|
||||||
|
|
||||||
virtual void UpdateControlDevices();
|
virtual void UpdateControlDevices();
|
||||||
void UpdateInputState();
|
void UpdateInputState();
|
||||||
|
|
||||||
|
103
Core/PPU.cpp
103
Core/PPU.cpp
@ -983,19 +983,16 @@ 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();
|
|
||||||
} 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()) {
|
||||||
@ -1247,52 +1244,8 @@ void PPU::ProcessOamCorruption()
|
|||||||
|
|
||||||
void PPU::Exec()
|
void PPU::Exec()
|
||||||
{
|
{
|
||||||
if(_cycle > 339) {
|
if(_cycle < 340) {
|
||||||
_cycle = 0;
|
//Process cycles 1 to 340
|
||||||
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
|
|
||||||
_cycle++;
|
_cycle++;
|
||||||
|
|
||||||
if(_scanline < 240) {
|
if(_scanline < 240) {
|
||||||
@ -1314,6 +1267,9 @@ void PPU::Exec()
|
|||||||
_state.SpriteRamAddr = 0;
|
_state.SpriteRamAddr = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
//Process cycle 0
|
||||||
|
ProcessScanlineFirstCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_needStateUpdate) {
|
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()
|
void PPU::UpdateState()
|
||||||
{
|
{
|
||||||
_needStateUpdate = false;
|
_needStateUpdate = false;
|
||||||
|
@ -129,16 +129,17 @@ class PPU : public IMemoryHandler, public Snapshotable
|
|||||||
uint16_t GetNameTableAddr();
|
uint16_t GetNameTableAddr();
|
||||||
uint16_t GetAttributeAddr();
|
uint16_t GetAttributeAddr();
|
||||||
|
|
||||||
|
void ProcessScanlineFirstCycle();
|
||||||
__forceinline void ProcessScanline();
|
__forceinline void ProcessScanline();
|
||||||
__forceinline void ProcessSpriteEvaluation();
|
__forceinline void ProcessSpriteEvaluation();
|
||||||
|
|
||||||
void BeginVBlank();
|
void BeginVBlank();
|
||||||
void TriggerNmi();
|
void TriggerNmi();
|
||||||
|
|
||||||
void LoadTileInfo();
|
__forceinline void LoadTileInfo();
|
||||||
void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite);
|
__forceinline void LoadSprite(uint8_t spriteY, uint8_t tileIndex, uint8_t attributes, uint8_t spriteX, bool extraSprite);
|
||||||
void LoadSpriteTileInfo();
|
__forceinline void LoadSpriteTileInfo();
|
||||||
void LoadExtraSprites();
|
__forceinline void LoadExtraSprites();
|
||||||
__forceinline void ShiftTileRegisters();
|
__forceinline void ShiftTileRegisters();
|
||||||
|
|
||||||
__forceinline uint8_t ReadSpriteRam(uint8_t addr);
|
__forceinline uint8_t ReadSpriteRam(uint8_t addr);
|
||||||
|
@ -73,8 +73,8 @@ private:
|
|||||||
|
|
||||||
double _previousTargetRate;
|
double _previousTargetRate;
|
||||||
|
|
||||||
double GetChannelOutput(AudioChannel channel, bool forRightChannel);
|
__forceinline double GetChannelOutput(AudioChannel channel, bool forRightChannel);
|
||||||
int16_t GetOutputVolume(bool forRightChannel);
|
__forceinline int16_t GetOutputVolume(bool forRightChannel);
|
||||||
void EndFrame(uint32_t time);
|
void EndFrame(uint32_t time);
|
||||||
|
|
||||||
void UpdateRates(bool forceUpdate);
|
void UpdateRates(bool forceUpdate);
|
||||||
|
Loading…
Reference in New Issue
Block a user