nestopia/source/core/NstCartridgeInes.cpp
2022-11-02 21:32:16 -06:00

905 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 <cstring>
#include "NstLog.hpp"
#include "NstPatcher.hpp"
#include "NstStream.hpp"
#include "NstChecksum.hpp"
#include "NstImageDatabase.hpp"
#include "NstCartridge.hpp"
#include "NstCartridgeInes.hpp"
namespace Nes
{
namespace Core
{
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
class Cartridge::Ines::Loader
{
bool Load(Ram&,dword);
enum TrainerSetup
{
TRAINER_NONE,
TRAINER_IGNORE,
TRAINER_READ
};
enum
{
VS_MAPPER_99 = 99,
VS_MAPPER_151 = 151,
FFE_MAPPER_6 = 6,
FFE_MAPPER_8 = 8,
FFE_MAPPER_17 = 17,
TRAINER_LENGTH = 0x200,
MIN_DB_SEARCH_STRIDE = SIZE_8K,
MAX_DB_SEARCH_LENGTH = SIZE_16K * 0xFFFUL + SIZE_8K * 0xFFFUL
};
Stream::In stream;
const FavoredSystem favoredSystem;
Profile& profile;
ProfileEx& profileEx;
Ram& prg;
Ram& chr;
const ImageDatabase* const database;
Patcher patcher;
public:
Loader
(
std::istream& stdStreamImage,
std::istream* const stdStreamPatch,
const bool patchBypassChecksum,
Result* const patchResult,
Ram& p,
Ram& c,
const FavoredSystem f,
Profile& r,
ProfileEx& x,
const ImageDatabase* const d
)
:
stream (&stdStreamImage),
favoredSystem (f),
profile (r),
profileEx (x),
prg (p),
chr (c),
database (d),
patcher (patchBypassChecksum)
{
NST_ASSERT( prg.Empty() && chr.Empty() );
if (stdStreamPatch)
*patchResult = patcher.Load( *stdStreamPatch, stdStreamImage );
profile = Profile();
profileEx = ProfileEx();
}
void Load()
{
const TrainerSetup trainerSetup = Collect();
if (!profile.patched)
{
if (const ImageDatabase::Entry entry = SearchDatabase( trainerSetup ))
{
entry.Fill( profile, patcher.Empty() );
profileEx.wramAuto = false;
}
}
prg.Set( profile.board.GetPrg() );
chr.Set( profile.board.GetChr() );
if (!profile.board.prg.empty())
{
for (Profile::Board::Pins::const_iterator it(profile.board.prg.front().pins.begin()), end(profile.board.prg.front().pins.end()); it != end; ++it)
prg.Pin(it->number) = it->function.c_str();
}
if (!profile.board.chr.empty())
{
for (Profile::Board::Pins::const_iterator it(profile.board.chr.front().pins.begin()), end(profile.board.chr.front().pins.end()); it != end; ++it)
chr.Pin(it->number) = it->function.c_str();
}
if (trainerSetup == TRAINER_READ)
{
profileEx.trainer.Set( TRAINER_LENGTH );
stream.Read( profileEx.trainer.Mem(), TRAINER_LENGTH );
}
else if (trainerSetup == TRAINER_IGNORE)
{
stream.Seek( TRAINER_LENGTH );
}
if (Load( prg, 16 ))
Log::Flush( "Ines: PRG-ROM was patched" NST_LINEBREAK );
if (Load( chr, 16 + prg.Size() ))
Log::Flush( "Ines: CHR-ROM was patched" NST_LINEBREAK );
}
private:
TrainerSetup Collect()
{
NST_COMPILE_ASSERT
(
Header::PPU_RP2C03B == 1 &&
Header::PPU_RP2C03G == 2 &&
Header::PPU_RP2C04_0001 == 3 &&
Header::PPU_RP2C04_0002 == 4 &&
Header::PPU_RP2C04_0003 == 5 &&
Header::PPU_RP2C04_0004 == 6 &&
Header::PPU_RC2C03B == 7 &&
Header::PPU_RC2C03C == 8 &&
Header::PPU_RC2C05_01 == 9 &&
Header::PPU_RC2C05_02 == 10 &&
Header::PPU_RC2C05_03 == 11 &&
Header::PPU_RC2C05_04 == 12 &&
Header::PPU_RC2C05_05 == 13
);
Header setup;
byte header[16];
stream.Read( header );
if (patcher.Patch( header, header, 16 ))
{
profile.patched = true;
Log::Flush( "Ines: header was patched" NST_LINEBREAK );
}
Result result = ReadHeader( setup, header, 16 );
if (NES_FAILED(result))
throw RESULT_ERR_CORRUPT_FILE;
Log log;
static const char title[] = "Ines: ";
if (setup.version)
{
log << title << "NES 2.0 (extended iNES)" NST_LINEBREAK;
}
if (result == RESULT_WARN_BAD_FILE_HEADER)
log << title << "warning, unknown or invalid header data!" NST_LINEBREAK;
log << title
<< (setup.prgRom / SIZE_1K)
<< "k PRG-ROM set" NST_LINEBREAK;
if (setup.version)
{
if (setup.prgRam)
{
log << title
<< (setup.prgRam % SIZE_1K ? setup.prgRam : setup.prgRam / SIZE_1K)
<< (setup.prgRam % SIZE_1K ? " byte" : "k")
<< " PRG-RAM set" NST_LINEBREAK;
}
if (setup.prgNvRam)
{
log << title
<< (setup.prgNvRam % SIZE_1K ? setup.prgNvRam : setup.prgNvRam / SIZE_1K)
<< (setup.prgNvRam % SIZE_1K ? " bytes" : "k")
<< " non-volatile PRG-RAM set" NST_LINEBREAK;
}
}
if (setup.chrRom)
{
log << title
<< (setup.chrRom / SIZE_1K)
<< "k CHR-ROM set" NST_LINEBREAK;
}
if (setup.version)
{
if (setup.chrRam)
{
profile.board.chrRam = setup.chrRam / SIZE_1K;
log << title
<< (setup.chrRam % SIZE_1K ? setup.chrRam : setup.chrRam / SIZE_1K)
<< (setup.chrRam % SIZE_1K ? " bytes" : "k")
<< " CHR-RAM set" NST_LINEBREAK;
}
if (setup.chrNvRam)
{
log << title
<< (setup.chrNvRam % SIZE_1K ? setup.chrNvRam : setup.chrNvRam / SIZE_1K)
<< (setup.chrNvRam % SIZE_1K ? " bytes" : "k")
<< " non-volatile CHR-RAM set" NST_LINEBREAK;
}
}
else
{
if (header[8])
{
log << title
<< (header[8] * 8U)
<< "k W-RAM set" NST_LINEBREAK;
}
if (header[6] & 0x2U)
{
log << title
<< "battery set" NST_LINEBREAK;
}
}
log << title <<
(
setup.mirroring == Header::MIRRORING_FOURSCREEN ? "four-screen" :
setup.mirroring == Header::MIRRORING_VERTICAL ? "vertical" :
"horizontal"
) << " mirroring set" NST_LINEBREAK;
log << title <<
(
setup.region == Header::REGION_BOTH ? "NTSC/PAL" :
setup.region == Header::REGION_PAL ? "PAL":
"NTSC"
) << " set" NST_LINEBREAK;
if (setup.system == Header::SYSTEM_VS)
{
log << title << "VS System set" NST_LINEBREAK;
if (setup.version)
{
if (setup.ppu)
{
static cstring const names[] =
{
"RP2C03B",
"RP2C03G",
"RP2C04-0001",
"RP2C04-0002",
"RP2C04-0003",
"RP2C04-0004",
"RC2C03B",
"RC2C03C",
"RC2C05-01",
"RC2C05-02",
"RC2C05-03",
"RC2C05-04",
"RC2C05-05"
};
NST_ASSERT( setup.ppu < 1+sizeof(array(names)) );
log << title << names[setup.ppu-1] << " PPU set" NST_LINEBREAK;
}
if (setup.security)
{
static const cstring names[] =
{
"RBI Baseball",
"TKO Boxing",
"Super Xevious"
};
NST_ASSERT( setup.security < 1+sizeof(array(names)) );
log << title << names[setup.security-1] << " VS mode set" NST_LINEBREAK;
}
}
}
else if (setup.system == Header::SYSTEM_PC10)
{
log << title << "PlayChoice-10 set" NST_LINEBREAK;
}
log << title << "mapper " << setup.mapper << " set";
if (setup.system != Header::SYSTEM_VS && (setup.mapper == VS_MAPPER_99 || setup.mapper == VS_MAPPER_151))
{
setup.system = Header::SYSTEM_VS;
setup.ppu = Header::PPU_RP2C03B;
log << ", forcing VS System";
}
log << NST_LINEBREAK;
if (setup.version && setup.subMapper)
log << title << "submapper " << setup.subMapper << " set" NST_LINEBREAK;
TrainerSetup trainerSetup;
if (!setup.trainer)
{
trainerSetup = TRAINER_NONE;
}
else if (setup.mapper == FFE_MAPPER_6 || setup.mapper == FFE_MAPPER_8 || setup.mapper == FFE_MAPPER_17)
{
trainerSetup = TRAINER_READ;
log << title << "trainer set" NST_LINEBREAK;
if (setup.prgRam + setup.prgNvRam < SIZE_8K)
{
setup.prgRam = SIZE_8K - setup.prgNvRam;
log << title << "warning, forcing 8k of W-RAM for trainer" NST_LINEBREAK;
}
}
else
{
trainerSetup = TRAINER_IGNORE;
log << title << "warning, trainer ignored" NST_LINEBREAK;
}
if (setup.prgRom)
{
Profile::Board::Rom rom;
rom.size = setup.prgRom;
profile.board.prg.push_back( rom );
}
if (setup.chrRom)
{
Profile::Board::Rom rom;
rom.size = setup.chrRom;
profile.board.chr.push_back( rom );
}
if (setup.prgNvRam)
{
Profile::Board::Ram ram;
ram.size = setup.prgNvRam;
ram.battery = true;
profile.board.wram.push_back( ram );
}
if (setup.prgRam)
{
Profile::Board::Ram ram;
ram.size = setup.prgRam;
profile.board.wram.push_back( ram );
}
if (setup.chrNvRam)
{
Profile::Board::Ram ram;
ram.size = setup.chrNvRam;
ram.battery = true;
profile.board.vram.push_back( ram );
}
if (setup.chrRam)
{
Profile::Board::Ram ram;
ram.size = setup.chrRam;
profile.board.vram.push_back( ram );
}
profile.board.mapper = setup.mapper;
profile.board.subMapper = setup.subMapper;
profileEx.wramAuto = (setup.version == 0 && profile.board.wram.empty());
switch (setup.mirroring)
{
case Header::MIRRORING_HORIZONTAL:
profile.board.solderPads = Profile::Board::SOLDERPAD_V;
break;
case Header::MIRRORING_VERTICAL:
profile.board.solderPads = Profile::Board::SOLDERPAD_H;
break;
case Header::MIRRORING_FOURSCREEN:
profileEx.nmt = ProfileEx::NMT_FOURSCREEN;
break;
case Header::MIRRORING_SINGLESCREEN:
case Header::MIRRORING_CONTROLLED:
default:
break;
}
profile.system.cpu = Profile::System::CPU_RP2A03;
profile.system.ppu = static_cast<Profile::System::Ppu>(setup.ppu);
switch (setup.system)
{
case Header::SYSTEM_VS:
profile.system.type = Profile::System::VS_UNISYSTEM;
break;
case Header::SYSTEM_PC10:
profile.system.type = Profile::System::PLAYCHOICE_10;
break;
default:
switch (setup.region)
{
case Header::REGION_NTSC:
if (favoredSystem == FAVORED_FAMICOM)
{
profile.system.type = Profile::System::FAMICOM;
}
else if (favoredSystem == FAVORED_DENDY)
{
profile.system.type = Profile::System::DENDY;
profile.system.ppu = Profile::System::PPU_DENDY;
profile.system.cpu = Profile::System::CPU_DENDY;
}
else
{
profile.system.type = Profile::System::NES_NTSC;
}
break;
default:
profile.multiRegion = true;
if (favoredSystem == FAVORED_FAMICOM)
{
profile.system.type = Profile::System::FAMICOM;
break;
}
else if (favoredSystem != FAVORED_NES_PAL && favoredSystem != FAVORED_DENDY)
{
profile.system.type = Profile::System::NES_NTSC;
break;
}
case Header::REGION_PAL:
if (favoredSystem == FAVORED_DENDY)
{
profile.system.type = Profile::System::DENDY;
profile.system.cpu = Profile::System::CPU_DENDY;
profile.system.ppu = Profile::System::PPU_DENDY;
}
else
{
profile.system.type = Profile::System::NES_PAL;
profile.system.cpu = Profile::System::CPU_RP2A07;
profile.system.ppu = Profile::System::PPU_RP2C07;
}
break;
}
break;
}
return trainerSetup;
}
ImageDatabase::Entry SearchDatabase(const TrainerSetup trainerSetup)
{
ImageDatabase::Entry entry;
if (database && database->Enabled())
{
if (trainerSetup != TRAINER_NONE)
stream.Seek( TRAINER_LENGTH );
const dword romLength = profile.board.GetPrg() + profile.board.GetChr();
dword count = 0;
for (Checksum it, checksum;;)
{
const uint data = stream.SafeRead8();
if (data <= 0xFF)
{
const byte in = data;
it.Compute( &in, 1 );
if (++count % MIN_DB_SEARCH_STRIDE == 0)
checksum = it;
}
const bool stop = (data > 0xFF || count == MAX_DB_SEARCH_LENGTH);
if (stop || count == romLength)
{
entry = database->Search( Profile::Hash(checksum.GetSha1(),checksum.GetCrc()), favoredSystem );
if (stop || entry)
break;
}
}
if (count)
stream.Seek( -idword(count) + (trainerSetup == TRAINER_NONE ? 0 : -TRAINER_LENGTH) );
}
return entry;
}
};
bool Cartridge::Ines::Loader::Load(Ram& rom,const dword offset)
{
if (rom.Size())
{
if (patcher.Empty())
{
stream.Read( rom.Mem(), rom.Size() );
}
else
{
dword size = stream.Length();
NST_VERIFY( size >= rom.Size() );
if (size > rom.Size())
size = rom.Size();
if (size)
stream.Read( rom.Mem(), size );
if (patcher.Patch( rom.Mem(), rom.Mem(), rom.Size(), offset ))
{
profile.patched = true;
return true;
}
}
}
return false;
}
void Cartridge::Ines::Load
(
std::istream& stdStreamImage,
std::istream* const stdStreamPatch,
const bool patchBypassChecksum,
Result* const patchResult,
Ram& prg,
Ram& chr,
const FavoredSystem favoredSystem,
Profile& profile,
ProfileEx& profileEx,
const ImageDatabase* const database
)
{
Loader loader
(
stdStreamImage,
stdStreamPatch,
patchBypassChecksum,
patchResult,
prg,
chr,
favoredSystem,
profile,
profileEx,
database
);
loader.Load();
}
Result Cartridge::Ines::ReadHeader(Header& setup,const byte* const file,const ulong length)
{
if (file == NULL)
return RESULT_ERR_INVALID_PARAM;
if
(
length < 4 ||
file[0] != Ascii<'N'>::V ||
file[1] != Ascii<'E'>::V ||
file[2] != Ascii<'S'>::V ||
file[3] != 0x1A
)
return RESULT_ERR_INVALID_FILE;
if (length < 16)
return RESULT_ERR_CORRUPT_FILE;
byte header[16];
std::memcpy( header, file, 16 );
Result result = RESULT_OK;
setup.version = ((header[7] & 0xCU) == 0x8 ? 2 : 0);
if (!setup.version)
{
for (uint i=10; i < 16; ++i)
{
if (header[i])
{
header[7] = 0;
header[8] = 0;
header[9] = 0;
result = RESULT_WARN_BAD_FILE_HEADER;
break;
}
}
}
setup.prgRom = header[4];
setup.chrRom = header[5];
if (setup.version)
{
setup.prgRom |= uint(header[9]) << 8 & 0xF00;
setup.chrRom |= uint(header[9]) << 4 & 0xF00;
}
setup.prgRom *= SIZE_16K;
setup.chrRom *= SIZE_8K;
setup.trainer = bool(header[6] & 0x4U);
setup.mapper = (header[6] >> 4) | (header[7] & 0xF0U);
setup.subMapper = 0;
if (setup.version)
{
setup.mapper |= uint(header[8]) << 8 & 0x300;
setup.subMapper = header[8] >> 4;
}
if (header[6] & 0x8U)
{
setup.mirroring = Header::MIRRORING_FOURSCREEN;
}
else if (header[6] & 0x1U)
{
setup.mirroring = Header::MIRRORING_VERTICAL;
}
else
{
setup.mirroring = Header::MIRRORING_HORIZONTAL;
}
setup.security = 0;
if (header[7] & 0x1U)
{
setup.system = Header::SYSTEM_VS;
setup.ppu = Header::PPU_RP2C03B;
if (setup.version)
{
if ((header[13] & 0xFU) < 13)
setup.ppu = static_cast<Header::Ppu>((header[13] & 0xFU) + 1);
if ((header[13] >> 4) < 4)
setup.security = header[13] >> 4;
}
}
else if (setup.version && (header[7] & 0x2U))
{
setup.system = Header::SYSTEM_PC10;
setup.ppu = Header::PPU_RP2C03B;
}
else
{
setup.system = Header::SYSTEM_CONSOLE;
setup.ppu = Header::PPU_RP2C02;
}
if (setup.version && (header[12] & 0x2U))
{
setup.region = Header::REGION_BOTH;
}
else if (header[setup.version ? 12 : 9] & 0x1U)
{
if (setup.system == Header::SYSTEM_CONSOLE)
{
setup.region = Header::REGION_PAL;
setup.ppu = Header::PPU_RP2C07;
}
else
{
setup.region = Header::REGION_NTSC;
}
}
else
{
setup.region = Header::REGION_NTSC;
}
if (setup.version)
{
setup.prgRam = ((header[10]) & 0xFU) - 1U < 14 ? 64UL << (header[10] & 0xFU) : 0;
setup.prgNvRam = ((header[10]) >> 4) - 1U < 14 ? 64UL << (header[10] >> 4) : 0;
setup.chrRam = ((header[11]) & 0xFU) - 1U < 14 ? 64UL << (header[11] & 0xFU) : 0;
setup.chrNvRam = ((header[11]) >> 4) - 1U < 14 ? 64UL << (header[11] >> 4) : 0;
}
else
{
setup.prgRam = ((header[6] & 0x2U) ? 0 : header[8] * dword(SIZE_8K));
setup.prgNvRam = ((header[6] & 0x2U) ? NST_MAX(header[8],1U) * dword(SIZE_8K) : 0);
setup.chrRam = (setup.chrRom ? 0 : SIZE_8K);
setup.chrNvRam = 0;
}
return result;
}
Result Cartridge::Ines::WriteHeader(const Header& setup,byte* const file,const ulong length)
{
if
(
(file == NULL || length < 16) ||
(setup.prgRom > (setup.version ? 0xFFFUL * SIZE_16K : 0xFFUL * SIZE_16K)) ||
(setup.chrRom > (setup.version ? 0xFFFUL * SIZE_8K : 0xFFUL * SIZE_8K)) ||
(setup.mapper > (setup.version ? 0x1FF : 0xFF)) ||
(setup.version && setup.subMapper > 0xF)
)
return RESULT_ERR_INVALID_PARAM;
byte header[16] =
{
Ascii<'N'>::V,
Ascii<'E'>::V,
Ascii<'S'>::V,
0x1A,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
};
if (setup.version)
header[7] |= 0x8U;
header[4] = (setup.prgRom / SIZE_16K) & 0xFF;
header[5] = (setup.chrRom / SIZE_8K) & 0xFF;
if (setup.version)
{
header[9] |= (setup.prgRom / SIZE_16K) >> 8;
header[9] |= (setup.chrRom / SIZE_8K) >> 4 & 0xF0;
}
if (setup.mirroring == Header::MIRRORING_FOURSCREEN)
{
header[6] |= 0x8U;
}
else if (setup.mirroring == Header::MIRRORING_VERTICAL)
{
header[6] |= 0x1U;
}
if (setup.prgNvRam)
header[6] |= 0x2U;
if (setup.trainer)
header[6] |= 0x4U;
if (setup.system == Header::SYSTEM_VS)
{
header[7] |= 0x1U;
}
else if (setup.version && setup.system == Header::SYSTEM_PC10)
{
header[7] |= 0x2U;
}
header[6] |= setup.mapper << 4 & 0xF0U;
header[7] |= setup.mapper & 0xF0U;
if (setup.version)
{
header[8] |= setup.mapper >> 8;
header[8] |= setup.subMapper << 4;
uint i, data;
for (i=0, data=setup.prgRam >> 7; data; data >>= 1, ++i)
{
if (i > 0xF)
return RESULT_ERR_INVALID_PARAM;
}
header[10] |= i;
for (i=0, data=setup.prgNvRam >> 7; data; data >>= 1, ++i)
{
if (i > 0xF)
return RESULT_ERR_INVALID_PARAM;
}
header[10] |= i << 4;
for (i=0, data=setup.chrRam >> 7; data; data >>= 1, ++i)
{
if (i > 0xF)
return RESULT_ERR_INVALID_PARAM;
}
header[11] |= i;
for (i=0, data=setup.chrNvRam >> 7; data; data >>= 1, ++i)
{
if (i > 0xF)
return RESULT_ERR_INVALID_PARAM;
}
header[11] |= i << 4;
if (setup.region == Header::REGION_BOTH)
{
header[12] |= 0x2U;
}
else if (setup.region == Header::REGION_PAL)
{
header[12] |= 0x1U;
}
if (setup.system == Header::SYSTEM_VS)
{
if (setup.ppu > 0xF || setup.security > 0xF)
return RESULT_ERR_INVALID_PARAM;
if (setup.ppu)
header[13] = setup.ppu - 1;
header[13] |= setup.security << 4;
}
}
else
{
header[8] = (setup.prgRam + setup.prgNvRam) / SIZE_8K;
header[9] = (setup.region == Header::REGION_PAL ? 0x1 : 0x0);
}
std::memcpy( file, header, 16 );
return RESULT_OK;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
}
}