mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
381 lines
No EOL
11 KiB
C++
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);
|
|
} |