4545#include " DebugHud.h"
4646#include " NotificationManager.h"
4747
48- Console::Console ()
48+ Console::Console (shared_ptr<Console> master )
4949{
50+ _master = master;
5051 _model = NesModel::NTSC;
52+
53+ if (_master) {
54+ _master->_notificationManager ->SendNotification (ConsoleNotificationType::VsDualSystemStarted);
55+ }
5156}
5257
5358Console::~Console ()
5459{
5560 MovieManager::Stop ();
56- GetSoundMixer ()->StopRecording ();
5761}
5862
5963void Console::Init ()
6064{
6165 _notificationManager.reset (new NotificationManager ());
62- _saveStateManager. reset ( new SaveStateManager ( shared_from_this ()));
66+
6367 _videoRenderer.reset (new VideoRenderer (shared_from_this ()));
6468 _videoDecoder.reset (new VideoDecoder (shared_from_this ()));
69+
70+ _saveStateManager.reset (new SaveStateManager (shared_from_this ()));
6571 _cheatManager.reset (new CheatManager (shared_from_this ()));
6672 _debugHud.reset (new DebugHud ());
73+
6774 _soundMixer.reset (new SoundMixer (shared_from_this ()));
75+ _soundMixer->SetNesModel (_model);
6876}
6977
7078void Console::Release (bool forShutdown)
7179{
7280 if (forShutdown) {
73- _saveStateManager.reset ();
74- _videoRenderer.reset ();
81+ _videoDecoder->StopThread ();
82+ _videoRenderer->StopThread ();
83+
7584 _videoDecoder.reset ();
85+ _videoRenderer.reset ();
86+
7687 _debugHud.reset ();
88+ _saveStateManager.reset ();
7789 _cheatManager.reset ();
90+
91+ _soundMixer.reset ();
92+ _notificationManager.reset ();
93+ }
94+
95+ if (_master) {
96+ _master->_notificationManager ->SendNotification (ConsoleNotificationType::VsDualSystemStopped);
7897 }
7998
8099 _rewindManager.reset ();
@@ -85,7 +104,13 @@ void Console::Release(bool forShutdown)
85104 _hdAudioDevice.reset ();
86105
87106 _systemActionManager.reset ();
107+
108+ if (_slave) {
109+ _slave->Release (true );
110+ _slave.reset ();
111+ }
88112
113+ _master.reset ();
89114 _cpu.reset ();
90115 _ppu.reset ();
91116 _apu.reset ();
@@ -246,20 +271,26 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
246271
247272 // Changed game, stop all recordings
248273 MovieManager::Stop ();
249- GetSoundMixer () ->StopRecording ();
274+ _soundMixer ->StopRecording ();
250275 StopRecordingHdPack ();
251276 }
252277
253- #ifndef LIBRETRO
254- // Don't use auto-save manager for libretro
255- _autoSaveManager.reset (new AutoSaveManager (shared_from_this ()));
256- #endif
257-
258278 _mapper = mapper;
259279 _memoryManager.reset (new MemoryManager (shared_from_this ()));
260280 _cpu.reset (new CPU (shared_from_this ()));
261281 _apu.reset (new APU (shared_from_this ()));
262282
283+ if (_slave) {
284+ _slave->Release (false );
285+ _slave.reset ();
286+ }
287+
288+ if (!_master && _mapper->GetMapperInfo ().System == GameSystem::VsDualSystem) {
289+ _slave.reset (new Console (shared_from_this ()));
290+ _slave->Init ();
291+ _slave->Initialize (romFile, patchFile);
292+ }
293+
263294 GameSystem system = _mapper->GetMapperInfo ().System ;
264295
265296 switch (system) {
@@ -269,6 +300,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
269300 break ;
270301
271302 case GameSystem::VsUniSystem:
303+ case GameSystem::VsDualSystem:
272304 _systemActionManager.reset (new VsSystemActionManager (shared_from_this ()));
273305 break ;
274306
@@ -285,7 +317,8 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
285317 // When power cycling, poll counter must be preserved to allow movies to playback properly
286318 pollCounter = _controlManager->GetPollCounter ();
287319 }
288- if (system == GameSystem::VsUniSystem) {
320+
321+ if (system == GameSystem::VsUniSystem || system == GameSystem::VsDualSystem) {
289322 _controlManager.reset (new VsControlManager (shared_from_this (), _systemActionManager, _mapper->GetMapperControlDevice ()));
290323 } else {
291324 _controlManager.reset (new ControlManager (shared_from_this (), _systemActionManager, _mapper->GetMapperControlDevice ()));
@@ -330,20 +363,29 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
330363
331364 ResetComponents (false );
332365
366+ #ifndef LIBRETRO
367+ // Don't use auto-save manager for libretro
368+ // Only enable auto-save for the master console (VS Dualsystem)
369+ if (IsMaster ()) {
370+ _autoSaveManager.reset (new AutoSaveManager (shared_from_this ()));
371+ }
372+ #endif
333373 _rewindManager.reset (new RewindManager (shared_from_this ()));
334374 _notificationManager->RegisterNotificationListener (_rewindManager);
335375
336376 _videoDecoder->StartThread ();
337377
338378 FolderUtilities::AddKnownGameFolder (romFile.GetFolderPath ());
339379
340- string modelName = _model == NesModel::PAL ? " PAL" : (_model == NesModel::Dendy ? " Dendy" : " NTSC" );
341- string messageTitle = MessageManager::Localize (" GameLoaded" ) + " (" + modelName + " )" ;
342- MessageManager::DisplayMessage (messageTitle, FolderUtilities::GetFilename (GetMapperInfo ().RomName , false ));
343- if (EmulationSettings::GetOverclockRate () != 100 ) {
344- MessageManager::DisplayMessage (" ClockRate" , std::to_string (EmulationSettings::GetOverclockRate ()) + " %" );
380+ if (IsMaster ()) {
381+ string modelName = _model == NesModel::PAL ? " PAL" : (_model == NesModel::Dendy ? " Dendy" : " NTSC" );
382+ string messageTitle = MessageManager::Localize (" GameLoaded" ) + " (" + modelName + " )" ;
383+ MessageManager::DisplayMessage (messageTitle, FolderUtilities::GetFilename (GetMapperInfo ().RomName , false ));
384+ if (EmulationSettings::GetOverclockRate () != 100 ) {
385+ MessageManager::DisplayMessage (" ClockRate" , std::to_string (EmulationSettings::GetOverclockRate ()) + " %" );
386+ }
387+ EmulationSettings::ClearFlags (EmulationFlags::ForceMaxSpeed);
345388 }
346- EmulationSettings::ClearFlags (EmulationFlags::ForceMaxSpeed);
347389 Resume ();
348390 return true ;
349391 }
@@ -392,6 +434,24 @@ shared_ptr<NotificationManager> Console::GetNotificationManager()
392434 return _notificationManager;
393435}
394436
437+ bool Console::IsDualSystem ()
438+ {
439+ return _slave != nullptr || _master != nullptr ;
440+ }
441+
442+ shared_ptr<Console> Console::GetDualConsole ()
443+ {
444+ // When called from the master, returns the slave.
445+ // When called from the slave, returns the master.
446+ // Returns a null pointer when not running a dualsystem game
447+ return _slave ? _slave : _master;
448+ }
449+
450+ bool Console::IsMaster ()
451+ {
452+ return !_master;
453+ }
454+
395455BaseMapper* Console::GetMapper ()
396456{
397457 return _mapper.get ();
@@ -476,6 +536,11 @@ void Console::Reset(bool softReset)
476536
477537void Console::ResetComponents (bool softReset)
478538{
539+ if (_slave) {
540+ // Always reset/power cycle the slave alongside the master CPU
541+ _slave->ResetComponents (softReset);
542+ }
543+
479544 _soundMixer->StopAudio (true );
480545
481546 _memoryManager->Reset (softReset);
@@ -523,15 +588,26 @@ void Console::Pause()
523588 // Make sure debugger resumes if we try to pause the emu, otherwise we will get deadlocked.
524589 debugger->Suspend ();
525590 }
526- _pauseLock.Acquire ();
527- // Spin wait until emu pauses
528- _runLock.Acquire ();
591+
592+ if (_master) {
593+ // When trying to pause/resume the slave, we need to pause/resume the master instead
594+ _master->Pause ();
595+ } else {
596+ _pauseLock.Acquire ();
597+ // Spin wait until emu pauses
598+ _runLock.Acquire ();
599+ }
529600}
530601
531602void Console::Resume ()
532603{
533- _runLock.Release ();
534- _pauseLock.Release ();
604+ if (_master) {
605+ // When trying to pause/resume the slave, we need to pause/resume the master instead
606+ _master->Resume ();
607+ } else {
608+ _runLock.Release ();
609+ _pauseLock.Release ();
610+ }
535611
536612 shared_ptr<Debugger> debugger = _debugger;
537613 if (debugger) {
@@ -567,6 +643,8 @@ void Console::Run()
567643 int timeLagDataIndex = 0 ;
568644 double lastFrameMin = 9999 ;
569645 double lastFrameMax = 0 ;
646+ int32_t cycleGap = 0 ;
647+ uint32_t currentFrameNumber = 0 ;
570648
571649 uint32_t lastFrameNumber = -1 ;
572650
@@ -590,13 +668,32 @@ void Console::Run()
590668 while (true ) {
591669 _cpu->Exec ();
592670
593- uint32_t currentFrameNumber = _ppu->GetFrameCount ();
671+ currentFrameNumber = _ppu->GetFrameCount ();
672+
673+ if (_slave) {
674+ while (true ) {
675+ // Run the slave until it catches up to the master CPU (and take into account the CPU count overflow that occurs every ~20mins)
676+ cycleGap = _cpu->GetCycleCount () - _slave->_cpu ->GetCycleCount ();
677+ if (cycleGap > 5 || cycleGap < -10000 || currentFrameNumber > _slave->_ppu ->GetFrameCount ()) {
678+ _slave->_cpu ->Exec ();
679+ } else {
680+ break ;
681+ }
682+ }
683+ }
684+
594685 if (currentFrameNumber != lastFrameNumber) {
595686 _soundMixer->ProcessEndOfFrame ();
687+ if (_slave) {
688+ _slave->_soundMixer ->ProcessEndOfFrame ();
689+ }
596690
597691 bool displayDebugInfo = EmulationSettings::CheckFlag (EmulationFlags::DisplayDebugInfo);
598692 if (displayDebugInfo) {
599693 DisplayDebugInformation (clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData);
694+ if (_slave) {
695+ _slave->DisplayDebugInformation (clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData);
696+ }
600697 }
601698 lastFrameTimer.Reset ();
602699
@@ -623,6 +720,9 @@ void Console::Run()
623720
624721 // Prevent audio from looping endlessly while game is paused
625722 _soundMixer->StopAudio ();
723+ if (_slave) {
724+ _slave->_soundMixer ->StopAudio ();
725+ }
626726
627727 _runLock.Release ();
628728
@@ -695,7 +795,7 @@ void Console::Run()
695795
696796 _soundMixer->StopAudio ();
697797 MovieManager::Stop ();
698- GetSoundMixer () ->StopRecording ();
798+ _soundMixer ->StopRecording ();
699799
700800 PlatformUtilities::EnableScreensaver ();
701801 PlatformUtilities::RestoreTimerResolution ();
@@ -725,12 +825,22 @@ void Console::Run()
725825
726826bool Console::IsRunning ()
727827{
728- return !_stopLock.IsFree () && _running;
828+ if (_master) {
829+ // For slave CPU, return the master's state
830+ return _master->IsRunning ();
831+ } else {
832+ return !_stopLock.IsFree () && _running;
833+ }
729834}
730835
731836bool Console::IsPaused ()
732837{
733- return _runLock.IsFree () || !_pauseLock.IsFree () || !_running;
838+ if (_master) {
839+ // For slave CPU, return the master's state
840+ return _master->IsPaused ();
841+ } else {
842+ return _runLock.IsFree () || !_pauseLock.IsFree () || !_running;
843+ }
734844}
735845
736846void Console::UpdateNesModel (bool sendNotification)
@@ -801,6 +911,11 @@ void Console::SaveState(ostream &saveStream)
801911 } else {
802912 Snapshotable::WriteEmptyBlock (&saveStream);
803913 }
914+
915+ if (_slave) {
916+ // For VS Dualsystem, append the 2nd console's savestate
917+ _slave->SaveState (saveStream);
918+ }
804919 }
805920}
806921
@@ -823,6 +938,11 @@ void Console::LoadState(istream &loadStream, uint32_t stateVersion)
823938 } else {
824939 Snapshotable::SkipBlock (&loadStream);
825940 }
941+
942+ if (_slave) {
943+ // For VS Dualsystem, the slave console's savestate is appended to the end of the file
944+ _slave->LoadState (loadStream, stateVersion);
945+ }
826946
827947 shared_ptr<Debugger> debugger = _debugger;
828948 if (debugger) {
0 commit comments