375 lines
7.1 KiB
C++
375 lines
7.1 KiB
C++
#include "NES.hpp"
|
|
#include <chrono>
|
|
|
|
|
|
const unsigned int VIDEO_SCALE = 4u;
|
|
const unsigned int WINDOW_WIDTH = VIDEO_WIDTH * VIDEO_SCALE;
|
|
const unsigned int WINDOW_HEIGHT = VIDEO_HEIGHT * VIDEO_SCALE;
|
|
const unsigned int VIDEO_PITCH = VIDEO_WIDTH * 4u;
|
|
const unsigned int PPU_CYCLES_PER_CPU_CYCLE = 3u;
|
|
|
|
|
|
void DrawDebugLines(uint32_t* buffer)
|
|
{
|
|
for (int y = 0; y < VIDEO_HEIGHT; ++y)
|
|
{
|
|
for (int x = 0; x < VIDEO_WIDTH; ++x)
|
|
{
|
|
if (x == 0 || y == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (x % 8 == 0u)
|
|
{
|
|
buffer[y * VIDEO_WIDTH + x] = 0xFF00FFFFu;
|
|
}
|
|
if (x % 32 == 0u)
|
|
{
|
|
buffer[y * VIDEO_WIDTH + x] = 0xFF0000FFu;
|
|
}
|
|
|
|
if (y % 8 == 0u)
|
|
{
|
|
buffer[y * VIDEO_WIDTH + x] = 0xFF00FFFFu;
|
|
}
|
|
if (y % 32 == 0u)
|
|
{
|
|
buffer[y * VIDEO_WIDTH + x] = 0xFF0000FFu;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
NES::NES()
|
|
: cpu(std::make_unique<CPU>()),
|
|
ppu(std::make_unique<PPU>()),
|
|
cartridge(std::make_unique<Cartridge>()),
|
|
platform(std::make_unique<Platform>("NES", WINDOW_WIDTH, WINDOW_HEIGHT, VIDEO_WIDTH, VIDEO_HEIGHT)),
|
|
input(std::make_unique<Input>())
|
|
{
|
|
ppu->nes = this;
|
|
|
|
palette[0] = Color(84, 84, 84);
|
|
palette[1] = Color(0, 30, 116);
|
|
palette[2] = Color(8, 16, 144);
|
|
palette[3] = Color(48, 0, 136);
|
|
|
|
palette[4] = Color(68, 0, 100);
|
|
palette[5] = Color(92, 0, 48);
|
|
palette[6] = Color(84, 4, 0);
|
|
palette[7] = Color(60, 24, 0);
|
|
|
|
palette[8] = Color(32, 42, 0);
|
|
palette[9] = Color(8, 58, 0);
|
|
palette[10] = Color(0, 64, 0);
|
|
palette[11] = Color(0, 60, 0);
|
|
|
|
palette[12] = Color(0, 50, 60);
|
|
palette[13] = Color(0, 0, 0);
|
|
palette[14] = Color(0, 0, 0);
|
|
palette[15] = Color(0, 0, 0);
|
|
|
|
palette[16] = Color(152, 150, 152);
|
|
palette[17] = Color(8, 76, 196);
|
|
palette[18] = Color(48, 50, 236);
|
|
palette[19] = Color(92, 30, 228);
|
|
|
|
palette[20] = Color(136, 20, 176);
|
|
palette[21] = Color(160, 20, 100);
|
|
palette[22] = Color(152, 34, 32);
|
|
palette[23] = Color(120, 60, 0);
|
|
|
|
palette[24] = Color(84, 90, 0);
|
|
palette[25] = Color(40, 114, 0);
|
|
palette[26] = Color(8, 124, 0);
|
|
palette[27] = Color(0, 118, 40);
|
|
|
|
palette[28] = Color(0, 102, 120);
|
|
palette[29] = Color(0, 0, 0);
|
|
palette[30] = Color(0, 0, 0);
|
|
palette[31] = Color(0, 0, 0);
|
|
|
|
palette[32] = Color(236, 238, 236);
|
|
palette[33] = Color(76, 154, 236);
|
|
palette[34] = Color(120, 124, 236);
|
|
palette[35] = Color(176, 98, 236);
|
|
|
|
palette[36] = Color(228, 84, 236);
|
|
palette[37] = Color(236, 88, 180);
|
|
palette[38] = Color(236, 106, 100);
|
|
palette[39] = Color(212, 136, 32);
|
|
|
|
palette[40] = Color(160, 170, 0);
|
|
palette[41] = Color(116, 196, 0);
|
|
palette[42] = Color(76, 208, 32);
|
|
palette[43] = Color(56, 204, 108);
|
|
|
|
palette[44] = Color(56, 180, 204);
|
|
palette[45] = Color(60, 60, 60);
|
|
palette[46] = Color(0, 0, 0);
|
|
palette[47] = Color(0, 0, 0);
|
|
|
|
palette[48] = Color(236, 238, 236);
|
|
palette[49] = Color(168, 204, 236);
|
|
palette[50] = Color(188, 188, 236);
|
|
palette[51] = Color(212, 178, 236);
|
|
|
|
palette[52] = Color(236, 174, 236);
|
|
palette[53] = Color(236, 174, 212);
|
|
palette[54] = Color(236, 180, 176);
|
|
palette[55] = Color(228, 196, 144);
|
|
|
|
palette[56] = Color(204, 210, 120);
|
|
palette[57] = Color(180, 222, 120);
|
|
palette[58] = Color(168, 226, 144);
|
|
palette[59] = Color(152, 226, 180);
|
|
|
|
palette[60] = Color(160, 214, 228);
|
|
palette[61] = Color(160, 162, 160);
|
|
palette[62] = Color(0, 0, 0);
|
|
palette[63] = Color(0, 0, 0);
|
|
}
|
|
|
|
void NES::InsertCartridge(char const* filename)
|
|
{
|
|
cartridge->Load(filename);
|
|
cpu->nes = this;
|
|
cpu->Reset();
|
|
}
|
|
|
|
void NES::Run()
|
|
{
|
|
uint8_t buttons[8]{};
|
|
|
|
while (!platform->ProcessInput(buttons))
|
|
{
|
|
input->Strobe(buttons);
|
|
|
|
uint64_t cpuCycles = cpu->Cycle();
|
|
|
|
for (auto i = 0; i < cpuCycles * PPU_CYCLES_PER_CPU_CYCLE; ++i)
|
|
{
|
|
ppu->Tick();
|
|
}
|
|
|
|
if (ppu->currentScanline == 261)
|
|
{
|
|
//DrawDebugLines(ppu->video);
|
|
platform->Update(ppu->video, VIDEO_PITCH);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NES::Write(BusSource source, uint16_t address, uint8_t value)
|
|
{
|
|
switch (source)
|
|
{
|
|
case BusSource::CPU:
|
|
{
|
|
// CPU RAM and Mirrors
|
|
if (address < 0x2000u)
|
|
{
|
|
uint16_t index = address & 0x7FFu;
|
|
|
|
cpu->ram[index] = value;
|
|
}
|
|
|
|
// PPU Registers and Mirrors
|
|
else if (address < 0x4000u)
|
|
{
|
|
auto index = static_cast<PPU::Register>(address % 8u);
|
|
|
|
ppu->WriteRegister(index, value);
|
|
}
|
|
|
|
// APU and IO
|
|
else if (address < 0x4020u)
|
|
{
|
|
uint16_t index = address & 0x1Fu;
|
|
|
|
// PPU OAM
|
|
if (index == 0x14u)
|
|
{
|
|
uint8_t addrMsb = value;
|
|
|
|
for (uint16_t addrLsb = 0x00; addrLsb <= 0xFF; ++addrLsb)
|
|
{
|
|
uint16_t addr = (addrMsb << 8u) | addrLsb;
|
|
|
|
ppu->oam[ppu->regOAMADDR + addrLsb] = Read(BusSource::CPU, addr);
|
|
}
|
|
}
|
|
|
|
// Controller
|
|
else if (index == 0x16u)
|
|
{
|
|
if (value & 0x1u)
|
|
{
|
|
input->strobe = true;
|
|
}
|
|
else
|
|
{
|
|
input->strobe = false;
|
|
input->buttonIndex = 0u;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expansion I/O
|
|
else if (address < 0x6000u)
|
|
{
|
|
}
|
|
|
|
// SRAM
|
|
else if (address < 0x8000u)
|
|
{
|
|
uint16_t index = address & 0x1FFFu;
|
|
|
|
cpu->sram[index] = value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case BusSource::PPU:
|
|
{
|
|
// Name Tables and Attribute Tables
|
|
if (address > 0x2000u && address < 0x3000u)
|
|
{
|
|
uint16_t index = address & 0x3FFu;
|
|
|
|
ppu->ram[index] = value;
|
|
}
|
|
|
|
// Mirrors of $2000 - $2EFF
|
|
else if (address < 0x3F00u)
|
|
{
|
|
uint16_t index = address & 0xEFFu;
|
|
|
|
ppu->ram[index] = value;
|
|
}
|
|
|
|
// Palette RAM Indexes and Mirrors
|
|
else if (address < 0x3F20u)
|
|
{
|
|
uint8_t index = (address & 0x1Cu) >> 2u;
|
|
uint8_t entry = address & 0x03u;
|
|
|
|
ppu->palette[index][entry] = value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t NES::Read(BusSource source, uint16_t address)
|
|
{
|
|
uint8_t byte{};
|
|
|
|
switch (source)
|
|
{
|
|
case BusSource::CPU:
|
|
{
|
|
// CPU RAM and Mirrors
|
|
if (address < 0x2000u)
|
|
{
|
|
uint16_t index = address & 0x7FFu;
|
|
|
|
byte = cpu->ram[index];
|
|
}
|
|
|
|
// PPU Registers and Mirrors
|
|
else if (address < 0x4000u)
|
|
{
|
|
auto index = static_cast<PPU::Register>(address % 8u);
|
|
|
|
byte = ppu->ReadRegister(index);
|
|
}
|
|
|
|
// APU and IO
|
|
else if (address < 0x4020u)
|
|
{
|
|
uint16_t index = address & 0x1Fu;
|
|
|
|
// Controller
|
|
if (index == 0x16u)
|
|
{
|
|
byte = input->Poll();
|
|
}
|
|
}
|
|
|
|
// Expansion I/O
|
|
else if (address < 0x6000u)
|
|
{
|
|
}
|
|
|
|
// SRAM
|
|
else if (address < 0x8000u)
|
|
{
|
|
uint16_t index = address & 0x1FFFu;
|
|
|
|
byte = cpu->sram[index];
|
|
}
|
|
|
|
// CHR-ROM
|
|
else if (address < 0xC000u)
|
|
{
|
|
uint16_t index = address & 0x3FFFu;
|
|
|
|
byte = cartridge->chrROM[index];
|
|
}
|
|
|
|
// PRG-ROM
|
|
else if (address < 0xFFFFu)
|
|
{
|
|
uint16_t index = address & 0x3FFFu;
|
|
|
|
byte = cartridge->prgROM[index];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case BusSource::PPU:
|
|
{
|
|
// Cartridge ROM
|
|
if (address < 0x2000u)
|
|
{
|
|
uint16_t index = address & 0x1FFFu;
|
|
|
|
byte = cartridge->chrROM[index];
|
|
}
|
|
|
|
// Name Tables and Attribute Tables
|
|
else if (address < 0x3000u)
|
|
{
|
|
uint16_t index = address & 0x3FFu;
|
|
|
|
byte = ppu->ram[index];
|
|
}
|
|
|
|
// Mirrors of $2000 - $2EFF
|
|
else if (address < 0x3F00u)
|
|
{
|
|
uint16_t index = address & 0xEFFu;
|
|
|
|
byte = ppu->ram[index];
|
|
}
|
|
|
|
// Palette RAM Indexes and Mirrors
|
|
else if (address < 0x3F20u)
|
|
{
|
|
uint8_t index = address & 0x0Cu;
|
|
uint8_t entry = address & 0x03u;
|
|
|
|
byte = ppu->palette[index][entry];
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return byte;
|
|
}
|