From 47fbe93b62c986111c0b474007477d00d6c65c3b Mon Sep 17 00:00:00 2001 From: Sour Date: Mon, 2 Jul 2018 21:32:59 -0400 Subject: [PATCH] Added support for VS DualSystem (WIP - dual video/audio, input, save states, movies & netplay working) --- Core/BaseMapper.cpp | 2 +- Core/BaseRenderer.cpp | 5 + Core/Console.cpp | 174 +++++++++++++++++++++++++----- Core/Console.h | 10 +- Core/ControlManager.h | 14 +-- Core/GameDatabase.cpp | 2 + Core/INotificationListener.h | 2 + Core/PPU.cpp | 2 +- Core/SoundMixer.cpp | 3 + Core/StandardController.h | 5 + Core/Types.h | 1 + Core/VsControlManager.cpp | 68 ++++++++++++ Core/VsControlManager.h | 13 ++- Core/VsSystem.h | 51 +++++++-- GUI.NET/Forms/frmMain.Designer.cs | 13 +++ GUI.NET/Forms/frmMain.cs | 38 ++++++- GUI.NET/InteropEmu.cs | 5 + InteropDLL/ConsoleWrapper.cpp | 63 +++++++---- InteropDLL/DebugWrapper.cpp | 2 +- Linux/SdlRenderer.cpp | 9 +- Linux/SdlRenderer.h | 4 +- Linux/SdlSoundManager.cpp | 1 + Windows/Renderer.cpp | 7 +- 23 files changed, 409 insertions(+), 85 deletions(-) diff --git a/Core/BaseMapper.cpp b/Core/BaseMapper.cpp index 5443b280..a5281598 100644 --- a/Core/BaseMapper.cpp +++ b/Core/BaseMapper.cpp @@ -643,7 +643,7 @@ void BaseMapper::ApplyCheats() void BaseMapper::GetMemoryRanges(MemoryRanges &ranges) { - if(_gameSystem == GameSystem::VsUniSystem) { + if(_gameSystem == GameSystem::VsUniSystem || _gameSystem == GameSystem::VsDualSystem) { ranges.AddHandler(MemoryOperation::Read, 0x6000, 0xFFFF); ranges.AddHandler(MemoryOperation::Write, 0x6000, 0xFFFF); } else { diff --git a/Core/BaseRenderer.cpp b/Core/BaseRenderer.cpp index 41020751..b1fd9394 100644 --- a/Core/BaseRenderer.cpp +++ b/Core/BaseRenderer.cpp @@ -9,6 +9,11 @@ BaseRenderer::BaseRenderer(shared_ptr console) { _console = console; + + if(console->IsMaster()) { + //Only display messages on the master CPU's screen + MessageManager::RegisterMessageManager(this); + } } void BaseRenderer::DisplayMessage(string title, string message) diff --git a/Core/Console.cpp b/Core/Console.cpp index 6574871b..054e8a45 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -45,36 +45,55 @@ #include "DebugHud.h" #include "NotificationManager.h" -Console::Console() +Console::Console(shared_ptr master) { + _master = master; _model = NesModel::NTSC; + + if(_master) { + _master->_notificationManager->SendNotification(ConsoleNotificationType::VsDualSystemStarted); + } } Console::~Console() { MovieManager::Stop(); - GetSoundMixer()->StopRecording(); } void Console::Init() { _notificationManager.reset(new NotificationManager()); - _saveStateManager.reset(new SaveStateManager(shared_from_this())); + _videoRenderer.reset(new VideoRenderer(shared_from_this())); _videoDecoder.reset(new VideoDecoder(shared_from_this())); + + _saveStateManager.reset(new SaveStateManager(shared_from_this())); _cheatManager.reset(new CheatManager(shared_from_this())); _debugHud.reset(new DebugHud()); + _soundMixer.reset(new SoundMixer(shared_from_this())); + _soundMixer->SetNesModel(_model); } void Console::Release(bool forShutdown) { if(forShutdown) { - _saveStateManager.reset(); - _videoRenderer.reset(); + _videoDecoder->StopThread(); + _videoRenderer->StopThread(); + _videoDecoder.reset(); + _videoRenderer.reset(); + _debugHud.reset(); + _saveStateManager.reset(); _cheatManager.reset(); + + _soundMixer.reset(); + _notificationManager.reset(); + } + + if(_master) { + _master->_notificationManager->SendNotification(ConsoleNotificationType::VsDualSystemStopped); } _rewindManager.reset(); @@ -85,7 +104,13 @@ void Console::Release(bool forShutdown) _hdAudioDevice.reset(); _systemActionManager.reset(); + + if(_slave) { + _slave->Release(true); + _slave.reset(); + } + _master.reset(); _cpu.reset(); _ppu.reset(); _apu.reset(); @@ -246,20 +271,26 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) //Changed game, stop all recordings MovieManager::Stop(); - GetSoundMixer()->StopRecording(); + _soundMixer->StopRecording(); StopRecordingHdPack(); } -#ifndef LIBRETRO - //Don't use auto-save manager for libretro - _autoSaveManager.reset(new AutoSaveManager(shared_from_this())); -#endif - _mapper = mapper; _memoryManager.reset(new MemoryManager(shared_from_this())); _cpu.reset(new CPU(shared_from_this())); _apu.reset(new APU(shared_from_this())); + if(_slave) { + _slave->Release(false); + _slave.reset(); + } + + if(!_master && _mapper->GetMapperInfo().System == GameSystem::VsDualSystem) { + _slave.reset(new Console(shared_from_this())); + _slave->Init(); + _slave->Initialize(romFile, patchFile); + } + GameSystem system = _mapper->GetMapperInfo().System; switch(system) { @@ -269,6 +300,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) break; case GameSystem::VsUniSystem: + case GameSystem::VsDualSystem: _systemActionManager.reset(new VsSystemActionManager(shared_from_this())); break; @@ -285,7 +317,8 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) //When power cycling, poll counter must be preserved to allow movies to playback properly pollCounter = _controlManager->GetPollCounter(); } - if(system == GameSystem::VsUniSystem) { + + if(system == GameSystem::VsUniSystem || system == GameSystem::VsDualSystem) { _controlManager.reset(new VsControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice())); } else { _controlManager.reset(new ControlManager(shared_from_this(), _systemActionManager, _mapper->GetMapperControlDevice())); @@ -330,6 +363,13 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) ResetComponents(false); +#ifndef LIBRETRO + //Don't use auto-save manager for libretro + //Only enable auto-save for the master console (VS Dualsystem) + if(IsMaster()) { + _autoSaveManager.reset(new AutoSaveManager(shared_from_this())); + } +#endif _rewindManager.reset(new RewindManager(shared_from_this())); _notificationManager->RegisterNotificationListener(_rewindManager); @@ -337,13 +377,15 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) 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(GetMapperInfo().RomName, false)); - if(EmulationSettings::GetOverclockRate() != 100) { - MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%"); + if(IsMaster()) { + string modelName = _model == NesModel::PAL ? "PAL" : (_model == NesModel::Dendy ? "Dendy" : "NTSC"); + string messageTitle = MessageManager::Localize("GameLoaded") + " (" + modelName + ")"; + MessageManager::DisplayMessage(messageTitle, FolderUtilities::GetFilename(GetMapperInfo().RomName, false)); + if(EmulationSettings::GetOverclockRate() != 100) { + MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%"); + } + EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed); } - EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed); Resume(); return true; } @@ -392,6 +434,24 @@ shared_ptr Console::GetNotificationManager() return _notificationManager; } +bool Console::IsDualSystem() +{ + return _slave != nullptr || _master != nullptr; +} + +shared_ptr Console::GetDualConsole() +{ + //When called from the master, returns the slave. + //When called from the slave, returns the master. + //Returns a null pointer when not running a dualsystem game + return _slave ? _slave : _master; +} + +bool Console::IsMaster() +{ + return !_master; +} + BaseMapper* Console::GetMapper() { return _mapper.get(); @@ -476,6 +536,11 @@ void Console::Reset(bool softReset) void Console::ResetComponents(bool softReset) { + if(_slave) { + //Always reset/power cycle the slave alongside the master CPU + _slave->ResetComponents(softReset); + } + _soundMixer->StopAudio(true); _memoryManager->Reset(softReset); @@ -523,15 +588,26 @@ void Console::Pause() //Make sure debugger resumes if we try to pause the emu, otherwise we will get deadlocked. debugger->Suspend(); } - _pauseLock.Acquire(); - //Spin wait until emu pauses - _runLock.Acquire(); + + if(_master) { + //When trying to pause/resume the slave, we need to pause/resume the master instead + _master->Pause(); + } else { + _pauseLock.Acquire(); + //Spin wait until emu pauses + _runLock.Acquire(); + } } void Console::Resume() { - _runLock.Release(); - _pauseLock.Release(); + if(_master) { + //When trying to pause/resume the slave, we need to pause/resume the master instead + _master->Resume(); + } else { + _runLock.Release(); + _pauseLock.Release(); + } shared_ptr debugger = _debugger; if(debugger) { @@ -567,6 +643,8 @@ void Console::Run() int timeLagDataIndex = 0; double lastFrameMin = 9999; double lastFrameMax = 0; + int32_t cycleGap = 0; + uint32_t currentFrameNumber = 0; uint32_t lastFrameNumber = -1; @@ -590,13 +668,32 @@ void Console::Run() while(true) { _cpu->Exec(); - uint32_t currentFrameNumber = _ppu->GetFrameCount(); + currentFrameNumber = _ppu->GetFrameCount(); + + if(_slave) { + while(true) { + //Run the slave until it catches up to the master CPU (and take into account the CPU count overflow that occurs every ~20mins) + cycleGap = _cpu->GetCycleCount() - _slave->_cpu->GetCycleCount(); + if(cycleGap > 5 || cycleGap < -10000 || currentFrameNumber > _slave->_ppu->GetFrameCount()) { + _slave->_cpu->Exec(); + } else { + break; + } + } + } + if(currentFrameNumber != lastFrameNumber) { _soundMixer->ProcessEndOfFrame(); + if(_slave) { + _slave->_soundMixer->ProcessEndOfFrame(); + } bool displayDebugInfo = EmulationSettings::CheckFlag(EmulationFlags::DisplayDebugInfo); if(displayDebugInfo) { DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData); + if(_slave) { + _slave->DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData); + } } lastFrameTimer.Reset(); @@ -623,6 +720,9 @@ void Console::Run() //Prevent audio from looping endlessly while game is paused _soundMixer->StopAudio(); + if(_slave) { + _slave->_soundMixer->StopAudio(); + } _runLock.Release(); @@ -695,7 +795,7 @@ void Console::Run() _soundMixer->StopAudio(); MovieManager::Stop(); - GetSoundMixer()->StopRecording(); + _soundMixer->StopRecording(); PlatformUtilities::EnableScreensaver(); PlatformUtilities::RestoreTimerResolution(); @@ -725,12 +825,22 @@ void Console::Run() bool Console::IsRunning() { - return !_stopLock.IsFree() && _running; + if(_master) { + //For slave CPU, return the master's state + return _master->IsRunning(); + } else { + return !_stopLock.IsFree() && _running; + } } bool Console::IsPaused() { - return _runLock.IsFree() || !_pauseLock.IsFree() || !_running; + if(_master) { + //For slave CPU, return the master's state + return _master->IsPaused(); + } else { + return _runLock.IsFree() || !_pauseLock.IsFree() || !_running; + } } void Console::UpdateNesModel(bool sendNotification) @@ -801,6 +911,11 @@ void Console::SaveState(ostream &saveStream) } else { Snapshotable::WriteEmptyBlock(&saveStream); } + + if(_slave) { + //For VS Dualsystem, append the 2nd console's savestate + _slave->SaveState(saveStream); + } } } @@ -823,6 +938,11 @@ void Console::LoadState(istream &loadStream, uint32_t stateVersion) } else { Snapshotable::SkipBlock(&loadStream); } + + if(_slave) { + //For VS Dualsystem, the slave console's savestate is appended to the end of the file + _slave->LoadState(loadStream, stateVersion); + } shared_ptr debugger = _debugger; if(debugger) { diff --git a/Core/Console.h b/Core/Console.h index e513786e..53770773 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -50,6 +50,10 @@ private: shared_ptr _mapper; shared_ptr _controlManager; shared_ptr _memoryManager; + + //Used by VS-DualSystem + shared_ptr _master; + shared_ptr _slave; shared_ptr _systemActionManager; @@ -87,7 +91,7 @@ private: void DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData); public: - Console(); + Console(shared_ptr master = nullptr); ~Console(); void Init(); @@ -100,6 +104,10 @@ public: shared_ptr GetSoundMixer(); shared_ptr GetNotificationManager(); + bool IsDualSystem(); + shared_ptr GetDualConsole(); + bool IsMaster(); + void ProcessCpuClock(); CPU* GetCpu(); PPU* GetPpu(); diff --git a/Core/ControlManager.h b/Core/ControlManager.h index ff77ca3d..45d3e713 100644 --- a/Core/ControlManager.h +++ b/Core/ControlManager.h @@ -20,25 +20,25 @@ class ControlManager : public Snapshotable, public IMemoryHandler private: vector _inputRecorders; vector _inputProviders; - SimpleLock _deviceLock; - + //Static so that power cycle does not reset its value //TODOCONSOLE : PollCounter needs to be kept through power cycle uint32_t _pollCounter; - vector> _controlDevices; - - shared_ptr _systemActionManager; shared_ptr _mapperControlDevice; uint32_t _lagCounter = 0; bool _isLagging = false; uint8_t GetOpenBusMask(uint8_t port); - void RegisterControlDevice(shared_ptr controlDevice); protected: shared_ptr _console; + SimpleLock _deviceLock; + vector> _controlDevices; + shared_ptr _systemActionManager; + + void RegisterControlDevice(shared_ptr controlDevice); virtual void StreamState(bool saving) override; virtual ControllerType GetControllerType(uint8_t port); @@ -47,7 +47,7 @@ public: ControlManager(shared_ptr console, shared_ptr systemActionManager, shared_ptr mapperControlDevice); virtual ~ControlManager(); - void UpdateControlDevices(); + virtual void UpdateControlDevices(); void UpdateInputState(); uint32_t GetLagCounter(); diff --git a/Core/GameDatabase.cpp b/Core/GameDatabase.cpp index ebebc3a1..876b0225 100644 --- a/Core/GameDatabase.cpp +++ b/Core/GameDatabase.cpp @@ -101,6 +101,8 @@ GameSystem GameDatabase::GetGameSystem(string system) return GameSystem::Famicom; } else if(system.compare("VsUni") == 0) { return GameSystem::VsUniSystem; + } else if(system.compare("VsDual") == 0) { + return GameSystem::VsDualSystem; } else if(system.compare("Dendy") == 0) { return GameSystem::Dendy; } else if(system.compare("Playchoice") == 0) { diff --git a/Core/INotificationListener.h b/Core/INotificationListener.h index c4af7a57..ac673993 100644 --- a/Core/INotificationListener.h +++ b/Core/INotificationListener.h @@ -23,6 +23,8 @@ enum class ConsoleNotificationType EmulationStopped = 17, EventViewerDisplayFrame = 18, BeforeEmulationStop = 19, + VsDualSystemStarted = 20, + VsDualSystemStopped = 21, }; class INotificationListener diff --git a/Core/PPU.cpp b/Core/PPU.cpp index 91ec440d..37e7894f 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -893,7 +893,7 @@ void PPU::ProcessScanline() if(IsRenderingEnabled()) { ReadVram(GetNameTableAddr()); - if(_scanline == -1 && _nesModel == NesModel::NTSC && _cycle == 339 && (_frameCount & 0x01)) { + if(_scanline == -1 && _cycle == 339 && (_frameCount & 0x01) && _nesModel == NesModel::NTSC && EmulationSettings::GetPpuModel() == PpuModel::Ppu2C02) { //This behavior is NTSC-specific - PAL frames are always the same number of cycles //"With rendering enabled, each odd PPU frame is one PPU clock shorter than normal" (skip from 339 to 0, going over 340) _cycle = 340; diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index d0da67c0..f077850a 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -11,6 +11,7 @@ SoundMixer::SoundMixer(shared_ptr console) { + _audioDevice = nullptr; _console = console; _eqFrequencyGrid.reset(new orfanidis_eq::freq_grid()); _oggMixer.reset(); @@ -23,6 +24,8 @@ SoundMixer::SoundMixer(shared_ptr console) SoundMixer::~SoundMixer() { + StopRecording(); + delete[] _outputBuffer; _outputBuffer = nullptr; diff --git a/Core/StandardController.h b/Core/StandardController.h index b2004d56..7221ad99 100644 --- a/Core/StandardController.h +++ b/Core/StandardController.h @@ -102,6 +102,11 @@ public: uint8_t ReadRAM(uint16_t addr) override { + if(_port >= 2 && _console->IsDualSystem()) { + //Ignore P3/P4 controllers for VS DualSystem - those are used by the slave CPU + return 0; + } + uint8_t output = 0; if((addr == 0x4016 && (_port & 0x01) == 0) || (addr == 0x4017 && (_port & 0x01) == 1)) { diff --git a/Core/Types.h b/Core/Types.h index c95e805e..68cd6e32 100644 --- a/Core/Types.h +++ b/Core/Types.h @@ -316,6 +316,7 @@ enum class GameSystem Famicom, Dendy, VsUniSystem, + VsDualSystem, Playchoice, FDS, Unknown, diff --git a/Core/VsControlManager.cpp b/Core/VsControlManager.cpp index 642a82ac..73feb22d 100644 --- a/Core/VsControlManager.cpp +++ b/Core/VsControlManager.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "VsControlManager.h" +#include "VsSystem.h" ControllerType VsControlManager::GetControllerType(uint8_t port) { @@ -12,7 +13,13 @@ ControllerType VsControlManager::GetControllerType(uint8_t port) void VsControlManager::Reset(bool softReset) { + ControlManager::Reset(softReset); _protectionCounter = 0; + UpdateSlaveMasterBit(_console->IsMaster() ? 0x00 : 0x02); + + if(!softReset && !_console->IsMaster() && _console->GetDualConsole()) { + RegisterInputProvider(this); + } } void VsControlManager::StreamState(bool saving) @@ -89,6 +96,7 @@ uint8_t VsControlManager::ReadRAM(uint16_t addr) value = ControlManager::ReadRAM(addr); value |= ((dipSwitches & 0x01) ? 0x08 : 0x00); value |= ((dipSwitches & 0x02) ? 0x10 : 0x00); + value |= (_console->IsMaster() ? 0x00 : 0x80); break; } @@ -143,5 +151,65 @@ void VsControlManager::WriteRAM(uint16_t addr, uint8_t value) if(addr == 0x4016) { _prgChrSelectBit = (value >> 2) & 0x01; + + //Bit 2: DualSystem-only + uint8_t slaveMasterBit = (value & 0x02); + if(slaveMasterBit != _slaveMasterBit) { + UpdateSlaveMasterBit(slaveMasterBit); + } } } + +void VsControlManager::UpdateSlaveMasterBit(uint8_t slaveMasterBit) +{ + shared_ptr dualConsole = _console->GetDualConsole(); + if(dualConsole) { + VsSystem* mapper = dynamic_cast(_console->GetMapper()); + + if(_console->IsMaster()) { + mapper->UpdateMemoryAccess(slaveMasterBit); + } + + if(slaveMasterBit) { + dualConsole->GetCpu()->ClearIrqSource(IRQSource::External); + } else { + //When low, asserts /IRQ on the other CPU + dualConsole->GetCpu()->SetIrqSource(IRQSource::External); + } + } + _slaveMasterBit = slaveMasterBit; +} + +void VsControlManager::UpdateControlDevices() +{ + if(_console->GetDualConsole()) { + auto lock = _deviceLock.AcquireSafe(); + _controlDevices.clear(); + RegisterControlDevice(_systemActionManager); + + //Force 4x standard controllers + //P3 & P4 will be sent to the slave CPU - see SetInput() below. + for(int i = 0; i < 4; i++) { + shared_ptr device = CreateControllerDevice(ControllerType::StandardController, i, _console); + if(device) { + RegisterControlDevice(device); + } + } + } else { + ControlManager::UpdateControlDevices(); + } +} + +bool VsControlManager::SetInput(BaseControlDevice* device) +{ + uint8_t port = device->GetPort(); + ControlManager* masterControlManager = _console->GetDualConsole()->GetControlManager(); + if(masterControlManager && port <= 1) { + shared_ptr controlDevice = masterControlManager->GetControlDevice(port + 2); + if(controlDevice) { + ControlDeviceState state = controlDevice->GetRawState(); + device->SetRawState(state); + } + } + return true; +} \ No newline at end of file diff --git a/Core/VsControlManager.h b/Core/VsControlManager.h index 5bc54f1c..63a179ca 100644 --- a/Core/VsControlManager.h +++ b/Core/VsControlManager.h @@ -7,13 +7,16 @@ #include #include "StandardController.h" #include "MovieManager.h" +#include "IInputProvider.h" class BaseControlDevice; -class VsControlManager : public ControlManager +class VsControlManager : public ControlManager, public IInputProvider { private: - uint8_t _prgChrSelectBit; + uint8_t _prgChrSelectBit = 0; + uint8_t _slaveMasterBit = 0; + bool _refreshState = false; uint32_t _protectionCounter = 0; @@ -39,6 +42,7 @@ private: }; ControllerType GetControllerType(uint8_t port) override; + void UpdateSlaveMasterBit(uint8_t slaveMasterBit); public: using ControlManager::ControlManager; @@ -50,8 +54,13 @@ public: uint8_t GetPrgChrSelectBit(); + void UpdateControlDevices() override; + void RemapControllerButtons(); uint8_t ReadRAM(uint16_t addr) override; void WriteRAM(uint16_t addr, uint8_t value) override; + + // Inherited via IInputProvider + virtual bool SetInput(BaseControlDevice* device) override; }; \ No newline at end of file diff --git a/Core/VsSystem.h b/Core/VsSystem.h index 7059512f..326cb706 100644 --- a/Core/VsSystem.h +++ b/Core/VsSystem.h @@ -6,31 +6,44 @@ class VsSystem : public BaseMapper { private: - uint8_t _prgChrSelectBit = false; + uint8_t _prgChrSelectBit = 0; protected: virtual uint16_t GetPRGPageSize() override { return 0x2000; } virtual uint16_t GetCHRPageSize() override { return 0x2000; } + virtual uint32_t GetWorkRamSize() override { return 0x800; } virtual void InitMapper() override { + //Force VS system if mapper 99 (since we assume VsControlManager exists below) + if(_prgSize >= 0x10000) { + //Assume DualSystem if PRG ROM is 64kb or larger + _gameSystem = GameSystem::VsDualSystem; + } else { + _gameSystem = GameSystem::VsUniSystem; + } + //"Note: unlike all other mappers, an undersize mapper 99 image implies open bus instead of mirroring." //However, it doesn't look like any game actually rely on this behavior? So not implemented for now. - SelectPRGPage(0, 0); - SelectPRGPage(1, 1); - SelectPRGPage(2, 2); - SelectPRGPage(3, 3); + uint8_t prgOuter = _console->IsMaster() ? 0 : (GetPRGPageCount() / 2); + SelectPRGPage(0, 0 | prgOuter); + SelectPRGPage(1, 1 | prgOuter); + SelectPRGPage(2, 2 | prgOuter); + SelectPRGPage(3, 3 | prgOuter); - SelectCHRPage(0, 0); + uint8_t chrOuter = _console->IsMaster() ? 0 : (GetCHRPageCount() / 2); + SelectCHRPage(0, 0 | chrOuter); + } - //Force VS system if mapper 99 (since we assume VsControlManager exists below) - _gameSystem = GameSystem::VsUniSystem; + void Reset(bool softReset) override + { + BaseMapper::Reset(softReset); + UpdateMemoryAccess(0); } void StreamState(bool saving) override { BaseMapper::StreamState(saving); - Stream(_prgChrSelectBit); } @@ -40,13 +53,29 @@ protected: if(_prgChrSelectBit != controlManager->GetPrgChrSelectBit()) { _prgChrSelectBit = controlManager->GetPrgChrSelectBit(); - if(_prgSize > 0x8000) { + if(_prgSize > 0x8000 && _prgSize < 0x10000) { //"Note: In case of games with 40KiB PRG - ROM(as found in VS Gumshoe), the above bit additionally changes 8KiB PRG - ROM at $8000 - $9FFF." //"Only Vs. Gumshoe uses the 40KiB PRG variant; in the iNES encapsulation, the 8KiB banks are arranged as 0, 1, 2, 3, 0alternate, empty" SelectPRGPage(0, _prgChrSelectBit << 2); } - SelectCHRPage(0, _prgChrSelectBit); + uint8_t chrOuter = _console->IsMaster() ? 0 : (GetCHRPageCount() / 2); + SelectCHRPage(0, _prgChrSelectBit | chrOuter); + } + } + +public: + void UpdateMemoryAccess(uint8_t slaveMasterBit) + { + shared_ptr dualConsole = _console->GetDualConsole(); + if(_console->IsMaster() && dualConsole) { + VsSystem* otherMapper = dynamic_cast(dualConsole->GetMapper()); + + //Give memory access to master CPU or slave CPU, based on "slaveMasterBit" + for(int i = 0; i < 4; i++) { + SetCpuMemoryMapping(0x6000 + i * 0x800, 0x67FF + i * 0x800, _workRam, slaveMasterBit ? MemoryAccessType::ReadWrite : MemoryAccessType::NoAccess); + otherMapper->SetCpuMemoryMapping(0x6000 + i * 0x800, 0x67FF + i * 0x800, _workRam, slaveMasterBit ? MemoryAccessType::NoAccess : MemoryAccessType::ReadWrite); + } } } }; diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index 3624d6e4..df082d38 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -208,6 +208,7 @@ namespace Mesen.GUI.Forms this.mnuReportBug = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator(); this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem(); + this.ctrlRendererDualSystem = new Mesen.GUI.Controls.ctrlRenderer(); this.panelRenderer.SuspendLayout(); this.panelInfo.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit(); @@ -221,6 +222,7 @@ namespace Mesen.GUI.Forms // panelRenderer // this.panelRenderer.BackColor = System.Drawing.Color.Black; + this.panelRenderer.Controls.Add(this.ctrlRendererDualSystem); this.panelRenderer.Controls.Add(this.ctrlLoading); this.panelRenderer.Controls.Add(this.panelInfo); this.panelRenderer.Controls.Add(this.ctrlRecentGames); @@ -1662,6 +1664,16 @@ namespace Mesen.GUI.Forms this.mnuAbout.Text = "About"; this.mnuAbout.Click += new System.EventHandler(this.mnuAbout_Click); // + // ctrlRendererDualSystem + // + this.ctrlRendererDualSystem.BackColor = System.Drawing.Color.Black; + this.ctrlRendererDualSystem.Location = new System.Drawing.Point(275, 0); + this.ctrlRendererDualSystem.Margin = new System.Windows.Forms.Padding(0); + this.ctrlRendererDualSystem.Name = "ctrlRendererDualSystem"; + this.ctrlRendererDualSystem.Size = new System.Drawing.Size(150, 90); + this.ctrlRendererDualSystem.TabIndex = 8; + this.ctrlRendererDualSystem.Visible = false; + // // frmMain // this.AllowDrop = true; @@ -1868,6 +1880,7 @@ namespace Mesen.GUI.Forms private System.Windows.Forms.ToolStripSeparator toolStripMenuItem27; private System.Windows.Forms.ToolStripMenuItem mnuInstallHdPack; private System.Windows.Forms.ToolStripMenuItem mnuTextHooker; + private Controls.ctrlRenderer ctrlRendererDualSystem; } } diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 0ba4522d..d446804d 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -52,6 +52,7 @@ namespace Mesen.GUI.Forms private bool _enableResize = false; private bool _overrideWindowSize = false; private bool _shuttingDown = false; + private bool _isDualSystem = false; private frmFullscreenRenderer _frmFullscreenRenderer = null; @@ -333,23 +334,32 @@ namespace Mesen.GUI.Forms UpdateEmulationFlags(); } - private void UpdateViewerSize() + private void UpdateViewerSize(bool forceUpdate = false) { this.Resize -= frmMain_Resize; InteropEmu.ScreenSize size = InteropEmu.GetScreenSize(false); - if(!_customSize && this.WindowState != FormWindowState.Maximized) { + int width = _isDualSystem ? (size.Width * 2) : size.Width; + if(forceUpdate || (!_customSize && this.WindowState != FormWindowState.Maximized)) { Size sizeGap = this.Size - this.ClientSize; UpdateScaleMenu(size.Scale); - this.ClientSize = new Size(Math.Max(this.MinimumSize.Width - sizeGap.Width, size.Width), Math.Max(this.MinimumSize.Height - sizeGap.Height, size.Height + (this.HideMenuStrip ? 0 : menuStrip.Height))); + this.ClientSize = new Size(Math.Max(this.MinimumSize.Width - sizeGap.Width, width), Math.Max(this.MinimumSize.Height - sizeGap.Height, size.Height + (this.HideMenuStrip ? 0 : menuStrip.Height))); } ctrlRenderer.Size = new Size(size.Width, size.Height); - ctrlRenderer.Left = (panelRenderer.Width - ctrlRenderer.Width) / 2; - ctrlRenderer.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2; + if(_isDualSystem) { + ctrlRendererDualSystem.Size = new Size(size.Width, size.Height); + ctrlRendererDualSystem.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2; + ctrlRenderer.Left = (panelRenderer.Width / 2 - ctrlRenderer.Width) / 2; + ctrlRendererDualSystem.Left = ctrlRenderer.Left + ctrlRenderer.Width; + } else { + ctrlRenderer.Left = (panelRenderer.Width - ctrlRenderer.Width) / 2; + } + ctrlRenderer.Top = (panelRenderer.Height - ctrlRenderer.Height) / 2; + if(this.HideMenuStrip) { this.menuStrip.Visible = false; } @@ -630,6 +640,24 @@ namespace Mesen.GUI.Forms } })); break; + + case InteropEmu.ConsoleNotificationType.VsDualSystemStarted: + _isDualSystem = true; + this.BeginInvoke((MethodInvoker)(() => { + ctrlRendererDualSystem.Visible = _isDualSystem; + UpdateViewerSize(true); + InteropEmu.InitializeDualSystem(this.Handle, ctrlRendererDualSystem.Handle); + })); + break; + + case InteropEmu.ConsoleNotificationType.VsDualSystemStopped: + _isDualSystem = false; + InteropEmu.ReleaseDualSystemAudioVideo(); + this.BeginInvoke((MethodInvoker)(() => { + ctrlRendererDualSystem.Visible = _isDualSystem; + UpdateViewerSize(true); + })); + break; } if(e.NotificationType != InteropEmu.ConsoleNotificationType.PpuFrameDone) { diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 3a7fbba7..c83b42b9 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -22,6 +22,9 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void InitializeEmu([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string homeFolder, IntPtr windowHandle, IntPtr dxViewerHandle, [MarshalAs(UnmanagedType.I1)]bool noAudio, [MarshalAs(UnmanagedType.I1)]bool noVideo, [MarshalAs(UnmanagedType.I1)]bool noInput); [DllImport(DLLPath)] public static extern void Release(); + [DllImport(DLLPath)] public static extern void InitializeDualSystem(IntPtr windowHandle, IntPtr viewerHandle); + [DllImport(DLLPath)] public static extern void ReleaseDualSystemAudioVideo(); + [DllImport(DLLPath)] public static extern void SetDisplayLanguage(Language lang); [DllImport(DLLPath)] public static extern void SetFullscreenMode([MarshalAs(UnmanagedType.I1)]bool fullscreen, IntPtr windowHandle, UInt32 monitorWidth, UInt32 monitorHeight); @@ -883,6 +886,8 @@ namespace Mesen.GUI EmulationStopped = 17, EventViewerDisplayFrame = 18, BeforeEmulationStop = 19, + VsDualSystemStarted = 20, + VsDualSystemStopped = 21, } public enum ControllerType diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index 8e60ad56..0fac41ba 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -41,11 +41,14 @@ #include "../Linux/LinuxKeyManager.h" #endif -IRenderingDevice *_renderer = nullptr; -IAudioDevice *_soundManager = nullptr; -IKeyManager *_keyManager = nullptr; +unique_ptr _renderer; +unique_ptr _soundManager; +unique_ptr _keyManager; unique_ptr _shortcutKeyHandler; +unique_ptr _dualRenderer; +unique_ptr _dualSoundManager; + void* _windowHandle = nullptr; void* _viewerHandle = nullptr; string _returnString; @@ -109,31 +112,55 @@ namespace InteropEmu { if(!noVideo) { #ifdef _WIN32 - _renderer = new Renderer(_console, (HWND)_viewerHandle); + _renderer.reset(new Renderer(_console, (HWND)_viewerHandle)); #else - _renderer = new SdlRenderer(_console, _viewerHandle); + _renderer.reset(new SdlRenderer(_console, _viewerHandle)); #endif } if(!noAudio) { #ifdef _WIN32 - _soundManager = new SoundManager(_console, (HWND)_windowHandle); + _soundManager.reset(new SoundManager(_console, (HWND)_windowHandle)); #else - _soundManager = new SdlSoundManager(_console); + _soundManager.reset(new SdlSoundManager(_console)); #endif } if(!noInput) { #ifdef _WIN32 - _keyManager = new WindowsKeyManager(_console, (HWND)_windowHandle); + _keyManager.reset(new WindowsKeyManager(_console, (HWND)_windowHandle)); #else - _keyManager = new LinuxKeyManager(_console); + _keyManager.reset(new LinuxKeyManager(_console)); #endif - KeyManager::RegisterKeyManager(_keyManager); + KeyManager::RegisterKeyManager(_keyManager.get()); } } } + + DllExport void __stdcall InitializeDualSystem(void *windowHandle, void *viewerHandle) + { + shared_ptr slaveConsole = _console->GetDualConsole(); + if(slaveConsole){ + _console->Pause(); + #ifdef _WIN32 + _dualRenderer.reset(new Renderer(slaveConsole, (HWND)viewerHandle)); + _dualSoundManager.reset(new SoundManager(slaveConsole, (HWND)windowHandle)); + #else + _dualRenderer.reset(new SdlRenderer(slaveConsole, viewerHandle)); + _dualSoundManager.reset(new SdlSoundManager(slaveConsole)); + #endif + _console->Resume(); + } + } + + DllExport void __stdcall ReleaseDualSystemAudioVideo() + { + _console->Pause(); + _dualRenderer.reset(); + _dualSoundManager.reset(); + _console->Resume(); + } DllExport void __stdcall SetFullscreenMode(bool fullscreen, void *windowHandle, uint32_t monitorWidth, uint32_t monitorHeight) { @@ -330,6 +357,7 @@ namespace InteropEmu { DllExport void __stdcall Release() { + ReleaseDualSystemAudioVideo(); _shortcutKeyHandler.reset(); GameServer::StopServer(); @@ -337,18 +365,9 @@ namespace InteropEmu { _console->Stop(); - if(_renderer) { - delete _renderer; - _renderer = nullptr; - } - if(_soundManager) { - delete _soundManager; - _soundManager = nullptr; - } - if(_keyManager) { - delete _keyManager; - _keyManager = nullptr; - } + _renderer.reset(); + _soundManager.reset(); + _keyManager.reset(); _console->Release(true); _console.reset(); diff --git a/InteropDLL/DebugWrapper.cpp b/InteropDLL/DebugWrapper.cpp index 791aeb3f..1e3ca453 100644 --- a/InteropDLL/DebugWrapper.cpp +++ b/InteropDLL/DebugWrapper.cpp @@ -21,7 +21,7 @@ extern "C" //Debugger wrapper DllExport void __stdcall DebugInitialize() { - _console->GetDebugger(); + GetDebugger(); } DllExport void __stdcall DebugRelease() diff --git a/Linux/SdlRenderer.cpp b/Linux/SdlRenderer.cpp index 02b09f00..0cbbab94 100755 --- a/Linux/SdlRenderer.cpp +++ b/Linux/SdlRenderer.cpp @@ -5,16 +5,21 @@ #include "../Core/VideoDecoder.h" #include "../Core/EmulationSettings.h" +SimpleLock SdlRenderer::_reinitLock; +SimpleLock SdlRenderer::_frameLock; + SdlRenderer::SdlRenderer(shared_ptr console, void* windowHandle) : BaseRenderer(console), _windowHandle(windowHandle) { _frameBuffer = nullptr; SetScreenSize(256,240); - MessageManager::RegisterMessageManager(this); } SdlRenderer::~SdlRenderer() { - _console->GetVideoRenderer()->UnregisterRenderingDevice(this); + shared_ptr videoRenderer = _console->GetVideoRenderer(); + if(videoRenderer) { + videoRenderer->UnregisterRenderingDevice(this); + } Cleanup(); } diff --git a/Linux/SdlRenderer.h b/Linux/SdlRenderer.h index 0f909ed9..4e65b855 100755 --- a/Linux/SdlRenderer.h +++ b/Linux/SdlRenderer.h @@ -35,8 +35,8 @@ private: VideoResizeFilter _resizeFilter = VideoResizeFilter::NearestNeighbor; - SimpleLock _frameLock; - SimpleLock _reinitLock; + static SimpleLock _frameLock; + static SimpleLock _reinitLock; uint32_t* _frameBuffer; const uint32_t _bytesPerPixel = 4; diff --git a/Linux/SdlSoundManager.cpp b/Linux/SdlSoundManager.cpp index 2289c234..47900049 100755 --- a/Linux/SdlSoundManager.cpp +++ b/Linux/SdlSoundManager.cpp @@ -42,6 +42,7 @@ void SdlSoundManager::Release() bool SdlSoundManager::InitializeAudio(uint32_t sampleRate, bool isStereo) { if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { + MessageManager::Log("[Audio] Failed to initialize audio subsystem"); return false; } diff --git a/Windows/Renderer.cpp b/Windows/Renderer.cpp index 916fc571..fbc1d53d 100644 --- a/Windows/Renderer.cpp +++ b/Windows/Renderer.cpp @@ -18,13 +18,14 @@ Renderer::Renderer(shared_ptr console, HWND hWnd) : BaseRenderer(consol _hWnd = hWnd; SetScreenSize(256, 240); - - MessageManager::RegisterMessageManager(this); } Renderer::~Renderer() { - _console->GetVideoRenderer()->UnregisterRenderingDevice(this); + shared_ptr videoRenderer = _console->GetVideoRenderer(); + if(videoRenderer) { + videoRenderer->UnregisterRenderingDevice(this); + } CleanupDevice(); }