Files
nestopia/source/core/NstCartridge.cpp

527 lines
13 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 <cstdlib>
#include <cstring>
#include "NstLog.hpp"
#include "NstChecksum.hpp"
#include "NstImageDatabase.hpp"
#include "board/NstBoard.hpp"
#include "NstCartridge.hpp"
#include "NstCartridgeRomset.hpp"
#include "NstCartridgeInes.hpp"
#include "NstCartridgeUnif.hpp"
#include "vssystem/NstVsSystem.hpp"
#include "api/NstApiUser.hpp"
namespace Nes
{
namespace Core
{
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Cartridge::ProfileEx::ProfileEx()
: nmt(NMT_DEFAULT), battery(false), wramAuto(false) {}
Cartridge::Cartridge(Context& context)
: Image(CARTRIDGE), board(NULL), vs(NULL), favoredSystem(context.favoredSystem)
{
try
{
ProfileEx profileEx;
switch (Stream::In(&context.stream).Peek32())
{
case INES_ID:
Ines::Load
(
context.stream,
context.patch,
context.patchBypassChecksum,
context.patchResult,
prg,
chr,
context.favoredSystem,
profile,
profileEx,
context.database
);
break;
case UNIF_ID:
Unif::Load
(
context.stream,
context.patch,
context.patchBypassChecksum,
context.patchResult,
prg,
chr,
context.favoredSystem,
profile,
profileEx,
context.database
);
break;
case 0x0000:
// Check for VC/NES Remix container for cartridge-based games
// Just load the game for now, until more is known about the container format.
//if (((Stream::In(&context.stream).Peek64() >> 32) & 0xFF) & 0x60)
//{
Ines::Load
(
context.stream,
context.patch,
context.patchBypassChecksum,
context.patchResult,
prg,
chr,
context.favoredSystem,
profile,
profileEx,
context.database
);
//}
break;
default:
Romset::Load
(
context.stream,
context.patch,
context.patchBypassChecksum,
context.patchResult,
prg,
chr,
context.favoredSystem,
context.askProfile,
profile
);
break;
}
if (profile.dump.state == Profile::Dump::BAD)
context.result = RESULT_WARN_BAD_DUMP;
else
context.result = RESULT_OK;
const Result result = SetupBoard( prg, chr, &board, &context, profile, profileEx, &prgCrc );
if (NES_FAILED(result))
throw result;
board->Load( savefile );
if ((profile.system.type) == Profile::System::VS_UNISYSTEM)
{
vs = VsSystem::Create
(
context.cpu,
context.ppu,
static_cast<PpuModel>(profile.system.ppu),
prgCrc
);
profile.system.ppu = static_cast<Profile::System::Ppu>(vs->GetPpuModel());
}
else if ((profile.system.type) == Profile::System::VS_DUALSYSTEM)
{
throw RESULT_ERR_UNSUPPORTED_VSSYSTEM;
}
if (Cartridge::QueryExternalDevice( EXT_DIP_SWITCHES ))
Log::Flush( "Cartridge: DIP Switches present" NST_LINEBREAK );
}
catch (...)
{
Destroy();
throw;
}
}
void Cartridge::Destroy()
{
VsSystem::Destroy( vs );
Boards::Board::Destroy( board );
}
Cartridge::~Cartridge()
{
Destroy();
}
void Cartridge::ReadRomset(std::istream& stream,FavoredSystem favoredSystem,bool askSystem,Profile& profile)
{
Log::Suppressor logSupressor;
Ram prg, chr;
ProfileEx profileEx;
Romset::Load( stream, NULL, false, NULL, prg, chr, favoredSystem, askSystem, profile, true );
SetupBoard( prg, chr, NULL, NULL, profile, profileEx, NULL, true );
}
void Cartridge::ReadInes(std::istream& stream,FavoredSystem favoredSystem,Profile& profile)
{
Log::Suppressor logSupressor;
Ram prg, chr;
ProfileEx profileEx;
Ines::Load( stream, NULL, false, NULL, prg, chr, favoredSystem, profile, profileEx, NULL );
SetupBoard( prg, chr, NULL, NULL, profile, profileEx, NULL );
}
void Cartridge::ReadUnif(std::istream& stream,FavoredSystem favoredSystem,Profile& profile)
{
Log::Suppressor logSupressor;
Ram prg, chr;
ProfileEx profileEx;
Unif::Load( stream, NULL, false, NULL, prg, chr, favoredSystem, profile, profileEx, NULL );
SetupBoard( prg, chr, NULL, NULL, profile, profileEx, NULL );
}
uint Cartridge::GetDesiredController(uint port) const
{
NST_ASSERT( port < Api::Input::NUM_CONTROLLERS );
return profile.game.controllers[port];
}
uint Cartridge::GetDesiredAdapter() const
{
return profile.game.adapter;
}
Cartridge::ExternalDevice Cartridge::QueryExternalDevice(ExternalDeviceType deviceType)
{
switch (deviceType)
{
case EXT_DIP_SWITCHES:
if (vs)
return &vs->GetDipSwiches();
else
return board->QueryDevice( Boards::Board::DEVICE_DIP_SWITCHES );
case EXT_BARCODE_READER:
return board->QueryDevice( Boards::Board::DEVICE_BARCODE_READER );
default:
return Image::QueryExternalDevice( deviceType );
}
}
Result Cartridge::SetupBoard
(
Ram& prg,
Ram& chr,
Boards::Board** board,
const Context* const context,
Profile& profile,
const ProfileEx& profileEx,
dword* const prgCrc,
const bool readOnly
)
{
NST_ASSERT( bool(board) == bool(context) );
Boards::Board::Type::Nmt nmt;
if (profile.board.solderPads & (Profile::Board::SOLDERPAD_H|Profile::Board::SOLDERPAD_V))
{
if (profile.board.solderPads & Profile::Board::SOLDERPAD_H)
nmt = Boards::Board::Type::NMT_VERTICAL;
else
nmt = Boards::Board::Type::NMT_HORIZONTAL;
}
else switch (profileEx.nmt)
{
case ProfileEx::NMT_HORIZONTAL: nmt = Boards::Board::Type::NMT_HORIZONTAL; break;
case ProfileEx::NMT_VERTICAL: nmt = Boards::Board::Type::NMT_VERTICAL; break;
case ProfileEx::NMT_SINGLESCREEN: nmt = Boards::Board::Type::NMT_SINGLESCREEN; break;
case ProfileEx::NMT_FOURSCREEN: nmt = Boards::Board::Type::NMT_FOURSCREEN; break;
default: nmt = Boards::Board::Type::NMT_CONTROLLED; break;
}
Chips chips;
for (Profile::Board::Chips::const_iterator i(profile.board.chips.begin()), end(profile.board.chips.end()); i != end; ++i)
{
Chips::Type& type = chips.Add( i->type.c_str() );
for (Profile::Board::Pins::const_iterator j(i->pins.begin()), end(i->pins.end()); j != end; ++j)
type.Pin(j->number) = j->function.c_str();
for (Profile::Board::Samples::const_iterator j(i->samples.begin()), end(i->samples.end()); j != end; ++j)
type.Sample(j->id) = j->file.c_str();
}
Boards::Board::Context b
(
context ? &context->cpu : NULL,
context ? &context->apu : NULL,
context ? &context->ppu : NULL,
prg,
chr,
profileEx.trainer,
nmt,
profileEx.battery || profile.board.HasWramBattery(),
profile.board.HasMmcBattery(),
chips
);
if (profile.board.type.empty() || !b.DetectBoard( profile.board.type.c_str(), profile.board.GetWram() ))
{
if (profile.board.mapper == Profile::Board::NO_MAPPER || (!b.DetectBoard( profile.board.mapper, profile.board.subMapper, profile.board.chrRam, profile.board.GetWram(), profileEx.wramAuto ) && board))
return RESULT_ERR_UNSUPPORTED_MAPPER;
if (profile.board.type.empty())
profile.board.type = std::wstring( b.name, b.name + std::strlen(b.name) );
}
if (profile.board.mapper == Profile::Board::NO_MAPPER && b.type.GetMapper() != Boards::Board::Type::NMPR)
profile.board.mapper = b.type.GetMapper();
for (uint i=0; i < 2; ++i)
{
dword size = (i ? b.chr : b.prg).Size();
if (size != (i ? profile.board.GetChr() : profile.board.GetPrg()))
{
Profile::Board::Roms& roms = (i ? profile.board.chr : profile.board.prg);
roms.clear();
if (size)
{
Profile::Board::Rom rom;
rom.size = size;
roms.push_back( rom );
}
}
size = (i ? b.type.GetVram() : b.type.GetWram());
if (size != (i ? profile.board.GetVram() : profile.board.GetWram()))
{
Profile::Board::Rams& rams = (i ? profile.board.vram : profile.board.wram);
rams.clear();
for (uint j=0; j < 2; ++j)
{
size = i ? (j ? b.type.GetNonSavableVram() : b.type.GetSavableVram()) :
(j ? b.type.GetNonSavableWram() : b.type.GetSavableWram());
if (size)
{
Profile::Board::Ram ram;
ram.size = size;
ram.battery = (i == 0 && j == 0 && b.wramBattery);
rams.push_back( ram );
}
}
}
Profile::Board::Roms& roms = (i ? profile.board.chr : profile.board.prg);
for (dword j=0, k=0, n=roms.size(); j < n; k += roms[j].size, ++j)
roms[j].hash.Compute( (i ? chr : prg).Mem(k), roms[j].size );
}
if (!readOnly)
{
Checksum checksum;
checksum.Compute( prg.Mem(), prg.Size() );
if (prgCrc)
*prgCrc = checksum.GetCrc();
checksum.Compute( chr.Mem(), chr.Size() );
profile.hash.Assign( checksum.GetSha1(), checksum.GetCrc() );
}
if (board)
*board = Boards::Board::Create( b );
return RESULT_OK;
}
void Cartridge::Reset(const bool hard)
{
board->Reset( hard );
if (vs)
vs->Reset( hard );
}
bool Cartridge::PowerOff()
{
try
{
if (board)
{
board->Sync( Boards::Board::EVENT_POWER_OFF, NULL );
board->Save( savefile );
}
return true;
}
catch (...)
{
return false;
}
}
void Cartridge::SaveState(State::Saver& state,const dword baseChunk) const
{
state.Begin( baseChunk );
board->SaveState( state, AsciiId<'M','P','R'>::V );
if (vs)
vs->SaveState( state, AsciiId<'V','S','S'>::V );
state.End();
}
void Cartridge::LoadState(State::Loader& state)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'M','P','R'>::V:
board->LoadState( state );
break;
case AsciiId<'V','S','S'>::V:
NST_VERIFY( vs );
if (vs)
vs->LoadState( state );
break;
}
state.End();
}
}
Region Cartridge::GetDesiredRegion() const
{
switch (profile.system.type)
{
case Profile::System::NES_PAL:
case Profile::System::NES_PAL_A:
case Profile::System::NES_PAL_B:
case Profile::System::DENDY:
return REGION_PAL;
case Profile::System::NES_NTSC:
case Profile::System::FAMICOM:
if (favoredSystem == FAVORED_DENDY)
return REGION_PAL;
default:
return REGION_NTSC;
}
}
System Cartridge::GetDesiredSystem(Region region,CpuModel* cpu,PpuModel* ppu) const
{
if (region == Cartridge::GetDesiredRegion())
{
if (favoredSystem == FAVORED_DENDY && region == REGION_PAL)
{
switch (profile.system.type)
{
case Profile::System::NES_NTSC:
case Profile::System::NES_PAL:
case Profile::System::NES_PAL_A:
case Profile::System::NES_PAL_B:
case Profile::System::FAMICOM:
case Profile::System::DENDY:
if (cpu)
*cpu = CPU_DENDY;
if (ppu)
*ppu = PPU_DENDY;
return SYSTEM_DENDY;
case Profile::System::VS_UNISYSTEM:
case Profile::System::VS_DUALSYSTEM:
case Profile::System::PLAYCHOICE_10:
default:
break;
}
}
if (cpu)
*cpu = static_cast<CpuModel>(profile.system.cpu);
if (ppu)
*ppu = static_cast<PpuModel>(profile.system.ppu);
return static_cast<System>(profile.system.type);
}
else
{
return Image::GetDesiredSystem( region, cpu, ppu );
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void Cartridge::BeginFrame(const Api::Input& input,Input::Controllers* controllers)
{
board->Sync( Boards::Board::EVENT_BEGIN_FRAME, controllers );
if (vs)
vs->BeginFrame( input, controllers );
}
void Cartridge::VSync()
{
board->Sync( Boards::Board::EVENT_END_FRAME, NULL );
if (vs)
vs->VSync();
}
}
}