Mesen-X/Core/BaseMapper.cpp
Persune a22b17b2c1 Add /A13 and /OE1 audio interference
In hardware, the audio from the 2A03 APU pins go through an inverted
preamp using one of the 74HCU04 inverter chips in the motherboard.
Though this may be cost-effective on Nintendo's part, the signals on the
hex inverter chip can interfere with one another, especially on the
audio. This can be heard as a buzz or a high pitched whine.

The only known signals to interfere with the audio so far is:
- /A13, the inverted signal of PPU A13 going into the cartridge
   connector.
- /OE1, Output enable for controller reads, though this is only found
   present on the RF Famicom.
2021-08-11 17:55:56 +08:00

1280 lines
No EOL
38 KiB
C++

#include "stdafx.h"
#include <random>
#include <assert.h>
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/IpsPatcher.h"
#include "BaseMapper.h"
#include "Console.h"
#include "CheatManager.h"
#include "Debugger.h"
#include "MemoryManager.h"
#include "BatteryManager.h"
#include "MessageManager.h"
#include "EmulationSettings.h"
#include "CPU.h"
void BaseMapper::WriteRegister(uint16_t addr, uint8_t value) { }
void BaseMapper::WriteEPSM(uint16_t addr, uint8_t value) { _epsmaudio->WriteRegister(addr, value); }
uint8_t BaseMapper::ReadRegister(uint16_t addr) { return 0; }
void BaseMapper::InitMapper(RomData &romData) { }
void BaseMapper::Reset(bool softReset) { }
//Make sure the page size is no bigger than the size of the ROM itself
//Otherwise we will end up reading from unallocated memory
uint16_t BaseMapper::InternalGetPrgPageSize() { return std::min((uint32_t)GetPRGPageSize(), _prgSize); }
uint16_t BaseMapper::InternalGetSaveRamPageSize() { return std::min((uint32_t)GetSaveRamPageSize(), _saveRamSize); }
uint16_t BaseMapper::InternalGetWorkRamPageSize() { return std::min((uint32_t)GetWorkRamPageSize(), _workRamSize); }
uint16_t BaseMapper::InternalGetChrPageSize() { return std::min((uint32_t)GetCHRPageSize(), _chrRomSize); }
uint16_t BaseMapper::InternalGetChrRamPageSize() { return std::min((uint32_t)GetChrRamPageSize(), _chrRamSize); }
bool BaseMapper::ValidateAddressRange(uint16_t startAddr, uint16_t endAddr)
{
if((startAddr & 0xFF) || (endAddr & 0xFF) != 0xFF) {
#ifdef _DEBUG
throw new std::runtime_error("Start/End address must be multiples of 256/0x100");
#else
//Ignore this request in release mode - granularity smaller than 256 bytes is not supported
return false;
#endif
}
return true;
}
void BaseMapper::SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, int16_t pageNumber, PrgMemoryType type, int8_t accessType)
{
if(!ValidateAddressRange(startAddr, endAddr) || startAddr > 0xFF00 || endAddr <= startAddr) {
return;
}
uint32_t pageCount;
uint32_t pageSize;
uint8_t defaultAccessType = MemoryAccessType::Read;
switch(type) {
case PrgMemoryType::PrgRom:
pageCount = GetPRGPageCount();
pageSize = InternalGetPrgPageSize();
break;
case PrgMemoryType::SaveRam:
pageSize = InternalGetSaveRamPageSize();
if(pageSize == 0) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined save ram.");
#endif
return;
}
pageCount = _saveRamSize / pageSize;
defaultAccessType |= MemoryAccessType::Write;
break;
case PrgMemoryType::WorkRam:
pageSize = InternalGetWorkRamPageSize();
if(pageSize == 0) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined work ram.");
#endif
return;
}
pageCount = _workRamSize / pageSize;
defaultAccessType |= MemoryAccessType::Write;
break;
default:
throw new std::runtime_error("Invalid parameter");
}
if(pageCount == 0) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined save/work ram.");
#endif
return;
}
auto wrapPageNumber = [=](int16_t &page) -> void {
if(page < 0) {
//Can't use modulo for negative number because pageCount is sometimes not a power of 2. (Fixes some Mapper 191 games)
page = pageCount + page;
} else {
page = page % pageCount;
}
};
wrapPageNumber(pageNumber);
accessType = accessType != -1 ? accessType : defaultAccessType;
if((uint16_t)(endAddr - startAddr) >= pageSize) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined prg - page size too small for selected range.");
#endif
//If range is bigger than a single page, keep going until we reach the last page
uint32_t addr = startAddr;
while(addr <= endAddr - pageSize + 1) {
SetCpuMemoryMapping(addr, addr + pageSize - 1, type, pageNumber * pageSize, accessType);
addr += pageSize;
pageNumber++;
wrapPageNumber(pageNumber);
}
} else {
SetCpuMemoryMapping(startAddr, endAddr, type, pageNumber * pageSize, accessType);
}
}
void BaseMapper::SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, PrgMemoryType type, uint32_t sourceOffset, int8_t accessType)
{
uint8_t* source = nullptr;
switch(type) {
default:
case PrgMemoryType::PrgRom: source = _prgRom; break;
case PrgMemoryType::SaveRam: source = _saveRam; break;
case PrgMemoryType::WorkRam: source = _workRam; break;
}
int firstSlot = startAddr >> 8;
int slotCount = (endAddr - startAddr + 1) >> 8;
for(int i = 0; i < slotCount; i++) {
_prgMemoryOffset[firstSlot + i] = sourceOffset + i * 0x100;
_prgMemoryType[firstSlot + i] = type;
_prgMemoryAccess[firstSlot + i] = (MemoryAccessType)accessType;
}
SetCpuMemoryMapping(startAddr, endAddr, source+sourceOffset, accessType);
}
void BaseMapper::SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t *source, int8_t accessType)
{
if(!ValidateAddressRange(startAddr, endAddr)) {
return;
}
startAddr >>= 8;
endAddr >>= 8;
for(uint16_t i = startAddr; i <= endAddr; i++) {
_prgPages[i] = source;
_prgMemoryAccess[i] = accessType != -1 ? (MemoryAccessType)accessType : MemoryAccessType::Read;
source += 0x100;
}
}
void BaseMapper::RemoveCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr)
{
//Unmap this section of memory (causing open bus behavior)
int firstSlot = startAddr >> 8;
int slotCount = (endAddr - startAddr + 1) >> 8;
for(int i = 0; i < slotCount; i++) {
_prgMemoryOffset[firstSlot + i] = -1;
_prgMemoryType[firstSlot + i] = PrgMemoryType::PrgRom;
_prgMemoryAccess[firstSlot + i] = MemoryAccessType::NoAccess;
}
SetCpuMemoryMapping(startAddr, endAddr, nullptr, MemoryAccessType::NoAccess);
}
void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint16_t pageNumber, ChrMemoryType type, int8_t accessType)
{
if(!ValidateAddressRange(startAddr, endAddr) || startAddr > 0x3F00 || endAddr > 0x3FFF || endAddr <= startAddr) {
return;
}
uint32_t pageCount = 0;
uint32_t pageSize = 0;
uint8_t defaultAccessType = MemoryAccessType::Read;
switch(type) {
case ChrMemoryType::Default:
pageSize = InternalGetChrPageSize();
if(pageSize == 0) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined chr rom/ram.");
#endif
return;
}
pageCount = GetCHRPageCount();
if(_onlyChrRam) {
defaultAccessType |= MemoryAccessType::Write;
}
break;
case ChrMemoryType::ChrRom:
pageSize = InternalGetChrPageSize();
if(pageSize == 0) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined chr rom.");
#endif
return;
}
pageCount = GetCHRPageCount();
break;
case ChrMemoryType::ChrRam:
pageSize = InternalGetChrRamPageSize();
if(pageSize == 0) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined chr ram.");
#endif
return;
}
pageCount = _chrRamSize / pageSize;
defaultAccessType |= MemoryAccessType::Write;
break;
case ChrMemoryType::NametableRam:
pageSize = BaseMapper::NametableSize;
pageCount = BaseMapper::NametableCount;
defaultAccessType |= MemoryAccessType::Write;
break;
}
if(pageCount == 0) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined chr ram.");
#endif
return;
}
pageNumber = pageNumber % pageCount;
if((uint16_t)(endAddr - startAddr) >= pageSize) {
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "Tried to map undefined chr - page size too small for selected range.");
#endif
uint32_t addr = startAddr;
while(addr <= endAddr - pageSize + 1) {
SetPpuMemoryMapping(addr, addr + pageSize - 1, type, pageNumber * pageSize, accessType);
addr += pageSize;
pageNumber = (pageNumber + 1) % pageCount;
}
} else {
SetPpuMemoryMapping(startAddr, endAddr, type, pageNumber * pageSize, accessType == -1 ? defaultAccessType : accessType);
}
}
void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, ChrMemoryType type, uint32_t sourceOffset, int8_t accessType)
{
uint8_t* sourceMemory = nullptr;
switch(type) {
default:
case ChrMemoryType::Default:
sourceMemory = _onlyChrRam ? _chrRam : _chrRom;
type = _onlyChrRam ? ChrMemoryType::ChrRam : ChrMemoryType::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++) {
_chrMemoryOffset[firstSlot + i] = sourceOffset + i * 256;
_chrMemoryType[firstSlot + i] = type;
_chrMemoryAccess[firstSlot + i] = (MemoryAccessType)accessType;
}
SetPpuMemoryMapping(startAddr, endAddr, sourceMemory + sourceOffset, accessType);
}
void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t* sourceMemory, int8_t accessType)
{
if(!ValidateAddressRange(startAddr, endAddr)) {
return;
}
startAddr >>= 8;
endAddr >>= 8;
for(uint16_t i = startAddr; i <= endAddr; i++) {
_chrPages[i] = sourceMemory;
_chrMemoryAccess[i] = accessType != -1 ? (MemoryAccessType)accessType : MemoryAccessType::ReadWrite;
if(sourceMemory != nullptr) {
sourceMemory += 0x100;
}
}
}
void BaseMapper::RemovePpuMemoryMapping(uint16_t startAddr, uint16_t endAddr)
{
//Unmap this section of memory (causing open bus behavior)
int firstSlot = startAddr >> 8;
int slotCount = (endAddr - startAddr + 1) >> 8;
for(int i = 0; i < slotCount; i++) {
_chrMemoryOffset[firstSlot + i] = -1;
_chrMemoryType[firstSlot + i] = ChrMemoryType::Default;
_chrMemoryAccess[firstSlot + i] = MemoryAccessType::NoAccess;
}
SetPpuMemoryMapping(startAddr, endAddr, nullptr, MemoryAccessType::NoAccess);
}
uint8_t BaseMapper::InternalReadRam(uint16_t addr)
{
return _prgPages[addr >> 8] ? _prgPages[addr >> 8][(uint8_t)addr] : 0;
}
void BaseMapper::SelectPrgPage4x(uint16_t slot, uint16_t page, PrgMemoryType memoryType)
{
BaseMapper::SelectPrgPage2x(slot*2, page, memoryType);
BaseMapper::SelectPrgPage2x(slot*2+1, page+2, memoryType);
}
void BaseMapper::SelectPrgPage2x(uint16_t slot, uint16_t page, PrgMemoryType memoryType)
{
BaseMapper::SelectPRGPage(slot*2, page, memoryType);
BaseMapper::SelectPRGPage(slot*2+1, page+1, memoryType);
}
void BaseMapper::SelectPRGPage(uint16_t slot, uint16_t page, PrgMemoryType memoryType)
{
if(_prgSize < 0x8000 && GetPRGPageSize() > _prgSize) {
//Total PRG size is smaller than available memory range, map the entire PRG to all slots
//i.e same logic as NROM (mapper 0) when PRG is 16kb
//Needed by "Pyramid" (mapper 79)
#ifdef _DEBUG
MessageManager::DisplayMessage("Debug", "PrgSizeWarning");
#endif
for(slot = 0; slot < 0x8000 / _prgSize; slot++) {
uint16_t startAddr = 0x8000 + slot * _prgSize;
uint16_t endAddr = startAddr + _prgSize - 1;
SetCpuMemoryMapping(startAddr, endAddr, 0, memoryType);
}
} else {
uint16_t startAddr = 0x8000 + slot * InternalGetPrgPageSize();
uint16_t endAddr = startAddr + InternalGetPrgPageSize() - 1;
SetCpuMemoryMapping(startAddr, endAddr, page, memoryType);
}
}
void BaseMapper::SelectChrPage8x(uint16_t slot, uint16_t page, ChrMemoryType memoryType)
{
BaseMapper::SelectChrPage4x(slot, page, memoryType);
BaseMapper::SelectChrPage4x(slot*2+1, page+4, memoryType);
}
void BaseMapper::SelectChrPage4x(uint16_t slot, uint16_t page, ChrMemoryType memoryType)
{
BaseMapper::SelectChrPage2x(slot*2, page, memoryType);
BaseMapper::SelectChrPage2x(slot*2+1, page+2, memoryType);
}
void BaseMapper::SelectChrPage2x(uint16_t slot, uint16_t page, ChrMemoryType memoryType)
{
BaseMapper::SelectCHRPage(slot*2, page, memoryType);
BaseMapper::SelectCHRPage(slot*2+1, page+1, memoryType);
}
void BaseMapper::SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memoryType)
{
uint16_t pageSize;
if(memoryType == ChrMemoryType::NametableRam) {
pageSize = BaseMapper::NametableSize;
} else {
pageSize = memoryType == ChrMemoryType::ChrRam ? InternalGetChrRamPageSize() : InternalGetChrPageSize();
}
uint16_t startAddr = slot * pageSize;
uint16_t endAddr = startAddr + pageSize - 1;
SetPpuMemoryMapping(startAddr, endAddr, page, memoryType);
}
uint8_t BaseMapper::GetPowerOnByte(uint8_t defaultValue)
{
if(_console->GetSettings()->CheckFlag(EmulationFlags::RandomizeMapperPowerOnState)) {
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<> dist(0, 255);
return dist(mt);
} else {
return defaultValue;
}
}
uint32_t BaseMapper::GetDipSwitches()
{
uint32_t mask = (1 << GetDipSwitchCount()) - 1;
return _console->GetSettings()->GetDipSwitches() & mask;
}
bool BaseMapper::HasBattery()
{
return _romInfo.HasBattery;
}
void BaseMapper::LoadBattery()
{
if(HasBattery() && _saveRamSize > 0) {
_console->GetBatteryManager()->LoadBattery(".sav", _saveRam, _saveRamSize);
}
if(_hasChrBattery && _chrRamSize > 0) {
_console->GetBatteryManager()->LoadBattery(".sav.chr", _chrRam, _chrRamSize);
}
}
void BaseMapper::SaveBattery()
{
if(HasBattery() && _saveRamSize > 0) {
_console->GetBatteryManager()->SaveBattery(".sav", _saveRam, _saveRamSize);
}
if(_hasChrBattery && _chrRamSize > 0) {
_console->GetBatteryManager()->SaveBattery(".sav.chr", _chrRam, _chrRamSize);
}
}
uint32_t BaseMapper::GetPRGPageCount()
{
uint16_t pageSize = InternalGetPrgPageSize();
return pageSize ? (_prgSize / pageSize) : 0;
}
uint32_t BaseMapper::GetCHRPageCount()
{
uint16_t pageSize = InternalGetChrPageSize();
return pageSize ? (_chrRomSize / pageSize) : 0;
}
string BaseMapper::GetBatteryFilename()
{
return FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), FolderUtilities::GetFilename(_romInfo.RomName, false) + ".sav");
}
void BaseMapper::InitializeChrRam(int32_t chrRamSize)
{
uint32_t defaultRamSize = GetChrRamSize() ? GetChrRamSize() : 0x2000;
_chrRamSize = chrRamSize >= 0 ? chrRamSize : defaultRamSize;
if(_chrRamSize > 0) {
_chrRam = new uint8_t[_chrRamSize];
_console->InitializeRam(_chrRam, _chrRamSize);
}
}
void BaseMapper::SetupDefaultWorkRam()
{
//Setup a default work/save ram in 0x6000-0x7FFF space
if(HasBattery() && _saveRamSize > 0) {
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, PrgMemoryType::SaveRam);
} else if(_workRamSize > 0) {
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, PrgMemoryType::WorkRam);
}
}
bool BaseMapper::HasChrRam()
{
return _chrRamSize > 0;
}
bool BaseMapper::HasChrRom()
{
return !_onlyChrRam;
}
void BaseMapper::AddRegisterRange(uint16_t startAddr, uint16_t endAddr, MemoryOperation operation)
{
for(int i = startAddr; i <= endAddr; i++) {
if((int)operation & (int)MemoryOperation::Read) {
_isReadRegisterAddr[i] = true;
}
if((int)operation & (int)MemoryOperation::Write) {
_isWriteRegisterAddr[i] = true;
}
}
}
void BaseMapper::RemoveRegisterRange(uint16_t startAddr, uint16_t endAddr, MemoryOperation operation)
{
for(int i = startAddr; i <= endAddr; i++) {
if((int)operation & (int)MemoryOperation::Read) {
_isReadRegisterAddr[i] = false;
}
if((int)operation & (int)MemoryOperation::Write) {
_isWriteRegisterAddr[i] = false;
}
}
}
void BaseMapper::StreamState(bool saving)
{
//Need to get the number of nametables in the state first, before we try to stream the nametable ram array
Stream(_nametableCount);
ArrayInfo<uint8_t> chrRam = { _chrRam, _chrRamSize };
ArrayInfo<uint8_t> workRam = { _workRam, _workRamSize };
ArrayInfo<uint8_t> saveRam = { _saveRam, _saveRamSize };
ArrayInfo<uint8_t> nametableRam = { _nametableRam, _nametableCount * BaseMapper::NametableSize };
ArrayInfo<int32_t> prgMemoryOffset = { _prgMemoryOffset, 0x100 };
ArrayInfo<int32_t> chrMemoryOffset = { _chrMemoryOffset, 0x40 };
ArrayInfo<PrgMemoryType> prgMemoryType = { _prgMemoryType, 0x100 };
ArrayInfo<ChrMemoryType> chrMemoryType = { _chrMemoryType, 0x40 };
ArrayInfo<MemoryAccessType> prgMemoryAccess = { _prgMemoryAccess, 0x100 };
ArrayInfo<MemoryAccessType> chrMemoryAccess = { _chrMemoryAccess, 0x40 };
SnapshotInfo epsmaudio{ _epsmaudio.get() };
SnapshotInfo invA13Audio{ _invA13Audio.get() };
SnapshotInfo invOE1Audio{ _invOE1Audio.get() };
Stream(_mirroringType, chrRam, workRam, saveRam, nametableRam, prgMemoryOffset, chrMemoryOffset, prgMemoryType, chrMemoryType, prgMemoryAccess, chrMemoryAccess, epsmaudio, invA13Audio, invOE1Audio);
if(!saving) {
RestorePrgChrState();
}
}
void BaseMapper::RestorePrgChrState()
{
for(uint16_t i = 0; i < 0x100; i++) {
uint16_t startAddr = i << 8;
if(_prgMemoryAccess[i] != MemoryAccessType::NoAccess) {
SetCpuMemoryMapping(startAddr, startAddr + 0xFF, _prgMemoryType[i], _prgMemoryOffset[i], _prgMemoryAccess[i]);
} else {
RemoveCpuMemoryMapping(startAddr, startAddr + 0xFF);
}
}
for(uint16_t i = 0; i < 0x40; i++) {
uint16_t startAddr = i << 8;
if(_chrMemoryAccess[i] != MemoryAccessType::NoAccess) {
SetPpuMemoryMapping(startAddr, startAddr + 0xFF, _chrMemoryType[i], _chrMemoryOffset[i], _chrMemoryAccess[i]);
} else {
RemovePpuMemoryMapping(startAddr, startAddr + 0xFF);
}
}
}
void BaseMapper::Initialize(RomData &romData)
{
_romInfo = romData.Info;
_batteryFilename = GetBatteryFilename();
if(romData.SaveRamSize == -1 || ForceSaveRamSize()) {
_saveRamSize = GetSaveRamSize();
} else {
_saveRamSize = romData.SaveRamSize;
}
if(romData.WorkRamSize == -1 || ForceWorkRamSize()) {
_workRamSize = GetWorkRamSize();
} else {
_workRamSize = romData.WorkRamSize;
}
_allowRegisterRead = AllowRegisterRead();
memset(_isReadRegisterAddr, 0, sizeof(_isReadRegisterAddr));
memset(_isWriteRegisterAddr, 0, sizeof(_isWriteRegisterAddr));
AddRegisterRange(RegisterStartAddress(), RegisterEndAddress(), MemoryOperation::Any);
_prgSize = (uint32_t)romData.PrgRom.size();
_chrRomSize = (uint32_t)romData.ChrRom.size();
_originalPrgRom = romData.PrgRom;
_originalChrRom = romData.ChrRom;
_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);
}
_hasChrBattery = romData.SaveChrRamSize > 0 || ForceChrBattery();
switch(romData.Info.BusConflicts) {
case BusConflictType::Default: _hasBusConflicts = HasBusConflicts(); break;
case BusConflictType::Yes: _hasBusConflicts = true; break;
case BusConflictType::No: _hasBusConflicts = false; break;
}
if(_hasBusConflicts) {
MessageManager::Log("[iNes] Bus conflicts enabled");
}
_saveRam = new uint8_t[_saveRamSize];
_workRam = new uint8_t[_workRamSize];
_console->InitializeRam(_saveRam, _saveRamSize);
_console->InitializeRam(_workRam, _workRamSize);
_nametableCount = 2;
_nametableRam = new uint8_t[BaseMapper::NametableSize*BaseMapper::NametableCount];
_console->InitializeRam(_nametableRam, BaseMapper::NametableSize*BaseMapper::NametableCount);
for(int i = 0; i < 0x100; i++) {
//Allow us to map a different page every 256 bytes
_prgPages[i] = nullptr;
_prgMemoryOffset[i] = -1;
_prgMemoryType[i] = PrgMemoryType::PrgRom;
_prgMemoryAccess[i] = MemoryAccessType::NoAccess;
_chrPages[i] = nullptr;
_chrMemoryOffset[i] = -1;
_chrMemoryType[i] = ChrMemoryType::Default;
_chrMemoryAccess[i] = MemoryAccessType::NoAccess;
}
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()) {
InitializeChrRam();
}
if(romData.Info.HasTrainer) {
if(_workRamSize >= 0x2000) {
memcpy(_workRam + 0x1000, romData.TrainerData.data(), 512);
} else if(_saveRamSize >= 0x2000) {
memcpy(_saveRam + 0x1000, romData.TrainerData.data(), 512);
}
}
SetupDefaultWorkRam();
SetMirroringType(romData.Info.Mirroring);
InitMapper();
InitMapper(romData);
_epsmaudio.reset(new EPSMAudio(_console));
_invA13Audio.reset(new InvA13Audio(_console));
_invOE1Audio.reset(new InvOE1Audio(_console));
//Load battery data if present
LoadBattery();
_romInfo.HasChrRam = HasChrRam();
}
BaseMapper::~BaseMapper()
{
delete[] _chrRam;
delete[] _chrRom;
delete[] _prgRom;
delete[] _saveRam;
delete[] _workRam;
delete[] _nametableRam;
}
void BaseMapper::GetMemoryRanges(MemoryRanges &ranges)
{
if(_romInfo.System == GameSystem::VsSystem) {
ranges.AddHandler(MemoryOperation::Read, 0x6000, 0xFFFF);
ranges.AddHandler(MemoryOperation::Write, 0x6000, 0xFFFF);
} else {
ranges.AddHandler(MemoryOperation::Read, 0x4018, 0xFFFF);
ranges.AddHandler(MemoryOperation::Write, 0x4018, 0xFFFF);
}
}
void BaseMapper::SetConsole(shared_ptr<Console> console)
{
_console = console;
}
uint8_t* BaseMapper::GetNametable(uint8_t nametableIndex)
{
if(nametableIndex >= BaseMapper::NametableCount) {
#ifdef _DEBUG
MessageManager::Log("Invalid nametable index");
#endif
return _nametableRam;
}
_nametableCount = std::max<uint8_t>(_nametableCount, nametableIndex + 1);
return _nametableRam + (nametableIndex * BaseMapper::NametableSize);
}
void BaseMapper::SetNametable(uint8_t index, uint8_t nametableIndex)
{
if(nametableIndex >= BaseMapper::NametableCount) {
#ifdef _DEBUG
MessageManager::Log("Invalid nametable index");
#endif
return;
}
_nametableCount = std::max<uint8_t>(_nametableCount, nametableIndex + 1);
SetPpuMemoryMapping(0x2000 + index * 0x400, 0x2000 + (index + 1) * 0x400 - 1, nametableIndex, ChrMemoryType::NametableRam);
//Mirror $2000-$2FFF to $3000-$3FFF, while keeping a distinction between the addresses
//Previously, $3000-$3FFF was being "redirected" to $2000-$2FFF to avoid MMC3 IRQ issues (which is incorrect)
//More info here: https://forums.nesdev.com/viewtopic.php?p=132145#p132145
SetPpuMemoryMapping(0x3000 + index * 0x400, 0x3000 + (index + 1) * 0x400 - 1, nametableIndex, ChrMemoryType::NametableRam);
}
void BaseMapper::SetNametables(uint8_t nametable1Index, uint8_t nametable2Index, uint8_t nametable3Index, uint8_t nametable4Index)
{
SetNametable(0, nametable1Index);
SetNametable(1, nametable2Index);
SetNametable(2, nametable3Index);
SetNametable(3, nametable4Index);
}
void BaseMapper::SetMirroringType(MirroringType type)
{
_mirroringType = type;
switch(type) {
case MirroringType::Vertical: SetNametables(0, 1, 0, 1); break;
case MirroringType::Horizontal: SetNametables(0, 0, 1, 1); break;
case MirroringType::FourScreens: SetNametables(0, 1, 2, 3); break;
case MirroringType::ScreenAOnly: SetNametables(0, 0, 0, 0); break;
case MirroringType::ScreenBOnly: SetNametables(1, 1, 1, 1); break;
}
}
ConsoleFeatures BaseMapper::GetAvailableFeatures()
{
return ConsoleFeatures::None;
}
shared_ptr<BaseControlDevice> BaseMapper::GetMapperControlDevice()
{
return _mapperControlDevice;
}
RomInfo BaseMapper::GetRomInfo()
{
RomInfo romInfo = _romInfo;
romInfo.BusConflicts = _hasBusConflicts ? BusConflictType::Yes : BusConflictType::No;
return romInfo;
}
uint32_t BaseMapper::GetMapperDipSwitchCount()
{
return GetDipSwitchCount();
}
MirroringType BaseMapper::GetMirroringType()
{
return _mirroringType;
}
uint8_t BaseMapper::ReadRAM(uint16_t addr)
{
if(_allowRegisterRead && _isReadRegisterAddr[addr]) {
return ReadRegister(addr);
} else if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Read) {
return _prgPages[addr >> 8][(uint8_t)addr];
} else {
//assert(false);
}
return _console->GetMemoryManager()->GetOpenBus();
}
uint8_t BaseMapper::PeekRAM(uint16_t addr)
{
return DebugReadRAM(addr);
}
uint8_t BaseMapper::DebugReadRAM(uint16_t addr)
{
if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Read) {
return _prgPages[addr >> 8][(uint8_t)addr];
} else {
//assert(false);
}
//Fake open bus
return addr >> 8;
}
void BaseMapper::WriteRAM(uint16_t addr, uint8_t value)
{
if((addr == 0x4016) & (_console->GetCpu()->GetCycleCount() % 2 == 1)){ WriteEPSM(addr, value); }
if ((addr >= 0x401c && addr <= 0x401f)) { WriteEPSM(addr, value); }
if(_isWriteRegisterAddr[addr]) {
if(_hasBusConflicts) {
uint8_t prgValue = _prgPages[addr >> 8][(uint8_t)addr];
if(value != prgValue) {
_console->DebugProcessEvent(EventType::BusConflict);
}
value &= prgValue;
}
WriteRegister(addr, value);
} else {
WritePrgRam(addr, value);
}
}
void BaseMapper::DebugWriteRAM(uint16_t addr, uint8_t value)
{
if(_isWriteRegisterAddr[addr]) {
if(_hasBusConflicts) {
value &= _prgPages[addr >> 8][(uint8_t)addr];
}
} else {
WritePrgRam(addr, value);
}
}
void BaseMapper::WritePrgRam(uint16_t addr, uint8_t value)
{
if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Write) {
_prgPages[addr >> 8][(uint8_t)addr] = value;
}
}
void BaseMapper::NotifyVRAMAddressChange(uint16_t addr)
{
//This is called when the VRAM addr on the PPU memory bus changes
//Used by MMC3/MMC5/etc
}
uint8_t BaseMapper::InternalReadVRAM(uint16_t addr)
{
if(_chrMemoryAccess[addr >> 8] & MemoryAccessType::Read) {
return _chrPages[addr >> 8][(uint8_t)addr];
}
//Open bus - "When CHR is disabled, the pattern tables are open bus. Theoretically, this should return the LSB of the address read, but real-world behavior varies."
return _vramOpenBusValue >= 0 ? _vramOpenBusValue : addr;
}
uint8_t BaseMapper::DebugReadVRAM(uint16_t addr, bool disableSideEffects)
{
addr &= 0x3FFF;
if(!disableSideEffects) {
NotifyVRAMAddressChange(addr);
}
return InternalReadVRAM(addr);
}
uint8_t BaseMapper::MapperReadVRAM(uint16_t addr, MemoryOperationType operationType)
{
return InternalReadVRAM(addr);
}
void BaseMapper::DebugWriteVRAM(uint16_t addr, uint8_t value, bool disableSideEffects)
{
addr &= 0x3FFF;
if(disableSideEffects) {
if(_chrPages[addr >> 8]) {
//Always allow writes when side-effects are disabled
_chrPages[addr >> 8][(uint8_t)addr] = value;
}
} else {
NotifyVRAMAddressChange(addr);
if(_chrMemoryAccess[addr >> 8] & MemoryAccessType::Write) {
_chrPages[addr >> 8][(uint8_t)addr] = value;
}
}
}
void BaseMapper::WriteVRAM(uint16_t addr, uint8_t value)
{
_console->DebugProcessVramWriteOperation(addr, value);
if(_chrMemoryAccess[addr >> 8] & MemoryAccessType::Write) {
_chrPages[addr >> 8][(uint8_t)addr] = value;
}
}
bool BaseMapper::IsNes20()
{
return _romInfo.NesHeader.GetRomHeaderVersion() == RomHeaderVersion::Nes2_0;
}
//Debugger Helper Functions
uint8_t* BaseMapper::GetPrgRom()
{
return _prgRom;
}
uint8_t* BaseMapper::GetSaveRam()
{
return _saveRam;
}
uint8_t* BaseMapper::GetWorkRam()
{
return _workRam;
}
uint32_t BaseMapper::CopyMemory(DebugMemoryType type, uint8_t* buffer)
{
uint32_t size = GetMemorySize(type);
switch(type) {
default: break;
case DebugMemoryType::ChrRam: memcpy(buffer, _chrRam, size); break;
case DebugMemoryType::ChrRom: memcpy(buffer, _chrRom, size); break;
case DebugMemoryType::NametableRam: memcpy(buffer, _nametableRam, size); break;
case DebugMemoryType::SaveRam: memcpy(buffer, _saveRam, size); break;
case DebugMemoryType::PrgRom: memcpy(buffer, _prgRom, size); break;
case DebugMemoryType::WorkRam: memcpy(buffer, _workRam, size); break;
}
return size;
}
void BaseMapper::WriteMemory(DebugMemoryType type, uint8_t* buffer, int32_t length)
{
int32_t size = std::min(length, (int32_t)GetMemorySize(type));
switch(type) {
default: break;
case DebugMemoryType::ChrRam: memcpy(_chrRam, buffer, size); break;
case DebugMemoryType::SaveRam: memcpy(_saveRam, buffer, size); break;
case DebugMemoryType::WorkRam: memcpy(_workRam, buffer, size); break;
case DebugMemoryType::NametableRam: memcpy(_nametableRam, buffer, size); break;
}
}
uint32_t BaseMapper::GetMemorySize(DebugMemoryType type)
{
switch(type) {
default: return 0;
case DebugMemoryType::ChrRom: return _onlyChrRam ? 0 : _chrRomSize;
case DebugMemoryType::ChrRam: return _chrRamSize;
case DebugMemoryType::NametableRam: return _nametableCount * BaseMapper::NametableSize;
case DebugMemoryType::SaveRam: return _saveRamSize;
case DebugMemoryType::PrgRom: return _prgSize;
case DebugMemoryType::WorkRam: return _workRamSize;
}
}
void BaseMapper::CopyChrTile(uint32_t address, uint8_t *dest)
{
if(_chrRamSize > 0 && address <= _chrRamSize - 16) {
memcpy(dest, _chrRam + address, 16);
} else if(_chrRomSize > 0 && address <= _chrRomSize - 16) {
memcpy(dest, _chrRom + address, 16);
}
}
uint8_t BaseMapper::GetMemoryValue(DebugMemoryType memoryType, uint32_t address)
{
uint32_t memorySize = GetMemorySize(memoryType);
if(memorySize > 0) {
if(address > memorySize) {
address %= memorySize;
}
switch(memoryType) {
default: break;
case DebugMemoryType::ChrRom: return _chrRom[address];
case DebugMemoryType::ChrRam: return _chrRam[address];
case DebugMemoryType::SaveRam: return _saveRam[address];
case DebugMemoryType::PrgRom: return _prgRom[address];
case DebugMemoryType::WorkRam: return _workRam[address];
case DebugMemoryType::NametableRam: return _nametableRam[address];
}
}
return 0;
}
void BaseMapper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value)
{
uint32_t memorySize = GetMemorySize(memoryType);
if(memorySize > 0) {
if(address > memorySize) {
address %= memorySize;
}
switch(memoryType) {
default: break;
case DebugMemoryType::ChrRom: _chrRom[address] = value; break;
case DebugMemoryType::ChrRam: _chrRam[address] = value; break;
case DebugMemoryType::SaveRam: _saveRam[address] = value; break;
case DebugMemoryType::PrgRom: _prgRom[address] = value; break;
case DebugMemoryType::WorkRam: _workRam[address] = value; break;
case DebugMemoryType::NametableRam: _nametableRam[address] = value; break;
}
}
}
void BaseMapper::GetAbsoluteAddressAndType(uint32_t relativeAddr, AddressTypeInfo* info)
{
if(relativeAddr < 0x2000) {
info->Address = relativeAddr & 0x7FF;
info->Type = AddressType::InternalRam;
} else {
uint8_t *prgAddr = _prgPages[relativeAddr >> 8] + (uint8_t)relativeAddr;
if(prgAddr >= _prgRom && prgAddr < _prgRom + _prgSize) {
info->Address = (uint32_t)(prgAddr - _prgRom);
info->Type = AddressType::PrgRom;
} else if(prgAddr >= _workRam && prgAddr < _workRam + _workRamSize) {
info->Address = (uint32_t)(prgAddr - _workRam);
info->Type = AddressType::WorkRam;
} else if(prgAddr >= _saveRam && prgAddr < _saveRam + _saveRamSize) {
info->Address = (uint32_t)(prgAddr - _saveRam);
info->Type = AddressType::SaveRam;
} else {
info->Address = -1;
info->Type = AddressType::InternalRam;
}
}
}
int32_t BaseMapper::ToAbsoluteAddress(uint16_t addr)
{
uint8_t *prgAddr = _prgPages[addr >> 8] + (uint8_t)addr;
if(prgAddr >= _prgRom && prgAddr < _prgRom + _prgSize) {
return (uint32_t)(prgAddr - _prgRom);
}
return -1;
}
int32_t BaseMapper::ToAbsoluteWorkRamAddress(uint16_t addr)
{
uint8_t *prgRamAddr = _prgPages[addr >> 8] + (uint8_t)addr;
if(prgRamAddr >= _workRam && prgRamAddr < _workRam + _workRamSize) {
return (uint32_t)(prgRamAddr - _workRam);
}
return -1;
}
int32_t BaseMapper::ToAbsoluteSaveRamAddress(uint16_t addr)
{
uint8_t *prgRamAddr = _prgPages[addr >> 8] + (uint8_t)addr;
if(prgRamAddr >= _saveRam && prgRamAddr < _saveRam + _saveRamSize) {
return (uint32_t)(prgRamAddr - _saveRam);
}
return -1;
}
void BaseMapper::GetPpuAbsoluteAddressAndType(uint32_t relativeAddr, PpuAddressTypeInfo* info)
{
if(relativeAddr >= 0x3F00) {
info->Address = relativeAddr & 0x1F;
info->Type = PpuAddressType::PaletteRam;
} else {
uint8_t *addr = _chrPages[relativeAddr >> 8] + (uint8_t)relativeAddr;
if(addr >= _chrRom && addr < _chrRom + _chrRomSize) {
info->Address = (uint32_t)(addr - _chrRom);
info->Type = PpuAddressType::ChrRom;
} else if(addr >= _chrRam && addr < _chrRam + _chrRamSize) {
info->Address = (uint32_t)(addr - _chrRam);
info->Type = PpuAddressType::ChrRam;
} else if(addr >= _nametableRam && addr < _nametableRam + BaseMapper::NametableSize * BaseMapper::NametableCount) {
info->Address = (uint32_t)(addr - _nametableRam);
info->Type = PpuAddressType::NametableRam;
} else {
info->Address = -1;
info->Type = PpuAddressType::None;
}
}
}
int32_t BaseMapper::ToAbsoluteChrAddress(uint16_t addr)
{
uint8_t *chrAddr = _chrPages[addr >> 8] + (uint8_t)addr;
if(chrAddr >= _chrRom && chrAddr < _chrRom + _chrRomSize) {
return (uint32_t)(chrAddr - _chrRom);
}
if(chrAddr >= _chrRam && chrAddr < _chrRam + _chrRamSize) {
return (uint32_t)(chrAddr - _chrRam);
}
return -1;
}
int32_t BaseMapper::ToAbsoluteChrRamAddress(uint16_t addr)
{
uint8_t *chrAddr = _chrPages[addr >> 8] + (uint8_t)addr;
if(chrAddr >= _chrRam && chrAddr < _chrRam + _chrRamSize) {
return (uint32_t)(chrAddr - _chrRam);
}
return -1;
}
int32_t BaseMapper::ToAbsoluteChrRomAddress(uint16_t addr)
{
uint8_t *chrAddr = _chrPages[addr >> 8] + (uint8_t)addr;
if(chrAddr >= _chrRom && chrAddr < _chrRom + _chrRomSize) {
return (uint32_t)(chrAddr - _chrRom);
}
return -1;
}
int32_t BaseMapper::FromAbsoluteChrAddress(uint32_t addr)
{
uint8_t* ptrAddress = (_onlyChrRam ? _chrRam : _chrRom) + (addr & 0x3FFF);
for(int i = 0; i < 64; i++) {
uint8_t* pageAddress = _chrPages[i];
if(pageAddress != nullptr && ptrAddress >= pageAddress && ptrAddress <= pageAddress + 0xFF) {
return (i << 8) + (uint32_t)(ptrAddress - pageAddress);
}
}
//Address is currently not mapped
return -1;
}
int32_t BaseMapper::FromAbsoluteAddress(uint32_t addr, AddressType type)
{
uint8_t* ptrAddress;
switch(type) {
case AddressType::PrgRom: ptrAddress = _prgRom; break;
case AddressType::WorkRam: ptrAddress = _workRam; break;
case AddressType::SaveRam: ptrAddress = _saveRam; break;
case AddressType::Register: return addr & 0xFFFF; break;
case AddressType::InternalRam: return addr & 0x1FFF; break;
default: return -1;
}
ptrAddress += addr;
for(int i = 0; i < 256; i++) {
uint8_t* pageAddress = _prgPages[i];
if(pageAddress != nullptr && ptrAddress >= pageAddress && ptrAddress <= pageAddress + 0xFF) {
return (i << 8) + (uint32_t)(ptrAddress - pageAddress);
}
}
//Address is currently not mapped
return -1;
}
int32_t BaseMapper::FromAbsolutePpuAddress(uint32_t addr, PpuAddressType type)
{
uint8_t* ptrAddress;
switch(type) {
case PpuAddressType::ChrRom: ptrAddress = _chrRom; break;
case PpuAddressType::ChrRam: ptrAddress = _chrRam; break;
case PpuAddressType::NametableRam: ptrAddress = _nametableRam; break;
default: return -1;
}
ptrAddress += addr;
for(int i = 0; i < 0x40; i++) {
uint8_t* pageAddress = _chrPages[i];
if(pageAddress != nullptr && ptrAddress >= pageAddress && ptrAddress <= pageAddress + 0xFF) {
return (i << 8) + (uint32_t)(ptrAddress - pageAddress);
}
}
//Address is currently not mapped
return -1;
}
bool BaseMapper::IsWriteRegister(uint16_t addr)
{
return _isWriteRegisterAddr[addr];
}
bool BaseMapper::IsReadRegister(uint16_t addr)
{
return _allowRegisterRead && _isReadRegisterAddr[addr];
}
CartridgeState BaseMapper::GetState()
{
CartridgeState state;
state.Mirroring = _mirroringType;
state.HasBattery = _romInfo.HasBattery;
state.PrgRomSize = _prgSize;
state.ChrRomSize = _onlyChrRam ? 0 : _chrRomSize;
state.ChrRamSize = _chrRamSize;
state.PrgPageCount = GetPRGPageCount();
state.PrgPageSize = InternalGetPrgPageSize();
state.ChrPageCount = GetCHRPageCount();
state.ChrPageSize = InternalGetChrPageSize();
state.ChrRamPageSize = _onlyChrRam ? InternalGetChrPageSize() : InternalGetChrRamPageSize();
for(int i = 0; i < 0x100; i++) {
state.PrgMemoryOffset[i] = _prgMemoryOffset[i];
state.PrgType[i] = _prgMemoryType[i];
state.PrgMemoryAccess[i] = _prgMemoryAccess[i];
}
for(int i = 0; i < 0x40; i++) {
state.ChrMemoryOffset[i] = _chrMemoryOffset[i];
state.ChrType[i] = _chrMemoryType[i];
state.ChrMemoryAccess[i] = _chrMemoryAccess[i];
}
state.WorkRamPageSize = GetWorkRamPageSize();
state.SaveRamPageSize = GetSaveRamPageSize();
return state;
}
void BaseMapper::GetRomFileData(vector<uint8_t> &out, bool asIpsFile, uint8_t* header)
{
if(header) {
//Get original rom with edited header
vector<uint8_t> originalFile;
_console->GetRomPath().ReadFile(originalFile);
out.insert(out.end(), header, header+sizeof(NESHeader));
if(_romInfo.IsHeaderlessRom) {
out.insert(out.end(), originalFile.begin(), originalFile.end());
} else {
out.insert(out.end(), originalFile.begin() + sizeof(NESHeader), originalFile.end());
}
} else {
vector<uint8_t> newFile;
newFile.insert(newFile.end(), (uint8_t*)&_romInfo.NesHeader, ((uint8_t*)&_romInfo.NesHeader) + sizeof(NESHeader));
newFile.insert(newFile.end(), _prgRom, _prgRom + _prgSize);
if(HasChrRom()) {
newFile.insert(newFile.end(), _chrRom, _chrRom + _chrRomSize);
}
//Get edited rom
if(asIpsFile) {
vector<uint8_t> originalFile;
_console->GetRomPath().ReadFile(originalFile);
vector<uint8_t> patchData = IpsPatcher::CreatePatch(originalFile, newFile);
out.insert(out.end(), patchData.begin(), patchData.end());
} else {
out.insert(out.end(), newFile.begin(), newFile.end());
}
}
}
vector<uint8_t> BaseMapper::GetPrgChrCopy()
{
vector<uint8_t> data;
data.resize(_prgSize + (_onlyChrRam ? 0 : _chrRomSize));
memcpy(data.data(), _prgRom, _prgSize);
if(!_onlyChrRam) {
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);
}
}
void BaseMapper::RevertPrgChrChanges()
{
memcpy(_prgRom, _originalPrgRom.data(), _originalPrgRom.size());
if(_chrRom) {
memcpy(_chrRom, _originalChrRom.data(), _originalChrRom.size());
}
}
bool BaseMapper::HasPrgChrChanges()
{
if(memcmp(_prgRom, _originalPrgRom.data(), _originalPrgRom.size()) != 0) {
return true;
}
if(_chrRom) {
if(memcmp(_chrRom, _originalChrRom.data(), _originalChrRom.size()) != 0) {
return true;
}
}
return false;
}
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);
}
}
}