#include "pch.h" #include "NES/NesConsole.h" #include "NES/NesControlManager.h" #include "NES/MapperFactory.h" #include "NES/APU/NesApu.h" #include "NES/NesCpu.h" #include "NES/BaseMapper.h" #include "NES/NesSoundMixer.h" #include "NES/NesMemoryManager.h" #include "NES/DefaultNesPpu.h" #include "NES/NsfPpu.h" #include "NES/HdPacks/HdAudioDevice.h" #include "NES/HdPacks/HdData.h" #include "NES/HdPacks/HdNesPpu.h" #include "NES/HdPacks/HdPackLoader.h" #include "NES/HdPacks/HdPackBuilder.h" #include "NES/HdPacks/HdBuilderPpu.h" #include "NES/HdPacks/HdVideoFilter.h" #include "NES/NesDefaultVideoFilter.h" #include "NES/NesNtscFilter.h" #include "NES/BisqwitNtscFilter.h" #include "NES/NesConstants.h" #include "NES/Epsm.h" #include "NES/Mappers/VsSystem/VsControlManager.h" #include "NES/Mappers/NSF/NsfMapper.h" #include "NES/Mappers/FDS/Fds.h" #include "Shared/Emulator.h" #include "Shared/Audio/SoundMixer.h" #include "Shared/SaveStateManager.h" #include "Shared/CheatManager.h" #include "Shared/Movies/MovieManager.h" #include "Shared/BaseControlManager.h" #include "Shared/Interfaces/IBattery.h" #include "Shared/EmuSettings.h" #include "Shared/NotificationManager.h" #include "Netplay/GameClient.h" #include "Debugger/DebugTypes.h" #include "Utilities/Serializer.h" #include "Utilities/sha1.h" NesConsole::NesConsole(Emulator* emu) { _emu = emu; } NesConsole::~NesConsole() { shared_ptr hdData = _hdData.lock(); if(hdData) { hdData->CancelLoad(); } } Emulator* NesConsole::GetEmulator() { return _emu; } NesConfig& NesConsole::GetNesConfig() { return _emu->GetSettings()->GetNesConfig(); } void NesConsole::ProcessCpuClock() { if(_mapper->HasCpuClockHook()) { _mapper->ProcessCpuClock(); } _apu->ProcessCpuClock(); if(_controlManager->HasPendingWrites()) { _controlManager->ProcessWrites(); } } Epsm* NesConsole::GetEpsm() { return _mapper->GetEpsm(); } NesConsole* NesConsole::GetVsMainConsole() { return _vsMainConsole; } NesConsole* NesConsole::GetVsSubConsole() { return _vsSubConsole.get(); } bool NesConsole::IsVsMainConsole() { return _vsMainConsole == nullptr; } void NesConsole::Serialize(Serializer& s) { SV(_cpu); SV(_ppu); SV(_memoryManager); SV(_apu); SV(_mapper); if(s.GetFormat() != SerializeFormat::Map) { SV(_mixer); } if(_hdAudioDevice) { //For HD packs), save the state of the bgm playback SV(_hdAudioDevice); } if(_vsSubConsole) { //For VS Dualsystem, the sub console's savestate is appended to the end of the file SV(_vsSubConsole); } SV(_controlManager); if(!s.IsSaving()) { UpdateRegion(true); } } void NesConsole::Reset() { _memoryManager->Reset(true); _ppu->Reset(true); _apu->Reset(true); _cpu->Reset(true, _region); _controlManager->Reset(true); _mixer->Reset(); if(_vsSubConsole) { _vsSubConsole->Reset(); } _mapper->OnAfterResetPowerOn(); } LoadRomResult NesConsole::LoadRom(VirtualFile& romFile) { RomData romData; LoadHdPack(romFile); LoadRomResult result = LoadRomResult::UnknownType; unique_ptr mapper = MapperFactory::InitializeFromFile(this, romFile, romData, result); if(mapper) { if(!_vsMainConsole && romData.Info.VsType == VsSystemType::VsDualSystem) { //Create 2nd console (sub) dualsystem games _vsSubConsole.reset(new NesConsole(_emu)); _vsSubConsole->_vsMainConsole = this; result = _vsSubConsole->LoadRom(romFile); if(result != LoadRomResult::Success) { return result; } } if(GetNesConfig().AutoConfigureInput && romData.Info.InputType != GameInputType::Unspecified) { //Auto-configure the inputs (if option is enabled) InitializeInputDevices(romData.Info.InputType, romData.Info.System); } _mapper.swap(mapper); _mixer.reset(new NesSoundMixer(this)); _memoryManager.reset(new NesMemoryManager(this, _mapper.get())); _cpu.reset(new NesCpu(this)); _apu.reset(new NesApu(this)); if(romData.Info.System == GameSystem::VsSystem) { _controlManager.reset(new VsControlManager(this)); } else { _controlManager.reset(new NesControlManager(this)); } if(_hdData) { _ppu.reset(new HdNesPpu(this, _hdData.get())); } else if(dynamic_cast(_mapper.get())) { //Disable most of the PPU for NSFs _ppu.reset(new NsfPpu(this)); } else { _ppu.reset(new DefaultNesPpu(this)); } _mapper->InitSpecificMapper(romData); if(_mapper->GetEpsm()) { _memoryManager->RegisterIODevice(_mapper->GetEpsm()); } _memoryManager->RegisterIODevice(_ppu.get()); _memoryManager->RegisterIODevice(_apu.get()); _memoryManager->RegisterIODevice(_controlManager.get()); _memoryManager->RegisterIODevice(_mapper.get()); if(_hdData) { _hdAudioDevice.reset(new HdAudioDevice(_emu, _hdData.get())); _memoryManager->RegisterIODevice(_hdAudioDevice.get()); } else { _hdAudioDevice.reset(); } UpdateRegion(); _mixer->Reset(); _ppu->Reset(false); _apu->Reset(false); _memoryManager->Reset(false); _controlManager->Reset(false); _cpu->Reset(false, _region); _mapper->OnAfterResetPowerOn(); } return result; } void NesConsole::LoadHdPack(VirtualFile& romFile) { _hdData.reset(); if(GetNesConfig().EnableHdPacks) { _hdData.reset(new HdPackData()); if(!HdPackLoader::LoadHdNesPack(romFile, *_hdData.get())) { _hdData.reset(); } else { auto result = _hdData->PatchesByHash.find(romFile.GetSha1Hash()); if(result != _hdData->PatchesByHash.end()) { VirtualFile patchFile = result->second; romFile.ApplyPatch(patchFile); } shared_ptr data = _hdData.lock(); if(data) { thread asyncLoadData([data]() { data->LoadAsync(); }); asyncLoadData.detach(); } } } } void NesConsole::UpdateRegion(bool forceUpdate) { ConsoleRegion region = GetNesConfig().Region; if(region == ConsoleRegion::Auto) { switch(_mapper->GetRomInfo().System) { case GameSystem::NesPal: region = ConsoleRegion::Pal; break; case GameSystem::Dendy: region = ConsoleRegion::Dendy; break; default: region = ConsoleRegion::Ntsc; break; } } if(_vsSubConsole) { _vsSubConsole->UpdateRegion(forceUpdate); } if(_region != region || forceUpdate) { _region = region; _cpu->SetMasterClockDivider(_region); _mapper->SetRegion(_region); _ppu->UpdateTimings(_region); _apu->SetRegion(_region); _mixer->SetRegion(_region); } } void NesConsole::RunFrame() { UpdateRegion(); uint32_t frame = _ppu->GetFrameCount(); if(_nextFrameOverclockDisabled) { //Disable overclocking for the next frame //This is used by the DMC when a sample is playing _ppu->UpdateTimings(_region, false); _nextFrameOverclockDisabled = false; } while(frame == _ppu->GetFrameCount()) { _cpu->Exec(); if(_vsSubConsole) { RunVsSubConsole(); } } _apu->EndFrame(); if(!_nextFrameOverclockDisabled) { //Re-update timings to allow overclocking _ppu->UpdateTimings(_region, true); } } void NesConsole::RunVsSubConsole() { int64_t cycleGap; while(true) { //Run the sub console until it catches up to the main CPU cycleGap = (int64_t)(_cpu->GetCycleCount() - _vsSubConsole->_cpu->GetCycleCount()); if(cycleGap > 5 || _ppu->GetFrameCount() > _vsSubConsole->_ppu->GetFrameCount()) { _vsSubConsole->_cpu->Exec(); } else { break; } } } void NesConsole::SetNextFrameOverclockStatus(bool disabled) { //Disable overclocking for the next frame //This is used by the DMC when a sample is playing _nextFrameOverclockDisabled = disabled; } BaseControlManager* NesConsole::GetControlManager() { return _controlManager.get(); } double NesConsole::GetFps() { UpdateRegion(); if(_region == ConsoleRegion::Ntsc) { return 60.0988118623484; } else { return 50.0069789081886; } } PpuFrameInfo NesConsole::GetPpuFrame() { PpuFrameInfo frame; frame.FrameBuffer = (uint8_t*)_ppu->GetScreenBuffer(false); frame.Width = NesConstants::ScreenWidth; frame.Height = NesConstants::ScreenHeight; frame.FrameBufferSize = frame.Width * frame.Height * sizeof(uint16_t); frame.FrameCount = _ppu->GetFrameCount(); frame.FirstScanline = -1; frame.ScanlineCount = _ppu->GetScanlineCount(); frame.CycleCount = 341; return frame; } ConsoleType NesConsole::GetConsoleType() { return ConsoleType::Nes; } ConsoleRegion NesConsole::GetRegion() { return _region; } vector NesConsole::GetCpuTypes() { return { CpuType::Nes }; } AddressInfo NesConsole::GetAbsoluteAddress(AddressInfo& relAddress) { if(relAddress.Type == MemoryType::NesMemory) { return _mapper->GetAbsoluteAddress(relAddress.Address); } else { return _mapper->GetPpuAbsoluteAddress(relAddress.Address); } } AddressInfo NesConsole::GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType) { return _mapper->GetRelativeAddress(absAddress); } void NesConsole::GetConsoleState(BaseState& baseState, ConsoleType consoleType) { NesState& state = (NesState&)baseState; state.ClockRate = GetMasterClockRate(); state.Cpu = _cpu->GetState(); _ppu->GetState(state.Ppu); state.Cartridge = _mapper->GetState(); state.Apu = _apu->GetState(); } uint64_t NesConsole::GetMasterClock() { return _cpu->GetCycleCount(); } uint32_t NesConsole::GetMasterClockRate() { return NesConstants::GetClockRate(_region); } void NesConsole::SaveBattery() { if(_mapper) { _mapper->SaveBattery(); } if(_controlManager) { _controlManager->SaveBattery(); } } ShortcutState NesConsole::IsShortcutAllowed(EmulatorShortcut shortcut, uint32_t shortcutParam) { bool isRunning = _emu->IsRunning(); bool isNetplayClient = _emu->GetGameClient()->Connected(); bool isMoviePlaying = _emu->GetMovieManager()->Playing(); RomFormat romFormat = GetRomFormat(); switch(shortcut) { case EmulatorShortcut::FdsEjectDisk: case EmulatorShortcut::FdsInsertNextDisk: case EmulatorShortcut::FdsSwitchDiskSide: return (ShortcutState)(isRunning && !isNetplayClient && !isMoviePlaying && romFormat == RomFormat::Fds); case EmulatorShortcut::FdsInsertDiskNumber: if(isRunning && !isNetplayClient && !isMoviePlaying && romFormat == RomFormat::Fds) { Fds* fds = dynamic_cast(_mapper.get()); return (ShortcutState)(fds && shortcutParam < fds->GetSideCount()); } return ShortcutState::Disabled; case EmulatorShortcut::VsInsertCoin1: case EmulatorShortcut::VsInsertCoin2: case EmulatorShortcut::VsServiceButton: return (ShortcutState)(isRunning && !isNetplayClient && !isMoviePlaying && (romFormat == RomFormat::VsSystem || romFormat == RomFormat::VsDualSystem)); case EmulatorShortcut::VsInsertCoin3: case EmulatorShortcut::VsInsertCoin4: case EmulatorShortcut::VsServiceButton2: return (ShortcutState)(isRunning && !isNetplayClient && !isMoviePlaying && romFormat == RomFormat::VsDualSystem); } return ShortcutState::Default; } BaseVideoFilter* NesConsole::GetVideoFilter(bool getDefaultFilter) { if(getDefaultFilter || GetRomFormat() == RomFormat::Nsf) { return new NesDefaultVideoFilter(_emu); } else if(_hdData && !_hdPackBuilder) { return new HdVideoFilter(this, _emu, _hdData.get()); } else { VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter; switch(filterType) { case VideoFilterType::NtscBlargg: return new NesNtscFilter(_emu); case VideoFilterType::NtscBisqwit: return new BisqwitNtscFilter(_emu); default: return new NesDefaultVideoFilter(_emu); } } } string NesConsole::GetHash(HashType hashType) { if(hashType == HashType::Sha1Cheat) { ConsoleMemoryInfo prgRom = _emu->GetMemory(MemoryType::NesPrgRom); if(prgRom.Size && prgRom.Memory) { return SHA1::GetHash((uint8_t*)prgRom.Memory, prgRom.Size); } } return ""; } RomFormat NesConsole::GetRomFormat() { return _mapper->GetRomInfo().Format; } AudioTrackInfo NesConsole::GetAudioTrackInfo() { NsfMapper* nsfMapper = dynamic_cast(_mapper.get()); if(nsfMapper) { return nsfMapper->GetAudioTrackInfo(); } return {}; } void NesConsole::ProcessAudioPlayerAction(AudioPlayerActionParams p) { NsfMapper* nsfMapper = dynamic_cast(_mapper.get()); if(nsfMapper) { return nsfMapper->ProcessAudioPlayerAction(p); } } uint8_t NesConsole::DebugRead(uint16_t addr) { return _memoryManager->DebugRead(addr); } void NesConsole::DebugWrite(uint16_t addr, uint8_t value, bool disableSideEffects) { _memoryManager->DebugWrite(addr, value, disableSideEffects); } uint8_t NesConsole::DebugReadVram(uint16_t addr) { if(addr >= 0x3F00) { return _ppu->ReadPaletteRam(addr); } else { return _mapper->DebugReadVram(addr); } } void NesConsole::DebugWriteVram(uint16_t addr, uint8_t value) { if(addr >= 0x3F00) { _ppu->WritePaletteRam(addr, value); } else { _mapper->DebugWriteVram(addr, value); } } void NesConsole::InitializeInputDevices(GameInputType inputType, GameSystem system) { ControllerType port1 = ControllerType::NesController; ControllerType port2 = ControllerType::NesController; ControllerType expDevice = ControllerType::None; auto log = [](string text) { MessageManager::Log(text); }; bool isFamicom = (system == GameSystem::Famicom || system == GameSystem::FDS || system == GameSystem::Dendy); if(inputType == GameInputType::VsZapper) { //VS Duck Hunt, etc. need the zapper in the first port log("[Input] VS Zapper connected"); port1 = ControllerType::NesZapper; } else if(inputType == GameInputType::Zapper) { log("[Input] Zapper connected"); if(isFamicom) { expDevice = ControllerType::FamicomZapper; } else { port2 = ControllerType::NesZapper; } } else if(inputType == GameInputType::FourScore) { log("[Input] Four score connected"); port1 = ControllerType::FourScore; port2 = ControllerType::FourScore; } else if(inputType == GameInputType::FourPlayerAdapter) { log("[Input] Four player adapter connected"); expDevice = ControllerType::TwoPlayerAdapter; } else if(inputType == GameInputType::ArkanoidControllerFamicom) { log("[Input] Arkanoid controller (Famicom) connected"); expDevice = ControllerType::FamicomArkanoidController; } else if(inputType == GameInputType::ArkanoidControllerNes) { log("[Input] Arkanoid controller (NES) connected"); port2 = ControllerType::NesArkanoidController; } else if(inputType == GameInputType::DoubleArkanoidController) { log("[Input] 2 arkanoid controllers (NES) connected"); port1 = ControllerType::NesArkanoidController; port2 = ControllerType::NesArkanoidController; } else if(inputType == GameInputType::OekaKidsTablet) { log("[Input] Oeka Kids Tablet connected"); expDevice = ControllerType::OekaKidsTablet; } else if(inputType == GameInputType::KonamiHyperShot) { log("[Input] Konami Hyper Shot connected"); expDevice = ControllerType::KonamiHyperShot; } else if(inputType == GameInputType::FamilyBasicKeyboard) { log("[Input] Family Basic Keyboard connected"); expDevice = ControllerType::FamilyBasicKeyboard; } else if(inputType == GameInputType::PartyTap) { log("[Input] Party Tap connected"); expDevice = ControllerType::PartyTap; } else if(inputType == GameInputType::PachinkoController) { log("[Input] Pachinko controller connected"); expDevice = ControllerType::Pachinko; } else if(inputType == GameInputType::ExcitingBoxing) { log("[Input] Exciting Boxing controller connected"); expDevice = ControllerType::ExcitingBoxing; } else if(inputType == GameInputType::SuborKeyboardMouse1) { log("[Input] Subor mouse connected"); log("[Input] Subor keyboard connected"); expDevice = ControllerType::SuborKeyboard; port2 = ControllerType::SuborMouse; } else if(inputType == GameInputType::JissenMahjong) { log("[Input] Jissen Mahjong controller connected"); expDevice = ControllerType::JissenMahjong; } else if(inputType == GameInputType::BarcodeBattler) { log("[Input] Barcode Battler barcode reader connected"); expDevice = ControllerType::BarcodeBattler; } else if(inputType == GameInputType::BandaiHypershot) { log("[Input] Bandai Hyper Shot gun connected"); expDevice = ControllerType::BandaiHyperShot; } else if(inputType == GameInputType::BattleBox) { log("[Input] Battle Box connected"); expDevice = ControllerType::BattleBox; } else if(inputType == GameInputType::TurboFile) { log("[Input] Ascii Turbo File connected"); expDevice = ControllerType::AsciiTurboFile; } else if(inputType == GameInputType::FamilyTrainerSideA) { log("[Input] Family Trainer mat connected (Side A)"); expDevice = ControllerType::FamilyTrainerMatSideA; } else if(inputType == GameInputType::FamilyTrainerSideB) { log("[Input] Family Trainer mat connected (Side B)"); expDevice = ControllerType::FamilyTrainerMatSideB; } else if(inputType == GameInputType::PowerPadSideA) { log("[Input] Power Pad connected (Side A)"); port2 = ControllerType::PowerPadSideA; } else if(inputType == GameInputType::PowerPadSideB) { log("[Input] Power Pad connected (Side B)"); port2 = ControllerType::PowerPadSideB; } else if(inputType == GameInputType::SnesControllers) { log("[Input] 2 SNES controllers connected"); port1 = ControllerType::SnesController; port2 = ControllerType::SnesController; } else { log("[Input] 2 NES controllers connected"); } isFamicom = (system == GameSystem::Famicom || system == GameSystem::FDS || system == GameSystem::Dendy); NesConfig& cfg = GetNesConfig(); cfg.Port1.Type = port1; cfg.Port2.Type = port2; cfg.ExpPort.Type = expDevice; if(port1 == ControllerType::FourScore) { cfg.Port1SubPorts[0].Type = ControllerType::NesController; cfg.Port1SubPorts[1].Type = ControllerType::NesController; cfg.Port1SubPorts[2].Type = ControllerType::NesController; cfg.Port1SubPorts[3].Type = ControllerType::NesController; } else if(expDevice == ControllerType::TwoPlayerAdapter) { cfg.ExpPortSubPorts[0].Type = ControllerType::NesController; cfg.ExpPortSubPorts[1].Type = ControllerType::NesController; } _emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::RequestConfigChange); } void NesConsole::ProcessCheatCode(InternalCheatCode& code, uint32_t addr, uint8_t& value) { if(code.Type == CheatType::NesGameGenie && addr >= 0xC020) { if(GetNesConfig().DisableGameGenieBusConflicts || _mapper->HasDefaultWorkRam()) { return; } AddressInfo absAddr = _mapper->GetAbsoluteAddress(addr - 0x8000); if(absAddr.Address >= 0) { //Game Genie causes a bus conflict when the cartridge maps anything below $8000 //Only processed when addr >= $C020 because the mapper implementation never maps anything below $4020 value &= _mapper->DebugReadRam(addr - 0x8000); } } } void NesConsole::InitializeRam(void* data, uint32_t length) { EmuSettings* settings = _emu->GetSettings(); settings->InitializeRam(settings->GetNesConfig().RamPowerOnState, data, length); } DipSwitchInfo NesConsole::GetDipSwitchInfo() { DipSwitchInfo info = {}; info.DipSwitchCount = _mapper->GetMapperDipSwitchCount(); info.DatabaseId = _mapper->GetRomInfo().Hash.PrgCrc32; return info; } void NesConsole::ProcessNotification(ConsoleNotificationType type, void* parameter) { if(type == ConsoleNotificationType::ExecuteShortcut) { ExecuteShortcutParams* params = (ExecuteShortcutParams*)parameter; switch(params->Shortcut) { default: break; case EmulatorShortcut::StartRecordHdPack: StartRecordingHdPack(*(HdPackBuilderOptions*)params->ParamPtr); break; case EmulatorShortcut::StopRecordHdPack: StopRecordingHdPack(); break; } } } void NesConsole::StartRecordingHdPack(HdPackBuilderOptions options) { auto lock = _emu->AcquireLock(); _emu->GetVideoDecoder()->WaitForAsyncFrameDecode(); std::stringstream saveState; _emu->Serialize(saveState, false, 0); _hdPackBuilder.reset(); _hdPackBuilder.reset(new HdPackBuilder(_emu, _ppu->GetPpuModel(), !_mapper->HasChrRom(), options)); _memoryManager->UnregisterIODevice(_ppu.get()); _ppu.reset(new HdBuilderPpu(this, _hdPackBuilder.get(), options.ChrRamBankSize)); _memoryManager->RegisterIODevice(_ppu.get()); _emu->Deserialize(saveState, SaveStateManager::FileFormatVersion, false); _emu->GetSoundMixer()->StopAudio(); _emu->GetVideoDecoder()->ForceFilterUpdate(); } void NesConsole::StopRecordingHdPack() { if(_hdPackBuilder) { auto lock = _emu->AcquireLock(); _emu->GetVideoDecoder()->WaitForAsyncFrameDecode(); std::stringstream saveState; _emu->Serialize(saveState, false, 0); _memoryManager->UnregisterIODevice(_ppu.get()); if(_hdData) { _ppu.reset(new HdNesPpu(this, _hdData.get())); } else { _ppu.reset(new DefaultNesPpu(this)); } _memoryManager->RegisterIODevice(_ppu.get()); _hdPackBuilder.reset(); _emu->Deserialize(saveState, SaveStateManager::FileFormatVersion, false); _emu->GetSoundMixer()->StopAudio(); _emu->GetVideoDecoder()->ForceFilterUpdate(); } }