mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
573 lines
No EOL
15 KiB
C++
573 lines
No EOL
15 KiB
C++
#include "pch.h"
|
|
#include "SNES/SnesConsole.h"
|
|
#include "SNES/SnesCpu.h"
|
|
#include "SNES/SnesPpu.h"
|
|
#include "SNES/Spc.h"
|
|
#include "SNES/InternalRegisters.h"
|
|
#include "SNES/SnesControlManager.h"
|
|
#include "SNES/SnesMemoryManager.h"
|
|
#include "SNES/SnesDmaController.h"
|
|
#include "SNES/BaseCartridge.h"
|
|
#include "SNES/RamHandler.h"
|
|
#include "SNES/CartTypes.h"
|
|
#include "SNES/SpcFileData.h"
|
|
#include "SNES/SnesDefaultVideoFilter.h"
|
|
#include "SNES/SnesNtscFilter.h"
|
|
#include "Gameboy/Gameboy.h"
|
|
#include "Gameboy/GbPpu.h"
|
|
#include "Debugger/Debugger.h"
|
|
#include "Debugger/DebugTypes.h"
|
|
#include "SNES/SnesState.h"
|
|
#include "SNES/Coprocessors/DSP/NecDsp.h"
|
|
#include "SNES/Coprocessors/MSU1/Msu1.h"
|
|
#include "SNES/Coprocessors/SA1/Sa1.h"
|
|
#include "SNES/Coprocessors/GSU/Gsu.h"
|
|
#include "SNES/Coprocessors/CX4/Cx4.h"
|
|
#include "SNES/Coprocessors/ST018/St018.h"
|
|
#include "Shared/Emulator.h"
|
|
#include "Shared/TimingInfo.h"
|
|
#include "Shared/EmuSettings.h"
|
|
#include "Shared/BaseControlManager.h"
|
|
#include "Utilities/Serializer.h"
|
|
#include "Utilities/Timer.h"
|
|
#include "Utilities/VirtualFile.h"
|
|
#include "Utilities/PlatformUtilities.h"
|
|
#include "Utilities/FolderUtilities.h"
|
|
#include "Shared/EventType.h"
|
|
#include "SNES/RegisterHandlerA.h"
|
|
#include "SNES/RegisterHandlerB.h"
|
|
#include "Utilities/ArchiveReader.h"
|
|
|
|
SnesConsole::SnesConsole(Emulator* emu)
|
|
{
|
|
_emu = emu;
|
|
_settings = emu->GetSettings();
|
|
}
|
|
|
|
SnesConsole::~SnesConsole()
|
|
{
|
|
}
|
|
|
|
void SnesConsole::Initialize()
|
|
{
|
|
}
|
|
|
|
void SnesConsole::Release()
|
|
{
|
|
}
|
|
|
|
void SnesConsole::RunFrame()
|
|
{
|
|
UpdateRegion();
|
|
|
|
_frameRunning = true;
|
|
|
|
while(_frameRunning) {
|
|
_cpu->Exec();
|
|
}
|
|
|
|
_spc->ProcessEndFrame();
|
|
}
|
|
|
|
void SnesConsole::ProcessEndOfFrame()
|
|
{
|
|
_cart->RunCoprocessors();
|
|
if(_cart->GetCoprocessor()) {
|
|
_cart->GetCoprocessor()->ProcessEndOfFrame();
|
|
}
|
|
|
|
//Run the SPC at least once per frame to prevent issues (buffer overflow)
|
|
//when a very long DMA transfer is running across multiple frames.
|
|
//(RunFrame above can run more than one frame in this scenario, which could cause crashes)
|
|
_spc->ProcessEndFrame();
|
|
|
|
_emu->ProcessEndOfFrame();
|
|
|
|
_controlManager->UpdateControlDevices();
|
|
_controlManager->UpdateInputState();
|
|
_internalRegisters->SetAutoJoypadReadClock();
|
|
_frameRunning = false;
|
|
}
|
|
|
|
void SnesConsole::Reset()
|
|
{
|
|
_dmaController->Reset();
|
|
_internalRegisters->Reset();
|
|
_memoryManager->Reset();
|
|
_spc->Reset();
|
|
_ppu->Reset();
|
|
_cart->Reset();
|
|
_controlManager->Reset(true);
|
|
|
|
//Reset cart before CPU to ensure correct memory mappings when fetching reset vector
|
|
_cpu->Reset();
|
|
|
|
//After reset, run the PPU/etc ahead of the CPU (simulates delay CPU takes to get out of reset)
|
|
_memoryManager->IncMasterClockStartup();
|
|
}
|
|
|
|
LoadRomResult SnesConsole::LoadRom(VirtualFile& romFile)
|
|
{
|
|
SnesConfig config = _settings->GetSnesConfig();
|
|
LoadRomResult loadResult = LoadRomResult::UnknownType;
|
|
|
|
unique_ptr<BaseCartridge> cart = BaseCartridge::CreateCartridge(this, romFile);
|
|
if(cart) {
|
|
_cart.swap(cart);
|
|
|
|
UpdateRegion();
|
|
|
|
_internalRegisters.reset(new InternalRegisters());
|
|
_memoryManager.reset(new SnesMemoryManager());
|
|
_ppu.reset(new SnesPpu(_emu, this));
|
|
_controlManager.reset(new SnesControlManager(this));
|
|
_dmaController.reset(new SnesDmaController(_memoryManager.get()));
|
|
_spc.reset(new Spc(this));
|
|
|
|
_msu1.reset(Msu1::Init(_emu, romFile, _spc.get()));
|
|
|
|
if(_cart->GetSpcData()) {
|
|
if(!LoadSpcFile(romFile)) {
|
|
return LoadRomResult::Failure;
|
|
}
|
|
}
|
|
|
|
_cpu.reset(new SnesCpu(this));
|
|
_memoryManager->Initialize(this);
|
|
_internalRegisters->Initialize(this);
|
|
|
|
_ppu->PowerOn();
|
|
_cpu->PowerOn();
|
|
|
|
//After power on, run the PPU/etc ahead of the CPU (simulates delay CPU takes to get out of reset)
|
|
_memoryManager->IncMasterClockStartup();
|
|
|
|
loadResult = LoadRomResult::Success;
|
|
}
|
|
|
|
//Loading a cartridge can alter the SNES ram init settings - restore their original values here
|
|
_settings->SetSnesConfig(config);
|
|
return loadResult;
|
|
}
|
|
|
|
bool SnesConsole::LoadSpcFile(VirtualFile& romFile)
|
|
{
|
|
_spc->LoadSpcFile(_cart->GetSpcData());
|
|
if(romFile.IsArchive()) {
|
|
string archivePath = romFile.GetFilePath();
|
|
unique_ptr<ArchiveReader> reader = ArchiveReader::GetReader(archivePath);
|
|
if(reader) {
|
|
for(string& spcFile : reader->GetFileList({ ".spc" })) {
|
|
_spcPlaylist.push_back((string)VirtualFile(archivePath, spcFile));
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
_spcPlaylist = FolderUtilities::GetFilesInFolder(romFile.GetFolderPath(), { ".spc" }, false);
|
|
}
|
|
|
|
std::sort(_spcPlaylist.begin(), _spcPlaylist.end());
|
|
auto result = std::find(_spcPlaylist.begin(), _spcPlaylist.end(), (string)romFile);
|
|
if(result == _spcPlaylist.end()) {
|
|
_spcPlaylist.push_back((string)romFile);
|
|
}
|
|
_spcTrackNumber = (uint32_t)std::distance(_spcPlaylist.begin(), result);
|
|
return true;
|
|
}
|
|
|
|
uint64_t SnesConsole::GetMasterClock()
|
|
{
|
|
return _memoryManager->GetMasterClock();
|
|
}
|
|
|
|
uint32_t SnesConsole::GetMasterClockRate()
|
|
{
|
|
return _masterClockRate;
|
|
}
|
|
|
|
ConsoleRegion SnesConsole::GetRegion()
|
|
{
|
|
return _region;
|
|
}
|
|
|
|
ConsoleType SnesConsole::GetConsoleType()
|
|
{
|
|
return ConsoleType::Snes;
|
|
}
|
|
|
|
void SnesConsole::UpdateRegion()
|
|
{
|
|
switch(_settings->GetSnesConfig().Region) {
|
|
case ConsoleRegion::Auto: _region = _cart->GetRegion(); break;
|
|
|
|
default:
|
|
case ConsoleRegion::Ntsc: _region = ConsoleRegion::Ntsc; break;
|
|
case ConsoleRegion::Pal: _region = ConsoleRegion::Pal; break;
|
|
}
|
|
|
|
_masterClockRate = _region == ConsoleRegion::Pal ? 21281370 : 21477270;
|
|
}
|
|
|
|
double SnesConsole::GetFps()
|
|
{
|
|
if(_region == ConsoleRegion::Ntsc) {
|
|
return 60.0988118623484;
|
|
} else {
|
|
return 50.0069789081886;
|
|
}
|
|
}
|
|
|
|
PpuFrameInfo SnesConsole::GetPpuFrame()
|
|
{
|
|
PpuFrameInfo frame = {};
|
|
frame.FrameBuffer = (uint8_t*)_ppu->GetScreenBuffer();
|
|
frame.Width = _ppu->IsHighResOutput() ? 512 : 256;
|
|
frame.Height = _ppu->IsHighResOutput() ? 478 : 239;
|
|
frame.FrameBufferSize = frame.Width * frame.Height * sizeof(uint16_t);
|
|
frame.FrameCount = _ppu->GetFrameCount();
|
|
frame.FirstScanline = 0;
|
|
frame.ScanlineCount = _ppu->GetVblankEndScanline() + 1;
|
|
frame.CycleCount = 341;
|
|
return frame;
|
|
}
|
|
|
|
TimingInfo SnesConsole::GetTimingInfo(CpuType cpuType)
|
|
{
|
|
if(cpuType == CpuType::Gameboy && _cart->GetGameboy()) {
|
|
return _cart->GetGameboy()->GetTimingInfo(cpuType);
|
|
}
|
|
return IConsole::GetTimingInfo(cpuType);
|
|
}
|
|
|
|
vector<CpuType> SnesConsole::GetCpuTypes()
|
|
{
|
|
vector<CpuType> cpuTypes = { CpuType::Snes, CpuType::Spc };
|
|
if(_cart->GetGsu()) {
|
|
cpuTypes.push_back(CpuType::Gsu);
|
|
} else if(_cart->GetDsp()) {
|
|
cpuTypes.push_back(CpuType::NecDsp);
|
|
} else if(_cart->GetCx4()) {
|
|
cpuTypes.push_back(CpuType::Cx4);
|
|
} else if(_cart->GetGameboy()) {
|
|
cpuTypes.push_back(CpuType::Gameboy);
|
|
} else if(_cart->GetSa1()) {
|
|
cpuTypes.push_back(CpuType::Sa1);
|
|
} else if(_cart->GetSt018()) {
|
|
cpuTypes.push_back(CpuType::St018);
|
|
}
|
|
return cpuTypes;
|
|
}
|
|
|
|
void SnesConsole::SaveBattery()
|
|
{
|
|
if(_cart) {
|
|
_cart->SaveBattery();
|
|
}
|
|
}
|
|
|
|
BaseVideoFilter* SnesConsole::GetVideoFilter(bool getDefaultFilter)
|
|
{
|
|
if(getDefaultFilter || GetRomFormat() == RomFormat::Spc) {
|
|
return new SnesDefaultVideoFilter(_emu);
|
|
}
|
|
|
|
VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter;
|
|
|
|
switch(filterType) {
|
|
case VideoFilterType::NtscBlargg:
|
|
case VideoFilterType::NtscBisqwit:
|
|
return new SnesNtscFilter(_emu);
|
|
|
|
default:
|
|
return new SnesDefaultVideoFilter(_emu);
|
|
}
|
|
}
|
|
|
|
RomFormat SnesConsole::GetRomFormat()
|
|
{
|
|
if(_cart->GetGameboy()) {
|
|
return RomFormat::Gb;
|
|
} else if(_cart->GetSpcData()) {
|
|
return RomFormat::Spc;
|
|
}
|
|
return RomFormat::Sfc;
|
|
}
|
|
|
|
AudioTrackInfo SnesConsole::GetAudioTrackInfo()
|
|
{
|
|
AudioTrackInfo track = {};
|
|
SpcFileData* spc = _cart->GetSpcData();
|
|
if(spc) {
|
|
track.Artist = spc->Artist;
|
|
track.Comment = spc->Comment;
|
|
track.GameTitle = spc->GameTitle;
|
|
track.SongTitle = spc->SongTitle;
|
|
track.Position = _ppu->GetFrameCount() / GetFps();
|
|
track.Length = spc->TrackLength + (spc->FadeLength / 1000.0);
|
|
track.FadeLength = spc->FadeLength / 1000.0;
|
|
track.TrackNumber = _spcTrackNumber + 1;
|
|
track.TrackCount = (uint32_t)_spcPlaylist.size();
|
|
|
|
}
|
|
return track;
|
|
}
|
|
|
|
void SnesConsole::ProcessAudioPlayerAction(AudioPlayerActionParams p)
|
|
{
|
|
if(_spcTrackNumber >= 0) {
|
|
int i = (int)_spcTrackNumber;
|
|
switch(p.Action) {
|
|
case AudioPlayerAction::NextTrack: i++; break;
|
|
case AudioPlayerAction::PrevTrack:
|
|
if(GetAudioTrackInfo().Position < 2) {
|
|
i--;
|
|
}
|
|
break;
|
|
|
|
case AudioPlayerAction::SelectTrack: i = (int)p.TrackNumber; break;
|
|
}
|
|
|
|
if(i < 0) {
|
|
i = (int)_spcPlaylist.size() - 1;
|
|
} else if(i >= _spcPlaylist.size()) {
|
|
i = 0;
|
|
}
|
|
|
|
//Asynchronously move to the next file
|
|
//Can't do this in the current thread in some contexts (e.g when track reaches end)
|
|
//because this is called from the emulation thread.
|
|
thread switchTrackTask([this, i]() {
|
|
_emu->LoadRom(VirtualFile(_spcPlaylist[i]), VirtualFile());
|
|
});
|
|
switchTrackTask.detach();
|
|
}
|
|
}
|
|
|
|
void SnesConsole::Serialize(Serializer& s)
|
|
{
|
|
SV(_cpu);
|
|
SV(_memoryManager);
|
|
SV(_ppu);
|
|
SV(_dmaController);
|
|
SV(_internalRegisters);
|
|
SV(_cart);
|
|
SV(_spc);
|
|
if(_msu1) {
|
|
SV(_msu1);
|
|
}
|
|
SV(_controlManager);
|
|
}
|
|
|
|
SaveStateCompatInfo SnesConsole::ValidateSaveStateCompatibility(ConsoleType stateConsoleType)
|
|
{
|
|
if(stateConsoleType == ConsoleType::Gameboy) {
|
|
return { true, "cart.gameboy.", "", {
|
|
//Keep all clock counters as-is when loading a GB/GBC state
|
|
//in a SGB core - this allows it to keep running in sync with
|
|
//the SNES core properly.
|
|
"apu.clockCounter", "apu.prevClockCount",
|
|
"cpu.cycleCount",
|
|
"memoryManager.apuCycleCount"
|
|
} };
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
SnesCpu* SnesConsole::GetCpu()
|
|
{
|
|
return _cpu.get();
|
|
}
|
|
|
|
SnesPpu* SnesConsole::GetPpu()
|
|
{
|
|
return _ppu.get();
|
|
}
|
|
|
|
Spc* SnesConsole::GetSpc()
|
|
{
|
|
return _spc.get();
|
|
}
|
|
|
|
BaseCartridge* SnesConsole::GetCartridge()
|
|
{
|
|
return _cart.get();
|
|
}
|
|
|
|
SnesMemoryManager* SnesConsole::GetMemoryManager()
|
|
{
|
|
return _memoryManager.get();
|
|
}
|
|
|
|
InternalRegisters* SnesConsole::GetInternalRegisters()
|
|
{
|
|
return _internalRegisters.get();
|
|
}
|
|
|
|
BaseControlManager* SnesConsole::GetControlManager()
|
|
{
|
|
return _controlManager.get();
|
|
}
|
|
|
|
SnesDmaController* SnesConsole::GetDmaController()
|
|
{
|
|
return _dmaController.get();
|
|
}
|
|
|
|
Msu1* SnesConsole::GetMsu1()
|
|
{
|
|
return _msu1.get();
|
|
}
|
|
|
|
Emulator* SnesConsole::GetEmulator()
|
|
{
|
|
return _emu;
|
|
}
|
|
|
|
bool SnesConsole::IsRunning()
|
|
{
|
|
return _cpu != nullptr;
|
|
}
|
|
|
|
AddressInfo SnesConsole::GetAbsoluteAddress(AddressInfo& relAddress)
|
|
{
|
|
static AddressInfo unmapped = { -1, MemoryType::None };
|
|
|
|
switch(relAddress.Type) {
|
|
case MemoryType::SnesMemory:
|
|
if(_memoryManager->IsRegister(relAddress.Address)) {
|
|
return { relAddress.Address & 0xFFFF, MemoryType::SnesRegister };
|
|
} else {
|
|
return _memoryManager->GetMemoryMappings()->GetAbsoluteAddress(relAddress.Address);
|
|
}
|
|
|
|
case MemoryType::SpcMemory: return _spc->GetAbsoluteAddress(relAddress.Address);
|
|
case MemoryType::Sa1Memory: return _cart->GetSa1() ? _cart->GetSa1()->GetMemoryMappings()->GetAbsoluteAddress(relAddress.Address) : unmapped;
|
|
case MemoryType::GsuMemory: return _cart->GetGsu() ? _cart->GetGsu()->GetMemoryMappings()->GetAbsoluteAddress(relAddress.Address) : unmapped;
|
|
case MemoryType::Cx4Memory: return _cart->GetCx4() ? _cart->GetCx4()->GetMemoryMappings()->GetAbsoluteAddress(relAddress.Address) : unmapped;
|
|
case MemoryType::St018Memory: return _cart->GetSt018() ? _cart->GetSt018()->GetArmAbsoluteAddress(relAddress.Address) : unmapped;
|
|
case MemoryType::NecDspMemory: return { relAddress.Address, MemoryType::DspProgramRom };
|
|
case MemoryType::GameboyMemory: return _cart->GetGameboy() ? _cart->GetGameboy()->GetAbsoluteAddress(relAddress.Address) : unmapped;
|
|
default: return unmapped;
|
|
}
|
|
}
|
|
|
|
AddressInfo SnesConsole::GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType)
|
|
{
|
|
static AddressInfo unmapped = { -1, MemoryType::None };
|
|
|
|
MemoryMappings* mappings = nullptr;
|
|
switch(cpuType) {
|
|
case CpuType::Snes: mappings = _memoryManager->GetMemoryMappings(); break;
|
|
case CpuType::Spc: break;
|
|
case CpuType::NecDsp: break;
|
|
case CpuType::Sa1: mappings = _cart->GetSa1() ? _cart->GetSa1()->GetMemoryMappings() : nullptr; break;
|
|
case CpuType::Gsu: mappings = _cart->GetGsu() ? _cart->GetGsu()->GetMemoryMappings() : nullptr; break;
|
|
case CpuType::Cx4: mappings = _cart->GetCx4() ? _cart->GetCx4()->GetMemoryMappings() : nullptr; break;
|
|
case CpuType::St018: break;
|
|
case CpuType::Gameboy: break;
|
|
default: return unmapped;
|
|
}
|
|
|
|
switch(absAddress.Type) {
|
|
case MemoryType::SnesPrgRom:
|
|
case MemoryType::SnesWorkRam:
|
|
case MemoryType::SnesSaveRam:
|
|
case MemoryType::SufamiTurboFirmware:
|
|
{
|
|
if(!mappings) {
|
|
return unmapped;
|
|
}
|
|
|
|
uint8_t startBank = 0;
|
|
//Try to find a mirror close to where the PC is
|
|
if(cpuType == CpuType::Snes) {
|
|
if(absAddress.Type == MemoryType::SnesWorkRam) {
|
|
startBank = 0x7E;
|
|
} else {
|
|
startBank = _cpu->GetState().K & 0xC0;
|
|
}
|
|
} else if(cpuType == CpuType::Sa1) {
|
|
startBank = (_cart->GetSa1()->GetCpuState().K & 0xC0);
|
|
} else if(cpuType == CpuType::Gsu) {
|
|
startBank = (_cart->GetGsu()->GetState().ProgramBank & 0xC0);
|
|
}
|
|
|
|
return { mappings->GetRelativeAddress(absAddress, startBank), DebugUtilities::GetCpuMemoryType(cpuType) };
|
|
}
|
|
|
|
case MemoryType::SpcRam:
|
|
case MemoryType::SpcRom:
|
|
return { _spc->GetRelativeAddress(absAddress), MemoryType::SpcMemory };
|
|
|
|
case MemoryType::GbPrgRom:
|
|
case MemoryType::GbWorkRam:
|
|
case MemoryType::GbCartRam:
|
|
case MemoryType::GbHighRam:
|
|
case MemoryType::GbBootRom:
|
|
return _cart->GetGameboy() ? AddressInfo { _cart->GetGameboy()->GetRelativeAddress(absAddress), MemoryType::GameboyMemory } : unmapped;
|
|
|
|
case MemoryType::St018PrgRom:
|
|
case MemoryType::St018DataRom:
|
|
case MemoryType::St018WorkRam:
|
|
return _cart->GetSt018() ? AddressInfo { _cart->GetSt018()->GetArmRelativeAddress(absAddress), MemoryType::St018Memory } : unmapped;
|
|
|
|
case MemoryType::DspProgramRom:
|
|
return { absAddress.Address, MemoryType::NecDspMemory };
|
|
|
|
case MemoryType::SnesRegister:
|
|
return { absAddress.Address & 0xFFFF, MemoryType::SnesMemory };
|
|
|
|
default:
|
|
return unmapped;
|
|
}
|
|
}
|
|
|
|
void SnesConsole::GetConsoleState(BaseState& baseState, ConsoleType consoleType)
|
|
{
|
|
if(consoleType == ConsoleType::Gameboy) {
|
|
_cart->GetGameboy()->GetConsoleState(baseState, consoleType);
|
|
return;
|
|
}
|
|
|
|
SnesState& state = (SnesState&)baseState;
|
|
state.MasterClock = GetMasterClock();
|
|
state.WramPosition = _memoryManager->GetWramPosition();
|
|
state.Cpu = _cpu->GetState();
|
|
_ppu->GetState(state.Ppu, false);
|
|
state.Spc = _spc->GetState();
|
|
state.Dsp = _spc->GetDspState();
|
|
|
|
state.Dma = _dmaController->GetState();
|
|
state.InternalRegs = _internalRegisters->GetState();
|
|
state.Alu = _internalRegisters->GetAluState();
|
|
|
|
if(_cart->GetDsp()) {
|
|
state.NecDsp = _cart->GetDsp()->GetState();
|
|
}
|
|
if(_cart->GetSa1()) {
|
|
state.Sa1 = _cart->GetSa1()->GetState();
|
|
}
|
|
if(_cart->GetGsu()) {
|
|
state.Gsu = _cart->GetGsu()->GetState();
|
|
}
|
|
if(_cart->GetCx4()) {
|
|
state.Cx4 = _cart->GetCx4()->GetState();
|
|
}
|
|
if(_cart->GetSt018()) {
|
|
state.St018 = _cart->GetSt018()->GetState();
|
|
}
|
|
}
|
|
|
|
void SnesConsole::InitializeRam(void* data, uint32_t length)
|
|
{
|
|
EmuSettings* settings = _emu->GetSettings();
|
|
RamState state;
|
|
if(_cart) {
|
|
state = _cart->GetRamPowerOnState();
|
|
} else {
|
|
state = settings->GetSnesConfig().RamPowerOnState;
|
|
}
|
|
settings->InitializeRam(state, data, length);
|
|
} |