MMC1 support + Mapper refactoring

Zelda 1, MegaMan 2, Final Fantasy2 working correctly
This commit is contained in:
Souryo 2014-06-24 02:47:32 -04:00
parent 4cd681c02b
commit 6437be44f5
10 changed files with 357 additions and 106 deletions

View file

@ -1,53 +1,70 @@
#pragma once
#include "stdafx.h"
#include "ROMLoader.h"
#include "IMemoryHandler.h"
#include "ROMLoader.h"
class BaseMapper : public IMemoryHandler
{
private:
MirroringType _mirroringType;
protected:
NESHeader _header;
vector<MemoryBank> _romBanks;
vector<MemoryBank> _vromBanks;
uint8_t* _prgRAM;
uint8_t* _chrRAM;
uint32_t _prgSize;
uint32_t _chrSize;
virtual void InitMapper() = 0;
public:
void Initialize(NESHeader header, vector<MemoryBank> romBanks, vector<MemoryBank> vromBanks)
const static int PRGSize = 0x8000;
const static int CHRSize = 0x2000;
public:
void Initialize(MirroringType mirroringType, ROMLoader &romLoader)
{
_header = header;
_romBanks = romBanks;
_vromBanks = vromBanks;
_mirroringType = mirroringType;
_prgRAM = romLoader.GetPRGRam();
_chrRAM = romLoader.GetCHRRam();
_prgSize = romLoader.GetPRGSize();
_chrSize = romLoader.GetCHRSize();
if(_chrSize == 0) {
_chrRAM = new uint8_t[BaseMapper::CHRSize];
_chrSize = BaseMapper::CHRSize;
}
InitMapper();
}
const NESHeader GetHeader()
~BaseMapper()
{
return _header;
delete[] _prgRAM;
delete[] _chrRAM;
}
virtual MirroringType GetMirroringType()
{
return _mirroringType;
}
};
class DefaultMapper : public BaseMapper
{
protected:
vector<MemoryBank*> _mappedRomBanks;
MemoryBank *_mappedVromBank;
vector<uint8_t*> _mappedRomBanks;
vector<uint8_t*> _mappedVromBanks;
void InitMapper()
virtual void InitMapper()
{
if(_romBanks.size() == 1) {
_mappedRomBanks = { &_romBanks[0], &_romBanks[0] };
if(_prgSize == 0x4000) {
_mappedRomBanks = { _prgRAM, _prgRAM };
} else {
_mappedRomBanks = { &_romBanks[0], &_romBanks[1] };
_mappedRomBanks = { _prgRAM, &_prgRAM[0x4000] };
}
if(_vromBanks.size() == 0) {
uint8_t *buffer = new uint8_t[ROMLoader::VROMBankSize];
_vromBanks.push_back(MemoryBank(buffer, buffer + ROMLoader::VROMBankSize));
}
_mappedVromBank = &_vromBanks[0];
_mappedVromBanks.push_back(_chrRAM);
}
public:
@ -61,24 +78,24 @@ class DefaultMapper : public BaseMapper
return { { { 0x0000, 0x1FFF } } };
}
uint8_t ReadRAM(uint16_t addr)
virtual uint8_t ReadRAM(uint16_t addr)
{
return (*_mappedRomBanks[(addr >> 14) & 0x01])[addr & 0x3FFF];
return _mappedRomBanks[(addr >> 14) & 0x01][addr & 0x3FFF];
}
void WriteRAM(uint16_t addr, uint8_t value)
virtual void WriteRAM(uint16_t addr, uint8_t value)
{
(*_mappedRomBanks[(addr >> 14) & 0x01])[addr & 0x3FFF] = value;
_mappedRomBanks[(addr >> 14) & 0x01][addr & 0x3FFF] = value;
}
uint8_t ReadVRAM(uint16_t addr)
virtual uint8_t ReadVRAM(uint16_t addr)
{
return (*_mappedVromBank)[addr & 0x1FFF];
return _mappedVromBanks[0][addr & 0x1FFF];
}
void WriteVRAM(uint16_t addr, uint8_t value)
virtual void WriteVRAM(uint16_t addr, uint8_t value)
{
(*_mappedVromBank)[addr & 0x1FFF] = value;
_mappedVromBanks[0][addr & 0x1FFF] = value;
}
};
@ -89,38 +106,13 @@ class Mapper2 : public DefaultMapper
{
DefaultMapper::InitMapper();
_mappedRomBanks[1] = &_romBanks[_romBanks.size() - 1];
uint8_t numberOfBanks = _prgSize / 0x4000;
_mappedRomBanks[1] = &_prgRAM[(numberOfBanks - 1) * 0x4000];
}
public:
void WriteRAM(uint16_t addr, uint8_t value)
{
_mappedRomBanks[0] = &_romBanks[value];
//(*_mappedRomBanks[(addr >> 14) & 0x01])[addr & 0x3FFF] = value;
_mappedRomBanks[0] = &_prgRAM[value * 0x4000];
}
};
class MapperFactory
{
public:
static unique_ptr<BaseMapper> InitializeFromFile(wstring filename)
{
ROMLoader loader(filename);
NESHeader header = loader.GetHeader();
uint8_t mapperID = header.GetMapperID();
BaseMapper* mapper = nullptr;
switch(mapperID) {
case 0: mapper = new DefaultMapper(); break;
case 2: mapper = new Mapper2(); break;
}
if(!mapper) {
throw std::exception("Unsupported mapper");
}
mapper->Initialize(header, loader.GetROMBanks(), loader.GetVROMBanks());
return unique_ptr<BaseMapper>(mapper);
}
};
};

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "Console.h"
#include "MapperFactory.h"
#include "../Utilities/Timer.h"
uint32_t Console::Flags = 0;
@ -10,7 +11,7 @@ Console::Console(wstring filename)
_romFilename = filename;
_mapper = MapperFactory::InitializeFromFile(filename);
_memoryManager.reset(new MemoryManager(_mapper->GetHeader()));
_memoryManager.reset(new MemoryManager(_mapper));
_cpu.reset(new CPU(_memoryManager.get()));
_ppu.reset(new PPU(_memoryManager.get()));
_apu.reset(new APU());
@ -22,7 +23,7 @@ Console::Console(wstring filename)
_memoryManager->RegisterIODevice(_apu.get());
_memoryManager->RegisterIODevice(_controlManager.get());
Reset();
ResetComponents();
}
Console::~Console()
@ -34,6 +35,13 @@ void Console::Reset()
_reset = true;
}
void Console::ResetComponents()
{
_cpu->Reset();
_ppu->Reset();
_apu->Reset();
}
void Console::Stop()
{
_stop = true;
@ -100,9 +108,7 @@ void Console::Run()
fpsTimer.Reset();
lastFrameCount = 0;
elapsedTime = 0;
_cpu->Reset();
_ppu->Reset();
_apu->Reset();
ResetComponents();
_reset = false;
}
}

View file

@ -22,7 +22,7 @@ class Console
unique_ptr<CPU> _cpu;
unique_ptr<PPU> _ppu;
unique_ptr<APU> _apu;
unique_ptr<BaseMapper> _mapper;
shared_ptr<BaseMapper> _mapper;
unique_ptr<ControlManager> _controlManager;
unique_ptr<MemoryManager> _memoryManager;
@ -31,6 +31,8 @@ class Console
bool _stop = false;
bool _reset = false;
void ResetComponents();
public:
Console(wstring filename);
~Console();

View file

@ -95,6 +95,8 @@
<ClInclude Include="IMemoryHandler.h" />
<ClInclude Include="Console.h" />
<ClInclude Include="IVideoDevice.h" />
<ClInclude Include="MapperFactory.h" />
<ClInclude Include="MMC1.h" />
<ClInclude Include="Nes_Apu\apu_snapshot.h" />
<ClInclude Include="Nes_Apu\blargg_common.h" />
<ClInclude Include="Nes_Apu\blargg_source.h" />

View file

@ -16,6 +16,12 @@
<Filter Include="Header Files\Nes_Apu">
<UniqueIdentifier>{c6dc2048-98f6-4551-89dc-830f12f1bb2e}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Interfaces">
<UniqueIdentifier>{ca661408-b52a-4378-aef4-80fda1d64cd6}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Mappers">
<UniqueIdentifier>{67b52e86-0ff2-4dbe-b9ed-7c92aace61d5}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
@ -36,21 +42,9 @@
<ClInclude Include="ROMLoader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BaseMapper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="IMemoryHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Console.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="IVideoDevice.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="IControlDevice.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ControlManager.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -91,7 +85,25 @@
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="IAudioDevice.h">
<Filter>Header Files</Filter>
<Filter>Header Files\Interfaces</Filter>
</ClInclude>
<ClInclude Include="IControlDevice.h">
<Filter>Header Files\Interfaces</Filter>
</ClInclude>
<ClInclude Include="IMemoryHandler.h">
<Filter>Header Files\Interfaces</Filter>
</ClInclude>
<ClInclude Include="IVideoDevice.h">
<Filter>Header Files\Interfaces</Filter>
</ClInclude>
<ClInclude Include="BaseMapper.h">
<Filter>Header Files\Mappers</Filter>
</ClInclude>
<ClInclude Include="MapperFactory.h">
<Filter>Header Files\Mappers</Filter>
</ClInclude>
<ClInclude Include="MMC1.h">
<Filter>Header Files\Mappers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>

203
Core/MMC1.h Normal file
View file

@ -0,0 +1,203 @@
#include "stdafx.h"
#include "BaseMapper.h"
class MMC1 : public DefaultMapper
{
private:
enum class MMC1Registers
{
Reg8000 = 0,
RegA000 = 1,
RegC000 = 2,
RegE000 = 3
};
enum class PrgMode
{
_16k = 16,
_32k = 32,
};
enum class ChrMode
{
_4k = 4,
_8k = 8,
};
enum class SlotSelect
{
x8000 = 0x8000,
xC000 = 0xC000,
};
uint8_t _writeBuffer = 0;
uint8_t _shiftCount = 0;
MirroringType _mirroringType;
bool _wramDisable;
ChrMode _chrMode;
PrgMode _prgMode;
SlotSelect _slotSelect;
uint8_t _chrReg1;
uint8_t _chrReg2;
uint8_t _prgReg;
struct {
uint8_t Reg8000;
uint8_t RegA000;
uint8_t RegC000;
uint8_t RegE000;
} _state;
private:
bool HasResetFlag(uint8_t value)
{
return (value & 0x80) == 0x80;
}
void ResetBuffer()
{
_shiftCount = 0;
_writeBuffer = 0;
}
bool IsBufferFull(uint8_t value)
{
if(HasResetFlag(value)) {
//When 'r' is set:
// - 'd' is ignored
// - hidden temporary reg is reset (so that the next write is the "first" write)
// - bits 2,3 of reg $8000 are set (16k PRG mode, $8000 swappable)
// - other bits of $8000 (and other regs) are unchanged
ResetBuffer();
_state.Reg8000 |= 0x0C;
return false;
} else {
//std::cout << std::hex << "input value: " << (short)value << std::endl;
_writeBuffer >>= 1;
_writeBuffer |= ((value << 4) & 0x10);
_shiftCount++;
if(_shiftCount == 5) {
//std::cout << std::hex << "value: " << (short)_writeBuffer << std::endl;
}
return _shiftCount == 5;
}
}
void UpdateState()
{
switch(_state.Reg8000 & 0x03) {
case 0: _mirroringType = MirroringType::ScreenAOnly; break;
case 1: _mirroringType = MirroringType::ScreenBOnly; break;
case 2: _mirroringType = MirroringType::Vertical; break;
case 3: _mirroringType = MirroringType::Horizontal; break;
}
_wramDisable = (_state.RegE000 & 0x10) == 0x10;
_slotSelect = ((_state.Reg8000 & 0x04) == 0x04) ? SlotSelect::x8000 : SlotSelect::xC000;
_prgMode = ((_state.Reg8000 & 0x08) == 0x08) ? PrgMode::_16k : PrgMode::_32k;
_chrMode = ((_state.Reg8000 & 0x10) == 0x10) ? ChrMode::_4k : ChrMode::_8k;
_chrReg1 = _state.RegA000 & 0x1F;
_chrReg2 = _state.RegC000 & 0x1F;
_prgReg = _state.RegE000 & 0x0F;
uint8_t page1;
uint8_t page2;
if(_prgMode == PrgMode::_32k) {
page1 = (_prgReg >> 1);
page2 = page1 + 1;
} else if(_prgMode == PrgMode::_16k) {
if(_slotSelect == SlotSelect::x8000) {
page1 = _prgReg;
page2 = 0x0F; //$C000 fixed to page $0F (mode B)
} else if(_slotSelect == SlotSelect::xC000) {
page1 = 0;
page2 = _prgReg;
}
}
uint8_t numberOfPRGPages = _prgSize / 0x4000;
page1 &= (numberOfPRGPages - 1);
page2 &= (numberOfPRGPages - 1);
_mappedRomBanks[0] = &_prgRAM[page1 * 0x4000];
_mappedRomBanks[1] = &_prgRAM[page2 * 0x4000];
//std::cout << std::dec << "PRG Bank: " << (short)page1 << " & " << (short)page2 << std::endl;
if(_chrMode == ChrMode::_8k) {
page1 = (_chrReg1 >> 1);
page2 = page1 + 1;
} else if(_chrMode == ChrMode::_4k) {
page1 = _chrReg1;
page2 = _chrReg2;
}
uint8_t numberOfCHRPages = _chrSize / 0x1000;
page1 &= (numberOfCHRPages - 1);
page2 &= (numberOfCHRPages - 1);
_mappedVromBanks[0] = &_chrRAM[page1 * 0x1000];
_mappedVromBanks[1] = &_chrRAM[page2 * 0x1000];
//std::cout << std::dec << "CHR Bank: " << (short)page1 << " & " << (short)page2 << std::endl;
}
protected:
void InitMapper()
{
_mappedRomBanks.push_back(nullptr);
_mappedRomBanks.push_back(nullptr);
_mappedVromBanks.push_back(nullptr);
_mappedVromBanks.push_back(nullptr);
_state.Reg8000 = 0x0C; //On powerup: bits 2,3 of $8000 are set
_state.RegA000 = 0x00;
_state.RegC000 = 0x00;
_state.RegE000 = 0x10; //WRAM Disable: assume it's enabled at startup
UpdateState();
}
public:
void WriteRAM(uint16_t addr, uint8_t value)
{
if(IsBufferFull(value)) {
switch((MMC1Registers)((addr & 0x6000) >> 13)) {
case MMC1Registers::Reg8000: _state.Reg8000 = _writeBuffer; break;
case MMC1Registers::RegA000: _state.RegA000 = _writeBuffer; break;
case MMC1Registers::RegC000: _state.RegC000 = _writeBuffer; break;
case MMC1Registers::RegE000: _state.RegE000 = _writeBuffer; break;
}
UpdateState();
//Reset buffer after writing 5 bits
ResetBuffer();
}
}
uint8_t ReadRAM(uint16_t addr)
{
return _mappedRomBanks[(addr >> 14) & 0x01][addr & 0x3FFF];
}
uint8_t ReadVRAM(uint16_t addr)
{
return _mappedVromBanks[(addr >> 12) & 0x01][addr & 0x0FFF];
}
void WriteVRAM(uint16_t addr, uint8_t value)
{
_mappedVromBanks[(addr >> 12) & 0x01][addr & 0x0FFF] = value;
}
MirroringType GetMirroringType()
{
return _mirroringType;
}
};

30
Core/MapperFactory.h Normal file
View file

@ -0,0 +1,30 @@
#include "stdafx.h"
#include "BaseMapper.h"
#include "MMC1.h"
#include "ROMLoader.h"
class MapperFactory
{
public:
static shared_ptr<BaseMapper> InitializeFromFile(wstring filename)
{
ROMLoader loader(filename);
NESHeader header = loader.GetHeader();
uint8_t mapperID = header.GetMapperID();
BaseMapper* mapper = nullptr;
switch(mapperID) {
case 0: mapper = new DefaultMapper(); break;
case 1: mapper = new MMC1(); break;
case 2: mapper = new Mapper2(); break;
}
if(!mapper) {
throw std::exception("Unsupported mapper");
}
mapper->Initialize(header.GetMirroringType(), loader);
return shared_ptr<BaseMapper>(mapper);
}
};

View file

@ -1,11 +1,9 @@
#include "stdafx.h"
#include "MemoryManager.h"
#include "ROMLoader.h"
MemoryManager::MemoryManager(NESHeader header)
MemoryManager::MemoryManager(shared_ptr<BaseMapper> mapper)
{
_header = header;
_mirroringType = _header.GetMirroringType();
_mapper = mapper;
_internalRAM = new uint8_t[InternalRAMSize];
_SRAM = new uint8_t[SRAMSize];
@ -142,7 +140,7 @@ void MemoryManager::WriteVRAM(uint16_t addr, uint8_t value)
_videoRAM[addr] = value;
if(_mirroringType == MirroringType::Vertical) {
if(_mapper->GetMirroringType() == MirroringType::Vertical) {
if(addr >= 0x2000 && addr < 0x2400) {
_videoRAM[addr + 0x800] = value;
} else if(addr >= 0x2400 && addr < 0x2800) {
@ -152,7 +150,7 @@ void MemoryManager::WriteVRAM(uint16_t addr, uint8_t value)
} else if(addr >= 0x2C00 && addr < 0x3000) {
_videoRAM[addr - 0x800] = value;
}
} else if(_mirroringType == MirroringType::Horizontal) {
} else if(_mapper->GetMirroringType() == MirroringType::Horizontal) {
if(addr >= 0x2000 && addr < 0x2400) {
_videoRAM[addr + 0x400] = value;
} else if(addr >= 0x2400 && addr < 0x2800) {

View file

@ -3,6 +3,7 @@
#include "stdafx.h"
#include "IMemoryHandler.h"
#include "ROMLoader.h"
#include "BaseMapper.h"
class MemoryManager
{
@ -11,8 +12,7 @@ class MemoryManager
const int SRAMSize = 0x2000;
const int VRAMSize = 0x4000;
NESHeader _header;
MirroringType _mirroringType;
shared_ptr<BaseMapper> _mapper;
uint8_t *_internalRAM;
uint8_t *_expansionRAM;
@ -29,7 +29,7 @@ class MemoryManager
void WriteMappedVRAM(uint16_t addr, uint8_t value);
public:
MemoryManager(NESHeader header);
MemoryManager(shared_ptr<BaseMapper> mapper);
~MemoryManager();
void RegisterIODevice(IMemoryHandler *handler);

View file

@ -6,6 +6,8 @@ enum class MirroringType
{
Horizontal,
Vertical,
ScreenAOnly,
ScreenBOnly,
FourScreens,
};
@ -35,23 +37,18 @@ struct NESHeader
}
};
typedef vector<uint8_t> MemoryBank;
class ROMLoader
{
private:
NESHeader _header;
vector<MemoryBank> _romBanks;
vector<MemoryBank> _vromBanks;
uint8_t* _prgRAM;
uint32_t _prgSize;
uint8_t* _chrRAM;
uint32_t _chrSize;
public:
const static int ROMBankSize = 0x4000;
const static int VROMBankSize = 0x2000;
ROMLoader(wstring filename)
{
_romBanks.clear();
_vromBanks.clear();
ifstream romFile(filename, ios::in | ios::binary);
if(!romFile) {
@ -60,30 +57,39 @@ class ROMLoader
romFile.read((char*)&_header, sizeof(NESHeader));
uint8_t *buffer = new uint8_t[max(ROMBankSize, VROMBankSize)];
uint8_t* prgBuffer = new uint8_t[0x4000 * _header.ROMCount];
for(int i = 0; i < _header.ROMCount; i++) {
romFile.read((char*)buffer, ROMBankSize);
_romBanks.push_back(MemoryBank(buffer, buffer + ROMBankSize));
romFile.read((char*)prgBuffer+i*0x4000, 0x4000);
}
_prgRAM = prgBuffer;
uint8_t* chrBuffer = new uint8_t[0x2000 * _header.VROMCount];
for(int i = 0; i < _header.VROMCount; i++) {
romFile.read((char*)buffer, VROMBankSize);
_vromBanks.push_back(MemoryBank(buffer, buffer + VROMBankSize));
romFile.read((char*)chrBuffer+i*0x2000, 0x2000);
}
delete[] buffer;
_chrRAM = chrBuffer;
romFile.close();
}
vector<MemoryBank> GetROMBanks()
uint8_t* GetPRGRam()
{
return _romBanks;
return _prgRAM;
}
vector<MemoryBank> GetVROMBanks()
uint8_t* GetCHRRam()
{
return _vromBanks;
return _chrRAM;
}
uint32_t GetPRGSize()
{
return _header.ROMCount * 0x4000;
}
uint32_t GetCHRSize()
{
return _header.VROMCount * 0x2000;
}
NESHeader GetHeader()