nestopia/source/core/NstFds.cpp
2012-09-02 13:13:55 -04:00

2056 lines
43 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 <new>
#include "NstLog.hpp"
#include "NstCrc32.hpp"
#include "NstState.hpp"
#include "NstFds.hpp"
#include "api/NstApiInput.hpp"
namespace Nes
{
namespace Core
{
const byte Fds::Sound::Modulator::steps[8] =
{
0x00,
0x01,
0x02,
0x04,
0x80,
0xFC,
0xFE,
0xFF
};
const byte Fds::Sound::volumes[4] =
{
30 * 8,
20 * 8,
15 * 8,
12 * 8
};
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
class Fds::Bios
{
public:
NES_DECL_PEEK( Rom );
NES_DECL_POKE( Nop );
private:
enum
{
FAMICOM_ID = 0x5E607DCF,
TWINSYSTEM_ID = 0x4DF24A6C
};
byte rom[SIZE_8K];
bool available;
public:
Bios()
: available(false)
{
}
void Set(std::istream* const stdStream)
{
available = false;
if (stdStream)
{
Stream::In(stdStream).Read( rom, SIZE_8K );
available = true;
if (Log::Available())
{
switch (Crc32::Compute( rom, SIZE_8K ))
{
case FAMICOM_ID:
case TWINSYSTEM_ID:
Log::Flush( "Fds: BIOS ROM ok" NST_LINEBREAK );
break;
default:
Log::Flush( "Fds: warning, unknown BIOS ROM!" NST_LINEBREAK );
break;
}
}
}
}
Result Get(std::ostream& stream) const
{
if (available)
{
Stream::Out(&stream).Write( rom, SIZE_8K );
return RESULT_OK;
}
else
{
return RESULT_ERR_NOT_READY;
}
}
bool Available() const
{
return available;
}
};
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
Fds::Bios Fds::bios;
NES_PEEK_A(Fds::Bios,Rom)
{
return rom[address - 0xE000];
}
NES_POKE(Fds::Bios,Nop)
{
}
inline byte* Fds::Disks::Sides::operator [] (uint i) const
{
NST_ASSERT( i < count );
return data + i * dword(SIDE_SIZE);
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
inline void Fds::Adapter::Mount(byte* io,bool protect)
{
unit.drive.Mount( io, protect );
}
Fds::Fds(Context& context)
:
Image (DISK),
disks (context.stream),
adapter (context.cpu,disks.sides),
cpu (context.cpu),
ppu (context.ppu),
sound (context.apu)
{
if (!bios.Available())
throw RESULT_ERR_MISSING_BIOS;
if (context.patch && context.patchResult)
*context.patchResult = RESULT_ERR_UNSUPPORTED;
ppu.GetChrMem().Source().Set( Core::Ram::RAM, true, true, SIZE_8K );
}
Fds::~Fds()
{
EjectDisk();
if (!disks.writeProtected)
disks.sides.Save();
}
void Fds::Reset(const bool hard)
{
disks.mounting = 0;
adapter.Reset
(
cpu,
disks.current == Disks::EJECTED ? NULL : disks.sides[disks.current],
disks.writeProtected
);
if (hard)
{
ram.Reset();
ppu.GetChrMem().Source().Fill( 0x00 );
ppu.GetChrMem().SwapBank<SIZE_8K,0x0000>( 0 );
}
cpu.Map( 0x4023 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4023 );
cpu.Map( 0x4025 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4025 );
cpu.Map( 0x4026 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4026 );
cpu.Map( 0x4031 ).Set( this, &Fds::Peek_4031, &Fds::Poke_Nop );
cpu.Map( 0x4033 ).Set( this, &Fds::Peek_4033, &Fds::Poke_Nop );
cpu.Map( 0x4040, 0x407F ).Set( this, &Fds::Peek_4040, &Fds::Poke_4040 );
cpu.Map( 0x4080 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4080 );
cpu.Map( 0x4082 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4082 );
cpu.Map( 0x4083 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4083 );
cpu.Map( 0x4084 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4084 );
cpu.Map( 0x4085 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4085 );
cpu.Map( 0x4086 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4086 );
cpu.Map( 0x4087 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4087 );
cpu.Map( 0x4088 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4088 );
cpu.Map( 0x4089 ).Set( this, &Fds::Peek_Nop, &Fds::Poke_4089 );
cpu.Map( 0x408A ).Set( this, &Fds::Peek_Nop, &Fds::Poke_408A );
cpu.Map( 0x4090 ).Set( this, &Fds::Peek_4090, &Fds::Poke_Nop );
cpu.Map( 0x4092 ).Set( this, &Fds::Peek_4092, &Fds::Poke_Nop );
cpu.Map( 0x6000, 0xDFFF ).Set( &ram, &Fds::Ram::Peek_Ram, &Fds::Ram::Poke_Ram );
cpu.Map( 0xE000, 0xFFFF ).Set( &bios, &Bios::Peek_Rom, &Bios::Poke_Nop );
}
bool Fds::PowerOff()
{
if (io.led != Api::Fds::MOTOR_OFF)
{
io.led = Api::Fds::MOTOR_OFF;
Api::Fds::driveCallback( Api::Fds::MOTOR_OFF );
}
return true;
}
void Fds::SetBios(std::istream* stream)
{
bios.Set( stream );
}
Result Fds::GetBios(std::ostream& stream)
{
return bios.Get( stream );
}
bool Fds::HasBios()
{
return bios.Available();
}
Region Fds::GetDesiredRegion() const
{
return REGION_NTSC;
}
System Fds::GetDesiredSystem(Region region,CpuModel* cpu,PpuModel* ppu) const
{
if (region == REGION_NTSC)
{
if (cpu)
*cpu = CPU_RP2A03;
if (ppu)
*ppu = PPU_RP2C02;
return SYSTEM_FAMICOM;
}
else
{
return Image::GetDesiredSystem( region, cpu, ppu );
}
}
uint Fds::GetDesiredController(const uint port) const
{
if (port == Api::Input::EXPANSION_PORT)
return (disks.id == DOREMIKKO_ID) ? Api::Input::DOREMIKKOKEYBOARD : Api::Input::UNCONNECTED;
else
return Image::GetDesiredController( port );
}
uint Fds::GetDesiredAdapter() const
{
return Api::Input::ADAPTER_FAMICOM;
}
Result Fds::InsertDisk(uint disk,const uint side)
{
NST_VERIFY( disks.sides.count );
if (side < 2)
{
disk = (disk * 2) + side;
if (disk < disks.sides.count)
{
if (disks.current != disk)
{
const uint prev = disks.current;
disks.current = disk;
disks.mounting = Disks::MOUNTING;
adapter.Mount( NULL );
if (prev != Disks::EJECTED)
Api::Fds::diskCallback( Api::Fds::DISK_EJECT, prev / 2, prev % 2 );
Api::Fds::diskCallback( Api::Fds::DISK_INSERT, disk / 2, disk % 2 );
return RESULT_OK;
}
return RESULT_NOP;
}
}
return RESULT_ERR_INVALID_PARAM;
}
Result Fds::EjectDisk()
{
if (disks.current != Disks::EJECTED)
{
const uint prev = disks.current;
disks.current = Disks::EJECTED;
disks.mounting = 0;
adapter.Mount( NULL );
Api::Fds::diskCallback( Api::Fds::DISK_EJECT, prev / 2, prev % 2 );
return RESULT_OK;
}
return RESULT_NOP;
}
Result Fds::GetDiskData(uint side,Api::Fds::DiskData& data) const
{
if (side < disks.sides.count)
return Unit::Drive::Analyze( disks.sides[side], data );
return RESULT_ERR_INVALID_PARAM;
}
void Fds::LoadState(State::Loader& state)
{
uint saveDisks[3] = {~0U,~0U,~0U};
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'I','O'>::V:
{
State::Loader::Data<4> data( state );
io.ctrl = data[0];
io.port = data[1];
break;
}
case AsciiId<'R','A','M'>::V:
state.Uncompress( ram.mem );
break;
case AsciiId<'C','H','R'>::V:
state.Uncompress( ppu.GetChrMem().Source().Mem(), SIZE_8K );
break;
case AsciiId<'I','R','Q'>::V:
case AsciiId<'D','R','V'>::V:
adapter.LoadState( state, chunk, ppu );
break;
case AsciiId<'D','S','K'>::V:
{
State::Loader::Data<4> data( state );
if (data[0] != disks.sides.count)
throw RESULT_ERR_INVALID_FILE;
saveDisks[0] = data[1];
saveDisks[1] = data[2];
saveDisks[2] = data[3];
break;
}
case AsciiId<'S','N','D'>::V:
sound.LoadState( state );
break;
default:
for (uint i=0; i < disks.sides.count; ++i)
{
if (chunk == AsciiId<'D','0','A'>::R( 0, i / 2, i % 2 ))
{
byte* const data = disks.sides[i];
state.Uncompress( data, SIDE_SIZE );
for (uint j=0; j < SIDE_SIZE; ++j)
data[j] ^= 0xFFU;
break;
}
}
break;
}
state.End();
}
disks.mounting = 0;
if (saveDisks[0] != ~0U)
{
disks.writeProtected = saveDisks[0] & 0x2U;
if (saveDisks[0] & 0x1U)
{
if (NES_FAILED(InsertDisk( saveDisks[1] / 2, saveDisks[1] % 2 )))
throw RESULT_ERR_CORRUPT_FILE;
disks.mounting = saveDisks[2];
}
else
{
EjectDisk();
}
}
adapter.Mount
(
disks.current != Disks::EJECTED && !disks.mounting ? disks.sides[disks.current] : NULL,
disks.writeProtected
);
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void Fds::SaveState(State::Saver& state,const dword baseChunk) const
{
state.Begin( baseChunk );
{
const byte data[4] =
{
io.ctrl,
io.port,
0,
0
};
state.Begin( AsciiId<'I','O'>::V ).Write( data ).End();
}
adapter.SaveState( state );
state.Begin( AsciiId<'R','A','M'>::V ).Compress( ram.mem ).End();
state.Begin( AsciiId<'C','H','R'>::V ).Compress( ppu.GetChrMem().Source().Mem(), SIZE_8K ).End();
{
const byte data[4] =
{
disks.sides.count,
(disks.current != Disks::EJECTED) | (disks.writeProtected ? 0x2U : 0x0U),
disks.current != Disks::EJECTED ? disks.current : 0xFF,
disks.current != Disks::EJECTED ? disks.mounting : 0
};
state.Begin( AsciiId<'D','S','K'>::V ).Write( data ).End();
}
bool saveData = true;
if (state.Internal())
{
Checksum recentChecksum;
for (uint i=0; i < disks.sides.count; ++i)
recentChecksum.Compute( disks.sides[i], SIDE_SIZE );
if (checksum == recentChecksum)
saveData = false;
else
checksum = recentChecksum;
}
if (saveData)
{
struct Dst
{
byte* const NST_RESTRICT mem;
Dst() : mem(new byte [SIDE_SIZE]) {}
~Dst() { delete [] mem; }
};
Dst dst;
for (uint i=0; i < disks.sides.count; ++i)
{
const byte* const NST_RESTRICT src = disks.sides[i];
for (uint j=0; j < SIDE_SIZE; ++j)
dst.mem[j] = src[j] ^ 0xFFU;
state.Begin( AsciiId<'D','0','A'>::R( 0, i / 2, i % 2 ) ).Compress( dst.mem, SIDE_SIZE ).End();
}
}
sound.SaveState( state, AsciiId<'S','N','D'>::V );
state.End();
}
NES_PEEK(Fds,Nop)
{
return OPEN_BUS;
}
NES_POKE(Fds,Nop)
{
}
NES_POKE_D(Fds,4023)
{
io.ctrl = data;
}
NES_POKE_D(Fds,4026)
{
io.port = data;
}
NES_PEEK(Fds,4033)
{
NST_VERIFY( io.port & Io::BATTERY_CHARGED );
return io.port & Io::BATTERY_CHARGED;
}
NES_PEEK_A(Fds,4040)
{
return sound.ReadWaveData( address );
}
NES_POKE_AD(Fds,4040)
{
sound.WriteWaveData( address, data );
}
NES_POKE_D(Fds,4080)
{
sound.WriteReg0( data );
}
NES_POKE_D(Fds,4082)
{
sound.WriteReg1( data );
}
NES_POKE_D(Fds,4083)
{
sound.WriteReg2( data );
}
NES_POKE_D(Fds,4084)
{
sound.WriteReg3( data );
}
NES_POKE_D(Fds,4085)
{
sound.WriteReg4( data );
}
NES_POKE_D(Fds,4086)
{
sound.WriteReg5( data );
}
NES_POKE_D(Fds,4087)
{
sound.WriteReg6( data );
}
NES_POKE_D(Fds,4088)
{
sound.WriteReg7( data );
}
NES_POKE_D(Fds,4089)
{
sound.WriteReg8( data );
}
NES_POKE_D(Fds,408A)
{
sound.WriteReg9( data );
}
NES_PEEK(Fds,4090)
{
return sound.ReadVolumeGain();
}
NES_PEEK(Fds,4092)
{
return sound.ReadSweepGain();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Fds::Disks::Sides::Sides(std::istream& stdStream)
{
Stream::In stream( &stdStream );
dword size;
uint header;
switch (stream.Read32())
{
case FDS_ID:
{
size = stream.Read8();
stream.Seek( -5 );
header = HEADER_SIZE;
break;
}
case FDS_RAW_ID:
{
stream.Seek( -4 );
for (size=0; size < 0xFF && !stream.Eof(); ++size)
stream.Seek( SIDE_SIZE );
stream.Seek( -idword(size * SIDE_SIZE) );
header = 0;
break;
}
default: throw RESULT_ERR_INVALID_FILE;
}
if (!size)
throw RESULT_ERR_CORRUPT_FILE;
count = size;
size *= SIDE_SIZE;
data = new byte [HEADER_SIZE + size];
std::memset( data, 0, HEADER_SIZE );
data += HEADER_SIZE;
try
{
stream.Read( data - header, header + size );
file.Load( data - header, header + size, File::DISK );
}
catch (...)
{
delete [] (data - HEADER_SIZE);
throw;
}
}
Fds::Disks::Sides::~Sides()
{
delete [] (data - HEADER_SIZE);
}
void Fds::Disks::Sides::Save() const
{
try
{
const uint header = HasHeader() ? HEADER_SIZE : 0;
file.Save( File::DISK, data - header, header + count * dword(SIDE_SIZE) );
}
catch (...)
{
NST_DEBUG_MSG("fds save failure!");
}
}
Fds::Disks::Disks(std::istream& stream)
:
sides (stream),
crc (Crc32::Compute( sides[0], sides.count * dword(SIDE_SIZE) )),
id (dword(sides[0][0x0F]) << 24 | dword(sides[0][0x10]) << 16 | uint(sides[0][0x11]) << 8 | sides[0][0x12]),
current (EJECTED),
mounting (0),
writeProtected (false)
{
if (Log::Available())
{
Log log;
for (uint i=0; i < sides.count; ++i)
{
Api::Fds::DiskData data;
if (NES_SUCCEEDED(Unit::Drive::Analyze( sides[i], data )))
{
dword disksize = 0;
for (Api::Fds::DiskData::Files::const_iterator it(data.files.begin()), end(data.files.end()); it != end; ++it)
disksize += it->data.size();
log << "Fds: Disk "
<< (1+i/2)
<< (i % 2 ? " Side B: " : " Side A: ")
<< (disksize / SIZE_1K)
<< "k in "
<< data.files.size()
<< " files";
if (const uint raw = data.raw.size())
log << ", " << raw << "b trailing data";
log << ".." NST_LINEBREAK;
for (Api::Fds::DiskData::Files::const_iterator it(data.files.begin()), end(data.files.end()); it != end; ++it)
{
log << "Fds: file: \"" << it->name
<< "\", id: " << it->id
<< ", size: " << it->data.size()
<< ", index: " << it->index
<< ", address: " << Log::Hex( 16, it->address )
<< ", type: "
<<
(
it->type == Api::Fds::DiskData::File::TYPE_PRG ? "PRG" NST_LINEBREAK :
it->type == Api::Fds::DiskData::File::TYPE_CHR ? "CHR" NST_LINEBREAK :
it->type == Api::Fds::DiskData::File::TYPE_NMT ? "NMT" NST_LINEBREAK :
"unknown" NST_LINEBREAK
);
}
}
}
}
}
Fds::Unit::Timer::Timer()
{
Reset();
}
void Fds::Unit::Timer::Reset()
{
ctrl = 0;
count = 0;
latch = 0;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void Fds::Unit::Timer::Advance(uint& timer)
{
timer |= STATUS_PENDING_IRQ;
if (ctrl & CTRL_REPEAT)
count = latch;
else
ctrl &= ~uint(CTRL_ENABLED);
}
NST_SINGLE_CALL bool Fds::Unit::Timer::Clock()
{
return !(ctrl & CTRL_ENABLED) || !count || --count;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Fds::Unit::Drive::Drive(const Disks::Sides& s)
: sides(s)
{
Reset();
}
void Fds::Unit::Drive::Reset()
{
count = 0;
headPos = 0;
dataPos = 0;
gap = 0;
io = NULL;
ctrl = 0;
length = 0;
in = 0;
out = 0;
status = STATUS_EJECTED|STATUS_UNREADY|STATUS_PROTECTED|OPEN_BUS;
}
void Fds::Unit::Drive::Mount(byte* data,bool protect)
{
io = data;
if (data)
{
status &= ~uint(STATUS_EJECTED|STATUS_PROTECTED);
if (protect)
status |= uint(STATUS_PROTECTED);
}
else
{
count = 0;
status |= uint(STATUS_EJECTED|STATUS_PROTECTED|STATUS_UNREADY);
}
}
Result Fds::Unit::Drive::Analyze(const byte* NST_RESTRICT src,Api::Fds::DiskData& dst)
{
try
{
idword i = SIDE_SIZE;
for (uint block=~0U, files=0; i; )
{
const uint prev = block;
block = src[0];
if (block == BLOCK_VOLUME)
{
i -= LENGTH_VOLUME+1;
if (i < 0 || prev != ~0U)
break;
src += LENGTH_VOLUME+1;
}
else if (block == BLOCK_COUNT)
{
i -= LENGTH_COUNT+1;
if (i < 0 || prev != BLOCK_VOLUME)
break;
files = src[1];
src += LENGTH_COUNT+1;
}
else if (block == BLOCK_HEADER)
{
i -= LENGTH_HEADER+1;
if (i < 0 || (prev != BLOCK_DATA && prev != BLOCK_COUNT) || !files)
break;
dst.files.push_back( Api::Fds::DiskData::File() );
Api::Fds::DiskData::File& file = dst.files.back();
file.index = src[1];
file.id = src[2];
Stream::In::AsciiToC( file.name, src+3, 8 );
for (uint j=8; j < sizeof(array(file.name)); ++j)
file.name[j] = '\0';
file.address = src[11] | uint(src[12]) << 8;
switch (src[15])
{
case 0: file.type = Api::Fds::DiskData::File::TYPE_PRG; break;
case 1: file.type = Api::Fds::DiskData::File::TYPE_CHR; break;
case 2: file.type = Api::Fds::DiskData::File::TYPE_NMT; break;
default: file.type = Api::Fds::DiskData::File::TYPE_UNKNOWN; break;
}
file.data.resize( src[13] | uint(src[14]) << 8 );
if (const dword size = file.data.size())
std::memset( &file.data.front(), 0x00, size );
src += LENGTH_HEADER+1;
}
else if (block == BLOCK_DATA)
{
if (prev != BLOCK_HEADER)
break;
Api::Fds::DiskData::Data& data = dst.files.back().data;
const idword size = data.size();
i -= size+1;
if (i < 0)
break;
++src;
if (size)
{
std::memcpy( &data.front(), src, size );
src += size;
}
NST_ASSERT( files );
if (!--files)
break;
}
else
{
break;
}
}
for (idword j=i; j-- > 0; )
{
if (src[j])
{
dst.raw.assign( src, src+j+1 );
break;
}
}
return i >= 0 ? RESULT_OK : RESULT_WARN_BAD_DUMP;
}
catch (const std::bad_alloc&)
{
return RESULT_ERR_OUT_OF_MEMORY;
}
catch (...)
{
return RESULT_ERR_GENERIC;
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_SINGLE_CALL void Fds::Unit::Drive::Write(uint reg)
{
ctrl = reg;
if (!(reg & CTRL_ON))
{
count = 0;
status |= uint(STATUS_UNREADY);
}
else if (!(reg & CTRL_STOP | count) && io)
{
count = CLK_MOTOR;
headPos = 0;
}
}
ibool Fds::Unit::Drive::Advance(uint& timer)
{
NST_ASSERT( io && !count );
if (headPos-1U < MAX_SIDE_SIZE && dataPos < SIDE_SIZE)
{
NST_VERIFY( !(status & uint(STATUS_UNREADY)) );
++headPos;
byte* stream = io + dataPos;
count = CLK_BYTE;
NST_VERIFY( ctrl & uint(CTRL_READ_MODE) || length != LENGTH_UNKNOWN );
if (ctrl & uint(CTRL_READ_MODE))
{
if (!gap)
{
if (length == LENGTH_UNKNOWN)
{
// Non-standard file layout which cannot accurately
// be emulated within the FDS file format since it
// removes the CRC value at the end of each block.
// No choice but to fall back on the BIOS.
in = *stream | 0x100U;
if (ctrl & uint(CTRL_CRC))
dataPos -= 2;
else
dataPos += 1;
}
else if (length-- > 2)
{
in = *stream;
++dataPos;
}
else if (length == 1)
{
if (*stream <= 4)
{
in = 0x91;
}
else
{
in = *stream;
++dataPos;
}
}
else
{
if (*stream <= 4)
{
in = 0x88;
length = 0;
gap = BYTES_GAP_NEXT;
}
else
{
in = *stream;
length = LENGTH_UNKNOWN;
++dataPos;
}
}
}
else
{
if (!--gap)
{
NST_VERIFY( *stream <= 4 );
switch (stream[0])
{
case BLOCK_HEADER:
length = LENGTH_HEADER + 3;
break;
case BLOCK_DATA:
length = (uint(stream[-2]) << 8 | stream[-3]) + 3;
NST_VERIFY( length > 3 );
break;
case BLOCK_VOLUME:
length = LENGTH_VOLUME + 3;
break;
case BLOCK_COUNT:
length = LENGTH_COUNT + 3;
break;
default:
gap = 1;
break;
}
}
if (ctrl & uint(CTRL_IO_MODE))
return false;
NST_VERIFY( !(ctrl & uint(CTRL_GEN_IRQ)) );
in = 0;
}
}
else if (!(status & uint(STATUS_PROTECTED)) && length != LENGTH_UNKNOWN)
{
NST_VERIFY( ctrl & uint(CTRL_IO_MODE) || !length );
gap -= (gap > 0);
const uint data = (ctrl & uint(CTRL_IO_MODE)) ? out : 0;
if (length-- > 3)
{
++dataPos;
*stream = data;
}
else if (length == 2)
{
}
else if (length == 1)
{
gap = BYTES_GAP_NEXT;
}
else
{
length = 0;
if (data-1 <= 3)
{
NST_VERIFY( ctrl & uint(CTRL_IO_MODE) );
++dataPos;
switch (*stream = data)
{
case BLOCK_VOLUME:
length = LENGTH_VOLUME + 3;
break;
case BLOCK_COUNT:
length = LENGTH_COUNT + 3;
break;
case BLOCK_HEADER:
length = LENGTH_HEADER + 3;
break;
case BLOCK_DATA:
length = (uint(stream[-2]) << 8 | stream[-3]) + 3;
NST_VERIFY( length > 3 );
break;
default:
NST_UNREACHABLE();
}
}
}
}
uint irq = ctrl & uint(CTRL_GEN_IRQ);
timer |= irq >> 6;
return irq;
}
else if (headPos)
{
count = CLK_REWIND;
headPos = 0;
status |= uint(STATUS_UNREADY);
}
else if (!(ctrl & uint(CTRL_STOP)))
{
count = CLK_BYTE;
headPos = 1;
dataPos = 0;
length = 0;
gap = BYTES_GAP_INIT + BYTES_GAP_NEXT;
status &= ~uint(STATUS_UNREADY);
}
return false;
}
NST_SINGLE_CALL bool Fds::Unit::Drive::Clock()
{
return !count || --count;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Fds::Unit::Unit(const Disks::Sides& s)
: drive(s)
{
status = 0;
}
void Fds::Unit::Reset(bool)
{
timer.Reset();
drive.Reset();
status = 0;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
ibool Fds::Unit::Clock()
{
return
(
(timer.Clock() ? 0 : (timer.Advance(status), 1)) |
(drive.Clock() ? 0 : drive.Advance(status))
);
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Fds::Adapter::Adapter(Cpu& c,const Disks::Sides& s)
: Timer::M2<Unit>(c,s) {}
void Fds::Adapter::Reset(Cpu& cpu,byte* const io,bool protect)
{
Timer::M2<Unit>::Reset( true, true );
unit.drive.Mount( io, protect );
cpu.Map( 0x4020 ).Set( this, &Adapter::Peek_Nop, &Adapter::Poke_4020 );
cpu.Map( 0x4021 ).Set( this, &Adapter::Peek_Nop, &Adapter::Poke_4021 );
cpu.Map( 0x4022 ).Set( this, &Adapter::Peek_Nop, &Adapter::Poke_4022 );
cpu.Map( 0x4024 ).Set( this, &Adapter::Peek_Nop, &Adapter::Poke_4024 );
cpu.Map( 0x4030 ).Set( this, &Adapter::Peek_4030, &Adapter::Poke_Nop );
cpu.Map( 0x4032 ).Set( this, &Adapter::Peek_4032, &Adapter::Poke_Nop );
}
void Fds::Adapter::SaveState(State::Saver& state) const
{
{
const byte data[7] =
{
unit.timer.ctrl,
unit.status,
unit.timer.latch & 0xFFU,
unit.timer.latch >> 8,
unit.timer.count & 0xFFU,
unit.timer.count >> 8,
0
};
state.Begin( AsciiId<'I','R','Q'>::V ).Write( data ).End();
}
{
const uint headPos = NST_MIN(unit.drive.headPos,SIDE_SIZE);
const byte data[16] =
{
unit.drive.ctrl,
unit.drive.status,
unit.drive.in & 0xFFU,
unit.drive.out,
unit.drive.count ? headPos & 0xFF : 0,
unit.drive.count ? headPos >> 8 : 0,
unit.drive.count ? unit.drive.dataPos & 0xFFU : 0,
unit.drive.count ? unit.drive.dataPos >> 8 : 0,
unit.drive.count ? unit.drive.gap & 0xFFU : 0,
unit.drive.count ? unit.drive.gap >> 8 : 0,
unit.drive.count ? unit.drive.length & 0xFFU : 0,
unit.drive.count ? unit.drive.length >> 8 : 0,
unit.drive.count >> 0 & 0xFF,
unit.drive.count >> 8 & 0xFF,
unit.drive.count >> 16,
unit.drive.in >> 8
};
state.Begin( AsciiId<'D','R','V'>::V ).Write( data ).End();
}
}
void Fds::Adapter::LoadState(State::Loader& state,const dword chunk,Ppu& ppu)
{
switch (chunk)
{
case AsciiId<'I','R','Q'>::V:
{
State::Loader::Data<7> data( state );
unit.timer.ctrl = data[0];
unit.status = data[1] & (Unit::STATUS_PENDING_IRQ|Unit::STATUS_TRANSFERED);
unit.timer.latch = data[2] | data[3] << 8;
unit.timer.count = data[4] | data[5] << 8;
break;
}
case AsciiId<'D','R','V'>::V:
{
State::Loader::Data<16> data( state );
unit.drive.ctrl = data[0];
unit.drive.status = data[1] & (Unit::Drive::STATUS_EJECTED|Unit::Drive::STATUS_UNREADY|Unit::Drive::STATUS_PROTECTED) | OPEN_BUS;
unit.drive.in = data[2] | (data[15] << 8 & 0x100);
unit.drive.out = data[3];
unit.drive.headPos = data[4] | data[5] << 8;
unit.drive.dataPos = data[6] | data[7] << 8;
unit.drive.gap = data[8] | data[9] << 8;
unit.drive.length = data[10] | data[11] << 8;
unit.drive.count = data[12] | data[13] << 8 | dword(data[14]) << 16;
if (unit.drive.dataPos > SIDE_SIZE)
unit.drive.dataPos = SIDE_SIZE;
if (unit.drive.headPos < unit.drive.dataPos)
unit.drive.headPos = unit.drive.dataPos;
ppu.SetMirroring( (unit.drive.ctrl & uint(CTRL1_NMT_HORIZONTAL)) ? Ppu::NMT_H : Ppu::NMT_V );
break;
}
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_SINGLE_CALL uint Fds::Adapter::Activity() const
{
return unit.drive.count ? (unit.drive.ctrl & uint(Unit::Drive::CTRL_READ_MODE)) ? Api::Fds::MOTOR_READ : Api::Fds::MOTOR_WRITE : Api::Fds::MOTOR_OFF;
}
NST_SINGLE_CALL void Fds::Adapter::WriteProtect()
{
unit.drive.status |= uint(Unit::Drive::STATUS_PROTECTED);
}
NST_SINGLE_CALL void Fds::Adapter::Write(uint reg)
{
Update();
unit.status &= (reg >> 6 & Unit::STATUS_TRANSFERED) | Unit::STATUS_PENDING_IRQ;
if (!unit.status)
ClearIRQ();
unit.drive.Write( reg );
}
NST_SINGLE_CALL uint Fds::Adapter::Read()
{
Update();
unit.status &= Unit::STATUS_PENDING_IRQ;
if (!unit.status)
ClearIRQ();
return unit.drive.in;
}
NES_PEEK(Fds::Adapter,Nop)
{
return OPEN_BUS;
}
NES_POKE(Fds::Adapter,Nop)
{
}
NES_POKE_D(Fds::Adapter,4020)
{
Update();
unit.timer.latch = (unit.timer.latch & 0xFF00U) | (data << 0);
}
NES_POKE_D(Fds::Adapter,4021)
{
Update();
unit.timer.latch = (unit.timer.latch & 0x00FFU) | (data << 8);
}
NES_POKE_D(Fds::Adapter,4022)
{
Update();
unit.timer.ctrl = data;
unit.timer.count = unit.timer.latch;
unit.status &= Unit::STATUS_TRANSFERED;
if (!unit.status)
ClearIRQ();
}
NES_POKE_D(Fds::Adapter,4024)
{
Update();
unit.drive.out = data;
unit.status &= Unit::STATUS_PENDING_IRQ;
if (!unit.status)
ClearIRQ();
}
NES_PEEK(Fds::Adapter,4030)
{
Update();
const uint status = unit.status;
unit.status = 0;
ClearIRQ();
return status;
}
NES_PEEK(Fds::Adapter,4032)
{
Update();
NST_ASSERT( unit.drive.status & uint(OPEN_BUS) );
return unit.drive.status | (unit.drive.ctrl & uint(Unit::Drive::CTRL_STOP));
}
void Fds::VSync()
{
adapter.VSync();
if (!disks.mounting)
{
const uint led = adapter.Activity();
if (io.led != led && (io.led != Api::Fds::MOTOR_WRITE || led != Api::Fds::MOTOR_READ))
{
io.led = led;
Api::Fds::driveCallback( static_cast<Api::Fds::Motor>(io.led) );
}
}
else if (!--disks.mounting)
{
adapter.Mount( disks.sides[disks.current], disks.writeProtected );
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Fds::Io::Io()
: led(Api::Fds::MOTOR_OFF)
{
Reset();
}
void Fds::Io::Reset()
{
ctrl = 0;
port = 0;
}
void Fds::Ram::Reset()
{
std::memset( mem, 0x00, sizeof(mem) );
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NES_PEEK_A(Fds::Ram,Ram)
{
return mem[address - 0x6000];
}
NES_POKE_AD(Fds::Ram,Ram)
{
mem[address - 0x6000] = data;
}
NES_POKE_D(Fds,4025)
{
adapter.Write( data );
ppu.SetMirroring( (data & CTRL1_NMT_HORIZONTAL) ? Ppu::NMT_H : Ppu::NMT_V );
}
NES_PEEK(Fds,4031)
{
const uint data = adapter.Read();
if (data <= 0xFF)
return data;
if (!disks.writeProtected)
{
disks.writeProtected = true;
adapter.WriteProtect();
Api::Fds::diskCallback( Api::Fds::DISK_NONSTANDARD, disks.current / 2, disks.current % 2 );
}
return data & 0xFF;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
void Fds::Sound::Envelope::Reset()
{
ctrl = 0;
counter = 0;
gain = GAIN_MIN;
output = GAIN_MIN;
}
void Fds::Sound::Envelope::SaveState(State::Saver& state,const dword chunk) const
{
const byte data[3] =
{
ctrl,
counter,
gain
};
state.Begin( chunk ).Write( data ).End();
}
void Fds::Sound::Envelope::LoadState(State::Loader& state)
{
State::Loader::Data<3> data( state );
ctrl = data[0];
counter = data[1] & CTRL_COUNT;
gain = data[2] & CTRL_COUNT;
output = NST_MIN(gain,GAIN_MAX);
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
inline uint Fds::Sound::Envelope::Gain() const
{
return gain;
}
inline uint Fds::Sound::Envelope::Output() const
{
NST_ASSERT( output == NST_MIN(gain,GAIN_MAX) );
return output;
}
void Fds::Sound::Envelope::Write(const uint data)
{
ctrl = data;
counter = data & CTRL_COUNT;
if (data & CTRL_DISABLE)
{
gain = data & CTRL_COUNT;
output = NST_MIN(gain,GAIN_MAX);
}
}
NST_SINGLE_CALL void Fds::Sound::Envelope::Clock()
{
if (!(ctrl & CTRL_DISABLE))
{
if (counter)
{
counter--;
}
else
{
counter = ctrl & CTRL_COUNT;
if (ctrl & CTRL_UP) gain += (gain < GAIN_MAX);
else gain -= (gain > GAIN_MIN);
output = NST_MIN(gain,GAIN_MAX);
}
}
}
inline bool Fds::Sound::CanModulate() const
{
return modulator.length && !modulator.writing;
}
bool Fds::Sound::CanOutput() const
{
return (status & STATUS_OUTPUT_ENABLED) && wave.length && !wave.writing && output;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Fds::Sound::Sound(Apu& a,bool connect)
: Channel(a)
{
Reset();
bool audible = UpdateSettings();
if (connect)
Connect( audible );
}
void Fds::Sound::Reset()
{
active = false;
wave.writing = false;
wave.length = 0;
wave.pos = 0;
wave.volume = 0;
modulator.active = false;
modulator.writing = false;
modulator.pos = 0;
modulator.length = 0;
modulator.timer = 0;
modulator.sweep = 0;
envelopes.counter = 0;
envelopes.length = 0;
envelopes.units[VOLUME].Reset();
envelopes.units[SWEEP].Reset();
std::memset( wave.table, 0, Wave::SIZE );
std::memset( modulator.table, 0x00, Modulator::SIZE );
status = 0;
volume = volumes[0];
amp = 0;
dcBlocker.Reset();
}
bool Fds::Sound::UpdateSettings()
{
envelopes.clock = GetCpuClock() * Envelopes::PULSE;
Cycle rate;
uint fixed;
GetOscillatorClock( rate, fixed );
NST_VERIFY( fixed <= 0xFFFF && rate <= 0x7FFFF );
modulator.rate = rate;
modulator.clock = dword(fixed) << 16;
wave.rate = GetSampleRate();
wave.frame = GetCpuClockBase();
wave.clock = 0x10000UL * GetCpuClock() * GetCpuClockDivider();
amp = 0;
uint volume = GetVolume(EXT_FDS) * 69 / DEFAULT_VOLUME;
output = IsMuted() ? 0 : volume;
dcBlocker.Reset();
active = CanOutput();
return volume;
}
void Fds::Sound::SaveState(State::Saver& state,const dword baseChunk) const
{
state.Begin( baseChunk );
state.Begin( AsciiId<'M','A','S'>::V );
{
{
byte data[6] =
{
((status & STATUS_OUTPUT_ENABLED) ? 0U : uint(REG3_OUTPUT_DISABLE)) |
((status & STATUS_ENVELOPES_ENABLED) ? 0U : uint(REG3_ENVELOPE_DISABLE)),
wave.writing ? REG9_WRITE_MODE : 0,
wave.length & 0xFFU,
wave.length >> 8,
envelopes.length,
envelopes.counter
};
for (uint i=0; i < sizeof(array(volumes)); ++i)
{
if (volume == volumes[i])
{
data[1] |= i;
break;
}
}
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
}
{
state.Begin( AsciiId<'W','A','V'>::V ).Compress( wave.table ).End();
}
}
state.End();
envelopes.units[VOLUME].SaveState( state, AsciiId<'V','O','L'>::V );
envelopes.units[SWEEP].SaveState( state, AsciiId<'S','W','P'>::V );
state.Begin( AsciiId<'M','O','D'>::V );
{
{
const byte data[4] =
{
modulator.length & 0xFF,
modulator.length >> 8 | (modulator.writing ? REG7_MOD_WRITE_MODE : 0),
modulator.sweep,
modulator.pos
};
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
}
{
byte data[Modulator::SIZE];
for (uint i=0; i < Modulator::SIZE; ++i)
{
for (uint j=0; j < sizeof(array(Modulator::steps)); ++j)
{
if (modulator.table[i] == Modulator::steps[j])
{
data[i] = j;
break;
}
}
}
state.Begin( AsciiId<'R','A','M'>::V ).Compress( data ).End();
}
}
state.End();
state.End();
}
void Fds::Sound::LoadState(State::Loader& state)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'M','A','S'>::V:
{
while (const dword subchunk = state.Begin())
{
switch (subchunk)
{
case AsciiId<'R','E','G'>::V:
{
State::Loader::Data<6> data( state );
status =
(
((data[0] & REG3_OUTPUT_DISABLE) ? 0U : uint(STATUS_OUTPUT_ENABLED)) |
((data[0] & REG3_ENVELOPE_DISABLE) ? 0U : uint(STATUS_ENVELOPES_ENABLED))
);
volume = volumes[data[1] & REG9_VOLUME];
wave.writing = data[1] & REG9_WRITE_MODE;
wave.length = data[2] | (data[3] & REG3_WAVELENGTH_HIGH) << 8;
envelopes.length = data[4];
envelopes.counter = data[5];
break;
}
case AsciiId<'W','A','V'>::V:
state.Uncompress( wave.table );
for (uint i=0; i < Wave::SIZE; ++i)
wave.table[i] &= 0x3FU;
break;
}
state.End();
}
break;
}
case AsciiId<'V','O','L'>::V:
envelopes.units[VOLUME].LoadState( state );
break;
case AsciiId<'S','W','P'>::V:
envelopes.units[SWEEP].LoadState( state );
break;
case AsciiId<'M','O','D'>::V:
{
while (const dword subchunk = state.Begin())
{
switch (subchunk)
{
case AsciiId<'R','E','G'>::V:
{
State::Loader::Data<4> data( state );
modulator.length = data[0] | (data[1] & REG7_MOD_WAVELENGTH_HIGH) << 8;
modulator.writing = data[1] & REG7_MOD_WRITE_MODE;
modulator.sweep = data[2] & (REG5_MOD_SWEEP|REG5_MOD_NEGATE);
modulator.pos = data[3] & 0x3F;
break;
}
case AsciiId<'R','A','M'>::V:
{
byte data[Modulator::SIZE];
state.Uncompress( data );
for (uint i=0; i < Modulator::SIZE; ++i)
modulator.table[i] = Modulator::steps[data[i] & uint(REG8_MOD_DATA)];
break;
}
}
state.End();
}
break;
}
}
state.End();
}
amp = 0;
wave.pos = 0;
wave.volume = envelopes.units[VOLUME].Output();
modulator.timer = 0;
modulator.active = CanModulate();
active = CanOutput();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
uint Fds::Sound::ReadWaveData(uint address) const
{
return wave.table[address & 0x3F] | uint(OPEN_BUS);
}
void Fds::Sound::WriteWaveData(uint address,uint data)
{
NST_VERIFY( wave.writing );
if (wave.writing)
{
Update();
wave.table[address & 0x3F] = data & 0x3F;
}
}
void Fds::Sound::WriteReg0(uint data)
{
Update();
envelopes.units[VOLUME].Write( data );
if (data & Envelope::CTRL_DISABLE && !wave.pos)
wave.volume = envelopes.units[VOLUME].Output();
}
void Fds::Sound::WriteReg1(uint data)
{
Update();
wave.length &= uint(REG3_WAVELENGTH_HIGH) << 8;
wave.length |= data;
active = CanOutput();
}
void Fds::Sound::WriteReg2(uint data)
{
Update();
wave.length &= uint(REG2_WAVELENGTH_LOW);
wave.length |= (data & REG3_WAVELENGTH_HIGH) << 8;
status = ~data & (REG3_OUTPUT_DISABLE|REG3_ENVELOPE_DISABLE);
if (data & REG3_OUTPUT_DISABLE)
{
wave.pos = 0;
wave.volume = envelopes.units[VOLUME].Output();
}
active = CanOutput();
}
void Fds::Sound::WriteReg3(uint data)
{
Update();
envelopes.units[SWEEP].Write( data );
}
void Fds::Sound::WriteReg4(uint data)
{
Update();
modulator.sweep = data & (REG5_MOD_SWEEP|REG5_MOD_NEGATE);
modulator.pos = 0x00;
}
void Fds::Sound::WriteReg5(uint data)
{
Update();
modulator.length &= uint(REG7_MOD_WAVELENGTH_HIGH) << 8;
modulator.length |= data;
modulator.active = CanModulate();
}
void Fds::Sound::WriteReg6(uint data)
{
Update();
modulator.length &= REG6_MOD_WAVELENGTH_LOW;
modulator.length |= (data & REG7_MOD_WAVELENGTH_HIGH) << 8;
modulator.writing = data & REG7_MOD_WRITE_MODE;
modulator.active = CanModulate();
}
void Fds::Sound::WriteReg7(uint data)
{
NST_VERIFY( modulator.writing );
if (modulator.writing)
{
Update();
std::memmove( modulator.table, modulator.table + 1, Modulator::SIZE-1 );
modulator.table[Modulator::SIZE-1] = Modulator::steps[data & REG8_MOD_DATA];
}
}
void Fds::Sound::WriteReg8(uint data)
{
Update();
volume = volumes[data & REG9_VOLUME];
wave.writing = data & REG9_WRITE_MODE;
active = CanOutput();
}
void Fds::Sound::WriteReg9(uint data)
{
Update();
envelopes.length = data;
}
uint Fds::Sound::ReadVolumeGain() const
{
return envelopes.units[VOLUME].Gain() | OPEN_BUS;
}
uint Fds::Sound::ReadSweepGain() const
{
return envelopes.units[SWEEP].Gain() | OPEN_BUS;
}
Cycle Fds::Sound::Clock(Cycle rateCycles,Cycle rateClock,const Cycle targetCycles)
{
rateClock *= envelopes.clock;
do
{
if (envelopes.counter)
{
envelopes.counter--;
}
else
{
envelopes.counter = envelopes.length;
if (envelopes.length && status & STATUS_ENVELOPES_ENABLED)
{
for (uint i=0; i < 2; ++i)
envelopes.units[i].Clock();
}
}
rateCycles += rateClock;
}
while (rateCycles <= targetCycles);
return rateCycles;
}
NST_SINGLE_CALL dword Fds::Sound::GetModulation() const
{
if (dword pos = envelopes.units[SWEEP].Gain())
{
pos = (pos * (((modulator.sweep & REG5_MOD_SWEEP) - (modulator.sweep & REG5_MOD_NEGATE)))) & 0xFFF;
if (modulator.sweep & REG5_MOD_NEGATE)
{
pos >>= 4;
if (pos >= 0xC0)
pos = (pos & 0x7F) - (pos & 0x80);
}
else
{
pos = (pos >> 4) + ((pos & 0xF) ? 2 : 0);
if (pos >= 0xC2)
{
pos -= 0x102;
pos = (pos & 0x7F) - (pos & 0x80);
}
}
pos *= wave.length;
if (pos & Modulator::TIMER_CARRY)
pos = wave.length - (~(pos-1) >> 6);
else
pos = wave.length + (pos >> 6);
return pos;
}
return wave.length;
}
Fds::Sound::Sample Fds::Sound::GetSample()
{
NST_ASSERT( modulator.active == CanModulate() && bool(active) == CanOutput() );
if (modulator.active)
{
for (modulator.timer -= modulator.length * modulator.rate; modulator.timer & Modulator::TIMER_CARRY; modulator.timer += modulator.clock)
{
const uint value = modulator.pos >> 1;
modulator.pos = (modulator.pos + 1U) & 0x3F;
modulator.sweep = (modulator.table[value] != 0x80) ? (modulator.sweep + modulator.table[value]) & 0x7FU : 0x00U;
}
}
dword sample = 0;
if (active)
{
const dword pos = wave.pos;
wave.pos = (wave.pos + dword(qword(GetModulation()) * wave.frame / wave.clock) + Wave::SIZE * wave.rate) % (Wave::SIZE * wave.rate);
if (wave.pos < pos)
wave.volume = envelopes.units[VOLUME].Output();
sample = wave.volume * volume * wave.table[(wave.pos / wave.rate) & 0x3F] / 30;
}
amp = (amp * 2 + sample) / 3;
return dcBlocker.Apply( amp * output / DEFAULT_VOLUME );
}
}
}