Mesen/Core/MMC3.h

289 lines
7.6 KiB
C++

#pragma once
#include "stdafx.h"
#include "BaseMapper.h"
#include "CPU.h"
#include "EmulationSettings.h"
#include "A12Watcher.h"
class MMC3 : public BaseMapper
{
private:
enum class MMC3Registers
{
Reg8000 = 0x8000,
Reg8001 = 0x8001,
RegA000 = 0xA000,
RegA001 = 0xA001,
RegC000 = 0xC000,
RegC001 = 0xC001,
RegE000 = 0xE000,
RegE001 = 0xE001
};
uint8_t _currentRegister;
bool _wramEnabled;
bool _wramWriteProtected;
A12Watcher _a12Watcher;
bool _forceMmc3RevAIrqs;
struct Mmc3State {
uint8_t Reg8000;
uint8_t RegA000;
uint8_t RegA001;
} _state;
protected:
uint8_t _irqReloadValue;
uint8_t _irqCounter;
bool _irqReload;
bool _irqEnabled;
uint8_t _prgMode;
uint8_t _chrMode;
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;
}
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam, access);
}
}
UpdatePrgMapping();
UpdateChrMapping();
}
virtual void StreamState(bool saving) override
{
BaseMapper::StreamState(saving);
ArrayInfo<uint8_t> registers = { _registers, 8 };
SnapshotInfo a12Watcher{ &_a12Watcher };
Stream(_state.Reg8000, _state.RegA000, _state.RegA001, _currentRegister, _chrMode, _prgMode,
_irqReloadValue, _irqCounter, _irqReload, _irqEnabled, a12Watcher,
_wramEnabled, _wramWriteProtected, registers);
}
virtual uint16_t GetPRGPageSize() override { return 0x2000; }
virtual uint16_t GetCHRPageSize() override { return 0x0400; }
virtual uint32_t GetSaveRamPageSize() override { return _romInfo.SubMapperID == 1 ? 0x200 : 0x2000; }
virtual uint32_t GetSaveRamSize() override { return _romInfo.SubMapperID == 1 ? 0x400 : 0x2000; }
virtual 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();
}
virtual void WriteRegister(uint16_t addr, uint8_t value) override
{
switch((MMC3Registers)(addr & 0xE001)) {
case MMC3Registers::Reg8000:
_state.Reg8000 = value;
UpdateState();
break;
case MMC3Registers::Reg8001:
if(_currentRegister <= 1) {
//"Writes to registers 0 and 1 always ignore bit 0"
value &= ~0x01;
}
_registers[_currentRegister] = value;
UpdateState();
break;
case MMC3Registers::RegA000:
_state.RegA000 = value;
UpdateMirroring();
break;
case MMC3Registers::RegA001:
_state.RegA001 = value;
UpdateState();
break;
case MMC3Registers::RegC000:
_irqReloadValue = value;
break;
case MMC3Registers::RegC001:
_irqCounter = 0;
_irqReload = true;
break;
case MMC3Registers::RegE000:
_irqEnabled = false;
_console->GetCpu()->ClearIrqSource(IRQSource::External);
break;
case MMC3Registers::RegE001:
_irqEnabled = true;
break;
}
}
virtual void TriggerIrq()
{
_console->GetCpu()->SetIrqSource(IRQSource::External);
}
public:
virtual void NotifyVRAMAddressChange(uint16_t addr) override
{
if(_a12Watcher.UpdateVramAddress(addr, _console->GetPpu()->GetFrameCycle()) == A12StateChange::Rise) {
uint32_t count = _irqCounter;
if(_irqCounter == 0 || _irqReload) {
_irqCounter = _irqReloadValue;
} else {
_irqCounter--;
}
if(ForceMmc3RevAIrqs() || _console->GetSettings()->CheckFlag(EmulationFlags::Mmc3IrqAltBehavior)) {
//MMC3 Revision A behavior
if((count > 0 || _irqReload) && _irqCounter == 0 && _irqEnabled) {
TriggerIrq();
}
} else {
if(_irqCounter == 0 && _irqEnabled) {
TriggerIrq();
}
}
_irqReload = false;
}
}
};