//////////////////////////////////////////////////////////////////////////////////////// // // 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 #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 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.Source(0)).SwapBank( bank & Regs::PRG_ROM_BANK ); } else if (Banks::Wrk::INVALID != (bank = banks.wrk[bank & Regs::PRG_RAM_BANK])) { banks.security |= RAM; static_cast(prg.Source(1)).SwapBank( 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( 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( 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( 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( banks.prg[3] ); break; } } void Mmc5::UpdateChrA() const { switch (regs.chrMode) { case Regs::CHR_MODE_8K: chr.SwapBank( banks.chrA[7] ); break; case Regs::CHR_MODE_4K: chr.SwapBanks( banks.chrA[3], banks.chrA[7] ); break; case Regs::CHR_MODE_2K: chr.SwapBanks( banks.chrA[1], banks.chrA[3], banks.chrA[5], banks.chrA[7] ); break; case Regs::CHR_MODE_1K: chr.SwapBanks( 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( banks.chrB[3] ); break; case Regs::CHR_MODE_4K: chr.SwapBanks( banks.chrB[3], banks.chrB[3] ); break; case Regs::CHR_MODE_2K: chr.SwapBanks( banks.chrB[1], banks.chrB[3], banks.chrB[1], banks.chrB[3] ); break; case Regs::CHR_MODE_1K: chr.SwapBanks( 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(uint address) const { return ciRam[0][address]; } template<> NST_FORCE_INLINE uint Mmc5::FetchByte(uint address) const { return ciRam[1][address]; } template<> NST_FORCE_INLINE uint Mmc5::FetchByte(uint address) const { return exRam.mem[address]; } template<> NST_FORCE_INLINE uint Mmc5::FetchByte(uint address) const { return (address & 0x3FF) < 0x3C0 ? filler.tile : filler.attribute; } template<> NST_FORCE_INLINE uint Mmc5::FetchByte(uint) const { return 0; } template NST_FORCE_INLINE uint Mmc5::FetchNt(uint address) const { return FetchByte( address ); } template NST_FORCE_INLINE uint Mmc5::FetchNtExt(uint address) { if ((address & 0x3FF) < 0x3C0) { exRam.tile = exRam.mem[address]; return FetchByte( address ); } else { return Filler::squared[exRam.tile >> 6]; } } template NST_FORCE_INLINE uint Mmc5::FetchNtSplit(uint address) { if ((address & 0x3FF) < 0x3C0) { if (ClockSpliter()) return exRam.mem[spliter.tile]; else return FetchByte( address ); } else { if (spliter.inside) return GetSpliterAttribute(); else return FetchByte( address ); } } template 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( 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::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::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 ( 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( 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; } } } }