Mesen-X/Core/VRC2_4.h
Sour c52cacd9e5 VRC2: Added missing 1-bit latch at 6000-7FFF to fix Contra (J)
This used to work because the game would be emulated with 8kb of work ram at 6000-7FFF, which had the same effect.  But the actual board does not have work ram, so it was removed when integrating the new NES 2.0 database
2020-05-17 11:16:15 -04:00

316 lines
No EOL
7.8 KiB
C++

#pragma once
#include "stdafx.h"
#include "BaseMapper.h"
#include "VrcIrq.h"
enum class VRCVariant
{
VRC2a, //Mapper 22
VRC2b, //23
VRC2c, //25
VRC4a, //21
VRC4b, //25
VRC4c, //21
VRC4d, //25
VRC4e, //23
VRC4_27, //27
VRC6a,
VRC6b
};
class VRC2_4 : public BaseMapper
{
private:
unique_ptr<VrcIrq> _irq;
VRCVariant _variant;
bool _useHeuristics;
uint8_t _prgReg0;
uint8_t _prgReg1;
uint8_t _prgMode;
uint8_t _hiCHRRegs[8];
uint8_t _loCHRRegs[8];
uint8_t _latch = 0;
void DetectVariant()
{
switch(_romInfo.MapperID) {
default:
case 21:
//Conflicts: VRC4c
switch(_romInfo.SubMapperID) {
default:
case 0: _variant = VRCVariant::VRC4a; break;
case 1: _variant = VRCVariant::VRC4a; break;
case 2: _variant = VRCVariant::VRC4c; break;
}
break;
case 22: _variant = VRCVariant::VRC2a; break;
case 23:
//Conflicts: VRC4e
switch(_romInfo.SubMapperID) {
default:
case 0: _variant = VRCVariant::VRC2b; break;
case 2: _variant = VRCVariant::VRC4e; break;
case 3: _variant = VRCVariant::VRC2b; break;
}
break;
case 25:
//Conflicts: VRC2c, VRC4d
switch(_romInfo.SubMapperID) {
default:
case 0: _variant = VRCVariant::VRC4b; break;
case 1: _variant = VRCVariant::VRC4b; break;
case 2: _variant = VRCVariant::VRC4d; break;
case 3: _variant = VRCVariant::VRC2c; break;
}
break;
case 27: _variant = VRCVariant::VRC4_27; break; //Untested
}
_useHeuristics = (_romInfo.SubMapperID == 0) && _romInfo.MapperID != 22 && _romInfo.MapperID != 27;
}
protected:
uint16_t GetPRGPageSize() override { return 0x2000; }
uint16_t GetCHRPageSize() override { return 0x0400; }
bool AllowRegisterRead() override { return true; }
void InitMapper() override
{
_irq.reset(new VrcIrq(_console));
DetectVariant();
//PRG mode only exists for VRC4+ (so keep it as 0 at all times for VRC2)
_prgMode = _variant >= VRCVariant::VRC4a ? (GetPowerOnByte() & 0x01) : 0;
_prgReg0 = GetPowerOnByte() & 0x1F;
_prgReg1 = GetPowerOnByte() & 0x1F;
_latch = false;
for(int i = 0; i < 8; i++) {
_loCHRRegs[i] = GetPowerOnByte() & 0x0F;
_hiCHRRegs[i] = GetPowerOnByte() & 0x1F;
}
UpdateState();
RemoveRegisterRange(0, 0xFFFF, MemoryOperation::Read);
if(!_useHeuristics && _variant <= VRCVariant::VRC2c && _workRamSize == 0 && _saveRamSize == 0) {
AddRegisterRange(0x6000, 0x7FFF, MemoryOperation::Any);
}
}
void ProcessCpuClock() override
{
if((_useHeuristics && _romInfo.MapperID != 22) || _variant >= VRCVariant::VRC4a) {
//Only VRC4 supports IRQs
_irq->ProcessCpuClock();
}
}
void UpdateState()
{
for(int i = 0; i < 8; i++) {
uint32_t page = _loCHRRegs[i] | (_hiCHRRegs[i] << 4);
if(_variant == VRCVariant::VRC2a) {
//"On VRC2a (mapper 022) only the high 7 bits of the CHR regs are used -- the low bit is ignored. Therefore, you effectively have to right-shift the CHR page by 1 to get the actual page number."
page >>= 1;
}
SelectCHRPage(i, page);
}
if(_prgMode == 0) {
SelectPRGPage(0, _prgReg0);
SelectPRGPage(1, _prgReg1);
SelectPRGPage(2, -2);
SelectPRGPage(3, -1);
} else {
SelectPRGPage(0, -2);
SelectPRGPage(1, _prgReg1);
SelectPRGPage(2, _prgReg0);
SelectPRGPage(3, -1);
}
}
uint8_t ReadRegister(uint16_t addr) override
{
//Microwire interface ($6000-$6FFF) (VRC2 only)
return _latch | (_console->GetMemoryManager()->GetOpenBus() & 0xFE);
}
void WriteRegister(uint16_t addr, uint8_t value) override
{
if(addr < 0x8000) {
//Microwire interface ($6000-$6FFF) (VRC2 only)
_latch = value & 0x01;
return;
}
addr = TranslateAddress(addr) & 0xF00F;
if(addr >= 0x8000 && addr <= 0x8006) {
_prgReg0 = value & 0x1F;
} else if((_variant <= VRCVariant::VRC2c && addr >= 0x9000 && addr <= 0x9003) || (_variant >= VRCVariant::VRC4a && addr >= 0x9000 && addr <= 0x9001)) {
uint8_t mask = 0x03;
if(!_useHeuristics && (_variant >= VRCVariant::VRC2a && _variant <= VRCVariant::VRC2c)) {
//When we are certain this is a VRC2 game, only use the first bit for mirroring selection
mask = 0x01;
}
switch(value & mask) {
case 0: SetMirroringType(MirroringType::Vertical); break;
case 1: SetMirroringType(MirroringType::Horizontal); break;
case 2: SetMirroringType(MirroringType::ScreenAOnly); break;
case 3: SetMirroringType(MirroringType::ScreenBOnly); break;
}
} else if(_variant >= VRCVariant::VRC4a && addr >= 0x9002 && addr <= 0x9003) {
_prgMode = (value >> 1) & 0x01;
} else if(addr >= 0xA000 && addr <= 0xA006) {
_prgReg1 = value & 0x1F;
} else if(addr >= 0xB000 && addr <= 0xE006) {
uint8_t regNumber = ((((addr >> 12) & 0x07) - 3) << 1) + ((addr >> 1) & 0x01);
bool lowBits = (addr & 0x01) == 0x00;
if(lowBits) {
//The other reg contains the low 4 bits
_loCHRRegs[regNumber] = value & 0x0F;
} else {
//One reg contains the high 5 bits
_hiCHRRegs[regNumber] = value & 0x1F;
}
} else if(addr == 0xF000) {
_irq->SetReloadValueNibble(value, false);
} else if(addr == 0xF001) {
_irq->SetReloadValueNibble(value, true);
} else if(addr == 0xF002) {
_irq->SetControlValue(value);
} else if(addr == 0xF003) {
_irq->AcknowledgeIrq();
}
UpdateState();
}
public:
uint16_t TranslateAddress(uint16_t addr)
{
uint32_t A0, A1;
if(_useHeuristics) {
switch(_variant) {
case VRCVariant::VRC2c:
case VRCVariant::VRC4b:
case VRCVariant::VRC4d:
//Mapper 25
//ORing both values should make most games work.
//VRC2c & VRC4b (Both uses the same bits)
A0 = (addr >> 1) & 0x01;
A1 = (addr & 0x01);
//VRC4d
A0 |= (addr >> 3) & 0x01;
A1 |= (addr >> 2) & 0x01;
break;
case VRCVariant::VRC4a:
case VRCVariant::VRC4c:
//Mapper 21
//VRC4a
A0 = (addr >> 1) & 0x01;
A1 = (addr >> 2) & 0x01;
//VRC4c
A0 |= (addr >> 6) & 0x01;
A1 |= (addr >> 7) & 0x01;
break;
case VRCVariant::VRC2b:
case VRCVariant::VRC4e:
//Mapper 23
//VRC2b
A0 = addr & 0x01;
A1 = (addr >> 1) & 0x01;
//VRC4e
A0 |= (addr >> 2) & 0x01;
A1 |= (addr >> 3) & 0x01;
break;
default:
throw std::runtime_error("not supported");
break;
}
} else {
switch(_variant) {
case VRCVariant::VRC2a:
//Mapper 22
A0 = (addr >> 1) & 0x01;
A1 = (addr & 0x01);
break;
case VRCVariant::VRC4_27:
//Mapper 27
A0 = addr & 0x01;
A1 = (addr >> 1) & 0x01;
break;
case VRCVariant::VRC2c:
case VRCVariant::VRC4b:
//Mapper 25
A0 = (addr >> 1) & 0x01;
A1 = (addr & 0x01);
break;
case VRCVariant::VRC4d:
//Mapper 25
A0 = (addr >> 3) & 0x01;
A1 = (addr >> 2) & 0x01;
break;
case VRCVariant::VRC4a:
//Mapper 21
A0 = (addr >> 1) & 0x01;
A1 = (addr >> 2) & 0x01;
break;
case VRCVariant::VRC4c:
//Mapper 21
A0 = (addr >> 6) & 0x01;
A1 = (addr >> 7) & 0x01;
break;
case VRCVariant::VRC2b:
//Mapper 23
A0 = addr & 0x01;
A1 = (addr >> 1) & 0x01;
break;
case VRCVariant::VRC4e:
//Mapper 23
A0 = (addr >> 2) & 0x01;
A1 = (addr >> 3) & 0x01;
break;
default:
throw std::runtime_error("not supported");
break;
}
}
return (addr & 0xFF00) | (A1 << 1) | A0;
}
void StreamState(bool saving) override
{
BaseMapper::StreamState(saving);
ArrayInfo<uint8_t> loChrRegs = { _loCHRRegs, 8 };
ArrayInfo<uint8_t> hiChrRegs = { _hiCHRRegs, 8 };
SnapshotInfo irq{ _irq.get() };
Stream(_prgReg0, _prgReg1, _prgMode, loChrRegs, hiChrRegs, _latch, irq);
}
};