Added support for VS DualSystem (WIP - dual video/audio, input, save states, movies & netplay working)
This commit is contained in:
parent
1489868a3f
commit
47fbe93b62
23 changed files with 409 additions and 85 deletions
|
@ -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 {
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
BaseRenderer::BaseRenderer(shared_ptr<Console> 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)
|
||||
|
|
148
Core/Console.cpp
148
Core/Console.cpp
|
@ -45,36 +45,55 @@
|
|||
#include "DebugHud.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
Console::Console()
|
||||
Console::Console(shared_ptr<Console> 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();
|
||||
|
@ -86,6 +105,12 @@ void Console::Release(bool forShutdown)
|
|||
|
||||
_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,6 +377,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
|
|||
|
||||
FolderUtilities::AddKnownGameFolder(romFile.GetFolderPath());
|
||||
|
||||
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));
|
||||
|
@ -344,6 +385,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
|
|||
MessageManager::DisplayMessage("ClockRate", std::to_string(EmulationSettings::GetOverclockRate()) + "%");
|
||||
}
|
||||
EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
}
|
||||
Resume();
|
||||
return true;
|
||||
}
|
||||
|
@ -392,6 +434,24 @@ shared_ptr<NotificationManager> Console::GetNotificationManager()
|
|||
return _notificationManager;
|
||||
}
|
||||
|
||||
bool Console::IsDualSystem()
|
||||
{
|
||||
return _slave != nullptr || _master != nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<Console> 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();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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 = _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()
|
||||
{
|
||||
if(_master) {
|
||||
//For slave CPU, return the master's state
|
||||
return _master->IsRunning();
|
||||
} else {
|
||||
return !_stopLock.IsFree() && _running;
|
||||
}
|
||||
}
|
||||
|
||||
bool Console::IsPaused()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -824,6 +939,11 @@ void Console::LoadState(istream &loadStream, uint32_t stateVersion)
|
|||
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 = _debugger;
|
||||
if(debugger) {
|
||||
debugger->ResetCounters();
|
||||
|
|
|
@ -51,6 +51,10 @@ private:
|
|||
shared_ptr<ControlManager> _controlManager;
|
||||
shared_ptr<MemoryManager> _memoryManager;
|
||||
|
||||
//Used by VS-DualSystem
|
||||
shared_ptr<Console> _master;
|
||||
shared_ptr<Console> _slave;
|
||||
|
||||
shared_ptr<SystemActionManager> _systemActionManager;
|
||||
|
||||
shared_ptr<VideoDecoder> _videoDecoder;
|
||||
|
@ -87,7 +91,7 @@ private:
|
|||
void DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData);
|
||||
|
||||
public:
|
||||
Console();
|
||||
Console(shared_ptr<Console> master = nullptr);
|
||||
~Console();
|
||||
|
||||
void Init();
|
||||
|
@ -100,6 +104,10 @@ public:
|
|||
shared_ptr<SoundMixer> GetSoundMixer();
|
||||
shared_ptr<NotificationManager> GetNotificationManager();
|
||||
|
||||
bool IsDualSystem();
|
||||
shared_ptr<Console> GetDualConsole();
|
||||
bool IsMaster();
|
||||
|
||||
void ProcessCpuClock();
|
||||
CPU* GetCpu();
|
||||
PPU* GetPpu();
|
||||
|
|
|
@ -20,25 +20,25 @@ class ControlManager : public Snapshotable, public IMemoryHandler
|
|||
private:
|
||||
vector<IInputRecorder*> _inputRecorders;
|
||||
vector<IInputProvider*> _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<shared_ptr<BaseControlDevice>> _controlDevices;
|
||||
|
||||
shared_ptr<BaseControlDevice> _systemActionManager;
|
||||
shared_ptr<BaseControlDevice> _mapperControlDevice;
|
||||
|
||||
uint32_t _lagCounter = 0;
|
||||
bool _isLagging = false;
|
||||
|
||||
uint8_t GetOpenBusMask(uint8_t port);
|
||||
void RegisterControlDevice(shared_ptr<BaseControlDevice> controlDevice);
|
||||
|
||||
protected:
|
||||
shared_ptr<Console> _console;
|
||||
SimpleLock _deviceLock;
|
||||
vector<shared_ptr<BaseControlDevice>> _controlDevices;
|
||||
shared_ptr<BaseControlDevice> _systemActionManager;
|
||||
|
||||
void RegisterControlDevice(shared_ptr<BaseControlDevice> controlDevice);
|
||||
|
||||
virtual void StreamState(bool saving) override;
|
||||
virtual ControllerType GetControllerType(uint8_t port);
|
||||
|
@ -47,7 +47,7 @@ public:
|
|||
ControlManager(shared_ptr<Console> console, shared_ptr<BaseControlDevice> systemActionManager, shared_ptr<BaseControlDevice> mapperControlDevice);
|
||||
virtual ~ControlManager();
|
||||
|
||||
void UpdateControlDevices();
|
||||
virtual void UpdateControlDevices();
|
||||
void UpdateInputState();
|
||||
|
||||
uint32_t GetLagCounter();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -23,6 +23,8 @@ enum class ConsoleNotificationType
|
|||
EmulationStopped = 17,
|
||||
EventViewerDisplayFrame = 18,
|
||||
BeforeEmulationStop = 19,
|
||||
VsDualSystemStarted = 20,
|
||||
VsDualSystemStopped = 21,
|
||||
};
|
||||
|
||||
class INotificationListener
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
SoundMixer::SoundMixer(shared_ptr<Console> console)
|
||||
{
|
||||
_audioDevice = nullptr;
|
||||
_console = console;
|
||||
_eqFrequencyGrid.reset(new orfanidis_eq::freq_grid());
|
||||
_oggMixer.reset();
|
||||
|
@ -23,6 +24,8 @@ SoundMixer::SoundMixer(shared_ptr<Console> console)
|
|||
|
||||
SoundMixer::~SoundMixer()
|
||||
{
|
||||
StopRecording();
|
||||
|
||||
delete[] _outputBuffer;
|
||||
_outputBuffer = nullptr;
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -316,6 +316,7 @@ enum class GameSystem
|
|||
Famicom,
|
||||
Dendy,
|
||||
VsUniSystem,
|
||||
VsDualSystem,
|
||||
Playchoice,
|
||||
FDS,
|
||||
Unknown,
|
||||
|
|
|
@ -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<Console> dualConsole = _console->GetDualConsole();
|
||||
if(dualConsole) {
|
||||
VsSystem* mapper = dynamic_cast<VsSystem*>(_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<BaseControlDevice> 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<BaseControlDevice> controlDevice = masterControlManager->GetControlDevice(port + 2);
|
||||
if(controlDevice) {
|
||||
ControlDeviceState state = controlDevice->GetRawState();
|
||||
device->SetRawState(state);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -7,13 +7,16 @@
|
|||
#include <assert.h>
|
||||
#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;
|
||||
};
|
|
@ -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<Console> dualConsole = _console->GetDualConsole();
|
||||
if(_console->IsMaster() && dualConsole) {
|
||||
VsSystem* otherMapper = dynamic_cast<VsSystem*>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
13
GUI.NET/Forms/frmMain.Designer.cs
generated
13
GUI.NET/Forms/frmMain.Designer.cs
generated
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,21 +334,30 @@ 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);
|
||||
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) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,11 +41,14 @@
|
|||
#include "../Linux/LinuxKeyManager.h"
|
||||
#endif
|
||||
|
||||
IRenderingDevice *_renderer = nullptr;
|
||||
IAudioDevice *_soundManager = nullptr;
|
||||
IKeyManager *_keyManager = nullptr;
|
||||
unique_ptr<IRenderingDevice> _renderer;
|
||||
unique_ptr<IAudioDevice> _soundManager;
|
||||
unique_ptr<IKeyManager> _keyManager;
|
||||
unique_ptr<ShortcutKeyHandler> _shortcutKeyHandler;
|
||||
|
||||
unique_ptr<IRenderingDevice> _dualRenderer;
|
||||
unique_ptr<IAudioDevice> _dualSoundManager;
|
||||
|
||||
void* _windowHandle = nullptr;
|
||||
void* _viewerHandle = nullptr;
|
||||
string _returnString;
|
||||
|
@ -109,32 +112,56 @@ 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<Console> 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)
|
||||
{
|
||||
if(_renderer) {
|
||||
|
@ -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();
|
||||
|
|
|
@ -21,7 +21,7 @@ extern "C"
|
|||
//Debugger wrapper
|
||||
DllExport void __stdcall DebugInitialize()
|
||||
{
|
||||
_console->GetDebugger();
|
||||
GetDebugger();
|
||||
}
|
||||
|
||||
DllExport void __stdcall DebugRelease()
|
||||
|
|
|
@ -5,16 +5,21 @@
|
|||
#include "../Core/VideoDecoder.h"
|
||||
#include "../Core/EmulationSettings.h"
|
||||
|
||||
SimpleLock SdlRenderer::_reinitLock;
|
||||
SimpleLock SdlRenderer::_frameLock;
|
||||
|
||||
SdlRenderer::SdlRenderer(shared_ptr<Console> console, void* windowHandle) : BaseRenderer(console), _windowHandle(windowHandle)
|
||||
{
|
||||
_frameBuffer = nullptr;
|
||||
SetScreenSize(256,240);
|
||||
MessageManager::RegisterMessageManager(this);
|
||||
}
|
||||
|
||||
SdlRenderer::~SdlRenderer()
|
||||
{
|
||||
_console->GetVideoRenderer()->UnregisterRenderingDevice(this);
|
||||
shared_ptr<VideoRenderer> videoRenderer = _console->GetVideoRenderer();
|
||||
if(videoRenderer) {
|
||||
videoRenderer->UnregisterRenderingDevice(this);
|
||||
}
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,13 +18,14 @@ Renderer::Renderer(shared_ptr<Console> console, HWND hWnd) : BaseRenderer(consol
|
|||
_hWnd = hWnd;
|
||||
|
||||
SetScreenSize(256, 240);
|
||||
|
||||
MessageManager::RegisterMessageManager(this);
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
_console->GetVideoRenderer()->UnregisterRenderingDevice(this);
|
||||
shared_ptr<VideoRenderer> videoRenderer = _console->GetVideoRenderer();
|
||||
if(videoRenderer) {
|
||||
videoRenderer->UnregisterRenderingDevice(this);
|
||||
}
|
||||
CleanupDevice();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue