mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
905 lines
21 KiB
C++
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
|
|
}
|
|
}
|