#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 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 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 SnesConsole::GetCpuTypes() { vector 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); }