mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
1733 lines
40 KiB
C++
1733 lines
40 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Nestopia - NES/Famicom emulator written in C++
|
|
//
|
|
// Copyright (C) 2003-2008 Martin Freij
|
|
//
|
|
// This file is part of Nestopia.
|
|
//
|
|
// Nestopia is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Nestopia is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Nestopia; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <cstring>
|
|
#include "NstBoard.hpp"
|
|
#include "NstBoardMmc5.hpp"
|
|
|
|
namespace Nes
|
|
{
|
|
namespace Core
|
|
{
|
|
namespace Boards
|
|
{
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("s", on)
|
|
#endif
|
|
|
|
const byte Mmc5::Filler::squared[4] = {0x00,0x55,0xAA,0xFF};
|
|
|
|
Mmc5::Sound::Sound(Apu& a,bool connect)
|
|
: Channel(a)
|
|
{
|
|
Reset();
|
|
bool audible = UpdateSettings();
|
|
|
|
if (connect)
|
|
Connect( audible );
|
|
}
|
|
|
|
Mmc5::Banks::Wrk::Wrk(dword s)
|
|
{
|
|
enum {X = INVALID};
|
|
|
|
static const byte access[6][8] =
|
|
{
|
|
{X,X,X,X,X,X,X,X},
|
|
{0,0,0,0,X,X,X,X},
|
|
{0,0,0,0,1,1,1,1},
|
|
{0,1,2,3,X,X,X,X},
|
|
{0,1,2,3,4,4,4,4},
|
|
{0,1,2,3,4,5,6,7}
|
|
};
|
|
|
|
std::memcpy( banks, access[s==SIZE_16K ? 2 : s==SIZE_32K ? 3 : s==SIZE_40K ? 4 : s==SIZE_64K ? 5 : s ? 1 : 0], 8 );
|
|
}
|
|
|
|
Mmc5::Banks::Banks(uint wrkSize)
|
|
: wrk(wrkSize) {}
|
|
|
|
Mmc5::Mmc5(const Context& c)
|
|
:
|
|
Board (c),
|
|
banks (board.GetWram()),
|
|
sound (*c.apu)
|
|
{
|
|
nmt.Source(1).Set( Ram::RAM, true, true, sizeof(exRam.mem), exRam.mem );
|
|
}
|
|
|
|
void Mmc5::Flow::Reset()
|
|
{
|
|
cycles = 0;
|
|
phase = &Mmc5::VBlank;
|
|
scanline = 240;
|
|
}
|
|
|
|
void Mmc5::Irq::Reset()
|
|
{
|
|
state = 0;
|
|
count = 0U-2U;
|
|
target = 0;
|
|
}
|
|
|
|
void Mmc5::Regs::Reset()
|
|
{
|
|
prgMode = PRG_MODE_8K;
|
|
chrMode = CHR_MODE_8K;
|
|
exRamMode = EXRAM_MODE_PPU_NT;
|
|
mul[0] = 0;
|
|
mul[1] = 0;
|
|
}
|
|
|
|
void Mmc5::Banks::Reset()
|
|
{
|
|
nmt = 0;
|
|
|
|
for (uint i=0; i < 8; ++i)
|
|
chrA[i] = i;
|
|
|
|
for (uint i=0; i < 4; ++i)
|
|
chrB[i] = i;
|
|
|
|
chrHigh = 0;
|
|
lastChr = LAST_CHR_A;
|
|
fetchMode = FETCH_MODE_NONE;
|
|
security = 0;
|
|
|
|
for (uint i=0; i < 4; ++i)
|
|
prg[i] = 0xFF;
|
|
}
|
|
|
|
void Mmc5::Filler::Reset()
|
|
{
|
|
tile = 0;
|
|
attribute = 0;
|
|
}
|
|
|
|
void Mmc5::Spliter::Reset()
|
|
{
|
|
ctrl = 0;
|
|
tile = 0;
|
|
inside = false;
|
|
yStart = 0;
|
|
chrBank = 0;
|
|
x = 0;
|
|
y = 0;
|
|
}
|
|
|
|
void Mmc5::ExRam::Reset(bool hard)
|
|
{
|
|
tile = 0;
|
|
|
|
if (hard)
|
|
std::memset( mem, 0x00, sizeof(mem) );
|
|
}
|
|
|
|
void Mmc5::Sound::Square::Reset()
|
|
{
|
|
waveLength = 0;
|
|
active = false;
|
|
frequency = 0;
|
|
timer = 0;
|
|
step = 0;
|
|
duty = 0;
|
|
|
|
lengthCounter.Reset();
|
|
envelope.Reset();
|
|
}
|
|
|
|
void Mmc5::Sound::Pcm::Reset()
|
|
{
|
|
sample = 0;
|
|
enabled = false;
|
|
amp = 0;
|
|
}
|
|
|
|
void Mmc5::Sound::Reset()
|
|
{
|
|
atHalfClock = 0;
|
|
|
|
for (uint i=0; i < NUM_SQUARES; ++i)
|
|
square[i].Reset();
|
|
|
|
pcm.Reset();
|
|
dcBlocker.Reset();
|
|
}
|
|
|
|
void Mmc5::SubReset(const bool hard)
|
|
{
|
|
cpu.AddHook( Hook(this,&Mmc5::Hook_Cpu) );
|
|
ppu.SetHActiveHook( Hook(this,&Mmc5::Hook_HActive) );
|
|
ppu.SetHBlankHook( Hook(this,&Mmc5::Hook_HBlank) );
|
|
|
|
Map( 0x5000U, &Mmc5::Poke_5000 );
|
|
Map( 0x5002U, &Mmc5::Poke_5002 );
|
|
Map( 0x5003U, &Mmc5::Poke_5003 );
|
|
Map( 0x5004U, &Mmc5::Poke_5004 );
|
|
Map( 0x5006U, &Mmc5::Poke_5006 );
|
|
Map( 0x5007U, &Mmc5::Poke_5007 );
|
|
Map( 0x5010U, &Mmc5::Poke_5010 );
|
|
Map( 0x5011U, &Mmc5::Poke_5011 );
|
|
Map( 0x5015U, &Mmc5::Peek_5015, &Mmc5::Poke_5015 );
|
|
Map( 0x5100U, &Mmc5::Poke_5100 );
|
|
Map( 0x5101U, &Mmc5::Poke_5101 );
|
|
Map( 0x5102U, &Mmc5::Poke_5102 );
|
|
Map( 0x5103U, &Mmc5::Poke_5103 );
|
|
Map( 0x5104U, &Mmc5::Poke_5104 );
|
|
Map( 0x5105U, &Mmc5::Poke_5105 );
|
|
Map( 0x5106U, &Mmc5::Poke_5106 );
|
|
Map( 0x5107U, &Mmc5::Poke_5107 );
|
|
Map( 0x5113U, &Mmc5::Poke_5113 );
|
|
Map( 0x5114U, 0x5117U, &Mmc5::Poke_5114 );
|
|
Map( 0x5120U, 0x5127U, &Mmc5::Poke_5120 );
|
|
Map( 0x5128U, 0x512BU, &Mmc5::Poke_5128 );
|
|
Map( 0x5130U, &Mmc5::Poke_5130 );
|
|
Map( 0x5200U, &Mmc5::Poke_5200 );
|
|
Map( 0x5201U, &Mmc5::Poke_5201 );
|
|
Map( 0x5202U, &Mmc5::Poke_5202 );
|
|
Map( 0x5203U, &Mmc5::Poke_5203 );
|
|
Map( 0x5204U, &Mmc5::Peek_5204, &Mmc5::Poke_5204 );
|
|
Map( 0x5205U, &Mmc5::Peek_5205, &Mmc5::Poke_5205 );
|
|
Map( 0x5206U, &Mmc5::Peek_5206, &Mmc5::Poke_5206 );
|
|
Map( 0x5C00U, 0x5FFFU, &Mmc5::Peek_5C00, &Mmc5::Poke_5C00 );
|
|
Map( 0x6000U, 0x7FFFU, &Mmc5::Peek_6000, &Mmc5::Poke_6000 );
|
|
Map( 0x8000U, 0x9FFFU, &Mmc5::Peek_8000, &Mmc5::Poke_8000 );
|
|
Map( 0xA000U, 0xBFFFU, &Mmc5::Peek_A000, &Mmc5::Poke_A000 );
|
|
Map( 0xC000U, 0xDFFFU, &Mmc5::Peek_C000, &Mmc5::Poke_C000 );
|
|
|
|
p2001 = cpu.Map( 0x2001 );
|
|
|
|
for (uint i=0x2001; i < 0x4000; i += 0x8)
|
|
cpu.Map( i ).Set( this, &Mmc5::Peek_2001, &Mmc5::Poke_2001 );
|
|
|
|
for (uint i=0; i < 2; ++i)
|
|
ciRam[i] = nmt.Source().Mem(SIZE_1K * i);
|
|
|
|
exRam.Reset( hard );
|
|
flow.Reset();
|
|
banks.Reset();
|
|
regs.Reset();
|
|
irq.Reset();
|
|
filler.Reset();
|
|
spliter.Reset();
|
|
|
|
UpdatePrg();
|
|
UpdateChrA();
|
|
UpdateRenderMethod();
|
|
}
|
|
|
|
inline bool Mmc5::Sound::Square::CanOutput() const
|
|
{
|
|
return lengthCounter.GetCount() && waveLength >= MIN_FRQ;
|
|
}
|
|
|
|
void Mmc5::Sound::Square::UpdateSettings(const uint fixed)
|
|
{
|
|
active = CanOutput();
|
|
frequency = (waveLength + 1UL) * fixed * 2;
|
|
}
|
|
|
|
bool Mmc5::Sound::UpdateSettings()
|
|
{
|
|
uint volume = GetVolume(EXT_MMC5);
|
|
output = IsMuted() ? 0 : volume;
|
|
|
|
GetOscillatorClock( rate, fixed );
|
|
|
|
for (uint i=0; i < NUM_SQUARES; ++i)
|
|
square[i].UpdateSettings( fixed );
|
|
|
|
quarterClock = GetCpuClockBase() / (240UL * GetCpuClockDivider() * GetCpuClock()) * GetCpuClock();
|
|
|
|
dcBlocker.Reset();
|
|
|
|
return volume;
|
|
}
|
|
|
|
void Mmc5::SubSave(State::Saver& state) const
|
|
{
|
|
state.Begin( AsciiId<'M','M','5'>::V );
|
|
|
|
{
|
|
const byte data[32] =
|
|
{
|
|
regs.prgMode | (regs.chrMode << 2) | (regs.exRamMode << 4),
|
|
banks.prg[0],
|
|
banks.prg[1],
|
|
banks.prg[2],
|
|
banks.prg[3],
|
|
banks.security & (Banks::READABLE_6|Banks::WRITABLE_6|Regs::WRK_WRITABLE_A|Regs::WRK_WRITABLE_B),
|
|
banks.nmt,
|
|
banks.chrA[0] & 0xFFU,
|
|
banks.chrA[1] & 0xFFU,
|
|
banks.chrA[2] & 0xFFU,
|
|
banks.chrA[3] & 0xFFU,
|
|
banks.chrA[4] & 0xFFU,
|
|
banks.chrA[5] & 0xFFU,
|
|
banks.chrA[6] & 0xFFU,
|
|
banks.chrA[7] & 0xFFU,
|
|
banks.chrB[0] & 0xFFU,
|
|
banks.chrB[1] & 0xFFU,
|
|
banks.chrB[2] & 0xFFU,
|
|
banks.chrB[3] & 0xFFU,
|
|
uint(banks.chrA[0]) >> 8 | uint(banks.chrA[1]) >> 8 << 2 | uint(banks.chrA[2]) >> 8 << 4 | uint(banks.chrA[3]) >> 8 << 6,
|
|
uint(banks.chrA[4]) >> 8 | uint(banks.chrA[5]) >> 8 << 2 | uint(banks.chrA[6]) >> 8 << 4 | uint(banks.chrA[7]) >> 8 << 6,
|
|
uint(banks.chrB[0]) >> 8 | uint(banks.chrB[1]) >> 8 << 2 | uint(banks.chrB[2]) >> 8 << 4 | uint(banks.chrB[3]) >> 8 << 6,
|
|
(banks.chrHigh >> 6) | (banks.lastChr != Banks::LAST_CHR_A ? 0x80 : 0x00),
|
|
filler.tile,
|
|
(filler.attribute & 0x3) | (spliter.tile >> 2 & 0xF8),
|
|
exRam.tile,
|
|
spliter.ctrl,
|
|
spliter.yStart,
|
|
spliter.chrBank >> 12,
|
|
spliter.tile & 0x1F,
|
|
spliter.x,
|
|
spliter.y
|
|
};
|
|
|
|
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
|
|
}
|
|
|
|
{
|
|
const byte data[2] = { regs.mul[0], regs.mul[1] };
|
|
state.Begin( AsciiId<'M','U','L'>::V ).Write( data ).End();
|
|
}
|
|
|
|
{
|
|
const byte data[2] =
|
|
{
|
|
irq.state,
|
|
irq.target
|
|
};
|
|
|
|
state.Begin( AsciiId<'I','R','Q'>::V ).Write( data ).End();
|
|
}
|
|
|
|
state.Begin( AsciiId<'R','A','M'>::V ).Compress( exRam.mem ).End();
|
|
|
|
sound.SaveState( state, AsciiId<'S','N','D'>::V );
|
|
|
|
state.End();
|
|
}
|
|
|
|
void Mmc5::SubLoad(State::Loader& state,const dword baseChunk)
|
|
{
|
|
NST_VERIFY( baseChunk == (AsciiId<'M','M','5'>::V) );
|
|
|
|
if (baseChunk == AsciiId<'M','M','5'>::V)
|
|
{
|
|
while (const dword chunk = state.Begin())
|
|
{
|
|
switch (chunk)
|
|
{
|
|
case AsciiId<'R','E','G'>::V:
|
|
{
|
|
State::Loader::Data<32> data( state );
|
|
|
|
regs.prgMode = data[0] >> 0 & Regs::PRG_MODE;
|
|
regs.chrMode = data[0] >> 2 & Regs::CHR_MODE;
|
|
regs.exRamMode = data[0] >> 4 & Regs::EXRAM_MODE;
|
|
|
|
for (uint i=0; i < 4; ++i)
|
|
banks.prg[i] = data[1+i];
|
|
|
|
banks.security = data[5] & (Banks::READABLE_6|Banks::WRITABLE_6|Regs::WRK_WRITABLE_A|Regs::WRK_WRITABLE_B);
|
|
banks.nmt = data[6];
|
|
|
|
for (uint i=0; i < 8; ++i)
|
|
banks.chrA[i] = data[7+i] | (data[19+(i/4)] & Regs::CHR_HIGH) << 8;
|
|
|
|
for (uint i=0; i < 4; ++i)
|
|
banks.chrB[i] = data[15+i] | (data[21+(i/4)] & Regs::CHR_HIGH) << 8;
|
|
|
|
banks.chrHigh = (data[22] & Regs::CHR_HIGH) << 6;
|
|
banks.lastChr = (data[22] & 0x80) ? Banks::LAST_CHR_B : Banks::LAST_CHR_A;
|
|
|
|
filler.tile = data[23];
|
|
filler.attribute = Filler::squared[data[24] & 0x3];
|
|
|
|
exRam.tile = data[25];
|
|
|
|
spliter.ctrl = data[26];
|
|
spliter.yStart = NST_MIN(data[27],239);
|
|
spliter.chrBank = data[28] << 12;
|
|
spliter.tile = (data[29] & 0x1F) | (data[24] << 2 & 0x3E0);
|
|
spliter.x = data[30] & 0x1F;
|
|
spliter.y = NST_MIN(data[31],239);
|
|
|
|
UpdatePrg();
|
|
|
|
if (banks.lastChr == Banks::LAST_CHR_A)
|
|
UpdateChrA();
|
|
else
|
|
UpdateChrB();
|
|
|
|
UpdateRenderMethod();
|
|
break;
|
|
}
|
|
|
|
case AsciiId<'M','U','L'>::V:
|
|
{
|
|
State::Loader::Data<2> data( state );
|
|
|
|
regs.mul[0] = data[0];
|
|
regs.mul[1] = data[1];
|
|
break;
|
|
}
|
|
|
|
case AsciiId<'I','R','Q'>::V:
|
|
{
|
|
State::Loader::Data<2> data( state );
|
|
|
|
NST_VERIFY( !(data[0] & Irq::FRAME) );
|
|
|
|
irq.state = data[0] & (Irq::HIT|Irq::ENABLED);
|
|
irq.target = data[1];
|
|
break;
|
|
}
|
|
|
|
case AsciiId<'R','A','M'>::V:
|
|
|
|
state.Uncompress( exRam.mem );
|
|
break;
|
|
|
|
case AsciiId<'S','N','D'>::V:
|
|
|
|
sound.LoadState( state );
|
|
break;
|
|
}
|
|
|
|
state.End();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mmc5::Sound::SaveState(State::Saver& state,const dword baseChunk) const
|
|
{
|
|
state.Begin( baseChunk );
|
|
|
|
state.Begin( AsciiId<'C','L','K'>::V ).Write8( atHalfClock ).End();
|
|
|
|
square[0].SaveState( state, AsciiId<'S','Q','0'>::V );
|
|
square[1].SaveState( state, AsciiId<'S','Q','1'>::V );
|
|
pcm.SaveState( state, AsciiId<'P','C','M'>::V );
|
|
|
|
state.End();
|
|
}
|
|
|
|
void Mmc5::Sound::LoadState(State::Loader& state)
|
|
{
|
|
while (const dword chunk = state.Begin())
|
|
{
|
|
switch (chunk)
|
|
{
|
|
case AsciiId<'C','L','K'>::V:
|
|
|
|
atHalfClock = state.Read8() & 0x1;
|
|
break;
|
|
|
|
case AsciiId<'S','Q','0'>::V:
|
|
|
|
square[0].LoadState( state, fixed );
|
|
break;
|
|
|
|
case AsciiId<'S','Q','1'>::V:
|
|
|
|
square[1].LoadState( state, fixed );
|
|
break;
|
|
|
|
case AsciiId<'P','C','M'>::V:
|
|
|
|
pcm.LoadState( state );
|
|
break;
|
|
}
|
|
|
|
state.End();
|
|
}
|
|
}
|
|
|
|
void Mmc5::Sound::Square::SaveState(State::Saver& state,const dword chunk) const
|
|
{
|
|
state.Begin( chunk );
|
|
|
|
{
|
|
const byte data[3] =
|
|
{
|
|
waveLength & 0xFF,
|
|
waveLength >> 8,
|
|
duty
|
|
};
|
|
|
|
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
|
|
}
|
|
|
|
lengthCounter.SaveState( state, AsciiId<'L','E','N'>::V );
|
|
envelope.SaveState( state, AsciiId<'E','N','V'>::V );
|
|
|
|
state.End();
|
|
}
|
|
|
|
void Mmc5::Sound::Square::LoadState(State::Loader& state,const dword fixed)
|
|
{
|
|
while (const dword chunk = state.Begin())
|
|
{
|
|
switch (chunk)
|
|
{
|
|
case AsciiId<'R','E','G'>::V:
|
|
|
|
waveLength = state.Read16() & 0x7FF;
|
|
duty = state.Read8() & 0x3;
|
|
break;
|
|
|
|
case AsciiId<'L','E','N'>::V:
|
|
|
|
lengthCounter.LoadState( state );
|
|
break;
|
|
|
|
case AsciiId<'E','N','V'>::V:
|
|
|
|
envelope.LoadState( state );
|
|
break;
|
|
}
|
|
|
|
state.End();
|
|
}
|
|
|
|
step = 0;
|
|
timer = 0;
|
|
frequency = (waveLength + 1UL) * fixed * 2;
|
|
active = CanOutput();
|
|
}
|
|
|
|
void Mmc5::Sound::Pcm::SaveState(State::Saver& state,const dword chunk) const
|
|
{
|
|
state.Begin( chunk ).Write16( (enabled != 0) | dword(amp / VOLUME) << 8 ).End();
|
|
}
|
|
|
|
void Mmc5::Sound::Pcm::LoadState(State::Loader& state)
|
|
{
|
|
const uint data = state.Read16();
|
|
|
|
enabled = data & 0x1;
|
|
amp = (data >> 8) * VOLUME;
|
|
sample = enabled ? amp : 0;
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
inline ibool Mmc5::IsPpuSprite8x16() const
|
|
{
|
|
return ppu.GetCtrl(0) & Regs::PPU_CTRL0_SP8X16;
|
|
}
|
|
|
|
void Mmc5::VBlank()
|
|
{
|
|
NST_ASSERT( flow.cycles == 0 );
|
|
|
|
flow.cycles = ppu.GetHVIntClock();
|
|
|
|
if (flow.cycles <= cpu.GetCycles())
|
|
HDummy();
|
|
else
|
|
flow.phase = &Mmc5::HDummy;
|
|
}
|
|
|
|
void Mmc5::HDummy()
|
|
{
|
|
if (ppu.IsEnabled())
|
|
irq.count++;
|
|
|
|
flow.cycles += ppu.GetHSyncClock() - (ppu.IsShortFrame() ? ppu.GetClock() : 0);
|
|
|
|
if (flow.cycles <= cpu.GetCycles())
|
|
HActive0();
|
|
else
|
|
flow.phase = &Mmc5::HActive0;
|
|
}
|
|
|
|
void Mmc5::HActive0()
|
|
{
|
|
if (ppu.IsEnabled())
|
|
{
|
|
irq.count++;
|
|
irq.state = (irq.state & Irq::ENABLED) | Irq::FRAME;
|
|
cpu.ClearIRQ();
|
|
}
|
|
|
|
flow.cycles += ppu.GetHSyncClock();
|
|
flow.scanline = 0;
|
|
|
|
if (flow.cycles <= cpu.GetCycles())
|
|
HActiveX();
|
|
else
|
|
flow.phase = &Mmc5::HActiveX;
|
|
}
|
|
|
|
void Mmc5::HActiveX()
|
|
{
|
|
for (;;)
|
|
{
|
|
flow.scanline++;
|
|
|
|
if (ppu.IsEnabled())
|
|
{
|
|
if (++irq.count == irq.target && irq.target)
|
|
irq.state |= Irq::HIT;
|
|
|
|
if ((irq.state & Irq::SIGNAL_HIT) == Irq::SIGNAL_HIT)
|
|
cpu.DoIRQ( Cpu::IRQ_EXT, flow.cycles );
|
|
}
|
|
|
|
flow.cycles += ppu.GetHSyncClock();
|
|
|
|
if (flow.scanline < 240)
|
|
{
|
|
if (flow.cycles > cpu.GetCycles())
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
irq.count = 0U-2U;
|
|
flow.cycles = Cpu::CYCLE_MAX;
|
|
irq.state &= (Irq::ENABLED|Irq::HIT);
|
|
|
|
ppu.Update();
|
|
|
|
banks.fetchMode = Banks::FETCH_MODE_NONE;
|
|
spliter.inside = false;
|
|
|
|
if (banks.lastChr == Banks::LAST_CHR_A)
|
|
UpdateChrA();
|
|
else
|
|
UpdateChrB();
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void Mmc5::Update()
|
|
{
|
|
if (flow.cycles <= cpu.GetCycles())
|
|
(*this.*flow.phase)();
|
|
}
|
|
|
|
void Mmc5::Sync(Event event,Input::Controllers* controllers)
|
|
{
|
|
if (event == EVENT_END_FRAME)
|
|
{
|
|
Update();
|
|
|
|
flow.cycles = 0;
|
|
flow.phase = &Mmc5::VBlank;
|
|
}
|
|
|
|
Board::Sync( event, controllers );
|
|
}
|
|
|
|
NES_HOOK(Mmc5,Cpu)
|
|
{
|
|
Update();
|
|
}
|
|
|
|
NES_HOOK(Mmc5,HActive)
|
|
{
|
|
if (ppu.IsEnabled())
|
|
{
|
|
banks.fetchMode = Banks::FETCH_MODE_BG;
|
|
spliter.x = 0x1F;
|
|
|
|
if (ppu.GetPixelCycles() != ~0U)
|
|
{
|
|
if (spliter.y < 239)
|
|
spliter.y++;
|
|
else
|
|
spliter.y = 0;
|
|
}
|
|
else
|
|
{
|
|
spliter.y = spliter.yStart;
|
|
}
|
|
|
|
if (banks.lastChr != Banks::LAST_CHR_A || IsPpuSprite8x16())
|
|
UpdateChrB();
|
|
else
|
|
UpdateChrA();
|
|
}
|
|
}
|
|
|
|
NES_HOOK(Mmc5,HBlank)
|
|
{
|
|
banks.fetchMode = Banks::FETCH_MODE_SP;
|
|
spliter.inside = false;
|
|
|
|
if (ppu.IsEnabled())
|
|
{
|
|
if (banks.lastChr == Banks::LAST_CHR_A || IsPpuSprite8x16())
|
|
UpdateChrA();
|
|
else
|
|
UpdateChrB();
|
|
}
|
|
}
|
|
|
|
inline uint Mmc5::Banks::Wrk::operator [] (uint i) const
|
|
{
|
|
NST_ASSERT( i < 8 );
|
|
return banks[i];
|
|
}
|
|
|
|
template<uint ADDRESS>
|
|
void Mmc5::SwapPrg8Ex(uint bank)
|
|
{
|
|
enum
|
|
{
|
|
ROM = uint(Banks::READABLE_8) << (ADDRESS / SIZE_8K),
|
|
RAM = uint(Banks::WRITABLE_8) << (ADDRESS / SIZE_8K) | ROM
|
|
};
|
|
|
|
// GCC goes banana without the explicit cast
|
|
|
|
if (bank & Regs::PRG_ROM_SELECT)
|
|
{
|
|
banks.security = (banks.security & ~uint(RAM)) | ROM;
|
|
static_cast<Prg::SourceProxy>(prg.Source(0)).SwapBank<SIZE_8K,ADDRESS>( bank & Regs::PRG_ROM_BANK );
|
|
}
|
|
else if (Banks::Wrk::INVALID != (bank = banks.wrk[bank & Regs::PRG_RAM_BANK]))
|
|
{
|
|
banks.security |= RAM;
|
|
static_cast<Prg::SourceProxy>(prg.Source(1)).SwapBank<SIZE_8K,ADDRESS>( bank );
|
|
}
|
|
else
|
|
{
|
|
banks.security &= ~uint(RAM);
|
|
NST_DEBUG_MSG("MMC5 open bus PRG bankswitch!");
|
|
}
|
|
}
|
|
|
|
void Mmc5::UpdatePrg()
|
|
{
|
|
enum
|
|
{
|
|
ROM_8_A_C = Banks::READABLE_8|Banks::READABLE_A|Banks::READABLE_C,
|
|
ROM_C = Banks::READABLE_C,
|
|
RAM_8_A_C = Banks::WRITABLE_8|Banks::WRITABLE_A|Banks::WRITABLE_C|ROM_8_A_C,
|
|
RAM_C = Banks::WRITABLE_C|ROM_C
|
|
};
|
|
|
|
switch (regs.prgMode & Regs::PRG_MODE)
|
|
{
|
|
case Regs::PRG_MODE_32K:
|
|
|
|
banks.security = (banks.security & ~uint(RAM_8_A_C)) | ROM_8_A_C;
|
|
prg.SwapBank<SIZE_32K,0x0000>( banks.prg[3] >> 2 );
|
|
break;
|
|
|
|
case Regs::PRG_MODE_16K:
|
|
|
|
banks.security = (banks.security & ~uint(RAM_C)) | ROM_C;
|
|
SwapPrg8Ex<0x0000>( banks.prg[1] & 0xFEU );
|
|
SwapPrg8Ex<0x2000>( banks.prg[1] | 0x01U );
|
|
prg.SwapBank<SIZE_16K,0x4000>( banks.prg[3] >> 1 );
|
|
break;
|
|
|
|
case Regs::PRG_MODE_16K_8K:
|
|
|
|
SwapPrg8Ex<0x0000>( banks.prg[1] & 0xFEU );
|
|
SwapPrg8Ex<0x2000>( banks.prg[1] | 0x01U );
|
|
SwapPrg8Ex<0x4000>( banks.prg[2] );
|
|
prg.SwapBank<SIZE_8K,0x6000>( banks.prg[3] );
|
|
break;
|
|
|
|
case Regs::PRG_MODE_8K:
|
|
|
|
SwapPrg8Ex<0x0000>( banks.prg[0] );
|
|
SwapPrg8Ex<0x2000>( banks.prg[1] );
|
|
SwapPrg8Ex<0x4000>( banks.prg[2] );
|
|
prg.SwapBank<SIZE_8K,0x6000>( banks.prg[3] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Mmc5::UpdateChrA() const
|
|
{
|
|
switch (regs.chrMode)
|
|
{
|
|
case Regs::CHR_MODE_8K:
|
|
|
|
chr.SwapBank<SIZE_8K,0x0000>( banks.chrA[7] );
|
|
break;
|
|
|
|
case Regs::CHR_MODE_4K:
|
|
|
|
chr.SwapBanks<SIZE_4K,0x0000>( banks.chrA[3], banks.chrA[7] );
|
|
break;
|
|
|
|
case Regs::CHR_MODE_2K:
|
|
|
|
chr.SwapBanks<SIZE_2K,0x0000>( banks.chrA[1], banks.chrA[3], banks.chrA[5], banks.chrA[7] );
|
|
break;
|
|
|
|
case Regs::CHR_MODE_1K:
|
|
|
|
chr.SwapBanks<SIZE_1K,0x0000>( banks.chrA[0], banks.chrA[1], banks.chrA[2], banks.chrA[3], banks.chrA[4], banks.chrA[5], banks.chrA[6], banks.chrA[7] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Mmc5::UpdateChrB() const
|
|
{
|
|
switch (regs.chrMode)
|
|
{
|
|
case Regs::CHR_MODE_8K:
|
|
|
|
chr.SwapBank<SIZE_8K,0x0000>( banks.chrB[3] );
|
|
break;
|
|
|
|
case Regs::CHR_MODE_4K:
|
|
|
|
chr.SwapBanks<SIZE_4K,0x0000>( banks.chrB[3], banks.chrB[3] );
|
|
break;
|
|
|
|
case Regs::CHR_MODE_2K:
|
|
|
|
chr.SwapBanks<SIZE_2K,0x0000>( banks.chrB[1], banks.chrB[3], banks.chrB[1], banks.chrB[3] );
|
|
break;
|
|
|
|
case Regs::CHR_MODE_1K:
|
|
|
|
chr.SwapBanks<SIZE_1K,0x0000>( banks.chrB[0], banks.chrB[1], banks.chrB[2], banks.chrB[3], banks.chrB[0], banks.chrB[1], banks.chrB[2], banks.chrB[3] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Mmc5::ClockSpliter()
|
|
{
|
|
NST_ASSERT( spliter.ctrl & Spliter::CTRL_ENABLED );
|
|
|
|
if (banks.fetchMode == Banks::FETCH_MODE_BG)
|
|
{
|
|
spliter.x = (spliter.x + 1) & 0x1F;
|
|
|
|
if
|
|
(
|
|
(spliter.ctrl & Spliter::CTRL_RIGHT_SIDE) ? (spliter.x >= (spliter.ctrl & Spliter::CTRL_START)) :
|
|
(spliter.x < (spliter.ctrl & Spliter::CTRL_START))
|
|
)
|
|
{
|
|
spliter.tile = ((spliter.y & 0xF8) << 2) | spliter.x;
|
|
spliter.inside = true;
|
|
return true;
|
|
}
|
|
|
|
spliter.inside = false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template<>
|
|
NST_FORCE_INLINE uint Mmc5::FetchByte<Mmc5::NT_CIRAM_0>(uint address) const
|
|
{
|
|
return ciRam[0][address];
|
|
}
|
|
|
|
template<>
|
|
NST_FORCE_INLINE uint Mmc5::FetchByte<Mmc5::NT_CIRAM_1>(uint address) const
|
|
{
|
|
return ciRam[1][address];
|
|
}
|
|
|
|
template<>
|
|
NST_FORCE_INLINE uint Mmc5::FetchByte<Mmc5::NT_EXRAM>(uint address) const
|
|
{
|
|
return exRam.mem[address];
|
|
}
|
|
|
|
template<>
|
|
NST_FORCE_INLINE uint Mmc5::FetchByte<Mmc5::NT_FILL>(uint address) const
|
|
{
|
|
return (address & 0x3FF) < 0x3C0 ? filler.tile : filler.attribute;
|
|
}
|
|
|
|
template<>
|
|
NST_FORCE_INLINE uint Mmc5::FetchByte<Mmc5::NT_ZERO>(uint) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
template<Mmc5::FetchType NT>
|
|
NST_FORCE_INLINE uint Mmc5::FetchNt(uint address) const
|
|
{
|
|
return FetchByte<NT>( address );
|
|
}
|
|
|
|
template<Mmc5::FetchType NT>
|
|
NST_FORCE_INLINE uint Mmc5::FetchNtExt(uint address)
|
|
{
|
|
if ((address & 0x3FF) < 0x3C0)
|
|
{
|
|
exRam.tile = exRam.mem[address];
|
|
return FetchByte<NT>( address );
|
|
}
|
|
else
|
|
{
|
|
return Filler::squared[exRam.tile >> 6];
|
|
}
|
|
}
|
|
|
|
template<Mmc5::FetchType NT>
|
|
NST_FORCE_INLINE uint Mmc5::FetchNtSplit(uint address)
|
|
{
|
|
if ((address & 0x3FF) < 0x3C0)
|
|
{
|
|
if (ClockSpliter())
|
|
return exRam.mem[spliter.tile];
|
|
else
|
|
return FetchByte<NT>( address );
|
|
}
|
|
else
|
|
{
|
|
if (spliter.inside)
|
|
return GetSpliterAttribute();
|
|
else
|
|
return FetchByte<NT>( address );
|
|
}
|
|
}
|
|
|
|
template<Mmc5::FetchType NT>
|
|
NST_FORCE_INLINE uint Mmc5::FetchNtExtSplit(uint address)
|
|
{
|
|
if ((address & 0x3FF) < 0x3C0)
|
|
{
|
|
if (ClockSpliter())
|
|
{
|
|
return exRam.mem[spliter.tile];
|
|
}
|
|
else
|
|
{
|
|
exRam.tile = exRam.mem[address];
|
|
return FetchByte<NT>( address );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (spliter.inside)
|
|
return GetSpliterAttribute();
|
|
else
|
|
return Filler::squared[exRam.tile >> 6];
|
|
}
|
|
}
|
|
|
|
uint Mmc5::GetSpliterAttribute() const
|
|
{
|
|
return Filler::squared[(exRam.mem[0x3C0 | (spliter.tile >> 4 & 0x38) | (spliter.tile >> 2 & 0x7)] >> ((spliter.tile >> 4 & 0x4) | (spliter.tile & 0x2))) & 0x3];
|
|
}
|
|
|
|
NES_ACCESSOR( Mmc5, Nt_CiRam_0 ) { return FetchNt < NT_CIRAM_0 >( address ); }
|
|
NES_ACCESSOR( Mmc5, Nt_CiRam_1 ) { return FetchNt < NT_CIRAM_1 >( address ); }
|
|
NES_ACCESSOR( Mmc5, Nt_ExRam ) { return FetchNt < NT_EXRAM >( address ); }
|
|
NES_ACCESSOR( Mmc5, Nt_Fill ) { return FetchNt < NT_FILL >( address ); }
|
|
NES_ACCESSOR( Mmc5, Nt_Zero ) { return FetchNt < NT_ZERO >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExt_CiRam_0 ) { return FetchNtExt < NT_CIRAM_0 >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExt_CiRam_1 ) { return FetchNtExt < NT_CIRAM_1 >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExt_ExRam ) { return FetchNtExt < NT_EXRAM >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExt_Fill ) { return FetchNtExt < NT_FILL >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtSplit_CiRam_0 ) { return FetchNtSplit < NT_CIRAM_0 >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtSplit_CiRam_1 ) { return FetchNtSplit < NT_CIRAM_1 >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtSplit_ExRam ) { return FetchNtSplit < NT_EXRAM >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtSplit_Fill ) { return FetchNtSplit < NT_FILL >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExtSplit_CiRam_0 ) { return FetchNtExtSplit < NT_CIRAM_0 >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExtSplit_CiRam_1 ) { return FetchNtExtSplit < NT_CIRAM_1 >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExtSplit_ExRam ) { return FetchNtExtSplit < NT_EXRAM >( address ); }
|
|
NES_ACCESSOR( Mmc5, NtExtSplit_Fill ) { return FetchNtExtSplit < NT_FILL >( address ); }
|
|
|
|
NES_ACCESSOR(Mmc5,CRom)
|
|
{
|
|
return chr.Peek( address );
|
|
}
|
|
|
|
uint Mmc5::GetSpliterPattern(uint address) const
|
|
{
|
|
return *chr.Source().Mem( spliter.chrBank + (address & 0xFFF) );
|
|
}
|
|
|
|
uint Mmc5::GetExtPattern(uint address) const
|
|
{
|
|
return *chr.Source().Mem( (((exRam.tile & Regs::EXRAM_EXT_CHR_BANK) + banks.chrHigh) << 12) + (address & 0xFFF) );
|
|
}
|
|
|
|
NES_ACCESSOR(Mmc5,CRomExt)
|
|
{
|
|
if (banks.fetchMode == Banks::FETCH_MODE_BG)
|
|
return GetExtPattern( address );
|
|
else
|
|
return chr.Peek( address );
|
|
}
|
|
|
|
NES_ACCESSOR(Mmc5,CRomSplit)
|
|
{
|
|
if (spliter.inside)
|
|
return GetSpliterPattern( address );
|
|
else
|
|
return chr.Peek( address );
|
|
}
|
|
|
|
NES_ACCESSOR(Mmc5,CRomExtSplit)
|
|
{
|
|
if (spliter.inside)
|
|
{
|
|
return GetSpliterPattern( address );
|
|
}
|
|
else if (banks.fetchMode == Banks::FETCH_MODE_BG)
|
|
{
|
|
return GetExtPattern( address );
|
|
}
|
|
else
|
|
{
|
|
return chr.Peek( address );
|
|
}
|
|
}
|
|
|
|
void Mmc5::UpdateRenderMethod()
|
|
{
|
|
ppu.Update();
|
|
|
|
const uint method = regs.exRamMode | (spliter.ctrl >> 5 & 0x4);
|
|
|
|
{
|
|
static const Io::Accessor::Type<Mmc5>::Function chrMethods[8] =
|
|
{
|
|
&Mmc5::Access_CRom, // PPU NT
|
|
&Mmc5::Access_CRomExt, // PPU EXT
|
|
&Mmc5::Access_CRom, // CPU EXRAM
|
|
&Mmc5::Access_CRom, // CPU EXROM
|
|
&Mmc5::Access_CRomSplit, // PPU NT + SPLIT
|
|
&Mmc5::Access_CRomExtSplit, // PPU EXT + SPLIT
|
|
&Mmc5::Access_CRom, // CPU EXRAM + denied SPLIT
|
|
&Mmc5::Access_CRom // CPU EXROM + denied SPLIT
|
|
};
|
|
|
|
chr.SetAccessor( this, chrMethods[method] );
|
|
}
|
|
|
|
uint bank = banks.nmt;
|
|
|
|
{
|
|
static const Io::Accessor::Type<Mmc5>::Function nmtMethods[8][4] =
|
|
{
|
|
{ // PPU NT
|
|
&Mmc5::Access_Nt_CiRam_0,
|
|
&Mmc5::Access_Nt_CiRam_1,
|
|
&Mmc5::Access_Nt_ExRam,
|
|
&Mmc5::Access_Nt_Fill
|
|
},
|
|
{ // PPU EXT
|
|
&Mmc5::Access_NtExt_CiRam_0,
|
|
&Mmc5::Access_NtExt_CiRam_1,
|
|
&Mmc5::Access_NtExt_ExRam,
|
|
&Mmc5::Access_NtExt_Fill
|
|
},
|
|
{ // CPU EXRAM
|
|
&Mmc5::Access_Nt_CiRam_0,
|
|
&Mmc5::Access_Nt_CiRam_1,
|
|
&Mmc5::Access_Nt_Zero,
|
|
&Mmc5::Access_Nt_Fill
|
|
},
|
|
{ // CPU EXROM
|
|
&Mmc5::Access_Nt_CiRam_0,
|
|
&Mmc5::Access_Nt_CiRam_1,
|
|
&Mmc5::Access_Nt_Zero,
|
|
&Mmc5::Access_Nt_Fill
|
|
},
|
|
{ // PPU NT + SPLIT
|
|
&Mmc5::Access_NtSplit_CiRam_0,
|
|
&Mmc5::Access_NtSplit_CiRam_1,
|
|
&Mmc5::Access_NtSplit_ExRam,
|
|
&Mmc5::Access_NtSplit_Fill
|
|
},
|
|
{ // PPU EXT + SPLIT
|
|
&Mmc5::Access_NtExtSplit_CiRam_0,
|
|
&Mmc5::Access_NtExtSplit_CiRam_1,
|
|
&Mmc5::Access_NtExtSplit_ExRam,
|
|
&Mmc5::Access_NtExtSplit_Fill
|
|
},
|
|
{ // CPU EXRAM + denied SPLIT
|
|
&Mmc5::Access_Nt_CiRam_0,
|
|
&Mmc5::Access_Nt_CiRam_1,
|
|
&Mmc5::Access_Nt_Zero,
|
|
&Mmc5::Access_Nt_Fill
|
|
},
|
|
{ // CPU EXROM + denied SPLIT
|
|
&Mmc5::Access_Nt_CiRam_0,
|
|
&Mmc5::Access_Nt_CiRam_1,
|
|
&Mmc5::Access_Nt_Zero,
|
|
&Mmc5::Access_Nt_Fill
|
|
}
|
|
};
|
|
|
|
nmt.SetAccessors
|
|
(
|
|
this,
|
|
nmtMethods[method][bank >> 0 & Regs::NMT_MODE],
|
|
nmtMethods[method][bank >> 2 & Regs::NMT_MODE],
|
|
nmtMethods[method][bank >> 4 & Regs::NMT_MODE],
|
|
nmtMethods[method][bank >> 6 & Regs::NMT_MODE]
|
|
);
|
|
}
|
|
|
|
for (uint address=0; address < SIZE_4K; address += SIZE_1K, bank >>= 2)
|
|
{
|
|
static const byte securities[4][4][2] =
|
|
{
|
|
{ {0,0}, {0,1}, {1,0}, {0,0} },
|
|
{ {0,0}, {0,1}, {1,0}, {0,0} },
|
|
{ {0,0}, {0,1}, {0,0}, {0,0} },
|
|
{ {0,0}, {0,1}, {0,0}, {0,0} }
|
|
};
|
|
|
|
nmt.Source( securities[regs.exRamMode][bank & Regs::NMT_MODE][0] ).SwapBank<SIZE_1K>
|
|
(
|
|
address,
|
|
securities[regs.exRamMode][bank & Regs::NMT_MODE][1]
|
|
);
|
|
}
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,2001)
|
|
{
|
|
Update();
|
|
|
|
if (!(data & Regs::PPU_CTRL1_ENABLED))
|
|
{
|
|
irq.count = 0U-2U;
|
|
irq.state &= (Irq::HIT|Irq::ENABLED);
|
|
banks.fetchMode = Banks::FETCH_MODE_NONE;
|
|
spliter.inside = false;
|
|
}
|
|
|
|
p2001.Poke( address, data );
|
|
}
|
|
|
|
NES_PEEK_A(Mmc5,2001)
|
|
{
|
|
return p2001.Peek( address );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5000)
|
|
{
|
|
sound.WriteSquareReg0( 0, data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5002)
|
|
{
|
|
sound.WriteSquareReg1( 0, data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5003)
|
|
{
|
|
sound.WriteSquareReg2( 0, data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5004)
|
|
{
|
|
sound.WriteSquareReg0( 1, data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5006)
|
|
{
|
|
sound.WriteSquareReg1( 1, data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5007)
|
|
{
|
|
sound.WriteSquareReg2( 1, data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5010)
|
|
{
|
|
sound.WritePcmReg0( data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5011)
|
|
{
|
|
sound.WritePcmReg1( data );
|
|
}
|
|
|
|
NES_PEEK(Mmc5,5015)
|
|
{
|
|
return sound.ReadCtrl();
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5015)
|
|
{
|
|
sound.WriteCtrl( data );
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5100)
|
|
{
|
|
data &= Regs::PRG_MODE;
|
|
|
|
if (regs.prgMode != data)
|
|
{
|
|
regs.prgMode = data;
|
|
UpdatePrg();
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5101)
|
|
{
|
|
data &= Regs::CHR_MODE;
|
|
|
|
if (regs.chrMode != data)
|
|
{
|
|
ppu.Update();
|
|
|
|
regs.chrMode = data;
|
|
|
|
if (!IsPpuSprite8x16() || !ppu.IsEnabled() || ppu.GetScanline() == Ppu::SCANLINE_VBLANK)
|
|
{
|
|
if (banks.lastChr == Banks::LAST_CHR_A)
|
|
UpdateChrA();
|
|
else
|
|
UpdateChrB();
|
|
}
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5102)
|
|
{
|
|
if (data == Regs::WRK_WRITABLE_A)
|
|
banks.security |= Regs::WRK_WRITABLE_A;
|
|
else
|
|
banks.security &= ~uint(Regs::WRK_WRITABLE_A);
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5103)
|
|
{
|
|
if (data == Regs::WRK_WRITABLE_B)
|
|
banks.security |= Regs::WRK_WRITABLE_B;
|
|
else
|
|
banks.security &= ~uint(Regs::WRK_WRITABLE_B);
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5104)
|
|
{
|
|
data &= Regs::EXRAM_MODE;
|
|
|
|
if (regs.exRamMode != data)
|
|
{
|
|
regs.exRamMode = data;
|
|
UpdateRenderMethod();
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5105)
|
|
{
|
|
if (banks.nmt != data)
|
|
{
|
|
banks.nmt = data;
|
|
UpdateRenderMethod();
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5106)
|
|
{
|
|
if (banks.nmt & (banks.nmt << 1))
|
|
ppu.Update();
|
|
|
|
filler.tile = data;
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5107)
|
|
{
|
|
if (banks.nmt & (banks.nmt << 1))
|
|
ppu.Update();
|
|
|
|
filler.attribute = Filler::squared[data & 0x3];
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5113)
|
|
{
|
|
data = banks.wrk[data & Regs::PRG_RAM_BANK];
|
|
|
|
if (data != Banks::Wrk::INVALID)
|
|
{
|
|
banks.security |= Banks::READABLE_6|Banks::WRITABLE_6;
|
|
wrk.SwapBank<SIZE_8K,0x0000>( data );
|
|
}
|
|
else
|
|
{
|
|
banks.security &= ~uint(Banks::READABLE_6|Banks::WRITABLE_6);
|
|
}
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,5114)
|
|
{
|
|
if (banks.prg[address - 0x5114] != data)
|
|
{
|
|
banks.prg[address - 0x5114] = data;
|
|
UpdatePrg();
|
|
}
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,5120)
|
|
{
|
|
data |= banks.chrHigh << 2;
|
|
address &= 0x7;
|
|
|
|
if (banks.lastChr != Banks::LAST_CHR_A || banks.chrA[address] != data)
|
|
{
|
|
ppu.Update();
|
|
|
|
banks.chrA[address] = data;
|
|
banks.lastChr = Banks::LAST_CHR_A;
|
|
|
|
if (!IsPpuSprite8x16() || !ppu.IsEnabled() || ppu.GetScanline() == Ppu::SCANLINE_VBLANK)
|
|
UpdateChrA();
|
|
}
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,5128)
|
|
{
|
|
data |= banks.chrHigh << 2;
|
|
address &= 0x3;
|
|
|
|
if (banks.lastChr == Banks::LAST_CHR_A || banks.chrB[address] != data)
|
|
{
|
|
ppu.Update();
|
|
|
|
banks.chrB[address] = data;
|
|
banks.lastChr = Banks::LAST_CHR_B;
|
|
|
|
if (!IsPpuSprite8x16() || !ppu.IsEnabled() || ppu.GetScanline() == Ppu::SCANLINE_VBLANK)
|
|
UpdateChrB();
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5130)
|
|
{
|
|
data = (data & Regs::CHR_HIGH) << 6;
|
|
|
|
if (banks.chrHigh != data)
|
|
{
|
|
ppu.Update();
|
|
banks.chrHigh = data;
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5200)
|
|
{
|
|
if (spliter.ctrl != data)
|
|
{
|
|
ppu.Update();
|
|
spliter.ctrl = data;
|
|
UpdateRenderMethod();
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5201)
|
|
{
|
|
if (data >= 240)
|
|
data -= 16;
|
|
|
|
if (spliter.yStart != data)
|
|
{
|
|
ppu.Update();
|
|
spliter.yStart = data;
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5202)
|
|
{
|
|
const dword chrBank = dword(data) << 12;
|
|
|
|
if (spliter.chrBank != chrBank)
|
|
{
|
|
ppu.Update();
|
|
spliter.chrBank = chrBank;
|
|
}
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5203)
|
|
{
|
|
Update();
|
|
irq.target = data;
|
|
}
|
|
|
|
NES_PEEK(Mmc5,5204)
|
|
{
|
|
Update();
|
|
|
|
const uint status = irq.state & (Irq::FRAME|Irq::HIT);
|
|
irq.state &= (Irq::FRAME|Irq::ENABLED);
|
|
|
|
cpu.ClearIRQ();
|
|
|
|
return status;
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5204)
|
|
{
|
|
Update();
|
|
|
|
if (data & 0x80)
|
|
{
|
|
irq.state |= Irq::ENABLED;
|
|
|
|
if (irq.state & Irq::HIT)
|
|
cpu.DoIRQ();
|
|
}
|
|
else
|
|
{
|
|
irq.state &= (Irq::HIT|Irq::FRAME);
|
|
cpu.ClearIRQ();
|
|
}
|
|
}
|
|
|
|
NES_PEEK(Mmc5,5205)
|
|
{
|
|
return (regs.mul[0] * regs.mul[1]) >> 0 & 0xFF;
|
|
}
|
|
|
|
NES_PEEK(Mmc5,5206)
|
|
{
|
|
return (regs.mul[0] * regs.mul[1]) >> 8 & 0xFF;
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5205)
|
|
{
|
|
regs.mul[0] = data;
|
|
}
|
|
|
|
NES_POKE_D(Mmc5,5206)
|
|
{
|
|
regs.mul[1] = data;
|
|
}
|
|
|
|
NES_PEEK_A(Mmc5,5C00)
|
|
{
|
|
if (regs.exRamMode & Regs::EXRAM_MODE_CPU_RAM)
|
|
return exRam.mem[address - 0x5C00];
|
|
else
|
|
return address >> 8;
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,5C00)
|
|
{
|
|
switch (regs.exRamMode)
|
|
{
|
|
default:
|
|
|
|
ppu.Update();
|
|
Update();
|
|
|
|
NST_VERIFY( irq.state & Irq::FRAME );
|
|
|
|
if (!(irq.state & Irq::FRAME))
|
|
data = 0;
|
|
|
|
case Regs::EXRAM_MODE_CPU_RAM:
|
|
|
|
exRam.mem[address - 0x5C00] = data;
|
|
|
|
case Regs::EXRAM_MODE_CPU_ROM:
|
|
break;
|
|
}
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,6000)
|
|
{
|
|
NST_VERIFY( (banks.security & Banks::CAN_WRITE_6) == Banks::CAN_WRITE_6 );
|
|
|
|
if ((banks.security & Banks::CAN_WRITE_6) == Banks::CAN_WRITE_6)
|
|
wrk[0][address - 0x6000] = data;
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,8000)
|
|
{
|
|
NST_VERIFY( (banks.security & Banks::CAN_WRITE_8) == Banks::CAN_WRITE_8 );
|
|
|
|
if ((banks.security & Banks::CAN_WRITE_8) == Banks::CAN_WRITE_8)
|
|
prg[0][address - 0x8000] = data;
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,A000)
|
|
{
|
|
NST_VERIFY( (banks.security & Banks::CAN_WRITE_A) == Banks::CAN_WRITE_A );
|
|
|
|
if ((banks.security & Banks::CAN_WRITE_A) == Banks::CAN_WRITE_A)
|
|
prg[1][address - 0xA000] = data;
|
|
}
|
|
|
|
NES_POKE_AD(Mmc5,C000)
|
|
{
|
|
NST_VERIFY( (banks.security & Banks::CAN_WRITE_C) == Banks::CAN_WRITE_C );
|
|
|
|
if ((banks.security & Banks::CAN_WRITE_C) == Banks::CAN_WRITE_C)
|
|
prg[2][address - 0xC000] = data;
|
|
}
|
|
|
|
NES_PEEK_A(Mmc5,6000)
|
|
{
|
|
NST_VERIFY( banks.security & Banks::READABLE_6 );
|
|
return (banks.security & Banks::READABLE_6) ? wrk[0][address - 0x6000] : (address >> 8);
|
|
}
|
|
|
|
NES_PEEK_A(Mmc5,8000)
|
|
{
|
|
NST_VERIFY( banks.security & Banks::READABLE_8 );
|
|
return (banks.security & Banks::READABLE_8) ? prg[0][address - 0x8000] : (address >> 8);
|
|
}
|
|
|
|
NES_PEEK_A(Mmc5,A000)
|
|
{
|
|
NST_VERIFY( banks.security & Banks::READABLE_A );
|
|
return (banks.security & Banks::READABLE_A) ? prg[1][address - 0xA000] : (address >> 8);
|
|
}
|
|
|
|
NES_PEEK_A(Mmc5,C000)
|
|
{
|
|
NST_VERIFY( banks.security & Banks::READABLE_C );
|
|
return (banks.security & Banks::READABLE_C) ? prg[2][address - 0xC000] : (address >> 8);
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Square::Disable(const bool disable)
|
|
{
|
|
if (disable)
|
|
{
|
|
timer = 0;
|
|
active = 0;
|
|
}
|
|
|
|
lengthCounter.Disable( disable );
|
|
}
|
|
|
|
void Mmc5::Sound::WriteCtrl(uint data)
|
|
{
|
|
Update();
|
|
|
|
data = ~data;
|
|
|
|
for (uint i=0; i < NUM_SQUARES; ++i)
|
|
square[i].Disable( data >> i & 0x1 );
|
|
}
|
|
|
|
inline uint Mmc5::Sound::Square::GetLengthCounter() const
|
|
{
|
|
return lengthCounter.GetCount();
|
|
}
|
|
|
|
uint Mmc5::Sound::ReadCtrl() const
|
|
{
|
|
Update();
|
|
|
|
uint data = 0;
|
|
|
|
for (uint i=0; i < NUM_SQUARES; ++i)
|
|
{
|
|
if (square[i].GetLengthCounter())
|
|
data |= 0x1U << i;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Square::WriteReg0(const uint data)
|
|
{
|
|
envelope.Write( data );
|
|
duty = data >> DUTY_SHIFT;
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Square::WriteReg1(const uint data,const uint fixed)
|
|
{
|
|
waveLength &= uint(REG2_WAVELENGTH_HIGH) << 8;
|
|
waveLength |= data;
|
|
frequency = (waveLength + 1UL) * fixed * 2;
|
|
|
|
active = CanOutput();
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Square::WriteReg2(const uint data,const uint fixed)
|
|
{
|
|
step = 0;
|
|
envelope.ResetClock();
|
|
|
|
lengthCounter.Write( data );
|
|
|
|
waveLength &= REG1_WAVELENGTH_LOW;
|
|
waveLength |= (data & REG2_WAVELENGTH_HIGH) << 8;
|
|
frequency = (waveLength + 1UL) * fixed * 2;
|
|
|
|
active = CanOutput();
|
|
}
|
|
|
|
void Mmc5::Sound::WriteSquareReg0(uint index,uint data)
|
|
{
|
|
Update();
|
|
NST_ASSERT( index < NUM_SQUARES );
|
|
square[index].WriteReg0( data );
|
|
}
|
|
|
|
void Mmc5::Sound::WriteSquareReg1(uint index,uint data)
|
|
{
|
|
Update();
|
|
NST_ASSERT( index < NUM_SQUARES );
|
|
square[index].WriteReg1( data, fixed );
|
|
}
|
|
|
|
void Mmc5::Sound::WriteSquareReg2(uint index,uint data)
|
|
{
|
|
Update();
|
|
NST_ASSERT( index < NUM_SQUARES );
|
|
square[index].WriteReg2( data, fixed );
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Pcm::WriteReg0(const uint data)
|
|
{
|
|
enabled = ~data & PCM_DISABLE;
|
|
sample = enabled ? amp : 0;
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Pcm::WriteReg1(const uint data)
|
|
{
|
|
amp = data * VOLUME;
|
|
sample = enabled ? amp : 0;
|
|
}
|
|
|
|
void Mmc5::Sound::WritePcmReg0(uint data)
|
|
{
|
|
Update();
|
|
pcm.WriteReg0( data );
|
|
}
|
|
|
|
void Mmc5::Sound::WritePcmReg1(uint data)
|
|
{
|
|
Update();
|
|
pcm.WriteReg1( data );
|
|
}
|
|
|
|
NST_SINGLE_CALL dword Mmc5::Sound::Square::GetSample(const Cycle rate)
|
|
{
|
|
NST_VERIFY( bool(active) == CanOutput() && timer >= 0 );
|
|
|
|
if (active)
|
|
{
|
|
dword sum = timer;
|
|
timer -= idword(rate);
|
|
|
|
static const byte duties[4][8] =
|
|
{
|
|
{0x1F,0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F},
|
|
{0x1F,0x00,0x00,0x1F,0x1F,0x1F,0x1F,0x1F},
|
|
{0x1F,0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F},
|
|
{0x00,0x1F,0x1F,0x00,0x00,0x00,0x00,0x00}
|
|
};
|
|
|
|
if (timer >= 0)
|
|
{
|
|
return dword(envelope.Volume()) >> duties[duty][step];
|
|
}
|
|
else
|
|
{
|
|
sum >>= duties[duty][step];
|
|
|
|
do
|
|
{
|
|
sum += NST_MIN(dword(-timer),frequency) >> duties[duty][step = (step + 1) & 0x7];
|
|
timer += idword(frequency);
|
|
}
|
|
while (timer < 0);
|
|
|
|
return (sum * envelope.Volume() + rate/2) / rate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
inline Mmc5::Sound::Sample Mmc5::Sound::Pcm::GetSample() const
|
|
{
|
|
return sample;
|
|
}
|
|
|
|
Mmc5::Sound::Sample Mmc5::Sound::GetSample()
|
|
{
|
|
if (output)
|
|
{
|
|
dword sample = 0;
|
|
|
|
for (uint i=0; i < NUM_SQUARES; ++i)
|
|
sample += square[i].GetSample( rate );
|
|
|
|
sample += pcm.GetSample();
|
|
|
|
return dcBlocker.Apply( sample * 2 * output / DEFAULT_VOLUME );
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Square::ClockQuarter()
|
|
{
|
|
envelope.Clock();
|
|
}
|
|
|
|
NST_SINGLE_CALL void Mmc5::Sound::Square::ClockHalf()
|
|
{
|
|
if (!envelope.Looping() && lengthCounter.Clock())
|
|
active = false;
|
|
}
|
|
|
|
Cycle Mmc5::Sound::Clock(Cycle rateCycles,Cycle rateClock,const Cycle targetCycles)
|
|
{
|
|
rateClock *= quarterClock;
|
|
|
|
do
|
|
{
|
|
for (uint i=0; i < NUM_SQUARES; ++i)
|
|
square[i].ClockQuarter();
|
|
|
|
if (atHalfClock)
|
|
{
|
|
for (uint i=0; i < NUM_SQUARES; ++i)
|
|
square[i].ClockHalf();
|
|
}
|
|
|
|
atHalfClock ^= 1;
|
|
|
|
rateCycles += rateClock;
|
|
}
|
|
while (rateCycles <= targetCycles);
|
|
|
|
return rateCycles;
|
|
}
|
|
}
|
|
}
|
|
}
|