Mesen2/Core/NES/Mappers/NSF/NsfMapper.cpp

381 lines
No EOL
11 KiB
C++

#include "pch.h"
#include <random>
#include "NES/Mappers/NSF/NsfMapper.h"
#include "NES/NesConstants.h"
#include "NES/NesConsole.h"
#include "NES/NesCpu.h"
#include "NES/NesMemoryManager.h"
#include "NES/Mappers/Audio/Mmc5Audio.h"
#include "NES/Mappers/Audio/Vrc6Audio.h"
#include "NES/Mappers/Audio/Vrc7Audio.h"
#include "NES/Mappers/FDS/FdsAudio.h"
#include "NES/Mappers/Audio/Namco163Audio.h"
#include "NES/Mappers/Audio/Sunsoft5bAudio.h"
#include "Shared/EmuSettings.h"
#include "Shared/SystemActionManager.h"
#include "Utilities/Serializer.h"
NsfMapper::NsfMapper()
{
}
NsfMapper::~NsfMapper()
{
}
void NsfMapper::InitMapper()
{
_settings = _console->GetEmulator()->GetSettings();
//Clear all register settings
RemoveRegisterRange(0x0000, 0xFFFF, MemoryOperation::Any);
AddRegisterRange(0x3F00, 0x3F1F, MemoryOperation::Read);
AddRegisterRange(0x3F00, 0x3F00, MemoryOperation::Write);
//NSF registers
AddRegisterRange(0x5FF6, 0x5FFF, MemoryOperation::Write);
}
void NsfMapper::InitMapper(RomData& romData)
{
_nsfHeader = romData.Info.NsfInfo;
_songNumber = _nsfHeader.StartingSong - 1;
if(_nsfHeader.SoundChips & NsfSoundChips::MMC5) {
AddRegisterRange(0x5000, 0x5015, MemoryOperation::Write); //Registers
AddRegisterRange(0x5205, 0x5206, MemoryOperation::Any); //Multiplication
SetCpuMemoryMapping(0x5C00, 0x5FFF, PrgMemoryType::WorkRam, 0x3000, MemoryAccessType::ReadWrite); //Exram
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC6) {
AddRegisterRange(0x9000, 0x9003, MemoryOperation::Write);
AddRegisterRange(0xA000, 0xA002, MemoryOperation::Write);
AddRegisterRange(0xB000, 0xB002, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC7) {
AddRegisterRange(0x9010, 0x9010, MemoryOperation::Write);
AddRegisterRange(0x9030, 0x9030, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::Namco) {
AddRegisterRange(0x4800, 0x4FFF, MemoryOperation::Any);
AddRegisterRange(0xF800, 0xFFFF, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) {
AddRegisterRange(0xC000, 0xFFFF, MemoryOperation::Write);
}
if(_nsfHeader.SoundChips & NsfSoundChips::FDS) {
AddRegisterRange(0x4040, 0x4092, MemoryOperation::Any);
}
//Reset/IRQ vector
AddRegisterRange(0xFFFC, 0xFFFF, MemoryOperation::Read);
//Set PLAY/INIT addresses in NSF code
_nsfBios[0x02] = _nsfHeader.InitAddress & 0xFF;
_nsfBios[0x03] = (_nsfHeader.InitAddress >> 8) & 0xFF;
_nsfBios[0x14] = _nsfHeader.PlayAddress & 0xFF;
_nsfBios[0x15] = (_nsfHeader.PlayAddress >> 8) & 0xFF;
}
void NsfMapper::Reset(bool softReset)
{
if(!softReset) {
_songNumber = _nsfHeader.StartingSong - 1;
}
_mmc5Audio.reset(new Mmc5Audio(_console));
_vrc6Audio.reset(new Vrc6Audio(_console));
_vrc7Audio.reset(new Vrc7Audio(_console));
_fdsAudio.reset(new FdsAudio(_console));
_namcoAudio.reset(new Namco163Audio(_console));
_sunsoftAudio.reset(new Sunsoft5bAudio(_console));
}
void NsfMapper::OnAfterResetPowerOn()
{
//Do this after reset/power on sequence is over, otherwise cpu reset will reset all these values
//INIT logic
NesMemoryManager* mm = _console->GetMemoryManager();
//Clear internal + work RAM
memset(mm->GetInternalRam(), 0, GetInternalRamSize());
memset(_workRam, 0, _workRamSize);
//Reset APU
for(uint16_t i = 0x4000; i < 0x4013; i++) {
mm->Write(i, 0, MemoryOperationType::Write);
}
mm->Write(0x4015, 0, MemoryOperationType::Write);
mm->Write(0x4015, 0x0F, MemoryOperationType::Write);
mm->Write(0x4017, 0x40, MemoryOperationType::Write);
//Disable PPU
mm->Write(0x2000, 0, MemoryOperationType::Write);
mm->Write(0x2001, 0, MemoryOperationType::Write);
//Setup bankswitching
_hasBankSwitching = HasBankSwitching();
if(!_hasBankSwitching) {
//Update bank config to select the right banks on init when no bankswitching is setup in header
int8_t startBank = (_nsfHeader.LoadAddress / 0x1000);
for(int32_t i = 0; i < (int32_t)GetPrgPageCount(); i++) {
if((startBank + i) > 0x0F) {
break;
}
if(startBank + i - 8 >= 0) {
_nsfHeader.BankSetup[startBank + i - 8] = i;
}
}
}
for(uint16_t i = 0; i < 8; i++) {
WriteRegister(0x5FF8 + i, _nsfHeader.BankSetup[i]);
}
if(_nsfHeader.SoundChips & NsfSoundChips::FDS) {
WriteRegister(0x5FF6, _nsfHeader.BankSetup[6]);
WriteRegister(0x5FF7, _nsfHeader.BankSetup[7]);
}
NesCpuState& state = _console->GetCpu()->GetState();
state.A = _songNumber;
state.X = (_nsfHeader.Flags & 0x01) ? 1 : 0; //PAL = 1, NTSC = 0
state.Y = 0;
state.SP = 0xFD;
//Disable Frame Counter & DMC interrupts
_console->GetCpu()->SetIrqMask((uint8_t)IRQSource::External);
_irqCounter = 0;
}
void NsfMapper::GetMemoryRanges(MemoryRanges& ranges)
{
BaseMapper::GetMemoryRanges(ranges);
//Allows us to override the PPU's range (0x3F00 - 0x3F1F)
ranges.SetAllowOverride();
ranges.AddHandler(MemoryOperation::Read, 0x3F00, 0x3F1F);
ranges.AddHandler(MemoryOperation::Write, 0x3F00, 0x3F1F);
}
uint32_t NsfMapper::GetIrqReloadValue()
{
switch(_console->GetRegion()) {
default:
case ConsoleRegion::Ntsc: return (uint16_t)(_nsfHeader.PlaySpeedNtsc * (NesConstants::ClockRateNtsc / 1000000.0)); break;
case ConsoleRegion::Pal: return (uint16_t)(_nsfHeader.PlaySpeedPal * (NesConstants::ClockRatePal / 1000000.0)); break;
case ConsoleRegion::Dendy: return (uint16_t)(_nsfHeader.PlaySpeedPal * (NesConstants::ClockRateDendy / 1000000.0)); break;
}
}
bool NsfMapper::HasBankSwitching()
{
for(int i = 0; i < 8; i++) {
if(_nsfHeader.BankSetup[i] != 0) {
return true;
}
}
return false;
}
void NsfMapper::TriggerIrq()
{
_console->GetCpu()->SetIrqSource(IRQSource::External);
}
void NsfMapper::ClearIrq()
{
_console->GetCpu()->ClearIrqSource(IRQSource::External);
}
void NsfMapper::ProcessCpuClock()
{
BaseProcessCpuClock();
if(_irqCounter > 0) {
_irqCounter--;
if(_irqCounter == 0) {
_irqCounter = GetIrqReloadValue();
TriggerIrq();
}
}
if(_nsfHeader.SoundChips & NsfSoundChips::MMC5) {
_mmc5Audio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC6) {
_vrc6Audio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::VRC7) {
_vrc7Audio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::Namco) {
_namcoAudio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) {
_sunsoftAudio->Clock();
}
if(_nsfHeader.SoundChips & NsfSoundChips::FDS) {
_fdsAudio->Clock();
}
}
uint8_t NsfMapper::ReadRegister(uint16_t addr)
{
if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) {
return _fdsAudio->ReadRegister(addr);
} else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && addr >= 0x4800 && addr <= 0x4FFF) {
return _namcoAudio->ReadRegister(addr);
} else if(addr >= 0x3F00 && addr <= 0x3F1F) {
return _nsfBios[addr & 0x1F];
} else {
switch(addr) {
case 0x5205: return (_mmc5MultiplierValues[0] * _mmc5MultiplierValues[1]) & 0xFF;
case 0x5206: return (_mmc5MultiplierValues[0] * _mmc5MultiplierValues[1]) >> 8;
//Reset/irq vectors
case 0xFFFC: return 0x00;
case 0xFFFD: return 0x3F;
case 0xFFFE: return 0x10;
case 0xFFFF: return 0x3F;
}
}
return _console->GetMemoryManager()->GetOpenBus();
}
void NsfMapper::WriteRegister(uint16_t addr, uint8_t value)
{
if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) {
_fdsAudio->WriteRegister(addr, value);
} else if((_nsfHeader.SoundChips & NsfSoundChips::MMC5) && addr >= 0x5000 && addr <= 0x5015) {
_mmc5Audio->WriteRegister(addr, value);
} else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && ((addr >= 0x4800 && addr <= 0x4FFF) || (addr >= 0xF800 && addr <= 0xFFFF))) {
_namcoAudio->WriteRegister(addr, value);
//Technically we should call this, but doing so breaks some NSFs
/*if(addr >= 0xF800 && _nsfHeader.SoundChips & NsfSoundChips::Sunsoft) {
_sunsoftAudio.WriteRegister(addr, value);
}*/
} else if((_nsfHeader.SoundChips & NsfSoundChips::Sunsoft) && addr >= 0xC000 && addr <= 0xFFFF) {
_sunsoftAudio->WriteRegister(addr, value);
} else {
switch(addr) {
case 0x3F00:
_irqCounter = GetIrqReloadValue();
ClearIrq();
break;
//MMC5 multiplication
case 0x5205: _mmc5MultiplierValues[0] = value; break;
case 0x5206: _mmc5MultiplierValues[1] = value; break;
case 0x5FF6:
SetCpuMemoryMapping(0x6000, 0x6FFF, value, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite);
break;
case 0x5FF7:
SetCpuMemoryMapping(0x7000, 0x7FFF, value, PrgMemoryType::PrgRom, MemoryAccessType::ReadWrite);
break;
case 0x5FF8: case 0x5FF9: case 0x5FFA: case 0x5FFB:
case 0x5FFC: case 0x5FFD: case 0x5FFE: case 0x5FFF:
SetCpuMemoryMapping(0x8000 + (addr & 0x07) * 0x1000, 0x8FFF + (addr & 0x07) * 0x1000, value, PrgMemoryType::PrgRom, (addr <= 0x5FFD && (_nsfHeader.SoundChips & NsfSoundChips::FDS)) ? MemoryAccessType::ReadWrite : MemoryAccessType::Read);
break;
case 0x9000: case 0x9001: case 0x9002: case 0x9003: case 0xA000: case 0xA001: case 0xA002: case 0xB000: case 0xB001: case 0xB002:
_vrc6Audio->WriteRegister(addr, value);
break;
case 0x9010: case 0x9030:
_vrc7Audio->WriteReg(addr, value);
break;
}
}
}
void NsfMapper::SelectTrack(uint8_t trackNumber)
{
if(trackNumber < _nsfHeader.TotalSongs) {
_songNumber = trackNumber;
//Need to change track while running
//Some NSFs keep the interrupt flag on at all times, preventing us from triggering an IRQ to change tracks
//Forcing the console to reset ensures changing tracks always works, even with a bad NSF file
_emu->GetSystemActionManager()->Reset();
}
}
uint8_t NsfMapper::GetCurrentTrack()
{
return _songNumber;
}
NsfHeader NsfMapper::GetNsfHeader()
{
return _nsfHeader;
}
AudioTrackInfo NsfMapper::GetAudioTrackInfo()
{
AudioTrackInfo track = {};
track.Artist = _nsfHeader.ArtistName;
string copyright = _nsfHeader.CopyrightHolder;
track.Comment = (copyright.size() > 0 ? copyright + " " : "") + _nsfHeader.RipperName;
track.GameTitle = _nsfHeader.SongName;
track.SongTitle = _nsfHeader.TrackNames.size() > _songNumber ? _nsfHeader.TrackNames[_songNumber] : "";
track.Position = _console->GetPpuFrame().FrameCount / _console->GetFps();
if(_nsfHeader.TrackLength[_songNumber] > 0) {
track.Length = _nsfHeader.TrackLength[_songNumber] / 1000.0 + _nsfHeader.TrackFade[_songNumber] / 1000.0;
track.FadeLength = _nsfHeader.TrackFade[_songNumber] / 1000.0;
}
track.TrackNumber = _songNumber + 1;
track.TrackCount = _nsfHeader.TotalSongs;
return track;
}
void NsfMapper::ProcessAudioPlayerAction(AudioPlayerActionParams p)
{
int selectedTrack = _songNumber;
switch(p.Action) {
case AudioPlayerAction::NextTrack: selectedTrack++; break;
case AudioPlayerAction::PrevTrack:
if(GetAudioTrackInfo().Position < 2) {
selectedTrack--;
}
break;
case AudioPlayerAction::SelectTrack: selectedTrack = (int)p.TrackNumber; break;
}
if(selectedTrack < 0) {
selectedTrack = _nsfHeader.TotalSongs - 1;
} else if(selectedTrack >= _nsfHeader.TotalSongs) {
selectedTrack = 0;
}
SelectTrack(selectedTrack);
}
void NsfMapper::Serialize(Serializer& s)
{
BaseMapper::Serialize(s);
SV(_mmc5Audio);
SV(_vrc6Audio);
SV(_vrc7Audio);
SV(_fdsAudio);
SV(_namcoAudio);
SV(_sunsoftAudio);
SV(_irqCounter); SV(_mmc5MultiplierValues[0]); SV(_mmc5MultiplierValues[1]); SV(_hasBankSwitching); SV(_songNumber);
}