Files
MesenCE/Core/PCE/PceMemoryManager.cpp

448 lines
12 KiB
C++

#include "pch.h"
#include "PCE/PceConsole.h"
#include "PCE/PceMemoryManager.h"
#include "PCE/PceVpc.h"
#include "PCE/PceVce.h"
#include "PCE/PceTimer.h"
#include "PCE/PcePsg.h"
#include "PCE/PceCpu.h"
#include "PCE/PceControlManager.h"
#include "PCE/CdRom/PceCdRom.h"
#include "Shared/MessageManager.h"
#include "Shared/EmuSettings.h"
#include "Utilities/Serializer.h"
PceMemoryManager::PceMemoryManager(Emulator* emu, PceConsole* console, PceVpc* vpc, PceVce* vce, PceControlManager* controlManager, PcePsg* psg, PceTimer* timer, IPceMapper* mapper, PceCdRom* cdrom, vector<uint8_t>& romData, uint32_t cardRamSize, bool cdromUnitEnabled)
{
_emu = emu;
_cheatManager = _emu->GetCheatManager();
_console = console;
_vpc = vpc;
_vce = vce;
_psg = psg;
_timer = timer;
_mapper = mapper;
_cdrom = cdrom;
_controlManager = controlManager;
_prgRomSize = (uint32_t)romData.size();
_prgRom = new uint8_t[_prgRomSize];
_workRamSize = console->IsSuperGrafx() ? 0x8000 : 0x2000;
_workRam = new uint8_t[_workRamSize];
_unmappedBank = new uint8_t[0x2000];
memset(_unmappedBank, 0xFF, 0x2000);
memcpy(_prgRom, romData.data(), _prgRomSize);
_console->InitializeRam(_workRam, _workRamSize);
_cdromUnitEnabled = cdromUnitEnabled;
if(cardRamSize > 0) {
if(cardRamSize == 0x30000) {
//Super/Arcade CD-ROM
_cardRamStartBank = 0x68;
_cardRamEndBank = 0x7F;
} else if(cardRamSize == 0x8000) {
//Populous
_cardRamStartBank = 0x40;
_cardRamEndBank = 0x5F;
}
_cardRamSize = cardRamSize;
_cardRam = new uint8_t[cardRamSize];
_console->InitializeRam(_cardRam, _cardRamSize);
_emu->RegisterMemory(MemoryType::PceCardRam, _cardRam, _cardRamSize);
}
_emu->RegisterMemory(MemoryType::PcePrgRom, _prgRom, _prgRomSize);
_emu->RegisterMemory(MemoryType::PceWorkRam, _workRam, _workRamSize);
_cdromRam = (uint8_t*)_emu->GetMemory(MemoryType::PceCdromRam).Memory;
_saveRam = (uint8_t*)_emu->GetMemory(MemoryType::PceSaveRam).Memory;
//CPU boots up in slow speed
_state.FastCpuSpeed = false;
UpdateExecCallback();
//Set to 0 on power on
_state.DisabledIrqs = 0;
//MPR 7 is set to 0 on power on
_state.Mpr[7] = 0;
if(_emu->GetSettings()->GetPcEngineConfig().EnableRandomPowerOnState) {
//Other MPR registers are random
//Note this appears to break some games?
for(int i = 0; i < 7; i++) {
_state.Mpr[i] = RandomHelper::GetValue(0, 255);
}
}
//Map ROM to all 128 banks
uint32_t bankCount = _prgRomSize / 0x2000;
if(bankCount == 0x30) {
//384KB games - Mirrored as 256x2, 128x4
uint32_t bankOffsets[8] = { 0, 0x10, 0, 0x10, 0x20, 0x20, 0x20, 0x20 };
UpdateMappings(bankOffsets);
} else if(bankCount == 0x40) {
//512KB games - Mirrored as either 256x2 + 256x2, or 512x2
//Mirror them as 256 + 256x3 for compatibility
uint32_t bankOffsets[8] = { 0, 0x10, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30 };
UpdateMappings(bankOffsets);
} else if(bankCount == 0x60) {
//768KB games - Mirrored as 512x1 + 256x2
uint32_t bankOffsets[8] = { 0, 0x10, 0x20, 0x30, 0x40, 0x50, 0x40, 0x50 };
UpdateMappings(bankOffsets);
} else {
//Use default mirroring for anything else
uint32_t bankOffsets[8] = { 0, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70 };
UpdateMappings(bankOffsets);
}
}
PceMemoryManager::~PceMemoryManager()
{
delete[] _prgRom;
delete[] _workRam;
delete[] _cardRam;
delete[] _unmappedBank;
}
void PceMemoryManager::SetSpeed(bool slow)
{
_state.FastCpuSpeed = !slow;
UpdateExecCallback();
}
void PceMemoryManager::UpdateMappings(uint32_t bankOffsets[8])
{
int bankCount = _prgRomSize / 0x2000;
for(int i = 0; i <= 0x7F; i++) {
//00 - 7F
int bank = (bankOffsets[i >> 4] + (i & 0x0F)) % bankCount;
_readBanks[i] = _prgRom + (bank * 0x2000);
_bankMemType[i] = MemoryType::PcePrgRom;
}
for(int i = 0x80; i <= 0xFF; i++) {
//80 - FF
_readBanks[i] = _unmappedBank;
_bankMemType[i] = MemoryType::None;
}
for(int i = 0; i < 4; i++) {
//F8 - FB
_readBanks[0xF8 + i] = _workRam + ((i * 0x2000) % _workRamSize);
_writeBanks[0xF8 + i] = _workRam + ((i * 0x2000) % _workRamSize);
_bankMemType[0xF8 + i] = MemoryType::PceWorkRam;
}
if(_cardRam) {
for(uint32_t i = _cardRamStartBank; i <= _cardRamEndBank; i++) {
//68-7F (cdrom) or 40-5F (populous)
_readBanks[i] = _cardRam + (((i - _cardRamStartBank) * 0x2000) % _cardRamSize);
_writeBanks[i] = _cardRam + (((i - _cardRamStartBank) * 0x2000) % _cardRamSize);
_bankMemType[i] = MemoryType::PceCardRam;
}
}
UpdateCdRomBanks();
}
void PceMemoryManager::UpdateCdRomBanks()
{
if(_cdrom) {
_cdrom->InitMemoryBanks(_readBanks, _writeBanks, _bankMemType, _unmappedBank);
}
}
void PceMemoryManager::UpdateExecCallback()
{
if(_cdromUnitEnabled) {
if(_console->IsSuperGrafx()) {
_exec = &PceMemoryManager::ExecTemplate<true, true>;
} else {
_exec = &PceMemoryManager::ExecTemplate<true, false>;
}
} else {
if(_console->IsSuperGrafx()) {
_exec = &PceMemoryManager::ExecTemplate<false, true>;
} else {
_exec = &PceMemoryManager::ExecTemplate<false, false>;
}
}
_fastExec = _exec;
if(!_state.FastCpuSpeed) {
_exec = &PceMemoryManager::ExecSlow;
}
}
template<bool hasCdRom, bool isSuperGrafx>
void PceMemoryManager::ExecTemplate()
{
_state.CycleCount += 3;
_timer->Exec();
if constexpr(isSuperGrafx) {
_vpc->ExecSuperGrafx();
} else {
_vpc->Exec();
}
if constexpr(hasCdRom) {
_cdrom->Exec();
}
}
void PceMemoryManager::ExecSlow()
{
(this->*_fastExec)();
(this->*_fastExec)();
(this->*_fastExec)();
(this->*_fastExec)();
}
uint8_t PceMemoryManager::ReadRegister(uint16_t addr)
{
if(addr <= 0x3FF) {
//VDC
Exec(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
return _vpc->Read(addr);
} else if(addr <= 0x7FF) {
//VCE
Exec(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
_vpc->DrawScanline();
return _vce->Read(addr);
} else if(addr <= 0xBFF) {
//PSG
return _state.IoBuffer;
} else if(addr <= 0xFFF) {
//Timer
_state.IoBuffer = (_state.IoBuffer & 0x80) | (_timer->Read(addr) & 0x7F);
return _state.IoBuffer;
} else if(addr <= 0x13FF) {
//IO
//Some NA games crash is bit 6 is not set (region locking) - e.g Order of the Griffon
//When in Auto mode, pretend this is a turbografx console for compatibilty
PcEngineConfig& cfg = _emu->GetSettings()->GetPcEngineConfig();
PceConsoleType consoleType = cfg.ConsoleType;
bool isTurboGrafx = consoleType == PceConsoleType::Auto || consoleType == PceConsoleType::TurboGrafx;
//Some games use the CDROM addon's save ram to save data and will check bit 7 for presence of the CDROM
//Unless the UI option to disable the save ram for hucard games is enabled, always report that the CDROM is connected
bool cdromConnected = _cdrom != nullptr;
_state.IoBuffer =
(cdromConnected ? 0 : 0x80) |
(isTurboGrafx ? 0 : 0x40) |
0x30 |
_controlManager->ReadInputPort();
return _state.IoBuffer;
} else if(addr <= 0x17FF) {
//IRQ
switch(addr & 0x03) {
case 0:
case 1:
break;
case 2: _state.IoBuffer = (_state.IoBuffer & 0xF8) | (_state.DisabledIrqs & 0x07); break;
case 3: _state.IoBuffer = (_state.IoBuffer & 0xF8) | (_state.ActiveIrqs & 0x07); break;
}
return _state.IoBuffer;
} else if(addr <= 0x1BFF) {
return _cdrom ? _cdrom->Read(addr) : 0xFF;
} else if(_console->GetRomFormat() == RomFormat::PceHes && addr <= 0x1C01) {
//Infinite BRA loop
return addr & 0x01 ? 0xFE : 0x80;
}
LogDebug("[Debug] Read - missing handler: $" + HexUtilities::ToHex(addr));
return 0xFF;
}
void PceMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
{
if(addr <= 0x3FF) {
_vpc->Write(addr, value);
_console->GetCpu()->RunIdleCpuCycle(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
} else if(addr <= 0x7FF) {
_vpc->DrawScanline();
_vce->Write(addr, value);
_console->GetCpu()->RunIdleCpuCycle(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
} else if(addr <= 0xBFF) {
//PSG
_psg->Write(addr, value);
_state.IoBuffer = value;
} else if(addr <= 0xFFF) {
//Timer
_timer->Write(addr, value);
_state.IoBuffer = value;
} else if(addr <= 0x13FF) {
//IO
_controlManager->WriteInputPort(value);
_state.IoBuffer = value;
} else if(addr <= 0x17FF) {
//IRQ
switch(addr & 0x03) {
case 0:
case 1:
LogDebug("[Debug] Write - missing handler: $" + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value));
break;
case 2: _state.DisabledIrqs = value & 0x07; break;
case 3: ClearIrqSource(PceIrqSource::TimerIrq); break;
}
_state.IoBuffer = value;
} else if(addr <= 0x1BFF) {
if(_cdrom) {
_cdrom->Write(addr, value);
}
} else {
LogDebug("[Debug] Write - missing handler: $" + HexUtilities::ToHex(addr) + " = " + HexUtilities::ToHex(value));
}
}
void PceMemoryManager::WriteVdc(uint16_t addr, uint8_t value)
{
_vpc->StVdcWrite(addr, value);
_console->GetCpu()->RunIdleCpuCycle(); //CPU is delayed by 1 CPU cycle when reading/writing to VDC/VCE
}
uint8_t PceMemoryManager::DebugRead(uint16_t addr)
{
uint8_t bank = _state.Mpr[(addr & 0xE000) >> 13];
if(bank != 0xFF) {
return _readBanks[bank][addr & 0x1FFF];
} else {
//TODO read registers without side effects
if(_console->GetRomFormat() == RomFormat::PceHes && addr >= 0x1C00 && addr <= 0x1C01) {
//Patch to fix trace logger for HES files
return addr & 0x01 ? 0xFE : 0x80;
}
}
return 0xFF;
}
void PceMemoryManager::DebugWrite(uint16_t addr, uint8_t value)
{
uint8_t bank = _state.Mpr[(addr & 0xE000) >> 13];
if(bank == 0xF7) {
if(_writeBanks[0xF7] && (addr & 0x1FFF) <= 0x7FF) {
//Only allow writes to the first 2kb - save RAM is not mirrored
//"Death Bringer" breaks if save RAM is mirrored.
_writeBanks[0xF7][addr & 0x7FF] = value;
}
} else if(bank != 0xFF) {
uint8_t* data = _writeBanks[bank];
if(data) {
data[addr & 0x1FFF] = value;
}
} else {
//TODO write registers
}
}
void PceMemoryManager::SetMprValue(uint8_t regSelect, uint8_t value)
{
if(regSelect == 0) {
return;
}
_state.MprReadBuffer = value;
for(int i = 0; i < 8; i++) {
if(regSelect & (1 << i)) {
_state.Mpr[i] = value;
}
}
}
uint8_t PceMemoryManager::GetMprValue(uint8_t regSelect)
{
if(regSelect == 0) {
//"If an operand of $00 is used, the accumulator is loaded with the last value that was written with TAM"
return _state.MprReadBuffer;
}
uint8_t value = 0;
for(int i = 0; i < 8; i++) {
//"If multiple bits are set in the operand to TMA, the values from several MPRs are combined togetherand returned."
if(regSelect & (1 << i)) {
value |= _state.Mpr[i];
}
}
_state.MprReadBuffer = value;
return value;
}
AddressInfo PceMemoryManager::GetAbsoluteAddress(uint32_t relAddr)
{
uint8_t bank = _state.Mpr[(relAddr & 0xE000) >> 13];
uint32_t absAddr;
switch(_bankMemType[bank]) {
case MemoryType::PcePrgRom: absAddr = (uint32_t)(_readBanks[bank] - _prgRom) + (relAddr & 0x1FFF); break;
case MemoryType::PceWorkRam: absAddr = (uint32_t)(_readBanks[bank] - _workRam) + (relAddr & 0x1FFF); break;
case MemoryType::PceSaveRam:
if((relAddr & 0x1FFF) <= 0x7FF) {
absAddr = (uint32_t)(_readBanks[bank] - _saveRam) + (relAddr & 0x7FF);
} else {
//Unmapped past the first 2kb
return { -1, MemoryType::None };
}
break;
case MemoryType::PceCdromRam: absAddr = (uint32_t)(_readBanks[bank] - _cdromRam) + (relAddr & 0x1FFF); break;
case MemoryType::PceCardRam: absAddr = (uint32_t)(_readBanks[bank] - _cardRam) + (relAddr & 0x1FFF); break;
default: return { -1, MemoryType::None };
}
return { (int32_t)absAddr, _bankMemType[bank] };
}
AddressInfo PceMemoryManager::GetRelativeAddress(AddressInfo absAddr, uint16_t pc)
{
//Start with the bank that the CPU is executing from
//This is to favor any mirror that exists in the current bank
//instead of always picking the first mirror, when mirrors exist.
int startBank = pc >> 13;
for(int i = 0; i < 8; i++) {
int bank = (startBank + i) & 0x07;
AddressInfo bankStart = GetAbsoluteAddress(bank * 0x2000);
if(bankStart.Type == absAddr.Type && bankStart.Address == (absAddr.Address & ~0x1FFF)) {
if(bankStart.Type == MemoryType::PceSaveRam && (absAddr.Address & 0x1FFF) >= 0x800) {
//unmapped
break;
}
return { (bank << 13) | (absAddr.Address & 0x1FFF), MemoryType::PceMemory };
}
}
return { -1, MemoryType::None };
}
void PceMemoryManager::Serialize(Serializer& s)
{
SVArray(_workRam, _workRamSize);
SVArray(_cardRam, _cardRamSize);
SV(_state.ActiveIrqs);
SV(_state.CycleCount);
SV(_state.DisabledIrqs);
SV(_state.FastCpuSpeed);
SV(_state.IoBuffer);
for(int i = 0; i < 8; i++) {
SVI(_state.Mpr[i]);
}
SV(_state.MprReadBuffer);
if(!s.IsSaving()) {
UpdateExecCallback();
}
}