583 lines
13 KiB
C++
583 lines
13 KiB
C++
#include "board.h"
|
|
|
|
// system includes
|
|
#include <algorithm>
|
|
|
|
// local includes
|
|
#include "rom.h"
|
|
|
|
Board::Board(NesEmulator &emu, const Rom &rom) :
|
|
m_emu(emu),
|
|
m_rom(rom)
|
|
{
|
|
// PRG RAM
|
|
m_sramSaveRequired = false;
|
|
|
|
/* foreach from db */
|
|
{
|
|
const int SIZE = prgRam8KbDefaultBlkCount() * 8;
|
|
const bool BATTERY = true;
|
|
|
|
if (BATTERY)
|
|
m_sramSaveRequired = true;
|
|
|
|
if (SIZE > 0)
|
|
{
|
|
int kb4_count = SIZE / 2;
|
|
for (int i = 0; i < kb4_count; i++)
|
|
m_prgRam.append({
|
|
/* .ram = */ {},
|
|
/* .enabled = */ true,
|
|
/* .writeable = */ true,
|
|
/* .battery = */ BATTERY
|
|
});
|
|
}
|
|
}
|
|
|
|
// TRAINER, it should be copied into the RAM blk at 0x7000. In this case, it should be copied into ram blk 3
|
|
if (rom.hasTrainer)
|
|
std::copy(std::begin(rom.trainer), std::end(rom.trainer), std::begin(m_prgRam[3].ram));
|
|
|
|
// CHR RAM
|
|
// Map 8 Kb for now
|
|
const int chr_ram_banks_1k = chrRom1KbDefaultBlkCount(); // from db
|
|
m_chrRam.reserve(chr_ram_banks_1k);
|
|
for (int i = 0; i < chr_ram_banks_1k; i++)
|
|
m_chrRam.append({
|
|
/* .ram = */ {},
|
|
/* .enabled = */ true,
|
|
/* .writeable = */ true,
|
|
/* .battery = */ false
|
|
});
|
|
|
|
// 6 Nametables
|
|
}
|
|
|
|
Board::~Board()
|
|
{
|
|
}
|
|
|
|
QString Board::name() const
|
|
{
|
|
throw std::runtime_error("has not been implemented");
|
|
}
|
|
|
|
quint8 Board::mapper() const
|
|
{
|
|
throw std::runtime_error("has not been implemented");
|
|
}
|
|
|
|
void Board::hardReset()
|
|
{
|
|
// Use the configuration of mapper 0 (NRAM).
|
|
// PRG Switching
|
|
// Toggle ram/rom
|
|
// Switch 16KB ram, from 0x4000 into 0x7000
|
|
toggle16kPrgRam(true, PRGArea::Area4000);
|
|
switch16kPrg(0, PRGArea::Area4000);
|
|
// Switch 32KB rom, from 0x8000 into 0xF000
|
|
toggle32kPrgRam(false, PRGArea::Area8000);
|
|
switch32kPrg(0, PRGArea::Area8000);
|
|
|
|
// CHR Switching
|
|
// Pattern tables
|
|
toggle8kChrRam((chrRom1KbCount() == 0));
|
|
switch8kChr(0);
|
|
|
|
// Nametables
|
|
switch1kNmt(m_rom.mirroring);
|
|
}
|
|
|
|
void Board::softReset()
|
|
{
|
|
}
|
|
|
|
quint8 Board::_readEx(quint16 address)
|
|
{
|
|
int prgTmpArea = address >> 12 & 0xF;
|
|
if (m_prgAreaBlk[prgTmpArea].ram)
|
|
{
|
|
int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask();
|
|
if (m_prgRam[prgTmpIndex].enabled)
|
|
return m_prgRam[prgTmpIndex].ram[address & 0xFFF];
|
|
else
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRom4KbMask();
|
|
return m_rom.prg[prgTmpIndex][address & 0xFFF];
|
|
}
|
|
}
|
|
|
|
quint8 Board::readEx(quint16 address)
|
|
{
|
|
auto result = _readEx(address);
|
|
return result;
|
|
}
|
|
|
|
void Board::writeEx(quint16 address, quint8 value)
|
|
{
|
|
int prgTmpArea = address >> 12 & 0xF;
|
|
if (m_prgAreaBlk[prgTmpArea].ram)
|
|
{
|
|
int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask();
|
|
|
|
if (m_prgRam[prgTmpIndex].enabled)
|
|
if (m_prgRam[prgTmpIndex].writeable)
|
|
m_prgRam[prgTmpIndex].ram[address & 0xFFF] = value;
|
|
}
|
|
}
|
|
|
|
quint8 Board::_readSrm(quint16 address)
|
|
{
|
|
int prgTmpArea = address >> 12 & 0xF;
|
|
if (m_prgAreaBlk[prgTmpArea].ram)
|
|
{
|
|
int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask();
|
|
if (m_prgRam[prgTmpIndex].enabled)
|
|
return m_prgRam[prgTmpIndex].ram[address & 0xFFF];
|
|
else
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRom4KbMask();
|
|
return m_rom.prg[prgTmpIndex][address & 0xFFF];
|
|
}
|
|
}
|
|
|
|
quint8 Board::readSrm(quint16 address)
|
|
{
|
|
auto result = _readSrm(address);
|
|
return result;
|
|
}
|
|
|
|
void Board::writeSrm(quint16 address, quint8 value)
|
|
{
|
|
int prgTmpArea = address >> 12 & 0xF;
|
|
if (m_prgAreaBlk[prgTmpArea].ram)
|
|
{
|
|
int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask();
|
|
|
|
if (m_prgRam[prgTmpIndex].enabled)
|
|
if (m_prgRam[prgTmpIndex].writeable)
|
|
m_prgRam[prgTmpIndex].ram[address & 0xFFF] = value;
|
|
}
|
|
}
|
|
|
|
quint8 Board::_readPrg(quint16 address)
|
|
{
|
|
int prgTmpArea = address >> 12 & 0xF;
|
|
if (m_prgAreaBlk[prgTmpArea].ram)
|
|
{
|
|
const int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask();
|
|
const int temp = address & 0xFFF;
|
|
if (m_prgRam[prgTmpIndex].enabled)
|
|
return m_prgRam[prgTmpIndex].ram[temp];
|
|
else
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
const int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRom4KbMask();
|
|
const int temp = address & 0xFFF;
|
|
return m_rom.prg[prgTmpIndex][temp];
|
|
}
|
|
|
|
/*
|
|
if (IsGameGenieActive)
|
|
{
|
|
foreach (GameGenieCode code in GameGenieCodes)
|
|
{
|
|
if (!code.Enabled)
|
|
continue;
|
|
|
|
if (code.Address != addr)
|
|
continue;
|
|
|
|
if (!code.IsCompare || code.Compare == return_value)
|
|
return code.Value;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
quint8 Board::readPrg(quint16 address)
|
|
{
|
|
auto result = _readPrg(address);
|
|
return result;
|
|
}
|
|
|
|
void Board::writePrg(quint16 address, quint8 value)
|
|
{
|
|
int prgTmpArea = address >> 12 & 0xF;
|
|
if (m_prgAreaBlk[prgTmpArea].ram)
|
|
{
|
|
int prgTmpIndex = m_prgAreaBlk[prgTmpArea].index & prgRam4KbMask();
|
|
|
|
if (m_prgRam[prgTmpIndex].enabled)
|
|
if (m_prgRam[prgTmpIndex].writeable)
|
|
m_prgRam[prgTmpIndex].ram[address & 0xFFF] = value;
|
|
}
|
|
}
|
|
|
|
quint8 Board::_readChr(quint16 address)
|
|
{
|
|
// 00-07 means patterntables
|
|
// 08-11 means nametables, should not included
|
|
// 12-15 nametables mirrors, should not included as well
|
|
int chrTmpArea = (address >> 10) & 0x7;// 0x0000 - 0x1FFF, 0-7.
|
|
int chrTmpIndex = m_chrAreaBlk[chrTmpArea].index;
|
|
if (m_chrAreaBlk[chrTmpArea].ram)
|
|
{
|
|
chrTmpIndex &= chrRam1KbMask();
|
|
if (m_chrRam[chrTmpIndex].enabled)
|
|
return m_chrRam[chrTmpIndex].ram[address & 0x3FF];
|
|
else
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
chrTmpIndex &= chrRom1KbMask();
|
|
return m_rom.chr[chrTmpIndex][address & 0x3FF];
|
|
}
|
|
}
|
|
|
|
quint8 Board::readChr(quint16 address)
|
|
{
|
|
auto result = _readChr(address);
|
|
return result;
|
|
}
|
|
|
|
void Board::writeChr(quint16 address, quint8 value)
|
|
{
|
|
// 00-07 means patterntables
|
|
// 08-11 means nametables, should not included
|
|
// 12-15 nametables mirrors, should not included as well
|
|
int chrTmpArea = (address >> 10) & 0x7;// 0x0000 - 0x1FFF, 0-7.
|
|
|
|
if (m_chrAreaBlk[chrTmpArea].ram)
|
|
{
|
|
int chrTmpIndex = m_chrAreaBlk[chrTmpArea].index & chrRam1KbMask();
|
|
if (m_chrRam[chrTmpIndex].enabled)
|
|
if (m_chrRam[chrTmpIndex].writeable)
|
|
m_chrRam[chrTmpIndex].ram[address & 0x3FF] = value;
|
|
}
|
|
}
|
|
|
|
quint8 Board::_readNmt(quint16 address)
|
|
{
|
|
int nmtTmpArea = (address >> 10) & 0x3;// 0x2000 - 0x2C00, 0-3.
|
|
int nmtTmpIndex = m_nmtRam[nmtTmpArea].index;
|
|
|
|
return m_nmtRam[nmtTmpIndex].ram[address & 0x3FF];
|
|
}
|
|
|
|
quint8 Board::readNmt(quint16 address)
|
|
{
|
|
auto result = _readNmt(address);
|
|
return result;
|
|
}
|
|
|
|
void Board::writeNmt(quint16 address, quint8 value)
|
|
{
|
|
int nmtTmpArea = (address >> 10) & 0x3;// 0x2000 - 0x2C00, 0-3.
|
|
int nmtTmpIndex = m_nmtRam[nmtTmpArea].index;
|
|
|
|
m_nmtRam[nmtTmpIndex].ram[address & 0x3FF] = value;
|
|
}
|
|
|
|
void Board::onPpuAddressUpdate(quint16 address)
|
|
{
|
|
if (ppuA12ToggleTimerEnabled())
|
|
{
|
|
m_oldVramAddress = m_newVramAddress;
|
|
m_newVramAddress = address & 0x1000;
|
|
if (ppuA12TogglesOnRaisingEdge())
|
|
{
|
|
if (m_oldVramAddress < m_newVramAddress)
|
|
{
|
|
if (m_ppuCyclesTimer > 8)
|
|
onPpuA12RaisingEdge();
|
|
|
|
m_ppuCyclesTimer = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_oldVramAddress > m_newVramAddress)
|
|
{
|
|
if (m_ppuCyclesTimer > 8)
|
|
onPpuA12RaisingEdge();
|
|
|
|
m_ppuCyclesTimer = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Board::onCpuClock()
|
|
{
|
|
}
|
|
|
|
void Board::onPpuClock()
|
|
{
|
|
if (ppuA12ToggleTimerEnabled())
|
|
m_ppuCyclesTimer++;
|
|
}
|
|
|
|
void Board::onPpuA12RaisingEdge()
|
|
{
|
|
}
|
|
|
|
void Board::onPpuScanlineTick()
|
|
{
|
|
}
|
|
|
|
void Board::onApuClockDuration()
|
|
{
|
|
}
|
|
|
|
void Board::onApuClockEnvelope()
|
|
{
|
|
}
|
|
|
|
void Board::onApuClockSingle()
|
|
{
|
|
}
|
|
|
|
void Board::onApuClock()
|
|
{
|
|
}
|
|
|
|
double Board::apuGetSample() const
|
|
{
|
|
return 0.;
|
|
}
|
|
|
|
void Board::apuApplyChannelsSettings()
|
|
{
|
|
}
|
|
|
|
void Board::readState(QDataStream &dataStream)
|
|
{
|
|
Q_UNUSED(dataStream)
|
|
}
|
|
|
|
void Board::writeState(QDataStream &dataStream) const
|
|
{
|
|
Q_UNUSED(dataStream)
|
|
}
|
|
|
|
int Board::prgRam8KbDefaultBlkCount() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int Board::chrRom1KbDefaultBlkCount() const
|
|
{
|
|
return 8;
|
|
}
|
|
|
|
bool Board::ppuA12ToggleTimerEnabled() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool Board::ppuA12TogglesOnRaisingEdge() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void Board::switch4kPrg(int index, PRGArea area)
|
|
{
|
|
m_prgAreaBlk[int(area)].index = index;
|
|
}
|
|
|
|
void Board::switch8kPrg(int index, PRGArea area)
|
|
{
|
|
index *= 2;
|
|
m_prgAreaBlk[int(area)].index = index;
|
|
m_prgAreaBlk[int(area) + 1].index = index + 1;
|
|
}
|
|
|
|
void Board::switch16kPrg(int index, PRGArea area)
|
|
{
|
|
index *= 4;
|
|
m_prgAreaBlk[int(area)].index = index;
|
|
m_prgAreaBlk[int(area) + 1].index = index + 1;
|
|
m_prgAreaBlk[int(area) + 2].index = index + 2;
|
|
m_prgAreaBlk[int(area) + 3].index = index + 3;
|
|
}
|
|
|
|
void Board::switch32kPrg(int index, PRGArea area)
|
|
{
|
|
index *= 8;
|
|
m_prgAreaBlk[int(area)].index = index;
|
|
m_prgAreaBlk[int(area) + 1].index = index + 1;
|
|
m_prgAreaBlk[int(area) + 2].index = index + 2;
|
|
m_prgAreaBlk[int(area) + 3].index = index + 3;
|
|
m_prgAreaBlk[int(area) + 4].index = index + 4;
|
|
m_prgAreaBlk[int(area) + 5].index = index + 5;
|
|
m_prgAreaBlk[int(area) + 6].index = index + 6;
|
|
m_prgAreaBlk[int(area) + 7].index = index + 7;
|
|
}
|
|
|
|
void Board::toggle4kPrgRam(bool ram, PRGArea area)
|
|
{
|
|
m_prgAreaBlk[int(area)].ram = ram;
|
|
}
|
|
|
|
void Board::toggle8kPrgRam(bool ram, PRGArea area)
|
|
{
|
|
m_prgAreaBlk[int(area)].ram = ram;
|
|
m_prgAreaBlk[int(area) + 1].ram = ram;
|
|
}
|
|
|
|
void Board::toggle16kPrgRam(bool ram, PRGArea area)
|
|
{
|
|
m_prgAreaBlk[int(area)].ram = ram;
|
|
m_prgAreaBlk[int(area) + 1].ram = ram;
|
|
m_prgAreaBlk[int(area) + 2].ram = ram;
|
|
m_prgAreaBlk[int(area) + 3].ram = ram;
|
|
}
|
|
|
|
void Board::toggle32kPrgRam(bool ram, PRGArea area)
|
|
{
|
|
m_prgAreaBlk[int(area)].ram = ram;
|
|
m_prgAreaBlk[int(area) + 1].ram = ram;
|
|
m_prgAreaBlk[int(area) + 2].ram = ram;
|
|
m_prgAreaBlk[int(area) + 3].ram = ram;
|
|
m_prgAreaBlk[int(area) + 4].ram = ram;
|
|
m_prgAreaBlk[int(area) + 5].ram = ram;
|
|
m_prgAreaBlk[int(area) + 6].ram = ram;
|
|
m_prgAreaBlk[int(area) + 7].ram = ram;
|
|
}
|
|
|
|
void Board::togglePrgRamEnable(bool enable)
|
|
{
|
|
for(auto &page : m_prgRam)
|
|
page.enabled = enable;
|
|
}
|
|
|
|
void Board::togglePrgRamWritableEnable(bool enable)
|
|
{
|
|
for(auto &page : m_prgRam)
|
|
page.writeable = enable;
|
|
}
|
|
|
|
void Board::toggle4kPrgRamEnabled(bool enable, int index)
|
|
{
|
|
m_prgRam[index].enabled = enable;
|
|
}
|
|
|
|
void Board::toggle4kPrgRamWritable(bool enable, int index)
|
|
{
|
|
m_prgRam[index].writeable = enable;
|
|
}
|
|
|
|
void Board::toggle4kPrgRamBattery(bool enable, int index)
|
|
{
|
|
m_prgRam[index].battery = enable;
|
|
}
|
|
|
|
void Board::switch1kChr(int index, CHRArea area)
|
|
{
|
|
m_chrAreaBlk[int(area)].index = index;
|
|
}
|
|
|
|
void Board::switch2kChr(int index, CHRArea area)
|
|
{
|
|
index *= 2;
|
|
m_chrAreaBlk[int(area)].index = index;
|
|
m_chrAreaBlk[int(area) + 1].index = index + 1;
|
|
}
|
|
|
|
void Board::switch4kChr(int index, CHRArea area)
|
|
{
|
|
index *= 4;
|
|
m_chrAreaBlk[int(area)].index = index;
|
|
m_chrAreaBlk[int(area) + 1].index = index + 1;
|
|
m_chrAreaBlk[int(area) + 2].index = index + 2;
|
|
m_chrAreaBlk[int(area) + 3].index = index + 3;
|
|
}
|
|
|
|
void Board::switch8kChr(int index)
|
|
{
|
|
index *= 8;
|
|
for(int i = 0; i < 8; i++)
|
|
m_chrAreaBlk[i].index = index + i;
|
|
}
|
|
|
|
void Board::toggle1kChrRam(bool ram, CHRArea area)
|
|
{
|
|
m_chrAreaBlk[int(area)].ram = ram;
|
|
}
|
|
|
|
void Board::toggle2kChrRam(bool ram, CHRArea area)
|
|
{
|
|
m_chrAreaBlk[int(area)].ram = ram;
|
|
m_chrAreaBlk[int(area) + 1].ram = ram;
|
|
}
|
|
|
|
void Board::toggle4kChrRam(bool ram, CHRArea area)
|
|
{
|
|
m_chrAreaBlk[int(area)].ram = ram;
|
|
m_chrAreaBlk[int(area) + 1].ram = ram;
|
|
m_chrAreaBlk[int(area) + 2].ram = ram;
|
|
m_chrAreaBlk[int(area) + 3].ram = ram;
|
|
}
|
|
|
|
void Board::toggle8kChrRam(bool ram)
|
|
{
|
|
for(auto &areaBlk : m_chrAreaBlk)
|
|
areaBlk.ram = ram;
|
|
}
|
|
|
|
void Board::toggle1kChrRamEnabled(bool enable, int index)
|
|
{
|
|
m_chrRam[index].enabled = enable;
|
|
}
|
|
|
|
void Board::toggle1kChrRamWritable(bool enable, int index)
|
|
{
|
|
m_chrRam[index].writeable = enable;
|
|
}
|
|
|
|
void Board::toggleChrRamWritableEnable(bool enable)
|
|
{
|
|
for (auto &ramPage : m_chrRam)
|
|
ramPage.writeable = enable;
|
|
}
|
|
|
|
void Board::toggle1kChrRamBattery(bool enable, int index)
|
|
{
|
|
m_chrRam[index].battery = enable;
|
|
}
|
|
|
|
void Board::switch1kNmt(int index, quint8 area)
|
|
{
|
|
m_nmtRam[area].index = index;
|
|
}
|
|
|
|
void Board::switch1kNmt(Mirroring mirroring)
|
|
{
|
|
// Mirroring value:
|
|
// 0000 0000
|
|
// ddcc bbaa
|
|
// aa: index for area 0x2000
|
|
// bb: index for area 0x2400
|
|
// cc: index for area 0x2800
|
|
// dd: index for area 0x2C00
|
|
m_nmtRam[0].index = int(mirroring) & 0x3;
|
|
m_nmtRam[1].index = (int(mirroring) >> 2) & 0x3;
|
|
m_nmtRam[2].index = (int(mirroring) >> 4) & 0x3;
|
|
m_nmtRam[3].index = (int(mirroring) >> 6) & 0x3;
|
|
}
|
|
|
|
bool Board::enableExternalSound() const
|
|
{
|
|
return false;
|
|
}
|