//////////////////////////////////////////////////////////////////////////////////////// // // 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 "NstBoard.hpp" #include "NstBoardMmc3.hpp" #include "NstBoardSomeriTeam.hpp" namespace Nes { namespace Core { namespace Boards { namespace SomeriTeam { #ifdef NST_MSVC_OPTIMIZE #pragma optimize("s", on) #endif Sl12::Sl12(const Context& c) : Board (c), irq (*c.cpu,*c.ppu,false) {} void Sl12::SubReset(const bool hard) { irq.Reset( hard ); if (hard) { mode = 0; vrc2.prg[0] = 0x0; vrc2.prg[1] = 0x1; vrc2.nmt = 0; for (uint i=0; i < 8; ++i) vrc2.chr[i] = i; mmc3.ctrl = 0; mmc3.nmt = 0; mmc3.banks[0] = 0x0; mmc3.banks[1] = 0x1; mmc3.banks[2] = 0x4; mmc3.banks[3] = 0x5; mmc3.banks[4] = 0x6; mmc3.banks[5] = 0x7; mmc3.banks[6] = 0x3C; mmc3.banks[7] = 0x3D; mmc3.banks[8] = 0xFE; mmc3.banks[9] = 0xFF; mmc1.buffer = 0; mmc1.shifter = 0; mmc1.regs[0] = 0x4U|0x8U; mmc1.regs[1] = 0; mmc1.regs[2] = 0; mmc1.regs[3] = 0; } for (uint i=0x4100; i < 0x6000; i += 0x200) Map( i + 0x00, i + 0xFF, &Sl12::Poke_4100 ); Map( 0x8000U, 0x8FFFU, &Sl12::Poke_8000 ); Map( 0x9000U, 0x9FFFU, &Sl12::Poke_9000 ); Map( 0xA000U, 0xAFFFU, &Sl12::Poke_A000 ); Map( 0xB000U, 0xBFFFU, &Sl12::Poke_B000 ); Map( 0xC000U, 0xCFFFU, &Sl12::Poke_C000 ); Map( 0xD000U, 0xDFFFU, &Sl12::Poke_D000 ); Map( 0xE000U, 0xEFFFU, &Sl12::Poke_E000 ); Map( 0xF000U, 0xFFFFU, &Sl12::Poke_F000 ); UpdatePrg(); UpdateNmt(); UpdateChr(); } void Sl12::SubLoad(State::Loader& state,const dword baseChunk) { NST_VERIFY( baseChunk == (AsciiId<'S','1','2'>::V) ); if (baseChunk == AsciiId<'S','1','2'>::V) { while (const dword chunk = state.Begin()) { switch (chunk) { case AsciiId<'R','E','G'>::V: mode = state.Read8(); break; case AsciiId<'V','R','2'>::V: state.Read( vrc2.chr ); state.Read( vrc2.prg ); vrc2.nmt = state.Read8(); break; case AsciiId<'M','M','3'>::V: state.Read( mmc3.banks ); mmc3.ctrl = state.Read8(); mmc3.nmt = state.Read8(); break; case AsciiId<'M','M','1'>::V: state.Read( mmc1.regs ); mmc1.buffer = state.Read8(); mmc1.shifter = state.Read8(); break; case AsciiId<'I','R','Q'>::V: irq.unit.LoadState( state ); break; } state.End(); } } UpdatePrg(); UpdateNmt(); UpdateChr(); } void Sl12::SubSave(State::Saver& state) const { state.Begin( AsciiId<'S','1','2'>::V ); state.Begin( AsciiId<'R','E','G'>::V ).Write8( mode ).End(); state.Begin( AsciiId<'V','R','2'>::V ).Write( vrc2.chr ).Write( vrc2.prg ).Write8( vrc2.nmt ).End(); state.Begin( AsciiId<'M','M','3'>::V ).Write( mmc3.banks ).Write8( mmc3.ctrl ).Write8( mmc3.nmt ).End(); state.Begin( AsciiId<'M','M','1'>::V ).Write( mmc1.regs ).Write8( mmc1.buffer ).Write8( mmc1.shifter ).End(); irq.unit.SaveState( state, AsciiId<'I','R','Q'>::V ); state.End(); } #ifdef NST_MSVC_OPTIMIZE #pragma optimize("", on) #endif void Sl12::UpdatePrg() { switch (mode & 0x3) { case 0x0: prg.SwapBanks( vrc2.prg[0], vrc2.prg[1], 0x1E, 0x1F ); break; case 0x1: { const uint i = mmc3.ctrl >> 5 & 0x2U; prg.SwapBanks( mmc3.banks[6+i], mmc3.banks[6+1], mmc3.banks[6+(i^2)], mmc3.banks[6+3] ); break; } case 0x2: { const uint bank = mmc1.regs[3] & 0xFU; if (mmc1.regs[0] & 0x8U) prg.SwapBanks( (mmc1.regs[0] & 0x4U) ? bank : 0x0, (mmc1.regs[0] & 0x4U) ? 0xF : bank ); else prg.SwapBank( bank >> 1 ); break; } } } void Sl12::UpdateChr() const { const uint base = (mode & 0x4) << 6; switch (mode & 0x3) { case 0x0: chr.SwapBanks( base|vrc2.chr[0], base|vrc2.chr[1], base|vrc2.chr[2], base|vrc2.chr[3], base|vrc2.chr[4], base|vrc2.chr[5], base|vrc2.chr[6], base|vrc2.chr[7] ); break; case 0x1: { const uint swap = (mmc3.ctrl & 0x80U) << 5; chr.SwapBanks( 0x0000 ^ swap, base >> 1 | mmc3.banks[0], base >> 1 | mmc3.banks[1] ); chr.SwapBanks( 0x1000 ^ swap, base|mmc3.banks[2], base|mmc3.banks[3], base|mmc3.banks[4], base|mmc3.banks[5] ); break; } case 0x2: chr.SwapBanks( (mmc1.regs[0] & 0x10U) ? mmc1.regs[1] : mmc1.regs[1] & 0x1EU, (mmc1.regs[0] & 0x10U) ? mmc1.regs[2] : mmc1.regs[1] | 0x01U ); break; } } void Sl12::UpdateNmt() const { Ppu::NmtMirroring nmtCtrl; switch (mode & 0x3) { case 0x0: nmtCtrl = (vrc2.nmt & 0x1U) ? Ppu::NMT_H : Ppu::NMT_V; break; case 0x1: nmtCtrl = (mmc3.nmt & 0x1U) ? Ppu::NMT_H : Ppu::NMT_V; break; case 0x2: switch (mmc1.regs[0] & 0x3U) { case 0x0: nmtCtrl = Ppu::NMT_0; break; case 0x1: nmtCtrl = Ppu::NMT_1; break; case 0x2: nmtCtrl = Ppu::NMT_V; break; default: nmtCtrl = Ppu::NMT_H; break; } break; default: return; } ppu.SetMirroring( nmtCtrl ); } void Sl12::Poke_Vrc2_8000(uint address,uint data) { NST_ASSERT( (mode & 0x3) == 0 ); data &= 0x1F; address = address >> 13 & 0x1; if (vrc2.prg[address] != data) { vrc2.prg[address] = data; UpdatePrg(); } } void Sl12::Poke_Vrc2_9000(uint,uint data) { NST_ASSERT( (mode & 0x3) == 0 ); data &= 0x1; if (vrc2.nmt != data) { vrc2.nmt = data; UpdateNmt(); } } void Sl12::Poke_Vrc2_B000(uint address,uint data) { NST_ASSERT( (mode & 0x3) == 0 ); data = (data & 0xF) << (address << 1 & 0x4); address = ((address - 0xB000) >> 11 & 0x6) | (address & 0x1); if (vrc2.chr[address] != data) { vrc2.chr[address] = data; ppu.Update(); UpdateChr(); } } void Sl12::Poke_Mmc3_8000(uint address,uint data) { NST_ASSERT( (mode & 0x3) == 1 ); if (address & 0x1) { address = mmc3.ctrl & 0x7U; if (address < 2) data >>= 1; if (mmc3.banks[address] != data) { mmc3.banks[address] = data; if (address < 6) { ppu.Update(); UpdateChr(); } else { UpdatePrg(); } } } else { address = mmc3.ctrl ^ data; mmc3.ctrl = data; if (address & 0x40) UpdatePrg(); if (address & (0x80U|0x07U)) { ppu.Update(); UpdateChr(); } } } void Sl12::Poke_Mmc3_A000(uint address,uint data) { NST_ASSERT( (mode & 0x3) == 1 ); if (!(address & 0x1)) { if (mmc3.nmt != data) { mmc3.nmt = data; UpdateNmt(); } } } void Sl12::Poke_Mmc3_C000(uint address,uint data) { NST_ASSERT( (mode & 0x3) == 1 ); irq.Update(); if (address & 0x1) irq.unit.Reload(); else irq.unit.SetLatch( data ); } void Sl12::Poke_Mmc3_E000(uint address,uint) { NST_ASSERT( (mode & 0x3) == 1 ); irq.Update(); if (address & 0x1) irq.unit.Enable(); else irq.unit.Disable( cpu ); } void Sl12::Poke_Mmc1_8000(uint address,uint data) { NST_ASSERT( (mode & 0x3) == 2 ); if (!(data & 0x80)) { mmc1.buffer |= (data & 0x1) << mmc1.shifter++; if (mmc1.shifter != 5) return; mmc1.shifter = 0; data = mmc1.buffer; mmc1.buffer = 0; address = address >> 13 & 0x3; if (mmc1.regs[address] != data) { mmc1.regs[address] = data; UpdatePrg(); UpdateNmt(); UpdateChr(); } } else { mmc1.buffer = 0; mmc1.shifter = 0; if ((mmc1.regs[0] & (0x4U|0x8U)) != (0x4U|0x8U)) { mmc1.regs[0] |= (0x4U|0x8U); UpdatePrg(); UpdateNmt(); UpdateChr(); } } } NES_POKE_D(Sl12,4100) { if (mode != data) { mode = data; if ((data & 0x3) != 1) irq.unit.Disable( cpu ); UpdatePrg(); UpdateNmt(); UpdateChr(); } } NES_POKE_AD(Sl12,8000) { switch (mode & 0x3) { case 0x0: Poke_Vrc2_8000( address, data ); break; case 0x1: Poke_Mmc3_8000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } NES_POKE_AD(Sl12,9000) { switch (mode & 0x3) { case 0x0: Poke_Vrc2_9000( address, data ); break; case 0x1: Poke_Mmc3_8000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } NES_POKE_AD(Sl12,A000) { switch (mode & 0x3) { case 0x0: Poke_Vrc2_8000( address, data ); break; case 0x1: Poke_Mmc3_A000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } NES_POKE_AD(Sl12,B000) { switch (mode & 0x3) { case 0x0: Poke_Vrc2_B000( address, data ); break; case 0x1: Poke_Mmc3_A000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } NES_POKE_AD(Sl12,C000) { switch (mode & 0x3) { case 0x0: Poke_Vrc2_B000( address, data ); break; case 0x1: Poke_Mmc3_C000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } NES_POKE_AD(Sl12,D000) { switch (mode & 0x3) { case 0x0: Poke_Vrc2_B000( address, data ); break; case 0x1: Poke_Mmc3_C000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } NES_POKE_AD(Sl12,E000) { switch (mode & 0x3) { case 0x0: Poke_Vrc2_B000( address, data ); break; case 0x1: Poke_Mmc3_E000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } NES_POKE_AD(Sl12,F000) { switch (mode & 0x3) { case 0x0: break; case 0x1: Poke_Mmc3_E000( address, data ); break; case 0x2: Poke_Mmc1_8000( address, data ); break; } } void Sl12::Sync(Event event,Input::Controllers* controllers) { if (event == EVENT_END_FRAME) irq.VSync(); Board::Sync( event, controllers ); } } } } }