Mesen-X/Core/Console.cpp

697 lines
18 KiB
C++
Raw Normal View History

2014-06-14 11:27:55 -04:00
#include "stdafx.h"
#include <thread>
2014-06-14 11:27:55 -04:00
#include "Console.h"
2017-04-29 08:29:56 -04:00
#include "CPU.h"
#include "PPU.h"
#include "APU.h"
#include "MemoryManager.h"
#include "AutoSaveManager.h"
#include "BaseMapper.h"
#include "ControlManager.h"
#include "VsControlManager.h"
#include "MapperFactory.h"
#include "Debugger.h"
2015-07-17 20:58:57 -04:00
#include "MessageManager.h"
2016-01-28 20:47:16 -05:00
#include "RomLoader.h"
2015-07-17 20:58:57 -04:00
#include "EmulationSettings.h"
#include "../Utilities/sha1.h"
2014-06-23 19:02:09 -04:00
#include "../Utilities/Timer.h"
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/PlatformUtilities.h"
#include "../Utilities/VirtualFile.h"
#include "HdBuilderPpu.h"
#include "HdPpu.h"
2016-06-25 20:46:54 -04:00
#include "NsfPpu.h"
#include "SoundMixer.h"
2016-06-25 20:46:54 -04:00
#include "NsfMapper.h"
2017-04-18 22:39:45 -04:00
#include "MovieManager.h"
2017-04-28 19:54:58 -04:00
#include "RewindManager.h"
#include "SaveStateManager.h"
#include "HdPackBuilder.h"
2017-08-19 16:46:57 -04:00
#include "HdAudioDevice.h"
2014-06-14 11:27:55 -04:00
shared_ptr<Console> Console::Instance(new Console());
2014-06-21 19:03:13 -04:00
Console::Console()
2014-06-14 11:27:55 -04:00
{
2016-12-11 10:56:23 -05:00
_resetRequested = false;
_lagCounter = 0;
}
Console::~Console()
{
2017-04-18 22:39:45 -04:00
MovieManager::Stop();
SoundMixer::StopRecording();
}
shared_ptr<Console> Console::GetInstance()
{
return Console::Instance;
}
void Console::Release()
{
Console::Instance.reset(new Console());
}
bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
{
SoundMixer::StopAudio();
StopRecordingHdPack();
if(!_romFilepath.empty() && _mapper) {
//Ensure we save any battery file before loading a new game
_mapper->SaveBattery();
//Save current game state before loading another one
SaveStateManager::SaveRecentGame(_mapper->GetRomName(), _romFilepath, _patchFilename);
}
if(romFile.IsValid()) {
LoadHdPack(romFile, patchFile);
if(patchFile.IsValid()) {
if(romFile.ApplyPatch(patchFile)) {
MessageManager::DisplayMessage("Patch", "ApplyingPatch", FolderUtilities::GetFilename(patchFile.GetFilePath(), true));
} else {
//Patch failed
}
}
vector<uint8_t> fileData;
romFile.ReadFile(fileData);
shared_ptr<BaseMapper> mapper = MapperFactory::InitializeFromFile(romFile.GetFileName(), fileData);
if(mapper) {
if(_mapper) {
//Send notification only if a game was already running and we successfully loaded the new one
MessageManager::SendNotification(ConsoleNotificationType::GameStopped);
}
2015-12-26 17:11:00 -05:00
_romFilepath = romFile;
_patchFilename = patchFile;
_autoSaveManager.reset(new AutoSaveManager());
VideoDecoder::GetInstance()->StopThread();
_mapper = mapper;
_memoryManager.reset(new MemoryManager(_mapper));
_cpu.reset(new CPU(_memoryManager.get()));
2014-06-21 15:43:41 -04:00
if(_hdData && (!_hdData->Tiles.empty() || !_hdData->Backgrounds.empty())) {
_ppu.reset(new HdPpu(_mapper.get(), _hdData->Version));
} else if(NsfMapper::GetInstance()) {
//Disable most of the PPU for NSFs
_ppu.reset(new NsfPpu(_mapper.get()));
} else {
_ppu.reset(new PPU(_mapper.get()));
}
_apu.reset(new APU(_memoryManager.get()));
_controlManager.reset(_mapper->GetGameSystem() == GameSystem::VsUniSystem ? new VsControlManager() : new ControlManager());
_controlManager->UpdateControlDevices();
_memoryManager->RegisterIODevice(_ppu.get());
_memoryManager->RegisterIODevice(_apu.get());
_memoryManager->RegisterIODevice(_controlManager.get());
_memoryManager->RegisterIODevice(_mapper.get());
2017-08-19 16:46:57 -04:00
if(_hdData && (!_hdData->BgmFilesById.empty() || !_hdData->SfxFilesById.empty())) {
_hdAudioDevice.reset(new HdAudioDevice(_hdData.get()));
_memoryManager->RegisterIODevice(_hdAudioDevice.get());
}
_model = NesModel::Auto;
UpdateNesModel(false);
_initialized = true;
2017-04-28 19:54:58 -04:00
if(_debugger) {
auto lock = _debuggerLock.AcquireSafe();
StopDebugger();
GetDebugger();
}
ResetComponents(false);
_rewindManager.reset(new RewindManager());
VideoDecoder::GetInstance()->StartThread();
FolderUtilities::AddKnownGameFolder(romFile.GetFolderPath());
string modelName = _model == NesModel::PAL ? "PAL" : (_model == NesModel::Dendy ? "Dendy" : "NTSC");
string messageTitle = MessageManager::Localize("GameLoaded") + " (" + modelName + ")";
MessageManager::DisplayMessage(messageTitle, FolderUtilities::GetFilename(_mapper->GetRomName(), false));
if(EmulationSettings::GetOverclockRate() != 100) {
MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%");
}
return true;
2016-06-12 18:11:31 -04:00
}
}
MessageManager::DisplayMessage("Error", "CouldNotLoadFile", romFile.GetFileName());
return false;
}
bool Console::LoadROM(VirtualFile romFile, VirtualFile patchFile)
2015-12-27 18:41:38 -05:00
{
Console::Pause();
bool result = Instance->Initialize(romFile, patchFile);
Console::Resume();
return result;
2014-06-14 11:27:55 -04:00
}
bool Console::LoadROM(string romName, HashInfo hashInfo)
2014-06-14 11:27:55 -04:00
{
string currentRomFilepath = Console::GetRomPath().GetFilePath();
string currentFolder = FolderUtilities::GetFolderName(currentRomFilepath);
if(!currentRomFilepath.empty()) {
HashInfo gameHashInfo = Instance->_mapper->GetHashInfo();
if(gameHashInfo.Crc32Hash == hashInfo.Crc32Hash || gameHashInfo.Sha1Hash.compare(hashInfo.Sha1Hash) == 0 || gameHashInfo.PrgChrMd5Hash.compare(hashInfo.PrgChrMd5Hash) == 0) {
//Current game matches, no need to do anything
return true;
}
}
string lcRomname = romName;
std::transform(lcRomname.begin(), lcRomname.end(), lcRomname.begin(), ::tolower);
std::unordered_set<string> validExtensions = { { ".nes", ".fds", "*.unif", "*.unif", "*.nsf", "*.nsfe", "*.7z", "*.zip" } };
vector<string> romFiles;
2016-12-09 12:49:17 -05:00
for(string folder : FolderUtilities::GetKnownGameFolders()) {
vector<string> files = FolderUtilities::GetFilesInFolder(folder, validExtensions, true);
romFiles.insert(romFiles.end(), files.begin(), files.end());
}
string match = RomLoader::FindMatchingRom(romFiles, romName, hashInfo, true);
if(!match.empty()) {
return Console::LoadROM(match);
}
//Perform slow CRC32 search for ROM
match = RomLoader::FindMatchingRom(romFiles, romName, hashInfo, false);
if(!match.empty()) {
return Console::LoadROM(match);
}
return false;
}
VirtualFile Console::GetRomPath()
{
return static_cast<VirtualFile>(Instance->_romFilepath);
2014-06-14 11:27:55 -04:00
}
string Console::GetRomName()
{
if(Instance->_mapper) {
return Instance->_mapper->GetRomName();
} else {
return "";
}
}
RomFormat Console::GetRomFormat()
{
if(Instance->_mapper) {
return Instance->_mapper->GetRomFormat();
} else {
return RomFormat::Unknown;
}
}
bool Console::IsChrRam()
{
if(Instance->_mapper) {
return Instance->_mapper->HasChrRam();
} else {
return false;
}
}
HashInfo Console::GetHashInfo()
2016-01-28 20:47:16 -05:00
{
if(Instance->_mapper) {
return Instance->_mapper->GetHashInfo();
} else {
return {};
}
2016-01-28 20:47:16 -05:00
}
2016-07-01 23:54:31 -04:00
NesModel Console::GetModel()
{
return Instance->_model;
}
void Console::PowerCycle()
{
if(Instance->_initialized && !Instance->_romFilepath.empty()) {
LoadROM(Instance->_romFilepath, Instance->_patchFilename);
}
}
void Console::Reset(bool softReset)
2014-06-14 11:27:55 -04:00
{
if(Instance->_initialized) {
if(softReset && EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset)) {
//Allow mid-frame resets to allow the PPU to get out-of-sync
RequestReset();
2015-12-26 17:11:00 -05:00
} else {
2017-04-18 22:39:45 -04:00
MovieManager::Stop();
SoundMixer::StopRecording();
Console::Pause();
if(Instance->_initialized) {
if(softReset) {
Instance->ResetComponents(softReset);
} else {
//Full reset of all objects to ensure the emulator always starts in the exact same state
LoadROM(Instance->_romFilepath, Instance->_patchFilename);
}
}
Console::Resume();
2015-12-26 17:11:00 -05:00
}
}
2014-06-14 11:27:55 -04:00
}
void Console::ResetComponents(bool softReset)
{
2017-04-18 22:39:45 -04:00
MovieManager::Stop();
if(!softReset) {
SoundMixer::StopRecording();
_hdPackBuilder.reset();
}
_memoryManager->Reset(softReset);
if(!EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset) || !softReset) {
_ppu->Reset();
}
_apu->Reset(softReset);
_cpu->Reset(softReset, _model);
_controlManager->Reset(softReset);
2016-07-10 18:22:37 -04:00
_lagCounter = 0;
SoundMixer::StopAudio(true);
2017-08-30 18:31:27 -04:00
MessageManager::SendNotification(softReset ? ConsoleNotificationType::GameReset : ConsoleNotificationType::GameLoaded);
}
void Console::Stop()
{
_stop = true;
2016-07-31 14:31:44 -04:00
shared_ptr<Debugger> debugger = _debugger;
if(debugger) {
debugger->Suspend();
}
_stopLock.Acquire();
_stopLock.Release();
2014-06-21 19:03:13 -04:00
}
void Console::Pause()
{
2016-07-31 14:31:44 -04:00
shared_ptr<Debugger> debugger = Console::Instance->_debugger;
if(debugger) {
//Make sure debugger resumes if we try to pause the emu, otherwise we will get deadlocked.
2016-07-31 14:31:44 -04:00
debugger->Suspend();
}
Console::Instance->_pauseLock.Acquire();
//Spin wait until emu pauses
Console::Instance->_runLock.Acquire();
}
void Console::Resume()
{
Console::Instance->_runLock.Release();
Console::Instance->_pauseLock.Release();
2016-07-31 14:31:44 -04:00
shared_ptr<Debugger> debugger = Console::Instance->_debugger;
if(debugger) {
//Make sure debugger resumes if we try to pause the emu, otherwise we will get deadlocked.
2016-07-31 14:31:44 -04:00
debugger->Resume();
}
}
2014-06-14 11:27:55 -04:00
void Console::Run()
{
2014-06-21 15:43:41 -04:00
Timer clockTimer;
2015-07-21 23:05:27 -04:00
double targetTime;
uint32_t lastFrameNumber = -1;
_autoSaveManager.reset(new AutoSaveManager());
_runLock.Acquire();
_stopLock.Acquire();
targetTime = GetFrameDelay();
2015-07-21 23:05:27 -04:00
VideoDecoder::GetInstance()->StartThread();
PlatformUtilities::DisableScreensaver();
UpdateNesModel(true);
bool crashed = false;
2014-06-21 15:43:41 -04:00
while(true) {
try {
_cpu->Exec();
} catch(const std::runtime_error &ex) {
crashed = true;
MessageManager::DisplayMessage("Error", "GameCrash", ex.what());
break;
}
if(_resetRequested) {
//Used by NSF player to reset console after changing track
//Also used with DisablePpuReset option to reset mid-frame
2017-04-18 22:39:45 -04:00
MovieManager::Stop();
ResetComponents(true);
_resetRequested = false;
}
2015-07-16 16:55:16 -04:00
uint32_t currentFrameNumber = PPU::GetFrameCount();
if(currentFrameNumber != lastFrameNumber) {
if(_controlManager->GetLagFlag()) {
_lagCounter++;
}
2017-04-28 19:54:58 -04:00
_rewindManager->ProcessEndOfFrame();
EmulationSettings::DisableOverclocking(_disableOcNextFrame || NsfMapper::GetInstance());
_disableOcNextFrame = false;
2017-04-28 19:54:58 -04:00
lastFrameNumber = PPU::GetFrameCount();
//Sleep until we're ready to start the next frame
clockTimer.WaitUntil(targetTime);
2016-06-25 20:46:54 -04:00
if(!_pauseLock.IsFree()) {
//Need to temporarely pause the emu (to save/load a state, etc.)
_runLock.Release();
//Spin wait until we are allowed to start again
_pauseLock.WaitForRelease();
_runLock.Acquire();
}
shared_ptr<Debugger> debugger = _debugger;
bool paused = EmulationSettings::IsPaused();
if(paused && !_stop) {
MessageManager::SendNotification(ConsoleNotificationType::GamePaused);
//Prevent audio from looping endlessly while game is paused
SoundMixer::StopAudio();
_runLock.Release();
PlatformUtilities::EnableScreensaver();
while(paused && !_stop && (!debugger || !debugger->CheckFlag(DebuggerFlags::DebuggerWindowEnabled))) {
//Sleep until emulation is resumed
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(30));
paused = EmulationSettings::IsPaused();
}
if(debugger && debugger->CheckFlag(DebuggerFlags::DebuggerWindowEnabled)) {
//Prevent pausing when debugger is active
EmulationSettings::ClearFlags(EmulationFlags::Paused);
}
PlatformUtilities::DisableScreensaver();
_runLock.Acquire();
MessageManager::SendNotification(ConsoleNotificationType::GameResumed);
}
2015-07-21 23:05:27 -04:00
if(debugger) {
debugger->ProcessEvent(EventType::StartFrame);
}
//Get next target time, and adjust based on whether we are ahead or behind
double timeLag = EmulationSettings::GetEmulationSpeed() == 0 ? 0 : clockTimer.GetElapsedMS() - targetTime;
UpdateNesModel(true);
targetTime = GetFrameDelay();
clockTimer.Reset();
targetTime -= timeLag;
if(targetTime < 0) {
targetTime = 0;
}
if(_stop) {
_stop = false;
break;
}
}
2014-06-14 18:20:56 -04:00
}
if(!crashed) {
SaveStateManager::SaveRecentGame(_mapper->GetRomName(), _romFilepath, _patchFilename);
}
2017-04-28 19:54:58 -04:00
_rewindManager.reset();
StopRecordingHdPack();
SoundMixer::StopAudio();
2017-04-18 22:39:45 -04:00
MovieManager::Stop();
SoundMixer::StopRecording();
PlatformUtilities::EnableScreensaver();
_autoSaveManager.reset();
VideoDecoder::GetInstance()->StopThread();
EmulationSettings::ClearFlags(EmulationFlags::Paused);
_initialized = false;
if(!_romFilepath.empty() && _mapper) {
//Ensure we save any battery file before unloading anything
_mapper->SaveBattery();
}
_romFilepath = "";
_mapper.reset();
_ppu.reset();
_cpu.reset();
_memoryManager.reset();
_controlManager.reset();
_hdPackBuilder.reset();
_hdData.reset();
_stopLock.Release();
_runLock.Release();
MessageManager::SendNotification(ConsoleNotificationType::GameStopped);
2014-06-25 21:52:37 -04:00
}
bool Console::IsRunning()
{
2017-08-30 18:31:27 -04:00
return !Instance->_stopLock.IsFree() && !Instance->_runLock.IsFree();
}
void Console::UpdateNesModel(bool sendNotification)
2015-07-21 23:05:27 -04:00
{
bool configChanged = false;
if(EmulationSettings::NeedControllerUpdate()) {
_controlManager->UpdateControlDevices();
configChanged = true;
}
2015-07-21 23:05:27 -04:00
NesModel model = EmulationSettings::GetNesModel();
if(model == NesModel::Auto) {
switch(_mapper->GetGameSystem()) {
case GameSystem::NesPal: model = NesModel::PAL; break;
case GameSystem::Dendy: model = NesModel::Dendy; break;
default: model = NesModel::NTSC; break;
}
2015-07-21 23:05:27 -04:00
}
if(_model != model) {
_model = model;
configChanged = true;
if(sendNotification) {
MessageManager::DisplayMessage("Region", model == NesModel::PAL ? "PAL" : (model == NesModel::Dendy ? "Dendy" : "NTSC"));
}
}
_mapper->SetNesModel(model);
_ppu->SetNesModel(model);
_apu->SetNesModel(model);
if(configChanged && sendNotification) {
MessageManager::SendNotification(ConsoleNotificationType::ConfigChanged);
}
}
double Console::GetFrameDelay()
{
uint32_t emulationSpeed = EmulationSettings::GetEmulationSpeed();
double frameDelay;
if(emulationSpeed == 0) {
frameDelay = 0;
} else {
//60.1fps (NTSC), 50.01fps (PAL/Dendy)
switch(_model) {
default:
2017-09-29 22:09:00 -04:00
case NesModel::NTSC: frameDelay = EmulationSettings::CheckFlag(EmulationFlags::IntegerFpsMode) ? 16.6666666666666666667 : 16.63926405550947; break;
case NesModel::PAL:
2017-09-29 22:09:00 -04:00
case NesModel::Dendy: frameDelay = EmulationSettings::CheckFlag(EmulationFlags::IntegerFpsMode) ? 20 : 19.99720920217466; break;
}
frameDelay /= (double)emulationSpeed / 100.0;
}
return frameDelay;
2015-07-21 23:05:27 -04:00
}
void Console::SaveState(ostream &saveStream)
2014-06-25 21:52:37 -04:00
{
if(Instance->_initialized) {
Instance->_cpu->SaveSnapshot(&saveStream);
Instance->_ppu->SaveSnapshot(&saveStream);
Instance->_memoryManager->SaveSnapshot(&saveStream);
Instance->_apu->SaveSnapshot(&saveStream);
Instance->_controlManager->SaveSnapshot(&saveStream);
Instance->_mapper->SaveSnapshot(&saveStream);
if(Instance->_hdAudioDevice) {
Instance->_hdAudioDevice->SaveSnapshot(&saveStream);
} else {
Snapshotable::WriteEmptyBlock(&saveStream);
}
}
}
2014-06-25 21:52:37 -04:00
void Console::LoadState(istream &loadStream)
{
if(Instance->_initialized) {
//Stop any movie that might have been playing/recording if a state is loaded
//(Note: Loading a state is disabled in the UI while a movie is playing/recording)
2017-04-18 22:39:45 -04:00
MovieManager::Stop();
Instance->_cpu->LoadSnapshot(&loadStream);
Instance->_ppu->LoadSnapshot(&loadStream);
Instance->_memoryManager->LoadSnapshot(&loadStream);
Instance->_apu->LoadSnapshot(&loadStream);
Instance->_controlManager->LoadSnapshot(&loadStream);
Instance->_mapper->LoadSnapshot(&loadStream);
if(Instance->_hdAudioDevice) {
Instance->_hdAudioDevice->LoadSnapshot(&loadStream);
} else {
Snapshotable::SkipBlock(&loadStream);
}
MessageManager::SendNotification(ConsoleNotificationType::StateLoaded);
}
}
void Console::LoadState(uint8_t *buffer, uint32_t bufferSize)
{
2017-04-28 19:54:58 -04:00
//Send any unprocessed sound to the SoundMixer - needed for rewind
Instance->_apu->EndFrame();
stringstream stream;
stream.write((char*)buffer, bufferSize);
stream.seekg(0, ios::beg);
LoadState(stream);
2014-06-25 21:52:37 -04:00
}
std::shared_ptr<Debugger> Console::GetDebugger(bool autoStart)
{
2016-07-31 14:31:44 -04:00
auto lock = _debuggerLock.AcquireSafe();
if(!_debugger && autoStart) {
2017-08-30 18:31:27 -04:00
_debugger.reset(new Debugger(Console::Instance, _cpu, _ppu, _apu, _memoryManager, _mapper));
}
return _debugger;
}
void Console::StopDebugger()
{
auto lock = _debuggerLock.AcquireSafe();
_debugger.reset();
}
2016-06-25 20:46:54 -04:00
void Console::RequestReset()
{
Instance->_resetRequested = true;
}
2016-07-10 18:22:37 -04:00
uint32_t Console::GetLagCounter()
{
return Instance->_lagCounter;
}
void Console::ResetLagCounter()
{
Instance->_lagCounter = 0;
}
bool Console::IsDebuggerAttached()
{
return (bool)Instance->_debugger;
}
void Console::SetNextFrameOverclockStatus(bool disabled)
{
Instance->_disableOcNextFrame = disabled;
}
HdPackData* Console::GetHdData()
{
return Instance->_hdData.get();
}
bool Console::IsHdPpu()
{
return Instance->_hdData && std::dynamic_pointer_cast<HdPpu>(Instance->_ppu) != nullptr;
}
void Console::LoadHdPack(VirtualFile &romFile, VirtualFile &patchFile)
{
_hdData.reset();
_hdAudioDevice.reset();
if(EmulationSettings::CheckFlag(EmulationFlags::UseHdPacks)) {
_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()) {
patchFile = result->second;
}
}
}
}
void Console::StartRecordingHdPack(string saveFolder, ScaleFilterType filterType, uint32_t scale, uint32_t flags, uint32_t chrRamBankSize)
{
Console::Pause();
std::stringstream saveState;
Instance->SaveState(saveState);
Instance->_hdPackBuilder.reset();
Instance->_hdPackBuilder.reset(new HdPackBuilder(saveFolder, filterType, scale, flags, chrRamBankSize, !Instance->_mapper->HasChrRom()));
Instance->_memoryManager->UnregisterIODevice(Instance->_ppu.get());
Instance->_ppu.reset();
Instance->_ppu.reset(new HdBuilderPpu(Instance->_mapper.get(), Instance->_hdPackBuilder.get(), chrRamBankSize));
Instance->_memoryManager->RegisterIODevice(Instance->_ppu.get());
Instance->LoadState(saveState);
Console::Resume();
}
void Console::StopRecordingHdPack()
{
if(Instance->_hdPackBuilder) {
Console::Pause();
std::stringstream saveState;
Instance->SaveState(saveState);
Instance->_memoryManager->UnregisterIODevice(Instance->_ppu.get());
Instance->_ppu.reset();
Instance->_ppu.reset(new PPU(Instance->_mapper.get()));
Instance->_memoryManager->RegisterIODevice(Instance->_ppu.get());
Instance->_hdPackBuilder.reset();
Instance->LoadState(saveState);
Console::Resume();
}
}