mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
527 lines
12 KiB
C++
527 lines
12 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 "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<SIZE_8K,0x0000>( vrc2.prg[0], vrc2.prg[1], 0x1E, 0x1F );
|
|
break;
|
|
|
|
case 0x1:
|
|
{
|
|
const uint i = mmc3.ctrl >> 5 & 0x2U;
|
|
prg.SwapBanks<SIZE_8K,0x0000>( 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<SIZE_16K,0x0000>( (mmc1.regs[0] & 0x4U) ? bank : 0x0, (mmc1.regs[0] & 0x4U) ? 0xF : bank );
|
|
else
|
|
prg.SwapBank<SIZE_32K,0x0000>( bank >> 1 );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sl12::UpdateChr() const
|
|
{
|
|
const uint base = (mode & 0x4) << 6;
|
|
|
|
switch (mode & 0x3)
|
|
{
|
|
case 0x0:
|
|
|
|
chr.SwapBanks<SIZE_1K,0x0000>( 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<SIZE_2K>( 0x0000 ^ swap, base >> 1 | mmc3.banks[0], base >> 1 | mmc3.banks[1] );
|
|
chr.SwapBanks<SIZE_1K>( 0x1000 ^ swap, base|mmc3.banks[2], base|mmc3.banks[3], base|mmc3.banks[4], base|mmc3.banks[5] );
|
|
break;
|
|
}
|
|
|
|
case 0x2:
|
|
|
|
chr.SwapBanks<SIZE_4K,0x0000>( (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 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|