#include "pch.h" #include #include #include "NES/BaseMapper.h" #include "NES/NesConsole.h" #include "NES/NesTypes.h" #include "NES/NesMemoryManager.h" #include "NES/RomData.h" #include "NES/Epsm.h" #include "Debugger/DebugTypes.h" #include "Shared/MessageManager.h" #include "Shared/BatteryManager.h" #include "Shared/EmuSettings.h" #include "Shared/RomInfo.h" #include "Utilities/FolderUtilities.h" #include "Utilities/Patches/IpsPatcher.h" #include "Utilities/Serializer.h" #include "Shared/MemoryType.h" #include "Shared/MemoryOperationType.h" #include "Shared/FirmwareHelper.h" void BaseMapper::WriteRegister(uint16_t addr, uint8_t value) { } uint8_t BaseMapper::ReadRegister(uint16_t addr) { return 0; } void BaseMapper::InitMapper(RomData& romData) { } void BaseMapper::Reset(bool softReset) { } 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 = _prgRomPageSize; break; case PrgMemoryType::SaveRam: pageSize = _saveRamPageSize; 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 = _workRamPageSize; 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 uint16_t gap = endAddr - startAddr + 1; if(gap % pageSize != 0) { 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; uint32_t sourceSize = 0; switch(type) { default: case PrgMemoryType::PrgRom: source = _prgRom; sourceSize = _prgSize; break; case PrgMemoryType::SaveRam: source = _saveRam; sourceSize = _saveRamSize; break; case PrgMemoryType::WorkRam: source = _workRam; sourceSize = _workRamSize; break; case PrgMemoryType::MapperRam: source = _mapperRam; sourceSize = _mapperRamSize; break; } int firstSlot = startAddr >> 8; int slotCount = (endAddr - startAddr + 1) >> 8; for(int i = 0; i < slotCount; i++) { if(sourceSize == 0 || accessType == 0) { _prgPages[i] = nullptr; _prgMemoryAccess[i] = MemoryAccessType::NoAccess; } else { while(sourceOffset >= sourceSize) { sourceOffset -= sourceSize; } _prgPages[firstSlot + i] = source + sourceOffset; _prgMemoryOffset[firstSlot + i] = sourceOffset + i * 0x100; _prgMemoryType[firstSlot + i] = type; _prgMemoryAccess[firstSlot + i] = (MemoryAccessType)accessType; } } SetCpuMemoryMapping(startAddr, endAddr, source, sourceOffset, sourceSize, accessType); } void BaseMapper::SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t* source, uint32_t sourceOffset, uint32_t sourceSize, int8_t accessType) { if(!ValidateAddressRange(startAddr, endAddr)) { return; } startAddr >>= 8; endAddr >>= 8; for(uint16_t i = startAddr; i <= endAddr; i++) { if(accessType != 0 && source && sourceSize > 0 && sourceOffset <= sourceSize - 0x100) { _prgPages[i] = source + sourceOffset; _prgMemoryAccess[i] = accessType != -1 ? (MemoryAccessType)accessType : MemoryAccessType::ReadWrite; } else { _prgPages[i] = nullptr; _prgMemoryAccess[i] = MemoryAccessType::NoAccess; } sourceOffset += 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, 0, 0, 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; if(type == ChrMemoryType::Default) { if(_chrRomSize > 0) { type = ChrMemoryType::ChrRom; } else { type = ChrMemoryType::ChrRam; } } switch(type) { case ChrMemoryType::Default: case ChrMemoryType::ChrRom: pageSize = _chrRomPageSize; if(pageSize == 0) { #ifdef _DEBUG MessageManager::DisplayMessage("Debug", "Tried to map undefined chr rom."); #endif return; } pageCount = GetChrRomPageCount(); break; case ChrMemoryType::ChrRam: pageSize = _chrRamPageSize; 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 = _nametableCount; defaultAccessType |= MemoryAccessType::Write; break; default: throw new std::runtime_error("Invalid parameter"); } 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; if(type == ChrMemoryType::Default) { type = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam; } uint32_t sourceSize = 0; switch(type) { case ChrMemoryType::Default: case ChrMemoryType::ChrRom: sourceMemory = _chrRom; sourceSize = _chrRomSize; break; case ChrMemoryType::ChrRam: sourceMemory = _chrRam; sourceSize = _chrRamSize; break; case ChrMemoryType::NametableRam: sourceMemory = _nametableRam; sourceSize = _ntRamSize; break; case ChrMemoryType::MapperRam: sourceMemory = _mapperRam; sourceSize = _mapperRamSize; break; } int firstSlot = startAddr >> 8; int slotCount = (endAddr - startAddr + 1) >> 8; for(int i = 0; i < slotCount; i++) { if(sourceSize == 0 || accessType == 0) { _chrPages[i] = nullptr; _chrMemoryAccess[i] = MemoryAccessType::NoAccess; } else { while(sourceOffset >= sourceSize) { sourceOffset -= sourceSize; } _chrMemoryOffset[firstSlot + i] = sourceOffset + i * 256; _chrMemoryType[firstSlot + i] = type; _chrMemoryAccess[firstSlot + i] = (MemoryAccessType)accessType; } } SetPpuMemoryMapping(startAddr, endAddr, sourceMemory, sourceOffset, sourceSize, accessType); } void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8_t* sourceMemory, uint32_t sourceOffset, uint32_t sourceSize, int8_t accessType) { if(!ValidateAddressRange(startAddr, endAddr)) { return; } startAddr >>= 8; endAddr >>= 8; for(uint16_t i = startAddr; i <= endAddr; i++) { if(accessType != 0 && sourceMemory && sourceSize > 0 && sourceOffset <= sourceSize - 0x100) { _chrPages[i] = sourceMemory + sourceOffset; _chrMemoryAccess[i] = accessType != -1 ? (MemoryAccessType)accessType : MemoryAccessType::ReadWrite; } else { _chrPages[i] = nullptr; _chrMemoryAccess[i] = MemoryAccessType::NoAccess; } sourceOffset += 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, 0, 0, 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 * _prgRomPageSize; uint16_t endAddr = startAddr + _prgRomPageSize - 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 { if(memoryType == ChrMemoryType::Default) { memoryType = _chrRomSize > 0 ? ChrMemoryType::ChrRom : ChrMemoryType::ChrRam; } pageSize = memoryType == ChrMemoryType::ChrRam ? _chrRamPageSize : _chrRomPageSize; } 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->GetNesConfig().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 _emu->GetSettings()->GetGameConfig().DipSwitches & mask; } bool BaseMapper::HasBattery() { return _romInfo.HasBattery; } void BaseMapper::LoadBattery() { if(HasBattery() && _saveRamSize > 0) { _emu->GetBatteryManager()->LoadBattery(".sav", _saveRam, _saveRamSize); } if(_hasChrBattery && _chrRamSize > 0) { _emu->GetBatteryManager()->LoadBattery(".chr.sav", _chrRam, _chrRamSize); } } void BaseMapper::SaveBattery() { if(HasBattery() && _saveRamSize > 0) { _emu->GetBatteryManager()->SaveBattery(".sav", _saveRam, _saveRamSize); } if(_hasChrBattery && _chrRamSize > 0) { _emu->GetBatteryManager()->SaveBattery(".chr.sav", _chrRam, _chrRamSize); } } uint32_t BaseMapper::GetPrgPageCount() { uint16_t pageSize = _prgRomPageSize; return pageSize ? (_prgSize / pageSize) : 0; } uint32_t BaseMapper::GetChrRomPageCount() { uint16_t pageSize = _chrRomPageSize; 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]; _emu->RegisterMemory(MemoryType::NesChrRam, _chrRam, _chrRamSize); _console->InitializeRam(_chrRam, _chrRamSize); } } bool BaseMapper::HasDefaultWorkRam() { return _hasDefaultWorkRam; } 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 _chrRomSize > 0; } 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::Serialize(Serializer& s) { SVArray(_chrRam, _chrRamSize); SVArray(_workRam, _workRamSize); SVArray(_saveRam, _saveRamSize); SVArray(_mapperRam, _mapperRamSize); SVArray(_nametableRam, _ntRamSize); SVArray(_prgMemoryOffset, 0x100); SVArray(_chrMemoryOffset, 0x40); SVArray(_prgMemoryType, 0x100); SVArray(_chrMemoryType, 0x40); SVArray(_prgMemoryAccess, 0x100); SVArray(_chrMemoryAccess, 0x40); SV(_mirroringType); if(_epsm) { SV(_epsm); } if(!s.IsSaving()) { 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(NesConsole* console, RomData& romData) { _console = console; _emu = console->GetEmulator(); _romInfo = romData.Info; _internalRamMask = GetInternalRamSize() - 1; _batteryFilename = GetBatteryFilename(); if(romData.SaveRamSize == -1) { _saveRamSize = HasBattery() ? GetSaveRamSize() : 0; _hasDefaultWorkRam = _saveRamSize > 0; } else if(ForceSaveRamSize()) { _saveRamSize = GetSaveRamSize(); } else { _saveRamSize = romData.SaveRamSize; } if(romData.WorkRamSize == -1) { _workRamSize = HasBattery() ? 0 : GetWorkRamSize(); _hasDefaultWorkRam = _workRamSize > 0; } else if(ForceWorkRamSize()) { _workRamSize = GetWorkRamSize(); } else { _workRamSize = romData.WorkRamSize; } _allowRegisterRead = AllowRegisterRead(); _hasCpuClockHook = EnableCpuClockHook(); _hasCustomReadVram = EnableCustomVramRead(); _hasVramAddressHook = EnableVramAddressHook(); _hasCustomReadRam = EnableCustomRamRead(); 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]; _emu->RegisterMemory(MemoryType::NesPrgRom, _prgRom, _prgSize); _chrRom = new uint8_t[_chrRomSize]; _emu->RegisterMemory(MemoryType::NesChrRom, _chrRom, _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]; _emu->RegisterMemory(MemoryType::NesSaveRam, _saveRam, _saveRamSize); _workRam = new uint8_t[_workRamSize]; _emu->RegisterMemory(MemoryType::NesWorkRam, _workRam, _workRamSize); _mapperRamSize = GetMapperRamSize(); _mapperRam = new uint8_t[_mapperRamSize]; _emu->RegisterMemory(MemoryType::NesMapperRam, _mapperRam, _mapperRamSize); _console->InitializeRam(_saveRam, _saveRamSize); _console->InitializeRam(_workRam, _workRamSize); _console->InitializeRam(_mapperRam, _mapperRamSize); _nametableCount = GetNametableCount(); if(_nametableCount == 0) { _nametableCount = romData.Info.Mirroring == MirroringType::FourScreens ? 4 : 2; } _ntRamSize = _nametableCount * BaseMapper::NametableSize; _nametableRam = new uint8_t[_ntRamSize]; _console->InitializeRam(_nametableRam, _ntRamSize); _emu->RegisterMemory(MemoryType::NesNametableRam, _nametableRam, _ntRamSize); 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; } for(int i = 0; i < 0x40; i++) { _chrPages[i] = nullptr; _chrMemoryOffset[i] = -1; _chrMemoryType[i] = ChrMemoryType::Default; _chrMemoryAccess[i] = MemoryAccessType::NoAccess; } //TODO This includes a hack to make CHR-NVRAM work for properly-specified headers that have just one //type of CHR-RAM. Most correct would be another memory type for CHR-NVRAM, but that would basically //just matter for RacerMate Challenge II, which uses a bike we don't currently support, anyway. int32_t totalChrRam = -1; if(romData.ChrRamSize >= 0 || romData.SaveChrRamSize >= 0) { totalChrRam = (romData.ChrRamSize > 0 ? romData.ChrRamSize : 0) + (romData.SaveChrRamSize > 0 ? romData.SaveChrRamSize : 0); } if(_chrRomSize == 0) { //Assume there is CHR RAM if no CHR ROM exists InitializeChrRam(totalChrRam); } else if(totalChrRam >= 0) { InitializeChrRam(totalChrRam); } else if(GetChrRamSize()) { InitializeChrRam(); } _saveChrRamSize = romData.SaveChrRamSize > 0 ? romData.SaveChrRamSize : 0; 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); } } UpdatePageSizes(); if(_chrRomSize == 0 && _chrRamSize > 0) { //Map CHR RAM to 0x0000-0x1FFF by default when no CHR ROM exists SetPpuMemoryMapping(0x0000, 0x1FFF, 0, ChrMemoryType::ChrRam); } SetupDefaultWorkRam(); SetMirroringType(romData.Info.Mirroring); //Load battery data if present LoadBattery(); _romInfo.HasChrRam = HasChrRam(); if(_romInfo.HasEpsm) { vector adpcmRom; FirmwareHelper::LoadYmf288AdpcmRom(_emu, adpcmRom); _epsm.reset(new Epsm(_emu, _console, adpcmRom)); _hasCpuClockHook = true; } } void BaseMapper::UpdatePageSizes() { //Make sure the page size is no bigger than the size of the ROM itself //Otherwise we will end up reading from unallocated memory _prgRomPageSize = std::min((uint32_t)GetPrgPageSize(), _prgSize); _saveRamPageSize = std::min((uint32_t)GetSaveRamPageSize(), _saveRamSize); _workRamPageSize = std::min((uint32_t)GetWorkRamPageSize(), _workRamSize); _chrRomPageSize = std::min((uint32_t)GetChrPageSize(), _chrRomSize); _chrRamPageSize = std::min((uint32_t)GetChrRamPageSize(), _chrRamSize); } void BaseMapper::InitSpecificMapper(RomData& romData) { InitMapper(); InitMapper(romData); } BaseMapper::BaseMapper() { } BaseMapper::~BaseMapper() { delete[] _chrRam; delete[] _chrRom; delete[] _prgRom; delete[] _saveRam; delete[] _workRam; delete[] _mapperRam; 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, 0x4020, 0xFFFF); ranges.AddHandler(MemoryOperation::Write, 0x4020, 0xFFFF); } } uint8_t* BaseMapper::GetNametable(uint8_t nametableIndex) { if(nametableIndex >= _nametableCount) { #ifdef _DEBUG MessageManager::Log("Invalid nametable index"); #endif return _nametableRam; } return _nametableRam + (nametableIndex * BaseMapper::NametableSize); } void BaseMapper::SetNametable(uint8_t index, uint8_t nametableIndex) { if(nametableIndex >= _nametableCount) { #ifdef _DEBUG MessageManager::Log("Invalid nametable index"); #endif return; } 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; } } GameSystem BaseMapper::GetGameSystem() { return _romInfo.System; } PpuModel BaseMapper::GetPpuModel() { return _romInfo.VsPpuModel; } NesRomInfo BaseMapper::GetRomInfo() { NesRomInfo 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) { return InternalRead(addr); } uint8_t BaseMapper::PeekRam(uint16_t addr) { return DebugReadRam(addr); } uint8_t BaseMapper::DebugReadRam(uint16_t addr) { if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Read) { uint8_t* page = _prgPages[addr >> 8]; return page[(uint8_t)addr]; } else { //assert(false); } //Fake open bus return addr >> 8; } void BaseMapper::WriteRam(uint16_t addr, uint8_t value) { if(_isWriteRegisterAddr[addr]) { if(_hasBusConflicts) { uint8_t prgValue = _prgPages[addr >> 8][(uint8_t)addr]; if(value != prgValue) { _emu->BreakIfDebugging(CpuType::Nes, BreakSource::NesBusConflict); } value &= prgValue; } WriteRegister(addr, value); } else { WritePrgRam(addr, value); } } void BaseMapper::DebugWriteRam(uint16_t addr, uint8_t value) { if(_isWriteRegisterAddr[addr]) { //not supported } else { if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Write) { uint8_t* page = _prgPages[addr >> 8]; page[(uint8_t)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::SetRegion(ConsoleRegion region) { if(_epsm) { _epsm->OnRegionChanged(); } } void BaseMapper::BaseProcessCpuClock() { if(_epsm) { _epsm->Exec(); } } void BaseMapper::ProcessCpuClock() { BaseProcessCpuClock(); } 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::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) { _emu->ProcessPpuWrite(addr, value, MemoryType::NesPpuMemory); MapperWriteVram(addr, value); } void BaseMapper::MapperWriteVram(uint16_t addr, uint8_t value) { InternalWriteVram(addr, value); } void BaseMapper::InternalWriteVram(uint16_t addr, uint8_t value) { if(_chrMemoryAccess[addr >> 8] & MemoryAccessType::Write) { _chrPages[addr >> 8][(uint8_t)addr] = value; } } bool BaseMapper::IsNes20() { return _romInfo.Header.GetRomHeaderVersion() == RomHeaderVersion::Nes2_0; } //Debugger Helper Functions 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); } } AddressInfo BaseMapper::GetAbsoluteAddress(uint16_t relativeAddr) { AddressInfo info; if(relativeAddr < 0x2000) { info.Address = relativeAddr & _internalRamMask; info.Type = MemoryType::NesInternalRam; } else { uint8_t* addr = _prgPages[relativeAddr >> 8] + (uint8_t)relativeAddr; if(addr >= _prgRom && addr < _prgRom + _prgSize) { info.Address = (uint32_t)(addr - _prgRom); info.Type = MemoryType::NesPrgRom; } else if(addr >= _workRam && addr < _workRam + _workRamSize) { info.Address = (uint32_t)(addr - _workRam); info.Type = MemoryType::NesWorkRam; } else if(addr >= _saveRam && addr < _saveRam + _saveRamSize) { info.Address = (uint32_t)(addr - _saveRam); info.Type = MemoryType::NesSaveRam; } else if(addr >= _mapperRam && addr < _mapperRam + _mapperRamSize) { info.Address = (uint32_t)(addr - _mapperRam); info.Type = MemoryType::NesMapperRam; } else { info.Address = -1; info.Type = MemoryType::None; } } return info; } void BaseMapper::GetPpuAbsoluteAddress(uint16_t relativeAddr, AddressInfo& info) { if(relativeAddr >= 0x3F00) { info.Address = relativeAddr & 0x1F; info.Type = MemoryType::NesPaletteRam; } else { uint8_t* addr = _chrPages[relativeAddr >> 8] + (uint8_t)relativeAddr; if(addr >= _chrRom && addr < _chrRom + _chrRomSize) { info.Address = (uint32_t)(addr - _chrRom); info.Type = MemoryType::NesChrRom; } else if(addr >= _chrRam && addr < _chrRam + _chrRamSize) { info.Address = (uint32_t)(addr - _chrRam); info.Type = MemoryType::NesChrRam; } else if(addr >= _mapperRam && addr < _mapperRam + _mapperRamSize) { info.Address = (uint32_t)(addr - _mapperRam); info.Type = MemoryType::NesMapperRam; } else if(addr >= _nametableRam && addr < _nametableRam + _nametableCount * BaseMapper::NametableSize) { info.Address = (uint32_t)(addr - _nametableRam); info.Type = MemoryType::NesNametableRam; } else { info.Address = -1; info.Type = MemoryType::None; } } } AddressInfo BaseMapper::GetPpuAbsoluteAddress(uint32_t relativeAddr) { AddressInfo info; GetPpuAbsoluteAddress(relativeAddr, info); return info; } AddressInfo BaseMapper::GetRelativeAddress(AddressInfo& addr) { uint8_t* ptrAddress; switch(addr.Type) { case MemoryType::NesPrgRom: ptrAddress = _prgRom; break; case MemoryType::NesWorkRam: ptrAddress = _workRam; break; case MemoryType::NesSaveRam: ptrAddress = _saveRam; break; case MemoryType::NesMapperRam: ptrAddress = _mapperRam; break; case MemoryType::NesInternalRam: return { (int32_t)(addr.Address & _internalRamMask), MemoryType::NesMemory }; default: return { GetPpuRelativeAddress(addr), MemoryType::NesPpuMemory }; } ptrAddress += addr.Address; for(int i = 0; i < 256; i++) { uint8_t* pageAddress = _prgPages[i]; if(pageAddress != nullptr && ptrAddress >= pageAddress && ptrAddress <= pageAddress + 0xFF) { return { (int)((i << 8) + (uint32_t)(ptrAddress - pageAddress)), MemoryType::NesMemory }; } } //Address is currently not mapped return { -1, MemoryType::None }; } int32_t BaseMapper::GetPpuRelativeAddress(AddressInfo& addr) { uint8_t* ptrAddress; switch(addr.Type) { case MemoryType::NesChrRom: ptrAddress = _chrRom; break; case MemoryType::NesChrRam: ptrAddress = _chrRam; break; case MemoryType::NesNametableRam: ptrAddress = _nametableRam; break; case MemoryType::NesMapperRam: ptrAddress = _mapperRam; break; case MemoryType::NesPaletteRam: return 0x3F00 | (addr.Address & 0x1F); break; default: return -1; } ptrAddress += addr.Address; 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 = _chrRomSize; state.ChrRamSize = _chrRamSize; state.PrgPageCount = GetPrgPageCount(); state.PrgPageSize = _prgRomPageSize; state.ChrPageCount = GetChrRomPageCount(); state.ChrPageSize = _chrRomPageSize; state.ChrRamPageSize = _chrRamPageSize; 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(); vector entries = GetMapperStateEntries(); assert(entries.size() <= 200); state.CustomEntryCount = (uint32_t)entries.size(); for(int i = 0; i < entries.size(); i++) { state.CustomEntries[i] = entries[i]; } return state; } void BaseMapper::GetRomFileData(vector& out, bool asIpsFile, uint8_t* header) { if(header) { //Get original rom with edited header vector originalFile; _emu->GetRomInfo().RomFile.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 newFile; newFile.insert(newFile.end(), (uint8_t*)&_romInfo.Header, ((uint8_t*)&_romInfo.Header) + sizeof(NesHeader)); newFile.insert(newFile.end(), _prgRom, _prgRom + _prgSize); if(HasChrRom()) { newFile.insert(newFile.end(), _chrRom, _chrRom + _chrRomSize); } //Get edited rom if(asIpsFile) { vector originalFile; _emu->GetRomInfo().RomFile.ReadFile(originalFile); vector patchData = IpsPatcher::CreatePatch(originalFile, newFile); out.insert(out.end(), patchData.begin(), patchData.end()); } else { out.insert(out.end(), newFile.begin(), newFile.end()); } } } vector BaseMapper::GetPrgChrCopy() { vector data; data.resize(_prgSize + _chrRomSize); memcpy(data.data(), _prgRom, _prgSize); memcpy(data.data() + _prgSize, _chrRom, _chrRomSize); return data; } void BaseMapper::RestorePrgChrBackup(vector& backupData) { memcpy(_prgRom, backupData.data(), _prgSize); 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(BaseMapper* mapper) { if(_prgSize == mapper->_prgSize && _chrRomSize == mapper->_chrRomSize) { memcpy(_prgRom, mapper->_prgRom, _prgSize); memcpy(_chrRom, mapper->_chrRom, _chrRomSize); } } void BaseMapper::SwapMemoryAccess(BaseMapper* sub, bool mainHasAccess) { //Used by VS Dualsystem to share memory access between 2 consoles if(_saveRamSize == 0 && _workRamSize == 0) { return; } uint8_t* memory = HasBattery() ? _saveRam : _workRam; uint32_t size = HasBattery() ? _saveRamSize : _workRamSize; SetCpuMemoryMapping(0x6000, 0x7FFF, memory, 0, size, mainHasAccess ? MemoryAccessType::ReadWrite : MemoryAccessType::NoAccess); sub->SetCpuMemoryMapping(0x6000, 0x7FFF, memory, 0, size, mainHasAccess ? MemoryAccessType::NoAccess : MemoryAccessType::ReadWrite); } void BaseMapper::LoadRomPatch(vector& orgPrgRom, vector* orgChrRom) { if(!_console->GetNesConfig().OverwriteOriginalRom) { //Apply save data (saved as an IPS file), if found vector ipsData = _emu->GetBatteryManager()->LoadBattery(".ips"); if(!ipsData.empty()) { vector patchedPrgRom; if(IpsPatcher::PatchBuffer(ipsData, orgPrgRom, patchedPrgRom)) { memcpy(_prgRom, patchedPrgRom.data(), _prgSize); } } if(_chrRomSize > 0 && orgChrRom) { ipsData = _emu->GetBatteryManager()->LoadBattery(".chr.ips"); if(!ipsData.empty()) { vector patchedChrRom; if(IpsPatcher::PatchBuffer(ipsData, *orgChrRom, patchedChrRom)) { memcpy(_chrRom, patchedChrRom.data(), _chrRomSize); } } } } } void BaseMapper::SaveRom(vector& orgPrgRom, vector* orgChrRom) { if(_console->GetNesConfig().OverwriteOriginalRom) { bool needUpdate = memcmp(orgPrgRom.data(), _prgRom, _prgSize) != 0; if(_chrRomSize > 0 && orgChrRom) { needUpdate |= memcmp(orgChrRom->data(), _chrRom, _chrRomSize) != 0; } if(needUpdate) { ofstream file(_emu->GetRomInfo().RomFile, ios::binary); if(!_romInfo.IsHeaderlessRom) { file.write((char*)&_romInfo.Header, sizeof(NesHeader)); } file.write((char*)_prgRom, _prgSize); if(_chrRomSize > 0 && orgChrRom) { file.write((char*)_chrRom, _chrRomSize); } } } else { vector prgRom = vector(_prgRom, _prgRom + _prgSize); vector ipsData = IpsPatcher::CreatePatch(orgPrgRom, prgRom); if(ipsData.size() > 8) { _emu->GetBatteryManager()->SaveBattery(".ips", ipsData.data(), (uint32_t)ipsData.size()); } if(_chrRomSize > 0 && orgChrRom) { vector chrRom = vector(_chrRom, _chrRom + _chrRomSize); ipsData = IpsPatcher::CreatePatch(*orgChrRom, chrRom); if(ipsData.size() > 8) { _emu->GetBatteryManager()->SaveBattery(".chr.ips", ipsData.data(), (uint32_t)ipsData.size()); } } } } void BaseMapper::SerializeRomDiff(Serializer& s, vector& orgPrgRom, vector* orgChrRom) { if(s.GetFormat() != SerializeFormat::Binary) { //Skip this completely for Lua return; } if(s.IsSaving()) { vector prgRom = vector(_prgRom, _prgRom + _prgSize); vector ipsData = IpsPatcher::CreatePatch(orgPrgRom, prgRom); SVVector(ipsData); if(_chrRomSize > 0 && orgChrRom) { vector chrRom = vector(_chrRom, _chrRom + _chrRomSize); vector chrIpsData = IpsPatcher::CreatePatch(*orgChrRom, chrRom); SVVector(chrIpsData); } } else { vector ipsData; SVVector(ipsData); vector patchedPrgRom; if(IpsPatcher::PatchBuffer(ipsData, orgPrgRom, patchedPrgRom)) { memcpy(_prgRom, patchedPrgRom.data(), _prgSize); } if(_chrRomSize > 0 && orgChrRom) { vector chrIpsData; SVVector(chrIpsData); vector patchedChrRom; if(IpsPatcher::PatchBuffer(chrIpsData, *orgChrRom, patchedChrRom)) { memcpy(_chrRom, patchedChrRom.data(), _chrRomSize); } } } }