diff --git a/.gitignore b/.gitignore index c732649f..ad8f8b82 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.suo *.user *.sln.docstates +.vs/* # Build results @@ -165,4 +166,4 @@ $RECYCLE.BIN/ *.VC.opendb *.VC.db *.VC.db-wal -*.VC.db-shm +*.VC.db-shm \ No newline at end of file diff --git a/Core/ArkanoidController.cpp b/Core/ArkanoidController.cpp deleted file mode 100644 index 32ca210c..00000000 --- a/Core/ArkanoidController.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "stdafx.h" -#include "ArkanoidController.h" -#include "ControlManager.h" -#include "PPU.h" -#include "GameServerConnection.h" -#include "IKeyManager.h" - -void ArkanoidController::StreamState(bool saving) -{ - BaseControlDevice::StreamState(saving); - Stream(_stateBuffer, _buttonPressed, _xPosition); -} - -uint8_t ArkanoidController::GetPortOutput() -{ - return GetControlState(); -} - -bool ArkanoidController::IsButtonPressed() -{ - if(!EmulationSettings::CheckFlag(EmulationFlags::InBackground) || EmulationSettings::CheckFlag(EmulationFlags::AllowBackgroundInput)) { - if(ControlManager::IsMouseButtonPressed(MouseButton::LeftButton)) { - return true; - } - } - - return false; -} - -uint32_t ArkanoidController::GetNetPlayState() -{ - //Used by netplay - uint32_t state = ControlManager::GetMousePosition().X; - - if(IsButtonPressed()) { - state |= 0x40000000; - } - - return state; -} - -uint8_t ArkanoidController::ProcessNetPlayState(uint32_t netplayState) -{ - _xPosition = netplayState & 0xFF; - _buttonPressed = ((netplayState >> 30) & 0x01) == 0x01; - - return RefreshState(); -} - -void ArkanoidController::RefreshStateBuffer() -{ - const uint8_t validRange = 0xF4 - 0x54; - if(!GameServerConnection::GetNetPlayDevice(_port)) { - _xPosition = ControlManager::GetMousePosition().X; - } - - _xPosition -= 48; - if(_xPosition < 0) { - _xPosition = 0; - } else if(_xPosition >= 160) { - _xPosition = 159; - } - - _stateBuffer = 0x54 + (uint32_t)(((double)_xPosition / 159) * validRange); -} - -uint8_t ArkanoidController::RefreshState() -{ - if(!GameServerConnection::GetNetPlayDevice(_port)) { - _buttonPressed = IsButtonPressed(); - } - - uint8_t output = ((~_stateBuffer) >> 3) & 0x10; - _stateBuffer <<= 1; - - if(_buttonPressed) { - output |= 0x08; - } - - return output; -} - -uint8_t ArkanoidController::GetExpansionPortOutput(uint8_t port) -{ - uint8_t output = 0; - if(port == 0) { - //Fire button is on port 1 - if(!GameServerConnection::GetNetPlayDevice(_port)) { - _buttonPressed = IsButtonPressed(); - } - - if(_buttonPressed) { - output |= 0x02; - } - } else if(port == 1) { - //Serial data is on port 2 - uint8_t arkanoidBits = GetPortOutput(); - output |= (arkanoidBits >> 3) & 0x02; - } - return output; -} \ No newline at end of file diff --git a/Core/ArkanoidController.h b/Core/ArkanoidController.h index 4469bd25..8b3ee708 100644 --- a/Core/ArkanoidController.h +++ b/Core/ArkanoidController.h @@ -1,28 +1,87 @@ #pragma once #include "stdafx.h" #include "BaseControlDevice.h" +#include "ControlManager.h" +#include "PPU.h" +#include "IKeyManager.h" +#include "KeyManager.h" class ArkanoidController : public BaseControlDevice { private: + uint32_t _currentValue = (0xF4 - 0x54) / 2; uint32_t _stateBuffer = 0; - bool _buttonPressed = false; - int32_t _xPosition = 0; - - bool IsButtonPressed(); + enum Buttons { Fire }; protected: - uint8_t RefreshState() override; - virtual void StreamState(bool saving) override; + bool HasCoordinates() override { return true; } + + string GetKeyNames() override + { + return "F"; + } + + void InternalSetStateFromInput() override + { + if(EmulationSettings::InputEnabled()) { + SetPressedState(Buttons::Fire, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + SetMovement(KeyManager::GetMouseMovement()); + } + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_stateBuffer, _currentValue); + } + + void RefreshStateBuffer() override + { + MouseMovement mov = GetMovement(); + + _currentValue += mov.dx; + if(_currentValue < 0x54) { + _currentValue = 0x54; + } else if(_currentValue > 0xF4) { + _currentValue = 0xF4; + } + + _stateBuffer = _currentValue; + } public: - using BaseControlDevice::BaseControlDevice; + ArkanoidController(uint8_t port) : BaseControlDevice(port) + { + } - uint8_t GetPortOutput() override; - void RefreshStateBuffer() override; + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; + if(IsExpansionDevice()) { + if(addr == 0x4016) { + //Fire button is on port 1 + if(IsPressed(ArkanoidController::Buttons::Fire)) { + output |= 0x02; + } + } else if(addr == 0x4017) { + //Serial data is on port 2 + output |= ((~_stateBuffer) >> 6) & 0x02; + _stateBuffer <<= 1; + } + } else if(IsCurrentPort(addr)) { + output = ((~_stateBuffer) >> 3) & 0x10; + _stateBuffer <<= 1; - virtual uint32_t GetNetPlayState() override; - uint8_t ProcessNetPlayState(uint32_t netplayState) override; + if(IsPressed(ArkanoidController::Buttons::Fire)) { + output |= 0x08; + } + } - uint8_t GetExpansionPortOutput(uint8_t port); + return output; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } }; \ No newline at end of file diff --git a/Core/AsciiTurboFile.h b/Core/AsciiTurboFile.h new file mode 100644 index 00000000..693996c3 --- /dev/null +++ b/Core/AsciiTurboFile.h @@ -0,0 +1,65 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/FolderUtilities.h" +#include "Console.h" +#include "BaseControlDevice.h" +#include "IBattery.h" +#include "BatteryManager.h" + +class AsciiTurboFile : public BaseControlDevice, public IBattery +{ +private: + static const int FileSize = 0x2000; + static const int BitCount = FileSize * 8; + uint8_t _lastWrite = 0; + uint16_t _position = 0; + uint8_t _data[AsciiTurboFile::FileSize]; + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + ArrayInfo data{ _data, AsciiTurboFile::FileSize }; + Stream(_position, _lastWrite, data); + } + +public: + AsciiTurboFile() : BaseControlDevice(BaseControlDevice::ExpDevicePort) + { + BatteryManager::LoadBattery(".tf", _data, AsciiTurboFile::FileSize); + } + + ~AsciiTurboFile() + { + SaveBattery(); + } + + void SaveBattery() + { + BatteryManager::SaveBattery(".tf", _data, AsciiTurboFile::FileSize); + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + return ((_data[_position / 8] >> (_position % 8)) & 0x01) << 2; + } + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + if(!(value & 0x02)) { + _position = 0; + } + + if(!(value & 0x04) && (_lastWrite & 0x04)) { + //Clock, perform write, increase position + _data[_position / 8] &= ~(1 << (_position % 8)); + _data[_position / 8] |= (value & 0x01) << (_position % 8); + _position = (_position + 1) & (AsciiTurboFile::BitCount - 1); + } + + _lastWrite = value; + } +}; \ No newline at end of file diff --git a/Core/BandaiFcg.h b/Core/BandaiFcg.h index da8546e6..e238c484 100644 --- a/Core/BandaiFcg.h +++ b/Core/BandaiFcg.h @@ -1,6 +1,8 @@ #pragma once #include "BaseMapper.h" #include "CPU.h" +#include "MemoryManager.h" +#include "DatachBarcodeReader.h" class BandaiFcg : public BaseMapper { @@ -11,6 +13,7 @@ private: uint8_t _prgPage; uint8_t _prgBankSelect; uint8_t _chrRegs[8]; + shared_ptr _barcodeReader; protected: uint16_t GetPRGPageSize() override { return 0x4000; } @@ -18,6 +21,7 @@ protected: uint16_t RegisterStartAddress() override { return 0x6000; } uint16_t RegisterEndAddress() override { return 0xFFFF; } bool AllowRegisterRead() override { return true; } + ConsoleFeatures GetAvailableFeatures() override { return _mapperID == 157 ? ConsoleFeatures::BarcodeReader : ConsoleFeatures::None; } void InitMapper() override { @@ -27,14 +31,22 @@ protected: _irqReload = 0; _prgPage = 0; _prgBankSelect = 0; + + if(_mapperID == 157) { + //"Mapper 157 is used for Datach Joint ROM System boards" + _barcodeReader.reset(new DatachBarcodeReader()); + _mapperControlDevice = _barcodeReader; + } - //Only allow reads from 0x6000 to 0xFFFF + //Only allow reads from 0x6000 to 0x7FFF RemoveRegisterRange(0x8000, 0xFFFF, MemoryOperation::Read); if(_mapperID != 16 || GetPRGPageCount() >= 0x20) { //"For iNES Mapper 153 (with SRAM), the writeable ports must only be mirrored across $8000-$FFFF." //"Mappers 157 and 159 do not need to support the FCG-1 and -2 and so should only mirror the ports across $8000-$FFFF." - RemoveRegisterRange(0x6000, 0x7FFF, MemoryOperation::Any); + + //TODO: Check if this is needed + //RemoveRegisterRange(0x6000, 0x7FFF, MemoryOperation::Any); } //Last bank @@ -61,11 +73,11 @@ protected: _irqCounter--; } } - + uint8_t ReadRegister(uint16_t addr) override { //Pretend EEPROM data is always 0 - return 0; + return _barcodeReader->GetOutput() | MemoryManager::GetOpenBus(0xE7); } void WriteRegister(uint16_t addr, uint8_t value) override diff --git a/Core/BandaiHyperShot.h b/Core/BandaiHyperShot.h new file mode 100644 index 00000000..6f4178a5 --- /dev/null +++ b/Core/BandaiHyperShot.h @@ -0,0 +1,76 @@ +#pragma once +#include "stdafx.h" +#include "StandardController.h" +#include "Zapper.h" +#include "IKeyManager.h" +#include "KeyManager.h" + +class BandaiHyperShot : public StandardController +{ +private: + uint32_t _stateBuffer = 0; + +protected: + enum ZapperButtons { Fire = 9 }; + + bool HasCoordinates() override { return true; } + + string GetKeyNames() override + { + return StandardController::GetKeyNames() + "F"; + } + + void InternalSetStateFromInput() override + { + StandardController::InternalSetStateFromInput(); + + if(EmulationSettings::InputEnabled()) { + SetPressedState(ZapperButtons::Fire, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + + MousePosition pos = KeyManager::GetMousePosition(); + if(KeyManager::IsMouseButtonPressed(MouseButton::RightButton)) { + pos.X = -1; + pos.Y = -1; + } + SetCoordinates(pos); + } + } + + bool IsLightFound() + { + return Zapper::StaticIsLightFound(GetCoordinates()); + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_stateBuffer); + } + +public: + BandaiHyperShot(KeyMappingSet keyMappings) : StandardController(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + void RefreshStateBuffer() override + { + _stateBuffer = (uint32_t)ToByte(); + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4016) { + uint8_t output = (_stateBuffer & 0x01) << 1; + _stateBuffer >>= 1; + StrobeProcessRead(); + return output; + } else { + return (IsLightFound() ? 0 : 0x08) | (IsPressed(BandaiHyperShot::ZapperButtons::Fire) ? 0x10 : 0x00); + } + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } +}; \ No newline at end of file diff --git a/Core/BandaiKaraoke.h b/Core/BandaiKaraoke.h index 3be9394e..cb4be937 100644 --- a/Core/BandaiKaraoke.h +++ b/Core/BandaiKaraoke.h @@ -3,13 +3,15 @@ #include "BaseMapper.h" #include "ControlManager.h" #include "StandardController.h" +#include "BandaiMicrophone.h" class BandaiKaraoke : public BaseMapper { protected: - virtual uint16_t GetPRGPageSize() override { return 0x4000; } - virtual uint16_t GetCHRPageSize() override { return 0x2000; } - virtual bool AllowRegisterRead() override { return true; } + uint16_t GetPRGPageSize() override { return 0x4000; } + uint16_t GetCHRPageSize() override { return 0x2000; } + bool AllowRegisterRead() override { return true; } + bool HasBusConflicts() override { return true; } void InitMapper() override { @@ -19,12 +21,13 @@ protected: SelectPRGPage(0, 0); SelectPRGPage(1, 0x07); SelectCHRPage(0, 0); + + _mapperControlDevice.reset(new BandaiMicrophone(EmulationSettings::GetControllerKeys(0))); } uint8_t ReadRegister(uint16_t addr) override { - //Microphone not implemented - always return A/B buttons as not pressed - return 0x03; + return _mapperControlDevice->ReadRAM(addr) | MemoryManager::GetOpenBus(0xF8); } void WriteRegister(uint16_t addr, uint8_t value) override diff --git a/Core/BandaiMicrophone.h b/Core/BandaiMicrophone.h new file mode 100644 index 00000000..798e0a37 --- /dev/null +++ b/Core/BandaiMicrophone.h @@ -0,0 +1,45 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class BandaiMicrophone : public BaseControlDevice +{ +protected: + enum Buttons { A, B, Microphone }; + + string GetKeyNames() override + { + return "ABM"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + //TODO: Add proper key mappings + SetPressedState(Buttons::A, keyMapping.A); + SetPressedState(Buttons::B, keyMapping.B); + SetPressedState(Buttons::Microphone, keyMapping.Microphone); + } + } + +public: + BandaiMicrophone(KeyMappingSet keyMappings) : BaseControlDevice(BaseControlDevice::MapperInputPort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr >= 0x6000 && addr <= 0x7FFF) { + return + (IsPressed(Buttons::A) ? 0 : 0x01) | + (IsPressed(Buttons::B) ? 0 : 0x02) | + (IsPressed(Buttons::Microphone) ? 0x04 : 0); + } else { + return 0; + } + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + } +}; \ No newline at end of file diff --git a/Core/BarcodeBattlerReader.h b/Core/BarcodeBattlerReader.h new file mode 100644 index 00000000..cf9d0e46 --- /dev/null +++ b/Core/BarcodeBattlerReader.h @@ -0,0 +1,102 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" +#include "IBarcodeReader.h" +#include "CPU.h" + +class BarcodeBattlerReader : public BaseControlDevice, public IBarcodeReader +{ +private: + static const int StreamSize = 200; + uint64_t _newBarcode = 0; + uint32_t _newBarcodeDigitCount = 0; + + uint8_t _barcodeStream[BarcodeBattlerReader::StreamSize]; + int32_t _insertCycle = 0; + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + + ArrayInfo bitStream{ _barcodeStream, BarcodeBattlerReader::StreamSize }; + Stream(_newBarcode, _newBarcodeDigitCount, _insertCycle, bitStream); + } + + bool IsRawString() override + { + return true; + } + + void InitBarcodeStream() + { + string barcodeText(_state.State.begin(), _state.State.end()); + + //Signature at the end, needed for code to be recognized + barcodeText += "EPOCH\xD\xA"; + //Pad to 20 characters with spaces + barcodeText.insert(0, 20 - barcodeText.size(), ' '); + + int pos = 0; + vector bits; + for(int i = 0; i < 20; i++) { + _barcodeStream[pos++] = 1; + for(int j = 0; j < 8; j++) { + _barcodeStream[pos++] = ~((barcodeText[i] >> j) & 0x01); + } + _barcodeStream[pos++] = 0; + } + } + +public: + BarcodeBattlerReader() : BaseControlDevice(BaseControlDevice::ExpDevicePort) + { + } + + void InternalSetStateFromInput() override + { + ClearState(); + + if(_newBarcodeDigitCount > 0) { + string barcodeText = std::to_string(_newBarcode); + //Pad 8 or 13 character barcode with 0s at start + barcodeText.insert(0, _newBarcodeDigitCount - barcodeText.size(), '0'); + SetTextState(barcodeText); + + _newBarcode = 0; + _newBarcodeDigitCount = 0; + } + } + + void OnAfterSetState() override + { + if(GetRawState().State.size() > 0) { + InitBarcodeStream(); + _insertCycle = CPU::GetCycleCount(); + } + } + + void InputBarcode(uint64_t barcode, uint32_t digitCount) override + { + _newBarcode = barcode; + _newBarcodeDigitCount = digitCount; + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + int32_t elapsedCycles = CPU::GetCycleCount() - _insertCycle; + constexpr uint32_t cyclesPerBit = CPU::ClockRateNtsc / 1200; + + uint32_t streamPosition = elapsedCycles / cyclesPerBit; + if(streamPosition < BarcodeBattlerReader::StreamSize) { + return _barcodeStream[streamPosition] << 2; + } + } + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + } +}; \ No newline at end of file diff --git a/Core/BaseControlDevice.cpp b/Core/BaseControlDevice.cpp index aea42ca5..b5a380a8 100644 --- a/Core/BaseControlDevice.cpp +++ b/Core/BaseControlDevice.cpp @@ -1,91 +1,261 @@ #include "stdafx.h" #include "BaseControlDevice.h" -#include "ControlManager.h" -#include "MovieManager.h" -#include "EmulationSettings.h" -#include "GameClient.h" -#include "GameServerConnection.h" -#include "AutomaticRomTest.h" -#include "RewindManager.h" -#include "Debugger.h" +#include "KeyManager.h" +#include "../Utilities/StringUtilities.h" -BaseControlDevice::BaseControlDevice(uint8_t port) +BaseControlDevice::BaseControlDevice(uint8_t port, KeyMappingSet keyMappingSet) { _port = port; - _famiconDevice = EmulationSettings::GetConsoleType() == ConsoleType::Famicom; - if(EmulationSettings::GetControllerType(port) == ControllerType::StandardController) { - AddKeyMappings(EmulationSettings::GetControllerKeys(port)); - } + _strobe = false; + _keyMappings = keyMappingSet.GetKeyMappingArray(); } BaseControlDevice::~BaseControlDevice() { } -void BaseControlDevice::StreamState(bool saving) -{ - Stream(_currentState); -} - uint8_t BaseControlDevice::GetPort() { return _port; } -void BaseControlDevice::AddKeyMappings(KeyMappingSet keyMappings) +void BaseControlDevice::SetStateFromInput() { - if(keyMappings.Mapping1.HasKeySet()) { - _keyMappings.push_back(keyMappings.Mapping1); - } - if(keyMappings.Mapping2.HasKeySet()) { - _keyMappings.push_back(keyMappings.Mapping2); - } - if(keyMappings.Mapping3.HasKeySet()) { - _keyMappings.push_back(keyMappings.Mapping3); - } - if(keyMappings.Mapping4.HasKeySet()) { - _keyMappings.push_back(keyMappings.Mapping4); - } - _turboSpeed = keyMappings.TurboSpeed; + ClearState(); + InternalSetStateFromInput(); } -void BaseControlDevice::RefreshStateBuffer() +void BaseControlDevice::InternalSetStateFromInput() { - //Do nothing by default - used by standard controllers and some others } -uint8_t BaseControlDevice::ProcessNetPlayState(uint32_t netplayState) +void BaseControlDevice::StreamState(bool saving) { - return netplayState; + ArrayInfo state{ _state.State.data(), (uint32_t)_state.State.size() }; + Stream(_strobe, state); } -uint8_t BaseControlDevice::GetControlState() +bool BaseControlDevice::IsCurrentPort(uint16_t addr) { - GameServerConnection* netPlayDevice = GameServerConnection::GetNetPlayDevice(_port); - if(RewindManager::IsRewinding()) { - _currentState = RewindManager::GetInput(_port); - } else if(MovieManager::Playing()) { - _currentState = MovieManager::GetState(_port); - } else if(GameClient::Connected()) { - _currentState = GameClient::GetControllerState(_port); - } else if(AutomaticRomTest::Running()) { - _currentState = AutomaticRomTest::GetControllerState(_port); - } else if(netPlayDevice) { - _currentState = ProcessNetPlayState(netPlayDevice->GetState()); - } else if(Debugger::HasInputOverride(_port)) { - _currentState = ProcessNetPlayState(Debugger::GetInputOverride(_port)); + return _port == (addr - 0x4016); +} + +bool BaseControlDevice::IsExpansionDevice() +{ + return _port == BaseControlDevice::ExpDevicePort; +} + +void BaseControlDevice::StrobeProcessRead() +{ + if(_strobe) { + RefreshStateBuffer(); + } +} + +void BaseControlDevice::StrobeProcessWrite(uint8_t value) +{ + bool prevStrobe = _strobe; + _strobe = (value & 0x01) == 0x01; + + if(prevStrobe && !_strobe) { + RefreshStateBuffer(); + } +} + +void BaseControlDevice::ClearState() +{ + _state = ControlDeviceState(); +} + +ControlDeviceState BaseControlDevice::GetRawState() +{ + return _state; +} + +void BaseControlDevice::SetRawState(ControlDeviceState state) +{ + _state = state; +} + +void BaseControlDevice::SetTextState(string textState) +{ + ClearState(); + + if(IsRawString()) { + _state.State.insert(_state.State.end(), textState.begin(), textState.end()); } else { - _currentState = RefreshState(); + if(HasCoordinates()) { + vector data = StringUtilities::Split(textState, ' '); + if(data.size() >= 3) { + MousePosition pos; + try { + pos.X = (int16_t)std::stol(data[0]); + pos.Y = (int16_t)std::stol(data[1]); + } catch(std::exception ex) { + pos.X = -1; + pos.Y = -1; + } + SetCoordinates(pos); + textState = data[2]; + } + } + + int i = 0; + for(char c : textState) { + if(c != '.') { + SetBit(i); + } + i++; + } } +} - if(MovieManager::Recording()) { - MovieManager::RecordState(_port, _currentState); +string BaseControlDevice::GetTextState() +{ + if(IsRawString()) { + return string((char*)_state.State.data(), _state.State.size()); + } else { + string keyNames = GetKeyNames(); + string output = ""; + + if(HasCoordinates()) { + MousePosition pos = GetCoordinates(); + output += std::to_string(pos.X) + " " + std::to_string(pos.Y) + " "; + } + + for(size_t i = 0; i < keyNames.size(); i++) { + output += IsPressed((uint8_t)i) ? keyNames[i] : '.'; + } + + return output; } +} - //For NetPlay - ControlManager::BroadcastInput(_port, _currentState); +void BaseControlDevice::EnsureCapacity(int32_t minBitCount) +{ + uint32_t minByteCount = minBitCount / 8 + 1 + (HasCoordinates() ? 32 : 0); + int32_t gap = minByteCount - (int32_t)_state.State.size(); - RewindManager::RecordInput(_port, _currentState); + if(gap > 0) { + _state.State.insert(_state.State.end(), gap, 0); + } +} - return _currentState; -} \ No newline at end of file +bool BaseControlDevice::HasCoordinates() +{ + return false; +} + +bool BaseControlDevice::IsRawString() +{ + return false; +} + +uint32_t BaseControlDevice::GetByteIndex(uint8_t bit) +{ + return bit / 8 + (HasCoordinates() ? 4 : 0); +} + +bool BaseControlDevice::IsPressed(uint8_t bit) +{ + EnsureCapacity(bit); + uint8_t bitMask = 1 << (bit % 8); + return (_state.State[GetByteIndex(bit)] & bitMask) != 0; +} + +void BaseControlDevice::SetBitValue(uint8_t bit, bool set) +{ + if(set) { + SetBit(bit); + } else { + ClearBit(bit); + } +} + +void BaseControlDevice::SetBit(uint8_t bit) +{ + EnsureCapacity(bit); + uint8_t bitMask = 1 << (bit % 8); + _state.State[GetByteIndex(bit)] |= bitMask; +} + +void BaseControlDevice::ClearBit(uint8_t bit) +{ + EnsureCapacity(bit); + uint8_t bitMask = 1 << (bit % 8); + _state.State[GetByteIndex(bit)] &= ~bitMask; +} + +void BaseControlDevice::InvertBit(uint8_t bit) +{ + if(IsPressed(bit)) { + ClearBit(bit); + } else { + SetBit(bit); + } +} + +void BaseControlDevice::SetPressedState(uint8_t bit, uint32_t keyCode) +{ + if(EmulationSettings::InputEnabled() && KeyManager::IsKeyPressed(keyCode)) { + SetBit(bit); + } +} + +void BaseControlDevice::SetPressedState(uint8_t bit, bool enabled) +{ + if(enabled) { + SetBit(bit); + } +} + +void BaseControlDevice::SetCoordinates(MousePosition pos) +{ + EnsureCapacity(-1); + + _state.State[0] = pos.X & 0xFF; + _state.State[1] = (pos.X >> 8) & 0xFF; + _state.State[2] = pos.Y & 0xFF; + _state.State[3] = (pos.Y >> 8) & 0xFF; +} + +MousePosition BaseControlDevice::GetCoordinates() +{ + EnsureCapacity(-1); + + MousePosition pos; + pos.X = _state.State[0] | (_state.State[1] << 8); + pos.Y = _state.State[2] | (_state.State[3] << 8); + return pos; +} + +void BaseControlDevice::SetMovement(MouseMovement mov) +{ + MouseMovement prev = GetMovement(); + mov.dx += prev.dx; + mov.dy += prev.dy; + SetCoordinates({ mov.dx, mov.dy }); +} + +MouseMovement BaseControlDevice::GetMovement() +{ + MousePosition pos = GetCoordinates(); + SetCoordinates({ 0, 0 }); + return { pos.X, pos.Y }; +} + +void BaseControlDevice::SwapButtons(shared_ptr state1, uint8_t button1, shared_ptr state2, uint8_t button2) +{ + bool pressed1 = state1->IsPressed(button1); + bool pressed2 = state2->IsPressed(button2); + + state1->ClearBit(button1); + state2->ClearBit(button2); + + if(pressed1) { + state2->SetBit(button2); + } + if(pressed2) { + state1->SetBit(button1); + } +} diff --git a/Core/BaseControlDevice.h b/Core/BaseControlDevice.h index f7e1278f..ea00e844 100644 --- a/Core/BaseControlDevice.h +++ b/Core/BaseControlDevice.h @@ -1,72 +1,80 @@ #pragma once - #include "stdafx.h" #include "EmulationSettings.h" #include "Snapshotable.h" - -struct ButtonState -{ - bool Up = false; - bool Down = false; - bool Left = false; - bool Right = false; - - bool A = false; - bool B = false; - - bool Select = false; - bool Start = false; - - uint8_t ToByte() - { - //"Button status for each controller is returned as an 8-bit report in the following order: A, B, Select, Start, Up, Down, Left, Right." - return (uint8_t)A | ((uint8_t)B << 1) | ((uint8_t)Select << 2) | ((uint8_t)Start << 3) | - ((uint8_t)Up << 4) | ((uint8_t)Down << 5) | ((uint8_t)Left << 6) | ((uint8_t)Right << 7); - } - - void FromByte(uint8_t stateData) - { - A = (stateData & 0x01) == 0x01; - B = (stateData & 0x02) == 0x02; - Select = (stateData & 0x04) == 0x04; - Start = (stateData & 0x08) == 0x08; - Up = (stateData & 0x10) == 0x10; - Down = (stateData & 0x20) == 0x20; - Left = (stateData & 0x40) == 0x40; - Right = (stateData & 0x80) == 0x80; - } -}; +#include "ControlManager.h" +#include "ControlDeviceState.h" class BaseControlDevice : public Snapshotable { protected: - uint8_t _port; - uint8_t _currentState; + ControlDeviceState _state; vector _keyMappings; - uint32_t _turboSpeed = 0; - bool _famiconDevice = false; + bool _strobe; + uint8_t _port; - uint8_t GetPort(); - void AddKeyMappings(KeyMappingSet keyMappings); + virtual void RefreshStateBuffer() { } + virtual void StreamState(bool saving); + + void EnsureCapacity(int32_t minBitCount); + uint32_t GetByteIndex(uint8_t bit); + virtual bool HasCoordinates(); + virtual bool IsRawString(); - //Defined in controller-specific code and called when we need to read a device's state - virtual uint8_t RefreshState() = 0; - virtual uint8_t ProcessNetPlayState(uint32_t netplayState); + bool IsCurrentPort(uint16_t addr); + bool IsExpansionDevice(); + void StrobeProcessRead(); + void StrobeProcessWrite(uint8_t value); - virtual void StreamState(bool saving) override; + virtual string GetKeyNames() { return ""; } + void SetPressedState(uint8_t bit, uint32_t keyCode); + void SetPressedState(uint8_t bit, bool enabled); + + void SetCoordinates(MousePosition pos); + + void SetMovement(MouseMovement mov); + MouseMovement GetMovement(); + + virtual void InternalSetStateFromInput(); + public: - //Used by controller-specific code to get the current state (buttons, position, etc) - uint8_t GetControlState(); + static const uint8_t ExpDevicePort = 4; + static const uint8_t ConsoleInputPort = 5; + static const uint8_t MapperInputPort = 6; + static const uint8_t PortCount = MapperInputPort + 1; - BaseControlDevice(uint8_t port); + BaseControlDevice(uint8_t port, KeyMappingSet keyMappingSet = KeyMappingSet()); virtual ~BaseControlDevice(); - //Called when reading $4016/7 - virtual uint8_t GetPortOutput() = 0; + uint8_t GetPort(); - virtual uint32_t GetNetPlayState() = 0; + bool IsPressed(uint8_t bit); + MousePosition GetCoordinates(); - //Used by standard controllers when $4017.1 is set - virtual void RefreshStateBuffer(); -}; \ No newline at end of file + void ClearState(); + void SetBit(uint8_t bit); + void ClearBit(uint8_t bit); + void InvertBit(uint8_t bit); + void SetBitValue(uint8_t bit, bool set); + + void SetTextState(string state); + string GetTextState(); + + void SetStateFromInput(); + virtual void OnAfterSetState() { } + + void SetRawState(ControlDeviceState state); + ControlDeviceState GetRawState(); + + template + shared_ptr GetState() + { + return std::dynamic_pointer_cast(_state); + } + + virtual uint8_t ReadRAM(uint16_t addr) = 0; + virtual void WriteRAM(uint16_t addr, uint8_t value) = 0; + + void static SwapButtons(shared_ptr state1, uint8_t button1, shared_ptr state2, uint8_t button2); +}; diff --git a/Core/BaseMapper.cpp b/Core/BaseMapper.cpp index 158f2c0b..a206f685 100644 --- a/Core/BaseMapper.cpp +++ b/Core/BaseMapper.cpp @@ -8,6 +8,7 @@ #include "CheatManager.h" #include "Debugger.h" #include "MemoryManager.h" +#include "BatteryManager.h" void BaseMapper::WriteRegister(uint16_t addr, uint8_t value) { } uint8_t BaseMapper::ReadRegister(uint16_t addr) { return 0; } @@ -338,40 +339,22 @@ bool BaseMapper::HasBattery() void BaseMapper::LoadBattery() { if(HasBattery()) { - ifstream batteryFile(_batteryFilename, ios::in | ios::binary); - if(batteryFile) { - batteryFile.read((char*)_saveRam, _saveRamSize); - batteryFile.close(); - } + BatteryManager::LoadBattery(".sav", _saveRam, _saveRamSize); } if(_hasChrBattery) { - ifstream batteryFile(_batteryFilename + ".chr", ios::in | ios::binary); - if(batteryFile) { - batteryFile.read((char*)_chrRam, _chrRamSize); - batteryFile.close(); - } + BatteryManager::LoadBattery(".sav.chr", _chrRam, _chrRamSize); } } void BaseMapper::SaveBattery() { if(HasBattery()) { - ofstream batteryFile(_batteryFilename, ios::out | ios::binary); - - if(batteryFile) { - batteryFile.write((char*)_saveRam, _saveRamSize); - - batteryFile.close(); - } + BatteryManager::SaveBattery(".sav", _saveRam, _saveRamSize); } if(_hasChrBattery) { - ofstream batteryFile(_batteryFilename + ".chr", ios::out | ios::binary); - if(batteryFile) { - batteryFile.write((char*)_chrRam, _chrRamSize); - batteryFile.close(); - } + BatteryManager::SaveBattery(".sav.chr", _chrRam, _chrRamSize); } } @@ -449,17 +432,19 @@ void BaseMapper::StreamState(bool saving) ArrayInfo nametableIndexes = { _nametableIndexes, 4 }; Stream(_mirroringType, chrRam, workRam, saveRam, prgPageNumbers, chrPageNumbers, nametableIndexes); - bool hasExtraNametable[2] = { _cartNametableRam[0] != nullptr, _cartNametableRam[1] != nullptr }; - Stream(hasExtraNametable[0], hasExtraNametable[1]); - - for(int i = 0; i < 2; i++) { - if(hasExtraNametable[i]) { - if(!_cartNametableRam[i]) { - _cartNametableRam[i] = new uint8_t[0x400]; - } + if(GetStateVersion() >= 7) { + bool hasExtraNametable[2] = { _cartNametableRam[0] != nullptr, _cartNametableRam[1] != nullptr }; + Stream(hasExtraNametable[0], hasExtraNametable[1]); - ArrayInfo ram = { _cartNametableRam[i], 0x400 }; - Stream(ram); + for(int i = 0; i < 2; i++) { + if(hasExtraNametable[i]) { + if(!_cartNametableRam[i]) { + _cartNametableRam[i] = new uint8_t[0x400]; + } + + ArrayInfo ram = { _cartNametableRam[i], 0x400 }; + Stream(ram); + } } } @@ -496,7 +481,7 @@ void BaseMapper::Initialize(RomData &romData) _hasBattery = (romData.HasBattery || ForceBattery()); if(romData.SaveRamSize == -1 || ForceSaveRamSize()) { - _saveRamSize = GetSaveRamSize(); //Needed because we need to call SaveBattery() in the destructor (and calling virtual functions in the destructor doesn't work correctly) + _saveRamSize = GetSaveRamSize(); } else { _saveRamSize = romData.SaveRamSize; } @@ -580,7 +565,7 @@ void BaseMapper::Initialize(RomData &romData) } else if(GetChrRamSize()) { InitializeChrRam(); } - + //Load battery data if present LoadBattery(); @@ -714,6 +699,16 @@ void BaseMapper::SetMirroringType(MirroringType type) } } +ConsoleFeatures BaseMapper::GetAvailableFeatures() +{ + return ConsoleFeatures::None; +} + +shared_ptr BaseMapper::GetMapperControlDevice() +{ + return _mapperControlDevice; +} + GameSystem BaseMapper::GetGameSystem() { return _gameSystem; diff --git a/Core/BaseMapper.h b/Core/BaseMapper.h index f1886387..1761d5a5 100644 --- a/Core/BaseMapper.h +++ b/Core/BaseMapper.h @@ -9,8 +9,11 @@ #include "DebuggerTypes.h" #include "Debugger.h" #include "Types.h" +#include "IBattery.h" -class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificationListener +class BaseControlDevice; + +class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificationListener, public IBattery { private: MirroringType _mirroringType; @@ -51,6 +54,8 @@ private: vector _originalChrRom; protected: + shared_ptr _mapperControlDevice; + NESHeader _nesHeader; GameInfo _databaseInfo; @@ -151,6 +156,8 @@ public: virtual ~BaseMapper(); virtual void Reset(bool softReset); + virtual ConsoleFeatures GetAvailableFeatures(); + virtual void SetNesModel(NesModel model) { } virtual void ProcessCpuClock() { } virtual void NotifyVRAMAddressChange(uint16_t addr); @@ -158,10 +165,12 @@ public: virtual void GetMemoryRanges(MemoryRanges &ranges) override; void ApplyCheats(); - virtual void SaveBattery(); + + virtual void SaveBattery() override; virtual void SetDefaultNametables(uint8_t* nametableA, uint8_t* nametableB); + shared_ptr GetMapperControlDevice(); GameSystem GetGameSystem(); HashInfo GetHashInfo(); string GetRomName(); diff --git a/Core/BatteryManager.cpp b/Core/BatteryManager.cpp new file mode 100644 index 00000000..cb53ce27 --- /dev/null +++ b/Core/BatteryManager.cpp @@ -0,0 +1,69 @@ +#include "stdafx.h" +#include "BatteryManager.h" +#include "VirtualFile.h" +#include "../Utilities/FolderUtilities.h" + +string BatteryManager::_romName; +std::weak_ptr BatteryManager::_recorder; +std::weak_ptr BatteryManager::_provider; + +void BatteryManager::Initialize(string romName) +{ + _romName = romName; +} + +string BatteryManager::GetBasePath() +{ + return FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), _romName); +} + +void BatteryManager::SetBatteryProvider(shared_ptr provider) +{ + _provider = provider; +} + +void BatteryManager::SetBatteryRecorder(shared_ptr recorder) +{ + _recorder = recorder; +} + +void BatteryManager::SaveBattery(string extension, uint8_t* data, uint32_t length) +{ + ofstream out(GetBasePath() + extension, ios::binary); + if(out) { + out.write((char*)data, length); + } +} + +vector BatteryManager::LoadBattery(string extension) +{ + shared_ptr provider = _provider.lock(); + + vector batteryData; + if(provider) { + //Used by movie player to provider initial state of ram at startup + batteryData = provider->LoadBattery(extension); + } else { + VirtualFile file = GetBasePath() + extension; + if(file.IsValid()) { + file.ReadFile(batteryData); + } + } + + if(!batteryData.empty()) { + shared_ptr recorder = _recorder.lock(); + if(recorder) { + //Used by movies to record initial state of battery-backed ram at power on + recorder->OnLoadBattery(extension, batteryData); + } + } + + return batteryData; +} + +void BatteryManager::LoadBattery(string extension, uint8_t* data, uint32_t length) +{ + vector batteryData = LoadBattery(extension); + memset(data, 0, length); + memcpy(data, batteryData.data(), std::min((uint32_t)batteryData.size(), length)); +} \ No newline at end of file diff --git a/Core/BatteryManager.h b/Core/BatteryManager.h new file mode 100644 index 00000000..4404324d --- /dev/null +++ b/Core/BatteryManager.h @@ -0,0 +1,37 @@ +#pragma once +#include "stdafx.h" + +class IBatteryProvider +{ +public: + virtual vector LoadBattery(string extension) = 0; +}; + +class IBatteryRecorder +{ +public: + virtual void OnLoadBattery(string extension, vector batteryData) = 0; +}; + +class BatteryManager +{ +private: + static string _romName; + static string GetBasePath(); + + static std::weak_ptr _provider; + static std::weak_ptr _recorder; + + BatteryManager() = delete; + +public: + static void Initialize(string romName); + + static void SetBatteryProvider(shared_ptr provider); + static void SetBatteryRecorder(shared_ptr recorder); + + static void SaveBattery(string extension, uint8_t* data, uint32_t length); + + static vector LoadBattery(string extension); + static void LoadBattery(string extension, uint8_t* data, uint32_t length); +}; \ No newline at end of file diff --git a/Core/BattleBox.h b/Core/BattleBox.h new file mode 100644 index 00000000..ec9e6fed --- /dev/null +++ b/Core/BattleBox.h @@ -0,0 +1,118 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/FolderUtilities.h" +#include "Console.h" +#include "BaseControlDevice.h" +#include "IBattery.h" +#include "BatteryManager.h" + +class BattleBox : public BaseControlDevice, public IBattery +{ +private: + static const int FileSize = 0x200; + uint8_t _lastWrite = 0; + uint8_t _address = 0; + uint8_t _chipSelect = 0; + uint16_t _data[BattleBox::FileSize/2]; + uint8_t _output = 0; + bool _writeEnabled = false; + + uint8_t _inputBitPosition = 0; + uint16_t _inputData = 0; + bool _isWrite = false; + bool _isRead = false; + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + ArrayInfo data{ (uint8_t*)_data, BattleBox::FileSize }; + Stream(_lastWrite, _address, _chipSelect, _output, _writeEnabled, _inputBitPosition, _isWrite, _isRead, _inputData, data); + } + +public: + BattleBox() : BaseControlDevice(BaseControlDevice::ExpDevicePort) + { + BatteryManager::LoadBattery(".bb", (uint8_t*)_data, BattleBox::FileSize); + } + + ~BattleBox() + { + SaveBattery(); + } + + void SaveBattery() + { + BatteryManager::SaveBattery(".bb", (uint8_t*)_data, BattleBox::FileSize); + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + if(_lastWrite & 0x01) { + _chipSelect ^= 0x01; + _inputData = 0; + _inputBitPosition = 0; + } + _output ^= 0x01; + + uint8_t readBit = 0; + if(_isRead) { + readBit = ((_data[(_chipSelect ? 0x80 : 0) | _address] >> _inputBitPosition) & 0x01) << 3; + } + uint8_t writeBit = (_output << 4); + return readBit | writeBit; + } + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + if(value & 0x01 && !(_lastWrite & 0x01)) { + //Clock + _inputData &= ~(1 << _inputBitPosition); + _inputData |= (_output << _inputBitPosition); + _inputBitPosition++; + + if(_inputBitPosition > 15) { + if(_isWrite) { + _data[(_chipSelect ? 0x80 : 0) | _address] = _inputData; + _isWrite = false; + } else { + _isRead = false; + + //done reading addr/command or write data + uint8_t address = (_inputData & 0x7F); + + uint8_t cmd = ((_inputData & 0x7F00) >> 8) ^ 0x7F; + switch(cmd) { + case 0x01: + //read + _address = address; + _isRead = true; + break; + case 0x06: + //program + if(_writeEnabled) { + _address = address; + _isWrite = true; + } + break; + case 0x0C: + //chip erase + if(_writeEnabled) { + memset(_data, 0, BattleBox::FileSize); + } + break; + + case 0x0D: break; //busy monitor + case 0x09: _writeEnabled = true; break; //erase/write enable + case 0x0B: _writeEnabled = false; break; //erase/write disable + } + } + _inputBitPosition = 0; + } + } + _lastWrite = value; + } +}; \ No newline at end of file diff --git a/Core/BizhawkMovie.cpp b/Core/BizhawkMovie.cpp index 5968ff7b..5c28e67b 100644 --- a/Core/BizhawkMovie.cpp +++ b/Core/BizhawkMovie.cpp @@ -1,4 +1,7 @@ #include "stdafx.h" +#include "SystemActionManager.h" +#include "FdsSystemActionManager.h" +#include "VsSystemActionManager.h" #include "BizhawkMovie.h" #include "VsControlManager.h" #include "FDS.h" @@ -7,84 +10,101 @@ BizhawkMovie::BizhawkMovie() { _originalPowerOnState = EmulationSettings::GetRamPowerOnState(); - MessageManager::RegisterNotificationListener(this); } BizhawkMovie::~BizhawkMovie() { - MessageManager::UnregisterNotificationListener(this); - EmulationSettings::SetRamPowerOnState(_originalPowerOnState); + Stop(); } -void BizhawkMovie::ProcessNotification(ConsoleNotificationType type, void* parameter) +void BizhawkMovie::Stop() { - if(type == ConsoleNotificationType::PpuFrameDone) { - int32_t frameNumber = PPU::GetFrameCount(); + if(_isPlaying) { + EndMovie(); + EmulationSettings::SetRamPowerOnState(_originalPowerOnState); + _isPlaying = false; + } + ControlManager::UnregisterInputProvider(this); +} + +bool BizhawkMovie::SetInput(BaseControlDevice *device) +{ + SystemActionManager* actionManager = dynamic_cast(device); + int32_t frameNumber = PPU::GetFrameCount(); + if(actionManager) { if(frameNumber < (int32_t)_systemActionByFrame.size()) { uint32_t systemAction = _systemActionByFrame[frameNumber]; if(systemAction & 0x01) { - //Power, not implemented yet + actionManager->SetBit(SystemActionManager::Buttons::PowerButton); } if(systemAction & 0x02) { - //Reset, not implemented yet + actionManager->SetBit(SystemActionManager::Buttons::ResetButton); } - - if(FDS::GetSideCount()) { + + VsSystemActionManager* vsActionManager = dynamic_cast(device); + if(vsActionManager) { + if(systemAction & 0x04) { + actionManager->SetBit(VsSystemActionManager::VsButtons::InsertCoin1); + } + if(systemAction & 0x08) { + actionManager->SetBit(VsSystemActionManager::VsButtons::InsertCoin2); + } + if(systemAction & 0x10) { + actionManager->SetBit(VsSystemActionManager::VsButtons::ServiceButton); + } + } + + FdsSystemActionManager* fdsActionManager = dynamic_cast(device); + if(fdsActionManager) { //FDS timings between NesHawk & Mesen are currently significantly different //So FDS games will always go out of sync if(systemAction & 0x04) { - FDS::EjectDisk(); - } else if(systemAction >= 8) { + fdsActionManager->SetBit(FdsSystemActionManager::FdsButtons::EjectDiskButton); + } + + if(systemAction >= 8) { systemAction >>= 3; uint32_t diskNumber = 0; while(!(systemAction & 0x01)) { systemAction >>= 1; diskNumber++; } - FDS::InsertDisk(diskNumber); - } - } else if(VsControlManager::GetInstance()) { - if(VsControlManager::GetInstance()) { - if(systemAction & 0x04) { - VsControlManager::GetInstance()->InsertCoin(0); - } - if(systemAction & 0x08) { - VsControlManager::GetInstance()->InsertCoin(1); - } - VsControlManager::GetInstance()->SetServiceButtonState(systemAction & 0x10 ? true : false); + + fdsActionManager->SetBit(FdsSystemActionManager::FdsButtons::InsertDisk1 + diskNumber); } } } - } -} - -uint8_t BizhawkMovie::GetState(uint8_t port) -{ - int32_t frameNumber = PPU::GetFrameCount() - (PPU::GetCurrentScanline() >= 240 ? 0 : 1); - if(frameNumber < (int32_t)_dataByFrame[0].size()) { - return _dataByFrame[port][frameNumber]; } else { - EndMovie(); - EmulationSettings::SetRamPowerOnState(_originalPowerOnState); - _isPlaying = false; - return 0; + int port = device->GetPort(); + StandardController* controller = dynamic_cast(device); + if(controller) { + if(frameNumber < (int32_t)_dataByFrame[port].size()) { + controller->SetTextState(_dataByFrame[port][frameNumber]); + } else { + Stop(); + } + } } + + return true; } -bool BizhawkMovie::InitializeGameData(ZipReader & reader) +bool BizhawkMovie::InitializeGameData(ZipReader &reader) { - std::stringstream ss = reader.GetStream("Header.txt"); + stringstream fileData; + if(!reader.GetStream("Header.txt", fileData)) { + return false; + } - bool result = false; - while(!ss.eof()) { + while(!fileData.eof()) { string line; - std::getline(ss, line); + std::getline(fileData, line); if(line.compare(0, 4, "SHA1", 4) == 0) { if(line.size() >= 45) { HashInfo hashInfo; hashInfo.Sha1Hash = line.substr(5, 40); if(Console::LoadROM("", hashInfo)) { - result = true; + return true; } } } else if(line.compare(0, 3, "MD5", 3) == 0) { @@ -93,31 +113,37 @@ bool BizhawkMovie::InitializeGameData(ZipReader & reader) hashInfo.PrgChrMd5Hash = line.substr(4, 32); std::transform(hashInfo.PrgChrMd5Hash.begin(), hashInfo.PrgChrMd5Hash.end(), hashInfo.PrgChrMd5Hash.begin(), ::toupper); if(Console::LoadROM("", hashInfo)) { - result = true; + return true; } } } } - return result; + return false; } -bool BizhawkMovie::InitializeInputData(ZipReader & reader) +bool BizhawkMovie::InitializeInputData(ZipReader &reader) { - const uint8_t orValues[8] = { 0x10, 0x20, 0x40, 0x80, 0x08, 0x04, 0x02, 0x01 }; - std::stringstream ss = reader.GetStream("Input Log.txt"); - - int systemActionCount = 2; - if(FDS::GetSideCount() > 0) { - //Eject disk + Insert Disk #XX - systemActionCount += FDS::GetSideCount() + 1; - } else if(VsControlManager::GetInstance()) { - //Insert coin 1, 2 + service button - systemActionCount += 3; + stringstream inputData; + if(!reader.GetStream("Input Log.txt", inputData)) { + return false; } - while(!ss.eof()) { + int systemActionCount = 2; + shared_ptr fdsActionManager = Console::GetInstance()->GetSystemActionManager(); + if(fdsActionManager) { + //Eject disk + Insert Disk #XX + systemActionCount += fdsActionManager->GetSideCount() + 1; + } else { + shared_ptr vsActionManager = Console::GetInstance()->GetSystemActionManager(); + if(vsActionManager) { + //Insert coin 1, 2 + service button + systemActionCount += 3; + } + } + + while(!inputData.eof()) { string line; - std::getline(ss, line); + std::getline(inputData, line); if(line.size() > 0 && line[0] == '|') { line.erase(std::remove(line.begin(), line.end(), '|'), line.end()); @@ -132,20 +158,16 @@ bool BizhawkMovie::InitializeInputData(ZipReader & reader) } _systemActionByFrame.push_back(systemAction); - //Only supports regular controllers (up to 4 of them) - for(int i = 0; i < 8*4; i++) { - uint8_t port = i / 8; - - if(port <= 3) { - uint8_t portValue = 0; - for(int j = 0; j < 8 && i + j + systemActionCount < (int)line.size(); j++) { - if(line[i+j+systemActionCount] != '.') { - portValue |= orValues[j]; - } - } - i += 7; - _dataByFrame[port].push_back(portValue); - } + line = line.substr(systemActionCount); + int port = 0; + while(line.size() >= 8) { + _dataByFrame[port].push_back(line.substr(0, 8)); + line = line.substr(8); + port++; + } + while(port < 4) { + _dataByFrame[port].push_back("........"); + port++; } } } @@ -153,39 +175,28 @@ bool BizhawkMovie::InitializeInputData(ZipReader & reader) return _dataByFrame[0].size() > 0; } -bool BizhawkMovie::Play(stringstream & filestream, bool autoLoadRom) +bool BizhawkMovie::Play(VirtualFile &file) { Console::Pause(); ZipReader reader; - reader.LoadArchive(filestream); - if(InitializeGameData(reader)) { - if(InitializeInputData(reader)) { - //NesHawk initializes memory to 1s - EmulationSettings::SetRamPowerOnState(RamPowerOnState::AllOnes); - Console::Reset(false); - _isPlaying = true; - } + + std::stringstream ss; + file.ReadFile(ss); + + reader.LoadArchive(ss); + ControlManager::RegisterInputProvider(this); + if(InitializeInputData(reader) && InitializeGameData(reader)) { + //NesHawk initializes memory to 1s + EmulationSettings::SetRamPowerOnState(RamPowerOnState::AllOnes); + _isPlaying = true; + } else { + ControlManager::UnregisterInputProvider(this); } Console::Resume(); return _isPlaying; } -bool BizhawkMovie::IsRecording() -{ - return false; -} - bool BizhawkMovie::IsPlaying() { return _isPlaying; } - -void BizhawkMovie::RecordState(uint8_t port, uint8_t value) -{ - //Not implemented -} - -void BizhawkMovie::Record(string filename, bool reset) -{ - //Not implemented -} diff --git a/Core/BizhawkMovie.h b/Core/BizhawkMovie.h index a12a37c4..b3b1d3d2 100644 --- a/Core/BizhawkMovie.h +++ b/Core/BizhawkMovie.h @@ -3,15 +3,18 @@ #include "MovieManager.h" #include "../Utilities/ZipReader.h" -class BizhawkMovie : public IMovie, public INotificationListener +class VirtualFile; + +class BizhawkMovie : public IMovie { private: bool InitializeGameData(ZipReader &reader); bool InitializeInputData(ZipReader &reader); + void Stop(); protected: vector _systemActionByFrame; - vector _dataByFrame[4]; + vector _dataByFrame[4]; bool _isPlaying = false; RamPowerOnState _originalPowerOnState; @@ -19,15 +22,7 @@ public: BizhawkMovie(); virtual ~BizhawkMovie(); - void RecordState(uint8_t port, uint8_t value) override; - void Record(string filename, bool reset) override; - - uint8_t GetState(uint8_t port) override; - - virtual bool Play(stringstream &filestream, bool autoLoadRom) override; - - bool IsRecording() override; + bool SetInput(BaseControlDevice *device) override; + bool Play(VirtualFile &file) override; bool IsPlaying() override; - - void ProcessNotification(ConsoleNotificationType type, void* parameter) override; }; \ No newline at end of file diff --git a/Core/Console.cpp b/Core/Console.cpp index 4e9856e9..3f95fc21 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -29,13 +29,20 @@ #include "SaveStateManager.h" #include "HdPackBuilder.h" #include "HdAudioDevice.h" +#include "FDS.h" +#include "SystemActionManager.h" +#include "FdsSystemActionManager.h" +#include "VsSystemActionManager.h" +#include "IBarcodeReader.h" +#include "IBattery.h" +#include "KeyManager.h" +#include "BatteryManager.h" shared_ptr Console::Instance(new Console()); Console::Console() { _resetRequested = false; - _lagCounter = 0; } Console::~Console() @@ -51,17 +58,26 @@ shared_ptr Console::GetInstance() void Console::Release() { - Console::Instance.reset(new Console()); + Console::Instance.reset(); +} + +void Console::SaveBatteries() +{ + _mapper->SaveBattery(); + + shared_ptr device = std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort)); + if(device) { + device->SaveBattery(); + } } 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(); + SaveBatteries(); //Save current game state before loading another one SaveStateManager::SaveRecentGame(_mapper->GetRomName(), _romFilepath, _patchFilename); @@ -71,7 +87,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) LoadHdPack(romFile, patchFile); if(patchFile.IsValid()) { if(romFile.ApplyPatch(patchFile)) { - MessageManager::DisplayMessage("Patch", "ApplyingPatch", FolderUtilities::GetFilename(patchFile.GetFilePath(), true)); + MessageManager::DisplayMessage("Patch", "ApplyingPatch", patchFile.GetFileName()); } else { //Patch failed } @@ -79,6 +95,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) vector fileData; romFile.ReadFile(fileData); + BatteryManager::Initialize(FolderUtilities::GetFilename(romFile.GetFileName(), false)); shared_ptr mapper = MapperFactory::InitializeFromFile(romFile.GetFileName(), fileData); if(mapper) { if(_mapper) { @@ -86,8 +103,15 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) MessageManager::SendNotification(ConsoleNotificationType::GameStopped); } - _romFilepath = romFile; - _patchFilename = patchFile; + if(_romFilepath != (string)romFile || _patchFilename != (string)patchFile) { + _romFilepath = romFile; + _patchFilename = patchFile; + + //Changed game, stop all recordings + MovieManager::Stop(); + SoundMixer::StopRecording(); + StopRecordingHdPack(); + } _autoSaveManager.reset(new AutoSaveManager()); VideoDecoder::GetInstance()->StopThread(); @@ -95,20 +119,29 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) _mapper = mapper; _memoryManager.reset(new MemoryManager(_mapper)); _cpu.reset(new CPU(_memoryManager.get())); - - 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()); + switch(_mapper->GetGameSystem()) { + case GameSystem::FDS: _systemActionManager.reset(new FdsSystemActionManager(Console::GetInstance(), _mapper)); break; + case GameSystem::VsUniSystem: _systemActionManager.reset(new VsSystemActionManager(Console::GetInstance())); break; + default: _systemActionManager.reset(new SystemActionManager(Console::GetInstance())); break; + } + + if(_mapper->GetGameSystem() == GameSystem::VsUniSystem) { + _controlManager.reset(new VsControlManager(_systemActionManager, _mapper->GetMapperControlDevice())); + } else { + _controlManager.reset(new ControlManager(_systemActionManager, _mapper->GetMapperControlDevice())); + } _controlManager->UpdateControlDevices(); + + if(_hdData && (!_hdData->Tiles.empty() || !_hdData->Backgrounds.empty())) { + _ppu.reset(new HdPpu(_mapper.get(), _controlManager.get(), _hdData->Version)); + } else if(NsfMapper::GetInstance()) { + //Disable most of the PPU for NSFs + _ppu.reset(new NsfPpu(_mapper.get(), _controlManager.get())); + } else { + _ppu.reset(new PPU(_mapper.get(), _controlManager.get())); + } _memoryManager->RegisterIODevice(_ppu.get()); _memoryManager->RegisterIODevice(_apu.get()); @@ -125,7 +158,6 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) _initialized = true; if(_debugger) { - auto lock = _debuggerLock.AcquireSafe(); StopDebugger(); GetDebugger(); } @@ -133,6 +165,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) ResetComponents(false); _rewindManager.reset(new RewindManager()); + _controlManager->UpdateInputState(); VideoDecoder::GetInstance()->StartThread(); @@ -148,6 +181,9 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) } } + //Reset battery source to current game if new game failed to load + BatteryManager::Initialize(FolderUtilities::GetFilename(GetRomName(), false)); + MessageManager::DisplayMessage("Error", "CouldNotLoadFile", romFile.GetFileName()); return false; } @@ -163,15 +199,33 @@ bool Console::LoadROM(VirtualFile romFile, VirtualFile patchFile) bool Console::LoadROM(string romName, HashInfo hashInfo) { 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 + //Current game matches, power cycle game and return + Instance->PowerCycle(); return true; } } + string match = FindMatchingRom(romName, hashInfo); + if(!match.empty()) { + return Console::LoadROM(match); + } + return false; +} + +string Console::FindMatchingRom(string romName, HashInfo hashInfo) +{ + VirtualFile currentRom = Console::GetRomPath(); + if(currentRom.IsValid() && !Console::GetPatchFile().IsValid()) { + 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 + return currentRom; + } + } + string lcRomname = romName; std::transform(lcRomname.begin(), lcRomname.end(), lcRomname.begin(), ::tolower); std::unordered_set validExtensions = { { ".nes", ".fds", "*.unif", "*.unif", "*.nsf", "*.nsfe", "*.7z", "*.zip" } }; @@ -180,19 +234,19 @@ bool Console::LoadROM(string romName, HashInfo hashInfo) vector 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); + return match; } //Perform slow CRC32 search for ROM match = RomLoader::FindMatchingRom(romFiles, romName, hashInfo, false); if(!match.empty()) { - return Console::LoadROM(match); + return match; } - return false; + return ""; } VirtualFile Console::GetRomPath() @@ -209,6 +263,11 @@ string Console::GetRomName() } } +VirtualFile Console::GetPatchFile() +{ + return Instance ? Instance->_patchFilename : VirtualFile(); +} + RomFormat Console::GetRomFormat() { if(Instance->_mapper) { @@ -240,45 +299,36 @@ NesModel Console::GetModel() return Instance->_model; } +shared_ptr Console::GetSystemActionManager() +{ + return _systemActionManager; +} + void Console::PowerCycle() { - if(Instance->_initialized && !Instance->_romFilepath.empty()) { - LoadROM(Instance->_romFilepath, Instance->_patchFilename); + if(_initialized && !_romFilepath.empty()) { + LoadROM(_romFilepath, _patchFilename); } } void Console::Reset(bool softReset) { if(Instance->_initialized) { - if(softReset && EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset)) { - //Allow mid-frame resets to allow the PPU to get out-of-sync - RequestReset(); - } else { - 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); - } + if(softReset) { + if(EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset)) { + //Allow mid-frame resets to allow the PPU to get out-of-sync + RequestReset(); + } else { + Instance->_systemActionManager->Reset(); } - Console::Resume(); + } else { + Instance->_systemActionManager->PowerCycle(); } } } void Console::ResetComponents(bool softReset) { - MovieManager::Stop(); - if(!softReset) { - SoundMixer::StopRecording(); - _hdPackBuilder.reset(); - } - _memoryManager->Reset(softReset); if(!EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset) || !softReset) { _ppu->Reset(); @@ -287,9 +337,7 @@ void Console::ResetComponents(bool softReset) _cpu->Reset(softReset, _model); _controlManager->Reset(softReset); - _lagCounter = 0; - - SoundMixer::StopAudio(true); + KeyManager::UpdateDevices(); MessageManager::SendNotification(softReset ? ConsoleNotificationType::GameReset : ConsoleNotificationType::GameLoaded); @@ -379,16 +427,10 @@ void Console::Run() uint32_t currentFrameNumber = PPU::GetFrameCount(); if(currentFrameNumber != lastFrameNumber) { - if(_controlManager->GetLagFlag()) { - _lagCounter++; - } - _rewindManager->ProcessEndOfFrame(); EmulationSettings::DisableOverclocking(_disableOcNextFrame || NsfMapper::GetInstance()); _disableOcNextFrame = false; - lastFrameNumber = PPU::GetFrameCount(); - //Sleep until we're ready to start the next frame clockTimer.WaitUntil(targetTime); @@ -427,6 +469,8 @@ void Console::Run() _runLock.Acquire(); MessageManager::SendNotification(ConsoleNotificationType::GameResumed); } + + _systemActionManager->ProcessSystemActions(); shared_ptr debugger = _debugger; if(debugger) { @@ -443,6 +487,8 @@ void Console::Run() if(targetTime < 0) { targetTime = 0; } + + lastFrameNumber = PPU::GetFrameCount(); if(_stop) { _stop = false; @@ -472,7 +518,7 @@ void Console::Run() if(!_romFilepath.empty() && _mapper) { //Ensure we save any battery file before unloading anything - _mapper->SaveBattery(); + SaveBatteries(); } _romFilepath = ""; @@ -575,21 +621,21 @@ void Console::SaveState(ostream &saveStream) } } -void Console::LoadState(istream &loadStream) +void Console::LoadState(istream &loadStream, uint32_t stateVersion) { 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) 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); + Instance->_cpu->LoadSnapshot(&loadStream, stateVersion); + Instance->_ppu->LoadSnapshot(&loadStream, stateVersion); + Instance->_memoryManager->LoadSnapshot(&loadStream, stateVersion); + Instance->_apu->LoadSnapshot(&loadStream, stateVersion); + Instance->_controlManager->LoadSnapshot(&loadStream, stateVersion); + Instance->_mapper->LoadSnapshot(&loadStream, stateVersion); if(Instance->_hdAudioDevice) { - Instance->_hdAudioDevice->LoadSnapshot(&loadStream); + Instance->_hdAudioDevice->LoadSnapshot(&loadStream, stateVersion); } else { Snapshotable::SkipBlock(&loadStream); } @@ -616,16 +662,16 @@ void Console::LoadState(uint8_t *buffer, uint32_t bufferSize) std::shared_ptr Console::GetDebugger(bool autoStart) { - auto lock = _debuggerLock.AcquireSafe(); - if(!_debugger && autoStart) { - _debugger.reset(new Debugger(Console::Instance, _cpu, _ppu, _apu, _memoryManager, _mapper)); + shared_ptr debugger = _debugger; + if(!debugger && autoStart) { + debugger.reset(new Debugger(Console::Instance, _cpu, _ppu, _apu, _memoryManager, _mapper)); + _debugger = debugger; } - return _debugger; + return debugger; } void Console::StopDebugger() { - auto lock = _debuggerLock.AcquireSafe(); _debugger.reset(); } @@ -634,19 +680,21 @@ void Console::RequestReset() Instance->_resetRequested = true; } -uint32_t Console::GetLagCounter() -{ - return Instance->_lagCounter; -} - std::thread::id Console::GetEmulationThreadId() { return Instance->_emulationThreadId; } +uint32_t Console::GetLagCounter() +{ + return Instance->_controlManager->GetLagCounter(); +} + void Console::ResetLagCounter() { - Instance->_lagCounter = 0; + Console::Pause(); + Instance->_controlManager->ResetLagCounter(); + Console::Reset(); } bool Console::IsDebuggerAttached() @@ -697,7 +745,7 @@ void Console::StartRecordingHdPack(string saveFolder, ScaleFilterType filterType Instance->_memoryManager->UnregisterIODevice(Instance->_ppu.get()); Instance->_ppu.reset(); - Instance->_ppu.reset(new HdBuilderPpu(Instance->_mapper.get(), Instance->_hdPackBuilder.get(), chrRamBankSize)); + Instance->_ppu.reset(new HdBuilderPpu(Instance->_mapper.get(), Instance->_controlManager.get(), Instance->_hdPackBuilder.get(), chrRamBankSize)); Instance->_memoryManager->RegisterIODevice(Instance->_ppu.get()); Instance->LoadState(saveState); @@ -713,7 +761,7 @@ void Console::StopRecordingHdPack() Instance->_memoryManager->UnregisterIODevice(Instance->_ppu.get()); Instance->_ppu.reset(); - Instance->_ppu.reset(new PPU(Instance->_mapper.get())); + Instance->_ppu.reset(new PPU(Instance->_mapper.get(), Instance->_controlManager.get())); Instance->_memoryManager->RegisterIODevice(Instance->_ppu.get()); Instance->_hdPackBuilder.reset(); @@ -721,4 +769,34 @@ void Console::StopRecordingHdPack() Instance->LoadState(saveState); Console::Resume(); } +} + +ConsoleFeatures Console::GetAvailableFeatures() +{ + ConsoleFeatures features = ConsoleFeatures::None; + if(_mapper) { + features = (ConsoleFeatures)((int)features | (int)_mapper->GetAvailableFeatures()); + + if(dynamic_cast(_controlManager.get())) { + features = (ConsoleFeatures)((int)features | (int)ConsoleFeatures::VsSystem); + } + + if(std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort))) { + features = (ConsoleFeatures)((int)features | (int)ConsoleFeatures::BarcodeReader); + } + } + return features; +} + +void Console::InputBarcode(uint64_t barcode, uint32_t digitCount) +{ + shared_ptr barcodeReader = std::dynamic_pointer_cast(_mapper->GetMapperControlDevice()); + if(barcodeReader) { + barcodeReader->InputBarcode(barcode, digitCount); + } + + barcodeReader = std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort)); + if(barcodeReader) { + barcodeReader->InputBarcode(barcode, digitCount); + } } \ No newline at end of file diff --git a/Core/Console.h b/Core/Console.h index 4d23276b..2bf9e143 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -5,6 +5,7 @@ #include "../Utilities/SimpleLock.h" #include "VirtualFile.h" #include "RomData.h" +#include "SaveStateManager.h" class Debugger; class BaseMapper; @@ -17,9 +18,11 @@ class ControlManager; class AutoSaveManager; class HdPackBuilder; class HdAudioDevice; +class SystemActionManager; struct HdPackData; enum class NesModel; enum class ScaleFilterType; +enum class ConsoleFeatures; class Console { @@ -34,11 +37,12 @@ class Console shared_ptr _ppu; shared_ptr _apu; shared_ptr _debugger; - SimpleLock _debuggerLock; shared_ptr _mapper; unique_ptr _controlManager; shared_ptr _memoryManager; + shared_ptr _systemActionManager; + unique_ptr _autoSaveManager; shared_ptr _hdPackBuilder; @@ -55,29 +59,41 @@ class Console bool _disableOcNextFrame = false; atomic _resetRequested; - atomic _lagCounter; bool _initialized = false; std::thread::id _emulationThreadId; void LoadHdPack(VirtualFile &romFile, VirtualFile &patchFile); - void ResetComponents(bool softReset); bool Initialize(VirtualFile &romFile, VirtualFile &patchFile); void UpdateNesModel(bool sendNotification); double GetFrameDelay(); + void SaveBatteries(); + public: Console(); ~Console(); void Run(); void Stop(); + shared_ptr GetSystemActionManager(); + + template + shared_ptr GetSystemActionManager() + { + return std::dynamic_pointer_cast(_systemActionManager); + } + + ConsoleFeatures GetAvailableFeatures(); + void InputBarcode(uint64_t barcode, uint32_t digitCount); + static std::thread::id GetEmulationThreadId(); static void RequestReset(); static void Reset(bool softReset = true); - static void PowerCycle(); + void PowerCycle(); + void ResetComponents(bool softReset); //Used to pause the emu loop to perform thread-safe operations static void Pause(); @@ -85,17 +101,19 @@ class Console //Used to resume the emu loop after calling Pause() static void Resume(); - std::shared_ptr GetDebugger(bool autoStart = true); + shared_ptr GetDebugger(bool autoStart = true); void StopDebugger(); static void SaveState(ostream &saveStream); - static void LoadState(istream &loadStream); + static void LoadState(istream &loadStream, uint32_t stateVersion = SaveStateManager::FileFormatVersion); static void LoadState(uint8_t *buffer, uint32_t bufferSize); static bool LoadROM(VirtualFile romFile, VirtualFile patchFile = {}); static bool LoadROM(string romName, HashInfo hashInfo); + static string FindMatchingRom(string romName, HashInfo hashInfo); static VirtualFile GetRomPath(); static string GetRomName(); + static VirtualFile GetPatchFile(); static bool IsChrRam(); static RomFormat GetRomFormat(); static HashInfo GetHashInfo(); diff --git a/Core/ControlDeviceState.h b/Core/ControlDeviceState.h new file mode 100644 index 00000000..1a4b0e12 --- /dev/null +++ b/Core/ControlDeviceState.h @@ -0,0 +1,13 @@ +#pragma once +#include "stdafx.h" +#include + +struct ControlDeviceState +{ + vector State; + + bool operator!=(ControlDeviceState &other) + { + return State.size() != other.State.size() || memcmp(State.data(), other.State.data(), State.size()) != 0; + } +}; \ No newline at end of file diff --git a/Core/ControlManager.cpp b/Core/ControlManager.cpp index 36a7512e..ed3d54fa 100644 --- a/Core/ControlManager.cpp +++ b/Core/ControlManager.cpp @@ -1,190 +1,217 @@ #include "stdafx.h" #include "ControlManager.h" -#include "StandardController.h" -#include "Zapper.h" -#include "ArkanoidController.h" -#include "OekaKidsTablet.h" +#include "BaseMapper.h" #include "EmulationSettings.h" #include "Console.h" #include "GameServerConnection.h" #include "MemoryManager.h" -#include "PPU.h" #include "IKeyManager.h" +#include "IInputProvider.h" +#include "IInputRecorder.h" +#include "BatteryManager.h" +#include "StandardController.h" +#include "Zapper.h" +#include "ArkanoidController.h" +#include "OekaKidsTablet.h" +#include "FourScore.h" +#include "SnesController.h" +#include "SnesMouse.h" +#include "PowerPad.h" +#include "FamilyMatTrainer.h" +#include "KonamiHyperShot.h" +#include "FamilyBasicKeyboard.h" +#include "FamilyBasicDataRecorder.h" +#include "PartyTap.h" +#include "PachinkoController.h" +#include "ExcitingBoxingController.h" +#include "SuborKeyboard.h" +#include "SuborMouse.h" +#include "JissenMahjongController.h" +#include "BarcodeBattlerReader.h" +#include "HoriTrack.h" +#include "BandaiHyperShot.h" +#include "VsZapper.h" +#include "AsciiTurboFile.h" +#include "BattleBox.h" -unique_ptr ControlManager::_keyManager = nullptr; -shared_ptr ControlManager::_controlDevices[2] = { nullptr, nullptr }; -IGameBroadcaster* ControlManager::_gameBroadcaster = nullptr; -MousePosition ControlManager::_mousePosition = { -1, -1 }; +ControlManager* ControlManager::_instance = nullptr; +vector ControlManager::_inputRecorders; +vector ControlManager::_inputProviders; +SimpleLock ControlManager::_deviceLock; -ControlManager::ControlManager() +ControlManager::ControlManager(shared_ptr systemActionManager, shared_ptr mapperControlDevice) { - + _systemActionManager = systemActionManager; + _mapperControlDevice = mapperControlDevice; + _instance = this; } ControlManager::~ControlManager() { -} - -void ControlManager::RegisterKeyManager(IKeyManager* keyManager) -{ - _keyManager.reset(keyManager); -} - -void ControlManager::RefreshKeyState() -{ - if(_keyManager != nullptr) { - return _keyManager->RefreshState(); + if(_instance == this) { + _instance = nullptr; } } -bool ControlManager::IsKeyPressed(uint32_t keyCode) +void ControlManager::RegisterInputProvider(IInputProvider* provider) { - if(_keyManager != nullptr) { - return _keyManager->IsKeyPressed(keyCode); - } - return false; + auto lock = _deviceLock.AcquireSafe(); + _inputProviders.push_back(provider); } -bool ControlManager::IsMouseButtonPressed(MouseButton button) +void ControlManager::UnregisterInputProvider(IInputProvider* provider) { - if(_keyManager != nullptr) { - return _keyManager->IsMouseButtonPressed(button); - } - return false; + auto lock = _deviceLock.AcquireSafe(); + vector &vec = _inputProviders; + vec.erase(std::remove(vec.begin(), vec.end(), provider), vec.end()); } -vector ControlManager::GetPressedKeys() +void ControlManager::RegisterInputRecorder(IInputRecorder* provider) { - if(_keyManager != nullptr) { - return _keyManager->GetPressedKeys(); - } - return vector(); + auto lock = _deviceLock.AcquireSafe(); + _inputRecorders.push_back(provider); } -string ControlManager::GetKeyName(uint32_t keyCode) +void ControlManager::UnregisterInputRecorder(IInputRecorder* provider) { - if(_keyManager != nullptr) { - return _keyManager->GetKeyName(keyCode); - } - return ""; + auto lock = _deviceLock.AcquireSafe(); + vector &vec = _inputRecorders; + vec.erase(std::remove(vec.begin(), vec.end(), provider), vec.end()); } -uint32_t ControlManager::GetKeyCode(string keyName) +vector ControlManager::GetPortStates() { - if(_keyManager != nullptr) { - return _keyManager->GetKeyCode(keyName); - } - return 0; -} + auto lock = _deviceLock.AcquireSafe(); -void ControlManager::RegisterBroadcaster(IGameBroadcaster* gameBroadcaster) -{ - ControlManager::_gameBroadcaster = gameBroadcaster; -} - -void ControlManager::UnregisterBroadcaster(IGameBroadcaster* gameBroadcaster) -{ - if(ControlManager::_gameBroadcaster == gameBroadcaster) { - ControlManager::_gameBroadcaster = nullptr; - } -} - -void ControlManager::BroadcastInput(uint8_t port, uint8_t state) -{ - if(ControlManager::_gameBroadcaster) { - //Used when acting as a game server - ControlManager::_gameBroadcaster->BroadcastInput(state, port); + vector states; + for(int i = 0; i < 4; i++) { + shared_ptr device = GetControlDevice(i); + if(device) { + states.push_back(device->GetRawState()); + } else { + states.push_back(ControlDeviceState()); + } } + return states; } shared_ptr ControlManager::GetControlDevice(uint8_t port) { - return ControlManager::_controlDevices[port]; + auto lock = _deviceLock.AcquireSafe(); + + auto result = std::find_if(_instance->_controlDevices.begin(), _instance->_controlDevices.end(), [port](const shared_ptr control) { return control->GetPort() == port; }); + if(result != _instance->_controlDevices.end()) { + return *result; + } + return nullptr; } -void ControlManager::RegisterControlDevice(shared_ptr controlDevice, uint8_t port) +void ControlManager::RegisterControlDevice(shared_ptr controlDevice) { - ControlManager::_controlDevices[port] = controlDevice; + _controlDevices.push_back(controlDevice); } -void ControlManager::UnregisterControlDevice(uint8_t port) +ControllerType ControlManager::GetControllerType(uint8_t port) { - ControlManager::_controlDevices[port].reset(); + return EmulationSettings::GetControllerType(port); } -void ControlManager::RefreshAllPorts() +shared_ptr ControlManager::CreateControllerDevice(ControllerType type, uint8_t port) { - if(_keyManager) { - _keyManager->RefreshState(); + shared_ptr device; + + switch(type) { + case ControllerType::StandardController: device.reset(new StandardController(port, EmulationSettings::GetControllerKeys(port))); break; + case ControllerType::Zapper: device.reset(new Zapper(port)); break; + case ControllerType::ArkanoidController: device.reset(new ArkanoidController(port)); break; + case ControllerType::SnesController: device.reset(new SnesController(port, EmulationSettings::GetControllerKeys(port))); break; + case ControllerType::PowerPad: device.reset(new PowerPad(port, EmulationSettings::GetControllerKeys(port))); break; + case ControllerType::SnesMouse: device.reset(new SnesMouse(port)); break; + case ControllerType::SuborMouse: device.reset(new SuborMouse(port)); break; + case ControllerType::VsZapper: device.reset(new VsZapper(port)); break; } - for(int i = 0; i < 2; i++) { - if(ControlManager::_controlDevices[i]) { - ControlManager::_controlDevices[i]->RefreshStateBuffer(); - } - } + return device; } -shared_ptr ControlManager::GetZapper(uint8_t port) +shared_ptr ControlManager::CreateExpansionDevice(ExpansionPortDevice type) { - return shared_ptr(new Zapper(port)); + shared_ptr device; + + switch(type) { + case ExpansionPortDevice::Zapper: device.reset(new Zapper(BaseControlDevice::ExpDevicePort)); break; + case ExpansionPortDevice::ArkanoidController: device.reset(new ArkanoidController(BaseControlDevice::ExpDevicePort)); break; + case ExpansionPortDevice::OekaKidsTablet: device.reset(new OekaKidsTablet()); break; + case ExpansionPortDevice::FamilyTrainerMat: device.reset(new FamilyMatTrainer(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::KonamiHyperShot: device.reset(new KonamiHyperShot(EmulationSettings::GetControllerKeys(0), EmulationSettings::GetControllerKeys(1))); break; + case ExpansionPortDevice::FamilyBasicKeyboard: device.reset(new FamilyBasicKeyboard(EmulationSettings::GetControllerKeys(0))); break; //TODO: tape reader + case ExpansionPortDevice::PartyTap: device.reset(new PartyTap(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::Pachinko: device.reset(new PachinkoController(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::ExcitingBoxing: device.reset(new ExcitingBoxingController(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::JissenMahjong: device.reset(new JissenMahjongController(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::SuborKeyboard: device.reset(new SuborKeyboard(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::BarcodeBattler: device.reset(new BarcodeBattlerReader()); break; + case ExpansionPortDevice::HoriTrack: device.reset(new HoriTrack(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::BandaiHyperShot: device.reset(new BandaiHyperShot(EmulationSettings::GetControllerKeys(0))); break; + case ExpansionPortDevice::AsciiTurboFile: device.reset(new AsciiTurboFile()); break; + case ExpansionPortDevice::BattleBox: device.reset(new BattleBox()); break; + + case ExpansionPortDevice::FourPlayerAdapter: + default: break; + } + + return device; } void ControlManager::UpdateControlDevices() { + auto lock = _deviceLock.AcquireSafe(); + + //Reset update flag + EmulationSettings::NeedControllerUpdate(); + + ControlManager::_controlDevices.clear(); + + ControlManager::RegisterControlDevice(_systemActionManager); + bool fourScore = EmulationSettings::CheckFlag(EmulationFlags::HasFourScore); + ConsoleType consoleType = EmulationSettings::GetConsoleType(); ExpansionPortDevice expansionDevice = EmulationSettings::GetExpansionDevice(); - - if(EmulationSettings::GetConsoleType() != ConsoleType::Famicom) { + + if(consoleType != ConsoleType::Famicom) { expansionDevice = ExpansionPortDevice::None; } else if(expansionDevice != ExpansionPortDevice::FourPlayerAdapter) { fourScore = false; } - for(int i = 0; i < 2; i++) { - shared_ptr device; - bool forceController = - i == 1 && EmulationSettings::GetControllerType(1) != ControllerType::StandardController && - (expansionDevice == ExpansionPortDevice::ArkanoidController || expansionDevice == ExpansionPortDevice::Zapper || expansionDevice == ExpansionPortDevice::OekaKidsTablet); - - bool controllerRequired = forceController || (EmulationSettings::GetConsoleType() == ConsoleType::Famicom && !EmulationSettings::CheckFlag(EmulationFlags::UseNes101Hvc101Behavior)); - - if(fourScore || controllerRequired) { - //Need to set standard controller in all slots if four score (to allow emulation to work correctly) - device.reset(new StandardController(i, forceController)); - } else { - switch(EmulationSettings::GetControllerType(i)) { - case ControllerType::StandardController: device.reset(new StandardController(i)); break; - case ControllerType::Zapper: device = GetZapper(i); break; - case ControllerType::ArkanoidController: device.reset(new ArkanoidController(i)); break; - } - } - + for(int i = 0; i < (fourScore ? 4 : 2); i++) { + shared_ptr device = CreateControllerDevice(GetControllerType(i), i); if(device) { - ControlManager::RegisterControlDevice(device, i); - - if(fourScore) { - if(EmulationSettings::GetControllerType(i + 2) == ControllerType::StandardController) { - std::dynamic_pointer_cast(device)->AddAdditionalController(shared_ptr(new StandardController(i + 2))); - } - } else if(i == 1 || expansionDevice == ExpansionPortDevice::ArkanoidController) { - switch(expansionDevice) { - case ExpansionPortDevice::Zapper: std::dynamic_pointer_cast(device)->AddAdditionalController(shared_ptr(new Zapper(2))); break; - case ExpansionPortDevice::ArkanoidController: std::dynamic_pointer_cast(device)->AddAdditionalController(shared_ptr(new ArkanoidController(2))); break; - case ExpansionPortDevice::OekaKidsTablet: std::dynamic_pointer_cast(device)->AddAdditionalController(shared_ptr(new OekaKidsTablet(2))); break; - default: break; - } - } - - } else { - //Remove current device if it's no longer in use - ControlManager::UnregisterControlDevice(i); + ControlManager::RegisterControlDevice(device); } } + + if(fourScore && consoleType == ConsoleType::Nes) { + //FourScore is only used to provide the signature for reads past the first 16 reads + ControlManager::RegisterControlDevice(shared_ptr(new FourScore())); + } + + shared_ptr expDevice = CreateExpansionDevice(expansionDevice); + if(expDevice) { + ControlManager::RegisterControlDevice(expDevice); + } + + if(_mapperControlDevice) { + ControlManager::RegisterControlDevice(_mapperControlDevice); + } } uint8_t ControlManager::GetOpenBusMask(uint8_t port) { + //"In the NES and Famicom, the top three (or five) bits are not driven, and so retain the bits of the previous byte on the bus. + //Usually this is the most significant byte of the address of the controller port - 0x40. + //Paperboy relies on this behavior and requires that reads from the controller ports return exactly $40 or $41 as appropriate." + switch(EmulationSettings::GetConsoleType()) { default: case ConsoleType::Nes: @@ -203,82 +230,87 @@ uint8_t ControlManager::GetOpenBusMask(uint8_t port) } } -uint8_t ControlManager::GetPortValue(uint8_t port) +void ControlManager::UpdateInputState() { - if(_refreshState) { - //Reload until strobe bit is set to off - RefreshAllPorts(); + if(_isLagging) { + _lagCounter++; + } else { + _isLagging = true; } - shared_ptr device = GetControlDevice(port); - //"In the NES and Famicom, the top three (or five) bits are not driven, and so retain the bits of the previous byte on the bus. - //Usually this is the most significant byte of the address of the controller port - 0x40. - //Paperboy relies on this behavior and requires that reads from the controller ports return exactly $40 or $41 as appropriate." - uint8_t value = MemoryManager::GetOpenBus(GetOpenBusMask(port)); - if(device) { - value |= device->GetPortOutput(); - - if(port == 0 && EmulationSettings::GetConsoleType() == ConsoleType::Famicom) { - //Connect $4016.2 to the 2nd controller's microphone on Famicoms - shared_ptr controller = std::dynamic_pointer_cast(GetControlDevice(1)); - if(controller && controller->IsMicrophoneActive()) { - value |= 0x04; + auto lock = _deviceLock.AcquireSafe(); + + string log = ""; + for(shared_ptr &device : _controlDevices) { + device->ClearState(); + + bool inputSet = false; + for(size_t i = 0; i < _inputProviders.size(); i++) { + IInputProvider* provider = _inputProviders[i]; + if(provider->SetInput(device.get())) { + inputSet = true; + break; } } + + if(!inputSet) { + device->SetStateFromInput(); + } + + device->OnAfterSetState(); + + shared_ptr debugger = Console::GetInstance()->GetDebugger(false); + if(debugger) { + debugger->ProcessEvent(EventType::InputPolled); + } + + log += "|" + device->GetTextState(); + + for(IInputRecorder* recorder : _inputRecorders) { + recorder->RecordInput(device.get()); + } + } + + for(IInputRecorder* recorder : _inputRecorders) { + recorder->EndFrame(); } - return value; + MessageManager::Log(log); +} + +uint32_t ControlManager::GetLagCounter() +{ + return _lagCounter; +} + +void ControlManager::ResetLagCounter() +{ + _lagCounter = 0; } uint8_t ControlManager::ReadRAM(uint16_t addr) { - //Used for lag counter - //Any frame where the input is read does not count as lag + //Used for lag counter - any frame where the input is read does not count as lag _isLagging = false; - switch(addr) { - case 0x4016: return GetPortValue(0); - case 0x4017: return GetPortValue(1); + uint8_t value = MemoryManager::GetOpenBus(GetOpenBusMask(addr - 0x4016)); + for(shared_ptr &device : _controlDevices) { + value |= device->ReadRAM(addr); } + return value; - return 0; -} - -template -shared_ptr ControlManager::GetExpansionDevice() -{ - shared_ptr controller; - controller = std::dynamic_pointer_cast(GetControlDevice(1)); - if(controller) { - shared_ptr expansionDevice; - expansionDevice = std::dynamic_pointer_cast(controller->GetAdditionalController()); - return expansionDevice; - } - return nullptr; } void ControlManager::WriteRAM(uint16_t addr, uint8_t value) { - //$4016 writes - bool previousState = _refreshState; - _refreshState = (value & 0x01) == 0x01; - - auto tablet = GetExpansionDevice(); - if(tablet) { - tablet->WriteRam(value); - } else { - if(previousState && !_refreshState) { - //Refresh controller once strobe bit is disabled - RefreshAllPorts(); - } + for(shared_ptr &device : _controlDevices) { + device->WriteRAM(addr, value); } } void ControlManager::Reset(bool softReset) { - if(_keyManager != nullptr) { - _keyManager->UpdateDevices(); - } + ResetLagCounter(); } void ControlManager::StreamState(bool saving) @@ -304,8 +336,11 @@ void ControlManager::StreamState(bool saving) } } + int32_t unusedMousePositionX = 0; + int32_t unusedMousePositionY = 0; + ArrayInfo types = { controllerTypes, 4 }; - Stream(_refreshState, _mousePosition.X, _mousePosition.Y, nesModel, expansionDevice, consoleType, types, hasFourScore, useNes101Hvc101Behavior, zapperDetectionRadius); + Stream(_refreshState, unusedMousePositionX, unusedMousePositionY, nesModel, expansionDevice, consoleType, types, hasFourScore, useNes101Hvc101Behavior, zapperDetectionRadius, _lagCounter); if(!saving) { EmulationSettings::SetNesModel(nesModel); @@ -322,31 +357,10 @@ void ControlManager::StreamState(bool saving) UpdateControlDevices(); } - SnapshotInfo device0{ GetControlDevice(0).get() }; - SnapshotInfo device1{ GetControlDevice(1).get() }; - Stream(device0, device1); -} - -void ControlManager::SetMousePosition(double x, double y) -{ - if(x < 0 || y < 0) { - _mousePosition.X = -1; - _mousePosition.Y = -1; - } else { - OverscanDimensions overscan = EmulationSettings::GetOverscanDimensions(); - _mousePosition.X = (int32_t)(x * (PPU::ScreenWidth - overscan.Left - overscan.Right) + overscan.Left); - _mousePosition.Y = (int32_t)(y * (PPU::ScreenHeight - overscan.Top - overscan.Bottom) + overscan.Top); + if(GetStateVersion() >= 7) { + for(uint8_t i = 0; i < _controlDevices.size(); i++) { + SnapshotInfo device{ _controlDevices[i].get() }; + Stream(device); + } } } - -MousePosition ControlManager::GetMousePosition() -{ - return _mousePosition; -} - -bool ControlManager::GetLagFlag() -{ - bool flag = _isLagging; - _isLagging = true; - return flag; -} \ No newline at end of file diff --git a/Core/ControlManager.h b/Core/ControlManager.h index dd30a3f8..d3385faa 100644 --- a/Core/ControlManager.h +++ b/Core/ControlManager.h @@ -7,75 +7,68 @@ class BaseControlDevice; class Zapper; -class IGameBroadcaster; -class IKeyManager; -enum class MouseButton; - -struct MousePosition -{ - int32_t X; - int32_t Y; -}; +class SystemActionManager; +class IInputRecorder; +class IInputProvider; +struct ControlDeviceState; +enum class ControllerType; +enum class ExpansionPortDevice; class ControlManager : public Snapshotable, public IMemoryHandler { - private: - static unique_ptr _keyManager; - static shared_ptr _controlDevices[2]; - static IGameBroadcaster* _gameBroadcaster; - static MousePosition _mousePosition; +private: + static ControlManager* _instance; + static vector _inputRecorders; + static vector _inputProviders; + static SimpleLock _deviceLock; - bool _isLagging = false; - bool _refreshState = false; + vector> _controlDevices; - uint8_t GetOpenBusMask(uint8_t port); + shared_ptr _systemActionManager; + shared_ptr _mapperControlDevice; - template shared_ptr GetExpansionDevice(); + uint32_t _lagCounter = 0; + bool _isLagging = false; + bool _refreshState = false; - virtual shared_ptr GetZapper(uint8_t port); + uint8_t GetOpenBusMask(uint8_t port); + void RegisterControlDevice(shared_ptr controlDevice); - static void RegisterControlDevice(shared_ptr controlDevice, uint8_t port); - void UnregisterControlDevice(uint8_t port); +protected: + virtual void StreamState(bool saving) override; + virtual ControllerType GetControllerType(uint8_t port); - protected: - uint8_t GetPortValue(uint8_t port); - virtual void RefreshAllPorts(); +public: - virtual void StreamState(bool saving) override; + ControlManager(shared_ptr systemActionManager, shared_ptr mapperControlDevice); + virtual ~ControlManager(); - public: - ControlManager(); - virtual ~ControlManager(); + void UpdateControlDevices(); + void UpdateInputState(); - void UpdateControlDevices(); - bool GetLagFlag(); + uint32_t GetLagCounter(); + void ResetLagCounter(); - virtual void Reset(bool softReset); - - static void RegisterBroadcaster(IGameBroadcaster* gameBroadcaster); - static void UnregisterBroadcaster(IGameBroadcaster* gameBroadcaster); + virtual void Reset(bool softReset); - static void RegisterKeyManager(IKeyManager* keyManager); - static void RefreshKeyState(); - static bool IsKeyPressed(uint32_t keyCode); - static bool IsMouseButtonPressed(MouseButton button); - static vector GetPressedKeys(); - static string GetKeyName(uint32_t keyCode); - static uint32_t GetKeyCode(string keyName); - - static shared_ptr GetControlDevice(uint8_t port); + static void RegisterInputProvider(IInputProvider* provider); + static void UnregisterInputProvider(IInputProvider* provider); - static void SetMousePosition(double x, double y); - static MousePosition GetMousePosition(); + static void RegisterInputRecorder(IInputRecorder* recorder); + static void UnregisterInputRecorder(IInputRecorder* recorder); - static void BroadcastInput(uint8_t port, uint8_t state); + static vector GetPortStates(); - virtual void GetMemoryRanges(MemoryRanges &ranges) override - { - ranges.AddHandler(MemoryOperation::Read, 0x4016, 0x4017); - ranges.AddHandler(MemoryOperation::Write, 0x4016); - } - - virtual uint8_t ReadRAM(uint16_t addr) override; - virtual void WriteRAM(uint16_t addr, uint8_t value) override; -}; \ No newline at end of file + static shared_ptr GetControlDevice(uint8_t port); + static shared_ptr CreateControllerDevice(ControllerType type, uint8_t port); + static shared_ptr CreateExpansionDevice(ExpansionPortDevice type); + + virtual void GetMemoryRanges(MemoryRanges &ranges) override + { + ranges.AddHandler(MemoryOperation::Read, 0x4016, 0x4017); + ranges.AddHandler(MemoryOperation::Write, 0x4016); + } + + virtual uint8_t ReadRAM(uint16_t addr) override; + virtual void WriteRAM(uint16_t addr, uint8_t value) override; +}; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index c708d660..8e6f25ef 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -411,25 +411,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -473,6 +501,10 @@ + + + + @@ -675,7 +707,6 @@ - @@ -776,6 +807,7 @@ + @@ -788,10 +820,10 @@ - + @@ -799,9 +831,11 @@ + + @@ -840,7 +874,6 @@ - @@ -852,7 +885,6 @@ - @@ -881,9 +913,7 @@ - - diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 33112965..e86eda7f 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -52,9 +52,6 @@ {4a7af167-f6cb-4173-b7ca-04ed7c5858b1} - - {acd315c2-48ad-4243-a997-bb9a970c24bd} - {783f3638-4293-480f-b525-2485c4209ff5} @@ -101,6 +98,21 @@ {e1e8a5d2-aa9a-40e1-94eb-00adec1cdef3} + + {acd315c2-48ad-4243-a997-bb9a970c24bd} + + + {b7a48fc3-5ebb-4aa7-93ae-f70d58bdbfd8} + + + {fdd6ac29-77ce-4db5-8d06-2aa27f95a41c} + + + {38798114-2511-4cce-97a7-c6adac514423} + + + {7b42e684-9487-4031-a769-a05934998777} + @@ -262,9 +274,6 @@ NetPlay - - NetPlay - Nes @@ -505,14 +514,8 @@ VideoDecoder - - Nes\Controllers - - - Nes\Controllers - - Nes\Controllers + Nes\Input NetPlay\Messages @@ -523,9 +526,6 @@ NetPlay\Messages - - Nes\Controllers - Nes\APU\Filters @@ -676,9 +676,6 @@ Nes\Mappers\Unnamed - - Nes\Controllers - Nes\RomLoader @@ -844,9 +841,6 @@ Nes\Mappers - - Nes\Controllers - Nes\Mappers\MMC @@ -1243,9 +1237,123 @@ Nes\RomLoader + + Nes\Mappers\Unnamed + + + Movies + + + Nes\Interfaces + + + Nes\Interfaces + + + Misc + + + Nes\Interfaces + + + Nes\Interfaces + + + Nes\Input + + + Nes + VideoDecoder + + Nes\Input\System + + + Nes\Input\BarcodeReaders + + + Nes\Input\BarcodeReaders + + + Nes\Input\Storage + + + Nes\Input\Storage + + + Nes\Input\System + + + Nes\Input\System + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + + + Nes\Input\Controllers + @@ -1350,17 +1458,8 @@ VideoDecoder - - Nes\Controllers - - - Nes\Controllers - - Nes\Controllers - - - Nes\Controllers + Nes\Input Nes\APU\Filters @@ -1395,18 +1494,12 @@ Nes\RomLoader - - Nes\Controllers - Nes\Mappers VideoDecoder - - Nes\Controllers - Misc @@ -1482,9 +1575,6 @@ HdPacks - - Misc - Debugger\Scripting\DebugHud @@ -1509,9 +1599,21 @@ HdPacks + + HdPacks + + Nes\RomLoader + + + Movies + + Misc + + Nes + VideoDecoder diff --git a/Core/DatachBarcodeReader.h b/Core/DatachBarcodeReader.h new file mode 100644 index 00000000..1f6081ca --- /dev/null +++ b/Core/DatachBarcodeReader.h @@ -0,0 +1,200 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" +#include "IBarcodeReader.h" +#include "CPU.h" + +class DatachBarcodeReader : public BaseControlDevice, public IBarcodeReader +{ +private: + vector _data; + int32_t _insertCycle = 0; + uint64_t _newBarcode = 0; + uint32_t _newBarcodeDigitCount = 0; + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + + uint32_t dataSize = (uint32_t)_data.size(); + Stream(dataSize); + if(!saving) { + _data.resize(dataSize); + } + + ArrayInfo data{ _data.data(), dataSize }; + Stream(_insertCycle, _newBarcode, _newBarcodeDigitCount, data); + } + + bool IsRawString() override + { + return true; + } + +public: + DatachBarcodeReader() : BaseControlDevice(BaseControlDevice::MapperInputPort) + { + } + + void InternalSetStateFromInput() override + { + if(_newBarcodeDigitCount > 0) { + string barcodeText = std::to_string(_newBarcode); + //Pad 8 or 13 character barcode with 0s at start + barcodeText.insert(0, _newBarcodeDigitCount - barcodeText.size(), '0'); + SetTextState(barcodeText); + _newBarcode = 0; + _newBarcodeDigitCount = 0; + } + } + + void OnAfterSetState() override + { + if(GetRawState().State.size() > 0) { + InitBarcodeData(); + } + } + + uint8_t GetOutput() + { + int32_t elapsedCycles = CPU::GetCycleCount() - _insertCycle; + int32_t bitNumber = elapsedCycles / 1000; + if(bitNumber < (int32_t)_data.size()) { + return _data[bitNumber]; + } else { + return 0; + } + } + + void InputBarcode(uint64_t barcode, uint32_t digitCount) + { + _newBarcode = barcode; + _newBarcodeDigitCount = digitCount; + } + + void InitBarcodeData() + { + _insertCycle = CPU::GetCycleCount(); + + static const uint8_t prefixParityType[10][6] = { + { 8,8,8,8,8,8 },{ 8,8,0,8,0,0 }, + { 8,8,0,0,8,0 },{ 8,8,0,0,0,8 }, + { 8,0,8,8,0,0 },{ 8,0,0,8,8,0 }, + { 8,0,0,0,8,8 },{ 8,0,8,0,8,0 }, + { 8,0,8,0,0,8 },{ 8,0,0,8,0,8 } + }; + + static const uint8_t dataLeftOdd[10][7] = { + { 8,8,8,0,0,8,0 },{ 8,8,0,0,8,8,0 }, + { 8,8,0,8,8,0,0 },{ 8,0,0,0,0,8,0 }, + { 8,0,8,8,8,0,0 },{ 8,0,0,8,8,8,0 }, + { 8,0,8,0,0,0,0 },{ 8,0,0,0,8,0,0 }, + { 8,0,0,8,0,0,0 },{ 8,8,8,0,8,0,0 } + }; + + static const uint8_t dataLeftEven[10][7] = { + { 8,0,8,8,0,0,0 },{ 8,0,0,8,8,0,0 }, + { 8,8,0,0,8,0,0 },{ 8,0,8,8,8,8,0 }, + { 8,8,0,0,0,8,0 },{ 8,0,0,0,8,8,0 }, + { 8,8,8,8,0,8,0 },{ 8,8,0,8,8,8,0 }, + { 8,8,8,0,8,8,0 },{ 8,8,0,8,0,0,0 } + }; + + static const uint8_t dataRight[10][7] = { + { 0,0,0,8,8,0,8 },{ 0,0,8,8,0,0,8 }, + { 0,0,8,0,0,8,8 },{ 0,8,8,8,8,0,8 }, + { 0,8,0,0,0,8,8 },{ 0,8,8,0,0,0,8 }, + { 0,8,0,8,8,8,8 },{ 0,8,8,8,0,8,8 }, + { 0,8,8,0,8,8,8 },{ 0,0,0,8,0,8,8 } + }; + + string barcode = GetTextState(); + vector code; + for(uint8_t i = 0; i < barcode.size(); i++) { + code.push_back(barcode[i] - '0'); + } + + _data.clear(); + + for(uint32_t i = 0; i < 33; i++) { + _data.push_back(8); + } + + _data.push_back(0); + _data.push_back(8); + _data.push_back(0); + + uint32_t sum = 0; + + if(barcode.size() == 13) { + for(uint32_t i = 0; i < 6; i++) { + bool odd = prefixParityType[code[0]][i] != 0; + for(uint32_t j = 0; j < 7; j++) { + _data.push_back(odd ? dataLeftOdd[code[i + 1]][j] : dataLeftEven[code[i + 1]][j]); + } + } + + _data.push_back(8); + _data.push_back(0); + _data.push_back(8); + _data.push_back(0); + _data.push_back(8); + + for(uint32_t i = 7; i < 12; i++) { + for(uint32_t j = 0; j < 7; j++) { + _data.push_back(dataRight[code[i]][j]); + } + } + + for(uint32_t i = 0; i < 12; i++) { + sum += (i & 1) ? (code[i] * 3) : (code[i] * 1); + } + } else { + for(uint32_t i = 0; i < 4; i++) { + for(uint32_t j = 0; j < 7; j++) { + _data.push_back(dataLeftOdd[code[i]][j]); + } + } + + _data.push_back(8); + _data.push_back(0); + _data.push_back(8); + _data.push_back(0); + _data.push_back(8); + + for(uint32_t i = 4; i < 7; i++) { + for(uint32_t j = 0; j < 7; j++) { + _data.push_back(dataRight[code[i]][j]); + } + } + + for(uint32_t i = 0; i < 7; i++) { + sum += (i & 1) ? (code[i] * 1) : (code[i] * 3); + } + } + + sum = (10 - (sum % 10)) % 10; + + for(uint32_t i = 0; i < 7; i++) { + _data.push_back(dataRight[sum][i]); + } + + _data.push_back(0); + _data.push_back(8); + _data.push_back(0); + + for(uint32_t i = 0; i < 32; i++) { + _data.push_back(8); + } + } + + uint8_t ReadRAM(uint16_t addr) + { + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) + { + } +}; \ No newline at end of file diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index e7713a00..47fc5284 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -24,6 +24,7 @@ #include "DebugBreakHelper.h" #include "ScriptHost.h" #include "DebugHud.h" +#include "StandardController.h" Debugger* Debugger::Instance = nullptr; const int Debugger::BreakpointTypeCount; @@ -1025,22 +1026,6 @@ int32_t Debugger::FindSubEntryPoint(uint16_t relativeAddress) return address > relativeAddress ? relativeAddress : (address + 1); } -bool Debugger::HasInputOverride(uint8_t port) -{ - if(Debugger::Instance) { - return Debugger::Instance->_inputOverride[port] != 0; - } - return false; -} - -uint32_t Debugger::GetInputOverride(uint8_t port) -{ - if(Debugger::Instance) { - return Debugger::Instance->_inputOverride[port]; - } - return 0; -} - void Debugger::SetInputOverride(uint8_t port, uint32_t state) { _inputOverride[port] = state; @@ -1143,4 +1128,22 @@ void Debugger::ProcessEvent(EventType type) script->ProcessEvent(type); } } + + if(type == EventType::InputPolled) { + for(int i = 0; i < 4; i++) { + if(_inputOverride[i] != 0) { + shared_ptr controller = std::dynamic_pointer_cast(ControlManager::GetControlDevice(i)); + if(controller) { + controller->SetBitValue(StandardController::Buttons::A, (_inputOverride[i] & 0x01) != 0); + controller->SetBitValue(StandardController::Buttons::B, (_inputOverride[i] & 0x02) != 0); + controller->SetBitValue(StandardController::Buttons::Select, (_inputOverride[i] & 0x04) != 0); + controller->SetBitValue(StandardController::Buttons::Start, (_inputOverride[i] & 0x08) != 0); + controller->SetBitValue(StandardController::Buttons::Up, (_inputOverride[i] & 0x10) != 0); + controller->SetBitValue(StandardController::Buttons::Down, (_inputOverride[i] & 0x20) != 0); + controller->SetBitValue(StandardController::Buttons::Left, (_inputOverride[i] & 0x40) != 0); + controller->SetBitValue(StandardController::Buttons::Right, (_inputOverride[i] & 0x80) != 0); + } + } + } + } } diff --git a/Core/DebuggerTypes.h b/Core/DebuggerTypes.h index 40cc9f8e..b91833cc 100644 --- a/Core/DebuggerTypes.h +++ b/Core/DebuggerTypes.h @@ -87,4 +87,6 @@ enum class EventType CodeBreak = 5, StateLoaded = 6, StateSaved = 7, + InputPolled = 8, + EventTypeSize }; \ No newline at end of file diff --git a/Core/Edu2000.h b/Core/Edu2000.h index c5a843ae..59491562 100644 --- a/Core/Edu2000.h +++ b/Core/Edu2000.h @@ -8,7 +8,7 @@ private: uint8_t _reg; protected: - uint16_t GetPRGPageSize() override { return 0x2000; } + uint16_t GetPRGPageSize() override { return 0x8000; } uint16_t GetCHRPageSize() override { return 0x2000; } uint32_t GetWorkRamSize() override { return 0x8000; } uint32_t GetWorkRamPageSize() override { return 0x2000; } @@ -31,7 +31,7 @@ protected: void UpdatePrg() { - SelectPrgPage4x(0, (_reg & 0x1F) << 2); + SelectPRGPage(0, _reg & 0x1F); SetCpuMemoryMapping(0x6000, 0x7FFF, (_reg >> 6) & 0x03, PrgMemoryType::WorkRam); } diff --git a/Core/EmulationSettings.cpp b/Core/EmulationSettings.cpp index 460fe22d..d12d480e 100644 --- a/Core/EmulationSettings.cpp +++ b/Core/EmulationSettings.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" #include "EmulationSettings.h" #include "Console.h" -#include "VsControlManager.h" #include "RewindManager.h" //Version 0.9.3 @@ -62,6 +61,7 @@ std::unordered_map EmulationSettings::_emulatorKeys[2] std::unordered_map> EmulationSettings::_shortcutSupersets[2]; RamPowerOnState EmulationSettings::_ramPowerOnState = RamPowerOnState::AllZeros; +uint32_t EmulationSettings::_dipSwitches = 0; InputDisplaySettings EmulationSettings::_inputDisplaySettings = { 0, InputDisplayPosition::TopLeft, false }; @@ -69,7 +69,7 @@ OverscanDimensions EmulationSettings::_overscan; VideoFilterType EmulationSettings::_videoFilterType = VideoFilterType::None; VideoResizeFilter EmulationSettings::_resizeFilter = VideoResizeFilter::NearestNeighbor; double EmulationSettings::_videoScale = 1; -VideoAspectRatio EmulationSettings::_aspectRatio = VideoAspectRatio::Auto; +VideoAspectRatio EmulationSettings::_aspectRatio = VideoAspectRatio::NoStretching; double EmulationSettings::_customAspectRatio = 1.0; PictureSettings EmulationSettings::_pictureSettings; NtscFilterSettings EmulationSettings::_ntscFilterSettings; @@ -83,6 +83,8 @@ ControllerType EmulationSettings::_controllerTypes[4] = { ControllerType::None, KeyMappingSet EmulationSettings::_controllerKeys[4] = { KeyMappingSet(), KeyMappingSet(), KeyMappingSet(), KeyMappingSet() }; bool EmulationSettings::_needControllerUpdate = false; uint32_t EmulationSettings::_zapperDetectionRadius = 0; +double EmulationSettings::_mouseSensitivity = 1.0; +int32_t EmulationSettings::_inputPollScanline = 240; uint32_t EmulationSettings::_defaultPpuPalette[64] = { /* 2C02 */ 0xFF666666, 0xFF002A88, 0xFF1412A7, 0xFF3B00A4, 0xFF5C007E, 0xFF6E0040, 0xFF6C0600, 0xFF561D00, 0xFF333500, 0xFF0B4800, 0xFF005200, 0xFF004F08, 0xFF00404D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFADADAD, 0xFF155FD9, 0xFF4240FF, 0xFF7527FE, 0xFFA01ACC, 0xFFB71E7B, 0xFFB53120, 0xFF994E00, 0xFF6B6D00, 0xFF388700, 0xFF0C9300, 0xFF008F32, 0xFF007C8D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFF64B0FF, 0xFF9290FF, 0xFFC676FF, 0xFFF36AFF, 0xFFFE6ECC, 0xFFFE8170, 0xFFEA9E22, 0xFFBCBE00, 0xFF88D800, 0xFF5CE430, 0xFF45E082, 0xFF48CDDE, 0xFF4F4F4F, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFFC0DFFF, 0xFFD3D2FF, 0xFFE8C8FF, 0xFFFBC2FF, 0xFFFEC4EA, 0xFFFECCC5, 0xFFF7D8A5, 0xFFE4E594, 0xFFCFEF96, 0xFFBDF4AB, 0xFFB3F3CC, 0xFFB5EBF2, 0xFFB8B8B8, 0xFF000000, 0xFF000000 }; @@ -143,3 +145,62 @@ double EmulationSettings::GetAspectRatio() } return 0.0; } + +const vector NesModelNames = { + "Auto", + "NTSC", + "PAL", + "Dendy" +}; + +const vector ConsoleTypeNames = { + "Nes", + "Famicom", +}; + +const vector ControllerTypeNames = { + "None", + "StandardController", + "Zapper", + "ArkanoidController", + "SnesController", + "PowerPad", + "SnesMouse", + "SuborMouse", + "VsZapper" +}; + +const vector ExpansionPortDeviceNames = { + "None", + "Zapper", + "FourPlayerAdapter", + "ArkanoidController", + "OekaKidsTablet", + "FamilyTrainerMat", + "KonamiHyperShot", + "FamilyBasicKeyboard", + "PartyTap", + "Pachinko", + "ExcitingBoxing", + "JissenMahjong", + "SuborKeyboard", + "BarcodeBattler", + "HoriTrack", + "BandaiHyperShot", + "AsciiTurboFile", + "BattleBox", +}; + +const vector PpuModelNames = { + "Ppu2C02", + "Ppu2C03", + "Ppu2C04A", + "Ppu2C04B", + "Ppu2C04C", + "Ppu2C04D", + "Ppu2C05A", + "Ppu2C05B", + "Ppu2C05C", + "Ppu2C05D", + "Ppu2C05E" +}; \ No newline at end of file diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index c459f752..0b509bd1 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -103,21 +103,6 @@ enum class EqualizerFilterType Chebyshev2 = 3 }; -enum class NesModel -{ - Auto = 0, - NTSC = 1, - PAL = 2, - Dendy = 3, -}; - -enum class RamPowerOnState -{ - AllZeros = 0, - AllOnes = 1, - Random = 2 -}; - enum class ScaleFilterType { xBRZ = 0, @@ -223,20 +208,44 @@ struct NtscFilterSettings double QFilterLength = 0; }; +enum class RamPowerOnState +{ + AllZeros = 0, + AllOnes = 1, + Random = 2 +}; + +extern const vector NesModelNames; +enum class NesModel +{ + Auto = 0, + NTSC = 1, + PAL = 2, + Dendy = 3, +}; + +extern const vector ConsoleTypeNames; enum class ConsoleType { Nes = 0, Famicom = 1 }; +extern const vector ControllerTypeNames; enum class ControllerType { None = 0, StandardController = 1, Zapper = 2, ArkanoidController = 3, + SnesController = 4, + PowerPad = 5, + SnesMouse = 6, + SuborMouse = 7, + VsZapper = 8 }; +extern const vector ExpansionPortDeviceNames; enum class ExpansionPortDevice { None = 0, @@ -244,6 +253,35 @@ enum class ExpansionPortDevice FourPlayerAdapter = 2, ArkanoidController = 3, OekaKidsTablet = 4, + FamilyTrainerMat = 5, + KonamiHyperShot = 6, + FamilyBasicKeyboard = 7, + PartyTap = 8, + Pachinko = 9, + ExcitingBoxing = 10, + JissenMahjong = 11, + SuborKeyboard = 12, + BarcodeBattler = 13, + HoriTrack = 14, + BandaiHyperShot = 15, + AsciiTurboFile = 16, + BattleBox = 17, +}; + +extern const vector PpuModelNames; +enum class PpuModel +{ + Ppu2C02 = 0, + Ppu2C03 = 1, + Ppu2C04A = 2, + Ppu2C04B = 3, + Ppu2C04C = 4, + Ppu2C04D = 5, + Ppu2C05A = 6, + Ppu2C05B = 7, + Ppu2C05C = 8, + Ppu2C05D = 9, + Ppu2C05E = 10 }; struct KeyMapping @@ -261,10 +299,20 @@ struct KeyMapping uint32_t TurboStart = 0; uint32_t TurboSelect = 0; uint32_t Microphone = 0; + uint32_t LButton = 0; + uint32_t RButton = 0; + + uint32_t PowerPadButtons[12] = {}; + uint32_t FamilyBasicKeyboardButtons[72] = {}; + uint32_t PartyTapButtons[6] = {}; + uint32_t PachinkoButtons[2] = {}; + uint32_t ExcitingBoxingButtons[8] = {}; + uint32_t JissenMahjongButtons[21] = {}; + uint32_t SuborKeyboardButtons[99] = {}; bool HasKeySet() { - return A || B || Up || Down || Left || Right || Start || Select || TurboA || TurboB || TurboStart || TurboSelect || Microphone; + return true || A || B || Up || Down || Left || Right || Start || Select || TurboA || TurboB || TurboStart || TurboSelect || Microphone || LButton || RButton; } }; @@ -274,7 +322,25 @@ struct KeyMappingSet KeyMapping Mapping2; KeyMapping Mapping3; KeyMapping Mapping4; - uint32_t TurboSpeed; + uint32_t TurboSpeed = 0; + + vector GetKeyMappingArray() + { + vector keyMappings; + if(Mapping1.HasKeySet()) { + keyMappings.push_back(Mapping1); + } + if(Mapping2.HasKeySet()) { + keyMappings.push_back(Mapping2); + } + if(Mapping3.HasKeySet()) { + keyMappings.push_back(Mapping3); + } + if(Mapping4.HasKeySet()) { + keyMappings.push_back(Mapping4); + } + return keyMappings; + } }; enum class EmulatorShortcut @@ -305,6 +371,8 @@ enum class EmulatorShortcut InsertCoin1, InsertCoin2, + + InputBarcode, TakeScreenshot, @@ -421,21 +489,6 @@ enum class StereoFilter Panning = 2, }; -enum class PpuModel -{ - Ppu2C02 = 0, - Ppu2C03 = 1, - Ppu2C04A = 2, - Ppu2C04B = 3, - Ppu2C04C = 4, - Ppu2C04D = 5, - Ppu2C05A = 6, - Ppu2C05B = 7, - Ppu2C05C = 8, - Ppu2C05D = 9, - Ppu2C05E = 10 -}; - enum class InputDisplayPosition { TopLeft = 0, @@ -521,6 +574,8 @@ private: static KeyMappingSet _controllerKeys[4]; static bool _needControllerUpdate; static uint32_t _zapperDetectionRadius; + static double _mouseSensitivity; + static int32_t _inputPollScanline; static int32_t _nsfAutoDetectSilenceDelay; static int32_t _nsfMoveToNextTrackTime; @@ -535,6 +590,7 @@ private: static std::unordered_map> _shortcutSupersets[2]; static RamPowerOnState _ramPowerOnState; + static uint32_t _dipSwitches; static SimpleLock _shortcutLock; static SimpleLock _equalizerLock; @@ -610,6 +666,11 @@ public: return (CheckFlag(EmulationFlags::Paused) || (CheckFlag(EmulationFlags::InBackground) && CheckFlag(EmulationFlags::PauseWhenInBackground) && !GameClient::Connected())) && !CheckFlag(EmulationFlags::DebuggerWindowEnabled); } + static bool InputEnabled() + { + return !EmulationSettings::CheckFlag(EmulationFlags::InBackground) || EmulationSettings::CheckFlag(EmulationFlags::AllowBackgroundInput); + } + static void SetNesModel(NesModel model) { _model = model; @@ -1172,6 +1233,16 @@ public: } } + static void SetMouseSensitivity(double sensitivity) + { + _mouseSensitivity = sensitivity; + } + + static double GetMouseSensitivity() + { + return _mouseSensitivity; + } + static void SetZapperDetectionRadius(uint32_t detectionRadius) { _zapperDetectionRadius = detectionRadius; @@ -1182,6 +1253,16 @@ public: return _zapperDetectionRadius; } + static void SetInputPollScanline(int32_t scanline) + { + _inputPollScanline = scanline; + } + + static int32_t GetInputPollScanline() + { + return _inputPollScanline; + } + static bool HasZapper() { return _controllerTypes[0] == ControllerType::Zapper || _controllerTypes[1] == ControllerType::Zapper || (_consoleType == ConsoleType::Famicom && _expansionDevice == ExpansionPortDevice::Zapper); @@ -1245,4 +1326,14 @@ public: showMessage = _autoSaveNotify; return _autoSaveDelay; } + + static void SetDipSwitches(uint32_t dipSwitches) + { + _dipSwitches = dipSwitches; + } + + static uint32_t GetDipSwitches() + { + return _dipSwitches; + } }; diff --git a/Core/ExcitingBoxingController.h b/Core/ExcitingBoxingController.h new file mode 100644 index 00000000..98fda6af --- /dev/null +++ b/Core/ExcitingBoxingController.h @@ -0,0 +1,61 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class ExcitingBoxingController : public BaseControlDevice +{ +private: + uint8_t _selectedSensors = 0; + enum Buttons { LeftHook = 0, MoveRight, MoveLeft, RightHook, LeftJab, HitBody, RightJab, Straight }; + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_selectedSensors); + } + + string GetKeyNames() override + { + return "HRLhJBjS"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + for(int i = 0; i < 8; i++) { + SetPressedState(i, keyMapping.ExcitingBoxingButtons[i]); + } + } + } + +public: + ExcitingBoxingController(KeyMappingSet keyMappings) : BaseControlDevice(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + if(_selectedSensors == 0) { + return + (IsPressed(ExcitingBoxingController::Buttons::LeftHook) ? 0 : 0x02) | + (IsPressed(ExcitingBoxingController::Buttons::MoveRight) ? 0 : 0x04) | + (IsPressed(ExcitingBoxingController::Buttons::MoveLeft) ? 0 : 0x08) | + (IsPressed(ExcitingBoxingController::Buttons::RightHook) ? 0 : 0x10); + } else { + return + (IsPressed(ExcitingBoxingController::Buttons::LeftJab) ? 0 : 0x02) | + (IsPressed(ExcitingBoxingController::Buttons::HitBody) ? 0 : 0x04) | + (IsPressed(ExcitingBoxingController::Buttons::RightJab) ? 0 : 0x08) | + (IsPressed(ExcitingBoxingController::Buttons::Straight) ? 0 : 0x10); + } + } + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + _selectedSensors = (value & 0x02) >> 1; + } +}; \ No newline at end of file diff --git a/Core/FDS.cpp b/Core/FDS.cpp index 4f70a1b5..a984f947 100644 --- a/Core/FDS.cpp +++ b/Core/FDS.cpp @@ -1,16 +1,14 @@ #include "stdafx.h" +#include "../Utilities/IpsPatcher.h" +#include "Console.h" #include "FDS.h" #include "CPU.h" #include "FdsAudio.h" #include "MemoryManager.h" - -FDS* FDS::Instance = nullptr; -bool FDS::_disableAutoInsertDisk = false; +#include "BatteryManager.h" void FDS::InitMapper() { - _newDiskNumber = (IsAutoInsertDiskEnabled() || EmulationSettings::CheckFlag(EmulationFlags::FdsAutoLoadDisk)) ? 0 : FDS::NoDiskInserted; - //FDS BIOS SetCpuMemoryMapping(0xE000, 0xFFFF, 0, PrgMemoryType::PrgRom, MemoryAccessType::Read); @@ -27,6 +25,38 @@ void FDS::InitMapper(RomData &romData) _fdsDiskSides = romData.FdsDiskData; _fdsDiskHeaders = romData.FdsDiskHeaders; _fdsRawData = romData.RawData; + + //Apply save data (saved as an IPS file), if found + vector ipsData = BatteryManager::LoadBattery(".ips"); + LoadDiskData(ipsData); +} + +void FDS::LoadDiskData(vector ipsData) +{ + _fdsDiskSides.clear(); + _fdsDiskHeaders.clear(); + + FdsLoader loader; + vector patchedData; + if(ipsData.size() > 0 && IpsPatcher::PatchBuffer(ipsData, _fdsRawData, patchedData)) { + loader.LoadDiskData(patchedData, _fdsDiskSides, _fdsDiskHeaders); + } else { + loader.LoadDiskData(_fdsRawData, _fdsDiskSides, _fdsDiskHeaders); + } +} + +vector FDS::CreateIpsPatch() +{ + FdsLoader loader; + bool needHeader = (memcmp(_fdsRawData.data(), "FDS\x1a", 4) == 0); + vector newData = loader.RebuildFdsFile(_fdsDiskSides, needHeader); + return IpsPatcher::CreatePatch(_fdsRawData, newData); +} + +void FDS::SaveBattery() +{ + vector ipsData = CreateIpsPatch(); + BatteryManager::SaveBattery(".ips", ipsData.data(), (uint32_t)ipsData.size()); } void FDS::Reset(bool softReset) @@ -54,9 +84,6 @@ void FDS::WriteFdsDisk(uint8_t value) { assert(_diskNumber < _fdsDiskSides.size()); assert(_diskPosition < _fdsDiskSides[_diskNumber].size()); - if(_fdsDiskSides[_diskNumber][_diskPosition - 2] != value) { - _isDirty = true; - } _fdsDiskSides[_diskNumber][_diskPosition - 2] = value; } @@ -75,11 +102,6 @@ void FDS::ClockIrq() } } -bool FDS::IsAutoInsertDiskEnabled() -{ - return !_disableAutoInsertDisk && EmulationSettings::CheckFlag(EmulationFlags::FdsAutoInsertDisk); -} - uint8_t FDS::ReadRAM(uint16_t addr) { if(addr == 0xE18C && !_gameStarted && (CPU::DebugReadByte(0x100) & 0xC0) != 0) { @@ -124,7 +146,6 @@ uint8_t FDS::ReadRAM(uint16_t addr) if(matchIndex >= 0) { //Found a single match, insert it - _newDiskNumber = matchIndex; _diskNumber = matchIndex; if(matchIndex > 0) { //Make sure we disable fast forward @@ -177,14 +198,6 @@ void FDS::ProcessCpuClock() ClockIrq(); _audio->Clock(); - if(_newDiskInsertDelay > 0) { - //Insert new disk after delay expires, to allow games to notice the disk was ejected - _newDiskInsertDelay--; - _diskNumber = FDS::NoDiskInserted; - } else { - _diskNumber = _newDiskNumber; - } - if(_diskNumber == FDS::NoDiskInserted || !_motorOn) { //Disk has been ejected _endOfHead = true; @@ -366,11 +379,6 @@ void FDS::WriteRegister(uint16_t addr, uint8_t value) } } -bool FDS::IsDiskInserted() -{ - return _diskNumber != FDS::NoDiskInserted; -} - uint8_t FDS::ReadRegister(uint16_t addr) { uint8_t value = MemoryManager::GetOpenBus(); @@ -411,7 +419,6 @@ uint8_t FDS::ReadRegister(uint16_t addr) //Eject the current disk and insert a new one in 300k cycles (~10 frames) _autoDiskSwitchCounter = 300000; _diskNumber = NoDiskInserted; - _newDiskNumber = NoDiskInserted; } return value; @@ -429,14 +436,35 @@ void FDS::StreamState(bool saving) BaseMapper::StreamState(saving); bool unusedNeedIrq = false; + uint32_t unusedNewDiskNumber = 0; + uint32_t unusedNewDiskInsertDelay = 0; + bool unusedIsDirty = false; SnapshotInfo audio{ _audio.get() }; + Stream(_irqReloadValue, _irqCounter, _irqEnabled, _irqRepeatEnabled, _diskRegEnabled, _soundRegEnabled, _writeDataReg, _motorOn, _resetTransfer, _readMode, _crcControl, _diskReady, _diskIrqEnabled, _extConWriteReg, _badCrc, _endOfHead, _readWriteEnabled, _readDataReg, _diskWriteProtected, - _diskNumber, _newDiskNumber, _newDiskInsertDelay, _diskPosition, _delay, _previousCrcControlFlag, _gapEnded, _scanningDisk, unusedNeedIrq, - _transferComplete, _isDirty, audio); + _diskNumber, unusedNewDiskNumber, unusedNewDiskInsertDelay, _diskPosition, _delay, _previousCrcControlFlag, _gapEnded, _scanningDisk, unusedNeedIrq, + _transferComplete, unusedIsDirty, audio); + + if(saving) { + vector ipsData = CreateIpsPatch(); + uint32_t size = (uint32_t)ipsData.size(); + Stream(size); + + ArrayInfo data{ ipsData.data(), (uint32_t)ipsData.size() }; + Stream(data); + } else { + uint32_t size = 0; + Stream(size); + if(size > 0) { + vector ipsData(size, 0); + ArrayInfo data{ ipsData.data(), (uint32_t)ipsData.size() }; + Stream(data); + + LoadDiskData(ipsData); + } - if(!saving) { //Make sure we disable fast forwarding when loading a state //Otherwise it's possible to get stuck in fast forward mode _gameStarted = true; @@ -445,8 +473,6 @@ void FDS::StreamState(bool saving) FDS::FDS() { - FDS::Instance = this; - _audio.reset(new FdsAudio()); } @@ -454,68 +480,41 @@ FDS::~FDS() { //Restore emulation speed to normal when closing EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed); - - if(FDS::Instance == this) { - FDS::Instance = nullptr; - } } -void FDS::SaveBattery() +ConsoleFeatures FDS::GetAvailableFeatures() { - if(_isDirty) { - FdsLoader loader; - loader.SaveIpsFile(_romFilepath, _fdsRawData, _fdsDiskSides); - } + return ConsoleFeatures::Fds; } uint32_t FDS::GetSideCount() { - if(FDS::Instance) { - return (uint32_t)FDS::Instance->_fdsDiskSides.size(); - } else { - return 0; - } -} - -void FDS::InsertDisk(uint32_t diskNumber) -{ - if(FDS::Instance) { - Console::Pause(); - FDS::Instance->_newDiskNumber = diskNumber; - FDS::Instance->_newDiskInsertDelay = FDS::DiskInsertDelay; - Console::Resume(); - - MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged); - } -} - -void FDS::InsertNextDisk() -{ - InsertDisk(((FDS::Instance->_diskNumber & 0xFE) + 2) % GetSideCount()); -} - -void FDS::SwitchDiskSide() -{ - if(FDS::Instance) { - Console::Pause(); - if(FDS::Instance->_newDiskInsertDelay == 0 && FDS::Instance->_diskNumber != NoDiskInserted) { - FDS::Instance->_newDiskNumber = (FDS::Instance->_diskNumber & 0x01) ? (FDS::Instance->_diskNumber & 0xFE) : (FDS::Instance->_diskNumber | 0x01); - FDS::Instance->_newDiskInsertDelay = FDS::DiskInsertDelay; - } - Console::Resume(); - - MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged); - } + return (uint32_t)_fdsDiskSides.size(); } void FDS::EjectDisk() { - if(FDS::Instance) { - Console::Pause(); - FDS::Instance->_newDiskNumber = NoDiskInserted; - FDS::Instance->_newDiskInsertDelay = 0; - Console::Resume(); + _diskNumber = FDS::NoDiskInserted; +} - MessageManager::SendNotification(ConsoleNotificationType::FdsDiskChanged); +void FDS::InsertDisk(uint32_t diskNumber) +{ + if(_diskNumber == FDS::NoDiskInserted) { + _diskNumber = diskNumber; } } + +uint32_t FDS::GetCurrentDisk() +{ + return _diskNumber; +} + +bool FDS::IsDiskInserted() +{ + return _diskNumber != FDS::NoDiskInserted; +} + +bool FDS::IsAutoInsertDiskEnabled() +{ + return !_disableAutoInsertDisk && EmulationSettings::CheckFlag(EmulationFlags::FdsAutoInsertDisk); +} \ No newline at end of file diff --git a/Core/FDS.h b/Core/FDS.h index 8356a4e6..6d6c4c0c 100644 --- a/Core/FDS.h +++ b/Core/FDS.h @@ -13,10 +13,7 @@ class FDS : public BaseMapper { private: static const uint32_t NoDiskInserted = 0xFF; - static const uint32_t DiskInsertDelay = 3600000; //approx 2 sec delay - static bool _disableAutoInsertDisk; - - static FDS* Instance; + bool _disableAutoInsertDisk; unique_ptr _audio; @@ -53,9 +50,7 @@ private: bool _diskWriteProtected = false; //Internal values - uint32_t _diskNumber = 0; - uint32_t _newDiskNumber = 0; - uint32_t _newDiskInsertDelay = 0; + uint32_t _diskNumber = FDS::NoDiskInserted; uint32_t _diskPosition = 0; uint32_t _delay = 0; uint16_t _crcAccumulator; @@ -63,7 +58,6 @@ private: bool _gapEnded = true; bool _scanningDisk = false; bool _transferComplete = false; - bool _isDirty = false; vector _fdsRawData; vector> _fdsDiskSides; @@ -83,6 +77,8 @@ protected: void InitMapper() override; void InitMapper(RomData &romData) override; + void LoadDiskData(vector ipsData = vector()); + vector CreateIpsPatch(); void Reset(bool softReset) override; uint32_t GetFdsDiskSideSize(uint8_t side); @@ -94,8 +90,6 @@ protected: void ProcessCpuClock() override; void UpdateCrc(uint8_t value); - bool IsDiskInserted(); - void WriteRegister(uint16_t addr, uint8_t value) override; uint8_t ReadRegister(uint16_t addr) override; @@ -108,12 +102,14 @@ public: ~FDS(); void SaveBattery() override; + ConsoleFeatures GetAvailableFeatures() override; - static uint32_t GetSideCount(); + uint32_t GetSideCount(); - static void InsertDisk(uint32_t diskNumber); - static void InsertNextDisk(); - static void SwitchDiskSide(); - static void EjectDisk(); - static bool IsAutoInsertDiskEnabled(); + void EjectDisk(); + void InsertDisk(uint32_t diskNumber); + uint32_t GetCurrentDisk(); + bool IsDiskInserted(); + + bool IsAutoInsertDiskEnabled(); }; \ No newline at end of file diff --git a/Core/FamilyBasicDataRecorder.h b/Core/FamilyBasicDataRecorder.h new file mode 100644 index 00000000..bffc0798 --- /dev/null +++ b/Core/FamilyBasicDataRecorder.h @@ -0,0 +1,74 @@ +#pragma once +#include "stdafx.h" +#include +#include "BaseControlDevice.h" +#include "CPU.h" + +//TODO: Integration with UI +class FamilyBasicDataRecorder : public BaseControlDevice +{ +private: + const uint32_t SamplingRate = 88; + vector _saveData; + bool _enabled = false; + int32_t _lastCycle = -1; + int32_t _readStartCycle = -1; + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_enabled); + } + +public: + FamilyBasicDataRecorder() : BaseControlDevice(BaseControlDevice::ExpDevicePort) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4016) { + if(EmulationSettings::CheckFlag(EmulationFlags::ShowFrameCounter)) { + if(_readStartCycle == -1) { + _readStartCycle = CPU::GetCycleCount(); + } + + int readPos = (CPU::GetCycleCount() - _readStartCycle) / 88; + + if((int32_t)_saveData.size() > readPos) { + uint8_t value = (_saveData[readPos] & 0x01) << 1; + return _enabled ? value : 0; + } + } else { + if(!EmulationSettings::CheckFlag(EmulationFlags::ShowFPS)) { + _lastCycle = -1; + _readStartCycle = -1; + } + } + } + + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + _enabled = (value & 0x04) != 0; + + if(EmulationSettings::CheckFlag(EmulationFlags::ShowFPS)) { + if(_lastCycle == -1) { + _saveData.clear(); + _lastCycle = CPU::GetCycleCount() - 88; + } + while(_lastCycle < CPU::GetCycleCount()) { + _saveData.push_back(value & 0x01); + _lastCycle += 88; + } + } else { + if(!EmulationSettings::CheckFlag(EmulationFlags::ShowFrameCounter)) { + _lastCycle = -1; + _readStartCycle = -1; + } + } + } +}; \ No newline at end of file diff --git a/Core/FamilyBasicKeyboard.h b/Core/FamilyBasicKeyboard.h new file mode 100644 index 00000000..64ac7fbc --- /dev/null +++ b/Core/FamilyBasicKeyboard.h @@ -0,0 +1,117 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class FamilyBasicKeyboard : public BaseControlDevice +{ +private: + uint8_t _row = 0; + uint8_t _column = 0; + bool _enabled = false; + + const uint32_t _keyMatrix[72] = { + F8, Return, RightBracket, LeftBracket, + Kana, RightShift, Yen, Stop, + F7, AtSign, Colon, SemiColon, + Underscore, Slash, Minus, Caret, + F6, O, L, K, + Dot, Comma, P, Num0, + F5, I, U, J, + M, N, Num9, Num8, + F4, Y, G, H, + B, V, Num7, Num6, + F3, T, R, D, + F, C, Num5, Num4, + F2, W, S, A, + X, Z, E, Num3, + F1, Esc, Q, Ctrl, + LeftShift, Grph, Num1, Num2, + ClrHome, Up, Right, Left, + Down, Space, Del, Ins + }; + + enum Buttons + { + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, + Return, Space, Del, Ins, Esc, + Ctrl, RightShift, LeftShift, + RightBracket, LeftBracket, + Up, Down, Left, Right, + Dot, Comma, Colon, SemiColon, Underscore, Slash, Minus, Caret, + F1, F2, F3, F4, F5, F6, F7, F8, + Yen, Stop, AtSign, Grph, ClrHome, Kana + }; + + string GetKeyNames() override + { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456798rsdiecSs[]udlrd,:;_/-^12345678ysagck"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + for(int i = 0; i < 72; i++) { + SetPressedState(i, keyMapping.FamilyBasicKeyboardButtons[i]); + } + } + } + + uint8_t GetActiveKeys(uint8_t row, uint8_t column) + { + if(row == 9) { + //10th row has no keys + return 0; + } + + uint8_t result = 0; + uint32_t baseIndex = row * 8 + (column ? 4 : 0); + for(int i = 0; i < 4; i++) { + if(IsPressed(_keyMatrix[baseIndex + i])) { + result |= 0x10; + } + result >>= 1; + } + return result; + } + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_row, _column, _enabled); + } + +public: + FamilyBasicKeyboard(KeyMappingSet keyMappings) : BaseControlDevice(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + if(_enabled) { + return ((~GetActiveKeys(_row, _column)) << 1) & 0x1E; + } else { + return 0; + } + } + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + uint8_t prevColumn = _column; + _column = (value & 0x02) >> 1; + if(!_column && prevColumn) { + //"Incrementing the row from the (keyless) 10th row will cause it to wrap back to the first row." + _row = (_row + 1) % 10; + } + + if(value & 0x01) { + _row = 0; + } + + _enabled = (value & 0x04) != 0; + } +}; \ No newline at end of file diff --git a/Core/FamilyMatTrainer.h b/Core/FamilyMatTrainer.h new file mode 100644 index 00000000..fa53b9a1 --- /dev/null +++ b/Core/FamilyMatTrainer.h @@ -0,0 +1,45 @@ +#pragma once +#include "stdafx.h" +#include "PowerPad.h" + +class FamilyMatTrainer : public PowerPad +{ +private: + uint8_t _ignoreRows = 0; + +protected: + void StreamState(bool saving) override + { + PowerPad::StreamState(saving); + Stream(_ignoreRows); + } + +public: + FamilyMatTrainer(KeyMappingSet keyMappings) : PowerPad(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; + if(addr == 0x4017) { + uint8_t pressedKeys[4] = {}; + for(int j = 0; j < 3; j++) { + if((_ignoreRows >> (2 - j)) & 0x01) { + //Ignore this row + continue; + } + for(int i = 0; i < 4; i++) { + pressedKeys[i] |= IsPressed(j * 4 + i) ? 1 : 0; + } + } + output = ~((pressedKeys[0] << 4) | (pressedKeys[1] << 3) | (pressedKeys[2] << 2) | (pressedKeys[3] << 1)) & 0x1E; + } + return output; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + _ignoreRows = value & 0x07; + } +}; \ No newline at end of file diff --git a/Core/FceuxMovie.cpp b/Core/FceuxMovie.cpp index 992020fa..3e6879ca 100644 --- a/Core/FceuxMovie.cpp +++ b/Core/FceuxMovie.cpp @@ -1,6 +1,8 @@ #include "stdafx.h" #include +#include "../Utilities/StringUtilities.h" #include "../Utilities/HexUtilities.h" +#include "ControlManager.h" #include "FceuxMovie.h" #include "Console.h" @@ -26,10 +28,13 @@ vector FceuxMovie::Base64Decode(string in) bool FceuxMovie::InitializeData(stringstream &filestream) { - const uint8_t orValues[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; - uint32_t systemActionCount = 0; - bool result = false; + + _dataByFrame[0].push_back(""); + _dataByFrame[1].push_back(""); + _dataByFrame[2].push_back(""); + _dataByFrame[3].push_back(""); + while(!filestream.eof()) { string line; std::getline(filestream, line); @@ -43,31 +48,27 @@ bool FceuxMovie::InitializeData(stringstream &filestream) return false; } } else if(line.size() > 0 && line[0] == '|') { - line.erase(std::remove(line.begin(), line.end(), '|'), line.end()); - line = line.substr(1, line.size() - 2); + vector lineData = StringUtilities::Split(line.substr(1), '|'); + + if(lineData.size() == 0) { + continue; + } //Read power/reset/FDS/VS/etc. commands - /*uint32_t systemAction = 0; - for(int i = 0; i < systemActionCount; i++) { - if(line[i] != '.') { - systemAction |= (1 << i); - } + uint32_t systemAction = 0; + try { + systemAction = (uint32_t)std::atol(lineData[0].c_str()); + } catch(std::exception ex) { } - _systemActionByFrame.push_back(systemAction);*/ + _systemActionByFrame.push_back(systemAction); //Only supports regular controllers (up to 4 of them) - for(int i = 0; i < 8 * 4; i++) { - uint8_t port = i / 8; - - if(port <= 3) { - uint8_t portValue = 0; - for(int j = 0; j < 8 && i + j + systemActionCount < line.size(); j++) { - if(line[i + j + systemActionCount] != '.') { - portValue |= orValues[j]; - } - } - i += 7; - _dataByFrame[port].push_back(portValue); + for(size_t i = 1; i < lineData.size() && i < 5; i++) { + if(lineData[i].size() >= 8) { + string data = lineData[i].substr(3, 1) + lineData[i].substr(2, 1) + lineData[i].substr(1, 1) + lineData[i].substr(0, 1); + _dataByFrame[i - 1].push_back(data + lineData[i].substr(4, 4)); + } else { + _dataByFrame[i - 1].push_back(""); } } } @@ -75,14 +76,19 @@ bool FceuxMovie::InitializeData(stringstream &filestream) return result; } -bool FceuxMovie::Play(stringstream & filestream, bool autoLoadRom) +bool FceuxMovie::Play(VirtualFile &file) { Console::Pause(); - if(InitializeData(filestream)) { + + std::stringstream ss; + file.ReadFile(ss); + if(InitializeData(ss)) { EmulationSettings::SetRamPowerOnState(RamPowerOnState::AllZeros); + ControlManager::RegisterInputProvider(this); Console::Reset(false); _isPlaying = true; } + Console::Resume(); return _isPlaying; } \ No newline at end of file diff --git a/Core/FceuxMovie.h b/Core/FceuxMovie.h index e79a6978..37cb54c8 100644 --- a/Core/FceuxMovie.h +++ b/Core/FceuxMovie.h @@ -11,5 +11,5 @@ private: bool InitializeData(stringstream &filestream); public: - bool Play(stringstream &filestream, bool autoLoadRom) override; + bool Play(VirtualFile &file) override; }; \ No newline at end of file diff --git a/Core/FdsLoader.h b/Core/FdsLoader.h index b3a9525b..11b151b0 100644 --- a/Core/FdsLoader.h +++ b/Core/FdsLoader.h @@ -2,7 +2,6 @@ #include "stdafx.h" #include #include "../Utilities/FolderUtilities.h" -#include "../Utilities/IpsPatcher.h" #include "../Utilities/CRC32.h" #include "../Utilities/sha1.h" #include "RomData.h" @@ -50,6 +49,21 @@ private: } } + vector LoadBios() + { + //For FDS, the PRG ROM is the FDS BIOS (8k) + vector biosData; + + ifstream biosFile("FdsBios.bin", ios::in | ios::binary); + if(biosFile) { + return vector(std::istreambuf_iterator(biosFile), {}); + } else { + MessageManager::SendNotification(ConsoleNotificationType::FdsBiosNotFound); + } + return {}; + } + +public: vector RebuildFdsFile(vector> diskData, bool needHeader) { vector output; @@ -93,7 +107,7 @@ private: return output; } - void LoadDiskData(vector& romFile, RomData &romData) + void LoadDiskData(vector& romFile, vector> &diskData, vector> &diskHeaders) { uint8_t numberOfSides = 0; size_t fileOffset = 0; @@ -106,12 +120,12 @@ private: } for(uint32_t i = 0; i < numberOfSides; i++) { - romData.FdsDiskData.push_back(vector()); - vector &fdsDiskImage = romData.FdsDiskData.back(); + diskData.push_back(vector()); + vector &fdsDiskImage = diskData.back(); - romData.FdsDiskHeaders.push_back(vector(romFile.data() + fileOffset + 1, romFile.data() + fileOffset + 57)); + diskHeaders.push_back(vector(romFile.data() + fileOffset + 1, romFile.data() + fileOffset + 57)); - AddGaps(fdsDiskImage, &romFile[fileOffset]); + AddGaps(fdsDiskImage, &romFile[fileOffset]); fileOffset += FdsDiskSideCapacity; //Ensure the image is 65500 bytes @@ -121,48 +135,8 @@ private: } } - vector LoadBios() + RomData LoadRom(vector &romFile, string filename) { - //For FDS, the PRG ROM is the FDS BIOS (8k) - vector biosData; - - ifstream biosFile("FdsBios.bin", ios::in | ios::binary); - if(biosFile) { - return vector(std::istreambuf_iterator(biosFile), {}); - } else { - MessageManager::SendNotification(ConsoleNotificationType::FdsBiosNotFound); - } - return {}; - } - -public: - void SaveIpsFile(string filename, vector &originalDiskData, vector> ¤tDiskData) - { - bool needHeader = (memcmp(originalDiskData.data(), "FDS\x1a", 4) == 0); - vector newData = RebuildFdsFile(currentDiskData, needHeader); - vector ipsData = IpsPatcher::CreatePatch(originalDiskData, newData); - - string fdsSaveFilepath = FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), FolderUtilities::GetFilename(filename, false) + ".ips"); - ofstream outputIps(fdsSaveFilepath, ios::binary); - if(outputIps) { - outputIps.write((char*)ipsData.data(), ipsData.size()); - outputIps.close(); - } - } - - RomData LoadRom(vector romFile, string filename) - { - //Note: "romFile" is intentionally passed by copy - modifying the original array will alter the "RawData" property which - //is used when saving the IPS file for FDS save data. If the RawData is modified by this function, then the IPS file will - //will only contain new changes, and all previous save data will be lost/corrupted. - - //Apply save data (saved as an IPS file), if found - string fdsSaveFilepath = FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), FolderUtilities::GetFilename(filename, false) + ".ips"); - vector patchedData; - if(IpsPatcher::PatchBuffer(fdsSaveFilepath, romFile, patchedData)) { - romFile = patchedData; - } - RomData romData; romData.Sha1 = SHA1::GetHash(romFile); @@ -177,8 +151,6 @@ public: if(romData.PrgRom.size() != 0x2000) { romData.Error = true; - } else { - LoadDiskData(romFile, romData); } //Setup default controllers diff --git a/Core/FdsSystemActionManager.h b/Core/FdsSystemActionManager.h new file mode 100644 index 00000000..c1632432 --- /dev/null +++ b/Core/FdsSystemActionManager.h @@ -0,0 +1,134 @@ +#pragma once +#include "stdafx.h" +#include "SystemActionManager.h" +#include "FDS.h" + +class FdsSystemActionManager : public SystemActionManager +{ +private: + const uint8_t ReinsertDiskFrameDelay = 120; + + std::weak_ptr _mapper; + + bool _needEjectDisk = false; + uint8_t _insertDiskNumber = 0; + uint8_t _insertDiskDelay = 0; + uint32_t _sideCount; + +protected: + string GetKeyNames() override + { + return string("RPE0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").substr(0, _sideCount + 3); + } + + void StreamState(bool saving) override + { + SystemActionManager::StreamState(saving); + Stream(_needEjectDisk, _insertDiskNumber, _insertDiskDelay); + } + +public: + enum FdsButtons { EjectDiskButton = 2, InsertDisk1 }; + + FdsSystemActionManager(shared_ptr console, shared_ptr mapper) : SystemActionManager(console) + { + _mapper = std::dynamic_pointer_cast(mapper); + _sideCount = std::dynamic_pointer_cast(mapper)->GetSideCount(); + + if(EmulationSettings::CheckFlag(EmulationFlags::FdsAutoLoadDisk)) { + InsertDisk(0); + } + } + + void OnAfterSetState() override + { + SystemActionManager::OnAfterSetState(); + if(_needEjectDisk) { + SetBit(FdsSystemActionManager::FdsButtons::EjectDiskButton); + _needEjectDisk = false; + } + if(_insertDiskDelay > 0) { + _insertDiskDelay--; + if(_insertDiskDelay == 0) { + SetBit(FdsSystemActionManager::FdsButtons::InsertDisk1 + _insertDiskNumber); + } + } + } + + void ProcessSystemActions() override + { + SystemActionManager::ProcessSystemActions(); + + shared_ptr mapper = _mapper.lock(); + if(mapper) { + if(IsPressed(FdsSystemActionManager::FdsButtons::EjectDiskButton)) { + mapper->EjectDisk(); + } + + for(int i = 0; i < 16; i++) { + if(IsPressed(FdsSystemActionManager::FdsButtons::InsertDisk1 + i)) { + mapper->InsertDisk(i); + break; + } + } + } + } + + void EjectDisk() + { + _needEjectDisk = true; + } + + void InsertDisk(uint8_t diskNumber) + { + shared_ptr mapper = _mapper.lock(); + if(mapper) { + if(mapper->IsDiskInserted()) { + //Eject disk on next frame, then insert new disk 2 seconds later + _needEjectDisk = true; + _insertDiskNumber = diskNumber; + _insertDiskDelay = FdsSystemActionManager::ReinsertDiskFrameDelay; + } else { + //Insert disk on next frame + _insertDiskNumber = diskNumber; + _insertDiskDelay = 1; + } + } + } + + void SwitchDiskSide() + { + shared_ptr mapper = _mapper.lock(); + if(mapper) { + InsertDisk(mapper->GetCurrentDisk() ^ 0x01); + } + } + + void InsertNextDisk() + { + shared_ptr mapper = _mapper.lock(); + if(mapper) { + InsertDisk(((mapper->GetCurrentDisk() & 0xFE) + 2) % mapper->GetSideCount()); + } + } + + uint32_t GetSideCount() + { + shared_ptr mapper = _mapper.lock(); + if(mapper) { + return mapper->GetSideCount(); + } else { + return 0; + } + } + + bool IsAutoInsertDiskEnabled() + { + shared_ptr mapper = _mapper.lock(); + if(mapper) { + return mapper->IsAutoInsertDiskEnabled(); + } else { + return false; + } + } +}; diff --git a/Core/FourScore.h b/Core/FourScore.h new file mode 100644 index 00000000..d524e01b --- /dev/null +++ b/Core/FourScore.h @@ -0,0 +1,48 @@ +#pragma once +#include "BaseControlDevice.h" + +class FourScore : public BaseControlDevice +{ +private: + uint32_t _signature4016 = 0; + uint32_t _signature4017 = 0; + +protected: + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_signature4016, _signature4017); + } + + void RefreshStateBuffer() override + { + //Signature for port 0 = 0x10, reversed bit order => 0x08 + //Signature for port 1 = 0x20, reversed bit order => 0x04 + _signature4016 = (0x08 << 16); + _signature4017 = (0x04 << 16); + } + +public: + FourScore() : BaseControlDevice(BaseControlDevice::ExpDevicePort) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; + if(addr == 0x4016) { + output = _signature4016 & 0x01; + _signature4016 >>= 1; + } else if(addr == 0x4017) { + output = _signature4017 & 0x01; + _signature4017 >>= 1; + } + StrobeProcessRead(); + return output; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } +}; diff --git a/Core/GameClient.cpp b/Core/GameClient.cpp index 1c6e4080..ca0f3bad 100644 --- a/Core/GameClient.cpp +++ b/Core/GameClient.cpp @@ -93,12 +93,6 @@ void GameClient::ProcessNotification(ConsoleNotificationType type, void* paramet } } -uint8_t GameClient::GetControllerState(uint8_t port) -{ - shared_ptr connection = GetConnection(); - return connection ? connection->GetControllerState(port) : 0; -} - void GameClient::SelectController(uint8_t port) { shared_ptr connection = GetConnection(); diff --git a/Core/GameClient.h b/Core/GameClient.h index 9cc6c9cf..50c9f4ac 100644 --- a/Core/GameClient.h +++ b/Core/GameClient.h @@ -34,7 +34,5 @@ public: static uint8_t GetControllerPort(); static uint8_t GetAvailableControllers(); - static uint8_t GetControllerState(uint8_t port); - void ProcessNotification(ConsoleNotificationType type, void* parameter) override; }; \ No newline at end of file diff --git a/Core/GameClientConnection.cpp b/Core/GameClientConnection.cpp index 7b6cb7db..3c8c3832 100644 --- a/Core/GameClientConnection.cpp +++ b/Core/GameClientConnection.cpp @@ -10,6 +10,9 @@ #include "ControlManager.h" #include "ClientConnectionData.h" #include "StandardController.h" +#include "Zapper.h" +#include "ArkanoidController.h" +#include "BandaiHyperShot.h" #include "SelectControllerMessage.h" #include "PlayerListMessage.h" #include "ForceDisconnectMessage.h" @@ -22,6 +25,7 @@ GameClientConnection::GameClientConnection(shared_ptr socket, shared_ptr MessageManager::RegisterNotificationListener(this); MessageManager::DisplayMessage("NetPlay", "ConnectedToServer"); + ControlManager::RegisterInputProvider(this); SendHandshake(); } @@ -36,10 +40,11 @@ void GameClientConnection::Shutdown() _shutdown = true; DisableControllers(); - EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed); + ControlManager::UnregisterInputProvider(this); MessageManager::UnregisterNotificationListener(this); MessageManager::SendNotification(ConsoleNotificationType::DisconnectedFromServer); MessageManager::DisplayMessage("NetPlay", "ConnectionLost"); + EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed); } } @@ -58,7 +63,7 @@ void GameClientConnection::SendControllerSelection(uint8_t port) void GameClientConnection::ClearInputData() { LockHandler lock = _writeLock.AcquireSafe(); - for(int i = 0; i < 4; i++) { + for(int i = 0; i < BaseControlDevice::PortCount; i++) { _inputSize[i] = 0; _inputData[i].clear(); } @@ -76,14 +81,7 @@ void GameClientConnection::ProcessMessage(NetMessage* message) ClearInputData(); ((SaveStateMessage*)message)->LoadState(); _enableControllers = true; - switch(EmulationSettings::GetControllerType(_controllerPort)) { - case ControllerType::StandardController: _controlDevice.reset(new StandardController(0)); break; - - case ControllerType::Zapper: - case ControllerType::ArkanoidController: - _controlDevice = ControlManager::GetControlDevice(_controllerPort); - break; - } + InitControlDevice(); Console::Resume(); } break; @@ -131,7 +129,7 @@ void GameClientConnection::ProcessMessage(NetMessage* message) } } -void GameClientConnection::PushControllerState(uint8_t port, uint8_t state) +void GameClientConnection::PushControllerState(uint8_t port, ControlDeviceState state) { LockHandler lock = _writeLock.AcquireSafe(); _inputData[port].push_back(state); @@ -147,14 +145,17 @@ void GameClientConnection::DisableControllers() //Used to prevent deadlocks when client is trying to fill its buffer while the host changes the current game/settings/etc. (i.e situations where we need to call Console::Pause()) ClearInputData(); _enableControllers = false; - for(int i = 0; i < 4; i++) { + for(int i = 0; i < BaseControlDevice::PortCount; i++) { _waitForInput[i].Signal(); } } -uint8_t GameClientConnection::GetControllerState(uint8_t port) +bool GameClientConnection::SetInput(BaseControlDevice *device) { + device->SetRawState(ControlDeviceState()); + if(_enableControllers) { + uint8_t port = device->GetPort(); while(_inputSize[port] == 0) { _waitForInput[port].Wait(); @@ -164,12 +165,12 @@ uint8_t GameClientConnection::GetControllerState(uint8_t port) } if(_shutdown || !_enableControllers) { - return 0; + return true; } } LockHandler lock = _writeLock.AcquireSafe(); - uint8_t state = _inputData[port].front(); + ControlDeviceState state = _inputData[port].front(); _inputData[port].pop_front(); _inputSize[port]--; @@ -180,18 +181,27 @@ uint8_t GameClientConnection::GetControllerState(uint8_t port) EmulationSettings::ClearFlags(EmulationFlags::ForceMaxSpeed); EmulationSettings::SetEmulationSpeed(100); } - return state; + + device->SetRawState(state); + return true; } - return 0; + return true; } - + +void GameClientConnection::InitControlDevice() +{ + if(_controllerPort == BaseControlDevice::ExpDevicePort) { + _newControlDevice = ControlManager::CreateExpansionDevice(EmulationSettings::GetExpansionDevice()); + } else { + //Pretend we are using port 0 (to use player 1's keybindings during netplay) + _newControlDevice = ControlManager::CreateControllerDevice(EmulationSettings::GetControllerType(_controllerPort), 0); + } +} + void GameClientConnection::ProcessNotification(ConsoleNotificationType type, void* parameter) { if(type == ConsoleNotificationType::ConfigChanged) { - switch(EmulationSettings::GetControllerType(_controllerPort)) { - case ControllerType::StandardController: _newControlDevice.reset(new StandardController(0)); break; - case ControllerType::Zapper: _newControlDevice = ControlManager::GetControlDevice(_controllerPort); break; - } + InitControlDevice(); } } @@ -203,9 +213,10 @@ void GameClientConnection::SendInput() _newControlDevice.reset(); } - uint32_t inputState = 0; + ControlDeviceState inputState; if(_controlDevice) { - inputState = _controlDevice->GetNetPlayState(); + _controlDevice->SetStateFromInput(); + inputState = _controlDevice->GetRawState(); } if(_lastInputSent != inputState) { @@ -223,9 +234,9 @@ void GameClientConnection::SelectController(uint8_t port) uint8_t GameClientConnection::GetAvailableControllers() { - uint8_t availablePorts = 0x0F; + uint8_t availablePorts = (1 << BaseControlDevice::PortCount) - 1; for(PlayerInfo &playerInfo : _playerList) { - if(playerInfo.ControllerPort < 4) { + if(playerInfo.ControllerPort < BaseControlDevice::PortCount) { availablePorts &= ~(1 << playerInfo.ControllerPort); } } diff --git a/Core/GameClientConnection.h b/Core/GameClientConnection.h index c591c900..5ca98933 100644 --- a/Core/GameClientConnection.h +++ b/Core/GameClientConnection.h @@ -4,16 +4,18 @@ #include "GameConnection.h" #include "../Utilities/AutoResetEvent.h" #include "../Utilities/SimpleLock.h" -#include "StandardController.h" +#include "BaseControlDevice.h" +#include "IInputProvider.h" +#include "ControlDeviceState.h" class ClientConnectionData; -class GameClientConnection : public GameConnection, public INotificationListener +class GameClientConnection : public GameConnection, public INotificationListener, public IInputProvider { private: - std::deque _inputData[4]; - atomic _inputSize[4]; - AutoResetEvent _waitForInput[4]; + std::deque _inputData[BaseControlDevice::PortCount]; + atomic _inputSize[BaseControlDevice::PortCount]; + AutoResetEvent _waitForInput[BaseControlDevice::PortCount]; SimpleLock _writeLock; atomic _shutdown; atomic _enableControllers; @@ -23,7 +25,7 @@ private: shared_ptr _controlDevice; shared_ptr _newControlDevice; - uint32_t _lastInputSent = 0x00; + ControlDeviceState _lastInputSent; bool _gameLoaded = false; uint8_t _controllerPort = GameConnection::SpectatorPort; @@ -31,7 +33,7 @@ private: void SendHandshake(); void SendControllerSelection(uint8_t port); void ClearInputData(); - void PushControllerState(uint8_t port, uint8_t state); + void PushControllerState(uint8_t port, ControlDeviceState state); void DisableControllers(); protected: @@ -45,7 +47,8 @@ public: void ProcessNotification(ConsoleNotificationType type, void* parameter) override; - uint8_t GetControllerState(uint8_t port); + bool SetInput(BaseControlDevice *device); + void InitControlDevice(); void SendInput(); void SelectController(uint8_t port); diff --git a/Core/GameDatabase.cpp b/Core/GameDatabase.cpp index 49625847..5cc81c70 100644 --- a/Core/GameDatabase.cpp +++ b/Core/GameDatabase.cpp @@ -28,6 +28,9 @@ void GameDatabase::InitDatabase() while(db.good()) { string lineContent; std::getline(db, lineContent); + if(lineContent[lineContent.size() - 1] == '\r') { + lineContent = lineContent.substr(0, lineContent.size() - 1); + } if(lineContent.empty() || lineContent[0] == '#') { continue; } @@ -105,7 +108,7 @@ void GameDatabase::InitializeInputDevices(string inputType, GameSystem system) ExpansionPortDevice expDevice = ExpansionPortDevice::None; EmulationSettings::ClearFlags(EmulationFlags::HasFourScore); - bool isFamicom = (system == GameSystem::Famicom || system == GameSystem::FDS); + bool isFamicom = (system == GameSystem::Famicom || system == GameSystem::FDS || system == GameSystem::Dendy); if(inputType.compare("Zapper") == 0) { MessageManager::Log("[DB] Input: Zapper connected"); @@ -134,6 +137,45 @@ void GameDatabase::InitializeInputDevices(string inputType, GameSystem system) MessageManager::Log("[DB] Input: Oeka Kids Tablet connected"); system = GameSystem::Famicom; expDevice = ExpansionPortDevice::OekaKidsTablet; + } else if(inputType.compare("KonamiHypershot") == 0) { + MessageManager::Log("[DB] Input: Konami Hyper Shot connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::KonamiHyperShot; + } else if(inputType.compare("FamilyKeyboard") == 0) { + MessageManager::Log("[DB] Input: Family Basic Keyboard connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::FamilyBasicKeyboard; + } else if(inputType.compare("PartyTap") == 0) { + MessageManager::Log("[DB] Input: Party Tap connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::PartyTap; + } else if(inputType.compare("Pachinko") == 0) { + MessageManager::Log("[DB] Input: Pachinko controller connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::Pachinko; + } else if(inputType.compare("ExcitingBoxing") == 0) { + MessageManager::Log("[DB] Input: Exciting Boxing controller connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::ExcitingBoxing; + } else if(inputType.compare("SuborKeyboard") == 0) { + MessageManager::Log("[DB] Input: Subor mouse connected"); + MessageManager::Log("[DB] Input: Subor keyboard connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::SuborKeyboard; + //TODO: FIX + //controllers[2] = ControllerType::SuborMouse; + } else if(inputType.compare("Mahjong") == 0) { + MessageManager::Log("[DB] Input: Jissen Mahjong controller connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::JissenMahjong; + } else if(inputType.compare("BarCodeWorld") == 0) { + MessageManager::Log("[DB] Input: Barcode Battler barcode reader connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::BarcodeBattler; + } else if(inputType.compare("BandaiHypershot") == 0) { + MessageManager::Log("[DB] Input: Bandai Hyper Shot gun connected"); + system = GameSystem::Famicom; + expDevice = ExpansionPortDevice::BandaiHyperShot; } else { MessageManager::Log("[DB] Input: 2 standard controllers connected"); } diff --git a/Core/GameServer.cpp b/Core/GameServer.cpp index 2bf7e35b..cd4ee1a6 100644 --- a/Core/GameServer.cpp +++ b/Core/GameServer.cpp @@ -17,7 +17,8 @@ GameServer::GameServer(uint16_t listenPort, string hostPlayerName) _port = listenPort; _hostPlayerName = hostPlayerName; _hostControllerPort = 0; - ControlManager::RegisterBroadcaster(this); + ControlManager::RegisterInputRecorder(this); + ControlManager::RegisterInputProvider(this); } GameServer::~GameServer() @@ -27,7 +28,8 @@ GameServer::~GameServer() Stop(); - ControlManager::UnregisterBroadcaster(this); + ControlManager::UnregisterInputRecorder(this); + ControlManager::UnregisterInputProvider(this); } void GameServer::AcceptConnections() @@ -68,6 +70,31 @@ list> GameServer::GetConnectionList() } } +bool GameServer::SetInput(BaseControlDevice *device) +{ + uint8_t port = device->GetPort(); + + GameServerConnection* connection = GameServerConnection::GetNetPlayDevice(port); + if(connection) { + //Device is controlled by a client + device->SetRawState(connection->GetState()); + return true; + } + + //Host is controlling this device + return false; +} + +void GameServer::RecordInput(BaseControlDevice *device) +{ + for(shared_ptr connection : _openConnections) { + if(!connection->ConnectionError()) { + //Send movie stream + connection->SendMovieData(device->GetPort(), device->GetRawState()); + } + } +} + void GameServer::Exec() { _listener.reset(new Socket()); @@ -114,16 +141,6 @@ bool GameServer::Started() } } -void GameServer::BroadcastInput(uint8_t inputData, uint8_t port) -{ - for(shared_ptr connection : _openConnections) { - if(!connection->ConnectionError()) { - //Send movie stream - connection->SendMovieData(inputData, port); - } - } -} - string GameServer::GetHostPlayerName() { if(GameServer::Started()) { @@ -155,9 +172,9 @@ void GameServer::SetHostControllerPort(uint8_t port) uint8_t GameServer::GetAvailableControllers() { - uint8_t availablePorts = 0x0F; + uint8_t availablePorts = (1 << BaseControlDevice::PortCount) - 1; for(PlayerInfo &playerInfo : GetPlayerList()) { - if(playerInfo.ControllerPort < 4) { + if(playerInfo.ControllerPort < BaseControlDevice::PortCount) { availablePorts &= ~(1 << playerInfo.ControllerPort); } } diff --git a/Core/GameServer.h b/Core/GameServer.h index b664127d..a508aaf7 100644 --- a/Core/GameServer.h +++ b/Core/GameServer.h @@ -3,10 +3,12 @@ #include #include "GameServerConnection.h" #include "INotificationListener.h" +#include "IInputProvider.h" +#include "IInputRecorder.h" using std::thread; -class GameServer : public IGameBroadcaster +class GameServer : public IInputRecorder, public IInputProvider { private: static unique_ptr Instance; @@ -44,5 +46,6 @@ public: static list> GetConnectionList(); - virtual void BroadcastInput(uint8_t inputData, uint8_t port); + bool SetInput(BaseControlDevice *device) override; + void RecordInput(BaseControlDevice *device) override; }; \ No newline at end of file diff --git a/Core/GameServerConnection.cpp b/Core/GameServerConnection.cpp index 18557612..a2322a72 100644 --- a/Core/GameServerConnection.cpp +++ b/Core/GameServerConnection.cpp @@ -15,8 +15,9 @@ #include "PlayerListMessage.h" #include "GameServer.h" #include "ForceDisconnectMessage.h" +#include "BaseControlDevice.h" -GameServerConnection* GameServerConnection::_netPlayDevices[4] = { nullptr,nullptr,nullptr,nullptr }; +GameServerConnection* GameServerConnection::_netPlayDevices[BaseControlDevice::PortCount] = { }; GameServerConnection::GameServerConnection(shared_ptr socket) : GameConnection(socket, nullptr) { @@ -45,7 +46,7 @@ void GameServerConnection::SendGameInformation() Console::Resume(); } -void GameServerConnection::SendMovieData(uint8_t state, uint8_t port) +void GameServerConnection::SendMovieData(uint8_t port, ControlDeviceState state) { if(_handshakeCompleted) { MovieDataMessage message(state, port); @@ -60,17 +61,18 @@ void GameServerConnection::SendForceDisconnectMessage(string disconnectMessage) Disconnect(); } -void GameServerConnection::PushState(uint32_t state) +void GameServerConnection::PushState(ControlDeviceState state) { if(_inputData.size() == 0 || state != _inputData.back()) { + _inputData.clear(); _inputData.push_back(state); } } -uint32_t GameServerConnection::GetState() +ControlDeviceState GameServerConnection::GetState() { size_t inputBufferSize = _inputData.size(); - uint32_t stateData = 0; + ControlDeviceState stateData; if(inputBufferSize > 0) { stateData = _inputData.front(); if(inputBufferSize > 1) { @@ -162,7 +164,6 @@ void GameServerConnection::ProcessNotification(ConsoleNotificationType type, voi case ConsoleNotificationType::GameReset: case ConsoleNotificationType::StateLoaded: case ConsoleNotificationType::CheatAdded: - case ConsoleNotificationType::FdsDiskChanged: case ConsoleNotificationType::ConfigChanged: SendGameInformation(); break; @@ -179,7 +180,7 @@ void GameServerConnection::RegisterNetPlayDevice(GameServerConnection* device, u void GameServerConnection::UnregisterNetPlayDevice(GameServerConnection* device) { if(device != nullptr) { - for(int i = 0; i < 4; i++) { + for(int i = 0; i < BaseControlDevice::PortCount; i++) { if(GameServerConnection::_netPlayDevices[i] == device) { GameServerConnection::_netPlayDevices[i] = nullptr; break; @@ -196,7 +197,7 @@ GameServerConnection* GameServerConnection::GetNetPlayDevice(uint8_t port) uint8_t GameServerConnection::GetFirstFreeControllerPort() { uint8_t hostPost = GameServer::GetHostControllerPort(); - for(int i = 0; i < 4; i++) { + for(int i = 0; i < BaseControlDevice::PortCount; i++) { if(hostPost != i && GameServerConnection::_netPlayDevices[i] == nullptr) { return i; } diff --git a/Core/GameServerConnection.h b/Core/GameServerConnection.h index f5e2ad4d..b5e05694 100644 --- a/Core/GameServerConnection.h +++ b/Core/GameServerConnection.h @@ -3,20 +3,22 @@ #include #include "GameConnection.h" #include "StandardController.h" -#include "IGameBroadcaster.h" #include "INotificationListener.h" +#include "BaseControlDevice.h" +#include "ControlDeviceState.h" class HandShakeMessage; class GameServerConnection : public GameConnection, public INotificationListener { private: - static GameServerConnection* _netPlayDevices[4]; + static GameServerConnection* _netPlayDevices[BaseControlDevice::PortCount]; - list _inputData; + list _inputData; int _controllerPort; bool _handshakeCompleted = false; - void PushState(uint32_t state); + + void PushState(ControlDeviceState state); void SendGameInformation(); void SelectControllerPort(uint8_t port); @@ -35,8 +37,8 @@ public: GameServerConnection(shared_ptr socket); ~GameServerConnection(); - uint32_t GetState(); - void SendMovieData(uint8_t state, uint8_t port); + ControlDeviceState GetState(); + void SendMovieData(uint8_t port, ControlDeviceState state); string GetPlayerName(); uint8_t GetControllerPort(); diff --git a/Core/HdBuilderPpu.h b/Core/HdBuilderPpu.h index f8149cd0..1f68d8bf 100644 --- a/Core/HdBuilderPpu.h +++ b/Core/HdBuilderPpu.h @@ -6,6 +6,8 @@ #include "RewindManager.h" #include "HdPackBuilder.h" +class ControlManager; + class HdBuilderPpu : public PPU { private: @@ -108,7 +110,7 @@ protected: } public: - HdBuilderPpu(BaseMapper* mapper, HdPackBuilder* hdPackBuilder, uint32_t chrRamBankSize) : PPU(mapper) + HdBuilderPpu(BaseMapper* mapper, ControlManager* controlManager, HdPackBuilder* hdPackBuilder, uint32_t chrRamBankSize) : PPU(mapper, controlManager) { _hdPackBuilder = hdPackBuilder; _chrRamBankSize = chrRamBankSize; diff --git a/Core/HdPackLoader.cpp b/Core/HdPackLoader.cpp index 1ce22060..0d3cd4fb 100644 --- a/Core/HdPackLoader.cpp +++ b/Core/HdPackLoader.cpp @@ -244,7 +244,7 @@ void HdPackLoader::ProcessPatchTag(vector &tokens) return; } - std::transform(tokens[1].begin(), tokens[1].end(), tokens[1].begin(), ::tolower); + std::transform(tokens[1].begin(), tokens[1].end(), tokens[1].begin(), ::toupper); if(_loadFromZip) { _data->PatchesByHash[tokens[1]] = VirtualFile(_hdPackFolder, tokens[0]); } else { diff --git a/Core/HdPpu.h b/Core/HdPpu.h index 31385d7e..6a5e37f5 100644 --- a/Core/HdPpu.h +++ b/Core/HdPpu.h @@ -5,6 +5,8 @@ #include "VideoDecoder.h" #include "RewindManager.h" +class ControlManager; + class HdPpu : public PPU { private: @@ -126,7 +128,7 @@ protected: } public: - HdPpu(BaseMapper* mapper, uint32_t version) : PPU(mapper) + HdPpu(BaseMapper* mapper, ControlManager* controlManager, uint32_t version) : PPU(mapper, controlManager) { _screenTileBuffers[0] = new HdPpuPixelInfo[256 * 240]; _screenTileBuffers[1] = new HdPpuPixelInfo[256 * 240]; diff --git a/Core/HoriTrack.h b/Core/HoriTrack.h new file mode 100644 index 00000000..703185bb --- /dev/null +++ b/Core/HoriTrack.h @@ -0,0 +1,51 @@ +#pragma once +#include "stdafx.h" +#include "StandardController.h" +#include "KeyManager.h" + +class HoriTrack : public StandardController +{ +protected: + bool HasCoordinates() override { return true; } + + void InternalSetStateFromInput() override + { + StandardController::InternalSetStateFromInput(); + SetPressedState(StandardController::Buttons::A, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + SetPressedState(StandardController::Buttons::B, KeyManager::IsMouseButtonPressed(MouseButton::RightButton)); + SetMovement(KeyManager::GetMouseMovement()); + } + +public: + HoriTrack(KeyMappingSet keyMappings) : StandardController(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) + { + uint8_t output = 0; + if(addr == 0x4016) { + output = (_stateBuffer & 0x01) << 1; + _stateBuffer >>= 1; + StrobeProcessRead(); + } + return output; + } + + void RefreshStateBuffer() override + { + MouseMovement mov = GetMovement(); + + mov.dx = std::max(-8, std::min((int)mov.dx, 7)); + mov.dy = std::max(-8, std::min((int)mov.dy, 7)); + + mov.dx = ((mov.dx & 0x08) >> 3) | ((mov.dx & 0x04) >> 1) | ((mov.dx & 0x02) << 1) | ((mov.dx & 0x01) << 3); + mov.dy = ((mov.dy & 0x08) >> 3) | ((mov.dy & 0x04) >> 1) | ((mov.dy & 0x02) << 1) | ((mov.dy & 0x01) << 3); + + uint8_t byte1 = (~mov.dy & 0x0F) | ((~mov.dx & 0x0F) << 4); + uint8_t byte2 = 0x09; + + StandardController::RefreshStateBuffer(); + _stateBuffer = (_stateBuffer & 0xFF) | (byte1 << 8) | (byte2 << 16); + } +}; \ No newline at end of file diff --git a/Core/IBarcodeReader.h b/Core/IBarcodeReader.h new file mode 100644 index 00000000..dededbf9 --- /dev/null +++ b/Core/IBarcodeReader.h @@ -0,0 +1,8 @@ +#pragma once +#include "stdafx.h" + +class IBarcodeReader +{ +public: + virtual void InputBarcode(uint64_t barcode, uint32_t digitCount) = 0; +}; diff --git a/Core/IBattery.h b/Core/IBattery.h new file mode 100644 index 00000000..c79cb13e --- /dev/null +++ b/Core/IBattery.h @@ -0,0 +1,7 @@ +#pragma once + +class IBattery +{ +public: + virtual void SaveBattery() = 0; +}; \ No newline at end of file diff --git a/Core/IGameBroadcaster.h b/Core/IGameBroadcaster.h deleted file mode 100644 index c32ea908..00000000 --- a/Core/IGameBroadcaster.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "stdafx.h" - -class IGameBroadcaster -{ -public: - virtual void BroadcastInput(uint8_t inputData, uint8_t port) = 0; -}; \ No newline at end of file diff --git a/Core/IInputProvider.h b/Core/IInputProvider.h new file mode 100644 index 00000000..d4dbab84 --- /dev/null +++ b/Core/IInputProvider.h @@ -0,0 +1,9 @@ +#pragma once + +class BaseControlDevice; + +class IInputProvider +{ +public: + virtual bool SetInput(BaseControlDevice* device) = 0; +}; \ No newline at end of file diff --git a/Core/IInputRecorder.h b/Core/IInputRecorder.h new file mode 100644 index 00000000..bd38f380 --- /dev/null +++ b/Core/IInputRecorder.h @@ -0,0 +1,10 @@ +#pragma once + +class BaseControlDevice; + +class IInputRecorder +{ +public: + virtual void RecordInput(BaseControlDevice *device) = 0; + virtual void EndFrame() { } +}; \ No newline at end of file diff --git a/Core/INotificationListener.h b/Core/INotificationListener.h index 089ea37a..dea191a8 100644 --- a/Core/INotificationListener.h +++ b/Core/INotificationListener.h @@ -15,13 +15,12 @@ enum class ConsoleNotificationType PpuFrameDone = 9, MovieEnded = 10, ResolutionChanged = 11, - FdsDiskChanged = 12, - FdsBiosNotFound = 13, - ConfigChanged = 14, - DisconnectedFromServer = 15, - PpuViewerDisplayFrame = 16, - ExecuteShortcut = 17, - EmulationStopped = 18, + FdsBiosNotFound = 12, + ConfigChanged = 13, + DisconnectedFromServer = 14, + PpuViewerDisplayFrame = 15, + ExecuteShortcut = 16, + EmulationStopped = 17, }; class INotificationListener diff --git a/Core/InputDataMessage.h b/Core/InputDataMessage.h index 7ef415a4..d64aa327 100644 --- a/Core/InputDataMessage.h +++ b/Core/InputDataMessage.h @@ -1,27 +1,28 @@ #pragma once #include "stdafx.h" #include "NetMessage.h" +#include "ControlDeviceState.h" class InputDataMessage : public NetMessage { private: - uint32_t _inputState; + ControlDeviceState _inputState; protected: virtual void ProtectedStreamState() { - Stream(_inputState); + StreamArray(_inputState.State); } public: InputDataMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { } - InputDataMessage(uint32_t inputState) : NetMessage(MessageType::InputData) + InputDataMessage(ControlDeviceState inputState) : NetMessage(MessageType::InputData) { _inputState = inputState; } - uint32_t GetInputState() + ControlDeviceState GetInputState() { return _inputState; } diff --git a/Core/JissenMahjongController.h b/Core/JissenMahjongController.h new file mode 100644 index 00000000..59b72ad1 --- /dev/null +++ b/Core/JissenMahjongController.h @@ -0,0 +1,98 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class JissenMahjongController : public BaseControlDevice +{ +private: + uint8_t _row = 0; + uint32_t _stateBuffer = 0; + +protected: + enum Buttons { A = 0, B, C, D, E, F, G, H, I, J, K, L, M, N, Select, Start, Kan, Pon, Chii, Riichi, Ron }; + + string GetKeyNames() override + { + return "ABCDEFGHIJKLMNSTkpcir"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + for(int i = 0; i < 21; i++) { + SetPressedState(i, keyMapping.JissenMahjongButtons[i]); + } + } + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_row, _stateBuffer); + } + +public: + JissenMahjongController(KeyMappingSet keyMappings) : BaseControlDevice(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + uint8_t value = (_stateBuffer & 0x01) << 1; + _stateBuffer >>= 1; + StrobeProcessRead(); + return value; + } + return 0; + } + + void RefreshStateBuffer() override + { + switch(_row) { + default: + case 0: + _stateBuffer = 0; + break; + + case 1: + _stateBuffer = + (IsPressed(JissenMahjongController::Buttons::N) ? 0x04 : 0) | + (IsPressed(JissenMahjongController::Buttons::M) ? 0x08 : 0) | + (IsPressed(JissenMahjongController::Buttons::L) ? 0x10 : 0) | + (IsPressed(JissenMahjongController::Buttons::K) ? 0x20 : 0) | + (IsPressed(JissenMahjongController::Buttons::J) ? 0x40 : 0) | + (IsPressed(JissenMahjongController::Buttons::I) ? 0x80 : 0); + break; + + case 2: + _stateBuffer = + (IsPressed(JissenMahjongController::Buttons::H) ? 0x01 : 0) | + (IsPressed(JissenMahjongController::Buttons::G) ? 0x02 : 0) | + (IsPressed(JissenMahjongController::Buttons::F) ? 0x04 : 0) | + (IsPressed(JissenMahjongController::Buttons::E) ? 0x08 : 0) | + (IsPressed(JissenMahjongController::Buttons::D) ? 0x10 : 0) | + (IsPressed(JissenMahjongController::Buttons::C) ? 0x20 : 0) | + (IsPressed(JissenMahjongController::Buttons::B) ? 0x40 : 0) | + (IsPressed(JissenMahjongController::Buttons::A) ? 0x80 : 0); + break; + + case 3: + _stateBuffer = + (IsPressed(JissenMahjongController::Buttons::Ron) ? 0x02 : 0) | + (IsPressed(JissenMahjongController::Buttons::Riichi) ? 0x04 : 0) | + (IsPressed(JissenMahjongController::Buttons::Chii) ? 0x08 : 0) | + (IsPressed(JissenMahjongController::Buttons::Pon) ? 0x10 : 0) | + (IsPressed(JissenMahjongController::Buttons::Kan) ? 0x20 : 0) | + (IsPressed(JissenMahjongController::Buttons::Start) ? 0x40 : 0) | + (IsPressed(JissenMahjongController::Buttons::Select) ? 0x80 : 0); + break; + } + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + _row = (value & 0x6) >> 1; + StrobeProcessWrite(value); + } +}; \ No newline at end of file diff --git a/Core/KeyManager.cpp b/Core/KeyManager.cpp new file mode 100644 index 00000000..87c149ba --- /dev/null +++ b/Core/KeyManager.cpp @@ -0,0 +1,108 @@ +#include "stdafx.h" +#include "KeyManager.h" +#include "IKeyManager.h" +#include "Types.h" +#include "EmulationSettings.h" +#include "PPU.h" + +unique_ptr KeyManager::_keyManager; +MousePosition KeyManager::_mousePosition; +atomic KeyManager::_xMouseMovement; +atomic KeyManager::_yMouseMovement; + +void KeyManager::RegisterKeyManager(IKeyManager* keyManager) +{ + _keyManager.reset(keyManager); +} + +void KeyManager::RefreshKeyState() +{ + if(_keyManager != nullptr) { + return _keyManager->RefreshState(); + } +} + +bool KeyManager::IsKeyPressed(uint32_t keyCode) +{ + if(_keyManager != nullptr) { + return _keyManager->IsKeyPressed(keyCode); + } + return false; +} + +bool KeyManager::IsMouseButtonPressed(MouseButton button) +{ + if(_keyManager != nullptr) { + return _keyManager->IsMouseButtonPressed(button); + } + return false; +} + +vector KeyManager::GetPressedKeys() +{ + if(_keyManager != nullptr) { + return _keyManager->GetPressedKeys(); + } + return vector(); +} + +string KeyManager::GetKeyName(uint32_t keyCode) +{ + if(_keyManager != nullptr) { + return _keyManager->GetKeyName(keyCode); + } + return ""; +} + +uint32_t KeyManager::GetKeyCode(string keyName) +{ + if(_keyManager != nullptr) { + return _keyManager->GetKeyCode(keyName); + } + return 0; +} + +void KeyManager::UpdateDevices() +{ + if(_keyManager != nullptr) { + _keyManager->UpdateDevices(); + } +} + +void KeyManager::SetMouseMovement(int16_t x, int16_t y) +{ + _xMouseMovement += x; + _yMouseMovement += y; +} + +MouseMovement KeyManager::GetMouseMovement() +{ + double factor = EmulationSettings::GetVideoScale() * EmulationSettings::GetMouseSensitivity(); + MouseMovement mov; + int16_t x = _xMouseMovement; + int16_t y = _yMouseMovement; + + mov.dx = (int16_t)(_xMouseMovement / factor); + mov.dy = (int16_t)(_yMouseMovement / factor); + _xMouseMovement -= (int16_t)(mov.dx * factor); + _yMouseMovement -= (int16_t)(mov.dy * factor); + + return mov; +} + +void KeyManager::SetMousePosition(double x, double y) +{ + if(x < 0 || y < 0) { + _mousePosition.X = -1; + _mousePosition.Y = -1; + } else { + OverscanDimensions overscan = EmulationSettings::GetOverscanDimensions(); + _mousePosition.X = (int32_t)(x * (PPU::ScreenWidth - overscan.Left - overscan.Right) + overscan.Left); + _mousePosition.Y = (int32_t)(y * (PPU::ScreenHeight - overscan.Top - overscan.Bottom) + overscan.Top); + } +} + +MousePosition KeyManager::GetMousePosition() +{ + return _mousePosition; +} \ No newline at end of file diff --git a/Core/KeyManager.h b/Core/KeyManager.h new file mode 100644 index 00000000..35e7e4fd --- /dev/null +++ b/Core/KeyManager.h @@ -0,0 +1,32 @@ +#pragma once +#include "stdafx.h" +#include "Types.h" + +class IKeyManager; +enum class MouseButton; + +class KeyManager +{ +private: + static unique_ptr _keyManager; + static MousePosition _mousePosition; + static atomic _xMouseMovement; + static atomic _yMouseMovement; + +public: + static void RegisterKeyManager(IKeyManager* keyManager); + static void RefreshKeyState(); + static bool IsKeyPressed(uint32_t keyCode); + static bool IsMouseButtonPressed(MouseButton button); + static vector GetPressedKeys(); + static string GetKeyName(uint32_t keyCode); + static uint32_t GetKeyCode(string keyName); + + static void UpdateDevices(); + + static void SetMouseMovement(int16_t x, int16_t y); + static MouseMovement GetMouseMovement(); + + static void SetMousePosition(double x, double y); + static MousePosition GetMousePosition(); +}; \ No newline at end of file diff --git a/Core/KonamiHyperShot.h b/Core/KonamiHyperShot.h new file mode 100644 index 00000000..f6533aca --- /dev/null +++ b/Core/KonamiHyperShot.h @@ -0,0 +1,84 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class KonamiHyperShot : public BaseControlDevice +{ +private: + bool _enableP1 = true; + bool _enableP2 = true; + uint32_t _p1TurboSpeed; + uint32_t _p2TurboSpeed; + vector _p2KeyMappings; + +protected: + enum Buttons { Player1Run = 0, Player1Jump, Player2Run, Player2Jump }; + + string GetKeyNames() override + { + return "RJrj"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + SetPressedState(Buttons::Player1Jump, keyMapping.A); + SetPressedState(Buttons::Player1Run, keyMapping.B); + + uint8_t turboFreq = 1 << (4 - _p1TurboSpeed); + bool turboOn = (uint8_t)(PPU::GetFrameCount() % turboFreq) < turboFreq / 2; + if(turboOn) { + SetPressedState(Buttons::Player1Jump, keyMapping.TurboA); + SetPressedState(Buttons::Player1Run, keyMapping.TurboB); + } + } + + for(KeyMapping keyMapping : _keyMappings) { + SetPressedState(Buttons::Player1Jump, keyMapping.A); + SetPressedState(Buttons::Player1Run, keyMapping.B); + + uint8_t turboFreq = 1 << (4 - _p2TurboSpeed); + bool turboOn = (uint8_t)(PPU::GetFrameCount() % turboFreq) < turboFreq / 2; + if(turboOn) { + SetPressedState(Buttons::Player2Jump, keyMapping.TurboA); + SetPressedState(Buttons::Player2Run, keyMapping.TurboB); + } + } + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_enableP1, _enableP2); + } + +public: + KonamiHyperShot(KeyMappingSet p1, KeyMappingSet p2) : BaseControlDevice(BaseControlDevice::ExpDevicePort, p1) + { + _p1TurboSpeed = p1.TurboSpeed; + _p2TurboSpeed = p2.TurboSpeed; + _p2KeyMappings = p2.GetKeyMappingArray(); + } + + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; + if(addr == 0x4017) { + if(_enableP1) { + output |= IsPressed(KonamiHyperShot::Buttons::Player1Jump) ? 0x02 : 0; + output |= IsPressed(KonamiHyperShot::Buttons::Player1Run) ? 0x04 : 0; + } + if(_enableP2) { + output |= IsPressed(KonamiHyperShot::Buttons::Player2Jump) ? 0x08 : 0; + output |= IsPressed(KonamiHyperShot::Buttons::Player2Run) ? 0x10 : 0; + } + } + return output; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + _enableP2 = (value & 0x02) == 0; + _enableP1 = (value & 0x04) == 0; + } +}; \ No newline at end of file diff --git a/Core/LuaApi.cpp b/Core/LuaApi.cpp index 3231ac6c..aff3fb2b 100644 --- a/Core/LuaApi.cpp +++ b/Core/LuaApi.cpp @@ -18,6 +18,7 @@ #include "StandardController.h" #include "PPU.h" #include "CheatManager.h" +#include "KeyManager.h" #define lua_pushintvalue(name, value) lua_pushliteral(lua, #name); lua_pushinteger(lua, (int)value); lua_settable(lua, -3); #define lua_pushboolvalue(name, value) lua_pushliteral(lua, #name); lua_pushboolean(lua, (int)value); lua_settable(lua, -3); @@ -135,6 +136,7 @@ int LuaApi::GetLibrary(lua_State *lua) lua_pushintvalue(codeBreak, EventType::CodeBreak); lua_pushintvalue(stateLoaded, EventType::StateLoaded); lua_pushintvalue(stateSaved, EventType::StateSaved); + lua_pushintvalue(inputPolled, EventType::InputPolled); lua_settable(lua, -3); lua_pushliteral(lua, "executeCountType"); @@ -255,7 +257,7 @@ int LuaApi::RegisterEventCallback(lua_State *lua) EventType type = (EventType)l.ReadInteger(); int reference = l.GetReference(); checkparams(); - errorCond(type < EventType::Reset || type > EventType::StateSaved, "the specified type is invalid"); + errorCond(type < EventType::Reset || type >= EventType::EventTypeSize, "the specified type is invalid"); errorCond(reference == LUA_NOREF, "the specified function could not be found"); _context->RegisterEventCallback(type, reference); l.Return(reference); @@ -268,7 +270,7 @@ int LuaApi::UnregisterEventCallback(lua_State *lua) EventType type = (EventType)l.ReadInteger(); int reference = l.ReadInteger(); checkparams(); - errorCond(type < EventType::Reset || type > EventType::StateSaved, "the specified type is invalid"); + errorCond(type < EventType::Reset || type >= EventType::EventTypeSize, "the specified type is invalid"); errorCond(reference == LUA_NOREF, "function reference is invalid"); _context->UnregisterEventCallback(type, reference); return l.ReturnCount(); @@ -280,7 +282,7 @@ int LuaApi::DrawString(lua_State *lua) l.ForceParamCount(6); int frameCount = l.ReadInteger(1); int backColor = l.ReadInteger(0); - int color = l.ReadInteger(0xFFFFFFFF); + int color = l.ReadInteger(0xFFFFFF); string text = l.ReadString(); int y = l.ReadInteger(); int x = l.ReadInteger(); @@ -294,8 +296,9 @@ int LuaApi::DrawString(lua_State *lua) int LuaApi::DrawLine(lua_State *lua) { LuaCallHelper l(lua); + l.ForceParamCount(6); int frameCount = l.ReadInteger(1); - int color = l.ReadInteger(0xFFFFFFFF); + int color = l.ReadInteger(0xFFFFFF); int y2 = l.ReadInteger(); int x2 = l.ReadInteger(); int y = l.ReadInteger(); @@ -310,6 +313,7 @@ int LuaApi::DrawLine(lua_State *lua) int LuaApi::DrawPixel(lua_State *lua) { LuaCallHelper l(lua); + l.ForceParamCount(4); int frameCount = l.ReadInteger(1); int color = l.ReadInteger(); int y = l.ReadInteger(); @@ -324,9 +328,10 @@ int LuaApi::DrawPixel(lua_State *lua) int LuaApi::DrawRectangle(lua_State *lua) { LuaCallHelper l(lua); + l.ForceParamCount(7); int frameCount = l.ReadInteger(1); bool fill = l.ReadBool(false); - int color = l.ReadInteger(0xFFFFFFFF); + int color = l.ReadInteger(0xFFFFFF); int height = l.ReadInteger(); int width = l.ReadInteger(); int y = l.ReadInteger(); @@ -364,14 +369,14 @@ int LuaApi::GetPixel(lua_State *lua) int LuaApi::GetMouseState(lua_State *lua) { LuaCallHelper l(lua); - MousePosition pos = ControlManager::GetMousePosition(); + MousePosition pos = KeyManager::GetMousePosition(); checkparams(); lua_newtable(lua); lua_pushintvalue(x, pos.X); lua_pushintvalue(y, pos.Y); - lua_pushboolvalue(left, ControlManager::IsMouseButtonPressed(MouseButton::LeftButton)); - lua_pushboolvalue(middle, ControlManager::IsMouseButtonPressed(MouseButton::MiddleButton)); - lua_pushboolvalue(right, ControlManager::IsMouseButtonPressed(MouseButton::RightButton)); + lua_pushboolvalue(left, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + lua_pushboolvalue(middle, KeyManager::IsMouseButtonPressed(MouseButton::MiddleButton)); + lua_pushboolvalue(right, KeyManager::IsMouseButtonPressed(MouseButton::RightButton)); return 1; } @@ -521,9 +526,9 @@ int LuaApi::IsKeyPressed(lua_State *lua) LuaCallHelper l(lua); string keyName = l.ReadString(); checkparams(); - uint32_t keyCode = ControlManager::GetKeyCode(keyName); + uint32_t keyCode = KeyManager::GetKeyCode(keyName); errorCond(keyCode == 0, "Invalid key name"); - l.Return(ControlManager::IsKeyPressed(keyCode)); + l.Return(KeyManager::IsKeyPressed(keyCode)); return l.ReturnCount(); } @@ -537,22 +542,25 @@ int LuaApi::GetInput(lua_State *lua) shared_ptr controller = std::dynamic_pointer_cast(ControlManager::GetControlDevice(port)); errorCond(controller == nullptr, "Input port must be connected to a standard controller"); - ButtonState state = controller->GetButtonState(); lua_newtable(lua); - lua_pushboolvalue(a, state.A); - lua_pushboolvalue(b, state.B); - lua_pushboolvalue(start, state.Start); - lua_pushboolvalue(select, state.Select); - lua_pushboolvalue(up, state.Up); - lua_pushboolvalue(down, state.Down); - lua_pushboolvalue(left, state.Left); - lua_pushboolvalue(right, state.Right); + lua_pushboolvalue(a, controller->IsPressed(StandardController::Buttons::A)); + lua_pushboolvalue(b, controller->IsPressed(StandardController::Buttons::B)); + lua_pushboolvalue(start, controller->IsPressed(StandardController::Buttons::Start)); + lua_pushboolvalue(select, controller->IsPressed(StandardController::Buttons::Select)); + lua_pushboolvalue(up, controller->IsPressed(StandardController::Buttons::Up)); + lua_pushboolvalue(down, controller->IsPressed(StandardController::Buttons::Down)); + lua_pushboolvalue(left, controller->IsPressed(StandardController::Buttons::Left)); + lua_pushboolvalue(right, controller->IsPressed(StandardController::Buttons::Right)); return 1; } int LuaApi::SetInput(lua_State *lua) { - lua_settop(lua, 2); + LuaCallHelper l(lua); + lua_settop(lua, 3); + + bool allowUserInput = l.ReadBool(); + luaL_checktype(lua, 2, LUA_TTABLE); lua_getfield(lua, 2, "a"); lua_getfield(lua, 2, "b"); @@ -563,22 +571,32 @@ int LuaApi::SetInput(lua_State *lua) lua_getfield(lua, 2, "left"); lua_getfield(lua, 2, "right"); - LuaCallHelper l(lua); - ButtonState buttonState; - buttonState.Right = l.ReadBool(); - buttonState.Left = l.ReadBool(); - buttonState.Down = l.ReadBool(); - buttonState.Up = l.ReadBool(); - buttonState.Select = l.ReadBool(); - buttonState.Start = l.ReadBool(); - buttonState.B = l.ReadBool(); - buttonState.A = l.ReadBool(); + Nullable right = l.ReadOptionalBool(); + Nullable left = l.ReadOptionalBool(); + Nullable down = l.ReadOptionalBool(); + Nullable up = l.ReadOptionalBool(); + Nullable select = l.ReadOptionalBool(); + Nullable start = l.ReadOptionalBool(); + Nullable b = l.ReadOptionalBool(); + Nullable a = l.ReadOptionalBool(); + lua_pop(lua, 1); int port = l.ReadInteger(); - _debugger->SetInputOverride(port, buttonState.ToByte()); errorCond(port < 0 || port > 3, "Invalid port number - must be between 0 to 3"); + shared_ptr controller = std::dynamic_pointer_cast(ControlManager::GetControlDevice(port)); + errorCond(controller == nullptr, "Input port must be connected to a standard controller"); + + if(right.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::Right, right.Value); + if(left.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::Left, left.Value); + if(down.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::Down, down.Value); + if(up.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::Up, up.Value); + if(select.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::Select, select.Value); + if(start.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::Start, start.Value); + if(b.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::B, b.Value); + if(a.HasValue || !allowUserInput) controller->SetBitValue(StandardController::Buttons::A, a.Value); + return l.ReturnCount(); } diff --git a/Core/LuaCallHelper.cpp b/Core/LuaCallHelper.cpp index 6fc4cfe3..699ea92e 100644 --- a/Core/LuaCallHelper.cpp +++ b/Core/LuaCallHelper.cpp @@ -50,6 +50,36 @@ bool LuaCallHelper::ReadBool(bool defaultValue) return value; } +Nullable LuaCallHelper::ReadOptionalBool() +{ + _paramCount++; + Nullable result; + if(lua_isboolean(_lua, -1)) { + result.HasValue = true; + result.Value = lua_toboolean(_lua, -1) != 0; + } else if(lua_isnumber(_lua, -1)) { + result.HasValue = true; + result.Value = lua_tonumber(_lua, -1) != 0; + } + lua_pop(_lua, 1); + return result; +} + +Nullable LuaCallHelper::ReadOptionalInteger() +{ + _paramCount++; + Nullable result; + if(lua_isinteger(_lua, -1)) { + result.HasValue = true; + result.Value = (uint32_t)lua_tointeger(_lua, -1); + } else if(lua_isnumber(_lua, -1)) { + result.HasValue = true; + result.Value = (uint32_t)lua_tonumber(_lua, -1); + } + lua_pop(_lua, 1); + return result; +} + uint32_t LuaCallHelper::ReadInteger(uint32_t defaultValue) { _paramCount++; diff --git a/Core/LuaCallHelper.h b/Core/LuaCallHelper.h index 28b562ca..e4fc7689 100644 --- a/Core/LuaCallHelper.h +++ b/Core/LuaCallHelper.h @@ -2,6 +2,13 @@ #include "stdafx.h" #include "../Lua/lua.hpp" +template +struct Nullable +{ + bool HasValue = false; + T Value = {}; +}; + class LuaCallHelper { private: @@ -22,6 +29,9 @@ public: string ReadString(); int GetReference(); + Nullable ReadOptionalBool(); + Nullable ReadOptionalInteger(); + void Return(bool value); void Return(int value); void Return(uint32_t value); diff --git a/Core/Mapper39.h b/Core/Mapper39.h new file mode 100644 index 00000000..b0581b5e --- /dev/null +++ b/Core/Mapper39.h @@ -0,0 +1,27 @@ +#pragma once +#include "stdafx.h" +#include "BaseMapper.h" + +//Used by Study and Game 32-in-1 (Ch) +class Mapper39 : public BaseMapper +{ +protected: + virtual uint16_t GetPRGPageSize() override { return 0x8000; } + virtual uint16_t GetCHRPageSize() override { return 0x2000; } + + void InitMapper() override + { + SelectPRGPage(0, 0); + SelectCHRPage(0, 0); + } + + void Reset(bool softReset) override + { + SelectPRGPage(0, 0); + } + + void WriteRegister(uint16_t addr, uint8_t value) override + { + SelectPRGPage(0, value); + } +}; diff --git a/Core/Mapper58.h b/Core/Mapper58.h index 912d20cb..07f7bfdd 100644 --- a/Core/Mapper58.h +++ b/Core/Mapper58.h @@ -22,8 +22,7 @@ protected: SelectPRGPage(0, prgBank); SelectPRGPage(1, prgBank); } else { - SelectPRGPage(0, prgBank & 0xFE); - SelectPRGPage(1, (prgBank & 0xFE) + 1); + SelectPrgPage2x(0, prgBank & 0x06); } SelectCHRPage(0, (addr >> 3) & 0x07); diff --git a/Core/MapperFactory.cpp b/Core/MapperFactory.cpp index 889628c0..fb1a4607 100644 --- a/Core/MapperFactory.cpp +++ b/Core/MapperFactory.cpp @@ -76,6 +76,7 @@ #include "MagicKidGooGoo.h" #include "Mapper15.h" #include "Mapper35.h" +#include "Mapper39.h" #include "Mapper40.h" #include "Mapper42.h" #include "Mapper43.h" @@ -319,6 +320,7 @@ BaseMapper* MapperFactory::GetMapperFromID(RomData &romData) case 36: return new Txc22000(); case 37: return new MMC3_37(); case 38: return new UnlPci556(); + case 39: return new Mapper39(); case 40: return new Mapper40(); case 41: return new Caltron41(); case 42: return new Mapper42(); diff --git a/Core/MesenMovie.cpp b/Core/MesenMovie.cpp index d0480257..718c6442 100644 --- a/Core/MesenMovie.cpp +++ b/Core/MesenMovie.cpp @@ -1,371 +1,303 @@ -#include "stdafx.h" -#include "MessageManager.h" +#include "stdafx.h" +#include "../Utilities/ZipReader.h" +#include "../Utilities/StringUtilities.h" +#include "../Utilities/HexUtilities.h" #include "MesenMovie.h" +#include "MessageManager.h" +#include "ControlManager.h" +#include "BaseControlDevice.h" #include "Console.h" -#include "../Utilities/FolderUtilities.h" -#include "RomLoader.h" -#include "CheatManager.h" #include "SaveStateManager.h" +#include "CheatManager.h" +#include "MovieRecorder.h" +#include "BatteryManager.h" +#include "VirtualFile.h" + +MesenMovie::MesenMovie() +{ +} MesenMovie::~MesenMovie() { Stop(); } +void MesenMovie::Stop() +{ + if(_playing) { + EndMovie(); + _playing = false; + } + ControlManager::UnregisterInputProvider(this); +} + +bool MesenMovie::SetInput(BaseControlDevice *device) +{ + if(_inputData.size() > _readIndex && _inputData[_readIndex].size() > _deviceIndex) { + device->SetTextState(_inputData[_readIndex][_deviceIndex]); + + _deviceIndex++; + if(_deviceIndex >= _inputData[_readIndex].size()) { + //Move to the next frame's data + _deviceIndex = 0; + _readIndex++; + } + } else { + Stop(); + } + return true; +} + bool MesenMovie::IsPlaying() { return _playing; } -bool MesenMovie::IsRecording() +vector MesenMovie::LoadBattery(string extension) { - return _recording; + vector batteryData; + _reader->ExtractFile("Battery" + extension, batteryData); + return batteryData; } -void MesenMovie::PushState(uint8_t port) +bool MesenMovie::Play(VirtualFile &file) { - if(_counter[port] > 0) { - uint16_t data = _lastState[port] << 8 | _counter[port]; - _data.PortData[port].push_back(data); + _movieFile = file; - _lastState[port] = 0; - _counter[port] = 0; + std::stringstream ss; + file.ReadFile(ss); + + _reader.reset(new ZipReader()); + _reader->LoadArchive(ss); + + stringstream settingsData, inputData; + if(!_reader->GetStream("GameSettings.txt", settingsData)) { + MessageManager::Log("[Movie] File not found: GameSettings.txt"); + return false; + } + if(!_reader->GetStream("Input.txt", inputData)) { + MessageManager::Log("[Movie] File not found: Input.txt"); + return false; } -} -void MesenMovie::RecordState(uint8_t port, uint8_t state) -{ - if(_recording) { - if(_lastState[port] != state || _counter[port] == 0) { - if(_counter[port] != 0) { - PushState(port); - } - _lastState[port] = state; - _counter[port] = 1; - } else { - _counter[port]++; - - if(_counter[port] == 255) { - PushState(port); - } + while(inputData) { + string line; + std::getline(inputData, line); + if(line.substr(0, 1) == "|") { + _inputData.push_back(StringUtilities::Split(line.substr(1), '|')); } } -} -uint8_t MesenMovie::GetState(uint8_t port) -{ - uint16_t data = --_data.PortData[port][_readPosition[port]]; - if((data & 0xFF) == 0) { - _readPosition[port]++; - } - - if(_readPosition[port] >= _data.DataSize[port]) { - //End of movie file - EndMovie(); - _playing = false; - } - - return (data >> 8); -} - -void MesenMovie::Reset() -{ - _startState.clear(); - _startState.seekg(0, ios::beg); - _startState.seekp(0, ios::beg); - - memset(_readPosition, 0, 4 * sizeof(uint32_t)); - memset(_counter, 0, 4); - memset(_lastState, 0, 4); - _data = MovieData(); - - _recording = false; - _playing = false; -} - -void MesenMovie::Record(string filename, bool reset) -{ - _filename = filename; - _file.open(filename, ios::out | ios::binary); - - if(_file) { - Console::Pause(); - - Reset(); - - if(reset) { - //Movies need a fixed power up state to be identical on each replay, force all 0s for RAM, no matter the setting - RamPowerOnState originalState = EmulationSettings::GetRamPowerOnState(); - EmulationSettings::SetRamPowerOnState(RamPowerOnState::AllZeros); - Console::Reset(false); - EmulationSettings::SetRamPowerOnState(originalState); - } else { - Console::SaveState(_startState); - } - - _recording = true; - - Console::Resume(); - - MessageManager::DisplayMessage("Movies", "MovieRecordingTo", FolderUtilities::GetFilename(filename, true)); - } -} - -void MesenMovie::Stop() -{ - if(_recording) { - _recording = false; - for(int i = 0; i < 4; i++) { - PushState(i); - } - Save(); - } -} - -bool MesenMovie::Play(stringstream &filestream, bool autoLoadRom) -{ - Stop(); - - Reset(); + _readIndex = 0; + _deviceIndex = 0; + ParseSettings(settingsData); + Console::Pause(); - if(Load(filestream, autoLoadRom)) { - if(_startState.tellp() > 0) { - //Restore state if one was present in the movie - Console::LoadState(_startState); - } + + BatteryManager::SetBatteryProvider(shared_from_this()); + ControlManager::RegisterInputProvider(this); + ApplySettings(); - CheatManager::SetCheats(_cheatList); - _playing = true; + if(!LoadGame()) { + Console::Resume(); + return false; } + + stringstream saveStateData; + if(_reader->GetStream("SaveState.mst", saveStateData)) { + if(!SaveStateManager::LoadState(saveStateData, true)) { + Console::Resume(); + return false; + } else { + //Reset to first line of the input log + //TODO: Change this to allow rewinding during movie playback + _readIndex = 0; + } + } + + _playing = true; + Console::Resume(); - return _playing; -} - -struct MovieHeader -{ - char Header[3] = { 'M', 'M', 'O' }; - uint32_t MesenVersion; - uint32_t MovieFormatVersion; - uint32_t SaveStateFormatVersion; - uint32_t RomCrc32; - uint32_t Region; - uint32_t ConsoleType; - uint8_t ControllerTypes[4]; - uint32_t ExpansionDevice; - uint32_t OverclockRate = 100; - bool OverclockAdjustApu = true; - uint32_t ExtraScanlinesBeforeNmi = 0; - uint32_t ExtraScanlinesAfterNmi = 0; - bool DisablePpu2004Reads = false; - bool DisablePaletteRead = false; - bool DisableOamAddrBug = false; - bool UseNes101Hvc101Behavior = false; - - uint32_t CheatCount; - uint32_t FilenameLength; -}; - -bool MesenMovie::Save() -{ - string romFilename = Console::GetRomName(); - - MovieHeader header = {}; - header.MesenVersion = EmulationSettings::GetMesenVersion(); - header.MovieFormatVersion = MesenMovie::MovieFormatVersion; - header.SaveStateFormatVersion = SaveStateManager::FileFormatVersion; - header.RomCrc32 = Console::GetHashInfo().Crc32Hash; - header.Region = (uint32_t)Console::GetModel(); - header.ConsoleType = (uint32_t)EmulationSettings::GetConsoleType(); - header.ExpansionDevice = (uint32_t)EmulationSettings::GetExpansionDevice(); - header.OverclockRate = (uint32_t)EmulationSettings::GetOverclockRate(); - header.OverclockAdjustApu = EmulationSettings::GetOverclockAdjustApu(); - header.DisablePpu2004Reads = EmulationSettings::CheckFlag(EmulationFlags::DisablePpu2004Reads); - header.DisablePaletteRead = EmulationSettings::CheckFlag(EmulationFlags::DisablePaletteRead); - header.DisableOamAddrBug = EmulationSettings::CheckFlag(EmulationFlags::DisableOamAddrBug); - header.UseNes101Hvc101Behavior = EmulationSettings::CheckFlag(EmulationFlags::UseNes101Hvc101Behavior); - for(int port = 0; port < 4; port++) { - header.ControllerTypes[port] = (uint32_t)EmulationSettings::GetControllerType(port); - } - header.FilenameLength = (uint32_t)romFilename.size(); - - vector cheatList = CheatManager::GetCheats(); - header.CheatCount = (uint32_t)cheatList.size(); - - _file.write((char*)header.Header, sizeof(header.Header)); - _file.write((char*)&header.MesenVersion, sizeof(header.MesenVersion)); - _file.write((char*)&header.MovieFormatVersion, sizeof(header.MovieFormatVersion)); - _file.write((char*)&header.SaveStateFormatVersion, sizeof(header.SaveStateFormatVersion)); - _file.write((char*)&header.RomCrc32, sizeof(header.RomCrc32)); - _file.write((char*)&header.Region, sizeof(header.Region)); - _file.write((char*)&header.ConsoleType, sizeof(header.ConsoleType)); - _file.write((char*)&header.ControllerTypes, sizeof(header.ControllerTypes)); - _file.write((char*)&header.ExpansionDevice, sizeof(header.ExpansionDevice)); - _file.write((char*)&header.OverclockRate, sizeof(header.OverclockRate)); - _file.write((char*)&header.OverclockAdjustApu, sizeof(header.OverclockAdjustApu)); - _file.write((char*)&header.ExtraScanlinesBeforeNmi, sizeof(header.ExtraScanlinesBeforeNmi)); - _file.write((char*)&header.ExtraScanlinesAfterNmi, sizeof(header.ExtraScanlinesAfterNmi)); - _file.write((char*)&header.DisablePpu2004Reads, sizeof(header.DisablePpu2004Reads)); - _file.write((char*)&header.DisablePaletteRead, sizeof(header.DisablePaletteRead)); - _file.write((char*)&header.DisableOamAddrBug, sizeof(header.DisableOamAddrBug)); - _file.write((char*)&header.UseNes101Hvc101Behavior, sizeof(header.UseNes101Hvc101Behavior)); - _file.write((char*)&header.CheatCount, sizeof(header.CheatCount)); - _file.write((char*)&header.FilenameLength, sizeof(header.FilenameLength)); - - _file.write((char*)romFilename.c_str(), header.FilenameLength); - - for(CodeInfo cheatCode : cheatList) { - _file.write((char*)&cheatCode.Address, sizeof(cheatCode.Address)); - _file.write((char*)&cheatCode.Value, sizeof(cheatCode.Value)); - _file.write((char*)&cheatCode.CompareValue, sizeof(cheatCode.CompareValue)); - _file.write((char*)&cheatCode.IsRelativeAddress, sizeof(cheatCode.IsRelativeAddress)); - } - - _data.SaveStateSize = (uint32_t)_startState.tellp(); - _file.write((char*)&_data.SaveStateSize, sizeof(uint32_t)); - - if(_data.SaveStateSize > 0) { - _startState.seekg(0, ios::beg); - uint8_t *stateBuffer = new uint8_t[_data.SaveStateSize]; - _startState.read((char*)stateBuffer, _data.SaveStateSize); - _file.write((char*)stateBuffer, _data.SaveStateSize); - delete[] stateBuffer; - } - - for(int i = 0; i < 4; i++) { - _data.DataSize[i] = (uint32_t)_data.PortData[i].size(); - _file.write((char*)&_data.DataSize[i], sizeof(uint32_t)); - if(_data.DataSize[i] > 0) { - _file.write((char*)&_data.PortData[i][0], _data.DataSize[i] * sizeof(uint16_t)); - } - } - - _file.close(); - - MessageManager::DisplayMessage("Movies", "MovieSaved", FolderUtilities::GetFilename(_filename, true)); return true; } -bool MesenMovie::Load(std::stringstream &file, bool autoLoadRom) +template +T FromString(string name, const vector &enumNames, T defaultValue) { - MovieHeader header = {}; - file.read((char*)header.Header, sizeof(header.Header)); - - if(memcmp(header.Header, "MMO", 3) != 0) { - //Invalid movie file - MessageManager::DisplayMessage("Movies", "MovieInvalid"); - return false; - } - - file.read((char*)&header.MesenVersion, sizeof(header.MesenVersion)); - - if(header.MesenVersion > EmulationSettings::GetMesenVersion()) { - MessageManager::DisplayMessage("Movies", "MovieNewerVersion"); - return false; - } - - file.read((char*)&header.MovieFormatVersion, sizeof(header.MovieFormatVersion)); - if(header.MovieFormatVersion < 2 || header.MovieFormatVersion > MesenMovie::MovieFormatVersion) { - //Currently compatible with version 2 & 3 - MessageManager::DisplayMessage("Movies", "MovieIncompatibleVersion"); - return false; - } - - file.read((char*)&header.SaveStateFormatVersion, sizeof(header.SaveStateFormatVersion)); - file.read((char*)&header.RomCrc32, sizeof(header.RomCrc32)); - file.read((char*)&header.Region, sizeof(header.Region)); - file.read((char*)&header.ConsoleType, sizeof(header.ConsoleType)); - file.read((char*)&header.ControllerTypes, sizeof(header.ControllerTypes)); - file.read((char*)&header.ExpansionDevice, sizeof(header.ExpansionDevice)); - if(header.MovieFormatVersion >= 3) { - //New fields in version 3 - file.read((char*)&header.OverclockRate, sizeof(header.OverclockRate)); - file.read((char*)&header.OverclockAdjustApu, sizeof(header.OverclockAdjustApu)); - } - if(header.MovieFormatVersion >= 4) { - file.read((char*)&header.ExtraScanlinesBeforeNmi, sizeof(header.ExtraScanlinesBeforeNmi)); - file.read((char*)&header.ExtraScanlinesAfterNmi, sizeof(header.ExtraScanlinesAfterNmi)); - } - if(header.MovieFormatVersion >= 5) { - file.read((char*)&header.DisablePpu2004Reads, sizeof(header.DisablePpu2004Reads)); - file.read((char*)&header.DisablePaletteRead, sizeof(header.DisablePaletteRead)); - file.read((char*)&header.DisableOamAddrBug, sizeof(header.DisableOamAddrBug)); - file.read((char*)&header.UseNes101Hvc101Behavior, sizeof(header.UseNes101Hvc101Behavior)); - } - - EmulationSettings::SetOverclockRate(header.OverclockRate, header.OverclockAdjustApu); - EmulationSettings::SetPpuNmiConfig(header.ExtraScanlinesBeforeNmi, header.ExtraScanlinesAfterNmi); - EmulationSettings::SetFlagState(EmulationFlags::UseNes101Hvc101Behavior, header.UseNes101Hvc101Behavior); - EmulationSettings::SetFlagState(EmulationFlags::DisablePpu2004Reads, header.DisablePpu2004Reads); - EmulationSettings::SetFlagState(EmulationFlags::DisablePaletteRead, header.DisablePaletteRead); - EmulationSettings::SetFlagState(EmulationFlags::DisableOamAddrBug, header.DisableOamAddrBug); - - file.read((char*)&header.CheatCount, sizeof(header.CheatCount)); - file.read((char*)&header.FilenameLength, sizeof(header.FilenameLength)); - - EmulationSettings::SetConsoleType((ConsoleType)header.ConsoleType); - EmulationSettings::SetExpansionDevice((ExpansionPortDevice)header.ExpansionDevice); - for(int port = 0; port < 4; port++) { - EmulationSettings::SetControllerType(port, (ControllerType)header.ControllerTypes[port]); - } - - char* romFilename = new char[header.FilenameLength + 1]; - memset(romFilename, 0, header.FilenameLength + 1); - file.read((char*)romFilename, header.FilenameLength); - - _cheatList.clear(); - CodeInfo cheatCode; - for(uint32_t i = 0; i < header.CheatCount; i++) { - file.read((char*)&cheatCode.Address, sizeof(cheatCode.Address)); - file.read((char*)&cheatCode.Value, sizeof(cheatCode.Value)); - file.read((char*)&cheatCode.CompareValue, sizeof(cheatCode.CompareValue)); - file.read((char*)&cheatCode.IsRelativeAddress, sizeof(cheatCode.IsRelativeAddress)); - _cheatList.push_back(cheatCode); - } - - file.read((char*)&_data.SaveStateSize, sizeof(uint32_t)); - - if(_data.SaveStateSize > 0) { - if(header.SaveStateFormatVersion != SaveStateManager::FileFormatVersion) { - MessageManager::DisplayMessage("Movies", "MovieIncompatibleVersion"); - return false; + for(size_t i = 0; i < enumNames.size(); i++) { + if(name == enumNames[i]) { + return (T)i; } } + return defaultValue; +} - bool loadedGame = true; - if(autoLoadRom) { - string currentRom = Console::GetRomName(); - if(currentRom.empty() || header.RomCrc32 != Console::GetHashInfo().Crc32Hash) { - //Loaded game isn't the same as the game used for the movie, attempt to load the correct game - HashInfo hashInfo; - hashInfo.Crc32Hash = header.RomCrc32; - loadedGame = Console::LoadROM(romFilename, hashInfo); +void MesenMovie::ParseSettings(stringstream &data) +{ + while(!data.eof()) { + string line; + std::getline(data, line); + + if(!line.empty()) { + size_t index = line.find_first_of(' '); + if(index >= 0) { + string name = line.substr(0, index); + string value = line.substr(index + 1); + + if(name == "Cheat") { + _cheats.push_back(value); + } else { + _settings[name] = value; + } + } + } + } +} + +bool MesenMovie::LoadGame() +{ + string mesenVersion = LoadString(_settings, MovieKeys::MesenVersion); + string gameFile = LoadString(_settings, MovieKeys::GameFile); + string sha1Hash = LoadString(_settings, MovieKeys::Sha1); + //string patchFile = LoadString(_settings, MovieKeys::PatchFile); + //string patchFileSha1 = LoadString(_settings, MovieKeys::PatchFileSha1); + //string patchedRomSha1 = LoadString(_settings, MovieKeys::PatchedRomSha1); + + HashInfo hashInfo; + hashInfo.Sha1Hash = sha1Hash; + + VirtualFile romFile = Console::FindMatchingRom(gameFile, hashInfo); + bool gameLoaded = false; + if(romFile.IsValid()) { + VirtualFile patchFile(_movieFile.GetFilePath(), "PatchData.dat"); + if(patchFile.IsValid()) { + gameLoaded = Console::LoadROM(romFile, patchFile); } else { - Console::Reset(false); + gameLoaded = Console::LoadROM(romFile); } } - if(loadedGame) { - if(_data.SaveStateSize > 0) { - uint8_t *stateBuffer = new uint8_t[_data.SaveStateSize]; - file.read((char*)stateBuffer, _data.SaveStateSize); - _startState.write((char*)stateBuffer, _data.SaveStateSize); - delete[] stateBuffer; - } + return gameLoaded; +} - for(int i = 0; i < 4; i++) { - file.read((char*)&_data.DataSize[i], sizeof(uint32_t)); +void MesenMovie::ApplySettings() +{ + NesModel region = FromString(LoadString(_settings, MovieKeys::Region), NesModelNames, NesModel::NTSC); + ConsoleType consoleType = FromString(LoadString(_settings, MovieKeys::ConsoleType), ConsoleTypeNames, ConsoleType::Nes); + ControllerType controller1 = FromString(LoadString(_settings, MovieKeys::Controller1), ControllerTypeNames, ControllerType::None); + ControllerType controller2 = FromString(LoadString(_settings, MovieKeys::Controller2), ControllerTypeNames, ControllerType::None); + ControllerType controller3 = FromString(LoadString(_settings, MovieKeys::Controller3), ControllerTypeNames, ControllerType::None); + ControllerType controller4 = FromString(LoadString(_settings, MovieKeys::Controller4), ControllerTypeNames, ControllerType::None); + ExpansionPortDevice expansionDevice = FromString(LoadString(_settings, MovieKeys::ExpansionDevice), ExpansionPortDeviceNames, ExpansionPortDevice::None); - uint16_t* readBuffer = new uint16_t[_data.DataSize[i]]; - file.read((char*)readBuffer, _data.DataSize[i] * sizeof(uint16_t)); - _data.PortData[i] = vector(readBuffer, readBuffer + _data.DataSize[i]); - delete[] readBuffer; + uint32_t ramPowerOnState = LoadInt(_settings, MovieKeys::RamPowerOnState); + if(ramPowerOnState == 0xFF) { + EmulationSettings::SetRamPowerOnState(RamPowerOnState::AllOnes); + } else { + EmulationSettings::SetRamPowerOnState(RamPowerOnState::AllZeros); + } + + EmulationSettings::SetZapperDetectionRadius(LoadInt(_settings, MovieKeys::ZapperDetectionRadius)); + + uint32_t cpuClockRate = LoadInt(_settings, MovieKeys::CpuClockRate); + if(cpuClockRate != 100) { + bool adjustApu = LoadBool(_settings, MovieKeys::OverclockAdjustApu); + EmulationSettings::SetOverclockRate(cpuClockRate, adjustApu); + } else { + EmulationSettings::SetOverclockRate(100, true); + } + + EmulationSettings::SetPpuNmiConfig( + LoadInt(_settings, MovieKeys::ExtraScanlinesBeforeNmi), + LoadInt(_settings, MovieKeys::ExtraScanlinesAfterNmi) + ); + + EmulationSettings::SetFlagState(EmulationFlags::DisablePpu2004Reads, LoadBool(_settings, MovieKeys::DisablePpu2004Reads)); + EmulationSettings::SetFlagState(EmulationFlags::DisablePaletteRead, LoadBool(_settings, MovieKeys::DisablePaletteRead)); + EmulationSettings::SetFlagState(EmulationFlags::DisableOamAddrBug, LoadBool(_settings, MovieKeys::DisableOamAddrBug)); + EmulationSettings::SetFlagState(EmulationFlags::UseNes101Hvc101Behavior, LoadBool(_settings, MovieKeys::UseNes101Hvc101Behavior)); + EmulationSettings::SetFlagState(EmulationFlags::EnableOamDecay, LoadBool(_settings, MovieKeys::EnableOamDecay)); + + //VS System flags + EmulationSettings::SetPpuModel(FromString(LoadString(_settings, MovieKeys::PpuModel), PpuModelNames, PpuModel::Ppu2C02)); + EmulationSettings::SetDipSwitches(HexUtilities::FromHex(LoadString(_settings, MovieKeys::DipSwitches))); + + LoadCheats(); +} + +uint32_t MesenMovie::LoadInt(std::unordered_map &settings, string name) +{ + auto result = settings.find(name); + if(result != settings.end()) { + try { + return (uint32_t)std::stoul(result->second); + } catch(std::exception ex) { + MessageManager::Log("[Movies] Invalid value for tag: " + name); + return 0; } } else { - MessageManager::DisplayMessage("Movies", "MovieMissingRom", romFilename); + return 0; } - delete[] romFilename; +} - return loadedGame; -} \ No newline at end of file +bool MesenMovie::LoadBool(std::unordered_map &settings, string name) +{ + auto result = settings.find(name); + if(result != settings.end()) { + if(result->second == "true") { + return true; + } else if(result->second == "false") { + return false; + } else { + MessageManager::Log("[Movies] Invalid value for tag: " + name); + return false; + } + } else { + return false; + } +} + +string MesenMovie::LoadString(std::unordered_map &settings, string name) +{ + auto result = settings.find(name); + if(result != settings.end()) { + return result->second; + } else { + return ""; + } +} + +void MesenMovie::LoadCheats() +{ + vector cheats; + for(string cheatData : _cheats) { + CodeInfo code; + if(LoadCheat(cheatData, code)) { + cheats.push_back(code); + } + } + CheatManager::SetCheats(cheats); +} + +bool MesenMovie::LoadCheat(string cheatData, CodeInfo &code) +{ + vector data = StringUtilities::Split(cheatData, ' '); + + if(data.size() >= 3) { + uint32_t address = HexUtilities::FromHex(data[0]); + uint8_t value = HexUtilities::FromHex(data[1]); + bool relativeAddress = data[2] == "true"; + int32_t compareValue = data.size() > 3 ? HexUtilities::FromHex(data[3]) : -1; + + code.Address = address; + code.Value = value; + code.IsRelativeAddress = relativeAddress; + code.CompareValue = compareValue; + return true; + } else { + MessageManager::Log("[Movie] Invalid cheat definition: " + cheatData); + } + return false; +} diff --git a/Core/MesenMovie.h b/Core/MesenMovie.h index 5ccfd3b2..dd658e28 100644 --- a/Core/MesenMovie.h +++ b/Core/MesenMovie.h @@ -3,46 +3,45 @@ #include "stdafx.h" #include "CheatManager.h" #include "MovieManager.h" +#include "ControlManager.h" +#include "BatteryManager.h" +#include "VirtualFile.h" -struct MovieData -{ - uint32_t SaveStateSize = 0; - uint32_t DataSize[4]; - vector PortData[4]; -}; +class ZipReader; -class MesenMovie : public IMovie +class MesenMovie : public IMovie, public IBatteryProvider, public std::enable_shared_from_this { private: - const uint32_t MovieFormatVersion = 5; - bool _recording = false; + VirtualFile _movieFile; + shared_ptr _reader; bool _playing = false; - uint8_t _counter[4]; - uint8_t _lastState[4]; - uint32_t _readPosition[4]; - ofstream _file; + size_t _readIndex = 0; + size_t _deviceIndex = 0; + vector> _inputData; + vector _cheats; + std::unordered_map _settings; string _filename; - stringstream _startState; - MovieData _data; - vector _cheatList; private: - void Reset(); - bool Save(); + void ParseSettings(stringstream &data); + void ApplySettings(); + bool LoadGame(); void Stop(); - bool Load(std::stringstream &file, bool autoLoadRom); -protected: - void PushState(uint8_t port); - void Record(string filename, bool reset); - bool Play(stringstream &filestream, bool autoLoadRom); - - bool IsPlaying(); - bool IsRecording(); + uint32_t LoadInt(std::unordered_map &settings, string name); + bool LoadBool(std::unordered_map &settings, string name); + string LoadString(std::unordered_map &settings, string name); + void LoadCheats(); + bool LoadCheat(string cheatData, CodeInfo &code); public: + MesenMovie(); ~MesenMovie(); - void RecordState(uint8_t port, uint8_t state); - uint8_t GetState(uint8_t port); + bool Play(VirtualFile &file) override; + bool SetInput(BaseControlDevice* device) override; + bool IsPlaying(); + + // Inherited via IBatteryProvider + virtual vector LoadBattery(string extension) override; }; \ No newline at end of file diff --git a/Core/MovieDataMessage.h b/Core/MovieDataMessage.h index a1de03d0..bafd7909 100644 --- a/Core/MovieDataMessage.h +++ b/Core/MovieDataMessage.h @@ -1,24 +1,25 @@ #pragma once #include "stdafx.h" #include "NetMessage.h" +#include "ControlDeviceState.h" class MovieDataMessage : public NetMessage { private: uint8_t _portNumber; - uint8_t _inputState; + ControlDeviceState _inputState; protected: virtual void ProtectedStreamState() { Stream(_portNumber); - Stream(_inputState); + StreamArray(_inputState.State); } public: MovieDataMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { } - MovieDataMessage(uint8_t state, uint8_t port) : NetMessage(MessageType::MovieData) + MovieDataMessage(ControlDeviceState state, uint8_t port) : NetMessage(MessageType::MovieData) { _portNumber = port; _inputState = state; @@ -29,7 +30,7 @@ public: return _portNumber; } - uint8_t GetInputState() + ControlDeviceState GetInputState() { return _inputState; } diff --git a/Core/MovieManager.cpp b/Core/MovieManager.cpp index c7d114e0..690de968 100644 --- a/Core/MovieManager.cpp +++ b/Core/MovieManager.cpp @@ -4,95 +4,68 @@ #include "MesenMovie.h" #include "BizhawkMovie.h" #include "FceuxMovie.h" +#include "MovieRecorder.h" +#include "VirtualFile.h" -shared_ptr MovieManager::_instance; +shared_ptr MovieManager::_player; +shared_ptr MovieManager::_recorder; void MovieManager::Record(string filename, bool reset) { - shared_ptr movie(new MesenMovie()); - movie->Record(filename, reset); - _instance = movie; -} - -void MovieManager::Play(string filename) -{ - ifstream file(filename, ios::in | ios::binary); - if(file.good()) { - std::stringstream ss; - ss << file.rdbuf(); - file.close(); - if(MovieManager::Play(ss, true)) { - MessageManager::DisplayMessage("Movies", "MoviePlaying", FolderUtilities::GetFilename(filename, true)); - } + shared_ptr recorder(new MovieRecorder()); + if(recorder->Record(filename, reset)) { + _recorder = recorder; } } -bool MovieManager::Play(std::stringstream &filestream, bool autoLoadRom) +void MovieManager::Play(VirtualFile file) { - char header[3] = { }; - filestream.read(header, 3); - filestream.seekg(0, ios::beg); + vector fileData; + if(file.IsValid() && file.ReadFile(fileData)) { + shared_ptr player; + if(memcmp(fileData.data(), "MMO", 3) == 0) { + //Old movie format, no longer supported + MessageManager::DisplayMessage("Movies", "MovieIncompatibleVersion"); + } else if(memcmp(fileData.data(), "PK", 2) == 0) { + //Mesen or Bizhawk movie + ZipReader reader; + reader.LoadArchive(fileData); - if(memcmp(header, "MMO", 3) == 0) { - shared_ptr movie(new MesenMovie()); - if(movie->Play(filestream, autoLoadRom)) { - _instance = movie; - return true; + vector files = reader.GetFileList(); + if(std::find(files.begin(), files.end(), "GameSettings.txt") != files.end()) { + player.reset(new MesenMovie()); + } else { + player.reset(new BizhawkMovie()); + } + } else if(memcmp(fileData.data(), "ver", 3) == 0) { + player.reset(new FceuxMovie()); } - } else if(memcmp(header, "PK", 2) == 0) { - shared_ptr movie(new BizhawkMovie()); - if(movie->Play(filestream, autoLoadRom)) { - _instance = movie; - return true; - } - } else if(memcmp(header, "ver", 3) == 0) { - shared_ptr movie(new FceuxMovie()); - if(movie->Play(filestream, autoLoadRom)) { - _instance = movie; - return true; + + if(player && player->Play(file)) { + _player = player; + + MessageManager::DisplayMessage("Movies", "MoviePlaying", file.GetFileName()); } } - - return false; } void MovieManager::Stop() { - if(_instance && _instance->IsPlaying()) { - MessageManager::DisplayMessage("Movies", "MovieEnded"); + _player.reset(); + + if(_recorder) { + _recorder->Stop(); + _recorder.reset(); } - _instance.reset(); } bool MovieManager::Playing() { - if(_instance) { - return _instance->IsPlaying(); - } else { - return false; - } + shared_ptr player = _player; + return player && player->IsPlaying(); } bool MovieManager::Recording() { - if(_instance) { - return _instance->IsRecording(); - } else { - return false; - } + return _recorder != nullptr; } - -void MovieManager::RecordState(uint8_t port, uint8_t value) -{ - if(_instance) { - _instance->RecordState(port, value); - } -} - -uint8_t MovieManager::GetState(uint8_t port) -{ - if(_instance) { - return _instance->GetState(port); - } - return 0; -} \ No newline at end of file diff --git a/Core/MovieManager.h b/Core/MovieManager.h index 4be169fd..d9af470b 100644 --- a/Core/MovieManager.h +++ b/Core/MovieManager.h @@ -2,8 +2,12 @@ #include "stdafx.h" #include "MessageManager.h" #include "EmulationSettings.h" +#include "IInputProvider.h" -class IMovie +class MovieRecorder; +class VirtualFile; + +class IMovie : public IInputProvider { protected: void EndMovie() @@ -16,29 +20,20 @@ protected: } public: - virtual void RecordState(uint8_t port, uint8_t value) = 0; - virtual uint8_t GetState(uint8_t port) = 0; - - virtual void Record(string filename, bool reset) = 0; - virtual bool Play(stringstream &filestream, bool autoLoadRom) = 0; - - virtual bool IsRecording() = 0; + virtual bool Play(VirtualFile &file) = 0; virtual bool IsPlaying() = 0; }; class MovieManager { private: - static shared_ptr _instance; + static shared_ptr _player; + static shared_ptr _recorder; public: static void Record(string filename, bool reset); - static void Play(string filename); - static bool Play(std::stringstream &filestream, bool autoLoadRom); + static void Play(VirtualFile file); static void Stop(); static bool Playing(); static bool Recording(); - - static void RecordState(uint8_t port, uint8_t value); - static uint8_t GetState(uint8_t port); }; \ No newline at end of file diff --git a/Core/MovieRecorder.cpp b/Core/MovieRecorder.cpp new file mode 100644 index 00000000..e31d3f6e --- /dev/null +++ b/Core/MovieRecorder.cpp @@ -0,0 +1,200 @@ +#include "stdafx.h" +#include "../Utilities/HexUtilities.h" +#include "../Utilities/FolderUtilities.h" +#include "../Utilities/ZipWriter.h" +#include "MovieRecorder.h" +#include "ControlManager.h" +#include "BaseControlDevice.h" +#include "Console.h" +#include "CheatManager.h" +#include "VirtualFile.h" +#include "SaveStateManager.h" + +MovieRecorder::MovieRecorder() +{ +} + +MovieRecorder::~MovieRecorder() +{ +} + +bool MovieRecorder::Record(string filename, bool reset) +{ + _filename = filename; + _writer.reset(new ZipWriter()); + _inputData = stringstream(); + _saveStateData = stringstream(); + _hasSaveState = false; + + if(!_writer->Initialize(_filename)) { + _writer.reset(); + return false; + } else { + Console::Pause(); + + //TODO: Prevent game from loading battery from disk when not recording battery files + BatteryManager::SetBatteryProvider(shared_from_this()); + + //Save existing battery files + BatteryManager::SetBatteryRecorder(shared_from_this()); + ControlManager::RegisterInputRecorder(this); + if(reset) { + Console::GetInstance()->PowerCycle(); + } else { + SaveStateManager::SaveState(_saveStateData); + _hasSaveState = true; + } + BatteryManager::SetBatteryRecorder(nullptr); + Console::Resume(); + + return true; + } +} + +void MovieRecorder::GetGameSettings(stringstream &out) +{ + NesModel model = Console::GetModel(); + + WriteString(out, MovieKeys::MesenVersion, EmulationSettings::GetMesenVersionString()); + WriteInt(out, MovieKeys::MovieFormatVersion, MovieRecorder::MovieFormatVersion); + + VirtualFile romFile = Console::GetRomPath(); + WriteString(out, MovieKeys::GameFile, romFile.GetFileName()); + WriteString(out, MovieKeys::Sha1, romFile.GetSha1Hash()); + + VirtualFile patchFile = Console::GetPatchFile(); + if(patchFile.IsValid()) { + WriteString(out, MovieKeys::PatchFile, patchFile.GetFileName()); + WriteString(out, MovieKeys::PatchFileSha1, patchFile.GetSha1Hash()); + WriteString(out, MovieKeys::PatchedRomSha1, Console::GetHashInfo().Sha1Hash); + } + + switch(model) { + case NesModel::NTSC: WriteString(out, MovieKeys::Region, "NTSC"); break; + case NesModel::PAL: WriteString(out, MovieKeys::Region, "PAL"); break; + case NesModel::Dendy: WriteString(out, MovieKeys::Region, "Dendy"); break; + } + + switch(EmulationSettings::GetConsoleType()) { + case ConsoleType::Nes: WriteString(out, MovieKeys::ConsoleType, "NES"); break; + case ConsoleType::Famicom: WriteString(out, MovieKeys::ConsoleType, "Famicom"); break; + } + + WriteString(out, MovieKeys::Controller1, ControllerTypeNames[(int)EmulationSettings::GetControllerType(0)]); + WriteString(out, MovieKeys::Controller2, ControllerTypeNames[(int)EmulationSettings::GetControllerType(1)]); + if(EmulationSettings::CheckFlag(EmulationFlags::HasFourScore)) { + WriteString(out, MovieKeys::Controller3, ControllerTypeNames[(int)EmulationSettings::GetControllerType(2)]); + WriteString(out, MovieKeys::Controller4, ControllerTypeNames[(int)EmulationSettings::GetControllerType(3)]); + } + + if(EmulationSettings::GetConsoleType() == ConsoleType::Famicom) { + WriteString(out, MovieKeys::ExpansionDevice, ExpansionPortDeviceNames[(int)EmulationSettings::GetExpansionDevice()]); + } + + WriteInt(out, MovieKeys::CpuClockRate, EmulationSettings::GetOverclockRateSetting()); + WriteInt(out, MovieKeys::ExtraScanlinesBeforeNmi, EmulationSettings::GetPpuExtraScanlinesBeforeNmi()); + WriteInt(out, MovieKeys::ExtraScanlinesAfterNmi, EmulationSettings::GetPpuExtraScanlinesAfterNmi()); + + if(EmulationSettings::GetOverclockRateSetting() != 100) { + WriteBool(out, MovieKeys::OverclockAdjustApu, EmulationSettings::GetOverclockAdjustApu()); + } + WriteBool(out, MovieKeys::DisablePpu2004Reads, EmulationSettings::CheckFlag(EmulationFlags::DisablePpu2004Reads)); + WriteBool(out, MovieKeys::DisablePaletteRead, EmulationSettings::CheckFlag(EmulationFlags::DisablePaletteRead)); + WriteBool(out, MovieKeys::DisableOamAddrBug, EmulationSettings::CheckFlag(EmulationFlags::DisableOamAddrBug)); + WriteBool(out, MovieKeys::UseNes101Hvc101Behavior, EmulationSettings::CheckFlag(EmulationFlags::UseNes101Hvc101Behavior)); + WriteBool(out, MovieKeys::EnableOamDecay, EmulationSettings::CheckFlag(EmulationFlags::EnableOamDecay)); + WriteBool(out, MovieKeys::DisablePpuReset, EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset)); + + WriteInt(out, MovieKeys::ZapperDetectionRadius, EmulationSettings::GetZapperDetectionRadius()); + + switch(EmulationSettings::GetRamPowerOnState()) { + case RamPowerOnState::AllZeros: WriteInt(out, MovieKeys::RamPowerOnState, 0x00); break; + case RamPowerOnState::AllOnes: WriteInt(out, MovieKeys::RamPowerOnState, 0xFF); break; + case RamPowerOnState::Random: WriteInt(out, MovieKeys::RamPowerOnState, -1); break; //TODO: Shouldn't be used for movies + } + + //VS System flags + if(Console::GetInstance()->GetAvailableFeatures() == ConsoleFeatures::VsSystem) { + WriteString(out, MovieKeys::DipSwitches, HexUtilities::ToHex(EmulationSettings::GetDipSwitches())); + WriteString(out, MovieKeys::PpuModel, PpuModelNames[(int)EmulationSettings::GetPpuModel()]); + } + + for(CodeInfo &code : CheatManager::GetCheats()) { + WriteCheat(out, code); + } +} + +void MovieRecorder::WriteCheat(stringstream &out, CodeInfo &code) +{ + out << "Cheat " << + HexUtilities::ToHex(code.Address) << " " << + HexUtilities::ToHex(code.Value) << " " << + (code.IsRelativeAddress ? "true" : "false") << " " << + (code.CompareValue < 0 ? HexUtilities::ToHex((uint8_t)code.CompareValue) : "") << "\n"; +} + +void MovieRecorder::WriteString(stringstream &out, string name, string value) +{ + out << name << " " << value << "\n"; +} + +void MovieRecorder::WriteInt(stringstream &out, string name, uint32_t value) +{ + out << name << " " << std::to_string(value) << "\n"; +} + +void MovieRecorder::WriteBool(stringstream &out, string name, bool enabled) +{ + out << name << " " << (enabled ? "true" : "false") << "\n"; +} + +bool MovieRecorder::Stop() +{ + if(_writer) { + ControlManager::UnregisterInputRecorder(this); + + _writer->AddFile(_inputData, "Input.txt"); + + stringstream out; + GetGameSettings(out); + _writer->AddFile(out, "GameSettings.txt"); + + VirtualFile patchFile = Console::GetPatchFile(); + vector patchData; + if(patchFile.IsValid() && patchFile.ReadFile(patchData)) { + _writer->AddFile(patchData, "PatchData.dat"); + } + + if(_hasSaveState) { + _writer->AddFile(_saveStateData, "SaveState.mst"); + } + + for(auto kvp : _batteryData) { + _writer->AddFile(kvp.second, "Battery" + kvp.first); + } + + return _writer->Save(); + } + + return false; +} + +void MovieRecorder::RecordInput(BaseControlDevice *device) +{ + _inputData << ("|" + device->GetTextState()); +} + +void MovieRecorder::EndFrame() +{ + _inputData << "\n"; +} + +void MovieRecorder::OnLoadBattery(string extension, vector batteryData) +{ + _batteryData[extension] = batteryData; +} + +vector MovieRecorder::LoadBattery(string extension) +{ + return vector(); +} diff --git a/Core/MovieRecorder.h b/Core/MovieRecorder.h new file mode 100644 index 00000000..b37088ce --- /dev/null +++ b/Core/MovieRecorder.h @@ -0,0 +1,74 @@ +#pragma once +#include "stdafx.h" +#include +#include "IInputRecorder.h" +#include "CheatManager.h" +#include "BatteryManager.h" + +class ZipWriter; + +class MovieRecorder : public IInputRecorder, public IBatteryRecorder, public IBatteryProvider, public std::enable_shared_from_this +{ +private: + static const uint32_t MovieFormatVersion = 1; + string _filename; + unique_ptr _writer; + std::unordered_map> _batteryData; + stringstream _inputData; + bool _hasSaveState; + stringstream _saveStateData; + + void GetGameSettings(stringstream &out); + void WriteCheat(stringstream &out, CodeInfo &code); + void WriteString(stringstream &out, string name, string value); + void WriteInt(stringstream &out, string name, uint32_t value); + void WriteBool(stringstream &out, string name, bool enabled); + +public: + MovieRecorder(); + ~MovieRecorder(); + + bool Record(string filename, bool reset); + bool Stop(); + + void RecordInput(BaseControlDevice *device) override; + void EndFrame() override; + + // Inherited via IBatteryRecorder + virtual void OnLoadBattery(string extension, vector batteryData) override; + + // Inherited via IBatteryProvider + virtual vector LoadBattery(string extension) override; +}; + +namespace MovieKeys +{ + constexpr const char* MesenVersion = "MesenVersion"; + constexpr const char* MovieFormatVersion = "MovieFormatVersion"; + constexpr const char* GameFile = "GameFile"; + constexpr const char* Sha1 = "SHA1"; + constexpr const char* PatchFile = "PatchFile"; + constexpr const char* PatchFileSha1 = "PatchFileSHA1"; + constexpr const char* PatchedRomSha1 = "PatchedRomSHA1"; + constexpr const char* Region = "Region"; + constexpr const char* ConsoleType = "ConsoleType"; + constexpr const char* Controller1 = "Controller1"; + constexpr const char* Controller2 = "Controller2"; + constexpr const char* Controller3 = "Controller3"; + constexpr const char* Controller4 = "Controller4"; + constexpr const char* ExpansionDevice = "ExpansionDevice"; + constexpr const char* CpuClockRate = "CpuClockRate"; + constexpr const char* ExtraScanlinesBeforeNmi = "ExtraScanlinesBeforeNmi"; + constexpr const char* ExtraScanlinesAfterNmi = "ExtraScanlinesAfterNmi"; + constexpr const char* OverclockAdjustApu = "OverclockAdjustApu"; + constexpr const char* DisablePpu2004Reads = "DisablePpu2004Reads"; + constexpr const char* DisablePaletteRead = "DisablePaletteRead"; + constexpr const char* DisableOamAddrBug = "DisableOamAddrBug"; + constexpr const char* UseNes101Hvc101Behavior = "UseNes101Hvc101Behavior"; + constexpr const char* EnableOamDecay = "EnableOamDecay"; + constexpr const char* DisablePpuReset = "DisablePpuReset"; + constexpr const char* ZapperDetectionRadius = "ZapperDetectionRadius"; + constexpr const char* RamPowerOnState = "RamPowerOnState"; + constexpr const char* PpuModel = "PpuModel"; + constexpr const char* DipSwitches = "DipSwitches"; +}; \ No newline at end of file diff --git a/Core/NetMessage.h b/Core/NetMessage.h index 50f4e3c1..c571661f 100644 --- a/Core/NetMessage.h +++ b/Core/NetMessage.h @@ -58,6 +58,26 @@ protected: } } + void StreamArray(vector &data) + { + uint32_t length = (uint32_t)data.size(); + Stream(length); + if(_sending) { + uint8_t* bytes = (uint8_t*)data.data(); + for(uint32_t i = 0, len = length; i < len; i++) { + _buffer.push_back(bytes[i]); + _position++; + } + } else { + data.resize(length, 0); + uint8_t* bytes = (uint8_t*)data.data(); + for(uint32_t i = 0, len = length; i < len; i++) { + bytes[i] = _buffer[_position]; + _position++; + } + } + } + void StreamState() { Stream(_type); diff --git a/Core/NsfMapper.cpp b/Core/NsfMapper.cpp index 77ff7b46..3bc03bc6 100644 --- a/Core/NsfMapper.cpp +++ b/Core/NsfMapper.cpp @@ -448,3 +448,8 @@ NsfHeader NsfMapper::GetNsfHeader() { return _nsfHeader; } + +ConsoleFeatures NsfMapper::GetAvailableFeatures() +{ + return ConsoleFeatures::Nsf; +} \ No newline at end of file diff --git a/Core/NsfMapper.h b/Core/NsfMapper.h index d0979a9b..3fc1a00e 100644 --- a/Core/NsfMapper.h +++ b/Core/NsfMapper.h @@ -121,6 +121,7 @@ public: static NsfMapper* GetInstance(); void SetNesModel(NesModel model) override; + ConsoleFeatures GetAvailableFeatures() override; void SelectTrack(uint8_t trackNumber); uint8_t GetCurrentTrack(); diff --git a/Core/NsfPpu.h b/Core/NsfPpu.h index 0ba6dba5..0b67078f 100644 --- a/Core/NsfPpu.h +++ b/Core/NsfPpu.h @@ -15,8 +15,5 @@ protected: } public: - NsfPpu(BaseMapper* mapper) : PPU(mapper) - { - - } + using PPU::PPU; }; \ No newline at end of file diff --git a/Core/OekaKidsTablet.cpp b/Core/OekaKidsTablet.cpp deleted file mode 100644 index 0da7112f..00000000 --- a/Core/OekaKidsTablet.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "stdafx.h" -#include "OekaKidsTablet.h" -#include "ControlManager.h" -#include "GameServerConnection.h" -#include "IKeyManager.h" -#include "IKeyManager.h" - -void OekaKidsTablet::StreamState(bool saving) -{ - BaseControlDevice::StreamState(saving); - Stream(_xPosition, _yPosition, _touch, _click); -} - -uint32_t OekaKidsTablet::GetNetPlayState() -{ - //Used by netplay - uint32_t state = _xPosition | (_yPosition << 8); - - if(_touch) { - state |= 0x40000000; - } - - if(_click) { - state |= 0x80000000; - } - - return state; -} - -uint8_t OekaKidsTablet::ProcessNetPlayState(uint32_t netplayState) -{ - _xPosition = netplayState & 0xFF; - _yPosition = (netplayState >> 8) & 0xFF; - - _touch = ((netplayState >> 30) & 0x01) == 0x01; - _click = ((netplayState >> 31) & 0x01) == 0x01; - - return RefreshState(); -} - -uint8_t OekaKidsTablet::RefreshState() -{ - if(_strobe) { - if(_shift) { - return (_stateBuffer & 0x40000) ? 0x00 : 0x08; - } else { - return 0x04; - } - } else { - return 0x00; - } -} - -uint8_t OekaKidsTablet::GetPortOutput() -{ - return GetControlState(); -} - -void OekaKidsTablet::WriteRam(uint8_t value) -{ - _strobe = (value & 0x01) == 0x01; - bool shift = ((value >> 1) & 0x01) == 0x01; - - if(_strobe) { - if(!_shift && shift) { - _stateBuffer <<= 1; - } - _shift = shift; - } else { - if(!GameServerConnection::GetNetPlayDevice(_port)) { - MousePosition position = ControlManager::GetMousePosition(); - _xPosition = (int32_t)((double)std::max(0, position.X + 8) / 256.0 * 240); - _yPosition = (int32_t)((double)std::max(0, position.Y - 14) / 240.0 * 256); - - if(!EmulationSettings::CheckFlag(EmulationFlags::InBackground) || EmulationSettings::CheckFlag(EmulationFlags::AllowBackgroundInput)) { - _touch = position.Y >= 48 || ControlManager::IsMouseButtonPressed(MouseButton::LeftButton); - _click = ControlManager::IsMouseButtonPressed(MouseButton::LeftButton); - } else { - _touch = false; - _click = false; - } - } - - _stateBuffer = ((_xPosition & 0xFF) << 10) | ((_yPosition & 0xFF) << 2) | (_touch ? 0x02 : 0x00) | (_click ? 0x01 : 0x00); - } -} diff --git a/Core/OekaKidsTablet.h b/Core/OekaKidsTablet.h index 1a7abfad..9e1bda33 100644 --- a/Core/OekaKidsTablet.h +++ b/Core/OekaKidsTablet.h @@ -9,21 +9,70 @@ private: bool _shift = false; uint32_t _stateBuffer = 0; - bool _click = false; - bool _touch = false; - int32_t _xPosition = 0; - int32_t _yPosition = 0; - protected: - virtual uint8_t RefreshState() override; - uint8_t ProcessNetPlayState(uint32_t netplayState) override; - void StreamState(bool saving) override; + enum Buttons { Click, Touch }; + bool HasCoordinates() override { return true; } + + string GetKeyNames() override + { + return "CT"; + } + + void InternalSetStateFromInput() override + { + if(EmulationSettings::InputEnabled()) { + MousePosition pos = KeyManager::GetMousePosition(); + SetPressedState(Buttons::Click, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + SetPressedState(Buttons::Touch, pos.Y >= 48 || KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + SetCoordinates(pos); + } + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_strobe, _shift, _stateBuffer); + } public: - using BaseControlDevice::BaseControlDevice; + OekaKidsTablet() : BaseControlDevice(BaseControlDevice::ExpDevicePort) + { + } - virtual uint8_t GetPortOutput() override; - virtual uint32_t GetNetPlayState() override; + uint8_t ReadRAM(uint16_t addr) + { + if(addr == 0x4017) { + if(_strobe) { + if(_shift) { + return (_stateBuffer & 0x40000) ? 0x00 : 0x08; + } else { + return 0x04; + } + } else { + return 0x00; + } + } - void WriteRam(uint8_t value); + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) + { + _strobe = (value & 0x01) == 0x01; + bool shift = ((value >> 1) & 0x01) == 0x01; + + if(_strobe) { + if(!_shift && shift) { + _stateBuffer <<= 1; + } + _shift = shift; + } else { + MousePosition pos = GetCoordinates(); + + uint8_t xPosition = (uint8_t)((double)std::max(0, pos.X + 8) / 256.0 * 240); + uint8_t yPosition = (uint8_t)((double)std::max(0, pos.Y - 14) / 240.0 * 256); + + _stateBuffer = (xPosition << 10) | (yPosition << 2) | (IsPressed(OekaKidsTablet::Buttons::Touch) ? 0x02 : 0x00) | (IsPressed(OekaKidsTablet::Buttons::Click) ? 0x01 : 0x00); + } + } }; \ No newline at end of file diff --git a/Core/PPU.cpp b/Core/PPU.cpp index d1a28f44..f97346fa 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -7,16 +7,18 @@ #include "Debugger.h" #include "BaseMapper.h" #include "RewindManager.h" +#include "ControlManager.h" PPU* PPU::Instance = nullptr; -PPU::PPU(BaseMapper *mapper) +PPU::PPU(BaseMapper *mapper, ControlManager *controlManager) { PPU::Instance = this; EmulationSettings::SetPpuModel(PpuModel::Ppu2C02); _mapper = mapper; + _controlManager = controlManager; _outputBuffers[0] = new uint16_t[256 * 240]; _outputBuffers[1] = new uint16_t[256 * 240]; @@ -1075,6 +1077,10 @@ void PPU::Exec() Debugger::ProcessPpuCycle(); UpdateApuStatus(); + + if(_scanline == EmulationSettings::GetInputPollScanline()) { + _controlManager->UpdateInputState(); + } //Cycle = 0 if(_scanline == -1) { diff --git a/Core/PPU.h b/Core/PPU.h index 8f3f1c20..d5711efe 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -10,6 +10,7 @@ enum class NesModel; class BaseMapper; +class ControlManager; enum PPURegisters { @@ -100,6 +101,8 @@ class PPU : public IMemoryHandler, public Snapshotable int32_t _oamDecayCycles[0x40]; bool _enableOamDecay; + + ControlManager *_controlManager; void UpdateStatusFlag(); @@ -158,7 +161,7 @@ class PPU : public IMemoryHandler, public Snapshotable static const uint32_t PixelCount = 256*240; static const uint32_t OutputBufferSize = 256*240*2; - PPU(BaseMapper *mapper); + PPU(BaseMapper *mapper, ControlManager *controlManager); virtual ~PPU(); void Reset(); diff --git a/Core/PachinkoController.h b/Core/PachinkoController.h new file mode 100644 index 00000000..dac3f2f2 --- /dev/null +++ b/Core/PachinkoController.h @@ -0,0 +1,60 @@ +#pragma once +#include "stdafx.h" +#include "StandardController.h" + +class PachinkoController : public StandardController +{ +private: + uint8_t _analogData = 0; + +protected: + enum PachinkoButtons { Press = 8, Release = 9 }; + + void InternalSetStateFromInput() override + { + StandardController::InternalSetStateFromInput(); + + for(KeyMapping keyMapping : _keyMappings) { + SetPressedState(PachinkoButtons::Press, keyMapping.PachinkoButtons[0]); + SetPressedState(PachinkoButtons::Release, keyMapping.PachinkoButtons[1]); + } + } + +public: + PachinkoController(KeyMappingSet keyMappings) : StandardController(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) + { + uint8_t output = 0; + if(addr == 0x4016) { + output = (_stateBuffer & 0x01) << 1; + _stateBuffer >>= 1; + StrobeProcessRead(); + } + return output; + } + + void RefreshStateBuffer() override + { + if(_analogData < 63 && IsPressed(PachinkoController::PachinkoButtons::Press)) { + _analogData++; + } else if(_analogData > 0 && IsPressed(PachinkoController::PachinkoButtons::Release)) { + _analogData--; + } + + uint8_t analogData = + ((_analogData & 0x01) << 7) | + ((_analogData & 0x02) << 5) | + ((_analogData & 0x04) << 3) | + ((_analogData & 0x08) << 1) | + ((_analogData & 0x10) >> 1) | + ((_analogData & 0x20) >> 3) | + ((_analogData & 0x40) >> 5) | + ((_analogData & 0x80) >> 7); + + StandardController::RefreshStateBuffer(); + _stateBuffer = (_stateBuffer & 0xFF) | (~analogData << 8); + } +}; \ No newline at end of file diff --git a/Core/PartyTap.h b/Core/PartyTap.h new file mode 100644 index 00000000..c9e5951b --- /dev/null +++ b/Core/PartyTap.h @@ -0,0 +1,73 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class PartyTap : public BaseControlDevice +{ +private: + uint8_t _stateBuffer = 0; + uint8_t _readCount = 0; + bool _enabled = false; + +protected: + enum Buttons { B1 = 0, B2, B3, B4, B5, B6 }; + + string GetKeyNames() override + { + return "123456"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + for(int i = 0; i < 6; i++) { + SetPressedState(i, keyMapping.PartyTapButtons[i]); + } + } + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_stateBuffer, _readCount, _enabled); + } + +public: + PartyTap(KeyMappingSet keyMappings) : BaseControlDevice(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + if(_readCount < 2) { + uint8_t value = (_stateBuffer & 0x7) << 2; + _stateBuffer >>= 3; + StrobeProcessRead(); + _readCount++; + return value; + } else { + //"After 1st/2nd reads, a detection value can be read : $4017 & $1C == $14" + return 0x14; + } + } + return 0; + } + + void RefreshStateBuffer() override + { + _readCount = 0; + _stateBuffer = + IsPressed(PartyTap::Buttons::B1) ? 1 : 0 | + IsPressed(PartyTap::Buttons::B2) ? 2 : 0 | + IsPressed(PartyTap::Buttons::B3) ? 4 : 0 | + IsPressed(PartyTap::Buttons::B4) ? 8 : 0 | + IsPressed(PartyTap::Buttons::B5) ? 16 : 0 | + IsPressed(PartyTap::Buttons::B6) ? 32 : 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } +}; \ No newline at end of file diff --git a/Core/PowerPad.h b/Core/PowerPad.h new file mode 100644 index 00000000..bb1b1be3 --- /dev/null +++ b/Core/PowerPad.h @@ -0,0 +1,71 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class PowerPad : public BaseControlDevice +{ +private: + uint8_t _stateBufferL; + uint8_t _stateBufferH; + +protected: + string GetKeyNames() override + { + return "123456789ABC"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + for(int i = 0; i < 12; i++) { + SetPressedState(i, keyMapping.PowerPadButtons[i]); + } + } + } + + void RefreshStateBuffer() override + { + uint8_t pressedKeys[12] = {}; + for(int i = 0; i < 12; i++) { + pressedKeys[i] |= IsPressed(i) ? 1 : 0; + } + + //"Serial data from buttons 2, 1, 5, 9, 6, 10, 11, 7" + _stateBufferL = pressedKeys[1] | (pressedKeys[0] << 1) | (pressedKeys[4] << 2) | (pressedKeys[8] << 3) | (pressedKeys[5] << 4) | (pressedKeys[9] << 5) | (pressedKeys[10] << 6) | (pressedKeys[6] << 7); + + //"Serial data from buttons 4, 3, 12, 8 (following 4 bits read as H=1)" + _stateBufferH = pressedKeys[3] | (pressedKeys[2] << 1) | (pressedKeys[11] << 2) | (pressedKeys[7] << 3) | 0xF0; + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_stateBufferL, _stateBufferH); + } + +public: + PowerPad(uint8_t port, KeyMappingSet keyMappings) : BaseControlDevice(port, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; + if(IsCurrentPort(addr)) { + output = ((_stateBufferH & 0x01) << 4) | ((_stateBufferL & 0x01) << 3); + _stateBufferL >>= 1; + _stateBufferH >>= 1; + + _stateBufferL |= 0x80; + _stateBufferH |= 0x80; + + StrobeProcessRead(); + } + return output; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } +}; \ No newline at end of file diff --git a/Core/RecordedRomTest.cpp b/Core/RecordedRomTest.cpp index 02093548..12eba0a7 100644 --- a/Core/RecordedRomTest.cpp +++ b/Core/RecordedRomTest.cpp @@ -7,6 +7,7 @@ #include "Debugger.h" #include "MovieManager.h" #include "PPU.h" +#include "VirtualFile.h" #include "../Utilities/FolderUtilities.h" #include "../Utilities/md5.h" #include "../Utilities/ZipWriter.h" @@ -129,8 +130,12 @@ void RecordedRomTest::Record(string filename, bool reset) } } -void RecordedRomTest::RecordFromMovie(string testFilename, stringstream &movieStream, bool autoLoadRom) +void RecordedRomTest::RecordFromMovie(string testFilename, VirtualFile movieFile) { + if(!movieFile.IsValid()) { + return; + } + _filename = testFilename; string mrtFilename = FolderUtilities::CombinePath(FolderUtilities::GetFolderName(testFilename), FolderUtilities::GetFilename(testFilename, false) + ".mrt"); @@ -143,33 +148,21 @@ void RecordedRomTest::RecordFromMovie(string testFilename, stringstream &movieSt _recording = true; //Start playing movie - MovieManager::Play(movieStream, autoLoadRom); - movieStream.seekg(0, ios::beg); - _movieStream << movieStream.rdbuf(); - + MovieManager::Play(movieFile); + movieFile.ReadFile(_movieData); _recordingFromMovie = true; Console::Resume(); } } -void RecordedRomTest::RecordFromMovie(string testFilename, string movieFilename) -{ - stringstream ss; - ifstream file(movieFilename, ios::in | ios::binary); - if(file) { - ss << file.rdbuf(); - file.close(); - RecordFromMovie(testFilename, ss, true); - } -} - void RecordedRomTest::RecordFromTest(string newTestFilename, string existingTestFilename) { ZipReader zipReader; zipReader.LoadArchive(existingTestFilename); - std::stringstream testMovie = zipReader.GetStream("TestMovie.mmo"); - std::stringstream testRom = zipReader.GetStream("TestRom.nes"); + stringstream testMovie, testRom; + zipReader.GetStream("TestMovie.mmo", testMovie); + zipReader.GetStream("TestRom.nes", testRom); VirtualFile romFile(testRom, newTestFilename); if(testMovie && testRom) { @@ -178,7 +171,7 @@ void RecordedRomTest::RecordFromTest(string newTestFilename, string existingTest testRom.seekg(0, ios::beg); _romStream << testRom.rdbuf(); - RecordFromMovie(newTestFilename, testMovie, false); + RecordFromMovie(newTestFilename, VirtualFile(existingTestFilename, "TestMovie.mmo")); Console::Resume(); } } @@ -198,13 +191,16 @@ int32_t RecordedRomTest::Run(string filename) EmulationSettings::SetNesModel(NesModel::NTSC); } + VirtualFile testMovie(filename, "TestMovie.mmo"); + VirtualFile testRom(filename, "TestRom.nes"); + ZipReader zipReader; zipReader.LoadArchive(filename); - std::stringstream testData = zipReader.GetStream("TestData.mrt"); - std::stringstream testMovie = zipReader.GetStream("TestMovie.mmo"); - std::stringstream testRom = zipReader.GetStream("TestRom.nes"); + + stringstream testData; + zipReader.GetStream("TestData.mrt", testData); - if(testData && testMovie && testRom) { + if(testData && testMovie.IsValid() && testRom.IsValid()) { char header[3]; testData.read((char*)&header, 3); if(memcmp((char*)&header, "MRT", 3) != 0) { @@ -235,26 +231,10 @@ int32_t RecordedRomTest::Run(string filename) _currentCount = _repetitionCount.front(); _repetitionCount.pop_front(); - shared_ptr reader = ArchiveReader::GetReader(testRom); - if(reader) { - //Some older test files contain a zip file instead of a rom file, grab the first rom we can find in the zip - vector files = reader->GetFileList({ ".nes" }); - vector fileData; - if(files.size() > 0 && reader->ExtractFile(files[0], fileData)) { - testRom = std::stringstream(); - testRom.write((char*)fileData.data(), fileData.size()); - testRom.seekg(0, ios::beg); - } else { - return -3; - } - } - - VirtualFile testRomFile(testRom, filename); - //Start playing movie - if(Console::LoadROM(testRomFile)) { + if(Console::LoadROM(testRom)) { _runningTest = true; - MovieManager::Play(testMovie, false); + MovieManager::Play(testMovie); Console::Resume(); EmulationSettings::ClearFlags(EmulationFlags::Paused); @@ -305,14 +285,15 @@ void RecordedRomTest::Save() _file.close(); - ZipWriter writer(_filename); + ZipWriter writer; + writer.Initialize(_filename); string mrtFilename = FolderUtilities::CombinePath(FolderUtilities::GetFolderName(_filename), FolderUtilities::GetFilename(_filename, false) + ".mrt"); writer.AddFile(mrtFilename, "TestData.mrt"); std::remove(mrtFilename.c_str()); if(_recordingFromMovie) { - writer.AddFile(_movieStream, "TestMovie.mmo"); + writer.AddFile(_movieData, "TestMovie.mmo"); writer.AddFile(_romStream, "TestRom.nes"); } else { string mmoFilename = FolderUtilities::CombinePath(FolderUtilities::GetFolderName(_filename), FolderUtilities::GetFilename(_filename, false) + ".mmo"); @@ -321,7 +302,7 @@ void RecordedRomTest::Save() writer.AddFile(Console::GetRomPath().GetFilePath(), "TestRom.nes"); } - + writer.Save(); MessageManager::DisplayMessage("Test", "TestFileSavedTo", FolderUtilities::GetFilename(_filename, true)); } \ No newline at end of file diff --git a/Core/RecordedRomTest.h b/Core/RecordedRomTest.h index 4eab3f01..26c22c85 100644 --- a/Core/RecordedRomTest.h +++ b/Core/RecordedRomTest.h @@ -5,6 +5,8 @@ #include "INotificationListener.h" #include "../Utilities/AutoResetEvent.h" +class VirtualFile; + class RecordedRomTest : public INotificationListener { private: @@ -18,8 +20,8 @@ private: std::deque _repetitionCount; uint8_t _currentCount; - //Used when make a test out of an existing movie/test - stringstream _movieStream; + //Used when making a test out of an existing movie/test + vector _movieData; stringstream _romStream; string _filename; @@ -32,7 +34,6 @@ private: void ValidateFrame(uint16_t* ppuFrameBuffer); void SaveFrame(uint16_t* ppuFrameBuffer); void Save(); - void RecordFromMovie(string testFilename, stringstream &movieStream, bool autoLoadRom); public: RecordedRomTest(); @@ -40,7 +41,7 @@ public: void ProcessNotification(ConsoleNotificationType type, void* parameter) override; void Record(string filename, bool reset); - void RecordFromMovie(string testFilename, string movieFilename); + void RecordFromMovie(string testFilename, VirtualFile movieFile); void RecordFromTest(string newTestFilename, string existingTestFilename); int32_t Run(string filename); void Stop(); diff --git a/Core/RewindData.h b/Core/RewindData.h index a7fda34f..2ce0e6a2 100644 --- a/Core/RewindData.h +++ b/Core/RewindData.h @@ -1,6 +1,7 @@ #pragma once #include "stdafx.h" #include +#include "BaseControlDevice.h" class RewindData { @@ -11,7 +12,7 @@ private: void CompressState(string stateData, vector &compressedState); public: - std::deque InputLogs[4]; + std::deque InputLogs[BaseControlDevice::PortCount]; int32_t FrameCount = 0; void LoadState(); diff --git a/Core/RewindManager.cpp b/Core/RewindManager.cpp index 4b90e718..59c891b5 100644 --- a/Core/RewindManager.cpp +++ b/Core/RewindManager.cpp @@ -4,6 +4,7 @@ #include "Console.h" #include "VideoRenderer.h" #include "SoundMixer.h" +#include "BaseControlDevice.h" RewindManager* RewindManager::_instance = nullptr; @@ -15,6 +16,8 @@ RewindManager::RewindManager() AddHistoryBlock(); MessageManager::RegisterNotificationListener(this); + ControlManager::RegisterInputProvider(this); + ControlManager::RegisterInputRecorder(this); } RewindManager::~RewindManager() @@ -23,6 +26,8 @@ RewindManager::~RewindManager() _instance = nullptr; } MessageManager::UnregisterNotificationListener(this); + ControlManager::UnregisterInputProvider(this); + ControlManager::UnregisterInputRecorder(this); } void RewindManager::ClearBuffer() @@ -262,21 +267,23 @@ bool RewindManager::ProcessAudio(int16_t * soundBuffer, uint32_t sampleCount, ui } } -void RewindManager::RecordInput(uint8_t port, uint8_t input) +void RewindManager::RecordInput(BaseControlDevice *device) { if(EmulationSettings::GetRewindBufferSize() > 0 && _instance && _instance->_rewindState == RewindState::Stopped) { - _instance->_currentHistory.InputLogs[port].push_back(input); + _instance->_currentHistory.InputLogs[device->GetPort()].push_back(device->GetRawState()); } } -uint8_t RewindManager::GetInput(uint8_t port) +bool RewindManager::SetInput(BaseControlDevice *device) { - if(!_instance->_currentHistory.InputLogs[port].empty()) { - uint8_t value = _instance->_currentHistory.InputLogs[port].front(); + uint8_t port = device->GetPort(); + if(!_instance->_currentHistory.InputLogs[port].empty() && RewindManager::IsRewinding()) { + ControlDeviceState state = _instance->_currentHistory.InputLogs[port].front(); _instance->_currentHistory.InputLogs[port].pop_front(); - return value; + device->SetRawState(state); + return true; } else { - return 0; + return false; } } diff --git a/Core/RewindManager.h b/Core/RewindManager.h index b6a54152..870c7455 100644 --- a/Core/RewindManager.h +++ b/Core/RewindManager.h @@ -3,6 +3,8 @@ #include #include "INotificationListener.h" #include "RewindData.h" +#include "IInputProvider.h" +#include "IInputRecorder.h" enum class RewindState { @@ -13,7 +15,7 @@ enum class RewindState Debugging = 4 }; -class RewindManager : public INotificationListener +class RewindManager : public INotificationListener, public IInputProvider, public IInputRecorder { private: static const uint32_t BufferSize = 30; //Number of frames between each save state @@ -48,9 +50,9 @@ public: void ProcessNotification(ConsoleNotificationType type, void* parameter) override; void ProcessEndOfFrame(); - - static void RecordInput(uint8_t port, uint8_t input); - static uint8_t GetInput(uint8_t port); + + void RecordInput(BaseControlDevice *device) override; + bool SetInput(BaseControlDevice *device) override; static void StartRewinding(bool forDebugger = false); static void StopRewinding(bool forDebugger = false); diff --git a/Core/RomLoader.cpp b/Core/RomLoader.cpp index e5e9a095..30b7cff8 100644 --- a/Core/RomLoader.cpp +++ b/Core/RomLoader.cpp @@ -26,7 +26,7 @@ bool RomLoader::LoadFile(VirtualFile romFile) bool RomLoader::LoadFile(string filename, vector &fileData) { - if(fileData.size() < 10) { + if(fileData.size() < 15) { return false; } @@ -104,7 +104,7 @@ string RomLoader::FindMatchingRomInFile(string filePath, HashInfo hashInfo) vector fileData; if(loader.LoadFile(filePath)) { if(hashInfo.Crc32Hash == loader._romData.Crc32 || hashInfo.Sha1Hash.compare(loader._romData.Sha1) == 0) { - return filePath+"\n"+file; + return VirtualFile(filePath, file); } } } @@ -123,12 +123,15 @@ string RomLoader::FindMatchingRomInFile(string filePath, HashInfo hashInfo) string RomLoader::FindMatchingRom(vector romFiles, string romFilename, HashInfo hashInfo, bool useFastSearch) { if(useFastSearch) { - for(string romFile : romFiles) { + string lcRomFile = romFilename; + std::transform(lcRomFile.begin(), lcRomFile.end(), lcRomFile.begin(), ::tolower); + + for(string currentFile : romFiles) { //Quick search by filename - string lcRomFile = romFile; - std::transform(lcRomFile.begin(), lcRomFile.end(), lcRomFile.begin(), ::tolower); - if(FolderUtilities::GetFilename(lcRomFile, false).compare(FolderUtilities::GetFilename(romFilename, false)) == 0) { - string match = RomLoader::FindMatchingRomInFile(romFile, hashInfo); + string lcCurrentFile = currentFile; + std::transform(lcCurrentFile.begin(), lcCurrentFile.end(), lcCurrentFile.begin(), ::tolower); + if(FolderUtilities::GetFilename(lcRomFile, false).compare(FolderUtilities::GetFilename(lcCurrentFile, false)) == 0) { + string match = RomLoader::FindMatchingRomInFile(currentFile, hashInfo); if(!match.empty()) { return match; } diff --git a/Core/SaveStateManager.cpp b/Core/SaveStateManager.cpp index 7a571505..5ed81318 100644 --- a/Core/SaveStateManager.cpp +++ b/Core/SaveStateManager.cpp @@ -147,7 +147,7 @@ bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired) } } - Console::LoadState(stream); + Console::LoadState(stream, fileFormatVersion); return true; } @@ -192,7 +192,8 @@ void SaveStateManager::SaveRecentGame(string romName, string romPath, string pat { if(!EmulationSettings::CheckFlag(EmulationFlags::ConsoleMode) && !EmulationSettings::CheckFlag(EmulationFlags::DisableGameSelectionScreen) && Console::GetRomFormat() != RomFormat::Nsf) { string filename = FolderUtilities::GetFilename(Console::GetRomName(), false) + ".rgd"; - ZipWriter writer(FolderUtilities::CombinePath(FolderUtilities::GetRecentGamesFolder(), filename)); + ZipWriter writer; + writer.Initialize(FolderUtilities::CombinePath(FolderUtilities::GetRecentGamesFolder(), filename)); std::stringstream pngStream; VideoDecoder::GetInstance()->TakeScreenshot(pngStream); @@ -207,6 +208,7 @@ void SaveStateManager::SaveRecentGame(string romName, string romPath, string pat romInfoStream << romPath << std::endl; romInfoStream << patchPath << std::endl; writer.AddFile(romInfoStream, "RomInfo.txt"); + writer.Save(); } } @@ -215,8 +217,9 @@ void SaveStateManager::LoadRecentGame(string filename, bool resetGame) ZipReader reader; reader.LoadArchive(filename); - std::stringstream romInfoStream = reader.GetStream("RomInfo.txt"); - std::stringstream stateStream = reader.GetStream("Savestate.mst"); + stringstream romInfoStream, stateStream; + reader.GetStream("RomInfo.txt", romInfoStream); + reader.GetStream("Savestate.mst", stateStream); string romName, romPath, patchPath; std::getline(romInfoStream, romName); diff --git a/Core/SaveStateManager.h b/Core/SaveStateManager.h index 61d67e8f..0445a308 100644 --- a/Core/SaveStateManager.h +++ b/Core/SaveStateManager.h @@ -11,7 +11,7 @@ private: static string GetStateFilepath(int stateIndex); public: - static const uint32_t FileFormatVersion = 6; + static const uint32_t FileFormatVersion = 7; static uint64_t GetStateInfo(int stateIndex); diff --git a/Core/ScriptHost.cpp b/Core/ScriptHost.cpp index 3c81cc81..4b792847 100644 --- a/Core/ScriptHost.cpp +++ b/Core/ScriptHost.cpp @@ -15,7 +15,8 @@ int ScriptHost::GetScriptId() const char* ScriptHost::GetLog() { - return _context ? _context->GetLog() : ""; + shared_ptr context = _context; + return context ? context->GetLog() : ""; } bool ScriptHost::LoadScript(string scriptName, string scriptContent, Debugger* debugger) diff --git a/Core/ScriptingContext.h b/Core/ScriptingContext.h index abbaf48b..78b17c9b 100644 --- a/Core/ScriptingContext.h +++ b/Core/ScriptingContext.h @@ -36,7 +36,7 @@ protected: string _scriptName; vector _callbacks[5][0x10000]; - vector _eventCallbacks[8]; + vector _eventCallbacks[(int)EventType::EventTypeSize]; virtual void InternalCallMemoryCallback(uint16_t addr, uint8_t &value, CallbackType type) = 0; virtual int InternalCallEventCallback(EventType type) = 0; diff --git a/Core/ShortcutKeyHandler.cpp b/Core/ShortcutKeyHandler.cpp index b2065045..90f38253 100644 --- a/Core/ShortcutKeyHandler.cpp +++ b/Core/ShortcutKeyHandler.cpp @@ -1,12 +1,15 @@ #include "stdafx.h" #include "ShortcutKeyHandler.h" #include "EmulationSettings.h" -#include "ControlManager.h" +#include "KeyManager.h" #include "VideoDecoder.h" -#include "VsControlManager.h" #include "FDS.h" #include "SaveStateManager.h" #include "RewindManager.h" +#include "SystemActionManager.h" +#include "FdsSystemActionManager.h" +#include "VsSystemActionManager.h" +#include "MovieManager.h" ShortcutKeyHandler::ShortcutKeyHandler() { @@ -49,9 +52,9 @@ bool ShortcutKeyHandler::IsKeyPressed(KeyCombination comb) return false; } - return ControlManager::IsKeyPressed(comb.Key1) && - (comb.Key2 == 0 || ControlManager::IsKeyPressed(comb.Key2)) && - (comb.Key3 == 0 || ControlManager::IsKeyPressed(comb.Key3)); + return KeyManager::IsKeyPressed(comb.Key1) && + (comb.Key2 == 0 || KeyManager::IsKeyPressed(comb.Key2)) && + (comb.Key3 == 0 || KeyManager::IsKeyPressed(comb.Key3)); } bool ShortcutKeyHandler::DetectKeyPress(EmulatorShortcut shortcut) @@ -104,18 +107,21 @@ void ShortcutKeyHandler::CheckMappedKeys() } } - if(VsControlManager::GetInstance() && !isNetplayClient && !isMovieActive) { - VsControlManager* manager = VsControlManager::GetInstance(); + shared_ptr vsSam = Console::GetInstance()->GetSystemActionManager(); + if(vsSam && !isNetplayClient && !isMovieActive) { if(DetectKeyPress(EmulatorShortcut::VsServiceButton)) { - manager->SetServiceButtonState(true); + vsSam->SetServiceButtonState(true); } if(DetectKeyRelease(EmulatorShortcut::VsServiceButton)) { - manager->SetServiceButtonState(false); + vsSam->SetServiceButtonState(false); } } if(DetectKeyPress(EmulatorShortcut::InsertNextDisk) && !isNetplayClient && !isMovieActive) { - FDS::InsertNextDisk(); + shared_ptr sam = Console::GetInstance()->GetSystemActionManager(); + if(sam) { + sam->InsertNextDisk(); + } } if(DetectKeyPress(EmulatorShortcut::MoveToNextStateSlot)) { @@ -185,9 +191,9 @@ void ShortcutKeyHandler::CheckMappedKeys() void ShortcutKeyHandler::ProcessKeys() { auto lock = _lock.AcquireSafe(); - ControlManager::RefreshKeyState(); + KeyManager::RefreshKeyState(); - _pressedKeys = ControlManager::GetPressedKeys(); + _pressedKeys = KeyManager::GetPressedKeys(); _isKeyUp = _pressedKeys.size() < _lastPressedKeys.size(); if(_pressedKeys.size() == _lastPressedKeys.size()) { diff --git a/Core/Snapshotable.cpp b/Core/Snapshotable.cpp index cdf8cbf9..301a6692 100644 --- a/Core/Snapshotable.cpp +++ b/Core/Snapshotable.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include #include "Snapshotable.h" +#include "SaveStateManager.h" void Snapshotable::StreamStartBlock() { @@ -57,13 +58,15 @@ void Snapshotable::Stream(Snapshotable* snapshotable) stream.write((char*)buffer, size); stream.seekg(0, ios::beg); stream.seekp(0, ios::beg); - snapshotable->LoadSnapshot(&stream); + snapshotable->LoadSnapshot(&stream, _stateVersion); delete[] buffer; } } void Snapshotable::SaveSnapshot(ostream* file) { + _stateVersion = SaveStateManager::FileFormatVersion; + _stream = new uint8_t[0xFFFFF]; _position = 0; _saving = true; @@ -79,8 +82,10 @@ void Snapshotable::SaveSnapshot(ostream* file) } } -void Snapshotable::LoadSnapshot(istream* file) +void Snapshotable::LoadSnapshot(istream* file, uint32_t stateVersion) { + _stateVersion = stateVersion; + _stream = new uint8_t[0xFFFFF]; memset((char*)_stream, 0, 0xFFFFF); _position = 0; diff --git a/Core/Snapshotable.h b/Core/Snapshotable.h index cc2a6d67..ce3bc988 100644 --- a/Core/Snapshotable.h +++ b/Core/Snapshotable.h @@ -35,6 +35,7 @@ private: uint8_t* _stream; uint32_t _position; uint32_t _streamSize; + uint32_t _stateVersion = 0; bool _inBlock = false; uint8_t* _blockBuffer = nullptr; @@ -145,6 +146,8 @@ protected: virtual void StreamState(bool saving) = 0; virtual void AfterLoadState() { } + uint32_t GetStateVersion() { return _stateVersion; } + void Stream(Snapshotable* snapshotable); template @@ -157,7 +160,7 @@ protected: public: void SaveSnapshot(ostream* file); - void LoadSnapshot(istream* file); + void LoadSnapshot(istream* file, uint32_t stateVersion); static void WriteEmptyBlock(ostream* file); static void SkipBlock(istream* file); diff --git a/Core/SnesController.h b/Core/SnesController.h new file mode 100644 index 00000000..f6de62b0 --- /dev/null +++ b/Core/SnesController.h @@ -0,0 +1,92 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class SnesController : public BaseControlDevice +{ +private: + uint32_t _stateBuffer = 0; + +protected: + enum Buttons { A = 0, B, Select, Start, Up, Down, Left, Right, Y, X, L, R }; + + string GetKeyNames() override + { + return "ABSTUDLRYXLR"; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + SetPressedState(Buttons::A, keyMapping.A); + SetPressedState(Buttons::B, keyMapping.B); + SetPressedState(Buttons::Start, keyMapping.Start); + SetPressedState(Buttons::Select, keyMapping.Select); + SetPressedState(Buttons::Up, keyMapping.Up); + SetPressedState(Buttons::Down, keyMapping.Down); + SetPressedState(Buttons::Left, keyMapping.Left); + SetPressedState(Buttons::Right, keyMapping.Right); + SetPressedState(Buttons::X, keyMapping.TurboA); + SetPressedState(Buttons::Y, keyMapping.TurboB); + SetPressedState(Buttons::L, keyMapping.LButton); + SetPressedState(Buttons::R, keyMapping.RButton); + } + } + + uint16_t ToByte() + { + //"A Super NES controller returns a 16-bit report in a similar order: B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R, then four 0 bits." + + return + (uint8_t)IsPressed(Buttons::B) | + ((uint8_t)IsPressed(Buttons::Y) << 1) | + ((uint8_t)IsPressed(Buttons::Select) << 2) | + ((uint8_t)IsPressed(Buttons::Start) << 3) | + ((uint8_t)IsPressed(Buttons::Up) << 4) | + ((uint8_t)IsPressed(Buttons::Down) << 5) | + ((uint8_t)IsPressed(Buttons::Left) << 6) | + ((uint8_t)IsPressed(Buttons::Right) << 7) | + ((uint8_t)IsPressed(Buttons::A) << 8) | + ((uint8_t)IsPressed(Buttons::X) << 9) | + ((uint8_t)IsPressed(Buttons::L) << 10) | + ((uint8_t)IsPressed(Buttons::R) << 11); + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_stateBuffer); + } + + void RefreshStateBuffer() override + { + _stateBuffer = (uint32_t)ToByte(); + } + +public: + SnesController(uint8_t port, KeyMappingSet keyMappings) : BaseControlDevice(port, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; + + if(IsCurrentPort(addr)) { + output = _stateBuffer & 0x01; + _stateBuffer >>= 1; + + //"All subsequent reads will return D=1 on an authentic controller but may return D=0 on third party controllers." + _stateBuffer |= 0x8000; + + StrobeProcessRead(); + } + + return output; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } +}; \ No newline at end of file diff --git a/Core/SnesMouse.h b/Core/SnesMouse.h new file mode 100644 index 00000000..d69dc677 --- /dev/null +++ b/Core/SnesMouse.h @@ -0,0 +1,83 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" +#include "IKeyManager.h" +#include "KeyManager.h" + +class SnesMouse : public BaseControlDevice +{ +private: + uint32_t _stateBuffer = 0; + uint8_t _sensitivity = 0; + +protected: + bool HasCoordinates() override { return true; } + + enum Buttons { Left = 0, Right }; + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_stateBuffer, _sensitivity); + } + + string GetKeyNames() override + { + return "LR"; + } + + void InternalSetStateFromInput() override + { + SetPressedState(Buttons::Left, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + SetPressedState(Buttons::Right, KeyManager::IsMouseButtonPressed(MouseButton::RightButton)); + SetMovement(KeyManager::GetMouseMovement()); + } + +public: + SnesMouse(uint8_t port) : BaseControlDevice(port) + { + } + + void WriteRAM(uint16_t addr, uint8_t value) + { + StrobeProcessWrite(value); + } + + uint8_t ReadRAM(uint16_t addr) + { + uint8_t output = 0; + if((addr == 0x4016 && (_port & 0x01) == 0) || (addr == 0x4017 && (_port & 0x01) == 1)) { + if(_strobe) { + _sensitivity = (_sensitivity + 1) & 0x03; + } + + output = (_stateBuffer & 0x80000000) >> 31; + if(_port >= 2) { + output <<= 1; + } + _stateBuffer <<= 1; + StrobeProcessRead(); + } + return output; + } + + void RefreshStateBuffer() override + { + MouseMovement mov = GetMovement(); + int32_t dx = mov.dx * (1 + _sensitivity); + int32_t dy = mov.dy * (1 + _sensitivity); + + uint32_t upFlag = dy < 0 ? 0x80 : 0; + uint32_t leftFlag = dx < 0 ? 0x80 : 0; + + dx = std::min(std::abs(dx), 127); + dy = std::min(std::abs(dy), 127); + + uint8_t byte1 = 0; + uint8_t byte2 = 0x01 | ((_sensitivity & 0x03) << 4) | (IsPressed(SnesMouse::Buttons::Left) ? 0x40 : 0) | (IsPressed(SnesMouse::Buttons::Right) ? 0x80 : 0); + uint8_t byte3 = dy | upFlag; + uint8_t byte4 = dx | leftFlag; + + _stateBuffer = (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4; + } +}; \ No newline at end of file diff --git a/Core/StandardController.cpp b/Core/StandardController.cpp deleted file mode 100644 index 92334f17..00000000 --- a/Core/StandardController.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "stdafx.h" -#include "StandardController.h" -#include "ControlManager.h" -#include "PPU.h" -#include "EmulationSettings.h" -#include "ArkanoidController.h" -#include "OekaKidsTablet.h" - -StandardController::StandardController(uint8_t port, bool emptyPort) : BaseControlDevice(port) -{ - _isEmptyPort = emptyPort; -} - -void StandardController::StreamState(bool saving) -{ - BaseControlDevice::StreamState(saving); - - SnapshotInfo additionalController{ _additionalController.get() }; - Stream(_stateBuffer, _stateBufferFamicom, additionalController); -} - -bool StandardController::IsMicrophoneActive() -{ - if(!EmulationSettings::CheckFlag(EmulationFlags::InBackground) || EmulationSettings::CheckFlag(EmulationFlags::AllowBackgroundInput)) { - for(size_t i = 0, len = _keyMappings.size(); i < len; i++) { - KeyMapping keyMapping = _keyMappings[i]; - if((PPU::GetFrameCount() % 3) == 0 && ControlManager::IsKeyPressed(keyMapping.Microphone)) { - return true; - } - } - } - return false; -} - -ButtonState StandardController::GetButtonState() -{ - ButtonState state; - - if(!EmulationSettings::CheckFlag(EmulationFlags::InBackground) || EmulationSettings::CheckFlag(EmulationFlags::AllowBackgroundInput)) { - for(size_t i = 0, len = _keyMappings.size(); i < len; i++) { - KeyMapping keyMapping = _keyMappings[i]; - - state.A |= ControlManager::IsKeyPressed(keyMapping.A); - state.B |= ControlManager::IsKeyPressed(keyMapping.B); - state.Select |= ControlManager::IsKeyPressed(keyMapping.Select); - state.Start |= ControlManager::IsKeyPressed(keyMapping.Start); - state.Up |= ControlManager::IsKeyPressed(keyMapping.Up); - state.Down |= ControlManager::IsKeyPressed(keyMapping.Down); - state.Left |= ControlManager::IsKeyPressed(keyMapping.Left); - state.Right |= ControlManager::IsKeyPressed(keyMapping.Right); - - //Turbo buttons - need to be applied for at least 2 reads in a row (some games require this) - uint8_t turboFreq = 1 << (4 - _turboSpeed); - bool turboOn = (uint8_t)(PPU::GetFrameCount() % turboFreq) < turboFreq / 2; - if(turboOn) { - state.A |= ControlManager::IsKeyPressed(keyMapping.TurboA); - state.B |= ControlManager::IsKeyPressed(keyMapping.TurboB); - state.Start |= ControlManager::IsKeyPressed(keyMapping.TurboStart); - state.Select |= ControlManager::IsKeyPressed(keyMapping.TurboSelect); - } - } - - if(!EmulationSettings::CheckFlag(EmulationFlags::AllowInvalidInput)) { - if(state.Up && state.Down) { - state.Down = false; - } - if(state.Left && state.Right) { - state.Right = false; - } - } - } - - return state; -} - -uint32_t StandardController::GetNetPlayState() -{ - return GetButtonState().ToByte(); -} - -uint8_t StandardController::GetPortOutput() -{ - uint8_t returnValue = _stateBuffer & 0x01; - _stateBuffer >>= 1; - - if(_famiconDevice && (_additionalController || EmulationSettings::CheckFlag(EmulationFlags::HasFourScore))) { - if(std::dynamic_pointer_cast(_additionalController) || std::dynamic_pointer_cast(_additionalController)) { - returnValue |= _additionalController->GetPortOutput(); - } else if(std::dynamic_pointer_cast(_additionalController)) { - returnValue |= std::dynamic_pointer_cast(_additionalController)->GetExpansionPortOutput(_port); - } else { - returnValue |= (_stateBufferFamicom & 0x01) << 1; - _stateBufferFamicom >>= 1; - _stateBufferFamicom |= 0x800000; - } - } - - //"All subsequent reads will return D=1 on an authentic controller but may return D=0 on third party controllers." - _stateBuffer |= _isEmptyPort ? 0 : 0x800000; - - return returnValue; -} - -void StandardController::RefreshStateBuffer() -{ - _stateBuffer = GetControlState(); - _lastButtonState = _stateBuffer; - if((_additionalController && !std::dynamic_pointer_cast(_additionalController)) || EmulationSettings::CheckFlag(EmulationFlags::HasFourScore)) { - //Next 8 bits = Gamepad 3/4 - if(_famiconDevice) { - if(std::dynamic_pointer_cast(_additionalController) || std::dynamic_pointer_cast(_additionalController)) { - _additionalController->RefreshStateBuffer(); - } else { - //Four player adapter (Famicom) - _stateBufferFamicom = _additionalController ? _additionalController->GetControlState() : 0; - _stateBufferFamicom |= 0xFFFF00; - } - } else { - //Four-score adapter (NES) - _stateBuffer |= _additionalController ? (_additionalController->GetControlState() << 8) : 0; - - //Last 8 bits = signature - //Signature for port 0 = 0x10, reversed bit order => 0x08 - //Signature for port 1 = 0x20, reversed bit order => 0x04 - _stateBuffer |= (GetPort() == 0 ? 0x08 : 0x04) << 16; - } - } else { - //"All subsequent reads will return D=1 on an authentic controller but may return D=0 on third party controllers." - _stateBuffer |= _isEmptyPort ? 0 : 0xFFFF00; - } -} - -uint8_t StandardController::RefreshState() -{ - return GetButtonState().ToByte(); -} - -void StandardController::AddAdditionalController(shared_ptr controller) -{ - _additionalController = controller; -} - -shared_ptr StandardController::GetAdditionalController() -{ - return _additionalController; -} - -uint32_t StandardController::GetInternalState() -{ - return _stateBuffer; -} - -void StandardController::SetInternalState(uint32_t state) -{ - _stateBuffer = state; -} - -uint8_t StandardController::GetLastButtonState() -{ - return _lastButtonState; -} - - diff --git a/Core/StandardController.h b/Core/StandardController.h index 2c289537..c2464ee2 100644 --- a/Core/StandardController.h +++ b/Core/StandardController.h @@ -1,39 +1,130 @@ #pragma once #include "stdafx.h" #include "BaseControlDevice.h" -#include "Zapper.h" +#include "PPU.h" class StandardController : public BaseControlDevice { private: - bool _isEmptyPort = false; - uint32_t _stateBuffer = 0; - uint32_t _stateBufferFamicom = 0; - uint8_t _lastButtonState = 0; - - shared_ptr _additionalController; + bool _microphoneEnabled = false; + uint32_t _turboSpeed = 0; protected: - uint8_t RefreshState() override; - virtual void StreamState(bool saving) override; + uint32_t _stateBuffer = 0; + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_stateBuffer, _microphoneEnabled); + } + + string GetKeyNames() override + { + string keys = "UDLRSsBA"; + if(_microphoneEnabled) { + keys += "M"; + } + return keys; + } + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + SetPressedState(Buttons::A, keyMapping.A); + SetPressedState(Buttons::B, keyMapping.B); + SetPressedState(Buttons::Start, keyMapping.Start); + SetPressedState(Buttons::Select, keyMapping.Select); + SetPressedState(Buttons::Up, keyMapping.Up); + SetPressedState(Buttons::Down, keyMapping.Down); + SetPressedState(Buttons::Left, keyMapping.Left); + SetPressedState(Buttons::Right, keyMapping.Right); + + uint8_t turboFreq = 1 << (4 - _turboSpeed); + bool turboOn = (uint8_t)(PPU::GetFrameCount() % turboFreq) < turboFreq / 2; + if(turboOn) { + SetPressedState(Buttons::A, keyMapping.TurboA); + SetPressedState(Buttons::B, keyMapping.TurboB); + } + + if(_microphoneEnabled && (PPU::GetFrameCount() % 3) == 0) { + SetPressedState(Buttons::Microphone, keyMapping.Microphone); + } + + if(!EmulationSettings::CheckFlag(EmulationFlags::AllowInvalidInput)) { + if(IsPressed(Buttons::Up) && IsPressed(Buttons::Down)) { + ClearBit(Buttons::Down); + } + if(IsPressed(Buttons::Left) && IsPressed(Buttons::Right)) { + ClearBit(Buttons::Right); + } + } + } + } + + void RefreshStateBuffer() override + { + if(EmulationSettings::GetConsoleType() == ConsoleType::Nes && EmulationSettings::CheckFlag(EmulationFlags::HasFourScore)) { + if(_port >= 2) { + _stateBuffer = ToByte() << 8; + } else { + //Add some 0 bit padding to allow P3/P4 controller bits + signature bits + _stateBuffer = (_port == 0 ? 0xFF000000 : 0xFF000000) | ToByte(); + } + } else { + _stateBuffer = 0xFFFFFF00 | ToByte(); + } + } public: - StandardController(uint8_t port, bool emptyPort = false); + enum Buttons { Up = 0, Down, Left, Right, Start, Select, B, A, Microphone }; - ButtonState GetButtonState(); - uint32_t GetNetPlayState() override; + StandardController(uint8_t port, KeyMappingSet keyMappings) : BaseControlDevice(port, keyMappings) + { + _turboSpeed = keyMappings.TurboSpeed; + _microphoneEnabled = port == 1 && EmulationSettings::GetConsoleType() == ConsoleType::Famicom; + } + + uint8_t ToByte() + { + //"Button status for each controller is returned as an 8-bit report in the following order: A, B, Select, Start, Up, Down, Left, Right." + return + (uint8_t)IsPressed(Buttons::A) | + ((uint8_t)IsPressed(Buttons::B) << 1) | + ((uint8_t)IsPressed(Buttons::Select) << 2) | + ((uint8_t)IsPressed(Buttons::Start) << 3) | + ((uint8_t)IsPressed(Buttons::Up) << 4) | + ((uint8_t)IsPressed(Buttons::Down) << 5) | + ((uint8_t)IsPressed(Buttons::Left) << 6) | + ((uint8_t)IsPressed(Buttons::Right) << 7); + } - uint8_t GetPortOutput() override; - void RefreshStateBuffer() override; + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; - //Used for VS System button unscrambling - uint32_t GetInternalState(); - void SetInternalState(uint32_t state); + if((addr == 0x4016 && (_port & 0x01) == 0) || (addr == 0x4017 && (_port & 0x01) == 1)) { + output = _stateBuffer & 0x01; + if(_port >= 2 && EmulationSettings::GetConsoleType() == ConsoleType::Famicom) { + //Famicom outputs P3 & P4 on bit 1 + output <<= 1; + } + _stateBuffer >>= 1; - bool IsMicrophoneActive(); + //"All subsequent reads will return D=1 on an authentic controller but may return D=0 on third party controllers." + _stateBuffer |= 0x80000000; - void AddAdditionalController(shared_ptr controller); - shared_ptr GetAdditionalController(); + StrobeProcessRead(); + } - uint8_t GetLastButtonState(); + if(addr == 0x4016 && IsPressed(StandardController::Buttons::Microphone)) { + output |= 0x04; + } + + return output; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } }; \ No newline at end of file diff --git a/Core/SuborKeyboard.h b/Core/SuborKeyboard.h new file mode 100644 index 00000000..be8c7654 --- /dev/null +++ b/Core/SuborKeyboard.h @@ -0,0 +1,113 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" + +class SuborKeyboard : public BaseControlDevice +{ +private: + uint8_t _row = 0; + uint8_t _column = 0; + bool _enabled = false; + +protected: + enum Buttons + { + Num4, G, F, C, F2, E, Num5, V, + Num2, D, S, End, F1, W, Num3, X, + Ins, Backspace, PageDown, Right, F8, PageUp, Delete, Home, + Num9, I, L, Comma, F5, O, Num0, Dot, + RightBracket, Enter, Up, Left, F7, LeftBracket, Backslash, Down, + Q, CapsLock, Z, Tab, Esc, A, Num1, Ctrl, + Num7, Y, K, M, F4, U, Num8, J, + Minus, SemiColon, Apostrophe, Slash, F6, P, Equal, Shift, + T, H, N, Space, F3, R, Num6, B, + NumpadEnter, Unknown1, Unknown2, Unknown3, + LeftMenu, Numpad4, Numpad7, F11, F12, Numpad1, Numpad2, Numpad8, + NumpadMinus, NumpadPlus, NumpadMultiply, Numpad9, F10, Numpad5, NumpadDivide, NumLock, + Grave, Numpad6, Pause, F9, Numpad3, NumpadDot, Numpad0 + }; + + Buttons _keyboardMatrix[104] = { + Num4, G, F, C, F2, E, Num5, V, + Num2, D, S, End, F1, W, Num3, X, + Ins, Backspace, PageDown, Right, F8, PageUp, Delete, Home, + Num9, I, L, Comma, F5, O, Num0, Dot, + RightBracket, Enter, Up, Left, F7, LeftBracket, Backslash, Down, + Q, CapsLock, Z, Tab, Esc, A, Num1, Ctrl, + Num7, Y, K, M, F4, U, Num8, J, + Minus, SemiColon, Apostrophe, Slash, F6, P, Equal, Shift, + T, H, N, Space, F3, R, Num6, B, + Numpad6, NumpadEnter, Numpad4, Numpad8, Numpad2, Unknown1, Unknown2, Unknown3, + LeftMenu, Numpad4, Numpad7, F11, F12, Numpad1, Numpad2, Numpad8, + NumpadMinus, NumpadPlus, NumpadMultiply, Numpad9, F10, Numpad5, NumpadDivide, NumLock, + Grave, Numpad6, Pause, Space, F9, Numpad3, NumpadDot, Numpad0 + }; + + void InternalSetStateFromInput() override + { + for(KeyMapping keyMapping : _keyMappings) { + for(int i = 0; i < 99; i++) { + SetPressedState(i, keyMapping.SuborKeyboardButtons[i]); + } + } + } + + uint8_t GetActiveKeys(uint8_t row, uint8_t column) + { + uint8_t result = 0; + uint32_t baseIndex = row * 8 + (column ? 4 : 0); + for(int i = 0; i < 4; i++) { + if(IsPressed(_keyboardMatrix[baseIndex + i])) { + result |= 0x10; + } + result >>= 1; + } + return result; + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + Stream(_row, _column, _enabled); + } + +public: + SuborKeyboard(KeyMappingSet keyMappings) : BaseControlDevice(BaseControlDevice::ExpDevicePort, keyMappings) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + if(addr == 0x4017) { + if(_enabled) { + uint8_t value = ((~GetActiveKeys(_row, _column)) << 1) & 0x1E; + MessageManager::Log(std::to_string(value)); + return value; + } else { + return 0x1E; + } + } + return 0; + } + + void RefreshStateBuffer() + { + _row = 0; + _column = 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + + uint8_t prevColumn = _column; + _column = (value & 0x02) >> 1; + _enabled = (value & 0x04) != 0; + + if(_enabled) { + if(!_column && prevColumn) { + _row = (_row + 1) % 13; + } + } + } +}; \ No newline at end of file diff --git a/Core/SuborMouse.h b/Core/SuborMouse.h new file mode 100644 index 00000000..b2d9935b --- /dev/null +++ b/Core/SuborMouse.h @@ -0,0 +1,105 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" +#include "IKeyManager.h" +#include "KeyManager.h" + +class SuborMouse : public BaseControlDevice +{ +private: + uint32_t _stateBuffer = 0; + uint8_t _packetBytes[3] = {}; + uint8_t _packetPos = 0; + uint8_t _packetSize = 1; + +protected: + bool HasCoordinates() override { return true; } + enum Buttons { Left = 0, Right }; + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + ArrayInfo packetBytes { _packetBytes, 3 }; + Stream(_stateBuffer, _packetPos, _packetSize, packetBytes); + } + + void InternalSetStateFromInput() override + { + SetPressedState(Buttons::Left, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + SetPressedState(Buttons::Right, KeyManager::IsMouseButtonPressed(MouseButton::RightButton)); + SetMovement(KeyManager::GetMouseMovement()); + } + +public: + SuborMouse(uint8_t port) : BaseControlDevice(port) + { + } + + void WriteRAM(uint16_t addr, uint8_t value) + { + StrobeProcessWrite(value); + } + + uint8_t ReadRAM(uint16_t addr) + { + uint8_t output = 0; + if((addr == 0x4016 && (_port & 0x01) == 0) || (addr == 0x4017 && (_port & 0x01) == 1)) { + output = (_stateBuffer & 0x80) >> 7; + if(_port >= 2) { + output <<= 1; + } + _stateBuffer <<= 1; + StrobeProcessRead(); + } + return output; + } + + void RefreshStateBuffer() override + { + if(_packetPos < _packetSize - 1) { + //3-byte packet is not done yet, move to next byte + _packetPos++; + _stateBuffer = _packetBytes[_packetPos]; + return; + } + + MouseMovement mov = GetMovement(); + + uint32_t upFlag = mov.dy < 0 ? 0x80 : 0; + uint32_t leftFlag = mov.dx < 0 ? 0x80 : 0; + + mov.dx = std::min(std::abs(mov.dx), 31); + mov.dy = std::min(std::abs(mov.dy), 31); + + if(mov.dx <= 1 && mov.dy <= 1) { + //1-byte packet + _packetBytes[0] = + (IsPressed(SuborMouse::Buttons::Left) ? 0x80 : 0) | + (IsPressed(SuborMouse::Buttons::Right) ? 0x40 : 0) | + (leftFlag && mov.dx ? 0x30 : (mov.dx ? 0x10 : 0)) | + (upFlag && mov.dy ? 0x0C : (mov.dy ? 0x04 : 0)); + _packetBytes[1] = 0; + _packetBytes[2] = 0; + + _packetSize = 1; + } else { + //3-byte packet + _packetBytes[0] = + (IsPressed(SuborMouse::Buttons::Left) ? 0x80 : 0) | + (IsPressed(SuborMouse::Buttons::Right) ? 0x40 : 0) | + (leftFlag ? 0x20 : 0) | + (mov.dx & 0x10) | + (upFlag ? 0x08 : 0) | + ((mov.dy & 0x10) >> 2) | + 0x01; + + _packetBytes[1] = ((mov.dx & 0x0F) << 2) | 0x02; + _packetBytes[2] = ((mov.dy & 0x0F) << 2) | 0x03; + + _packetSize = 3; + } + + _packetPos = 0; + _stateBuffer = _packetBytes[0]; + } +}; \ No newline at end of file diff --git a/Core/SystemActionManager.h b/Core/SystemActionManager.h new file mode 100644 index 00000000..2cec9ddc --- /dev/null +++ b/Core/SystemActionManager.h @@ -0,0 +1,76 @@ +#pragma once +#include "stdafx.h" +#include "BaseControlDevice.h" +#include "Console.h" + +class SystemActionManager : public BaseControlDevice +{ +private: + std::weak_ptr _console; + bool _needReset = false; + bool _needPowerCycle = false; + +protected: + + string GetKeyNames() override + { + return "RP"; + } + + void StreamState(bool saving) override + { + BaseControlDevice::StreamState(saving); + } + +public: + enum Buttons { ResetButton = 0, PowerButton = 1 }; + + SystemActionManager(std::shared_ptr console) : BaseControlDevice(BaseControlDevice::ConsoleInputPort) + { + _console = console; + } + + uint8_t ReadRAM(uint16_t addr) override + { + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + } + + void OnAfterSetState() override + { + if(_needReset) { + SetBit(SystemActionManager::Buttons::ResetButton); + _needReset = false; + } + if(_needPowerCycle) { + SetBit(SystemActionManager::Buttons::PowerButton); + _needPowerCycle = false; + } + } + + void Reset() + { + _needReset = true; + } + + void PowerCycle() + { + _needPowerCycle = true; + } + + virtual void ProcessSystemActions() + { + shared_ptr console = _console.lock(); + if(console) { + if(IsPressed(SystemActionManager::Buttons::ResetButton)) { + console->ResetComponents(true); + } + if(IsPressed(SystemActionManager::Buttons::PowerButton)) { + console->PowerCycle(); + } + } + } +}; diff --git a/Core/Types.h b/Core/Types.h index fb205545..b87d09d9 100644 --- a/Core/Types.h +++ b/Core/Types.h @@ -229,4 +229,25 @@ struct ApuState ApuNoiseState Noise; ApuDmcState Dmc; ApuFrameCounterState FrameCounter; -}; \ No newline at end of file +}; + +struct MousePosition +{ + int16_t X; + int16_t Y; +}; + +struct MouseMovement +{ + int16_t dx; + int16_t dy; +}; + +enum class ConsoleFeatures +{ + None = 0, + Fds = 1, + Nsf = 2, + VsSystem = 4, + BarcodeReader = 8, +}; diff --git a/Core/VideoHud.cpp b/Core/VideoHud.cpp index 93c6b4aa..80a552eb 100644 --- a/Core/VideoHud.cpp +++ b/Core/VideoHud.cpp @@ -1,16 +1,23 @@ #include "stdafx.h" #include "VideoHud.h" #include "ControlManager.h" +#include "BaseControlDevice.h" +#include "ControlDeviceState.h" #include "StandardController.h" +#include "FourScore.h" +#include "Zapper.h" #include "MovieManager.h" void VideoHud::DrawHud(uint8_t *outputBuffer, FrameInfo frameInfo, OverscanDimensions overscan) { uint32_t displayCount = 0; InputDisplaySettings settings = EmulationSettings::GetInputDisplaySettings(); + + //TODO: FIX + vector states = ControlManager::GetPortStates(); for(int inputPort = 0; inputPort < 4; inputPort++) { if((settings.VisiblePorts >> inputPort) & 0x01) { - if(DisplayControllerInput(inputPort, outputBuffer, frameInfo, overscan, displayCount)) { + if(DisplayControllerInput(states[inputPort], inputPort, outputBuffer, frameInfo, overscan, displayCount)) { displayCount++; } } @@ -19,7 +26,7 @@ void VideoHud::DrawHud(uint8_t *outputBuffer, FrameInfo frameInfo, OverscanDimen DrawMovieIcons(outputBuffer, frameInfo, overscan); } -bool VideoHud::DisplayControllerInput(int inputPort, uint8_t *outputBuffer, FrameInfo &frameInfo, OverscanDimensions &overscan, uint32_t displayIndex) +bool VideoHud::DisplayControllerInput(ControlDeviceState &state, int inputPort, uint8_t *outputBuffer, FrameInfo &frameInfo, OverscanDimensions &overscan, uint32_t displayIndex) { bool axisInverted = (EmulationSettings::GetScreenRotation() % 180) != 0; int scale = frameInfo.Width / (axisInverted ? overscan.GetScreenHeight() : overscan.GetScreenWidth()); @@ -47,14 +54,17 @@ bool VideoHud::DisplayControllerInput(int inputPort, uint8_t *outputBuffer, Fram break; } - int port = inputPort > 1 ? inputPort - 2 : inputPort; - shared_ptr device = std::dynamic_pointer_cast(ControlManager::GetControlDevice(port)); - if(inputPort > 1 && device) { - device = std::dynamic_pointer_cast(device->GetAdditionalController()); + int32_t buttonState = -1; + + shared_ptr device = ControlManager::CreateControllerDevice(EmulationSettings::GetControllerType(inputPort), 0); + device->SetRawState(state); + + shared_ptr controller = std::dynamic_pointer_cast(device); + if(controller) { + buttonState = controller->ToByte(); } - if(device) { - uint8_t buttonState = device->GetLastButtonState(); + if(buttonState >= 0) { for(int y = 0; y < 13 * scale; y++) { for(int x = 0; x < 38 * scale; x++) { uint32_t bufferPos = (yStart + y)*frameInfo.Width + (xStart + x); @@ -72,6 +82,26 @@ bool VideoHud::DisplayControllerInput(int inputPort, uint8_t *outputBuffer, Fram } return true; } + + shared_ptr zapper = std::dynamic_pointer_cast(device); + if(zapper) { + MousePosition pos = zapper->GetCoordinates(); + if(pos.X != -1 && pos.Y != -1) { + for(int i = -1; i <= 1; i++) { + int y = (pos.Y - overscan.Top) * scale + i; + if(y < 0 || y >(int)frameInfo.Height) continue; + + for(int j = -1; j <= 1; j++) { + int x = (pos.X - overscan.Left) * scale + j; + if(x < 0 || x > (int)frameInfo.Width) continue; + + uint32_t bufferPos = y*frameInfo.Width + x; + BlendColors(rgbaBuffer + bufferPos, 0xFFFF0000); + } + } + } + } + return false; } diff --git a/Core/VideoHud.h b/Core/VideoHud.h index 6e407761..9a146ec7 100644 --- a/Core/VideoHud.h +++ b/Core/VideoHud.h @@ -3,6 +3,8 @@ #include "EmulationSettings.h" #include "FrameInfo.h" +struct ControlDeviceState; + class VideoHud { private: @@ -11,7 +13,7 @@ private: static const vector _recordIcon; void BlendColors(uint32_t* output, uint32_t input); - bool DisplayControllerInput(int inputPort, uint8_t *outputBuffer, FrameInfo &frameInfo, OverscanDimensions &overscan, uint32_t displayIndex); + bool DisplayControllerInput(ControlDeviceState &state, int inputPort, uint8_t *outputBuffer, FrameInfo &frameInfo, OverscanDimensions &overscan, uint32_t displayIndex); void DrawMovieIcons(uint8_t *outputBuffer, FrameInfo &frameInfo, OverscanDimensions &overscan); public: diff --git a/Core/VirtualFile.cpp b/Core/VirtualFile.cpp index 5f043fbe..09ef4818 100644 --- a/Core/VirtualFile.cpp +++ b/Core/VirtualFile.cpp @@ -110,9 +110,8 @@ string VirtualFile::GetFileName() string VirtualFile::GetSha1Hash() { - string hash = SHA1::GetHash(_data); - std::transform(hash.begin(), hash.end(), hash.begin(), ::tolower); - return hash; + LoadFile(); + return SHA1::GetHash(_data); } bool VirtualFile::ReadFile(vector& out) @@ -136,7 +135,7 @@ bool VirtualFile::ReadFile(std::stringstream & out) return false; } -bool VirtualFile::ApplyPatch(VirtualFile & patch) +bool VirtualFile::ApplyPatch(VirtualFile &patch) { //Apply patch file bool result = false; diff --git a/Core/VsControlManager.cpp b/Core/VsControlManager.cpp index 8d52229f..65214e77 100644 --- a/Core/VsControlManager.cpp +++ b/Core/VsControlManager.cpp @@ -1,4 +1,168 @@ #include "stdafx.h" #include "VsControlManager.h" -VsControlManager *VsControlManager::_instance = nullptr; \ No newline at end of file +VsControlManager *VsControlManager::_instance = nullptr; + +VsControlManager::VsControlManager(shared_ptr systemActionManager, shared_ptr mapperControlDevice) : ControlManager(systemActionManager, mapperControlDevice) +{ + _instance = this; +} + +VsControlManager::~VsControlManager() +{ + if(_instance == this) { + _instance = nullptr; + } +} + +ControllerType VsControlManager::GetControllerType(uint8_t port) +{ + ControllerType type = ControlManager::GetControllerType(port); + if(type == ControllerType::Zapper) { + type = ControllerType::VsZapper; + } + return type; +} + +void VsControlManager::Reset(bool softReset) +{ + _protectionCounter = 0; +} + +VsControlManager* VsControlManager::GetInstance() +{ + return _instance; +} + +void VsControlManager::StreamState(bool saving) +{ + ControlManager::StreamState(saving); + Stream(_prgChrSelectBit, _protectionCounter, _refreshState, _inputType); +} + +void VsControlManager::SetInputType(VsInputType inputType) +{ + _inputType = inputType; +} + +void VsControlManager::GetMemoryRanges(MemoryRanges &ranges) +{ + ControlManager::GetMemoryRanges(ranges); + ranges.AddHandler(MemoryOperation::Read, 0x4020, 0x5FFF); + ranges.AddHandler(MemoryOperation::Write, 0x4020, 0x5FFF); +} + +uint8_t VsControlManager::GetPrgChrSelectBit() +{ + return _prgChrSelectBit; +} + +void VsControlManager::RemapControllerButtons() +{ + shared_ptr controllers[2]; + controllers[0] = std::dynamic_pointer_cast(GetControlDevice(0)); + controllers[1] = std::dynamic_pointer_cast(GetControlDevice(1)); + + if(!controllers[0] || !controllers[1]) { + return; + } + + if(_inputType == VsInputType::TypeA) { + BaseControlDevice::SwapButtons(controllers[0], StandardController::Buttons::Select, controllers[0], StandardController::Buttons::Start); + BaseControlDevice::SwapButtons(controllers[1], StandardController::Buttons::Select, controllers[1], StandardController::Buttons::Start); + } else if(_inputType == VsInputType::TypeB) { + std::swap(controllers[0], controllers[1]); + BaseControlDevice::SwapButtons(controllers[1], StandardController::Buttons::Select, controllers[0], StandardController::Buttons::Start); + BaseControlDevice::SwapButtons(controllers[0], StandardController::Buttons::Select, controllers[1], StandardController::Buttons::Start); + } else if(_inputType == VsInputType::TypeC) { + std::swap(controllers[0], controllers[1]); + + if(controllers[0]->IsPressed(StandardController::Buttons::Start)) { + controllers[1]->SetBit(StandardController::Buttons::Select); + } else { + controllers[1]->ClearBit(StandardController::Buttons::Select); + } + + controllers[0]->ClearBit(StandardController::Buttons::Start); + controllers[0]->ClearBit(StandardController::Buttons::Select); + } else if(_inputType == VsInputType::TypeD) { + std::swap(controllers[0], controllers[1]); + BaseControlDevice::SwapButtons(controllers[1], StandardController::Buttons::Select, controllers[0], StandardController::Buttons::Start); + BaseControlDevice::SwapButtons(controllers[0], StandardController::Buttons::Select, controllers[1], StandardController::Buttons::Start); + controllers[0]->InvertBit(StandardController::Buttons::Select); + controllers[1]->InvertBit(StandardController::Buttons::Select); + } else if(_inputType == VsInputType::TypeE) { + BaseControlDevice::SwapButtons(controllers[0], StandardController::Buttons::B, controllers[1], StandardController::Buttons::A); + BaseControlDevice::SwapButtons(controllers[0], StandardController::Buttons::Select, controllers[0], StandardController::Buttons::Start); + BaseControlDevice::SwapButtons(controllers[1], StandardController::Buttons::Select, controllers[1], StandardController::Buttons::Start); + } +} + +uint8_t VsControlManager::ReadRAM(uint16_t addr) +{ + uint8_t value = 0; + + uint32_t crc = Console::GetHashInfo().PrgCrc32Hash; + + switch(addr) { + case 0x4016: { + uint32_t dipSwitches = EmulationSettings::GetDipSwitches(); + value = ControlManager::ReadRAM(addr); + value |= ((dipSwitches & 0x01) ? 0x08 : 0x00); + value |= ((dipSwitches & 0x02) ? 0x10 : 0x00); + break; + } + + case 0x4017: { + value = ControlManager::ReadRAM(addr) & 0x01; + + uint32_t dipSwitches = EmulationSettings::GetDipSwitches(); + value |= ((dipSwitches & 0x04) ? 0x04 : 0x00); + value |= ((dipSwitches & 0x08) ? 0x08 : 0x00); + value |= ((dipSwitches & 0x10) ? 0x10 : 0x00); + value |= ((dipSwitches & 0x20) ? 0x20 : 0x00); + value |= ((dipSwitches & 0x40) ? 0x40 : 0x00); + value |= ((dipSwitches & 0x80) ? 0x80 : 0x00); + break; + } + + case 0x5E00: + _protectionCounter = 0; + break; + + case 0x5E01: + if(crc == 0xEB2DBA63 || crc == 0x98CFE016) { + //TKO Boxing + value = _protectionData[0][_protectionCounter++ & 0x1F]; + } else if(crc == 0x135ADF7C) { + //RBI Baseball + value = _protectionData[1][_protectionCounter++ & 0x1F]; + } + break; + + default: + if((crc == 0xF9D3B0A3 || crc == 0x66BB838F || crc == 0x9924980A) && addr >= 0x5400 && addr <= 0x57FF) { + //Super devious + return _protectionData[2][_protectionCounter++ & 0x1F]; + } + break; + } + + return value; +} + +void VsControlManager::WriteRAM(uint16_t addr, uint8_t value) +{ + ControlManager::WriteRAM(addr, value); + + bool previousState = _refreshState; + _refreshState = (value & 0x01) == 0x01; + + if(previousState && !_refreshState) { + RemapControllerButtons(); + } + + if(addr == 0x4016) { + _prgChrSelectBit = (value >> 2) & 0x01; + } +} diff --git a/Core/VsControlManager.h b/Core/VsControlManager.h index d484836a..731f155c 100644 --- a/Core/VsControlManager.h +++ b/Core/VsControlManager.h @@ -8,6 +8,8 @@ #include "StandardController.h" #include "MovieManager.h" +class BaseControlDevice; + enum class VsInputType { Default = 0, @@ -23,14 +25,11 @@ class VsControlManager : public ControlManager private: static VsControlManager *_instance; uint8_t _prgChrSelectBit; - uint8_t _dipSwitches = 0; - bool _serviceButton = false; - bool _coinInserted[2] = { }; - int32_t _coinInsertCycle[2] = { }; VsInputType _inputType = VsInputType::Default; - + bool _refreshState = false; + uint32_t _protectionCounter = 0; - uint32_t _protectionData[3][32] = { + const uint32_t _protectionData[3][32] = { { 0xFF, 0xBF, 0xB7, 0x97, 0x97, 0x17, 0x57, 0x4F, 0x6F, 0x6B, 0xEB, 0xA9, 0xB1, 0x90, 0x94, 0x14, @@ -51,207 +50,25 @@ private: } }; -private: - void UpdateCoinInsertedFlags() - { - int32_t cycle = CPU::GetCycleCount(); - for(int i = 0; i < 2; i++) { - if(_coinInserted[i] && cycle - _coinInsertCycle[i] > 120000) { - _coinInserted[i] = false; - } - } - } + ControllerType GetControllerType(uint8_t port) override; public: - VsControlManager() - { - _instance = this; - } + VsControlManager(shared_ptr systemActionManager, shared_ptr mapperControlDevice); + virtual ~VsControlManager(); - virtual ~VsControlManager() - { - if(_instance == this) { - _instance = nullptr; - } - } + void StreamState(bool saving) override; + void Reset(bool softReset) override; - shared_ptr GetZapper(uint8_t port) override - { - return shared_ptr(new VsZapper(port)); - } - - void Reset(bool softReset) override - { - _protectionCounter = 0; - } - - static VsControlManager* GetInstance() - { - return _instance; - } - - void StreamState(bool saving) override - { - ControlManager::StreamState(saving); - Stream(_prgChrSelectBit, _protectionCounter); - } - - void InsertCoin(uint8_t port) - { - assert(port < 2); - - _coinInsertCycle[port] = CPU::GetCycleCount(); - _coinInserted[port] = true; - } - - void SetDipSwitches(uint8_t dipSwitches) - { - _dipSwitches = dipSwitches; - } - - void SetInputType(VsInputType inputType) - { - _inputType = inputType; - } - - void SetServiceButtonState(bool pushed) - { - _serviceButton = pushed; - } - - void GetMemoryRanges(MemoryRanges &ranges) override - { - ControlManager::GetMemoryRanges(ranges); - ranges.AddHandler(MemoryOperation::Read, 0x4020, 0x5FFF); - ranges.AddHandler(MemoryOperation::Write, 0x4020, 0x5FFF); - } - - uint8_t GetPrgChrSelectBit() - { - return _prgChrSelectBit; - } - - void RemapControllerButtons() - { - ButtonState ports[2]; - shared_ptr controllers[2]; - controllers[0] = std::dynamic_pointer_cast(GetControlDevice(0)); - controllers[1] = std::dynamic_pointer_cast(GetControlDevice(1)); - if(controllers[0]) { - ports[0].FromByte(controllers[0]->GetInternalState()); - } - if(controllers[1]) { - ports[1].FromByte(controllers[1]->GetInternalState()); - } - - if(_inputType == VsInputType::TypeA) { - std::swap(ports[0], ports[1]); - - std::swap(ports[0].Select, ports[0].Start); - std::swap(ports[1].Select, ports[1].Start); - } else if(_inputType == VsInputType::TypeB) { - std::swap(ports[1].Select, ports[0].Start); - std::swap(ports[1].Start, ports[0].Select); - } else if(_inputType == VsInputType::TypeC) { - ports[1].Select = ports[0].Start; - ports[0].Select = false; - ports[0].Start = false; - } else if(_inputType == VsInputType::TypeD) { - std::swap(ports[1].Select, ports[0].Start); - std::swap(ports[1].Start, ports[0].Select); - ports[0].Select = !ports[0].Select; - ports[1].Select = !ports[1].Select; - } else if(_inputType == VsInputType::TypeE) { - std::swap(ports[0], ports[1]); - - std::swap(ports[0].B, ports[1].A); - std::swap(ports[1].Select, ports[1].Start); - std::swap(ports[0].Select, ports[0].Start); - } - - if(controllers[0]) { - controllers[0]->SetInternalState((controllers[0]->GetInternalState() & ~0xFF) | ports[0].ToByte()); - } - if(controllers[1]) { - controllers[1]->SetInternalState((controllers[1]->GetInternalState() & ~0xFF) | ports[1].ToByte()); - } - } - - void RefreshAllPorts() override - { - ControlManager::RefreshAllPorts(); - if(!MovieManager::Playing() && _inputType != VsInputType::Default) { - RemapControllerButtons(); - } - } - - uint8_t ReadRAM(uint16_t addr) override - { - UpdateCoinInsertedFlags(); - - uint8_t value = 0; - - uint32_t crc = Console::GetHashInfo().PrgCrc32Hash; - - switch(addr) { - case 0x4016: - value = GetPortValue(1) & 0x01; - if(_coinInserted[0]) { - value |= 0x20; - } - if(_coinInserted[1]) { - value |= 0x40; - } - if(_serviceButton) { - value |= 0x04; - } - - value |= ((_dipSwitches & 0x01) ? 0x08 : 0x00); - value |= ((_dipSwitches & 0x02) ? 0x10 : 0x00); - break; - - case 0x4017: - value = GetPortValue(0) & 0x01; - - value |= ((_dipSwitches & 0x04) ? 0x04 : 0x00); - value |= ((_dipSwitches & 0x08) ? 0x08 : 0x00); - value |= ((_dipSwitches & 0x10) ? 0x10 : 0x00); - value |= ((_dipSwitches & 0x20) ? 0x20 : 0x00); - value |= ((_dipSwitches & 0x40) ? 0x40 : 0x00); - value |= ((_dipSwitches & 0x80) ? 0x80 : 0x00); - break; - - case 0x5E00: - _protectionCounter = 0; - break; - - case 0x5E01: - if(crc == 0xEB2DBA63 || crc == 0x98CFE016) { - //TKO Boxing - value = _protectionData[0][_protectionCounter++ & 0x1F]; - } else if(crc == 0x135ADF7C) { - //RBI Baseball - value = _protectionData[1][_protectionCounter++ & 0x1F]; - } - break; - - default: - if((crc == 0xF9D3B0A3 || crc == 0x66BB838F || crc == 0x9924980A) && addr >= 0x5400 && addr <= 0x57FF) { - //Super devious - return _protectionData[2][_protectionCounter++ & 0x1F]; - } - break; - } - - return value; - } + static VsControlManager* GetInstance(); - void WriteRAM(uint16_t addr, uint8_t value) override - { - ControlManager::WriteRAM(addr, value); + void SetInputType(VsInputType inputType); + + void GetMemoryRanges(MemoryRanges &ranges) override; - if(addr == 0x4016) { - _prgChrSelectBit = (value >> 2) & 0x01; - } - } + uint8_t GetPrgChrSelectBit(); + + void RemapControllerButtons(); + + uint8_t ReadRAM(uint16_t addr) override; + void WriteRAM(uint16_t addr, uint8_t value) override; }; \ No newline at end of file diff --git a/Core/VsSystemActionManager.h b/Core/VsSystemActionManager.h new file mode 100644 index 00000000..ed9ef266 --- /dev/null +++ b/Core/VsSystemActionManager.h @@ -0,0 +1,71 @@ +#pragma once +#include "stdafx.h" +#include "SystemActionManager.h" + +class VsSystemActionManager : public SystemActionManager +{ +private: + static const uint8_t InsertCoinFrameCount = 4; + + uint8_t _needInsertCoin[2] = { 0, 0 }; + bool _needServiceButton = false; + + void ProcessInsertCoin(uint8_t port) + { + if(_needInsertCoin[port] > 0) { + _needInsertCoin[port]--; + SetBit(VsSystemActionManager::VsButtons::InsertCoin1 + port); + } + } + + string GetKeyNames() override + { + return "RP12S"; + } + +public: + enum VsButtons { InsertCoin1 = 2, InsertCoin2, ServiceButton }; + + VsSystemActionManager(std::shared_ptr console) : SystemActionManager(console) + { + } + + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t value = 0; + if(addr == 0x4016) { + if(IsPressed(VsSystemActionManager::VsButtons::InsertCoin1)) { + value |= 0x20; + } + if(IsPressed(VsSystemActionManager::VsButtons::InsertCoin2)) { + value |= 0x40; + } + if(IsPressed(VsSystemActionManager::VsButtons::ServiceButton)) { + value |= 0x04; + } + } + return value; + } + + void OnAfterSetState() override + { + SystemActionManager::OnAfterSetState(); + + ProcessInsertCoin(0); + ProcessInsertCoin(1); + + if(_needServiceButton) { + SetBit(VsSystemActionManager::VsButtons::ServiceButton); + } + } + + void InsertCoin(uint8_t port) + { + _needInsertCoin[port] = VsSystemActionManager::InsertCoinFrameCount; + } + + void SetServiceButtonState(bool pressed) + { + _needServiceButton = pressed; + } +}; \ No newline at end of file diff --git a/Core/VsZapper.cpp b/Core/VsZapper.cpp deleted file mode 100644 index fe1fd1cd..00000000 --- a/Core/VsZapper.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "stdafx.h" -#include "VsZapper.h" -#include "CPU.h" -#include "PPU.h" -#include "ControlManager.h" -#include "GameServerConnection.h" - -void VsZapper::RefreshStateBuffer() -{ - _stateBuffer = GetControlState(); -} - -uint8_t VsZapper::RefreshState() -{ - Zapper::RefreshState(); - ZapperButtonState state = GetZapperState(); - return 0x10 | (state.LightNotDetected ? 0x00 : 0x40) | (state.TriggerPressed ? 0x80 : 0x00); -} - -uint8_t VsZapper::GetPortOutput() -{ - uint8_t returnValue = _stateBuffer & 0x01; - _stateBuffer >>= 1; - return returnValue; -} - diff --git a/Core/VsZapper.h b/Core/VsZapper.h index c76e42b7..7c54fed0 100644 --- a/Core/VsZapper.h +++ b/Core/VsZapper.h @@ -8,11 +8,36 @@ private: uint32_t _stateBuffer = 0; protected: - uint8_t RefreshState(); - void RefreshStateBuffer(); + void StreamState(bool saving) override + { + Zapper::StreamState(saving); + Stream(_stateBuffer); + } + + void RefreshStateBuffer() override + { + _stateBuffer = 0x10 | (IsLightFound() ? 0x40 : 0x00) | (IsPressed(Zapper::Buttons::Fire) ? 0x80 : 0x00); + } public: - using Zapper::Zapper; + VsZapper(uint8_t port) : Zapper(port) + { + } - uint8_t GetPortOutput(); + uint8_t ReadRAM(uint16_t addr) override + { + if(IsCurrentPort(addr)) { + uint8_t returnValue = _stateBuffer & 0x01; + _stateBuffer >>= 1; + StrobeProcessRead(); + return returnValue; + } + + return 0; + } + + void WriteRAM(uint16_t addr, uint8_t value) override + { + StrobeProcessWrite(value); + } }; \ No newline at end of file diff --git a/Core/Zapper.cpp b/Core/Zapper.cpp deleted file mode 100644 index 318a2afc..00000000 --- a/Core/Zapper.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "stdafx.h" -#include "Zapper.h" -#include "CPU.h" -#include "PPU.h" -#include "ControlManager.h" -#include "GameServerConnection.h" -#include "IKeyManager.h" - -void Zapper::StreamState(bool saving) -{ - BaseControlDevice::StreamState(saving); - Stream(_xPosition, _yPosition, _pulled); -} - -uint32_t Zapper::GetNetPlayState() -{ - //Used by netplay - uint32_t state; - if(_yPosition == -1 || _xPosition == -1) { - state = 0x80000000; - } else { - state = _xPosition | (_yPosition << 8); - } - - if(_pulled) { - state |= 0x40000000; - } - - return state; -} - -uint8_t Zapper::ProcessNetPlayState(uint32_t netplayState) -{ - if(netplayState >> 31) { - _xPosition = -1; - _yPosition = -1; - } else { - _xPosition = netplayState & 0xFF; - _yPosition = (netplayState >> 8) & 0xFF; - } - _pulled = ((netplayState >> 30) & 0x01) == 0x01; - - return RefreshState(); -} - -ZapperButtonState Zapper::GetZapperState() -{ - ZapperButtonState state; - state.TriggerPressed = _pulled; - - int32_t scanline = PPU::GetCurrentScanline(); - int32_t cycle = PPU::GetCurrentCycle(); - - bool lightFound = false; - int radius = (int)EmulationSettings::GetZapperDetectionRadius(); - if(_xPosition != -1 && _yPosition != -1) { - for(int y = -radius; y <= radius; y++) { - int yPosition = _yPosition + y; - if(yPosition >= 0 && yPosition < PPU::ScreenHeight) { - for(int x = -radius; x <= radius; x++) { - int xPosition = _xPosition + x; - if(xPosition >= 0 && xPosition < PPU::ScreenWidth) { - if(scanline >= yPosition && (scanline - yPosition <= 20) && (scanline != yPosition || cycle > xPosition) && PPU::GetPixelBrightness(xPosition, yPosition) >= 85) { - //Light cannot be detected if the Y/X position is further ahead than the PPU, or if the PPU drew a dark color - lightFound = true; - break; - } - } - } - } - } - } - state.LightNotDetected = !lightFound; - - return state; -} - -uint8_t Zapper::RefreshState() -{ - if(!GameServerConnection::GetNetPlayDevice(_port)) { - if(ControlManager::IsMouseButtonPressed(MouseButton::RightButton)) { - _xPosition = -1; - _yPosition = -1; - } else { - MousePosition position = ControlManager::GetMousePosition(); - _xPosition = position.X; - _yPosition = position.Y; - } - - if(!EmulationSettings::CheckFlag(EmulationFlags::InBackground) || EmulationSettings::CheckFlag(EmulationFlags::AllowBackgroundInput)) { - _pulled = ControlManager::IsMouseButtonPressed(MouseButton::LeftButton); - } else { - _pulled = false; - } - } - - return GetZapperState().ToByte(); -} - -uint8_t Zapper::GetPortOutput() -{ - return GetControlState(); -} - diff --git a/Core/Zapper.h b/Core/Zapper.h index b81b33b8..54e3a23f 100644 --- a/Core/Zapper.h +++ b/Core/Zapper.h @@ -1,35 +1,83 @@ #pragma once #include "stdafx.h" #include "BaseControlDevice.h" - -struct ZapperButtonState -{ - bool TriggerPressed = false; - bool LightNotDetected = false; - - uint8_t ToByte() - { - return (LightNotDetected ? 0x08 : 0x00) | (TriggerPressed ? 0x10 : 0x00); - } -}; +#include "KeyManager.h" +#include "IKeyManager.h" +#include "PPU.h" class Zapper : public BaseControlDevice { -private: - bool _pulled = false; - int32_t _xPosition = -1; - int32_t _yPosition = -1; - protected: - virtual uint8_t RefreshState() override; - uint8_t ProcessNetPlayState(uint32_t netplayState) override; - void StreamState(bool saving) override; - ZapperButtonState GetZapperState(); + bool HasCoordinates() override { return true; } + + string GetKeyNames() override + { + return "F"; + } + + enum Buttons { Fire }; + + void InternalSetStateFromInput() override + { + if(EmulationSettings::InputEnabled()) { + MousePosition pos = KeyManager::GetMousePosition(); + SetPressedState(Buttons::Fire, KeyManager::IsMouseButtonPressed(MouseButton::LeftButton)); + } + + MousePosition pos = KeyManager::GetMousePosition(); + if(KeyManager::IsMouseButtonPressed(MouseButton::RightButton)) { + pos.X = -1; + pos.Y = -1; + } + SetCoordinates(pos); + } + + bool IsLightFound() + { + return StaticIsLightFound(GetCoordinates()); + } public: - using BaseControlDevice::BaseControlDevice; + Zapper(uint8_t port) : BaseControlDevice(port) + { + } - virtual uint8_t GetPortOutput() override; + uint8_t ReadRAM(uint16_t addr) override + { + uint8_t output = 0; + if(IsCurrentPort(addr)) { + output = (IsLightFound() ? 0 : 0x08) | (IsPressed(Zapper::Buttons::Fire) ? 0x10 : 0x00); + } + return output; + } - virtual uint32_t GetNetPlayState() override; + void WriteRAM(uint16_t addr, uint8_t value) override + { + } + + static bool StaticIsLightFound(MousePosition pos) + { + int32_t scanline = PPU::GetCurrentScanline(); + int32_t cycle = PPU::GetCurrentCycle(); + int radius = (int)EmulationSettings::GetZapperDetectionRadius(); + + if(pos.X >= 0 && pos.Y >= 0) { + for(int yOffset = -radius; yOffset <= radius; yOffset++) { + int yPos = pos.Y + yOffset; + if(yPos >= 0 && yPos < PPU::ScreenHeight) { + for(int xOffset = -radius; xOffset <= radius; xOffset++) { + int xPos = pos.X + xOffset; + if(xPos >= 0 && xPos < PPU::ScreenWidth) { + if(scanline >= yPos && (scanline - yPos <= 20) && (scanline != yPos || cycle > xPos) && PPU::GetPixelBrightness(xPos, yPos) >= 85) { + //Light cannot be detected if the Y/X position is further ahead than the PPU, or if the PPU drew a dark color + return true; + } + } + } + } + } + } + + return false; + } }; \ No newline at end of file diff --git a/GUI.NET/Config/InputInfo.cs b/GUI.NET/Config/InputInfo.cs index 99389465..9f13a402 100644 --- a/GUI.NET/Config/InputInfo.cs +++ b/GUI.NET/Config/InputInfo.cs @@ -25,13 +25,32 @@ namespace Mesen.GUI.Config public UInt32 Microphone; + public UInt32 LButton; + public UInt32 RButton; + + public UInt32[] PowerPadButtons = new UInt32[12]; + public UInt32[] FamilyBasicKeyboardButtons = new UInt32[72]; + public UInt32[] PartyTapButtons = new UInt32[6]; + public UInt32[] PachinkoButtons = new UInt32[2]; + public UInt32[] ExcitingBoxingButtons = new UInt32[8]; + public UInt32[] JissenMahjong = new UInt32[21]; + public UInt32[] SuborKeyboardButtons = new UInt32[99]; + public KeyMappings() { } public KeyMappings Clone() { - return (KeyMappings)this.MemberwiseClone(); + KeyMappings clone = (KeyMappings)this.MemberwiseClone(); + clone.PowerPadButtons = new UInt32[12]; + clone.FamilyBasicKeyboardButtons = new UInt32[72]; + clone.PartyTapButtons = new UInt32[6]; + clone.PachinkoButtons = new UInt32[2]; + clone.ExcitingBoxingButtons = new UInt32[8]; + clone.JissenMahjong = new UInt32[21]; + clone.SuborKeyboardButtons = new UInt32[99]; + return clone; } public InteropEmu.KeyMapping ToInteropMapping() @@ -51,6 +70,97 @@ namespace Mesen.GUI.Config mapping.TurboStart = TurboStart; mapping.TurboSelect = TurboSelect; mapping.Microphone = Microphone; + mapping.LButton = LButton; + mapping.RButton = RButton; + mapping.PowerPadButtons = PowerPadButtons; + //mapping.FamilyBasicKeyboardButtons = FamilyBasicKeyboardButtons; + + mapping.FamilyBasicKeyboardButtons = new UInt32[72] { + InteropEmu.GetKeyCode("A"), InteropEmu.GetKeyCode("B"), InteropEmu.GetKeyCode("C"), InteropEmu.GetKeyCode("D"), + InteropEmu.GetKeyCode("E"), InteropEmu.GetKeyCode("F"), InteropEmu.GetKeyCode("G"), InteropEmu.GetKeyCode("H"), + InteropEmu.GetKeyCode("I"), InteropEmu.GetKeyCode("J"), InteropEmu.GetKeyCode("K"), InteropEmu.GetKeyCode("L"), + InteropEmu.GetKeyCode("M"), InteropEmu.GetKeyCode("N"), InteropEmu.GetKeyCode("O"), InteropEmu.GetKeyCode("P"), + InteropEmu.GetKeyCode("Q"), InteropEmu.GetKeyCode("R"), InteropEmu.GetKeyCode("S"), InteropEmu.GetKeyCode("T"), + InteropEmu.GetKeyCode("U"), InteropEmu.GetKeyCode("V"), InteropEmu.GetKeyCode("W"), InteropEmu.GetKeyCode("X"), + InteropEmu.GetKeyCode("Y"), InteropEmu.GetKeyCode("Z"), InteropEmu.GetKeyCode("0"), InteropEmu.GetKeyCode("1"), + InteropEmu.GetKeyCode("2"), InteropEmu.GetKeyCode("3"), InteropEmu.GetKeyCode("4"), InteropEmu.GetKeyCode("5"), + InteropEmu.GetKeyCode("6"), InteropEmu.GetKeyCode("7"), InteropEmu.GetKeyCode("8"), InteropEmu.GetKeyCode("9"), + InteropEmu.GetKeyCode("Enter"), InteropEmu.GetKeyCode("Spacebar"), InteropEmu.GetKeyCode("Del"), InteropEmu.GetKeyCode("Ins"), + InteropEmu.GetKeyCode("Esc"), InteropEmu.GetKeyCode("Ctrl"), InteropEmu.GetKeyCode("RIGHT SHIFT"), InteropEmu.GetKeyCode("Shift"), + InteropEmu.GetKeyCode("["), InteropEmu.GetKeyCode("]"), + InteropEmu.GetKeyCode("Up Arrow"), InteropEmu.GetKeyCode("Down Arrow"), InteropEmu.GetKeyCode("Left Arrow"), InteropEmu.GetKeyCode("Right Arrow"), + InteropEmu.GetKeyCode("."), InteropEmu.GetKeyCode(","), InteropEmu.GetKeyCode("'"), InteropEmu.GetKeyCode(";"), + InteropEmu.GetKeyCode("="), InteropEmu.GetKeyCode("/"), InteropEmu.GetKeyCode("-"), InteropEmu.GetKeyCode("`"), + InteropEmu.GetKeyCode("F1"), InteropEmu.GetKeyCode("F2"), InteropEmu.GetKeyCode("F3"), InteropEmu.GetKeyCode("F4"), + InteropEmu.GetKeyCode("F5"), InteropEmu.GetKeyCode("F6"), InteropEmu.GetKeyCode("F7"), InteropEmu.GetKeyCode("F8"), + InteropEmu.GetKeyCode("\\"), InteropEmu.GetKeyCode("Page Up"), InteropEmu.GetKeyCode("F9"), InteropEmu.GetKeyCode("Page Down"), + InteropEmu.GetKeyCode("Home"), InteropEmu.GetKeyCode("End") + }; + + mapping.PartyTapButtons = new UInt32[6] { + InteropEmu.GetKeyCode("1"), + InteropEmu.GetKeyCode("2"), + InteropEmu.GetKeyCode("3"), + InteropEmu.GetKeyCode("4"), + InteropEmu.GetKeyCode("5"), + InteropEmu.GetKeyCode("6"), + }; + + mapping.PachinkoButtons = new UInt32[2] { + InteropEmu.GetKeyCode("I"), + InteropEmu.GetKeyCode("K"), + }; + + mapping.ExcitingBoxingButtons = new UInt32[8] { + InteropEmu.GetKeyCode("Numpad 7"), + InteropEmu.GetKeyCode("Numpad 6"), + InteropEmu.GetKeyCode("Numpad 4"), + InteropEmu.GetKeyCode("Numpad 9"), + InteropEmu.GetKeyCode("Numpad 1"), + InteropEmu.GetKeyCode("Numpad 5"), + InteropEmu.GetKeyCode("Numpad 3"), + InteropEmu.GetKeyCode("Numpad 8"), + }; + + mapping.JissenMahjongButtons = new UInt32[21] { + InteropEmu.GetKeyCode("A"), + InteropEmu.GetKeyCode("B"), + InteropEmu.GetKeyCode("C"), + InteropEmu.GetKeyCode("D"), + InteropEmu.GetKeyCode("E"), + InteropEmu.GetKeyCode("F"), + InteropEmu.GetKeyCode("G"), + InteropEmu.GetKeyCode("H"), + InteropEmu.GetKeyCode("I"), + InteropEmu.GetKeyCode("J"), + InteropEmu.GetKeyCode("K"), + InteropEmu.GetKeyCode("L"), + InteropEmu.GetKeyCode("M"), + InteropEmu.GetKeyCode("N"), + InteropEmu.GetKeyCode("Spacebar"), + InteropEmu.GetKeyCode("Enter"), + InteropEmu.GetKeyCode("1"), + InteropEmu.GetKeyCode("2"), + InteropEmu.GetKeyCode("3"), + InteropEmu.GetKeyCode("4"), + InteropEmu.GetKeyCode("5"), + }; + + mapping.SuborKeyboardButtons = new UInt32[99] { + InteropEmu.GetKeyCode("4"), InteropEmu.GetKeyCode("G"), InteropEmu.GetKeyCode("F"), InteropEmu.GetKeyCode("C"), InteropEmu.GetKeyCode("F2"), InteropEmu.GetKeyCode("E"), InteropEmu.GetKeyCode("5"), InteropEmu.GetKeyCode("V"), + InteropEmu.GetKeyCode("2"), InteropEmu.GetKeyCode("D"), InteropEmu.GetKeyCode("S"), InteropEmu.GetKeyCode("End"), InteropEmu.GetKeyCode("F1"), InteropEmu.GetKeyCode("W"), InteropEmu.GetKeyCode("3"), InteropEmu.GetKeyCode("X"), + InteropEmu.GetKeyCode("Ins"), InteropEmu.GetKeyCode("Backspace"), InteropEmu.GetKeyCode("Page Down"), InteropEmu.GetKeyCode("Right Arrow"), InteropEmu.GetKeyCode("F8"), InteropEmu.GetKeyCode("Page Up"), InteropEmu.GetKeyCode("Delete"), InteropEmu.GetKeyCode("Home"), + InteropEmu.GetKeyCode("9"), InteropEmu.GetKeyCode("I"), InteropEmu.GetKeyCode("L"), InteropEmu.GetKeyCode(","), InteropEmu.GetKeyCode("F5"), InteropEmu.GetKeyCode("O"), InteropEmu.GetKeyCode("0"), InteropEmu.GetKeyCode("."), + InteropEmu.GetKeyCode("]"), InteropEmu.GetKeyCode("Enter"), InteropEmu.GetKeyCode("Up Arrow"), InteropEmu.GetKeyCode("Left Arrow"), InteropEmu.GetKeyCode("F7"), InteropEmu.GetKeyCode("["), InteropEmu.GetKeyCode("\\"), InteropEmu.GetKeyCode("Down Arrow"), + InteropEmu.GetKeyCode("Q"), InteropEmu.GetKeyCode("Caps Lock"), InteropEmu.GetKeyCode("Z"), InteropEmu.GetKeyCode("Tab"), InteropEmu.GetKeyCode("Esc"), InteropEmu.GetKeyCode("A"), InteropEmu.GetKeyCode("1"), InteropEmu.GetKeyCode("Ctrl"), + InteropEmu.GetKeyCode("7"), InteropEmu.GetKeyCode("Y"), InteropEmu.GetKeyCode("K"), InteropEmu.GetKeyCode("M"), InteropEmu.GetKeyCode("F4"), InteropEmu.GetKeyCode("U"), InteropEmu.GetKeyCode("8"), InteropEmu.GetKeyCode("J"), + InteropEmu.GetKeyCode("-"), InteropEmu.GetKeyCode(";"), InteropEmu.GetKeyCode("'"), InteropEmu.GetKeyCode("/"), InteropEmu.GetKeyCode("F6"), InteropEmu.GetKeyCode("P"), InteropEmu.GetKeyCode("="), InteropEmu.GetKeyCode("Shift"), + InteropEmu.GetKeyCode("T"), InteropEmu.GetKeyCode("H"), InteropEmu.GetKeyCode("N"), InteropEmu.GetKeyCode("Spacebar"), InteropEmu.GetKeyCode("F3"), InteropEmu.GetKeyCode("R"), InteropEmu.GetKeyCode("6"), InteropEmu.GetKeyCode("B"), + InteropEmu.GetKeyCode("Numpad Enter"), 0, 0, 0, + InteropEmu.GetKeyCode("Left Menu"), InteropEmu.GetKeyCode("Numpad 4"), InteropEmu.GetKeyCode("Numpad 7"), InteropEmu.GetKeyCode("F11"), InteropEmu.GetKeyCode("F12"), InteropEmu.GetKeyCode("Numpad 1"), InteropEmu.GetKeyCode("Numpad 2"), InteropEmu.GetKeyCode("Numpad 8"), + InteropEmu.GetKeyCode("Numpad -"), InteropEmu.GetKeyCode("Numpad +"), InteropEmu.GetKeyCode("Numpad *"), InteropEmu.GetKeyCode("Numpad 9"), InteropEmu.GetKeyCode("F10"), InteropEmu.GetKeyCode("Numpad 5"), InteropEmu.GetKeyCode("Numpad /"), InteropEmu.GetKeyCode("Num Lock"), + InteropEmu.GetKeyCode("`"), InteropEmu.GetKeyCode("Numpad 6"), InteropEmu.GetKeyCode("Pause"), InteropEmu.GetKeyCode("F9"), InteropEmu.GetKeyCode("Numpad 3"), InteropEmu.GetKeyCode("Numpad ."), InteropEmu.GetKeyCode("Numpad 0") + }; return mapping; } diff --git a/GUI.NET/CursorManager.cs b/GUI.NET/CursorManager.cs index 5841e18c..e573fd96 100644 --- a/GUI.NET/CursorManager.cs +++ b/GUI.NET/CursorManager.cs @@ -17,6 +17,7 @@ namespace Mesen.GUI private static Point _lastPosition; private static Timer _tmrHideMouse = new Timer(); private static Timer _tmrCheckMouseMove = new Timer(); + private static bool _mouseCaptured = false; static CursorManager() { @@ -32,10 +33,8 @@ namespace Mesen.GUI //Rarely the cursor becomes hidden despite leaving the window or moving //Have not been able to find a reliable way to reproduce it yet //This is a patch to prevent that bug from having any negative impact - if(_lastPosition != Cursor.Position) { - if(!InteropEmu.HasArkanoidPaddle()) { - ShowMouse(); - } + if(!_mouseCaptured && _lastPosition != Cursor.Position) { + ShowMouse(); _lastPosition = Cursor.Position; } } @@ -69,25 +68,45 @@ namespace Mesen.GUI public static bool NeedMouseIcon { - get { return InteropEmu.GetExpansionDevice() == InteropEmu.ExpansionPortDevice.OekaKidsTablet || InteropEmu.HasZapper(); } + get { + switch(InteropEmu.GetExpansionDevice()) { + case InteropEmu.ExpansionPortDevice.OekaKidsTablet: + case InteropEmu.ExpansionPortDevice.BandaiHyperShot: + return true; + } + if(InteropEmu.HasZapper()) { + return true; + } + return false; + } } public static void OnMouseMove(Control ctrl) { - if(!InteropEmu.IsRunning() || InteropEmu.IsPaused() || !InteropEmu.HasArkanoidPaddle()) { - ShowMouse(); - } else if(InteropEmu.HasArkanoidPaddle() && !CursorManager.NeedMouseIcon) { + if(_mouseCaptured && AllowMouseCapture) { HideMouse(); - } + _tmrHideMouse.Stop(); + Form frm = Application.OpenForms[0]; + Point centerPos = frm.PointToScreen(new Point(frm.Width / 2, frm.Height / 2)); + Point diff = new Point(Cursor.Position.X - centerPos.X, Cursor.Position.Y - centerPos.Y); + if(diff.X != 0 || diff.Y != 0) { + InteropEmu.SetMouseMovement((Int16)diff.X, (Int16)diff.Y); + Cursor.Position = centerPos; + } + } else { + if(!InteropEmu.IsRunning() || InteropEmu.IsPaused()) { + ShowMouse(); + } - _tmrHideMouse.Stop(); + _tmrHideMouse.Stop(); - if(!CursorManager.NeedMouseIcon) { - //Only hide mouse if no zapper (otherwise this could be pretty annoying) - ctrl.Cursor = Cursors.Default; + if(!CursorManager.NeedMouseIcon) { + //Only hide mouse if no zapper (otherwise this could be pretty annoying) + ctrl.Cursor = Cursors.Default; - if(InteropEmu.IsRunning() && !InteropEmu.IsPaused()) { - _tmrHideMouse.Start(); + if(InteropEmu.IsRunning() && !InteropEmu.IsPaused()) { + _tmrHideMouse.Start(); + } } } } @@ -97,5 +116,47 @@ namespace Mesen.GUI _tmrHideMouse.Stop(); ShowMouse(); } + + public static bool AllowMouseCapture + { + get + { + if(InteropEmu.IsPaused()) { + return false; + } + + switch(InteropEmu.GetExpansionDevice()) { + case InteropEmu.ExpansionPortDevice.ArkanoidController: + case InteropEmu.ExpansionPortDevice.HoriTrack: + return true; + } + for(int i = 0; i < 4; i++) { + switch(InteropEmu.GetControllerType(i)) { + case InteropEmu.ControllerType.ArkanoidController: + case InteropEmu.ControllerType.SnesMouse: + case InteropEmu.ControllerType.SuborMouse: + return true; + } + } + return false; + } + } + + public static void ReleaseMouse() + { + _mouseCaptured = false; + ShowMouse(); + } + + public static void CaptureMouse() + { + if(AllowMouseCapture) { + _mouseCaptured = true; + HideMouse(); + Form frm = Application.OpenForms[0]; + Point centerPos = frm.PointToScreen(new Point(frm.Width / 2, frm.Height / 2)); + Cursor.Position = centerPos; + } + } } } diff --git a/GUI.NET/Dependencies/MesenDB.txt b/GUI.NET/Dependencies/MesenDB.txt index 99d7cb52..ff71b879 100644 --- a/GUI.NET/Dependencies/MesenDB.txt +++ b/GUI.NET/Dependencies/MesenDB.txt @@ -4,7 +4,7 @@ # # Automatically generated database based on Nestopia's DB and NesCartDB # -# Generated on 2017-10-07 using: +# Generated on 2017-11-19 using: # -NesCartDB (dated 2017-08-21) # -Nestopia UE's latest DB (dated 2015-10-22) # @@ -264,7 +264,7 @@ 14374128,Famicom,HVC-SKEPROM,HVC-SKEPROM-01,MMC1B2,1,128,128,,0,0,0,,,, 143B2F27,Famicom,,,,60,64,32,,0,0,0,h,,, 145A9A6C,Famicom,HVC-SROM,HVC-SROM-03,,0,16,8,,0,0,0,h,,, -1460EC7B,Dendy,,,,15,128,,8,8,0,0,h,SubOrKeyboard,, +1460EC7B,Dendy,,,,15,128,,8,8,0,0,h,SuborKeyboard,, 147733DF,Famicom,,,,226,2048,,8,0,0,0,,,, 1488E95F,NesNtsc,,,,146,64,64,,0,0,0,v,,, 149C0EC3,Famicom,,,,1,128,128,,0,8,1,,,, @@ -322,7 +322,7 @@ 19CE7F12,NesPal,,,,0,32,8,,0,0,0,v,,, 19E81461,Famicom,,,,157,256,,8,0,0,0,,,, 19F4CA6B,NesNtsc,NES-SJROM,NES-SJROM-03,MMC1B2,1,128,32,,8,0,0,,,, -1A128930,Famicom,,,,241,512,,8,0,8,1,v,SubOrKeyboard,, +1A128930,Famicom,,,,241,512,,8,0,8,1,v,SuborKeyboard,, 1A1D3CA2,Famicom,,,,2,128,,8,0,0,0,v,,, 1A2A7EF7,Famicom,,,,178,512,,8,0,8,1,,,, 1A2EA6B9,Famicom,,,,1,128,,8,0,0,0,,,, @@ -664,7 +664,7 @@ 362000F0,Famicom,,,,90,256,512,,0,0,0,,,, 36584C96,Famicom,,,,4,256,128,,0,0,0,,,, 3674FFDB,Famicom,,,,1,256,,8,0,0,0,,,, -368C19A8,Dendy,,,,34,128,,8,0,0,0,h,SubOrKeyboard,, +368C19A8,Dendy,,,,34,128,,8,0,0,0,h,SuborKeyboard,, 3691C120,Famicom,JALECO-JF-40,JF-40,SS88006,18,256,128,,0,8,1,,,, 369DA42D,Famicom,NAMCOT-163,60-16,163,19,128,128,,0,8,1,,,, 36B35988,NesNtsc,AVE-MB-91,MB-91«J»,,79,32,32,,0,0,0,v,,, @@ -804,7 +804,7 @@ 41CC30A7,Famicom,HVC-SLROM,HVC-SLROM-03,MMC1B2,1,128,128,,0,0,0,,FourPlayer,, 41CF5B6A,Famicom,,,,246,512,512,,0,8,1,h,,, 41D32FD7,NesPal,NES-AOROM,NES-AOROM-03,,7,256,,8,0,0,0,,,, -41EF9AC4,Dendy,,,,167,1024,,8,0,8,1,h,SubOrKeyboard,, +41EF9AC4,Dendy,,,,167,1024,,8,0,8,1,h,SuborKeyboard,, 41F5D38D,Famicom,,,,1,128,128,,0,0,0,,,, 41F9E0AA,NesNtsc,NES-TLSROM,NES-TLSROM-01,MMC3C,118,128,128,,0,0,0,,,, 4220C170,NesNtsc,NES-AOROM,NES-AOROM-03,,7,128,,8,0,0,0,,,, @@ -1006,7 +1006,7 @@ 53A9B53A,NesPal,NES-SLROM,NES-SLROM-06,MMC1B3,1,128,128,,0,0,0,,,, 53A9E2BA,NesNtsc,,,,4,256,256,,0,8,1,,,, 542BED56,Famicom,,,,225,1024,512,,0,0,0,,,, -543AB532,Dendy,,,,7,128,8,,0,0,0,,SubOrKeyboard,, +543AB532,Dendy,,,,7,128,8,,0,0,0,,SuborKeyboard,, 543C6EDE,Famicom,,,,0,32,8,,0,0,0,h,,, 5440811C,Famicom,HVC-UNROM,HVC-UNROM-03,,2,128,,8,0,0,0,v,,, 5452966E,Famicom,,,,4,128,128,,0,0,0,,,, @@ -1113,7 +1113,7 @@ 5DCE2EEA,NesNtsc,NES-SLROM,NES-SLROM-06,MMC1B2,1,128,128,,0,0,0,,,, 5DE61639,NesNtsc,NES-SNROM,NES-SNROM-06,MMC1B2,1,256,,8,0,8,1,,,, 5DEC84F8,Famicom,,,,1,128,128,,0,0,0,,,, -5E073A1B,Dendy,,,,167,1024,,8,0,8,1,h,SubOrKeyboard,, +5E073A1B,Dendy,,,,167,1024,,8,0,8,1,h,SuborKeyboard,, 5E1ADD91,Famicom,,,,3,32,16,,0,0,0,h,,, 5E24EEDA,Famicom,HVC-TLROM,HVC-TLROM-04,MMC3B,4,128,128,,0,0,0,,,, 5E345B6D,Famicom,HVC-NROM-256,3A,,0,32,8,,0,0,0,h,,, @@ -1459,7 +1459,7 @@ 7BB5664F,Famicom,NAMCOT-3405,3405,109,206,128,32,,0,0,0,h,,, 7BCCAFBB,NesPal,NES-TLROM,NES-TLROM-03,MMC3B,4,256,256,,0,0,0,,,, 7BD8F902,Famicom,,,,4,256,128,,0,0,0,,,, -7BDD12F3,Dendy,,,,241,128,,8,8,0,0,v,SubOrKeyboard,, +7BDD12F3,Dendy,,,,241,128,,8,8,0,0,v,SuborKeyboard,, 7BF8A890,Famicom,HVC-TLROM,HVC-TLROM-03,MMC3B,4,128,128,,0,0,0,,,, 7C108923,Famicom,,,,1,128,128,,0,0,0,,,, 7C16F819,NesPal,NES-TLROM,NES-TLROM-03,MMC3B,4,128,128,,0,0,0,,FourPlayer,, @@ -1543,7 +1543,7 @@ 82AFA828,NesNtsc,NES-SLROM,NES-SLROM-04,MMC1B2,1,128,128,,0,0,0,,,, 82B9BBA7,Famicom,,,,4,256,256,,0,0,0,,,, 82BE4724,NesNtsc,NES-UNROM,NES-UN-ROM-04,,2,128,,8,0,0,0,h,,, -82F1FB96,Dendy,,,,166,128,,8,8,0,0,v,SubOrKeyboard,, +82F1FB96,Dendy,,,,166,128,,8,8,0,0,v,SuborKeyboard,, 83000991,NesPal,NES-UNROM,NES-UNROM-09,,2,128,,8,0,0,0,h,,, 8308FED7,Famicom,HVC-SLROM,HVC-SLROM-02,MMC1A,1,128,128,,0,0,0,,,, 831E6E0E,Famicom,,,,4,128,128,,0,0,0,,,, @@ -1721,7 +1721,7 @@ 900E3A23,Famicom,,,,4,256,256,,0,8,1,,,, 90226E40,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3B,4,128,256,,0,0,0,,,, 902E3168,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3C,4,128,128,,0,0,0,,,, -903A95EB,Dendy,,,,241,128,,8,8,0,0,v,SubOrKeyboard,, +903A95EB,Dendy,,,,241,128,,8,8,0,0,v,SuborKeyboard,, 9044550E,Famicom,,,,3,32,32,,0,0,0,h,FamilyTrainer,, 905B93F6,NesPal,NTDEC-N715061,N715061,,3,32,8,,0,0,0,v,,, 90600B85,Famicom,HVC-NROM-256,HVC-NROM-256K-02,,0,32,8,,0,0,0,v,,, @@ -2080,7 +2080,7 @@ AB41445E,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3C,4,128,128,,0,0,0,,,, AB47A50E,Famicom,HVC-UNROM,HVC-UNROM-02,,2,128,,8,0,0,0,h,,, AB671224,NesPal,NES-SLROM,NES-SLROM-06,MMC1B2,1,128,128,,0,0,0,,,, ABAA6F78,Famicom,HVC-SNROM,HVC-SNROM-04,MMC1A,1,256,,8,0,8,1,,,, -ABB2F974,Famicom,,,,39,1024,,8,8,0,0,v,SubOrKeyboard,, +ABB2F974,Famicom,,,,39,1024,,8,8,0,0,v,SuborKeyboard,, ABBF7217,Famicom,HVC-SOROM,HVC-SOROM-03,MMC1A,1,256,,8,8,8,1,,,, ABE1A0C2,VsUni,,,,99,32,16,,0,0,0,4,Zapper,, ABE8E174,VsUni,,,,99,32,16,,0,0,0,4,,, @@ -2137,7 +2137,7 @@ B037246D,NesNtsc,COLORDREAMS-74*377,UNK-COLORDREAMS-REVA,,11,32,32,,0,0,0,v,Zapp B0480AE9,NesNtsc,NES-ELROM,NES-ELROM-01,MMC5,5,128,128,,0,0,0,,Zapper,, B049A8C4,Famicom,BANDAI-LZ93D50+24C02,DRAGON BALL Z-B,FCG-3,16,256,256,,0,0,0,,,, B04BA659,Famicom,HVC-UNROM,HVC-UNROM-02,,2,128,,8,0,0,0,v,,, -B066111A,Dendy,,,,0,32,,8,0,0,0,v,SubOrKeyboard,, +B066111A,Dendy,,,,0,32,,8,0,0,0,v,SuborKeyboard,, B06C0674,Famicom,KONAMI-VRC-1,024-3,VRC1,75,128,128,,0,0,0,,,, B0BC46D1,NesPal,NES-GNROM,NES-GNROM-05,,66,128,32,,0,0,0,v,,, B0CD000F,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3C,4,128,128,,0,0,0,,,, @@ -2359,7 +2359,7 @@ C1E91D3F,NesPal,NES-NROM-256,NES-NROM-256-06,,0,32,8,,0,0,0,v,,, C1FBF659,Famicom,KONAMI-VRC-4,352396,VRC4,23,128,128,,0,0,0,,,, C20E9CA7,VsUni,,,,99,32,16,,0,0,0,4,,, C22BC87B,Famicom,HVC-TKROM,HVC-TKROM-03,MMC3C,4,128,128,,0,8,1,,,, -C22C23AB,Famicom,HVC-SLRROM,HVC-SLRROM-01,MMC1B2,1,128,128,,0,0,0,,,, +C22C23AB,Famicom,,,,1,128,128,,0,0,0,,Pachinko,, C22F3E9F,NesNtsc,NES-SKEPROM,NES-SKEPROM-01,MMC1B2,1,128,128,,8,0,0,,,, C22FF1D8,Famicom,HVC-SLROM,HVC-SLROM-03,MMC1B2,1,128,128,,0,0,0,,,, C247A23D,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3C,4,128,256,,0,0,0,,,, @@ -2589,7 +2589,7 @@ D568563F,Famicom,HVC-TLROM,HVC-TLROM-04,MMC3C,4,128,128,,0,0,0,,,, D5941AA9,Famicom,HVC-SLROM,HVC-SLROM-03,MMC1B2,1,128,128,,0,0,0,,,, D5C64257,NesNtsc,NES-NROM-256,NES-NROM-256-01,,0,32,8,,0,0,0,v,,, D5C71458,NesNtsc,,,,7,128,,8,0,0,0,,,, -D5D6EAC4,Famicom,,,,241,512,,8,0,8,1,v,SubOrKeyboard,, +D5D6EAC4,Famicom,,,,241,512,,8,0,8,1,v,SuborKeyboard,, D5ED8AC0,Famicom,HVC-NROM-256,,,0,32,8,,0,0,0,h,,, D630EE8F,NesPal,NES-SLROM,NES-SLROM-06,MMC1B2,1,128,128,,0,0,0,,,, D63B30F5,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3C,4,128,128,,0,0,0,,,, @@ -2844,7 +2844,7 @@ E95E51E0,Famicom,,,,1,128,128,,0,0,0,,,, E98AB943,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3C,4,128,128,,0,0,0,,,, E9A6C211,NesNtsc,KONAMI-SLROM,351908,MMC1B2,1,128,128,,0,0,0,,,, E9A6F17D,VsUni,,,,99,32,8,,0,0,0,4,,, -E9A7FE9E,Dendy,UNL-EDU2000,,,,1024,,8,0,32,1,,SubOrKeyboard,, +E9A7FE9E,Dendy,UNL-EDU2000,,,65000,1024,,8,0,32,1,,SuborKeyboard,, E9AD2163,NesPal,NTDEC-N715061,N715061,,3,32,32,,0,0,0,h,,, E9C387EC,NesNtsc,NES-TLROM,NES-TLROM-02,MMC3B,4,128,128,,0,0,0,,,, E9D352EB,NesPal,NES-TLROM,NES-TLROM-03,MMC3B,4,128,128,,0,0,0,,,, @@ -2872,7 +2872,7 @@ EB803610,NesNtsc,NES-TLROM,NES-TLROM-03,MMC3B,4,128,128,,0,0,0,,,, EB84C54C,NesNtsc,NES-AMROM,NES-AMROM-01,,7,128,,8,0,0,0,,,, EB92B32A,Famicom,KONAMI-VRC-2,351948,VRC2,25,256,256,,0,8,1,,,, EB9960EE,NesNtsc,NES-CNROM,NES-CNROM-07,,3,32,32,,0,0,0,v,,, -EBB56E10,Dendy,,,,7,128,8,,0,0,0,,SubOrKeyboard,, +EBB56E10,Dendy,,,,7,128,8,,0,0,0,,SuborKeyboard,, EBCF8419,Famicom,HVC-TKROM,HVC-TKROM-02,MMC3B,4,256,128,,0,8,1,,,, EBCFE7C5,NesNtsc,NES-SC1ROM,NES-SC1ROM-01,MMC1B2,1,64,128,,0,0,0,,,, EBD0644D,NesNtsc,,,,3,32,32,,0,0,0,v,,, diff --git a/GUI.NET/Dependencies/resources.ca.xml b/GUI.NET/Dependencies/resources.ca.xml index 1a41ce23..50ff6574 100644 --- a/GUI.NET/Dependencies/resources.ca.xml +++ b/GUI.NET/Dependencies/resources.ca.xml @@ -19,6 +19,7 @@ Configuració de joc VS Insereix moneda (1) Insereix moneda (2) + Input Barcode Opcions Velocitat Normal (100%) @@ -597,6 +598,11 @@ Això iniciarà Mesen en mode de pantalla completa i amb la rom "MyGame.nes" carregada. A més, farà servir el filtre NTSC ajustat a una escala de 2x i configurarà les opcions de sobreescaneig. El paràmetre "DoNotSaveSettings" s'utilitza per evitar que els canvis efectuats des de la línia d'ordres es desin permanentment a la configuració de Mesen. /fullscreen - Inicia Mesen en pantalla completa /DoNotSaveSettings - Evita que els canvis es desin (útil per a evitar que les opcions de la línia d'ordres es converteixin en la configuració per defecte) +
+ Barcode: + D'acord + Cancel·la +
Tots els fitxers (*.*)|*.* @@ -756,6 +762,30 @@ Comandament estàndard Zapper Comandament Arkanoid + SNES Controller + Power Pad + SNES Mouse + Subor Mouse + + + Cap + Zapper + Adaptador per a 4 jugadors + Comandament Arkanoid + Tauleta Oeka Kids + Family Trainer + Konami Hyper Shot + Family Basic Keyboard + Partytap + Pachinko Controller + Exciting Boxing Punching Bag + Jissen Mahjong Controller + Subor Keyboard + Barcode Battler + Hori Track + Bandai Hyper Shot + Turbo File + Battle Box Per defecte (no estirar) @@ -803,13 +833,6 @@ NES Famicom - - Cap - Zapper - Adaptador per a 4 jugadors - Comandament Arkanoid - Tauleta Oeka Kids - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index 222dbe94..477b394b 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -25,6 +25,7 @@ Connect to Server Disconnect Player {0} + Expansion Device {0} roms found @@ -160,6 +161,30 @@ Standard Controller Zapper Arkanoid Controller + SNES Controller + Power Pad + SNES Mouse + Subor Mouse + + + None + Zapper + Four Player Adapter + Arkanoid Controller + Oeka Kids Tablet + Family Trainer + Konami Hyper Shot + Family Basic Keyboard + Partytap + Pachinko Controller + Exciting Boxing Punching Bag + Jissen Mahjong Controller + Subor Keyboard + Barcode Battler + Hori Track + Bandai Hyper Shot + Turbo File + Battle Box Default (No Stretching) @@ -207,13 +232,6 @@ NES Famicom - - None - Zapper - Four Player Adapter - Arkanoid Controller - Oeka Kids Tablet - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Dependencies/resources.es.xml b/GUI.NET/Dependencies/resources.es.xml index c5e986fb..ae83bb96 100644 --- a/GUI.NET/Dependencies/resources.es.xml +++ b/GUI.NET/Dependencies/resources.es.xml @@ -19,6 +19,7 @@ Configuración de juego VS Insertar moneda (1) Insertar moneda (2) + Input Barcode Opciones Velocidad Normal (100%) @@ -616,6 +617,11 @@ CONFIRMAR Cancelar +
+ Barcode: + OK + Cancelar +
Todos los tipos de archivo (*.*)|*.* @@ -775,6 +781,30 @@ Control estándar Zapper Control Arkanoid + SNES Controller + Power Pad + SNES Mouse + Subor Mouse +
+ + Ninguno + Zapper + Adaptador para 4 jugadores + Control Arkanoid + Oeka Kids Tablet + Family Trainer + Konami Hyper Shot + Family Basic Keyboard + Partytap + Pachinko Controller + Exciting Boxing Punching Bag + Jissen Mahjong Controller + Subor Keyboard + Barcode Battler + Hori Track + Bandai Hyper Shot + Turbo File + Battle Box Por defecto (No estirar) @@ -822,13 +852,6 @@ NES Famicom - - Ninguno - Zapper - Adaptador para 4 jugadores - Control Arkanoid - Oeka Kids Tablet - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index 5d312c8d..4d0919e4 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -19,6 +19,7 @@ Configuration du jeu VS Insérer une pièce (1) Insérer une pièce (2) + Entrer un code-barres Options Vitesse Normale (100%) @@ -60,6 +61,7 @@ Joueur 2 Joueur 3 Joueur 4 + Port d'extension Spectateur Trouver un serveur publique... Configurer votre profil @@ -628,6 +630,11 @@ CONFIRMER Annuler +
+ Code-barres : + OK + Annuler +
Tous les fichiers (*.*)|*.* @@ -788,6 +795,30 @@ Manette standard Zapper Manette Arkanoid + Manette SNES + Power Pad + Souris SNES + Souris Subor +
+ + Aucun + Zapper + Adapteur pour 4 joueurs + Manette Arkanoid + Tablette Oeka Kids + Family Trainer + Konami Hyper Shot + Clavier Family Basic + Partytap + Manette Pachinko + Exciting Boxing Punching Bag + Manette Jissen Mah-jong + Clavier Subor + Barcode Battler + Hori Track + Bandai Hyper Shot + Turbo File + Battle Box Défaut (Aucun étirement) @@ -835,13 +866,6 @@ NES Famicom - - Aucun - Zapper - Adapteur pour 4 joueurs - Manette Arkanoid - Tablette Oeka Kids - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index fb855c46..9fa6fbfb 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -19,6 +19,7 @@ VSゲームの設定 インサートコイン 1 インサートコイン 2 + バーコードを入力 設定 速度 通常 (100%) @@ -613,6 +614,11 @@ 確認 キャンセル +
+ バーコード: + 入力 + キャンセル +
すべてのファイル (*.*)|*.* @@ -773,6 +779,30 @@ ファミコンコントローラ ガン Arkanoidコントローラ + スーパーファミコンコントローラ + パワーパッド + スーパーファミコマウス + Suborマウス +
+ + なし + ガン + 4-Player Adapter + Arkanoidコントローラ + おえかきっずタブレット + ファミリートレーナー + Konamiハイパーショット + Family Basicキーボード + Partytap + パチンココントローラ + エキサイティングボクシングエアバッグ + 実践麻雀コントローラ + Suborキーボード + バーコードバトラー + ホリトラク + Bandaiハイパーショット + Turbo File + バトルボックス デフォルト (1:1) @@ -820,13 +850,6 @@ NES ファミコン - - なし - ガン - 4-Player Adapter - Arkanoidコントローラ - おえかきっずタブレット - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Dependencies/resources.pt.xml b/GUI.NET/Dependencies/resources.pt.xml index fe194af7..3ae56871 100644 --- a/GUI.NET/Dependencies/resources.pt.xml +++ b/GUI.NET/Dependencies/resources.pt.xml @@ -19,6 +19,7 @@ Configuração de jogo VS Inserir moeda (1) Inserir moeda (2) + Input Barcode Opções Velocidade Normal (100%) @@ -614,6 +615,11 @@ CONFIRMAR Cancelar +
+ Barcode: + Ok + Cancelar +
Todos os tipos de arquivo (*.*)|*.* @@ -773,6 +779,30 @@ Controle padrão Zapper Controle Arkanoid + SNES Controller + Power Pad + SNES Mouse + Subor Mouse +
+ + Nenhum + Zapper + Adaptador para 4 jogadores + Controle Arkanoid + Tablet Oeka Kids + Family Trainer + Konami Hyper Shot + Family Basic Keyboard + Partytap + Pachinko Controller + Exciting Boxing Punching Bag + Jissen Mahjong Controller + Subor Keyboard + Barcode Battler + Hori Track + Bandai Hyper Shot + Turbo File + Battle Box Padrão (Não esticado) @@ -820,13 +850,6 @@ NES Famicom - - Nenhum - Zapper - Adaptador para 4 jogadores - Controle Arkanoid - Tablet Oeka Kids - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Dependencies/resources.ru.xml b/GUI.NET/Dependencies/resources.ru.xml index 4587b9cc..d365948e 100644 --- a/GUI.NET/Dependencies/resources.ru.xml +++ b/GUI.NET/Dependencies/resources.ru.xml @@ -19,6 +19,7 @@ Конфигурация VS Вставить монету в слот 1 Вставить монету в слот 2 + Input Barcode Опции Скорость эмуляции Нормальная (100%) @@ -617,7 +618,12 @@ Create a shortcut on my desktop CONFIRM Cancel - + +
+ Barcode: + OK + Отмена +
Все файлы (*.*)|*.* @@ -778,6 +784,30 @@ Стандартный контроллер Zapper Arkanoid + SNES Controller + Power Pad + SNES Mouse + Subor Mouse +
+ + Пусто + Zapper + Адаптер на 4 игрока + Arkanoid + Oeka Kids Tablet + Family Trainer + Konami Hyper Shot + Family Basic Keyboard + Partytap + Pachinko Controller + Exciting Boxing Punching Bag + Jissen Mahjong Controller + Subor Keyboard + Barcode Battler + Hori Track + Bandai Hyper Shot + Turbo File + Battle Box Default (No Stretching) @@ -825,13 +855,6 @@ NES Famicom - - Пусто - Zapper - Адаптер на 4 игрока - Arkanoid - Oeka Kids Tablet - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Dependencies/resources.uk.xml b/GUI.NET/Dependencies/resources.uk.xml index cba7b090..e1d98cc0 100644 --- a/GUI.NET/Dependencies/resources.uk.xml +++ b/GUI.NET/Dependencies/resources.uk.xml @@ -19,6 +19,7 @@ Конфігурація VS Вставити монету в слот 1 Вставити монету в слот 2 + Input Barcode Опції Швидкість емуляції Нормальна (100%) @@ -618,6 +619,11 @@ ПIДТВЕРДИТИ Скасувати +
+ Barcode: + OK + Вiдмiна +
Всi файли (*.*)|*.* @@ -746,7 +752,7 @@ Переключити Background Переключити Sprite Layer Переключити максимальну швидкість - Завантажити випадкову гру/Message> + Завантажити випадкову гру Save State - Slot 1 Save State - Slot 2 Save State - Slot 3 @@ -778,6 +784,30 @@ Стандартний контролер Zapper Arkanoid + SNES Controller + Power Pad + SNES Mouse + Subor Mouse +
+ + Пусто + Zapper + Адаптер на 4 гравців + Arkanoid + Oeka Kids Tablet + Family Trainer + Konami Hyper Shot + Family Basic Keyboard + Partytap + Pachinko Controller + Exciting Boxing Punching Bag + Jissen Mahjong Controller + Subor Keyboard + Barcode Battler + Hori Track + Bandai Hyper Shot + Turbo File + Battle Box Default (Не Розтягувати) @@ -825,13 +855,6 @@ NES Famicom - - Пусто - Zapper - Адаптер на 4 гравців - Arkanoid - Oeka Kids Tablet - RP2C03 RP2C04-0001 diff --git a/GUI.NET/Forms/Config/BaseInputConfigControl.cs b/GUI.NET/Forms/Config/BaseInputConfigControl.cs new file mode 100644 index 00000000..b33630fe --- /dev/null +++ b/GUI.NET/Forms/Config/BaseInputConfigControl.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Mesen.GUI.Config; +using Mesen.GUI.Controls; + +namespace Mesen.GUI.Forms.Config +{ + public class BaseInputConfigControl : BaseControl + { + public event EventHandler Change; + protected KeyMappings _mappings; + protected HashSet