#pragma once #include "stdafx.h" #include "BaseMapper.h" #include "CPU.h" class JyCompany : public BaseMapper { private: enum class JyIrqSource { CpuClock = 0, PpuA12Rise = 1, PpuRead = 2, CpuWrite = 3 }; uint8_t _prgRegs[4]; uint8_t _chrLowRegs[8]; uint8_t _chrHighRegs[8]; uint8_t _chrLatch[2]; uint8_t _prgMode; bool _enablePrgAt6000; uint8_t _chrMode; bool _chrBlockMode; uint8_t _chrBlock; bool _mirrorChr; uint8_t _mirroringReg; bool _advancedNtControl; bool _disableNtRam; uint8_t _ntRamSelectBit; uint8_t _ntLowRegs[4]; uint8_t _ntHighRegs[4]; bool _irqEnabled; JyIrqSource _irqSource; uint8_t _irqCountDirection; bool _irqFunkyMode; uint8_t _irqFunkyModeReg; bool _irqSmallPrescaler; uint8_t _irqPrescaler; uint8_t _irqCounter; uint8_t _irqXorReg; uint8_t _multiplyValue1; uint8_t _multiplyValue2; uint8_t _regRamValue; uint16_t _lastPpuAddr; protected: virtual uint16_t GetPRGPageSize() { return 0x2000; } virtual uint16_t GetCHRPageSize() { return 0x0400; } virtual bool AllowRegisterRead() { return true; } void InitMapper() { RemoveRegisterRange(0x8000, 0xFFFF, MemoryOperation::Read); AddRegisterRange(0x5000, 0x5FFF, MemoryOperation::Any); _chrLatch[0] = 0; _chrLatch[1] = 4; memset(_prgRegs, 0, sizeof(_prgRegs)); memset(_chrLowRegs, 0, sizeof(_chrLowRegs)); memset(_chrHighRegs, 0, sizeof(_chrHighRegs)); _prgMode = 0; _enablePrgAt6000 = false; _chrMode = 0; _chrBlockMode = false; _chrBlock = 0; _mirrorChr = false; _mirroringReg = 0; _advancedNtControl = false; _disableNtRam = false; _ntRamSelectBit = 0; memset(_ntLowRegs, 0, sizeof(_ntLowRegs)); memset(_ntHighRegs, 0, sizeof(_ntHighRegs)); _irqEnabled = false; _irqSource = JyIrqSource::CpuClock; _lastPpuAddr = 0; _irqCountDirection = 0; _irqFunkyMode = false; _irqFunkyModeReg = 0; _irqSmallPrescaler = false; _irqPrescaler = 0; _irqCounter = 0; _irqXorReg = 0; _multiplyValue1 = 0; _multiplyValue2 = 0; _regRamValue = 0; UpdateState(); } void StreamState(bool saving) { BaseMapper::StreamState(saving); ArrayInfo prgRegs{ _prgRegs, 4 }; ArrayInfo chrLowRegs{ _chrLowRegs, 8 }; ArrayInfo chrHighRegs{ _chrHighRegs, 8 }; ArrayInfo ntLowRegs{ _ntLowRegs, 4 }; ArrayInfo ntHighRegs{ _ntHighRegs, 4 }; Stream(_chrLatch[0], _chrLatch[1], _prgMode, _enablePrgAt6000, _chrMode, _chrBlockMode, _chrBlock, _mirrorChr, _mirroringReg, _advancedNtControl, _disableNtRam, _ntRamSelectBit, _irqEnabled, _irqSource, _lastPpuAddr, _irqCountDirection, _irqFunkyMode, _irqFunkyModeReg, _irqSmallPrescaler, _irqPrescaler, _irqCounter, _irqXorReg, _multiplyValue1, _multiplyValue2, _regRamValue, prgRegs, chrLowRegs, chrHighRegs, ntLowRegs, ntHighRegs); if(!saving) { UpdateState(); } } void UpdateState() { UpdatePrgState(); UpdateChrState(); UpdateMirroringState(); } uint8_t InvertPrgBits(uint8_t prgReg, bool needInvert) { if(needInvert) { return (prgReg & 0x01) << 6 | (prgReg & 0x02) << 4 | (prgReg & 0x04) << 2 | (prgReg & 0x10) >> 2 | (prgReg & 0x20) >> 4 | (prgReg & 0x40) >> 6; } else { return prgReg; } } void UpdatePrgState() { bool invertBits = (_prgMode & 0x03) == 0x03; int prgRegs[4] = { InvertPrgBits(_prgRegs[0], invertBits), InvertPrgBits(_prgRegs[1], invertBits), InvertPrgBits(_prgRegs[2], invertBits), InvertPrgBits(_prgRegs[3], invertBits) }; int lastPage = (_prgMode & 0x04) ? prgRegs[3] : -1; switch(_prgMode & 0x03) { case 0: SelectPrgPage4x(0, lastPage * 4); if(_enablePrgAt6000) { SetCpuMemoryMapping(0x6000, 0x7FFF, prgRegs[3] * 4 + 3, PrgMemoryType::PrgRom); } break; case 1: SelectPrgPage2x(0, prgRegs[1] << 1); SelectPrgPage2x(1, lastPage * 2); if(_enablePrgAt6000) { SetCpuMemoryMapping(0x6000, 0x7FFF, prgRegs[3] * 2 + 1, PrgMemoryType::PrgRom); } break; case 2: case 3: SelectPRGPage(0, prgRegs[0]); SelectPRGPage(1, prgRegs[1]); SelectPRGPage(2, prgRegs[2]); SelectPRGPage(3, lastPage); if(_enablePrgAt6000) { SetCpuMemoryMapping(0x6000, 0x7FFF, prgRegs[3], PrgMemoryType::PrgRom); } break; } if(!_enablePrgAt6000) { RemoveCpuMemoryMapping(0x6000, 0x7FFF); } } uint16_t GetChrReg(int index) { if(_chrMode >= 2 && _mirrorChr && (index == 2 || index == 3)) { index -= 2; } if(_chrBlockMode) { return _chrLowRegs[index] | (_chrBlock << 8); } else { return _chrLowRegs[index] | (_chrHighRegs[index] << 8); } } void UpdateChrState() { int chrRegs[8] = { GetChrReg(0), GetChrReg(1), GetChrReg(2), GetChrReg(3), GetChrReg(4), GetChrReg(5), GetChrReg(6), GetChrReg(7) }; switch(_chrMode) { case 0: SelectChrPage8x(0, chrRegs[0] << 3); break; case 1: SelectChrPage4x(0, chrRegs[_chrLatch[0]] << 2); SelectChrPage4x(1, chrRegs[_chrLatch[1]] << 2); break; case 2: SelectChrPage2x(0, chrRegs[0] << 1); SelectChrPage2x(1, chrRegs[2] << 1); SelectChrPage2x(2, chrRegs[4] << 1); SelectChrPage2x(3, chrRegs[6] << 1); break; case 3: for(int i = 0; i < 8; i++) { SelectCHRPage(i, chrRegs[i]); } break; } } void UpdateMirroringState() { //"Mapper 211 behaves as though N were always set (1), and mapper 090 behaves as though N were always clear(0)." if((_advancedNtControl || _mapperID == 211) && _mapperID != 90) { for(int i = 0; i < 4; i++) { if(_disableNtRam || (_ntLowRegs[i] & 0x80) != (_ntRamSelectBit & 0x80)) { SetPpuMemoryMapping(0x2000 + 0x400 * i, 0x23FF + 0x400 * i, _ntLowRegs[i] | (_ntHighRegs[i] << 8), ChrMemoryType::ChrRom); } else { SetNametable(i, _ntLowRegs[i] & 0x01); } } } else { switch(_mirroringReg) { case 0: SetMirroringType(MirroringType::Vertical); break; case 1: SetMirroringType(MirroringType::Horizontal); break; case 2: SetMirroringType(MirroringType::ScreenAOnly); break; case 3: SetMirroringType(MirroringType::ScreenBOnly); break; } } } uint8_t ReadRegister(uint16_t addr) { switch(addr & 0xF803) { case 0x5000: return 0; //Dip switches case 0x5800: return (_multiplyValue1 * _multiplyValue2) & 0xFF; case 0x5801: return ((_multiplyValue1 * _multiplyValue2) >> 8) & 0xFF; case 0x5803: return _regRamValue; } return MemoryManager::GetOpenBus(); } void WriteRegister(uint16_t addr, uint8_t value) { if(addr < 0x8000) { switch(addr & 0xF803) { case 0x5800: _multiplyValue1 = value; break; case 0x5801: _multiplyValue2 = value; break; case 0x5803: _regRamValue = value; break; } } else { switch(addr & 0xF007) { case 0x8000: case 0x8001: case 0x8002: case 0x8003: case 0x8004: case 0x8005: case 0x8006: case 0x8007: _prgRegs[addr & 0x03] = value & 0x7F; break; case 0x9000: case 0x9001: case 0x9002: case 0x9003: case 0x9004: case 0x9005: case 0x9006: case 0x9007: _chrLowRegs[addr & 0x07] = value; break; case 0xA000: case 0xA001: case 0xA002: case 0xA003: case 0xA004: case 0xA005: case 0xA006: case 0xA007: _chrHighRegs[addr & 0x07] = value; break; case 0xB000: case 0xB001: case 0xB002: case 0xB003: _ntLowRegs[addr & 0x03] = value; break; case 0xB004: case 0xB005: case 0xB006: case 0xB007: _ntHighRegs[addr & 0x03] = value; break; case 0xC000: if(value & 0x01) { _irqEnabled = true; } else { _irqEnabled = false; CPU::ClearIRQSource(IRQSource::External); } break; case 0xC001: _irqCountDirection = (value >> 6) & 0x03; _irqFunkyMode = (value & 0x08) == 0x08; _irqSmallPrescaler = ((value >> 2) & 0x01) == 0x01; _irqSource = (JyIrqSource)(value & 0x03); break; case 0xC002: _irqEnabled = false; CPU::ClearIRQSource(IRQSource::External); break; case 0xC003: _irqEnabled = true; break; case 0xC004: _irqPrescaler = value ^ _irqXorReg; break; case 0xC005: _irqCounter = value ^ _irqXorReg; break; case 0xC006: _irqXorReg = value; break; case 0xC007: _irqFunkyModeReg = value; break; case 0xD000: _prgMode = value & 0x07; _chrMode = (value >> 3) & 0x03; _advancedNtControl = (value & 0x20) == 0x20; _disableNtRam = (value & 0x40) == 0x40; _enablePrgAt6000 = (value & 0x80) == 0x80; break; case 0xD001: _mirroringReg = value & 0x03; break; case 0xD002: _ntRamSelectBit = value & 0x80; break; case 0xD003: _mirrorChr = (value & 0x80) == 0x80; _chrBlockMode = (value & 0x20) == 0x00; _chrBlock = value & 0x1F; break; } } UpdateState(); } void ProcessCpuClock() { if(_irqSource == JyIrqSource::CpuClock || (_irqSource == JyIrqSource::CpuWrite && CPU::IsCpuWrite())) { TickIrqCounter(); } } uint8_t ReadVRAM(uint16_t addr, MemoryOperationType type) { if(_irqSource == JyIrqSource::PpuRead && type == MemoryOperationType::PpuRenderingRead) { TickIrqCounter(); } return BaseMapper::ReadVRAM(addr, type); } void NotifyVRAMAddressChange(uint16_t addr) { if(_irqSource == JyIrqSource::PpuA12Rise && (addr & 0x1000) && !(_lastPpuAddr & 0x1000)) { TickIrqCounter(); } _lastPpuAddr = addr; if(_mapperID == 209) { switch(addr & 0x2FF8) { case 0x0FD8: case 0x0FE8: _chrLatch[addr >> 12] = addr >> 4 & ((addr >> 10 & 0x04) | 0x02); UpdateChrState(); break; } } } void TickIrqCounter() { bool clockIrqCounter = false; uint8_t mask = _irqSmallPrescaler ? 0x07 : 0xFF; uint8_t prescaler = _irqPrescaler & mask; if(_irqCountDirection == 0x01) { prescaler++; if((prescaler & mask) == 0) { clockIrqCounter = true; } } else if(_irqCountDirection == 0x02) { if(--prescaler == 0) { clockIrqCounter = true; } } _irqPrescaler = (_irqPrescaler & ~mask) | (prescaler & mask); if(clockIrqCounter) { if(_irqCountDirection == 0x01) { _irqCounter++; if(_irqCounter == 0 && _irqEnabled) { CPU::SetIRQSource(IRQSource::External); } } else if(_irqCountDirection == 0x02) { _irqCounter--; if(_irqCounter == 0xFF && _irqEnabled) { CPU::SetIRQSource(IRQSource::External); } } } } };