mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
956 lines
28 KiB
C++
956 lines
28 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 "NstFds.hpp"
|
|
#include "board/NstBoard.hpp"
|
|
#include "board/NstBoardMmc5.hpp"
|
|
#include "board/NstBoardKonami.hpp"
|
|
#include "board/NstBoardNamcot.hpp"
|
|
#include "board/NstBoardSunsoft.hpp"
|
|
#include "api/NstApiNsf.hpp"
|
|
#include "NstNsf.hpp"
|
|
|
|
namespace Nes
|
|
{
|
|
namespace Core
|
|
{
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("s", on)
|
|
#endif
|
|
|
|
class Nsf::Chips : Apu::Channel
|
|
{
|
|
struct Mmc5 : Boards::Mmc5::Sound
|
|
{
|
|
uint mul[2];
|
|
byte exRam[SIZE_1K];
|
|
|
|
explicit Mmc5(Apu& a)
|
|
: Sound(a,false) {}
|
|
|
|
void Reset();
|
|
void ClearExRam();
|
|
|
|
using Sound::UpdateSettings;
|
|
using Sound::GetSample;
|
|
using Sound::Clock;
|
|
};
|
|
|
|
struct Fds : Core::Fds::Sound
|
|
{
|
|
byte ram[SIZE_8K+SIZE_32K];
|
|
|
|
explicit Fds(Apu& a)
|
|
: Sound(a,false) {}
|
|
|
|
void Reset();
|
|
void SwapBank(const Prg&,uint,uint);
|
|
|
|
using Sound::UpdateSettings;
|
|
using Sound::GetSample;
|
|
using Sound::Clock;
|
|
};
|
|
|
|
struct N163 : Boards::Namcot::N163::Sound
|
|
{
|
|
explicit N163(Apu& a)
|
|
: Sound(a,false) {}
|
|
|
|
using Sound::Reset;
|
|
using Sound::UpdateSettings;
|
|
using Sound::GetSample;
|
|
};
|
|
|
|
struct Vrc6 : Boards::Konami::Vrc6::Sound
|
|
{
|
|
explicit Vrc6(Apu& a)
|
|
: Sound(a,false) {}
|
|
|
|
using Sound::Reset;
|
|
using Sound::UpdateSettings;
|
|
using Sound::GetSample;
|
|
};
|
|
|
|
struct Vrc7 : Boards::Konami::Vrc7::Sound
|
|
{
|
|
explicit Vrc7(Apu& a)
|
|
: Sound(a,false) {}
|
|
|
|
using Sound::Reset;
|
|
using Sound::UpdateSettings;
|
|
using Sound::GetSample;
|
|
};
|
|
|
|
struct S5b : Boards::Sunsoft::S5b::Sound
|
|
{
|
|
explicit S5b(Apu& a)
|
|
: Sound(a,false) {}
|
|
|
|
using Sound::Reset;
|
|
using Sound::UpdateSettings;
|
|
using Sound::GetSample;
|
|
};
|
|
|
|
template<typename T>
|
|
struct Chip : Pointer<T>
|
|
{
|
|
Chip(Apu& a,uint t)
|
|
: Pointer<T>(t ? new T(a) : NULL) {}
|
|
};
|
|
|
|
struct Clocks
|
|
{
|
|
void Reset(bool,bool);
|
|
|
|
Cycle next;
|
|
Cycle mmc5;
|
|
Cycle fds;
|
|
};
|
|
|
|
void Reset();
|
|
bool UpdateSettings();
|
|
Sample GetSample();
|
|
Cycle Clock(Cycle,Cycle,Cycle);
|
|
|
|
Clocks clocks;
|
|
|
|
public:
|
|
|
|
Chips(uint,Apu&);
|
|
|
|
Chip<Mmc5> mmc5;
|
|
Chip<Vrc6> vrc6;
|
|
Chip<Vrc7> vrc7;
|
|
Chip<Fds> fds;
|
|
Chip<S5b> s5b;
|
|
Chip<N163> n163;
|
|
};
|
|
|
|
void Nsf::Chips::Mmc5::ClearExRam()
|
|
{
|
|
std::memset( exRam, 0, sizeof(exRam) );
|
|
}
|
|
|
|
void Nsf::Chips::Mmc5::Reset()
|
|
{
|
|
mul[0] = 0;
|
|
mul[1] = 0;
|
|
|
|
ClearExRam();
|
|
|
|
Sound::Reset();
|
|
}
|
|
|
|
void Nsf::Chips::Fds::Reset()
|
|
{
|
|
std::memset( ram, 0, sizeof(ram) );
|
|
|
|
Sound::Reset();
|
|
}
|
|
|
|
void Nsf::Chips::Fds::SwapBank(const Prg& prg,uint page,uint bank)
|
|
{
|
|
std::memcpy( ram + SIZE_4K * page, prg.Source().Mem(bank * SIZE_4K), SIZE_4K );
|
|
}
|
|
|
|
Nsf::Chips::Chips(const uint types,Apu& apu)
|
|
:
|
|
Channel ( apu ),
|
|
mmc5 ( apu, types & Api::Nsf::CHIP_MMC5 ),
|
|
vrc6 ( apu, types & Api::Nsf::CHIP_VRC6 ),
|
|
vrc7 ( apu, types & Api::Nsf::CHIP_VRC7 ),
|
|
fds ( apu, types & Api::Nsf::CHIP_FDS ),
|
|
s5b ( apu, types & Api::Nsf::CHIP_S5B ),
|
|
n163 ( apu, types & Api::Nsf::CHIP_N163 )
|
|
{
|
|
Connect( UpdateSettings() );
|
|
}
|
|
|
|
void Nsf::Chips::Clocks::Reset(bool mmc5Chip,bool fdsChip)
|
|
{
|
|
next = (mmc5Chip || fdsChip ? 0UL : Cpu::CYCLE_MAX);
|
|
mmc5 = (mmc5Chip ? 0UL : Cpu::CYCLE_MAX);
|
|
fds = (fdsChip ? 0UL : Cpu::CYCLE_MAX);
|
|
}
|
|
|
|
void Nsf::Chips::Reset()
|
|
{
|
|
clocks.Reset( mmc5, fds );
|
|
|
|
if ( mmc5 ) mmc5->Reset();
|
|
if ( vrc6 ) vrc6->Reset();
|
|
if ( vrc7 ) vrc7->Reset();
|
|
if ( fds ) fds->Reset();
|
|
if ( s5b ) s5b->Reset();
|
|
if ( n163 ) n163->Reset();
|
|
}
|
|
|
|
bool Nsf::Chips::UpdateSettings()
|
|
{
|
|
clocks.Reset( mmc5, fds );
|
|
|
|
return
|
|
(
|
|
( mmc5 ? mmc5->UpdateSettings() : 0U ) |
|
|
( vrc6 ? vrc6->UpdateSettings() : 0U ) |
|
|
( vrc7 ? vrc7->UpdateSettings() : 0U ) |
|
|
( fds ? fds->UpdateSettings() : 0U ) |
|
|
( s5b ? s5b->UpdateSettings() : 0U ) |
|
|
( n163 ? n163->UpdateSettings() : 0U )
|
|
);
|
|
}
|
|
|
|
Nsf::Nsf(Context& context)
|
|
:
|
|
Image (SOUND),
|
|
cpu (context.cpu),
|
|
apu (context.apu),
|
|
chips (NULL),
|
|
favoredSystem (context.favoredSystem),
|
|
tuneMode (Api::Nsf::TUNE_MODE_NTSC)
|
|
{
|
|
if (context.patch && context.patchResult)
|
|
*context.patchResult = RESULT_ERR_UNSUPPORTED;
|
|
|
|
Stream::In stream( &context.stream );
|
|
|
|
uint version;
|
|
|
|
{
|
|
byte data[5+1+2+6];
|
|
stream.Read( data );
|
|
|
|
if
|
|
(
|
|
data[0] != Ascii<'N'>::V ||
|
|
data[1] != Ascii<'E'>::V ||
|
|
data[2] != Ascii<'S'>::V ||
|
|
data[3] != Ascii<'M'>::V ||
|
|
data[4] != 0x1A
|
|
)
|
|
throw RESULT_ERR_INVALID_FILE;
|
|
|
|
if (!data[6] || data[9] < 0x60 || data[11] < 0x60 || data[13] < 0x60)
|
|
throw RESULT_ERR_CORRUPT_FILE;
|
|
|
|
songs.count = data[6];
|
|
songs.start = data[7] >= 1 && data[7] <= data[6] ? data[7] - 1 : 0;
|
|
|
|
addressing.load = data[8] | uint( data[9] ) << 8;
|
|
addressing.init = data[10] | uint( data[11] ) << 8;
|
|
addressing.play = data[12] | uint( data[13] ) << 8;
|
|
|
|
version = data[5];
|
|
}
|
|
|
|
stream.Read( songs.info.name, 32 );
|
|
stream.Read( songs.info.artist, 32 );
|
|
stream.Read( songs.info.copyright, 32 );
|
|
|
|
songs.info.name[31] = '\0';
|
|
songs.info.artist[31] = '\0';
|
|
songs.info.copyright[31] = '\0';
|
|
|
|
speed.ntsc = stream.Read16();
|
|
stream.Read( banks );
|
|
|
|
addressing.bankSwitched = 0 !=
|
|
(
|
|
uint( banks[0] ) |
|
|
uint( banks[1] ) |
|
|
uint( banks[2] ) |
|
|
uint( banks[3] ) |
|
|
uint( banks[4] ) |
|
|
uint( banks[5] ) |
|
|
uint( banks[6] ) |
|
|
uint( banks[7] )
|
|
);
|
|
|
|
speed.pal = stream.Read16();
|
|
songs.current = songs.start;
|
|
|
|
switch (stream.Read8() & 0x3)
|
|
{
|
|
case 0x0: tuneMode = Api::Nsf::TUNE_MODE_NTSC; break;
|
|
case 0x1: tuneMode = Api::Nsf::TUNE_MODE_PAL; break;
|
|
default: tuneMode = Api::Nsf::TUNE_MODE_BOTH; break;
|
|
}
|
|
|
|
uint types = stream.Read8();
|
|
|
|
if (!(types & Api::Nsf::CHIP_FDS) && addressing.load < 0x8000)
|
|
throw RESULT_ERR_CORRUPT_FILE;
|
|
|
|
dword length = 0;
|
|
|
|
while (length < SIZE_4096K && stream.SafeRead8() <= 0xFF)
|
|
++length;
|
|
|
|
if (length <= HEADER_RESERVED_LENGTH)
|
|
throw RESULT_ERR_CORRUPT_FILE;
|
|
|
|
length -= HEADER_RESERVED_LENGTH;
|
|
stream.Seek( -idword(length) );
|
|
|
|
{
|
|
const uint offset = addressing.load & 0xFFFU;
|
|
|
|
prg.Source().Set( Ram::ROM, true, false, offset + length );
|
|
prg.Source().Fill( JAM );
|
|
stream.Read( prg.Source().Mem() + offset, length );
|
|
}
|
|
|
|
if (types & Api::Nsf::CHIP_ALL)
|
|
chips = new Chips (types,apu);
|
|
|
|
if (Log::Available())
|
|
{
|
|
Log log;
|
|
|
|
log << "Nsf: version " << version;
|
|
|
|
if (*songs.info.name)
|
|
log << NST_LINEBREAK "Nsf: name: " << songs.info.name;
|
|
|
|
if (*songs.info.artist)
|
|
log << NST_LINEBREAK "Nsf: artist: " << songs.info.artist;
|
|
|
|
if (*songs.info.copyright)
|
|
log << NST_LINEBREAK "Nsf: copyright: " << songs.info.copyright;
|
|
|
|
log << NST_LINEBREAK "Nsf: starting song "
|
|
<< (songs.start+1U)
|
|
<< " of "
|
|
<< songs.count
|
|
<<
|
|
(
|
|
tuneMode == Api::Nsf::TUNE_MODE_NTSC ? NST_LINEBREAK "Nsf: NTSC mode" :
|
|
tuneMode == Api::Nsf::TUNE_MODE_PAL ? NST_LINEBREAK "Nsf: PAL mode" :
|
|
NST_LINEBREAK "Nsf: PAL/NTSC mode"
|
|
)
|
|
<< NST_LINEBREAK "Nsf: "
|
|
<< (length / SIZE_1K)
|
|
<< (addressing.bankSwitched ? "k bank-switched " : "k flat ")
|
|
<< ((types & Api::Nsf::CHIP_FDS) ? "PRG-RAM" : "PRG-ROM")
|
|
<< NST_LINEBREAK "Nsf: load address - " << Log::Hex( 16, addressing.load )
|
|
<< NST_LINEBREAK "Nsf: init address - " << Log::Hex( 16, addressing.init )
|
|
<< NST_LINEBREAK "Nsf: play address - " << Log::Hex( 16, addressing.play )
|
|
<< NST_LINEBREAK;
|
|
|
|
if (types & Api::Nsf::CHIP_ALL)
|
|
{
|
|
if ( chips->mmc5 ) log << "Nsf: MMC5 sound chip present" NST_LINEBREAK;
|
|
if ( chips->vrc6 ) log << "Nsf: VRC6 sound chip present" NST_LINEBREAK;
|
|
if ( chips->vrc7 ) log << "Nsf: VRC7 sound chip present" NST_LINEBREAK;
|
|
if ( chips->fds ) log << "Nsf: FDS sound chip present" NST_LINEBREAK;
|
|
if ( chips->s5b ) log << "Nsf: Sunsoft5B sound chip present" NST_LINEBREAK;
|
|
if ( chips->n163 ) log << "Nsf: N163 sound chip present" NST_LINEBREAK;
|
|
}
|
|
}
|
|
}
|
|
|
|
Nsf::~Nsf()
|
|
{
|
|
delete chips;
|
|
}
|
|
|
|
Region Nsf::GetDesiredRegion() const
|
|
{
|
|
return tuneMode == Api::Nsf::TUNE_MODE_PAL ? REGION_PAL : REGION_NTSC;
|
|
}
|
|
|
|
System Nsf::GetDesiredSystem(Region region,CpuModel* cpu,PpuModel* ppu) const
|
|
{
|
|
if ((region == REGION_PAL) && (favoredSystem == FAVORED_DENDY))
|
|
{
|
|
if (cpu)
|
|
*cpu = CPU_DENDY;
|
|
|
|
if (ppu)
|
|
*ppu = PPU_DENDY;
|
|
|
|
return SYSTEM_DENDY;
|
|
}
|
|
else
|
|
{
|
|
return Image::GetDesiredSystem( region, cpu, ppu );
|
|
}
|
|
}
|
|
|
|
uint Nsf::GetChips() const
|
|
{
|
|
uint types = 0;
|
|
|
|
if (chips)
|
|
{
|
|
if ( chips->vrc6 ) types |= Api::Nsf::CHIP_VRC6;
|
|
if ( chips->vrc7 ) types |= Api::Nsf::CHIP_VRC7;
|
|
if ( chips->fds ) types |= Api::Nsf::CHIP_FDS;
|
|
if ( chips->mmc5 ) types |= Api::Nsf::CHIP_MMC5;
|
|
if ( chips->n163 ) types |= Api::Nsf::CHIP_N163;
|
|
if ( chips->s5b ) types |= Api::Nsf::CHIP_S5B;
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
void Nsf::Reset(bool)
|
|
{
|
|
cpu.Map( 0x38EC ).Set( this, &Nsf::Peek_38EC, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38ED ).Set( this, &Nsf::Peek_38ED, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38EE ).Set( this, &Nsf::Peek_38EE, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38EF ).Set( this, &Nsf::Peek_38EF, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F0 ).Set( this, &Nsf::Peek_38F0, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F1 ).Set( this, &Nsf::Peek_38F1, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F2 ).Set( this, &Nsf::Peek_38F2, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F3 ).Set( this, &Nsf::Peek_38F3, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F4 ).Set( this, &Nsf::Peek_38F4, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F5 ).Set( this, &Nsf::Peek_38F5, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F6 ).Set( this, &Nsf::Peek_38F6, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F7 ).Set( this, &Nsf::Peek_38F7, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F8 ).Set( this, &Nsf::Peek_38F8, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38F9 ).Set( this, &Nsf::Peek_38F9, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38FA ).Set( this, &Nsf::Peek_38FA, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38FB ).Set( this, &Nsf::Peek_38FB, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38FC ).Set( this, &Nsf::Peek_38FC, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38FD ).Set( this, &Nsf::Peek_38FD, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38FE ).Set( this, &Nsf::Peek_38FE, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x38FF ).Set( this, &Nsf::Peek_38FF, &Nsf::Poke_Nop );
|
|
|
|
cpu.Map( 0x4017 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_4017 );
|
|
|
|
const bool fds = chips && chips->fds;
|
|
|
|
if (addressing.bankSwitched)
|
|
{
|
|
if (fds)
|
|
{
|
|
cpu.Map( 0x5FF6 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_5FF6 );
|
|
cpu.Map( 0x5FF7 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_5FF7 );
|
|
}
|
|
|
|
cpu.Map( 0x5FF8 ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FF8 : &Nsf::Poke_5FF8 );
|
|
cpu.Map( 0x5FF9 ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FF9 : &Nsf::Poke_5FF9 );
|
|
cpu.Map( 0x5FFA ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FFA : &Nsf::Poke_5FFA );
|
|
cpu.Map( 0x5FFB ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FFB : &Nsf::Poke_5FFB );
|
|
cpu.Map( 0x5FFC ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FFC : &Nsf::Poke_5FFC );
|
|
cpu.Map( 0x5FFD ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FFD : &Nsf::Poke_5FFD );
|
|
cpu.Map( 0x5FFE ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FFE : &Nsf::Poke_5FFE );
|
|
cpu.Map( 0x5FFF ).Set( this, &Nsf::Peek_Nop, fds ? &Nsf::Poke_Fds_5FFF : &Nsf::Poke_5FFF );
|
|
}
|
|
else if (!fds)
|
|
{
|
|
for (dword i=0x8000, j=0; i < 0x10000; j += (i >= (addressing.load & 0xF000U)), i += 0x1000)
|
|
prg.SwapBank<SIZE_4K>( i-0x8000, j );
|
|
}
|
|
|
|
if (fds)
|
|
{
|
|
cpu.Map( 0x4040, 0x407F ).Set( this, &Nsf::Peek_Fds_4040, &Nsf::Poke_Fds_4040 );
|
|
cpu.Map( 0x4080 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4080 );
|
|
cpu.Map( 0x4082 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4082 );
|
|
cpu.Map( 0x4083 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4083 );
|
|
cpu.Map( 0x4084 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4084 );
|
|
cpu.Map( 0x4085 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4085 );
|
|
cpu.Map( 0x4086 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4086 );
|
|
cpu.Map( 0x4087 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4087 );
|
|
cpu.Map( 0x4088 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4088 );
|
|
cpu.Map( 0x4089 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_4089 );
|
|
cpu.Map( 0x408A ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Fds_408A );
|
|
cpu.Map( 0x4090 ).Set( this, &Nsf::Peek_Fds_4090, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x4092 ).Set( this, &Nsf::Peek_Fds_4092, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x6000, 0xFFFF ).Set( this, &Nsf::Peek_Fds_Ram, &Nsf::Poke_Fds_Ram );
|
|
}
|
|
else
|
|
{
|
|
cpu.Map( 0x6000, 0x7FFF ).Set( this, &Nsf::Peek_Wrk, &Nsf::Poke_Wrk );
|
|
cpu.Map( 0x8000, 0x8FFF ).Set( this, &Nsf::Peek_Prg_8, &Nsf::Poke_Nop );
|
|
cpu.Map( 0x9000, 0x9FFF ).Set( this, &Nsf::Peek_Prg_9, &Nsf::Poke_Nop );
|
|
cpu.Map( 0xA000, 0xAFFF ).Set( this, &Nsf::Peek_Prg_A, &Nsf::Poke_Nop );
|
|
cpu.Map( 0xB000, 0xBFFF ).Set( this, &Nsf::Peek_Prg_B, &Nsf::Poke_Nop );
|
|
cpu.Map( 0xC000, 0xCFFF ).Set( this, &Nsf::Peek_Prg_C, &Nsf::Poke_Nop );
|
|
cpu.Map( 0xD000, 0xDFFF ).Set( this, &Nsf::Peek_Prg_D, &Nsf::Poke_Nop );
|
|
cpu.Map( 0xE000, 0xEFFF ).Set( this, &Nsf::Peek_Prg_E, &Nsf::Poke_Nop );
|
|
cpu.Map( 0xF000, 0xFFFF ).Set( this, &Nsf::Peek_Prg_F, &Nsf::Poke_Nop );
|
|
}
|
|
|
|
if (chips)
|
|
{
|
|
if (chips->mmc5)
|
|
{
|
|
cpu.Map( 0x5000 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5000 );
|
|
cpu.Map( 0x5002 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5002 );
|
|
cpu.Map( 0x5003 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5003 );
|
|
cpu.Map( 0x5004 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5004 );
|
|
cpu.Map( 0x5006 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5006 );
|
|
cpu.Map( 0x5007 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5007 );
|
|
cpu.Map( 0x5010 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5010 );
|
|
cpu.Map( 0x5011 ).Set( this, &Nsf::Peek_Nop, &Nsf::Poke_Mmc5_5011 );
|
|
cpu.Map( 0x5015 ).Set( this, &Nsf::Peek_Mmc5_5015, &Nsf::Poke_Mmc5_5015 );
|
|
cpu.Map( 0x5205 ).Set( this, &Nsf::Peek_Mmc5_5205, &Nsf::Poke_Mmc5_5205 );
|
|
cpu.Map( 0x5206 ).Set( this, &Nsf::Peek_Mmc5_5206, &Nsf::Poke_Mmc5_5206 );
|
|
cpu.Map( 0x5C00, 0x5FF5 ).Set( this, &Nsf::Peek_Mmc5_5C00, &Nsf::Poke_Mmc5_5C00 );
|
|
}
|
|
|
|
if (chips->vrc6)
|
|
{
|
|
cpu.Map( 0x9000 ).Set( &Nsf::Poke_Vrc6_9000 );
|
|
cpu.Map( 0x9001 ).Set( &Nsf::Poke_Vrc6_9001 );
|
|
cpu.Map( 0x9002 ).Set( &Nsf::Poke_Vrc6_9002 );
|
|
cpu.Map( 0xA000 ).Set( &Nsf::Poke_Vrc6_A000 );
|
|
cpu.Map( 0xA001 ).Set( &Nsf::Poke_Vrc6_A001 );
|
|
cpu.Map( 0xA002 ).Set( &Nsf::Poke_Vrc6_A002 );
|
|
cpu.Map( 0xB000 ).Set( &Nsf::Poke_Vrc6_B000 );
|
|
cpu.Map( 0xB001 ).Set( &Nsf::Poke_Vrc6_B001 );
|
|
cpu.Map( 0xB002 ).Set( &Nsf::Poke_Vrc6_B002 );
|
|
}
|
|
|
|
if (chips->vrc7)
|
|
{
|
|
cpu.Map( 0x9010 ).Set( &Nsf::Poke_Vrc7_9010 );
|
|
cpu.Map( 0x9030 ).Set( &Nsf::Poke_Vrc7_9030 );
|
|
}
|
|
|
|
if (chips->n163)
|
|
{
|
|
cpu.Map( 0x4800 ).Set( this, &Nsf::Peek_N163_48, &Nsf::Poke_N163_48 );
|
|
cpu.Map( 0xF800 ).Set( &Nsf::Poke_N163_F8 );
|
|
}
|
|
|
|
if (chips->s5b)
|
|
{
|
|
cpu.Map( 0xC000 ).Set( &Nsf::Poke_S5b_C );
|
|
cpu.Map( 0xE000 ).Set( &Nsf::Poke_S5b_E );
|
|
}
|
|
}
|
|
|
|
cpu.Map( 0xFFFA ).Set( &Nsf::Peek_FFFA );
|
|
cpu.Map( 0xFFFB ).Set( &Nsf::Peek_FFFB );
|
|
cpu.Map( 0xFFFC ).Set( &Nsf::Peek_FFFC );
|
|
cpu.Map( 0xFFFD ).Set( &Nsf::Peek_FFFD );
|
|
|
|
routine.reset = Routine::RESET;
|
|
routine.nmi = Routine::NMI;
|
|
|
|
cpu.SetFrameCycles( cpu.GetModel() == CPU_RP2A03 ? PPU_RP2C02_HVSYNC : cpu.GetModel() == CPU_RP2A07 ? PPU_RP2C07_HVSYNC : PPU_DENDY_HVSYNC );
|
|
}
|
|
|
|
bool Nsf::PowerOff()
|
|
{
|
|
StopSong();
|
|
return true;
|
|
}
|
|
|
|
Result Nsf::SelectSong(const uint song)
|
|
{
|
|
if (song < songs.count)
|
|
{
|
|
if (songs.current != song)
|
|
{
|
|
songs.current = song;
|
|
|
|
if (routine.playing)
|
|
{
|
|
routine.nmi = Routine::NMI;
|
|
apu.ClearBuffers();
|
|
}
|
|
|
|
Api::Nsf::eventCallback( Api::Nsf::EVENT_SELECT_SONG );
|
|
|
|
return RESULT_OK;
|
|
}
|
|
|
|
return RESULT_NOP;
|
|
}
|
|
|
|
return RESULT_ERR_INVALID_PARAM;
|
|
}
|
|
|
|
Result Nsf::PlaySong()
|
|
{
|
|
if (!routine.playing)
|
|
{
|
|
routine.nmi = Routine::NMI;
|
|
routine.playing = true;
|
|
|
|
Api::Nsf::eventCallback( Api::Nsf::EVENT_PLAY_SONG );
|
|
|
|
return RESULT_OK;
|
|
}
|
|
|
|
return RESULT_NOP;
|
|
}
|
|
|
|
Result Nsf::StopSong()
|
|
{
|
|
if (routine.playing)
|
|
{
|
|
routine.playing = false;
|
|
routine.nmi = Routine::NMI;
|
|
apu.ClearBuffers();
|
|
|
|
Api::Nsf::eventCallback( Api::Nsf::EVENT_STOP_SONG );
|
|
|
|
return RESULT_OK;
|
|
}
|
|
|
|
return RESULT_NOP;
|
|
}
|
|
|
|
void Nsf::InitSong()
|
|
{
|
|
std::memset( wrk, 0x00, SIZE_8K );
|
|
|
|
if (chips && chips->mmc5)
|
|
chips->mmc5->ClearExRam();
|
|
|
|
const bool fds = chips && chips->fds;
|
|
|
|
if (addressing.bankSwitched)
|
|
{
|
|
if (fds)
|
|
{
|
|
for (uint i=0; i < 2; ++i)
|
|
cpu.Poke( 0x5FF6+i, banks[6+i] );
|
|
}
|
|
|
|
for (uint i=0; i < 8; ++i)
|
|
cpu.Poke( 0x5FF8+i, banks[i] );
|
|
}
|
|
else if (fds)
|
|
{
|
|
for (dword i=0x6000, j=0; i < 0x10000; j += (i >= (addressing.load & 0xF000U)), i += 0x1000)
|
|
std::memcpy( chips->fds->ram + (i-0x6000), prg.Source().Mem(j * 0x1000), 0x1000 );
|
|
}
|
|
|
|
if (fds)
|
|
{
|
|
cpu.Poke( 0x4089, 0x80 );
|
|
cpu.Poke( 0x408A, 0xE8 );
|
|
}
|
|
|
|
apu.ClearBuffers();
|
|
std::memset( cpu.GetRam(), 0x00, Cpu::RAM_SIZE );
|
|
|
|
for (uint i=0x4000; i <= 0x4013; ++i)
|
|
cpu.Poke( i, 0x00 );
|
|
|
|
cpu.Poke( 0x4015, 0x0F );
|
|
cpu.Poke( 0x4017, 0xC0 );
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
void Nsf::BeginFrame()
|
|
{
|
|
routine.jmp = (routine.playing ? 0xFA : 0xFD);
|
|
|
|
if (routine.nmi)
|
|
cpu.DoNMI(0);
|
|
}
|
|
|
|
Cycle Nsf::Chips::Clock(Cycle rateCycles,Cycle rateClock,const Cycle targetCycles)
|
|
{
|
|
if (clocks.next != Cpu::CYCLE_MAX)
|
|
{
|
|
NST_ASSERT( (mmc5 || fds) && (clocks.mmc5 != Cpu::CYCLE_MAX || clocks.fds != Cpu::CYCLE_MAX) );
|
|
|
|
if (clocks.mmc5 == clocks.next)
|
|
clocks.mmc5 = mmc5->Clock( rateCycles, rateClock, targetCycles ) - rateCycles;
|
|
|
|
if (clocks.fds == clocks.next)
|
|
clocks.fds = fds->Clock( rateCycles, rateClock, targetCycles ) - rateCycles;
|
|
|
|
clocks.next = NST_MIN(clocks.mmc5,clocks.fds);
|
|
|
|
rateCycles += clocks.next;
|
|
|
|
return rateCycles;
|
|
}
|
|
else
|
|
{
|
|
NST_ASSERT( !mmc5 && !fds );
|
|
|
|
return Channel::Clock( rateCycles, rateClock, targetCycles );
|
|
}
|
|
}
|
|
|
|
Nsf::Chips::Sample Nsf::Chips::GetSample()
|
|
{
|
|
return
|
|
(
|
|
(mmc5 ? mmc5->GetSample() : 0) +
|
|
(vrc6 ? vrc6->GetSample() : 0) +
|
|
(vrc7 ? vrc7->GetSample() : 0) +
|
|
(fds ? fds->GetSample() : 0) +
|
|
(s5b ? s5b->GetSample() : 0) +
|
|
(n163 ? n163->GetSample() : 0)
|
|
);
|
|
}
|
|
|
|
inline uint Nsf::FetchLast(uint offset) const
|
|
{
|
|
NST_ASSERT( offset <= 0xFFF );
|
|
return offset[chips && chips->fds ? chips->fds->ram + (sizeof(array(chips->fds->ram))-SIZE_4K) : prg[7]];
|
|
}
|
|
|
|
NES_PEEK(Nsf,FFFA)
|
|
{
|
|
return routine.nmi ? routine.nmi &= Routine::NMI_B, routine.playing ? 0xEC : 0xFD : FetchLast(0xFFA);
|
|
}
|
|
|
|
NES_PEEK(Nsf,FFFB)
|
|
{
|
|
return routine.nmi ? routine.nmi &= Routine::NMI_A, 0x38 : FetchLast(0xFFB);
|
|
}
|
|
|
|
NES_PEEK(Nsf,FFFC)
|
|
{
|
|
return routine.reset ? routine.reset &= Routine::RESET_B, 0xFD : FetchLast(0xFFC);
|
|
}
|
|
|
|
NES_PEEK(Nsf,FFFD)
|
|
{
|
|
return routine.reset ? routine.reset &= Routine::RESET_A, 0x38 : FetchLast(0xFFD);
|
|
}
|
|
|
|
NES_PEEK(Nsf,38EC)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
|
|
InitSong();
|
|
return LDA;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38ED)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return songs.current;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38EE)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return LDX;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38EF)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return 0xFC;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F0)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return TXS;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F1)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return LDX;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F2)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return cpu.GetModel() == CPU_RP2A07;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F3)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return JSR;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F4)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return addressing.init & 0xFFU;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F5)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return addressing.init >> 8;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F6)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return SEI;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F7)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
routine.jmp = 0xFD;
|
|
return JMP;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F8)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return 0xFD;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38F9)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return 0x38;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38FA)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
routine.jmp = 0xFD;
|
|
return JSR;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38FB)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return addressing.play & 0xFFU;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38FC)
|
|
{
|
|
NST_VERIFY( routine.playing );
|
|
return addressing.play >> 8;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38FD)
|
|
{
|
|
return JMP;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38FE)
|
|
{
|
|
return routine.jmp;
|
|
}
|
|
|
|
NES_PEEK(Nsf,38FF)
|
|
{
|
|
return 0x38;
|
|
}
|
|
|
|
NES_POKE_D(Nsf,4017)
|
|
{
|
|
apu.WriteFrameCtrl( data );
|
|
}
|
|
|
|
NES_PEEK_A (Nsf,Nop) { return address >> 8; }
|
|
NES_POKE (Nsf,Nop) {}
|
|
|
|
NES_PEEK_A (Nsf,Prg_8) { return prg[0][address - 0x8000]; }
|
|
NES_PEEK_A (Nsf,Prg_9) { return prg[1][address - 0x9000]; }
|
|
NES_PEEK_A (Nsf,Prg_A) { return prg[2][address - 0xA000]; }
|
|
NES_PEEK_A (Nsf,Prg_B) { return prg[3][address - 0xB000]; }
|
|
NES_PEEK_A (Nsf,Prg_C) { return prg[4][address - 0xC000]; }
|
|
NES_PEEK_A (Nsf,Prg_D) { return prg[5][address - 0xD000]; }
|
|
NES_PEEK_A (Nsf,Prg_E) { return prg[6][address - 0xE000]; }
|
|
NES_PEEK_A (Nsf,Prg_F) { return prg[7][address - 0xF000]; }
|
|
|
|
NES_PEEK_A (Nsf,Wrk) { return wrk[address - 0x6000]; }
|
|
NES_POKE_AD (Nsf,Wrk) { wrk[address - 0x6000] = data; }
|
|
|
|
NES_POKE_D (Nsf,5FF8) { prg.SwapBank<SIZE_4K,0x0000>( data ); }
|
|
NES_POKE_D (Nsf,5FF9) { prg.SwapBank<SIZE_4K,0x1000>( data ); }
|
|
NES_POKE_D (Nsf,5FFA) { prg.SwapBank<SIZE_4K,0x2000>( data ); }
|
|
NES_POKE_D (Nsf,5FFB) { prg.SwapBank<SIZE_4K,0x3000>( data ); }
|
|
NES_POKE_D (Nsf,5FFC) { prg.SwapBank<SIZE_4K,0x4000>( data ); }
|
|
NES_POKE_D (Nsf,5FFD) { prg.SwapBank<SIZE_4K,0x5000>( data ); }
|
|
NES_POKE_D (Nsf,5FFE) { prg.SwapBank<SIZE_4K,0x6000>( data ); }
|
|
NES_POKE_D (Nsf,5FFF) { prg.SwapBank<SIZE_4K,0x7000>( data ); }
|
|
|
|
NES_PEEK_A (Nsf,Fds_4040) { return chips->fds->ReadWaveData( address ); }
|
|
NES_POKE_AD (Nsf,Fds_4040) { chips->fds->WriteWaveData( address, data ); }
|
|
NES_POKE_D (Nsf,Fds_4080) { chips->fds->WriteReg0( data ); }
|
|
NES_POKE_D (Nsf,Fds_4082) { chips->fds->WriteReg1( data ); }
|
|
NES_POKE_D (Nsf,Fds_4083) { chips->fds->WriteReg2( data ); }
|
|
NES_POKE_D (Nsf,Fds_4084) { chips->fds->WriteReg3( data ); }
|
|
NES_POKE_D (Nsf,Fds_4085) { chips->fds->WriteReg4( data ); }
|
|
NES_POKE_D (Nsf,Fds_4086) { chips->fds->WriteReg5( data ); }
|
|
NES_POKE_D (Nsf,Fds_4087) { chips->fds->WriteReg6( data ); }
|
|
NES_POKE_D (Nsf,Fds_4088) { chips->fds->WriteReg7( data ); }
|
|
NES_POKE_D (Nsf,Fds_4089) { chips->fds->WriteReg8( data ); }
|
|
NES_POKE_D (Nsf,Fds_408A) { chips->fds->WriteReg9( data ); }
|
|
NES_PEEK (Nsf,Fds_4090) { return chips->fds->ReadVolumeGain(); }
|
|
NES_PEEK (Nsf,Fds_4092) { return chips->fds->ReadSweepGain(); }
|
|
NES_POKE_D (Nsf,Fds_5FF6) { chips->fds->SwapBank( prg, 0, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FF7) { chips->fds->SwapBank( prg, 1, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FF8) { chips->fds->SwapBank( prg, 2, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FF9) { chips->fds->SwapBank( prg, 3, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FFA) { chips->fds->SwapBank( prg, 4, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FFB) { chips->fds->SwapBank( prg, 5, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FFC) { chips->fds->SwapBank( prg, 6, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FFD) { chips->fds->SwapBank( prg, 7, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FFE) { chips->fds->SwapBank( prg, 8, data ); }
|
|
NES_POKE_D (Nsf,Fds_5FFF) { chips->fds->SwapBank( prg, 9, data ); }
|
|
NES_PEEK_A (Nsf,Fds_Ram) { return chips->fds->ram[address - 0x6000]; }
|
|
NES_POKE_AD (Nsf,Fds_Ram) { chips->fds->ram[address - 0x6000] = data; }
|
|
|
|
NES_POKE_D (Nsf,Mmc5_5000) { chips->mmc5->WriteSquareReg0( 0, data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5002) { chips->mmc5->WriteSquareReg1( 0, data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5003) { chips->mmc5->WriteSquareReg2( 0, data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5004) { chips->mmc5->WriteSquareReg0( 1, data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5006) { chips->mmc5->WriteSquareReg1( 1, data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5007) { chips->mmc5->WriteSquareReg2( 1, data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5010) { chips->mmc5->WritePcmReg0( data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5011) { chips->mmc5->WritePcmReg1( data ); }
|
|
NES_POKE_D (Nsf,Mmc5_5015) { chips->mmc5->WriteCtrl( data ); }
|
|
NES_PEEK (Nsf,Mmc5_5015) { return chips->mmc5->ReadCtrl(); }
|
|
NES_PEEK (Nsf,Mmc5_5205) { return (chips->mmc5->mul[0] * chips->mmc5->mul[1]) >> 0 & 0xFF; }
|
|
NES_PEEK (Nsf,Mmc5_5206) { return (chips->mmc5->mul[0] * chips->mmc5->mul[1]) >> 8 & 0xFF; }
|
|
NES_POKE_D (Nsf,Mmc5_5205) { chips->mmc5->mul[0] = data; }
|
|
NES_POKE_D (Nsf,Mmc5_5206) { chips->mmc5->mul[1] = data; }
|
|
NES_PEEK_A (Nsf,Mmc5_5C00) { return chips->mmc5->exRam[address - 0x5C00]; }
|
|
NES_POKE_AD (Nsf,Mmc5_5C00) { chips->mmc5->exRam[address - 0x5C00] = data; }
|
|
|
|
NES_POKE_D (Nsf,Vrc6_9000) { chips->vrc6->WriteSquareReg0( 0, data ); }
|
|
NES_POKE_D (Nsf,Vrc6_9001) { chips->vrc6->WriteSquareReg1( 0, data ); }
|
|
NES_POKE_D (Nsf,Vrc6_9002) { chips->vrc6->WriteSquareReg2( 0, data ); }
|
|
NES_POKE_D (Nsf,Vrc6_A000) { chips->vrc6->WriteSquareReg0( 1, data ); }
|
|
NES_POKE_D (Nsf,Vrc6_A001) { chips->vrc6->WriteSquareReg1( 1, data ); }
|
|
NES_POKE_D (Nsf,Vrc6_A002) { chips->vrc6->WriteSquareReg2( 1, data ); }
|
|
NES_POKE_D (Nsf,Vrc6_B000) { chips->vrc6->WriteSawReg0( data ); }
|
|
NES_POKE_D (Nsf,Vrc6_B001) { chips->vrc6->WriteSawReg1( data ); }
|
|
NES_POKE_D (Nsf,Vrc6_B002) { chips->vrc6->WriteSawReg2( data ); }
|
|
|
|
NES_POKE_D (Nsf,Vrc7_9010) { chips->vrc7->SelectReg( data ); }
|
|
NES_POKE_D (Nsf,Vrc7_9030) { chips->vrc7->WriteReg( data ); }
|
|
|
|
NES_POKE_D (Nsf,S5b_C) { chips->s5b->SelectReg( data ); }
|
|
NES_POKE_D (Nsf,S5b_E) { chips->s5b->WriteReg( data ); }
|
|
|
|
NES_PEEK (Nsf,N163_48) { return chips->n163->ReadData(); }
|
|
NES_POKE_D (Nsf,N163_48) { chips->n163->WriteData( data ); }
|
|
NES_POKE_D (Nsf,N163_F8) { chips->n163->WriteAddress( data ); }
|
|
}
|
|
}
|