From 6437be44f5530042f2a5b1b6216d56728bf2d779 Mon Sep 17 00:00:00 2001 From: Souryo Date: Tue, 24 Jun 2014 02:47:32 -0400 Subject: [PATCH] MMC1 support + Mapper refactoring Zelda 1, MegaMan 2, Final Fantasy2 working correctly --- Core/BaseMapper.h | 108 ++++++++++---------- Core/Console.cpp | 16 ++- Core/Console.h | 4 +- Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 38 ++++--- Core/MMC1.h | 203 ++++++++++++++++++++++++++++++++++++++ Core/MapperFactory.h | 30 ++++++ Core/MemoryManager.cpp | 10 +- Core/MemoryManager.h | 6 +- Core/ROMLoader.h | 46 +++++---- 10 files changed, 357 insertions(+), 106 deletions(-) create mode 100644 Core/MMC1.h create mode 100644 Core/MapperFactory.h diff --git a/Core/BaseMapper.h b/Core/BaseMapper.h index 026dc562..07beb9a0 100644 --- a/Core/BaseMapper.h +++ b/Core/BaseMapper.h @@ -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 _romBanks; - vector _vromBanks; + uint8_t* _prgRAM; + uint8_t* _chrRAM; + uint32_t _prgSize; + uint32_t _chrSize; virtual void InitMapper() = 0; public: - void Initialize(NESHeader header, vector romBanks, vector 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 _mappedRomBanks; - MemoryBank *_mappedVromBank; + vector _mappedRomBanks; + vector _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 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(mapper); - } -}; +}; \ No newline at end of file diff --git a/Core/Console.cpp b/Core/Console.cpp index 7e421f2e..20022c43 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -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; } } diff --git a/Core/Console.h b/Core/Console.h index 783873ba..8367bb2c 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -22,7 +22,7 @@ class Console unique_ptr _cpu; unique_ptr _ppu; unique_ptr _apu; - unique_ptr _mapper; + shared_ptr _mapper; unique_ptr _controlManager; unique_ptr _memoryManager; @@ -31,6 +31,8 @@ class Console bool _stop = false; bool _reset = false; + void ResetComponents(); + public: Console(wstring filename); ~Console(); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 894afb5c..8eb1c71e 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -95,6 +95,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index c81552dc..9cc48b89 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -16,6 +16,12 @@ {c6dc2048-98f6-4551-89dc-830f12f1bb2e} + + {ca661408-b52a-4378-aef4-80fda1d64cd6} + + + {67b52e86-0ff2-4dbe-b9ed-7c92aace61d5} + @@ -36,21 +42,9 @@ Header Files - - Header Files - - - Header Files - Header Files - - Header Files - - - Header Files - Header Files @@ -91,7 +85,25 @@ Header Files\Nes_Apu - Header Files + Header Files\Interfaces + + + Header Files\Interfaces + + + Header Files\Interfaces + + + Header Files\Interfaces + + + Header Files\Mappers + + + Header Files\Mappers + + + Header Files\Mappers diff --git a/Core/MMC1.h b/Core/MMC1.h new file mode 100644 index 00000000..3fbce9ea --- /dev/null +++ b/Core/MMC1.h @@ -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; + } +}; diff --git a/Core/MapperFactory.h b/Core/MapperFactory.h new file mode 100644 index 00000000..c37055a8 --- /dev/null +++ b/Core/MapperFactory.h @@ -0,0 +1,30 @@ +#include "stdafx.h" +#include "BaseMapper.h" +#include "MMC1.h" +#include "ROMLoader.h" + +class MapperFactory +{ + public: + static shared_ptr 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(mapper); + } +}; diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 46c2dabe..d604cbce 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -1,11 +1,9 @@ #include "stdafx.h" #include "MemoryManager.h" -#include "ROMLoader.h" -MemoryManager::MemoryManager(NESHeader header) +MemoryManager::MemoryManager(shared_ptr 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) { diff --git a/Core/MemoryManager.h b/Core/MemoryManager.h index 81d9a70b..3bf6e174 100644 --- a/Core/MemoryManager.h +++ b/Core/MemoryManager.h @@ -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 _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 mapper); ~MemoryManager(); void RegisterIODevice(IMemoryHandler *handler); diff --git a/Core/ROMLoader.h b/Core/ROMLoader.h index 2c94ddcc..1a39540a 100644 --- a/Core/ROMLoader.h +++ b/Core/ROMLoader.h @@ -6,6 +6,8 @@ enum class MirroringType { Horizontal, Vertical, + ScreenAOnly, + ScreenBOnly, FourScreens, }; @@ -35,23 +37,18 @@ struct NESHeader } }; -typedef vector MemoryBank; class ROMLoader { private: NESHeader _header; - vector _romBanks; - vector _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 GetROMBanks() + uint8_t* GetPRGRam() { - return _romBanks; + return _prgRAM; } - vector GetVROMBanks() + uint8_t* GetCHRRam() { - return _vromBanks; + return _chrRAM; + } + + uint32_t GetPRGSize() + { + return _header.ROMCount * 0x4000; + } + + uint32_t GetCHRSize() + { + return _header.VROMCount * 0x2000; } NESHeader GetHeader()