2015-07-29 22:10:34 -04:00
# pragma once
# include "stdafx.h"
# include "BaseMapper.h"
# include "PPU.h"
2016-06-12 11:28:45 -04:00
# include "MMC5Audio.h"
2020-01-19 14:45:04 -05:00
# include "MMC5MemoryHandler.h"
2015-07-29 22:10:34 -04:00
class MMC5 : public BaseMapper
{
private :
2019-01-12 13:18:29 -05:00
static constexpr int ExRamSize = 0x400 ;
static constexpr uint8_t NtWorkRamIndex = 4 ;
static constexpr uint8_t NtEmptyIndex = 2 ;
static constexpr uint8_t NtFillModeIndex = 3 ;
2016-01-24 11:18:50 -05:00
2018-07-01 15:21:05 -04:00
unique_ptr < MMC5Audio > _audio ;
2020-01-19 14:45:04 -05:00
unique_ptr < MMC5MemoryHandler > _mmc5MemoryHandler ;
2016-06-12 11:28:45 -04:00
2015-07-29 22:10:34 -04:00
uint8_t _prgRamProtect1 ;
uint8_t _prgRamProtect2 ;
uint8_t _fillModeTile ;
uint8_t _fillModeColor ;
bool _verticalSplitEnabled ;
bool _verticalSplitRightSide ;
uint8_t _verticalSplitDelimiterTile ;
uint8_t _verticalSplitScroll ;
uint8_t _verticalSplitBank ;
2016-07-02 14:48:46 -04:00
bool _splitInSplitRegion ;
uint32_t _splitVerticalScroll ;
uint32_t _splitTile ;
int32_t _splitTileNumber ;
2015-07-29 22:10:34 -04:00
uint8_t _multiplierValue1 ;
uint8_t _multiplierValue2 ;
uint8_t _nametableMapping ;
uint8_t _extendedRamMode ;
//Extended attribute mode fields (used when _extendedRamMode == 1)
uint16_t _exAttributeLastNametableFetch ;
int8_t _exAttrLastFetchCounter ;
uint8_t _exAttrSelectedChrBank ;
uint8_t _prgMode ;
uint8_t _prgBanks [ 5 ] ;
//CHR-related fields
uint8_t _chrMode ;
2019-01-12 13:18:29 -05:00
uint8_t _chrUpperBits ;
2015-07-29 22:10:34 -04:00
uint16_t _chrBanks [ 12 ] ;
uint16_t _lastChrReg ;
2020-01-19 14:45:04 -05:00
bool _prevChrA ;
2015-07-29 22:10:34 -04:00
//IRQ counter related fields
uint8_t _irqCounterTarget ;
bool _irqEnabled ;
2020-01-19 14:45:04 -05:00
uint8_t _scanlineCounter ;
2015-07-29 22:10:34 -04:00
bool _irqPending ;
2020-01-19 14:45:04 -05:00
bool _needInFrame ;
bool _ppuInFrame ;
uint8_t _ppuIdleCounter ;
uint16_t _lastPpuReadAddr ;
uint8_t _ntReadCounter ;
2017-04-14 14:09:54 -04:00
2015-07-29 22:10:34 -04:00
void SwitchPrgBank ( uint16_t reg , uint8_t value )
{
_prgBanks [ reg - 0x5113 ] = value ;
UpdatePrgBanks ( ) ;
}
void GetCpuBankInfo ( uint16_t reg , uint8_t & bankNumber , PrgMemoryType & memoryType , uint8_t & accessType )
{
bankNumber = _prgBanks [ reg - 0x5113 ] ;
memoryType = PrgMemoryType : : PrgRom ;
2016-12-30 13:42:12 -05:00
if ( ( ( ( bankNumber & 0x80 ) = = 0x00 ) & & reg ! = 0x5117 ) | | reg = = 0x5113 ) {
2015-07-29 22:10:34 -04:00
bankNumber & = 0x07 ;
2015-08-18 18:02:40 -04:00
accessType = MemoryAccessType : : Read ;
if ( _prgRamProtect1 = = 0x02 & & _prgRamProtect2 = = 0x01 ) {
2015-07-29 22:10:34 -04:00
accessType | = MemoryAccessType : : Write ;
}
2019-01-12 13:18:29 -05:00
// WRAM/SRAM mirroring logic (only supports existing/known licensed MMC5 boards)
// Bank number
// 0 1 2 3 4 5 6 7
// --------------------------
// None : - - - - - - - -
// 1x 8kb : 0 0 0 0 - - - -
// 2x 8kb : 0 0 0 0 1 1 1 1
// 1x 32kb : 0 1 2 3 - - - -
2019-12-23 22:20:34 -05:00
int32_t realWorkRamSize = _workRamSize - ( HasBattery ( ) ? 0 : MMC5 : : ExRamSize ) ;
int32_t realSaveRamSize = _saveRamSize - ( ! HasBattery ( ) ? 0 : MMC5 : : ExRamSize ) ;
2019-01-12 13:18:29 -05:00
if ( IsNes20 ( ) | | _romInfo . IsInDatabase ) {
memoryType = PrgMemoryType : : WorkRam ;
2019-12-23 22:20:34 -05:00
if ( HasBattery ( ) & & ( bankNumber < = 3 | | realSaveRamSize > 0x2000 ) ) {
2019-01-12 13:18:29 -05:00
memoryType = PrgMemoryType : : SaveRam ;
}
2019-12-23 22:20:34 -05:00
if ( realSaveRamSize + realWorkRamSize ! = 0x4000 & & bankNumber > = 4 ) {
2019-01-12 13:18:29 -05:00
//When not 2x 8kb (=16kb), banks 4/5/6/7 select the empty socket and return open bus
accessType = MemoryAccessType : : NoAccess ;
}
} else {
memoryType = HasBattery ( ) ? PrgMemoryType : : SaveRam : PrgMemoryType : : WorkRam ;
}
if ( memoryType = = PrgMemoryType : : WorkRam ) {
//Properly mirror work ram (by ignoring the extra 1kb ExRAM section)
bankNumber & = ( realWorkRamSize / 0x2000 ) - 1 ;
if ( _workRamSize = = MMC5 : : ExRamSize ) {
accessType = MemoryAccessType : : NoAccess ;
}
2019-12-23 22:20:34 -05:00
} else if ( memoryType = = PrgMemoryType : : SaveRam ) {
//Properly mirror work ram (by ignoring the extra 1kb ExRAM section)
bankNumber & = ( realSaveRamSize / 0x2000 ) - 1 ;
if ( _saveRamSize = = MMC5 : : ExRamSize ) {
accessType = MemoryAccessType : : NoAccess ;
}
2019-01-12 13:18:29 -05:00
}
2015-07-29 22:10:34 -04:00
} else {
accessType = MemoryAccessType : : Read ;
bankNumber & = 0x7F ;
}
}
void UpdatePrgBanks ( )
{
uint8_t value ;
PrgMemoryType memoryType ;
uint8_t accessType ;
2019-01-12 13:18:29 -05:00
2015-07-29 22:10:34 -04:00
GetCpuBankInfo ( 0x5113 , value , memoryType , accessType ) ;
SetCpuMemoryMapping ( 0x6000 , 0x7FFF , value , memoryType , accessType ) ;
2019-01-12 13:18:29 -05:00
2015-07-29 22:10:34 -04:00
//PRG Bank 0
//Mode 0,1,2 - Ignored
//Mode 3 - Select an 8KB PRG bank at $8000-$9FFF
if ( _prgMode = = 3 ) {
GetCpuBankInfo ( 0x5114 , value , memoryType , accessType ) ;
SetCpuMemoryMapping ( 0x8000 , 0x9FFF , value , memoryType , accessType ) ;
}
//PRG Bank 1
//Mode 0 - Ignored
//Mode 1,2 - Select a 16KB PRG bank at $8000-$BFFF (ignore bottom bit)
//Mode 3 - Select an 8KB PRG bank at $A000-$BFFF
GetCpuBankInfo ( 0x5115 , value , memoryType , accessType ) ;
if ( _prgMode = = 1 | | _prgMode = = 2 ) {
SetCpuMemoryMapping ( 0x8000 , 0xBFFF , value & 0xFE , memoryType , accessType ) ;
} else if ( _prgMode = = 3 ) {
SetCpuMemoryMapping ( 0xA000 , 0xBFFF , value , memoryType , accessType ) ;
}
//Mode 0,1 - Ignored
//Mode 2,3 - Select an 8KB PRG bank at $C000-$DFFF
if ( _prgMode = = 2 | | _prgMode = = 3 ) {
GetCpuBankInfo ( 0x5116 , value , memoryType , accessType ) ;
SetCpuMemoryMapping ( 0xC000 , 0xDFFF , value , memoryType , accessType ) ;
}
//Mode 0 - Select a 32KB PRG ROM bank at $8000-$FFFF (ignore bottom 2 bits)
//Mode 1 - Select a 16KB PRG ROM bank at $C000-$FFFF (ignore bottom bit)
//Mode 2,3 - Select an 8KB PRG ROM bank at $E000-$FFFF
GetCpuBankInfo ( 0x5117 , value , memoryType , accessType ) ;
if ( _prgMode = = 0 ) {
SetCpuMemoryMapping ( 0x8000 , 0xFFFF , value & 0x7C , memoryType , accessType ) ;
} else if ( _prgMode = = 1 ) {
SetCpuMemoryMapping ( 0xC000 , 0xFFFF , value & 0x7E , memoryType , accessType ) ;
} else if ( _prgMode = = 2 | | _prgMode = = 3 ) {
SetCpuMemoryMapping ( 0xE000 , 0xFFFF , value & 0x7F , memoryType , accessType ) ;
}
}
void SwitchChrBank ( uint16_t reg , uint8_t value )
{
2020-01-19 14:45:04 -05:00
uint16_t newValue = value | ( _chrUpperBits < < 8 ) ;
if ( newValue ! = _chrBanks [ reg - 0x5120 ] | | _lastChrReg ! = reg ) {
_chrBanks [ reg - 0x5120 ] = newValue ;
2017-04-23 19:26:56 -04:00
_lastChrReg = reg ;
2020-01-19 14:45:04 -05:00
UpdateChrBanks ( true ) ;
2017-04-23 19:26:56 -04:00
}
2015-07-29 22:10:34 -04:00
}
2020-01-19 14:45:04 -05:00
void UpdateChrBanks ( bool forceUpdate )
2015-07-29 22:10:34 -04:00
{
2020-01-19 14:45:04 -05:00
bool largeSprites = ( _mmc5MemoryHandler - > GetReg ( 0x2000 ) & 0x20 ) ! = 0 ;
if ( ! largeSprites ) {
2017-04-23 19:26:56 -04:00
//Using 8x8 sprites resets the last written to bank logic
_lastChrReg = 0 ;
}
2020-01-19 14:45:04 -05:00
bool chrA = ! largeSprites | | ( _splitTileNumber > = 32 & & _splitTileNumber < 40 ) | | ( ! _ppuInFrame & & _lastChrReg < = 0x5127 ) ;
if ( ! forceUpdate & & chrA = = _prevChrA ) {
return ;
}
_prevChrA = chrA ;
2015-07-29 22:10:34 -04:00
if ( _chrMode = = 0 ) {
2016-01-24 11:18:50 -05:00
SelectChrPage8x ( 0 , _chrBanks [ chrA ? 0x07 : 0x0B ] < < 3 ) ;
2015-07-29 22:10:34 -04:00
} else if ( _chrMode = = 1 ) {
2016-01-24 11:18:50 -05:00
SelectChrPage4x ( 0 , _chrBanks [ chrA ? 0x03 : 0x0B ] < < 2 ) ;
SelectChrPage4x ( 1 , _chrBanks [ chrA ? 0x07 : 0x0B ] < < 2 ) ;
2015-07-29 22:10:34 -04:00
} else if ( _chrMode = = 2 ) {
2016-01-24 11:18:50 -05:00
SelectChrPage2x ( 0 , _chrBanks [ chrA ? 0x01 : 0x09 ] < < 1 ) ;
SelectChrPage2x ( 1 , _chrBanks [ chrA ? 0x03 : 0x0B ] < < 1 ) ;
SelectChrPage2x ( 2 , _chrBanks [ chrA ? 0x05 : 0x09 ] < < 1 ) ;
SelectChrPage2x ( 3 , _chrBanks [ chrA ? 0x07 : 0x0B ] < < 1 ) ;
2015-07-29 22:10:34 -04:00
} else if ( _chrMode = = 3 ) {
2016-01-24 11:18:50 -05:00
SelectCHRPage ( 0 , _chrBanks [ chrA ? 0x00 : 0x08 ] ) ;
SelectCHRPage ( 1 , _chrBanks [ chrA ? 0x01 : 0x09 ] ) ;
SelectCHRPage ( 2 , _chrBanks [ chrA ? 0x02 : 0x0A ] ) ;
SelectCHRPage ( 3 , _chrBanks [ chrA ? 0x03 : 0x0B ] ) ;
SelectCHRPage ( 4 , _chrBanks [ chrA ? 0x04 : 0x08 ] ) ;
SelectCHRPage ( 5 , _chrBanks [ chrA ? 0x05 : 0x09 ] ) ;
SelectCHRPage ( 6 , _chrBanks [ chrA ? 0x06 : 0x0A ] ) ;
SelectCHRPage ( 7 , _chrBanks [ chrA ? 0x07 : 0x0B ] ) ;
2015-07-29 22:10:34 -04:00
}
}
2016-12-17 23:14:47 -05:00
void ProcessCpuClock ( ) override
2016-01-24 21:35:24 -05:00
{
2018-07-01 15:21:05 -04:00
_audio - > Clock ( ) ;
2016-01-24 21:35:24 -05:00
2020-01-19 14:45:04 -05:00
if ( _ppuIdleCounter ) {
_ppuIdleCounter - - ;
if ( _ppuIdleCounter = = 0 ) {
//"The "in-frame" flag is cleared when the PPU is no longer rendering. This is detected when 3 CPU cycles pass without a PPU read having occurred (PPU /RD has not been low during the last 3 M2 rises)."
_ppuInFrame = false ;
UpdateChrBanks ( true ) ;
2015-07-29 22:10:34 -04:00
}
}
}
void SetNametableMapping ( uint8_t value )
{
_nametableMapping = value ;
2019-01-12 13:18:29 -05:00
uint8_t nametables [ 4 ] = {
2016-01-24 11:18:50 -05:00
0 , //"0 - On-board VRAM page 0"
1 , //"1 - On-board VRAM page 1"
_extendedRamMode < = 1 ? NtWorkRamIndex : NtEmptyIndex , //"2 - Internal Expansion RAM, only if the Extended RAM mode allows it ($5104 is 00/01); otherwise, the nametable will read as all zeros,"
NtFillModeIndex //"3 - Fill-mode data"
2015-07-29 22:10:34 -04:00
} ;
2019-01-09 20:19:16 -05:00
for ( int i = 0 ; i < 4 ; i + + ) {
uint8_t nametableId = nametables [ ( value > > ( i * 2 ) ) & 0x03 ] ;
if ( nametableId = = NtWorkRamIndex ) {
2019-12-23 22:20:34 -05:00
uint8_t * source = HasBattery ( ) ? ( _saveRam + ( _saveRamSize - MMC5 : : ExRamSize ) ) : ( _workRam + ( _workRamSize - MMC5 : : ExRamSize ) ) ;
SetPpuMemoryMapping ( 0x2000 + i * 0x400 , 0x2000 + i * 0x400 + 0x3FF , source , MemoryAccessType : : ReadWrite ) ;
2019-01-09 20:19:16 -05:00
} else {
SetNametable ( i , nametableId ) ;
}
}
2015-07-29 22:10:34 -04:00
}
void SetExtendedRamMode ( uint8_t mode )
{
_extendedRamMode = mode ;
2019-01-12 13:18:29 -05:00
MemoryAccessType accessType ;
2015-07-29 22:10:34 -04:00
if ( _extendedRamMode < = 1 ) {
//"Mode 0/1 - Not readable (returns open bus), can only be written while the PPU is rendering (otherwise, 0 is written)"
//See overridden WriteRam function for implementation
2019-01-12 13:18:29 -05:00
accessType = MemoryAccessType : : Write ;
2015-07-29 22:10:34 -04:00
} else if ( _extendedRamMode = = 2 ) {
//"Mode 2 - Readable and writable"
2019-01-12 13:18:29 -05:00
accessType = MemoryAccessType : : ReadWrite ;
2015-07-29 22:10:34 -04:00
} else {
//"Mode 3 - Read-only"
2019-01-12 13:18:29 -05:00
accessType = MemoryAccessType : : Read ;
2015-07-29 22:10:34 -04:00
}
2019-12-23 22:20:34 -05:00
if ( HasBattery ( ) ) {
SetCpuMemoryMapping ( 0x5C00 , 0x5FFF , PrgMemoryType : : SaveRam , _saveRamSize - MMC5 : : ExRamSize , accessType ) ;
} else {
SetCpuMemoryMapping ( 0x5C00 , 0x5FFF , PrgMemoryType : : WorkRam , _workRamSize - MMC5 : : ExRamSize , accessType ) ;
}
2015-07-29 22:10:34 -04:00
SetNametableMapping ( _nametableMapping ) ;
}
void SetFillModeTile ( uint8_t tile )
{
_fillModeTile = tile ;
2019-01-09 20:19:16 -05:00
memset ( GetNametable ( NtFillModeIndex ) , tile , 32 * 30 ) ; //32 tiles per row, 30 rows
2015-07-29 22:10:34 -04:00
}
void SetFillModeColor ( uint8_t color )
{
_fillModeColor = color ;
2019-04-07 08:13:13 -04:00
uint8_t attributeByte = color | color < < 2 | color < < 4 | color < < 6 ;
memset ( GetNametable ( NtFillModeIndex ) + 32 * 30 , attributeByte , 64 ) ; //Attribute table is 64 bytes
2015-07-29 22:10:34 -04:00
}
protected :
2016-12-17 23:14:47 -05:00
virtual uint16_t GetPRGPageSize ( ) override { return 0x2000 ; }
2019-01-12 13:18:29 -05:00
virtual uint16_t GetCHRPageSize ( ) override { return 0x400 ; }
2016-12-17 23:14:47 -05:00
virtual uint16_t RegisterStartAddress ( ) override { return 0x5000 ; }
virtual uint16_t RegisterEndAddress ( ) override { return 0x5206 ; }
virtual uint32_t GetSaveRamPageSize ( ) override { return 0x2000 ; }
2019-01-12 13:18:29 -05:00
virtual uint32_t GetWorkRamPageSize ( ) override { return 0x2000 ; }
2016-12-17 23:14:47 -05:00
virtual bool ForceSaveRamSize ( ) override { return true ; }
virtual bool ForceWorkRamSize ( ) override { return true ; }
2019-01-12 13:18:29 -05:00
virtual uint32_t GetSaveRamSize ( ) override
{
2019-12-23 22:20:34 -05:00
uint32_t size ;
2019-01-12 13:18:29 -05:00
if ( IsNes20 ( ) ) {
2019-12-23 22:20:34 -05:00
size = _romInfo . NesHeader . GetSaveRamSize ( ) ;
2019-01-12 13:18:29 -05:00
} else if ( _romInfo . IsInDatabase ) {
2019-12-23 22:20:34 -05:00
size = _romInfo . DatabaseInfo . SaveRamSize ;
2019-01-12 13:18:29 -05:00
} else {
//Emulate as if a single 64k block of work/save ram existed
2019-12-23 22:20:34 -05:00
size = _romInfo . HasBattery ? 0x10000 : 0 ;
2019-01-12 13:18:29 -05:00
}
2019-12-23 22:20:34 -05:00
if ( HasBattery ( ) ) {
//If there's a battery on the board, exram gets saved, too.
size + = MMC5 : : ExRamSize ;
}
return size ;
2019-01-12 13:18:29 -05:00
}
virtual uint32_t GetWorkRamSize ( ) override
{
2019-12-23 22:20:34 -05:00
uint32_t size ;
2019-01-12 13:18:29 -05:00
if ( IsNes20 ( ) ) {
2019-12-23 22:20:34 -05:00
size = _romInfo . NesHeader . GetWorkRamSize ( ) ;
2019-01-12 13:18:29 -05:00
} else if ( _romInfo . IsInDatabase ) {
2019-12-23 22:20:34 -05:00
size = _romInfo . DatabaseInfo . WorkRamSize ;
2019-01-12 13:18:29 -05:00
} else {
//Emulate as if a single 64k block of work/save ram existed (+ 1kb of ExRAM)
2019-12-23 22:20:34 -05:00
size = ( _romInfo . HasBattery ? 0 : 0x10000 ) ;
2019-01-12 13:18:29 -05:00
}
2019-12-23 22:20:34 -05:00
if ( ! HasBattery ( ) ) {
size + = MMC5 : : ExRamSize ;
}
return size ;
2019-01-12 13:18:29 -05:00
}
2016-12-17 23:14:47 -05:00
virtual bool AllowRegisterRead ( ) override { return true ; }
virtual void InitMapper ( ) override
2015-07-29 22:10:34 -04:00
{
2020-01-19 14:45:04 -05:00
AddRegisterRange ( 0xFFFA , 0xFFFB , MemoryOperation : : Read ) ;
2018-07-01 15:21:05 -04:00
_audio . reset ( new MMC5Audio ( _console ) ) ;
2019-01-12 13:18:29 -05:00
2020-01-19 14:45:04 -05:00
//Override the 2000-2007 registers to catch all writes to the PPU registers (but not their mirrors)
_mmc5MemoryHandler . reset ( new MMC5MemoryHandler ( _console . get ( ) ) ) ;
_ppuIdleCounter = 0 ;
_lastPpuReadAddr = 0 ;
_ntReadCounter = 0 ;
_prevChrA = false ;
2015-07-29 22:10:34 -04:00
_chrMode = 0 ;
_prgRamProtect1 = 0 ;
_prgRamProtect2 = 0 ;
_extendedRamMode = 0 ;
2019-01-12 13:18:29 -05:00
_nametableMapping = 0 ;
2015-07-29 22:10:34 -04:00
_fillModeColor = 0 ;
_fillModeTile = 0 ;
_verticalSplitScroll = 0 ;
_verticalSplitBank = 0 ;
2019-06-23 20:10:55 -04:00
_verticalSplitEnabled = false ;
_verticalSplitDelimiterTile = 0 ;
_verticalSplitRightSide = false ;
2015-07-29 22:10:34 -04:00
_multiplierValue1 = 0 ;
_multiplierValue2 = 0 ;
_chrUpperBits = 0 ;
memset ( _chrBanks , 0 , sizeof ( _chrBanks ) ) ;
_lastChrReg = 0 ;
_exAttrLastFetchCounter = 0 ;
_exAttributeLastNametableFetch = 0 ;
_exAttrSelectedChrBank = 0 ;
2019-06-23 20:10:55 -04:00
_irqPending = false ;
2015-07-29 22:10:34 -04:00
_irqCounterTarget = 0 ;
2020-01-19 14:45:04 -05:00
_scanlineCounter = 0 ;
2015-07-29 22:10:34 -04:00
_irqEnabled = false ;
_ppuInFrame = false ;
2020-01-19 14:45:04 -05:00
_needInFrame = false ;
2019-06-23 20:10:55 -04:00
2016-07-02 14:48:46 -04:00
_splitInSplitRegion = false ;
_splitVerticalScroll = 0 ;
_splitTile = 0 ;
_splitTileNumber = - 1 ;
2019-01-09 20:19:16 -05:00
memset ( GetNametable ( NtEmptyIndex ) , 0 , BaseMapper : : NametableSize ) ;
2015-07-29 22:10:34 -04:00
2019-01-12 13:18:29 -05:00
SetExtendedRamMode ( 0 ) ;
2015-07-29 22:10:34 -04:00
//"Additionally, Romance of the 3 Kingdoms 2 seems to expect it to be in 8k PRG mode ($5100 = $03)."
WriteRegister ( 0x5100 , 0x03 ) ;
//"Games seem to expect $5117 to be $FF on powerup (last PRG page swapped in)."
WriteRegister ( 0x5117 , 0xFF ) ;
2020-01-19 14:45:04 -05:00
UpdateChrBanks ( true ) ;
}
void Reset ( bool softReset ) override
{
_console - > GetMemoryManager ( ) - > RegisterWriteHandler ( _mmc5MemoryHandler . get ( ) , 0x2000 , 0x2007 ) ;
2015-07-29 22:10:34 -04:00
}
2016-12-17 23:14:47 -05:00
void StreamState ( bool saving ) override
2015-07-29 22:10:34 -04:00
{
BaseMapper : : StreamState ( saving ) ;
2016-01-24 21:54:05 -05:00
2020-01-19 14:45:04 -05:00
int16_t unusedPreviousScanline = 0 ;
bool unusedSpriteFetch = false ;
bool unusedLargeSprites = false ;
2016-06-02 23:56:11 -04:00
ArrayInfo < uint8_t > prgBanks = { _prgBanks , 5 } ;
ArrayInfo < uint16_t > chrBanks = { _chrBanks , 12 } ;
2018-07-01 15:21:05 -04:00
SnapshotInfo audio { _audio . get ( ) } ;
2016-06-02 20:20:26 -04:00
Stream ( _prgRamProtect1 , _prgRamProtect2 , _fillModeTile , _fillModeColor , _verticalSplitEnabled , _verticalSplitRightSide ,
_verticalSplitDelimiterTile , _verticalSplitScroll , _verticalSplitBank , _multiplierValue1 , _multiplierValue2 ,
_nametableMapping , _extendedRamMode , _exAttributeLastNametableFetch , _exAttrLastFetchCounter , _exAttrSelectedChrBank ,
2016-06-02 23:56:11 -04:00
_prgMode , prgBanks , _chrMode , _chrUpperBits , chrBanks , _lastChrReg ,
2020-01-19 14:45:04 -05:00
unusedSpriteFetch , unusedLargeSprites , _irqCounterTarget , _irqEnabled , unusedPreviousScanline , _scanlineCounter , _irqPending , _ppuInFrame , audio ,
_splitInSplitRegion , _splitVerticalScroll , _splitTile , _splitTileNumber , _needInFrame ) ;
2016-06-02 20:20:26 -04:00
2016-01-24 21:54:05 -05:00
if ( ! saving ) {
UpdatePrgBanks ( ) ;
2019-01-09 20:19:16 -05:00
SetNametableMapping ( _nametableMapping ) ;
2016-01-24 21:54:05 -05:00
}
2015-07-29 22:10:34 -04:00
}
2016-12-17 23:14:47 -05:00
virtual void WriteRAM ( uint16_t addr , uint8_t value ) override
2015-07-29 22:10:34 -04:00
{
2020-01-19 14:45:04 -05:00
if ( addr > = 0x5C00 & & addr < = 0x5FFF & & _extendedRamMode < = 1 & & ! _ppuInFrame ) {
//Expansion RAM ($5C00-$5FFF, read/write)
//Mode 0/1 - Not readable (returns open bus), can only be written while the PPU is rendering (otherwise, 0 is written)
value = 0 ;
2015-07-29 22:10:34 -04:00
}
BaseMapper : : WriteRAM ( addr , value ) ;
}
2020-01-19 14:45:04 -05:00
void DetectScanlineStart ( uint16_t addr )
2015-07-29 22:10:34 -04:00
{
2020-01-19 14:45:04 -05:00
if ( addr > = 0x2000 & & addr < = 0x2FFF ) {
if ( _lastPpuReadAddr = = addr ) {
//Count consecutive identical reads
_ntReadCounter + + ;
} else {
_ntReadCounter = 0 ;
}
2017-04-14 14:09:54 -04:00
2020-01-19 14:45:04 -05:00
if ( _ntReadCounter > = 2 ) {
if ( ! _ppuInFrame & & ! _needInFrame ) {
_needInFrame = true ;
_scanlineCounter = 0 ;
} else {
_scanlineCounter + + ;
if ( _irqCounterTarget = = _scanlineCounter ) {
_irqPending = true ;
if ( _irqEnabled ) {
_console - > GetCpu ( ) - > SetIrqSource ( IRQSource : : External ) ;
}
}
2016-07-02 14:48:46 -04:00
}
2020-01-19 14:45:04 -05:00
_splitTileNumber = 0 ;
2016-07-02 14:48:46 -04:00
}
2020-01-19 14:45:04 -05:00
} else {
_ntReadCounter = 0 ;
}
}
2016-07-02 14:48:46 -04:00
2020-01-19 14:45:04 -05:00
virtual uint8_t MapperReadVRAM ( uint16_t addr , MemoryOperationType memoryOperationType ) override
{
bool isNtFetch = addr > = 0x2000 & & addr < = 0x2FFF & & ( addr & 0x3FF ) < 0x3C0 ;
if ( isNtFetch ) {
//Nametable data, not an attribute fetch
_splitInSplitRegion = false ;
_splitTileNumber + + ;
if ( _ppuInFrame ) {
UpdateChrBanks ( false ) ;
} else if ( _needInFrame ) {
_needInFrame = false ;
_ppuInFrame = true ;
UpdateChrBanks ( false ) ;
2016-07-02 14:48:46 -04:00
}
2020-01-19 14:45:04 -05:00
}
DetectScanlineStart ( addr ) ;
2016-07-02 14:48:46 -04:00
2020-01-19 14:45:04 -05:00
_ppuIdleCounter = 3 ;
_lastPpuReadAddr = addr ;
if ( _extendedRamMode < = 1 & & _ppuInFrame ) {
if ( _verticalSplitEnabled ) {
uint16_t verticalSplitScroll = ( _verticalSplitScroll + _scanlineCounter ) % 240 ;
2016-07-02 14:48:46 -04:00
if ( addr > = 0x2000 ) {
2020-01-19 14:45:04 -05:00
if ( isNtFetch ) {
uint8_t tileNumber = ( _splitTileNumber + 2 ) % 42 ;
2020-01-22 20:04:51 -05:00
if ( tileNumber < = 32 & & ( ( _verticalSplitRightSide & & tileNumber > = _verticalSplitDelimiterTile ) | | ( ! _verticalSplitRightSide & & tileNumber < _verticalSplitDelimiterTile ) ) ) {
2020-01-19 14:45:04 -05:00
//Split region (for next 3 fetches, attribute + 2x tile data)
2016-07-02 14:48:46 -04:00
_splitInSplitRegion = true ;
2020-01-19 14:45:04 -05:00
_splitTile = ( ( verticalSplitScroll & 0xF8 ) < < 2 ) | tileNumber ;
2016-07-02 14:48:46 -04:00
return InternalReadRam ( 0x5C00 + _splitTile ) ;
} else {
2020-01-19 14:45:04 -05:00
//Outside of split region (or sprite data), result can get modified by ex ram mode code below
2016-07-02 14:48:46 -04:00
_splitInSplitRegion = false ;
}
} else if ( _splitInSplitRegion ) {
2020-01-19 14:45:04 -05:00
return InternalReadRam ( 0x5FC0 | ( ( _splitTile & 0x380 ) > > 4 ) | ( ( _splitTile & 0x1F ) > > 2 ) ) ;
2016-07-02 14:48:46 -04:00
}
} else if ( _splitInSplitRegion ) {
2020-01-19 14:45:04 -05:00
//CHR tile fetches for split region
return _chrRom [ ( _verticalSplitBank % ( GetCHRPageCount ( ) / 4 ) ) * 0x1000 + ( ( ( addr & ~ 0x07 ) | ( verticalSplitScroll & 0x07 ) ) & 0xFFF ) ] ;
2016-07-02 14:48:46 -04:00
}
}
2020-01-19 14:45:04 -05:00
if ( _extendedRamMode = = 1 & & ( _splitTileNumber < 32 | | _splitTileNumber > = 40 ) ) {
//"In Mode 1, nametable fetches are processed normally, and can come from CIRAM nametables, fill mode, or even Expansion RAM, but attribute fetches are replaced by data from Expansion RAM."
//"Each byte of Expansion RAM is used to enhance the tile at the corresponding address in every nametable"
//When fetching NT data, we set a flag and then alter the VRAM values read by the PPU on the following 3 cycles (palette, tile low/high byte)
if ( isNtFetch ) {
//Nametable fetches
_exAttributeLastNametableFetch = addr & 0x03FF ;
_exAttrLastFetchCounter = 3 ;
} else if ( _exAttrLastFetchCounter > 0 ) {
//Attribute fetches
_exAttrLastFetchCounter - - ;
switch ( _exAttrLastFetchCounter ) {
case 2 :
{
//PPU palette fetch
//Check work ram (expansion ram) to see which tile/palette to use
//Use InternalReadRam to bypass the fact that the ram is supposed to be write-only in mode 0/1
uint8_t value = InternalReadRam ( 0x5C00 + _exAttributeLastNametableFetch ) ;
//"The pattern fetches ignore the standard CHR banking bits, and instead use the top two bits of $5130 and the bottom 6 bits from Expansion RAM to choose a 4KB bank to select the tile from."
_exAttrSelectedChrBank = ( ( value & 0x3F ) | ( _chrUpperBits < < 6 ) ) % ( _chrRomSize / 0x1000 ) ;
//Return a byte containing the same palette 4 times - this allows the PPU to select the right palette no matter the shift value
uint8_t palette = ( value & 0xC0 ) > > 6 ;
return palette | palette < < 2 | palette < < 4 | palette < < 6 ;
}
2017-04-14 14:09:54 -04:00
2020-01-19 14:45:04 -05:00
case 1 :
case 0 :
//PPU tile data fetch (high byte & low byte)
return _chrRom [ _exAttrSelectedChrBank * 0x1000 + ( addr & 0xFFF ) ] ;
}
2015-07-29 22:10:34 -04:00
}
}
}
2020-01-19 14:45:04 -05:00
return InternalReadVRAM ( addr ) ;
2015-07-29 22:10:34 -04:00
}
2016-12-17 23:14:47 -05:00
void WriteRegister ( uint16_t addr , uint8_t value ) override
2015-07-29 22:10:34 -04:00
{
if ( addr > = 0x5113 & & addr < = 0x5117 ) {
SwitchPrgBank ( addr , value ) ;
} else if ( addr > = 0x5120 & & addr < = 0x512B ) {
SwitchChrBank ( addr , value ) ;
} else {
switch ( addr ) {
2016-06-12 11:28:45 -04:00
case 0x5000 : case 0x5001 : case 0x5002 : case 0x5003 : case 0x5004 : case 0x5005 : case 0x5006 : case 0x5007 : case 0x5010 : case 0x5011 : case 0x5015 :
2018-07-01 15:21:05 -04:00
_audio - > WriteRegister ( addr , value ) ;
2016-06-12 11:28:45 -04:00
break ;
2015-07-29 22:10:34 -04:00
case 0x5100 : _prgMode = value & 0x03 ; UpdatePrgBanks ( ) ; break ;
2020-01-19 14:45:04 -05:00
case 0x5101 : _chrMode = value & 0x03 ; UpdateChrBanks ( true ) ; break ;
2015-07-29 22:10:34 -04:00
case 0x5102 : _prgRamProtect1 = value & 0x03 ; UpdatePrgBanks ( ) ; break ;
case 0x5103 : _prgRamProtect2 = value & 0x03 ; UpdatePrgBanks ( ) ; break ;
case 0x5104 : SetExtendedRamMode ( value & 0x03 ) ; break ;
case 0x5105 : SetNametableMapping ( value ) ; break ;
case 0x5106 : SetFillModeTile ( value ) ; break ;
case 0x5107 : SetFillModeColor ( value & 0x03 ) ; break ;
case 0x5130 : _chrUpperBits = value & 0x03 ; break ;
case 0x5200 :
_verticalSplitEnabled = ( value & 0x80 ) = = 0x80 ;
_verticalSplitRightSide = ( value & 0x40 ) = = 0x40 ;
_verticalSplitDelimiterTile = ( value & 0x1F ) ;
break ;
case 0x5201 : _verticalSplitScroll = value ; break ;
case 0x5202 : _verticalSplitBank = value ; break ;
case 0x5203 : _irqCounterTarget = value ; break ;
case 0x5204 :
_irqEnabled = ( value & 0x80 ) = = 0x80 ;
if ( ! _irqEnabled ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > ClearIrqSource ( IRQSource : : External ) ;
2015-07-29 22:10:34 -04:00
} else if ( _irqEnabled & & _irqPending ) {
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > SetIrqSource ( IRQSource : : External ) ;
2015-07-29 22:10:34 -04:00
}
break ;
case 0x5205 : _multiplierValue1 = value ; break ;
case 0x5206 : _multiplierValue2 = value ; break ;
default :
break ;
}
}
}
2016-12-17 23:14:47 -05:00
uint8_t ReadRegister ( uint16_t addr ) override
2015-07-29 22:10:34 -04:00
{
switch ( addr ) {
2016-06-12 11:28:45 -04:00
case 0x5010 : case 0x5015 :
2018-07-01 15:21:05 -04:00
return _audio - > ReadRegister ( addr ) ;
2016-06-12 11:28:45 -04:00
2015-07-29 22:10:34 -04:00
case 0x5204 :
{
uint8_t value = ( _ppuInFrame ? 0x40 : 0x00 ) | ( _irqPending ? 0x80 : 0x00 ) ;
_irqPending = false ;
2018-07-01 15:21:05 -04:00
_console - > GetCpu ( ) - > ClearIrqSource ( IRQSource : : External ) ;
2015-07-29 22:10:34 -04:00
return value ;
}
case 0x5205 : return ( _multiplierValue1 * _multiplierValue2 ) & 0xFF ;
case 0x5206 : return ( _multiplierValue1 * _multiplierValue2 ) > > 8 ;
2020-01-19 14:45:04 -05:00
case 0xFFFA :
case 0xFFFB :
_ppuInFrame = false ;
UpdateChrBanks ( true ) ;
_lastPpuReadAddr = 0 ;
_scanlineCounter = 0 ;
_irqPending = false ;
_console - > GetCpu ( ) - > ClearIrqSource ( IRQSource : : External ) ;
return DebugReadRAM ( addr ) ;
2015-07-29 22:10:34 -04:00
}
2016-06-12 11:28:45 -04:00
2018-07-01 15:21:05 -04:00
return _console - > GetMemoryManager ( ) - > GetOpenBus ( ) ;
2015-07-29 22:10:34 -04:00
}
2018-01-07 13:15:25 -05:00
public :
bool IsExtendedAttributes ( )
{
return _extendedRamMode = = 1 ;
}
uint8_t GetExAttributeNtPalette ( uint16_t ntAddr )
{
ntAddr & = 0x3FF ;
uint8_t value = InternalReadRam ( 0x5C00 + ntAddr ) ;
return ( value & 0xC0 ) > > 6 ;
}
uint32_t GetExAttributeAbsoluteTileAddr ( uint16_t ntAddr , uint16_t chrAddr )
{
ntAddr & = 0x3FF ;
uint8_t value = InternalReadRam ( 0x5C00 + ntAddr ) ;
//"The pattern fetches ignore the standard CHR banking bits, and instead use the top two bits of $5130 and the bottom 6 bits from Expansion RAM to choose a 4KB bank to select the tile from."
uint16_t chrBank = ( ( value & 0x3F ) | ( _chrUpperBits < < 6 ) ) % ( _chrRomSize / 0x1000 ) ;
return chrBank * 0x1000 + ( chrAddr & 0xFFF ) ;
}
uint8_t GetExAttributeTileData ( uint16_t ntAddr , uint16_t chrAddr )
{
return _chrRom [ GetExAttributeAbsoluteTileAddr ( ntAddr , chrAddr ) ] ;
}
2015-07-29 22:10:34 -04:00
} ;