nestopia/source/core/board/NstBoardJyCompany.cpp
2013-01-14 23:37:59 +01:00

875 lines
21 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 "../NstTimer.hpp"
#include "../NstDipSwitches.hpp"
#include "NstBoard.hpp"
#include "NstBoardJyCompany.hpp"
namespace Nes
{
namespace Core
{
namespace Boards
{
namespace JyCompany
{
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Standard::CartSwitches::CartSwitches(uint d,bool l)
: data(d), ppuLatched(l) {}
inline bool Standard::CartSwitches::IsPpuLatched() const
{
return ppuLatched;
}
Standard::Irq::A12::A12(Irq& irq)
: base(irq) {}
Standard::Irq::M2::M2(Irq& irq)
: base(irq) {}
#if NST_MSVC >= 1200
#pragma warning( push )
#pragma warning( disable : 4355 )
#endif
Standard::Irq::Irq(Cpu& cpu,Ppu& ppu)
: a12(cpu,ppu,*this), m2(cpu,*this) {}
#if NST_MSVC >= 1200
#pragma warning( pop )
#endif
Standard::Standard(const Context& c)
:
Board (c),
irq (*c.cpu,*c.ppu),
cartSwitches
(
board == Type::JYCOMPANY_TYPE_A ? DEFAULT_DIP_NMT_OFF :
board == Type::JYCOMPANY_TYPE_B ? DEFAULT_DIP_NMT_CONTROLLED :
DEFAULT_DIP_NMT_ON,
board == Type::JYCOMPANY_TYPE_B
)
{
}
void Standard::Regs::Reset()
{
mul[0] = 0;
mul[1] = 0;
tmp = 0;
ctrl[0] = 0;
ctrl[1] = 0;
ctrl[2] = 0;
ctrl[3] = 0;
}
void Standard::Banks::Reset()
{
for (uint i=0; i < 4; ++i)
prg[i] = 0xFF;
for (uint i=0; i < 8; ++i)
chr[i] = 0xFFFF;
for (uint i=0; i < 4; ++i)
nmt[i] = 0x00;
prg6 = NULL;
chrLatch[0] = 0;
chrLatch[1] = 4;
}
void Standard::Irq::A12::Reset(bool)
{
}
void Standard::Irq::M2::Reset(bool)
{
}
void Standard::Irq::Reset()
{
enabled = false;
mode = 0;
prescaler = 0;
scale = 0xFF;
count = 0;
flip = 0;
a12.Reset( true );
m2.Reset( true, true );
}
void Standard::SubReset(bool)
{
for (uint i=0x5000; i < 0x5800; i += 0x4)
Map( i, &Standard::Peek_5000 );
for (uint i=0x5800; i < 0x6000; i += 0x4)
{
cpu.Map( i + 0x0 ).Set( &regs, &Regs::Peek_5800, &Regs::Poke_5800 );
cpu.Map( i + 0x1 ).Set( &regs, &Regs::Peek_5801, &Regs::Poke_5801 );
cpu.Map( i + 0x3 ).Set( &regs, &Regs::Peek_5803, &Regs::Poke_5803 );
}
Map( 0x6000U, 0x7FFFU, &Standard::Peek_6000 );
Map( 0x8000U, 0x8FFFU, &Standard::Poke_8000 );
Map( 0x9000U, 0x9FFFU, &Standard::Poke_9000 );
Map( 0xA000U, 0xAFFFU, &Standard::Poke_A000 );
for (uint i=0x0000; i < 0x1000; i += 0x8)
{
Map( 0xB000 + i, 0xB003 + i, &Standard::Poke_B000 );
Map( 0xB004 + i, 0xB007 + i, &Standard::Poke_B004 );
Map( 0xC000 + i, &Standard::Poke_C000 );
Map( 0xC001 + i, &Standard::Poke_C001 );
Map( 0xC002 + i, &Standard::Poke_C002 );
Map( 0xC003 + i, &Standard::Poke_C003 );
Map( 0xC004 + i, &Standard::Poke_C004 );
Map( 0xC005 + i, &Standard::Poke_C005 );
Map( 0xC006 + i, &Standard::Poke_C006 );
}
for (uint i=0x0000; i < 0x1000; i += 0x4)
{
Map( 0xD000 + i, &Standard::Poke_D000 );
Map( 0xD001 + i, &Standard::Poke_D001 );
Map( 0xD002 + i, &Standard::Poke_D002 );
Map( 0xD003 + i, &Standard::Poke_D003 );
}
regs.Reset();
banks.Reset();
irq.Reset();
ppu.SetHActiveHook( Hook(this,&Standard::Hook_HActive) );
ppu.SetHBlankHook( Hook(this,&Standard::Hook_HBlank) );
if (cartSwitches.IsPpuLatched())
chr.SetAccessor( this, &Standard::Access_Chr );
UpdatePrg();
UpdateExChr();
UpdateChr();
UpdateNmt();
}
void Standard::SubLoad(State::Loader& state,const dword baseChunk)
{
NST_VERIFY( baseChunk == (AsciiId<'J','Y','C'>::V) );
if (baseChunk == AsciiId<'J','Y','C'>::V)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'R','E','G'>::V:
{
State::Loader::Data<35> data( state );
regs.ctrl[0] = data[0];
regs.ctrl[1] = data[1];
regs.ctrl[2] = data[2];
regs.ctrl[3] = data[3];
regs.mul[0] = data[4];
regs.mul[1] = data[5];
regs.tmp = data[6];
banks.prg[0] = data[7];
banks.prg[1] = data[8];
banks.prg[2] = data[9];
banks.prg[3] = data[10];
banks.chr[0] = data[11] | data[12] << 8;
banks.chr[1] = data[13] | data[14] << 8;
banks.chr[2] = data[15] | data[16] << 8;
banks.chr[3] = data[17] | data[18] << 8;
banks.chr[4] = data[19] | data[20] << 8;
banks.chr[5] = data[21] | data[22] << 8;
banks.chr[6] = data[23] | data[24] << 8;
banks.chr[7] = data[25] | data[26] << 8;
banks.nmt[0] = data[27] | data[28] << 8;
banks.nmt[1] = data[29] | data[30] << 8;
banks.nmt[2] = data[31] | data[32] << 8;
banks.nmt[3] = data[33] | data[34] << 8;
UpdatePrg();
UpdateExChr();
UpdateChr();
UpdateNmt();
break;
}
case AsciiId<'L','A','T'>::V:
NST_VERIFY( cartSwitches.IsPpuLatched() );
if (cartSwitches.IsPpuLatched())
{
banks.chrLatch[0] = state.Read8();
banks.chrLatch[1] = banks.chrLatch[0] >> 3 & 0x7;
banks.chrLatch[0] &= 0x7;
UpdateChr();
}
break;
case AsciiId<'I','R','Q'>::V:
{
byte data[5];
state.Read( data );
irq.enabled = data[0] & 0x1U;
irq.mode = data[1];
irq.prescaler = data[2];
irq.count = data[3];
irq.flip = data[4];
irq.scale = (irq.mode & Irq::MODE_SCALE_3BIT) ? 0x7 : 0xFF;
break;
}
}
state.End();
}
}
}
void Standard::SubSave(State::Saver& state) const
{
state.Begin( AsciiId<'J','Y','C'>::V );
{
const byte data[35] =
{
regs.ctrl[0],
regs.ctrl[1],
regs.ctrl[2],
regs.ctrl[3],
regs.mul[0],
regs.mul[1],
regs.tmp,
banks.prg[0],
banks.prg[1],
banks.prg[2],
banks.prg[3],
banks.chr[0] & 0xFF,
banks.chr[0] >> 8,
banks.chr[1] & 0xFF,
banks.chr[1] >> 8,
banks.chr[2] & 0xFF,
banks.chr[2] >> 8,
banks.chr[3] & 0xFF,
banks.chr[3] >> 8,
banks.chr[4] & 0xFF,
banks.chr[4] >> 8,
banks.chr[5] & 0xFF,
banks.chr[5] >> 8,
banks.chr[6] & 0xFF,
banks.chr[6] >> 8,
banks.chr[7] & 0xFF,
banks.chr[7] >> 8,
banks.nmt[0] & 0xFF,
banks.nmt[0] >> 8,
banks.nmt[1] & 0xFF,
banks.nmt[1] >> 8,
banks.nmt[2] & 0xFF,
banks.nmt[2] >> 8,
banks.nmt[3] & 0xFF,
banks.nmt[3] >> 8
};
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
}
if (cartSwitches.IsPpuLatched())
state.Begin( AsciiId<'L','A','T'>::V ).Write8( banks.chrLatch[0] | banks.chrLatch[1] << 3 ).End();
{
const byte data[5] =
{
irq.enabled != 0,
irq.mode,
irq.prescaler & 0xFF,
irq.count,
irq.flip
};
state.Begin( AsciiId<'I','R','Q'>::V ).Write( data ).End();
}
state.End();
}
uint Standard::CartSwitches::NumDips() const
{
return 2;
}
uint Standard::CartSwitches::NumValues(uint i) const
{
NST_ASSERT( i < 2 );
return (i == 0) ? 4 : 3;
}
cstring Standard::CartSwitches::GetDipName(uint dip) const
{
NST_ASSERT( dip < 2 );
return (dip == 0) ? "Game Select" : "Extended Mirroring";
}
cstring Standard::CartSwitches::GetValueName(uint dip,uint value) const
{
NST_ASSERT( dip < 2 );
if (dip == 0)
{
NST_ASSERT( value < 4 );
return (value == 0) ? "1" : (value == 1) ? "2" : (value == 2) ? "3" : "4";
}
else
{
NST_ASSERT( value < 3 );
return (value == 0) ? "Off" : (value == 1) ? "Controlled" : "On";
}
}
uint Standard::CartSwitches::GetValue(uint dip) const
{
NST_ASSERT( dip < 2 );
if (dip == 0)
return data >> 6;
else
return data & DIPSWITCH_NMT;
}
void Standard::CartSwitches::SetValue(uint dip,uint value)
{
NST_ASSERT( dip < 2 );
if (dip == 0)
{
NST_ASSERT( value < 4 );
data = (data & ~uint(DIPSWITCH_GAME)) | (value << 6);
}
else
{
NST_ASSERT( value < 3 );
data = (data & ~uint(DIPSWITCH_NMT)) | (value << 0);
}
}
Standard::Device Standard::QueryDevice(DeviceType type)
{
if (type == DEVICE_DIP_SWITCHES)
return &cartSwitches;
else
return Board::QueryDevice( type );
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
inline uint Standard::CartSwitches::GetSetting() const
{
return data;
}
inline void Standard::Irq::Update()
{
a12.Update();
m2.Update();
}
bool Standard::Irq::IsEnabled() const
{
return enabled &&
(
(mode & MODE_COUNT_ENABLE) == MODE_COUNT_DOWN ||
(mode & MODE_COUNT_ENABLE) == MODE_COUNT_UP
);
}
bool Standard::Irq::IsEnabled(uint checkMode) const
{
return (mode & MODE_SOURCE) == checkMode && IsEnabled();
}
bool Standard::Irq::Clock()
{
NST_ASSERT( IsEnabled() );
if (mode & MODE_COUNT_DOWN)
return (--prescaler & scale) == scale && (count-- & 0xFF) == 0x00;
else
return (++prescaler & scale) == 0x00 && (++count & 0xFF) == 0x00;
}
bool Standard::Irq::A12::Clock()
{
return base.IsEnabled(MODE_PPU_A12) && base.Clock();
}
bool Standard::Irq::M2::Clock()
{
return base.IsEnabled(MODE_M2) && base.Clock();
}
uint Standard::Banks::Unscramble(const uint bank)
{
return
(
(bank & 0x01) << 6 |
(bank & 0x02) << 4 |
(bank & 0x04) << 2 |
(bank & 0x10) >> 2 |
(bank & 0x20) >> 4 |
(bank & 0x40) >> 6
);
}
NES_HOOK(Standard,HActive)
{
if (irq.IsEnabled(Irq::MODE_PPU_READ) && ppu.IsEnabled())
{
for (uint i=0, hit=false; i < (32*4) * 2; i += 2)
{
if (irq.Clock() && !hit)
{
hit = true;
cpu.DoIRQ( Cpu::IRQ_EXT, cpu.GetCycles() + ppu.GetClock() * i );
}
}
}
}
NES_HOOK(Standard,HBlank)
{
if (irq.IsEnabled(Irq::MODE_PPU_READ) && ppu.IsEnabled())
{
for (uint i=0, hit=false; i < (8*4+2*4+2) * 2; i += 2)
{
if (irq.Clock() && !hit)
{
hit = true;
cpu.DoIRQ( Cpu::IRQ_EXT, cpu.GetCycles() + ppu.GetClock() * i );
}
}
}
}
NES_ACCESSOR(Standard,Chr)
{
const uint data = chr.Peek( address );
switch (address & 0xFF8)
{
case 0xFD8:
case 0xFE8:
banks.chrLatch[address >> 12] = address >> 4 & ((address >> 10 & 0x4) | 0x2);
if ((regs.ctrl[0] & Regs::CTRL0_CHR_MODE) == Regs::CTRL0_CHR_SWAP_4K)
UpdateChrLatch();
break;
}
return data;
}
NES_PEEK_A(Standard,5000)
{
return (cartSwitches.GetSetting() & DIPSWITCH_GAME) | (address >> 8 & ~uint(DIPSWITCH_GAME));
}
NES_POKE_D(Standard::Regs,5800) { mul[0] = data; }
NES_POKE_D(Standard::Regs,5801) { mul[1] = data; }
NES_POKE_D(Standard::Regs,5803) { tmp = data; }
NES_PEEK(Standard::Regs,5800) { return (mul[0] * mul[1]) & 0xFF; }
NES_PEEK(Standard::Regs,5801) { return (mul[0] * mul[1]) >> 8; }
NES_PEEK(Standard::Regs,5803) { return tmp; }
NES_PEEK_A(Standard,6000)
{
NST_VERIFY( banks.prg6 );
return banks.prg6 ? banks.prg6[address - 0x6000] : (address >> 8);
}
NES_POKE_AD(Standard,8000)
{
address &= 0x3;
data &= 0x3F;
if (banks.prg[address] != data)
{
banks.prg[address] = data;
UpdatePrg();
}
}
NES_POKE_AD(Standard,9000)
{
address &= 0x7;
data |= banks.chr[address] & 0xFF00;
if (banks.chr[address] != data)
{
banks.chr[address] = data;
UpdateChr();
}
}
NES_POKE_AD(Standard,A000)
{
address &= 0x7;
data = data << 8 | (banks.chr[address] & 0x00FF);
if (banks.chr[address] != data)
{
banks.chr[address] = data;
UpdateChr();
}
}
NES_POKE_AD(Standard,B000)
{
address &= 0x3;
data |= banks.nmt[address] & 0xFF00;
if (banks.nmt[address] != data)
{
banks.nmt[address] = data;
UpdateNmt();
}
}
NES_POKE_AD(Standard,B004)
{
address &= 0x3;
data = data << 8 | (banks.nmt[address] & 0x00FF);
if (banks.nmt[address] != data)
{
banks.nmt[address] = data;
UpdateNmt();
}
}
NES_POKE_D(Standard,C000)
{
data &= Irq::TOGGLE;
if (irq.enabled != data)
{
irq.Update();
irq.enabled = data;
if (!data)
cpu.ClearIRQ();
}
}
NES_POKE_D(Standard,C001)
{
if (irq.mode != data)
{
irq.Update();
NST_VERIFY
(
(data & Irq::MODE_SCALE_ADJUST) == 0 &&
(
(data & Irq::MODE_SOURCE) == Irq::MODE_M2 ||
(data & Irq::MODE_SOURCE) == Irq::MODE_PPU_A12 ||
(data & Irq::MODE_SOURCE) == Irq::MODE_PPU_READ
)
);
irq.mode = data;
irq.scale = (data & Irq::MODE_SCALE_3BIT) ? 0x7 : 0xFF;
}
}
NES_POKE(Standard,C002)
{
if (irq.enabled)
{
irq.Update();
irq.enabled = false;
cpu.ClearIRQ();
}
}
NES_POKE(Standard,C003)
{
if (!irq.enabled)
{
irq.Update();
irq.enabled = true;
}
}
NES_POKE_D(Standard,C004)
{
irq.Update();
irq.prescaler = data ^ irq.flip;
}
NES_POKE_D(Standard,C005)
{
irq.Update();
irq.count = data ^ irq.flip;
}
NES_POKE_D(Standard,C006)
{
irq.flip = data;
}
NES_POKE_D(Standard,D000)
{
if (regs.ctrl[0] != data)
{
regs.ctrl[0] = data;
UpdatePrg();
UpdateExChr();
UpdateChr();
UpdateNmt();
}
}
NES_POKE_D(Standard,D001)
{
if (regs.ctrl[1] != data)
{
regs.ctrl[1] = data;
UpdateNmt();
}
}
NES_POKE_D(Standard,D002)
{
if (regs.ctrl[2] != data)
{
regs.ctrl[2] = data;
UpdateNmt();
}
}
NES_POKE_D(Standard,D003)
{
if (regs.ctrl[3] != data)
{
regs.ctrl[3] = data;
UpdatePrg();
UpdateExChr();
UpdateChr();
}
}
void Standard::UpdatePrg()
{
NST_VERIFY( (regs.ctrl[0] & Regs::CTRL0_PRG_MODE) != Regs::CTRL0_PRG_SWAP_8K_R );
const uint exPrg = (regs.ctrl[3] & Regs::CTRL3_EX_PRG) << 5;
if (!(regs.ctrl[0] & Regs::CTRL0_PRG6_ENABLE))
{
banks.prg6 = NULL;
}
else
{
uint bank = banks.prg[3];
switch (regs.ctrl[0] & Regs::CTRL0_PRG_MODE)
{
case Regs::CTRL0_PRG_SWAP_32K: bank = bank << 2 | 0x3; break;
case Regs::CTRL0_PRG_SWAP_16K: bank = bank << 1 | 0x1; break;
case Regs::CTRL0_PRG_SWAP_8K_R: bank = banks.Unscramble( bank ); break;
}
banks.prg6 = prg.Source().Mem( ((bank & 0x3FUL) | exPrg) * SIZE_8K );
}
const uint last = (regs.ctrl[0] & Regs::CTRL0_PRG_NOT_LAST) ? banks.prg[3] : 0x3F;
switch (regs.ctrl[0] & Regs::CTRL0_PRG_MODE)
{
case Regs::CTRL0_PRG_SWAP_32K:
prg.SwapBank<SIZE_32K,0x0000>
(
(last & 0xF) | (exPrg >> 2)
);
break;
case Regs::CTRL0_PRG_SWAP_16K:
prg.SwapBanks<SIZE_16K,0x0000>
(
(banks.prg[1] & 0x1F) | (exPrg >> 1),
(last & 0x1F) | (exPrg >> 1)
);
break;
case Regs::CTRL0_PRG_SWAP_8K:
prg.SwapBanks<SIZE_8K,0x0000>
(
(banks.prg[0] & 0x3F) | exPrg,
(banks.prg[1] & 0x3F) | exPrg,
(banks.prg[2] & 0x3F) | exPrg,
(last & 0x3F) | exPrg
);
break;
case Regs::CTRL0_PRG_SWAP_8K_R:
prg.SwapBanks<SIZE_8K,0x0000>
(
(banks.Unscramble( banks.prg[0] ) & 0x3F) | exPrg,
(banks.Unscramble( banks.prg[1] ) & 0x3F) | exPrg,
(banks.Unscramble( banks.prg[2] ) & 0x3F) | exPrg,
(banks.Unscramble( last ) & 0x3F) | exPrg
);
break;
}
}
void Standard::UpdateExChr()
{
if (regs.ctrl[3] & Regs::CTRL3_NO_EX_CHR)
{
banks.exChr.mask = 0xFFFF;
banks.exChr.bank = 0x0000;
}
else
{
const uint mode = (regs.ctrl[0] & Regs::CTRL0_CHR_MODE) >> 3;
banks.exChr.mask = 0x00FFU >> (mode ^ 0x3);
banks.exChr.bank = ((regs.ctrl[3] & Regs::CTRL3_EX_CHR_0) | ((regs.ctrl[3] & Regs::CTRL3_EX_CHR_1) >> 2)) << (mode + 5);
}
}
void Standard::UpdateChr() const
{
ppu.Update();
switch (regs.ctrl[0] & Regs::CTRL0_CHR_MODE)
{
case Regs::CTRL0_CHR_SWAP_8K:
chr.SwapBank<SIZE_8K,0x0000>
(
(banks.chr[0] & banks.exChr.mask) | banks.exChr.bank
);
break;
case Regs::CTRL0_CHR_SWAP_4K:
UpdateChrLatch();
break;
case Regs::CTRL0_CHR_SWAP_2K:
chr.SwapBanks<SIZE_2K,0x0000>
(
(banks.chr[0] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[2] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[4] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[6] & banks.exChr.mask) | banks.exChr.bank
);
break;
case Regs::CTRL0_CHR_SWAP_1K:
chr.SwapBanks<SIZE_1K,0x0000>
(
(banks.chr[0] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[1] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[2] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[3] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[4] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[5] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[6] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[7] & banks.exChr.mask) | banks.exChr.bank
);
break;
}
}
void Standard::UpdateChrLatch() const
{
NST_ASSERT( (regs.ctrl[0] & Regs::CTRL0_CHR_MODE) == Regs::CTRL0_CHR_SWAP_4K );
chr.SwapBanks<SIZE_4K,0x0000>
(
(banks.chr[banks.chrLatch[0]] & banks.exChr.mask) | banks.exChr.bank,
(banks.chr[banks.chrLatch[1]] & banks.exChr.mask) | banks.exChr.bank
);
}
void Standard::UpdateNmt()
{
if ((regs.ctrl[0] >> 5 & cartSwitches.GetSetting() & 0x1) | (cartSwitches.GetSetting() & 0x2))
{
ppu.Update();
for (uint i=0; i < 4; ++i)
nmt.Source( (regs.ctrl[0] & Regs::CTRL0_NMT_CHR_ROM) || ((banks.nmt[i] ^ regs.ctrl[2]) & Regs::CTRL2_NMT_USE_RAM) ).SwapBank<SIZE_1K>( i * 0x0400, banks.nmt[i] );
}
else
{
SetMirroringVH01( regs.ctrl[1] );
}
}
void Standard::Sync(Event event,Input::Controllers* controllers)
{
if (event == EVENT_END_FRAME)
{
irq.a12.VSync();
irq.m2.VSync();
}
Board::Sync( event, controllers );
}
}
}
}
}