//////////////////////////////////////////////////////////////////////////////////////// // // 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 "NstBoardTengenRambo1.hpp" namespace Nes { namespace Core { namespace Boards { namespace Tengen { #ifdef NST_MSVC_OPTIMIZE #pragma optimize("s", on) #endif Rambo1::Irq::Irq(Cpu& cpu,Ppu& ppu) : a12 ( cpu, ppu, unit ), m2 ( cpu, unit ) {} Rambo1::Rambo1(const Context& c) : Board (c), irq (*c.cpu,*c.ppu) {} void Rambo1::Irq::Unit::Reset(const bool hard) { if (hard) { count = 0; cycles = 0; reload = false; latch = 0; enabled = false; } } void Rambo1::Regs::Reset() { for (uint i=0; i < 8; ++i) chr[i] = i; for (uint i=0; i < 3; ++i) prg[i] = i; ctrl = 0; } void Rambo1::SubReset(const bool hard) { irq.a12.Reset( hard, !irq.m2.Connected() ); irq.m2.Reset( hard, irq.m2.Connected() ); if (hard) regs.Reset(); for (uint i=0x0000; i < 0x1000; i += 0x2) { Map( 0x8000 + i, &Rambo1::Poke_8000 ); Map( 0x8001 + i, &Rambo1::Poke_8001 ); Map( 0xA000 + i, NMT_SWAP_HV ); Map( 0xC000 + i, &Rambo1::Poke_C000 ); Map( 0xC001 + i, &Rambo1::Poke_C001 ); Map( 0xE000 + i, &Rambo1::Poke_E000 ); Map( 0xE001 + i, &Rambo1::Poke_E001 ); } UpdateChr(); UpdatePrg(); } void Rambo1::SubLoad(State::Loader& state,const dword baseChunk) { NST_VERIFY( baseChunk == (AsciiId<'T','R','1'>::V) ); if (baseChunk == AsciiId<'T','R','1'>::V) { while (const dword chunk = state.Begin()) { switch (chunk) { case AsciiId<'R','E','G'>::V: { State::Loader::Data<1+8+3> data( state ); regs.ctrl = data[0]; for (uint i=0; i < 3; ++i) regs.prg[i] = data[1+i]; for (uint i=0; i < 8; ++i) regs.chr[i] = data[1+3+i]; break; } case AsciiId<'I','R','Q'>::V: { State::Loader::Data<4> data( state ); irq.unit.enabled = data[0] & 0x1; irq.unit.mode = data[0] & 0x2 ? 1 : 0; irq.a12.Connect( data[0] & 0x2 ); irq.m2.Connect( data[0] & 0x2 ); irq.unit.reload = data[0] & 0x4; irq.unit.latch = data[1]; irq.unit.count = data[2]; irq.unit.cycles = data[3]; break; } } state.End(); } } } void Rambo1::SubSave(State::Saver& state) const { state.Begin( AsciiId<'T','R','1'>::V ); { const byte data[1+8+3] = { regs.ctrl, regs.prg[0], regs.prg[1], regs.prg[2], regs.chr[0], regs.chr[1], regs.chr[2], regs.chr[3], regs.chr[4], regs.chr[5], regs.chr[6], regs.chr[7] }; state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End(); } { const byte data[4] = { (irq.unit.enabled ? 0x1U : 0x0U) | (irq.m2.Connected() ? 0x2U : 0x0U) | (irq.unit.reload ? 0x4U : 0x0U), irq.unit.latch, irq.unit.count & 0xFF, irq.unit.cycles, }; state.Begin( AsciiId<'I','R','Q'>::V ).Write( data ).End(); } state.End(); } #ifdef NST_MSVC_OPTIMIZE #pragma optimize("", on) #endif bool Rambo1::Irq::Unit::Clock() { cycles++; if (latch == 1) { count = 0; } else if (reload) { reload = false; count = latch | (latch ? 1 : 0); if (mode) count |= 2; if (!latch && cycles > A12_FILTER) count = 1; else if (latch && (cycles > (A12_FILTER * 3))) count++; cycles = 0; } else if (!count) { count = latch; if (cycles > A12_FILTER) cycles = 0; } else { count--; } return (!count && enabled); } void Rambo1::Irq::Update() { a12.Update(); m2.Update(); } void Rambo1::UpdatePrg() { prg.SwapBanks ( regs.prg[(regs.ctrl & 0x40U) ? 2 : 0], regs.prg[(regs.ctrl & 0x40U) ? 0 : 1], regs.prg[(regs.ctrl & 0x40U) ? 1 : 2], 0xFF ); } void Rambo1::UpdateChr() const { ppu.Update(); const uint offset = (regs.ctrl & 0x80U) << 5; if (regs.ctrl & 0x20U) chr.SwapBanks( offset, regs.chr[0], regs.chr[6], regs.chr[1], regs.chr[7] ); else chr.SwapBanks( offset, regs.chr[0] >> 1, regs.chr[1] >> 1 ); chr.SwapBanks( offset ^ 0x1000, regs.chr[2], regs.chr[3], regs.chr[4], regs.chr[5] ); } NES_POKE_D(Rambo1,8000) { const uint diff = regs.ctrl ^ data; regs.ctrl = data; if (diff & 0x40) UpdatePrg(); if (diff & (0x20|0x80)) UpdateChr(); } NES_POKE_D(Rambo1,8001) { const uint index = regs.ctrl & 0xFU; if (index < 0x6) { if (regs.chr[index] != data) { regs.chr[index] = data; UpdateChr(); } } else switch (index) { case 0x6: case 0x7: if (regs.prg[index - 0x6] != data) { regs.prg[index - 0x6] = data; UpdatePrg(); } break; case 0x8: case 0x9: if (regs.chr[index - 0x2] != data) { regs.chr[index - 0x2] = data; UpdateChr(); } break; case 0xF: if (regs.prg[2] != data) { regs.prg[2] = data; UpdatePrg(); } break; } } NES_POKE_D(Rambo1,C000) { irq.Update(); irq.unit.latch = data; irq.unit.mode = irq.m2.Connected(); } NES_POKE_D(Rambo1,C001) { irq.Update(); irq.unit.reload = true; data &= Irq::SOURCE; irq.a12.Connect( data == Irq::SOURCE_PPU ); irq.m2.Connect( data == Irq::SOURCE_CPU ); } NES_POKE(Rambo1,E000) { irq.Update(); irq.unit.enabled = false; cpu.ClearIRQ(); } NES_POKE(Rambo1,E001) { irq.Update(); irq.unit.enabled = true; } void Rambo1::Sync(Event event,Input::Controllers* controllers) { if (event == EVENT_END_FRAME) { irq.a12.VSync(); irq.m2.VSync(); } Board::Sync( event, controllers ); } } } } }