c52cacd9e5
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
316 lines
No EOL
7.8 KiB
C++
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);
|
|
}
|
|
}; |