mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
336 lines
No EOL
10 KiB
C++
336 lines
No EOL
10 KiB
C++
#pragma once
|
|
|
|
#include "pch.h"
|
|
#include "NES/BaseMapper.h"
|
|
#include "NES/Mappers/A12Watcher.h"
|
|
#include "NES/NesConsole.h"
|
|
#include "NES/NesCpu.h"
|
|
#include "NES/BaseNesPpu.h"
|
|
|
|
class MMC3 : public BaseMapper
|
|
{
|
|
private:
|
|
uint8_t _currentRegister = 0;
|
|
|
|
bool _wramEnabled = false;
|
|
bool _wramWriteProtected = false;
|
|
|
|
uint64_t _a12LowClock = 0;
|
|
|
|
bool _forceMmc3RevAIrqs = false;
|
|
|
|
struct Mmc3State
|
|
{
|
|
uint8_t Reg8000;
|
|
uint8_t RegA000;
|
|
uint8_t RegA001;
|
|
} _state = {};
|
|
|
|
protected:
|
|
uint8_t _irqReloadValue = 0;
|
|
uint8_t _irqCounter = 0;
|
|
bool _irqReload = false;
|
|
bool _irqEnabled = false;
|
|
uint8_t _prgMode = 0;
|
|
uint8_t _chrMode = 0;
|
|
uint8_t _registers[8] = {};
|
|
|
|
uint8_t GetCurrentRegister()
|
|
{
|
|
return _currentRegister;
|
|
}
|
|
|
|
Mmc3State GetState()
|
|
{
|
|
return _state;
|
|
}
|
|
|
|
uint8_t GetChrMode()
|
|
{
|
|
return _chrMode;
|
|
}
|
|
|
|
void ResetMmc3()
|
|
{
|
|
_state.Reg8000 = GetPowerOnByte();
|
|
_state.RegA000 = GetPowerOnByte();
|
|
_state.RegA001 = GetPowerOnByte();
|
|
|
|
_chrMode = GetPowerOnByte() & 0x01;
|
|
_prgMode = GetPowerOnByte() & 0x01;
|
|
|
|
_currentRegister = GetPowerOnByte();
|
|
|
|
_registers[0] = GetPowerOnByte(0);
|
|
_registers[1] = GetPowerOnByte(2);
|
|
_registers[2] = GetPowerOnByte(4);
|
|
_registers[3] = GetPowerOnByte(5);
|
|
_registers[4] = GetPowerOnByte(6);
|
|
_registers[5] = GetPowerOnByte(7);
|
|
_registers[6] = GetPowerOnByte(0);
|
|
_registers[7] = GetPowerOnByte(1);
|
|
|
|
_irqCounter = GetPowerOnByte();
|
|
_irqReloadValue = GetPowerOnByte();
|
|
_irqReload = GetPowerOnByte() & 0x01;
|
|
_irqEnabled = GetPowerOnByte() & 0x01;
|
|
|
|
_wramEnabled = GetPowerOnByte() & 0x01;
|
|
_wramWriteProtected = GetPowerOnByte() & 0x01;
|
|
}
|
|
|
|
virtual bool ForceMmc3RevAIrqs() { return _forceMmc3RevAIrqs; }
|
|
|
|
virtual void UpdateMirroring()
|
|
{
|
|
if(GetMirroringType() != MirroringType::FourScreens) {
|
|
SetMirroringType(((_state.RegA000 & 0x01) == 0x01) ? MirroringType::Horizontal : MirroringType::Vertical);
|
|
}
|
|
}
|
|
|
|
virtual void UpdateChrMapping()
|
|
{
|
|
if(_chrMode == 0) {
|
|
SelectChrPage(0, _registers[0] & 0xFE);
|
|
SelectChrPage(1, _registers[0] | 0x01);
|
|
SelectChrPage(2, _registers[1] & 0xFE);
|
|
SelectChrPage(3, _registers[1] | 0x01);
|
|
|
|
SelectChrPage(4, _registers[2]);
|
|
SelectChrPage(5, _registers[3]);
|
|
SelectChrPage(6, _registers[4]);
|
|
SelectChrPage(7, _registers[5]);
|
|
} else if(_chrMode == 1) {
|
|
SelectChrPage(0, _registers[2]);
|
|
SelectChrPage(1, _registers[3]);
|
|
SelectChrPage(2, _registers[4]);
|
|
SelectChrPage(3, _registers[5]);
|
|
|
|
SelectChrPage(4, _registers[0] & 0xFE);
|
|
SelectChrPage(5, _registers[0] | 0x01);
|
|
SelectChrPage(6, _registers[1] & 0xFE);
|
|
SelectChrPage(7, _registers[1] | 0x01);
|
|
}
|
|
}
|
|
|
|
virtual void UpdatePrgMapping()
|
|
{
|
|
if(_prgMode == 0) {
|
|
SelectPrgPage(0, _registers[6]);
|
|
SelectPrgPage(1, _registers[7]);
|
|
SelectPrgPage(2, -2);
|
|
SelectPrgPage(3, -1);
|
|
} else if(_prgMode == 1) {
|
|
SelectPrgPage(0, -2);
|
|
SelectPrgPage(1, _registers[7]);
|
|
SelectPrgPage(2, _registers[6]);
|
|
SelectPrgPage(3, -1);
|
|
}
|
|
}
|
|
|
|
bool CanWriteToWorkRam()
|
|
{
|
|
return _wramEnabled && !_wramWriteProtected;
|
|
}
|
|
|
|
virtual void UpdateState()
|
|
{
|
|
_currentRegister = _state.Reg8000 & 0x07;
|
|
_chrMode = (_state.Reg8000 & 0x80) >> 7;
|
|
_prgMode = (_state.Reg8000 & 0x40) >> 6;
|
|
|
|
if(_romInfo.MapperID == 4 && _romInfo.SubMapperID == 1) {
|
|
//MMC6
|
|
bool wramEnabled = (_state.Reg8000 & 0x20) == 0x20;
|
|
|
|
uint8_t firstBankAccess = (_state.RegA001 & 0x10 ? MemoryAccessType::Write : 0) | (_state.RegA001 & 0x20 ? MemoryAccessType::Read : 0);
|
|
uint8_t lastBankAccess = (_state.RegA001 & 0x40 ? MemoryAccessType::Write : 0) | (_state.RegA001 & 0x80 ? MemoryAccessType::Read : 0);
|
|
if(!wramEnabled) {
|
|
firstBankAccess = MemoryAccessType::NoAccess;
|
|
lastBankAccess = MemoryAccessType::NoAccess;
|
|
}
|
|
|
|
for(int i = 0; i < 4; i++) {
|
|
SetCpuMemoryMapping(0x7000 + i * 0x400, 0x71FF + i * 0x400, 0, PrgMemoryType::SaveRam, firstBankAccess);
|
|
SetCpuMemoryMapping(0x7200 + i * 0x400, 0x73FF + i * 0x400, 1, PrgMemoryType::SaveRam, lastBankAccess);
|
|
}
|
|
} else {
|
|
_wramEnabled = (_state.RegA001 & 0x80) == 0x80;
|
|
_wramWriteProtected = (_state.RegA001 & 0x40) == 0x40;
|
|
|
|
if(_romInfo.SubMapperID == 0) {
|
|
MemoryAccessType access;
|
|
if(_wramEnabled) {
|
|
access = CanWriteToWorkRam() ? MemoryAccessType::ReadWrite : MemoryAccessType::Read;
|
|
} else {
|
|
access = MemoryAccessType::NoAccess;
|
|
}
|
|
if((HasBattery() && _saveRamSize > 0) || (!HasBattery() && _workRamSize > 0)) {
|
|
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam, access);
|
|
} else {
|
|
RemoveCpuMemoryMapping(0x6000, 0x7FFF);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdatePrgMapping();
|
|
UpdateChrMapping();
|
|
}
|
|
|
|
void Serialize(Serializer& s) override
|
|
{
|
|
BaseMapper::Serialize(s);
|
|
SVArray(_registers, 8);
|
|
SV(_a12LowClock);
|
|
SV(_state.Reg8000); SV(_state.RegA000); SV(_state.RegA001); SV(_currentRegister); SV(_chrMode); SV(_prgMode);
|
|
SV(_irqReloadValue); SV(_irqCounter); SV(_irqReload); SV(_irqEnabled); SV(_wramEnabled); SV(_wramWriteProtected);
|
|
}
|
|
|
|
uint16_t GetPrgPageSize() override { return 0x2000; }
|
|
uint16_t GetChrPageSize() override { return 0x0400; }
|
|
uint32_t GetSaveRamPageSize() override { return _romInfo.SubMapperID == 1 ? 0x200 : 0x2000; }
|
|
uint32_t GetSaveRamSize() override { return _romInfo.SubMapperID == 1 ? 0x400 : 0x2000; }
|
|
bool EnableVramAddressHook() override { return true; }
|
|
|
|
void InitMapper() override
|
|
{
|
|
//Force MMC3A irqs for boards that are known to use the A revision.
|
|
//Some MMC3B boards also have the A behavior, but currently no way to tell them apart.
|
|
_forceMmc3RevAIrqs = _romInfo.DatabaseInfo.Chip.substr(0, 5).compare("MMC3A") == 0;
|
|
|
|
ResetMmc3();
|
|
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam);
|
|
UpdateState();
|
|
UpdateMirroring();
|
|
}
|
|
|
|
void WriteRegister(uint16_t addr, uint8_t value) override
|
|
{
|
|
switch(addr & 0xE001) {
|
|
case 0x8000:
|
|
_state.Reg8000 = value;
|
|
UpdateState();
|
|
break;
|
|
|
|
case 0x8001:
|
|
if(_currentRegister <= 1) {
|
|
//"Writes to registers 0 and 1 always ignore bit 0"
|
|
value &= ~0x01;
|
|
}
|
|
_registers[_currentRegister] = value;
|
|
UpdateState();
|
|
break;
|
|
|
|
case 0xA000:
|
|
_state.RegA000 = value;
|
|
UpdateMirroring();
|
|
break;
|
|
|
|
case 0xA001:
|
|
_state.RegA001 = value;
|
|
UpdateState();
|
|
break;
|
|
|
|
case 0xC000:
|
|
_irqReloadValue = value;
|
|
break;
|
|
|
|
case 0xC001:
|
|
_irqCounter = 0;
|
|
_irqReload = true;
|
|
break;
|
|
|
|
case 0xE000:
|
|
_irqEnabled = false;
|
|
_console->GetCpu()->ClearIrqSource(IRQSource::External);
|
|
break;
|
|
|
|
case 0xE001:
|
|
_irqEnabled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
virtual void TriggerIrq()
|
|
{
|
|
_console->GetCpu()->SetIrqSource(IRQSource::External);
|
|
}
|
|
|
|
vector<MapperStateEntry> GetMapperStateEntries() override
|
|
{
|
|
bool isMmc6 = _romInfo.MapperID == 4 && _romInfo.SubMapperID == 1;
|
|
|
|
vector<MapperStateEntry> entries;
|
|
entries.push_back(MapperStateEntry("$8000.0-2", "Current Register", _state.Reg8000 & 0x07, MapperStateValueType::Number8));
|
|
if(isMmc6) {
|
|
entries.push_back(MapperStateEntry("$8000.5", "Work RAM Enabled", (_state.Reg8000 & 0x20) != 0));
|
|
}
|
|
entries.push_back(MapperStateEntry("$8000.6", "PRG Banking Mode", (_state.Reg8000 & 0x40) != 0));
|
|
entries.push_back(MapperStateEntry("$8000.7", "CHR Banking Mode", (_state.Reg8000 & 0x80) != 0));
|
|
|
|
entries.push_back(MapperStateEntry("$A000.0", "Mirroring", _state.RegA000 & 0x01 ? "Horizontal" : "Vertical"));
|
|
|
|
if(isMmc6) {
|
|
entries.push_back(MapperStateEntry("$A001.4", "Work RAM Bank 0 Write Enabled", (_state.RegA001 & 0x10) != 0));
|
|
entries.push_back(MapperStateEntry("$A001.5", "Work RAM Bank 0 Read Enabled", (_state.RegA001 & 0x20) != 0));
|
|
entries.push_back(MapperStateEntry("$A001.6", "Work RAM Bank 1 Write Enabled", (_state.RegA001 & 0x40) != 0));
|
|
entries.push_back(MapperStateEntry("$A001.7", "Work RAM Bank 1 Read Enabled", (_state.RegA001 & 0x80) != 0));
|
|
} else {
|
|
entries.push_back(MapperStateEntry("$A001.6", "Work RAM Write Protected", (_state.RegA001 & 0x40) != 0));
|
|
entries.push_back(MapperStateEntry("$A001.7", "Work RAM Enabled", (_state.RegA001 & 0x80) != 0));
|
|
}
|
|
|
|
entries.push_back(MapperStateEntry("$C000", "IRQ Reload Value", _irqReloadValue, MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "IRQ Counter", _irqCounter, MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "IRQ Reload Flag", _irqReload));
|
|
entries.push_back(MapperStateEntry("$E000/1", "IRQ Enabled", _irqEnabled));
|
|
|
|
entries.push_back(MapperStateEntry("", "Register 0 (CHR)", _registers[0], MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "Register 1 (CHR)", _registers[1], MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "Register 2 (CHR)", _registers[2], MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "Register 3 (CHR)", _registers[3], MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "Register 4 (CHR)", _registers[4], MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "Register 5 (CHR)", _registers[5], MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "Register 6 (PRG)", _registers[6], MapperStateValueType::Number8));
|
|
entries.push_back(MapperStateEntry("", "Register 7 (PRG)", _registers[7], MapperStateValueType::Number8));
|
|
|
|
return entries;
|
|
}
|
|
|
|
bool IsA12RisingEdge(uint16_t addr)
|
|
{
|
|
if(addr & 0x1000) {
|
|
bool isRisingEdge = _a12LowClock > 0 && (_console->GetMasterClock() - _a12LowClock) >= 3;
|
|
_a12LowClock = 0;
|
|
return isRisingEdge;
|
|
} else if(_a12LowClock == 0) {
|
|
_a12LowClock = _console->GetMasterClock();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
void NotifyVramAddressChange(uint16_t addr) override
|
|
{
|
|
if(IsA12RisingEdge(addr)) {
|
|
uint32_t count = _irqCounter;
|
|
if(_irqCounter == 0 || _irqReload) {
|
|
_irqCounter = _irqReloadValue;
|
|
} else {
|
|
_irqCounter--;
|
|
}
|
|
|
|
if(ForceMmc3RevAIrqs()) {
|
|
//MMC3 Revision A behavior
|
|
if((count > 0 || _irqReload) && _irqCounter == 0 && _irqEnabled) {
|
|
TriggerIrq();
|
|
}
|
|
} else {
|
|
if(_irqCounter == 0 && _irqEnabled) {
|
|
TriggerIrq();
|
|
}
|
|
}
|
|
_irqReload = false;
|
|
}
|
|
}
|
|
}; |