2014-08-04 19:55:20 +00:00
|
|
|
#pragma once
|
|
|
|
|
2014-06-25 01:59:58 +00:00
|
|
|
#include "stdafx.h"
|
|
|
|
#include "BaseMapper.h"
|
2014-07-09 23:05:07 +00:00
|
|
|
#include "CPU.h"
|
2016-01-07 04:10:29 +00:00
|
|
|
#include "EmulationSettings.h"
|
2016-08-26 03:29:23 +00:00
|
|
|
#include "A12Watcher.h"
|
2014-06-25 01:59:58 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2016-08-26 03:29:23 +00:00
|
|
|
A12Watcher _a12Watcher;
|
2016-06-03 00:35:08 +00:00
|
|
|
bool _needIrq;
|
|
|
|
|
2016-07-30 23:35:28 +00:00
|
|
|
bool _forceMmc3RevAIrqs;
|
|
|
|
|
2016-10-24 00:51:01 +00:00
|
|
|
struct Mmc3State {
|
2014-06-25 01:59:58 +00:00
|
|
|
uint8_t Reg8000;
|
|
|
|
uint8_t RegA000;
|
|
|
|
uint8_t RegA001;
|
|
|
|
} _state;
|
|
|
|
|
2016-06-03 23:16:31 +00:00
|
|
|
bool IsMcAcc()
|
|
|
|
{
|
|
|
|
return _mapperID == 4 && _subMapperID == 3;
|
|
|
|
}
|
|
|
|
|
2014-08-04 19:55:20 +00:00
|
|
|
protected:
|
2016-07-17 03:19:02 +00:00
|
|
|
uint8_t _irqReloadValue;
|
|
|
|
uint8_t _irqCounter;
|
|
|
|
bool _irqReload;
|
|
|
|
bool _irqEnabled;
|
2016-10-20 22:13:36 +00:00
|
|
|
uint8_t _prgMode;
|
2016-07-23 00:27:35 +00:00
|
|
|
uint8_t _chrMode;
|
|
|
|
uint8_t _registers[8];
|
2016-07-17 03:19:02 +00:00
|
|
|
|
2016-01-24 02:09:01 +00:00
|
|
|
uint8_t GetCurrentRegister()
|
|
|
|
{
|
|
|
|
return _currentRegister;
|
|
|
|
}
|
|
|
|
|
2016-10-24 00:51:01 +00:00
|
|
|
Mmc3State GetState()
|
|
|
|
{
|
|
|
|
return _state;
|
|
|
|
}
|
|
|
|
|
2016-01-24 02:09:01 +00:00
|
|
|
uint8_t GetChrMode()
|
|
|
|
{
|
|
|
|
return _chrMode;
|
|
|
|
}
|
|
|
|
|
2016-12-18 04:14:47 +00:00
|
|
|
void ResetMmc3()
|
2016-10-27 02:04:05 +00:00
|
|
|
{
|
|
|
|
_state.Reg8000 = 0;
|
|
|
|
_state.RegA000 = 0;
|
|
|
|
_state.RegA001 = 0;
|
|
|
|
_chrMode = 0;
|
|
|
|
_prgMode = 0;
|
|
|
|
_currentRegister = 0;
|
|
|
|
memset(_registers, 0, sizeof(_registers));
|
|
|
|
|
|
|
|
_irqCounter = 0;
|
|
|
|
_irqReloadValue = 0;
|
|
|
|
_irqReload = false;
|
|
|
|
_irqEnabled = false;
|
|
|
|
|
|
|
|
_wramEnabled = false;
|
|
|
|
_wramWriteProtected = false;
|
|
|
|
|
|
|
|
_needIrq = false;
|
|
|
|
}
|
|
|
|
|
2016-07-30 23:35:28 +00:00
|
|
|
virtual bool ForceMmc3RevAIrqs() { return _forceMmc3RevAIrqs; }
|
2014-06-25 01:59:58 +00:00
|
|
|
|
2016-01-01 17:34:16 +00:00
|
|
|
virtual void UpdateMirroring()
|
|
|
|
{
|
2015-07-30 02:10:34 +00:00
|
|
|
if(GetMirroringType() != MirroringType::FourScreens) {
|
|
|
|
SetMirroringType(((_state.RegA000 & 0x01) == 0x01) ? MirroringType::Horizontal : MirroringType::Vertical);
|
2014-06-25 01:59:58 +00:00
|
|
|
}
|
2016-01-01 17:34:16 +00:00
|
|
|
}
|
2014-06-25 01:59:58 +00:00
|
|
|
|
2016-01-01 17:34:16 +00:00
|
|
|
virtual void UpdateChrMapping()
|
|
|
|
{
|
2014-06-25 01:59:58 +00:00
|
|
|
if(_chrMode == 0) {
|
2014-07-13 01:01:28 +00:00
|
|
|
SelectCHRPage(0, _registers[0] & 0xFE);
|
|
|
|
SelectCHRPage(1, _registers[0] | 0x01);
|
|
|
|
SelectCHRPage(2, _registers[1] & 0xFE);
|
|
|
|
SelectCHRPage(3, _registers[1] | 0x01);
|
2014-06-25 01:59:58 +00:00
|
|
|
|
|
|
|
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]);
|
|
|
|
|
2014-07-13 01:01:28 +00:00
|
|
|
SelectCHRPage(4, _registers[0] & 0xFE);
|
|
|
|
SelectCHRPage(5, _registers[0] | 0x01);
|
|
|
|
SelectCHRPage(6, _registers[1] & 0xFE);
|
|
|
|
SelectCHRPage(7, _registers[1] | 0x01);
|
2014-06-25 01:59:58 +00:00
|
|
|
}
|
2016-01-01 17:34:16 +00:00
|
|
|
}
|
|
|
|
|
2016-07-23 19:20:25 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-21 01:05:18 +00:00
|
|
|
bool CanWriteToWorkRam()
|
|
|
|
{
|
|
|
|
return _wramEnabled && !_wramWriteProtected;
|
|
|
|
}
|
|
|
|
|
2016-01-01 17:34:16 +00:00
|
|
|
virtual void UpdateState()
|
|
|
|
{
|
|
|
|
_currentRegister = _state.Reg8000 & 0x07;
|
|
|
|
_chrMode = (_state.Reg8000 & 0x80) >> 7;
|
|
|
|
_prgMode = (_state.Reg8000 & 0x40) >> 6;
|
|
|
|
|
2016-07-26 23:19:28 +00:00
|
|
|
if(_subMapperID == 1) {
|
2016-12-18 04:14:47 +00:00
|
|
|
//bool wramEnabled = (_state.Reg8000 & 0x20) == 0x20;
|
2016-07-26 23:19:28 +00:00
|
|
|
RemoveCpuMemoryMapping(0x6000, 0x7000);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
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(IsNes20() && _subMapperID == 0) {
|
|
|
|
if(_wramEnabled) {
|
|
|
|
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam, CanWriteToWorkRam() ? MemoryAccessType::ReadWrite : MemoryAccessType::Read);
|
|
|
|
} else {
|
|
|
|
RemoveCpuMemoryMapping(0x6000, 0x7FFF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-01 17:34:16 +00:00
|
|
|
|
2016-07-23 19:20:25 +00:00
|
|
|
UpdatePrgMapping();
|
2016-01-01 17:34:16 +00:00
|
|
|
UpdateChrMapping();
|
2014-06-25 01:59:58 +00:00
|
|
|
}
|
|
|
|
|
2016-12-18 04:14:47 +00:00
|
|
|
virtual void StreamState(bool saving) override
|
2014-06-26 01:52:37 +00:00
|
|
|
{
|
|
|
|
BaseMapper::StreamState(saving);
|
2016-06-03 03:56:11 +00:00
|
|
|
ArrayInfo<uint8_t> registers = { _registers, 8 };
|
2016-08-26 03:29:23 +00:00
|
|
|
SnapshotInfo a12Watcher{ &_a12Watcher };
|
2016-06-03 00:20:26 +00:00
|
|
|
Stream(_state.Reg8000, _state.RegA000, _state.RegA001, _currentRegister, _chrMode, _prgMode,
|
2016-08-26 03:29:23 +00:00
|
|
|
_irqReloadValue, _irqCounter, _irqReload, _irqEnabled, a12Watcher,
|
2016-06-03 03:56:11 +00:00
|
|
|
_wramEnabled, _wramWriteProtected, registers, _needIrq);
|
2014-06-26 01:52:37 +00:00
|
|
|
}
|
|
|
|
|
2016-11-12 14:47:52 +00:00
|
|
|
void AfterLoadState() override
|
|
|
|
{
|
|
|
|
UpdateState();
|
|
|
|
}
|
|
|
|
|
2016-12-18 04:14:47 +00:00
|
|
|
virtual uint16_t GetPRGPageSize() override { return 0x2000; }
|
|
|
|
virtual uint16_t GetCHRPageSize() override { return 0x0400; }
|
|
|
|
virtual uint32_t GetSaveRamPageSize() override { return _subMapperID == 1 ? 0x200 : 0x2000; }
|
|
|
|
virtual uint32_t GetSaveRamSize() override { return _subMapperID == 1 ? 0x400 : 0x2000; }
|
2014-06-25 01:59:58 +00:00
|
|
|
|
2016-12-18 04:14:47 +00:00
|
|
|
virtual void InitMapper() override
|
2014-06-25 01:59:58 +00:00
|
|
|
{
|
2016-07-30 23:35:28 +00:00
|
|
|
//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 = _databaseInfo.Chip.substr(0, 5).compare("MMC3A") == 0;
|
|
|
|
|
2016-12-18 04:14:47 +00:00
|
|
|
ResetMmc3();
|
2016-01-24 16:18:50 +00:00
|
|
|
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam);
|
2014-06-25 01:59:58 +00:00
|
|
|
UpdateState();
|
2016-10-30 16:50:09 +00:00
|
|
|
UpdateMirroring();
|
2014-06-25 01:59:58 +00:00
|
|
|
}
|
|
|
|
|
2016-12-18 04:14:47 +00:00
|
|
|
virtual void WriteRegister(uint16_t addr, uint8_t value) override
|
2014-06-25 01:59:58 +00:00
|
|
|
{
|
|
|
|
switch((MMC3Registers)(addr & 0xE001)) {
|
|
|
|
case MMC3Registers::Reg8000:
|
|
|
|
_state.Reg8000 = value;
|
|
|
|
UpdateState();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MMC3Registers::Reg8001:
|
2017-01-15 19:24:34 +00:00
|
|
|
if(_currentRegister <= 1) {
|
2014-06-25 01:59:58 +00:00
|
|
|
//"Writes to registers 0 and 1 always ignore bit 0"
|
|
|
|
value &= ~0x01;
|
|
|
|
}
|
|
|
|
_registers[_currentRegister] = value;
|
|
|
|
UpdateState();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MMC3Registers::RegA000:
|
|
|
|
_state.RegA000 = value;
|
2016-10-25 23:11:53 +00:00
|
|
|
UpdateMirroring();
|
2014-06-25 01:59:58 +00:00
|
|
|
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;
|
2014-06-25 21:30:35 +00:00
|
|
|
CPU::ClearIRQSource(IRQSource::External);
|
2014-06-25 01:59:58 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MMC3Registers::RegE001:
|
|
|
|
_irqEnabled = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-17 03:19:02 +00:00
|
|
|
virtual void TriggerIrq()
|
2016-06-03 00:35:08 +00:00
|
|
|
{
|
2016-06-03 23:16:31 +00:00
|
|
|
if(IsMcAcc()) {
|
|
|
|
//MC-ACC (Acclaim copy of the MMC3)
|
2016-06-03 00:35:08 +00:00
|
|
|
//IRQ will be triggered on the next falling edge of A12 instead of on the rising edge like normal MMC3 behavior
|
|
|
|
//This adds a 4 ppu cycle delay (until the PPU fetches the next garbage NT tile between sprites)
|
|
|
|
_needIrq = true;
|
2016-06-03 23:16:31 +00:00
|
|
|
} else {
|
|
|
|
CPU::SetIRQSource(IRQSource::External);
|
2016-06-03 00:35:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-07-13 02:22:40 +00:00
|
|
|
public:
|
2016-12-18 04:14:47 +00:00
|
|
|
virtual void NotifyVRAMAddressChange(uint16_t addr) override
|
2014-06-25 01:59:58 +00:00
|
|
|
{
|
2016-08-26 03:29:23 +00:00
|
|
|
switch(_a12Watcher.UpdateVramAddress(addr)) {
|
|
|
|
case A12StateChange::Fall:
|
|
|
|
if(_needIrq) {
|
|
|
|
//Used by MC-ACC (Acclaim copy of the MMC3), see TriggerIrq above
|
|
|
|
CPU::SetIRQSource(IRQSource::External);
|
|
|
|
_needIrq = false;
|
2014-06-25 21:30:35 +00:00
|
|
|
}
|
2016-08-26 03:29:23 +00:00
|
|
|
break;
|
|
|
|
case A12StateChange::Rise:
|
2014-06-25 21:30:35 +00:00
|
|
|
uint32_t count = _irqCounter;
|
|
|
|
if(_irqCounter == 0 || _irqReload) {
|
|
|
|
_irqCounter = _irqReloadValue;
|
|
|
|
} else {
|
|
|
|
_irqCounter--;
|
|
|
|
}
|
2014-06-26 20:41:07 +00:00
|
|
|
|
2016-06-03 23:16:31 +00:00
|
|
|
//SubMapper 2 = MC-ACC (Acclaim MMC3 clone)
|
|
|
|
if(!IsMcAcc() && (ForceMmc3RevAIrqs() || EmulationSettings::CheckFlag(EmulationFlags::Mmc3IrqAltBehavior))) {
|
2016-01-21 01:32:49 +00:00
|
|
|
//MMC3 Revision A behavior
|
2016-01-07 04:10:29 +00:00
|
|
|
if((count > 0 || _irqReload) && _irqCounter == 0 && _irqEnabled) {
|
2016-06-03 00:35:08 +00:00
|
|
|
TriggerIrq();
|
2016-01-07 04:10:29 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(_irqCounter == 0 && _irqEnabled) {
|
2016-06-03 00:35:08 +00:00
|
|
|
TriggerIrq();
|
2016-01-07 04:10:29 +00:00
|
|
|
}
|
2014-06-25 21:30:35 +00:00
|
|
|
}
|
|
|
|
_irqReload = false;
|
2016-08-26 03:29:23 +00:00
|
|
|
break;
|
2014-06-25 21:30:35 +00:00
|
|
|
}
|
2014-06-25 01:59:58 +00:00
|
|
|
}
|
|
|
|
};
|