Input: Added support for ~20 new peripherals (Incomplete, more fixes/changes to come)

Movies: Rewrote format to support all IO devices and console types
Netplay: Now supports all IO devices and console types
This commit is contained in:
Souryo 2017-11-19 23:08:23 -05:00
parent 3f83097e64
commit 850102bbdc
163 changed files with 7535 additions and 2793 deletions

3
.gitignore vendored
View File

@ -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

View File

@ -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;
}

View File

@ -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);
}
};

65
Core/AsciiTurboFile.h Normal file
View File

@ -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<uint8_t> 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;
}
};

View File

@ -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<DatachBarcodeReader> _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

76
Core/BandaiHyperShot.h Normal file
View File

@ -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);
}
};

View File

@ -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

45
Core/BandaiMicrophone.h Normal file
View File

@ -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
{
}
};

102
Core/BarcodeBattlerReader.h Normal file
View File

@ -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<uint8_t> 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<uint8_t> 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
{
}
};

View File

@ -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<uint8_t> 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<string> 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;
}
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<BaseControlDevice> state1, uint8_t button1, shared_ptr<BaseControlDevice> 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);
}
}

View File

@ -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<KeyMapping> _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();
};
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<typename T>
shared_ptr<T> GetState()
{
return std::dynamic_pointer_cast<T>(_state);
}
virtual uint8_t ReadRAM(uint16_t addr) = 0;
virtual void WriteRAM(uint16_t addr, uint8_t value) = 0;
void static SwapButtons(shared_ptr<BaseControlDevice> state1, uint8_t button1, shared_ptr<BaseControlDevice> state2, uint8_t button2);
};

View File

@ -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<uint8_t> 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<uint8_t> 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<uint8_t> 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<BaseControlDevice> BaseMapper::GetMapperControlDevice()
{
return _mapperControlDevice;
}
GameSystem BaseMapper::GetGameSystem()
{
return _gameSystem;

View File

@ -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<uint8_t> _originalChrRom;
protected:
shared_ptr<BaseControlDevice> _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<BaseControlDevice> GetMapperControlDevice();
GameSystem GetGameSystem();
HashInfo GetHashInfo();
string GetRomName();

69
Core/BatteryManager.cpp Normal file
View File

@ -0,0 +1,69 @@
#include "stdafx.h"
#include "BatteryManager.h"
#include "VirtualFile.h"
#include "../Utilities/FolderUtilities.h"
string BatteryManager::_romName;
std::weak_ptr<IBatteryRecorder> BatteryManager::_recorder;
std::weak_ptr<IBatteryProvider> BatteryManager::_provider;
void BatteryManager::Initialize(string romName)
{
_romName = romName;
}
string BatteryManager::GetBasePath()
{
return FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), _romName);
}
void BatteryManager::SetBatteryProvider(shared_ptr<IBatteryProvider> provider)
{
_provider = provider;
}
void BatteryManager::SetBatteryRecorder(shared_ptr<IBatteryRecorder> 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<uint8_t> BatteryManager::LoadBattery(string extension)
{
shared_ptr<IBatteryProvider> provider = _provider.lock();
vector<uint8_t> 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<IBatteryRecorder> 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<uint8_t> batteryData = LoadBattery(extension);
memset(data, 0, length);
memcpy(data, batteryData.data(), std::min((uint32_t)batteryData.size(), length));
}

37
Core/BatteryManager.h Normal file
View File

@ -0,0 +1,37 @@
#pragma once
#include "stdafx.h"
class IBatteryProvider
{
public:
virtual vector<uint8_t> LoadBattery(string extension) = 0;
};
class IBatteryRecorder
{
public:
virtual void OnLoadBattery(string extension, vector<uint8_t> batteryData) = 0;
};
class BatteryManager
{
private:
static string _romName;
static string GetBasePath();
static std::weak_ptr<IBatteryProvider> _provider;
static std::weak_ptr<IBatteryRecorder> _recorder;
BatteryManager() = delete;
public:
static void Initialize(string romName);
static void SetBatteryProvider(shared_ptr<IBatteryProvider> provider);
static void SetBatteryRecorder(shared_ptr<IBatteryRecorder> recorder);
static void SaveBattery(string extension, uint8_t* data, uint32_t length);
static vector<uint8_t> LoadBattery(string extension);
static void LoadBattery(string extension, uint8_t* data, uint32_t length);
};

118
Core/BattleBox.h Normal file
View File

@ -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<uint8_t> 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;
}
};

View File

@ -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<SystemActionManager*>(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<VsSystemActionManager*>(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<FdsSystemActionManager*>(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<StandardController*>(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<FdsSystemActionManager> fdsActionManager = Console::GetInstance()->GetSystemActionManager<FdsSystemActionManager>();
if(fdsActionManager) {
//Eject disk + Insert Disk #XX
systemActionCount += fdsActionManager->GetSideCount() + 1;
} else {
shared_ptr<VsSystemActionManager> vsActionManager = Console::GetInstance()->GetSystemActionManager<VsSystemActionManager>();
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
}

View File

@ -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<uint32_t> _systemActionByFrame;
vector<uint8_t> _dataByFrame[4];
vector<string> _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;
};

View File

@ -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> Console::Instance(new Console());
Console::Console()
{
_resetRequested = false;
_lagCounter = 0;
}
Console::~Console()
@ -51,17 +58,26 @@ shared_ptr<Console> Console::GetInstance()
void Console::Release()
{
Console::Instance.reset(new Console());
Console::Instance.reset();
}
void Console::SaveBatteries()
{
_mapper->SaveBattery();
shared_ptr<IBattery> device = std::dynamic_pointer_cast<IBattery>(_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<uint8_t> fileData;
romFile.ReadFile(fileData);
BatteryManager::Initialize(FolderUtilities::GetFilename(romFile.GetFileName(), false));
shared_ptr<BaseMapper> 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<string> validExtensions = { { ".nes", ".fds", "*.unif", "*.unif", "*.nsf", "*.nsfe", "*.7z", "*.zip" } };
@ -180,19 +234,19 @@ bool Console::LoadROM(string romName, HashInfo hashInfo)
vector<string> files = FolderUtilities::GetFilesInFolder(folder, validExtensions, true);
romFiles.insert(romFiles.end(), files.begin(), files.end());
}
string match = RomLoader::FindMatchingRom(romFiles, romName, hashInfo, true);
if(!match.empty()) {
return Console::LoadROM(match);
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<SystemActionManager> 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 = _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<Debugger> 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 = _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<VsControlManager*>(_controlManager.get())) {
features = (ConsoleFeatures)((int)features | (int)ConsoleFeatures::VsSystem);
}
if(std::dynamic_pointer_cast<IBarcodeReader>(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort))) {
features = (ConsoleFeatures)((int)features | (int)ConsoleFeatures::BarcodeReader);
}
}
return features;
}
void Console::InputBarcode(uint64_t barcode, uint32_t digitCount)
{
shared_ptr<IBarcodeReader> barcodeReader = std::dynamic_pointer_cast<IBarcodeReader>(_mapper->GetMapperControlDevice());
if(barcodeReader) {
barcodeReader->InputBarcode(barcode, digitCount);
}
barcodeReader = std::dynamic_pointer_cast<IBarcodeReader>(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort));
if(barcodeReader) {
barcodeReader->InputBarcode(barcode, digitCount);
}
}

View File

@ -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> _ppu;
shared_ptr<APU> _apu;
shared_ptr<Debugger> _debugger;
SimpleLock _debuggerLock;
shared_ptr<BaseMapper> _mapper;
unique_ptr<ControlManager> _controlManager;
shared_ptr<MemoryManager> _memoryManager;
shared_ptr<SystemActionManager> _systemActionManager;
unique_ptr<AutoSaveManager> _autoSaveManager;
shared_ptr<HdPackBuilder> _hdPackBuilder;
@ -55,29 +59,41 @@ class Console
bool _disableOcNextFrame = false;
atomic<bool> _resetRequested;
atomic<uint32_t> _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<SystemActionManager> GetSystemActionManager();
template<typename T>
shared_ptr<T> GetSystemActionManager()
{
return std::dynamic_pointer_cast<T>(_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<Debugger> GetDebugger(bool autoStart = true);
shared_ptr<Debugger> 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();

13
Core/ControlDeviceState.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include "stdafx.h"
#include <cstring>
struct ControlDeviceState
{
vector<uint8_t> State;
bool operator!=(ControlDeviceState &other)
{
return State.size() != other.State.size() || memcmp(State.data(), other.State.data(), State.size()) != 0;
}
};

View File

@ -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<IKeyManager> ControlManager::_keyManager = nullptr;
shared_ptr<BaseControlDevice> ControlManager::_controlDevices[2] = { nullptr, nullptr };
IGameBroadcaster* ControlManager::_gameBroadcaster = nullptr;
MousePosition ControlManager::_mousePosition = { -1, -1 };
ControlManager* ControlManager::_instance = nullptr;
vector<IInputRecorder*> ControlManager::_inputRecorders;
vector<IInputProvider*> ControlManager::_inputProviders;
SimpleLock ControlManager::_deviceLock;
ControlManager::ControlManager()
ControlManager::ControlManager(shared_ptr<BaseControlDevice> systemActionManager, shared_ptr<BaseControlDevice> 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<IInputProvider*> &vec = _inputProviders;
vec.erase(std::remove(vec.begin(), vec.end(), provider), vec.end());
}
vector<uint32_t> ControlManager::GetPressedKeys()
void ControlManager::RegisterInputRecorder(IInputRecorder* provider)
{
if(_keyManager != nullptr) {
return _keyManager->GetPressedKeys();
}
return vector<uint32_t>();
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<IInputRecorder*> &vec = _inputRecorders;
vec.erase(std::remove(vec.begin(), vec.end(), provider), vec.end());
}
uint32_t ControlManager::GetKeyCode(string keyName)
vector<ControlDeviceState> 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<ControlDeviceState> states;
for(int i = 0; i < 4; i++) {
shared_ptr<BaseControlDevice> device = GetControlDevice(i);
if(device) {
states.push_back(device->GetRawState());
} else {
states.push_back(ControlDeviceState());
}
}
return states;
}
shared_ptr<BaseControlDevice> 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<BaseControlDevice> control) { return control->GetPort() == port; });
if(result != _instance->_controlDevices.end()) {
return *result;
}
return nullptr;
}
void ControlManager::RegisterControlDevice(shared_ptr<BaseControlDevice> controlDevice, uint8_t port)
void ControlManager::RegisterControlDevice(shared_ptr<BaseControlDevice> 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<BaseControlDevice> ControlManager::CreateControllerDevice(ControllerType type, uint8_t port)
{
if(_keyManager) {
_keyManager->RefreshState();
shared_ptr<BaseControlDevice> 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<BaseControlDevice> ControlManager::GetZapper(uint8_t port)
shared_ptr<BaseControlDevice> ControlManager::CreateExpansionDevice(ExpansionPortDevice type)
{
return shared_ptr<BaseControlDevice>(new Zapper(port));
shared_ptr<BaseControlDevice> 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<BaseControlDevice> 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<BaseControlDevice> device = CreateControllerDevice(GetControllerType(i), i);
if(device) {
ControlManager::RegisterControlDevice(device, i);
if(fourScore) {
if(EmulationSettings::GetControllerType(i + 2) == ControllerType::StandardController) {
std::dynamic_pointer_cast<StandardController>(device)->AddAdditionalController(shared_ptr<StandardController>(new StandardController(i + 2)));
}
} else if(i == 1 || expansionDevice == ExpansionPortDevice::ArkanoidController) {
switch(expansionDevice) {
case ExpansionPortDevice::Zapper: std::dynamic_pointer_cast<StandardController>(device)->AddAdditionalController(shared_ptr<Zapper>(new Zapper(2))); break;
case ExpansionPortDevice::ArkanoidController: std::dynamic_pointer_cast<StandardController>(device)->AddAdditionalController(shared_ptr<ArkanoidController>(new ArkanoidController(2))); break;
case ExpansionPortDevice::OekaKidsTablet: std::dynamic_pointer_cast<StandardController>(device)->AddAdditionalController(shared_ptr<OekaKidsTablet>(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<FourScore>(new FourScore()));
}
shared_ptr<BaseControlDevice> 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<BaseControlDevice> 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<StandardController> controller = std::dynamic_pointer_cast<StandardController>(GetControlDevice(1));
if(controller && controller->IsMicrophoneActive()) {
value |= 0x04;
auto lock = _deviceLock.AcquireSafe();
string log = "";
for(shared_ptr<BaseControlDevice> &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> 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<BaseControlDevice> &device : _controlDevices) {
value |= device->ReadRAM(addr);
}
return value;
return 0;
}
template<typename T>
shared_ptr<T> ControlManager::GetExpansionDevice()
{
shared_ptr<StandardController> controller;
controller = std::dynamic_pointer_cast<StandardController>(GetControlDevice(1));
if(controller) {
shared_ptr<T> expansionDevice;
expansionDevice = std::dynamic_pointer_cast<T>(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<OekaKidsTablet>();
if(tablet) {
tablet->WriteRam(value);
} else {
if(previousState && !_refreshState) {
//Refresh controller once strobe bit is disabled
RefreshAllPorts();
}
for(shared_ptr<BaseControlDevice> &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<ControllerType> 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;
}

View File

@ -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<IKeyManager> _keyManager;
static shared_ptr<BaseControlDevice> _controlDevices[2];
static IGameBroadcaster* _gameBroadcaster;
static MousePosition _mousePosition;
private:
static ControlManager* _instance;
static vector<IInputRecorder*> _inputRecorders;
static vector<IInputProvider*> _inputProviders;
static SimpleLock _deviceLock;
bool _isLagging = false;
bool _refreshState = false;
vector<shared_ptr<BaseControlDevice>> _controlDevices;
uint8_t GetOpenBusMask(uint8_t port);
shared_ptr<BaseControlDevice> _systemActionManager;
shared_ptr<BaseControlDevice> _mapperControlDevice;
template<typename T> shared_ptr<T> GetExpansionDevice();
uint32_t _lagCounter = 0;
bool _isLagging = false;
bool _refreshState = false;
virtual shared_ptr<BaseControlDevice> GetZapper(uint8_t port);
uint8_t GetOpenBusMask(uint8_t port);
void RegisterControlDevice(shared_ptr<BaseControlDevice> controlDevice);
static void RegisterControlDevice(shared_ptr<BaseControlDevice> 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<BaseControlDevice> systemActionManager, shared_ptr<BaseControlDevice> 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<uint32_t> GetPressedKeys();
static string GetKeyName(uint32_t keyCode);
static uint32_t GetKeyCode(string keyName);
static shared_ptr<BaseControlDevice> 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<ControlDeviceState> 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;
};
static shared_ptr<BaseControlDevice> GetControlDevice(uint8_t port);
static shared_ptr<BaseControlDevice> CreateControllerDevice(ControllerType type, uint8_t port);
static shared_ptr<BaseControlDevice> 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;
};

View File

@ -411,25 +411,53 @@
<ClInclude Include="ArkanoidController.h" />
<ClInclude Include="Assembler.h" />
<ClInclude Include="AutomaticRomTest.h" />
<ClInclude Include="BandaiHyperShot.h" />
<ClInclude Include="BandaiMicrophone.h" />
<ClInclude Include="BarcodeBattlerReader.h" />
<ClInclude Include="BaseRenderer.h" />
<ClInclude Include="BatteryManager.h" />
<ClInclude Include="BattleBox.h" />
<ClInclude Include="ControlDeviceState.h" />
<ClInclude Include="FdsSystemActionManager.h" />
<ClInclude Include="IBarcodeReader.h" />
<ClInclude Include="IBattery.h" />
<ClInclude Include="IInputProvider.h" />
<ClInclude Include="IInputRecorder.h" />
<ClInclude Include="KeyManager.h" />
<ClInclude Include="MovieRecorder.h" />
<ClInclude Include="AsciiTurboFile.h" />
<ClInclude Include="SystemActionManager.h" />
<ClInclude Include="DatachBarcodeReader.h" />
<ClInclude Include="DebugHud.h" />
<ClInclude Include="DrawCommand.h" />
<ClInclude Include="DrawLineCommand.h" />
<ClInclude Include="DrawPixelCommand.h" />
<ClInclude Include="DrawStringCommand.h" />
<ClInclude Include="ExcitingBoxingController.h" />
<ClInclude Include="FamilyBasicDataRecorder.h" />
<ClInclude Include="FamilyBasicKeyboard.h" />
<ClInclude Include="FamilyMatTrainer.h" />
<ClInclude Include="FceuxMovie.h" />
<ClInclude Include="FourScore.h" />
<ClInclude Include="HdAudioDevice.h" />
<ClInclude Include="HdBuilderPpu.h" />
<ClInclude Include="HdData.h" />
<ClInclude Include="HdPackBuilder.h" />
<ClInclude Include="HdPackLoader.h" />
<ClInclude Include="HoriTrack.h" />
<ClInclude Include="JissenMahjongController.h" />
<ClInclude Include="KonamiHyperShot.h" />
<ClInclude Include="RotateFilter.h" />
<ClInclude Include="LuaApi.h" />
<ClInclude Include="LuaCallHelper.h" />
<ClInclude Include="LuaScriptingContext.h" />
<ClInclude Include="Mapper174.h" />
<ClInclude Include="Mapper39.h" />
<ClInclude Include="OggMixer.h" />
<ClInclude Include="OggReader.h" />
<ClInclude Include="PachinkoController.h" />
<ClInclude Include="PartyTap.h" />
<ClInclude Include="PowerPad.h" />
<ClInclude Include="Rambo1_158.h" />
<ClInclude Include="RecordedRomTest.h" />
<ClInclude Include="AutoSaveManager.h" />
@ -473,6 +501,10 @@
<ClInclude Include="ScriptHost.h" />
<ClInclude Include="ScriptingContext.h" />
<ClInclude Include="SealieComputing.h" />
<ClInclude Include="SnesController.h" />
<ClInclude Include="SnesMouse.h" />
<ClInclude Include="SuborKeyboard.h" />
<ClInclude Include="SuborMouse.h" />
<ClInclude Include="UnlD1038.h" />
<ClInclude Include="DaouInfosys.h" />
<ClInclude Include="DebugBreakHelper.h" />
@ -675,7 +707,6 @@
<ClInclude Include="HdPpu.h" />
<ClInclude Include="IAudioDevice.h" />
<ClInclude Include="BaseControlDevice.h" />
<ClInclude Include="IGameBroadcaster.h" />
<ClInclude Include="IKeyManager.h" />
<ClInclude Include="IMemoryHandler.h" />
<ClInclude Include="Console.h" />
@ -776,6 +807,7 @@
<ClInclude Include="VsControlManager.h" />
<ClInclude Include="VsSystem.h" />
<ClInclude Include="ScaleFilter.h" />
<ClInclude Include="VsSystemActionManager.h" />
<ClInclude Include="VsZapper.h" />
<ClInclude Include="Waixing162.h" />
<ClInclude Include="Waixing164.h" />
@ -788,10 +820,10 @@
<ItemGroup>
<ClCompile Include="APU.cpp" />
<ClCompile Include="ApuLengthCounter.cpp" />
<ClCompile Include="ArkanoidController.cpp" />
<ClCompile Include="Assembler.cpp" />
<ClCompile Include="AutomaticRomTest.cpp" />
<ClCompile Include="BaseRenderer.cpp" />
<ClCompile Include="BatteryManager.cpp" />
<ClCompile Include="DebugHud.cpp" />
<ClCompile Include="DrawRectangleCommand.h" />
<ClCompile Include="FceuxMovie.cpp" />
@ -799,9 +831,11 @@
<ClCompile Include="HdNesPack.cpp" />
<ClCompile Include="HdPackBuilder.cpp" />
<ClCompile Include="HdPackLoader.cpp" />
<ClCompile Include="KeyManager.cpp" />
<ClCompile Include="LuaApi.cpp" />
<ClCompile Include="LuaCallHelper.cpp" />
<ClCompile Include="LuaScriptingContext.cpp" />
<ClCompile Include="MovieRecorder.cpp" />
<ClCompile Include="OggMixer.cpp" />
<ClCompile Include="OggReader.cpp" />
<ClCompile Include="RecordedRomTest.cpp" />
@ -840,7 +874,6 @@
<ClCompile Include="MesenMovie.cpp" />
<ClCompile Include="NsfMapper.cpp" />
<ClCompile Include="NtscFilter.cpp" />
<ClCompile Include="OekaKidsTablet.cpp" />
<ClCompile Include="Profiler.cpp" />
<ClCompile Include="ReverbFilter.cpp" />
<ClCompile Include="RewindData.cpp" />
@ -852,7 +885,6 @@
<ClCompile Include="ShortcutKeyHandler.cpp" />
<ClCompile Include="Snapshotable.cpp" />
<ClCompile Include="SoundMixer.cpp" />
<ClCompile Include="StandardController.cpp" />
<ClCompile Include="MapperFactory.cpp" />
<ClCompile Include="MemoryManager.cpp" />
<ClCompile Include="MessageManager.cpp" />
@ -881,9 +913,7 @@
<ClCompile Include="VirtualFile.cpp" />
<ClCompile Include="VsControlManager.cpp" />
<ClCompile Include="ScaleFilter.cpp" />
<ClCompile Include="VsZapper.cpp" />
<ClCompile Include="WaveRecorder.cpp" />
<ClCompile Include="Zapper.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -52,9 +52,6 @@
<Filter Include="Nes\Mappers\FDS">
<UniqueIdentifier>{4a7af167-f6cb-4173-b7ca-04ed7c5858b1}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\Controllers">
<UniqueIdentifier>{acd315c2-48ad-4243-a997-bb9a970c24bd}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\APU\Filters">
<UniqueIdentifier>{783f3638-4293-480f-b525-2485c4209ff5}</UniqueIdentifier>
</Filter>
@ -101,6 +98,21 @@
<Filter Include="Debugger\Scripting\Lua">
<UniqueIdentifier>{e1e8a5d2-aa9a-40e1-94eb-00adec1cdef3}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\Input">
<UniqueIdentifier>{acd315c2-48ad-4243-a997-bb9a970c24bd}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\Input\System">
<UniqueIdentifier>{b7a48fc3-5ebb-4aa7-93ae-f70d58bdbfd8}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\Input\Storage">
<UniqueIdentifier>{fdd6ac29-77ce-4db5-8d06-2aa27f95a41c}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\Input\BarcodeReaders">
<UniqueIdentifier>{38798114-2511-4cce-97a7-c6adac514423}</UniqueIdentifier>
</Filter>
<Filter Include="Nes\Input\Controllers">
<UniqueIdentifier>{7b42e684-9487-4031-a769-a05934998777}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="IAudioDevice.h">
@ -262,9 +274,6 @@
<ClInclude Include="GameServerConnection.h">
<Filter>NetPlay</Filter>
</ClInclude>
<ClInclude Include="IGameBroadcaster.h">
<Filter>NetPlay</Filter>
</ClInclude>
<ClInclude Include="Console.h">
<Filter>Nes</Filter>
</ClInclude>
@ -505,14 +514,8 @@
<ClInclude Include="VideoRenderer.h">
<Filter>VideoDecoder</Filter>
</ClInclude>
<ClInclude Include="StandardController.h">
<Filter>Nes\Controllers</Filter>
</ClInclude>
<ClInclude Include="Zapper.h">
<Filter>Nes\Controllers</Filter>
</ClInclude>
<ClInclude Include="BaseControlDevice.h">
<Filter>Nes\Controllers</Filter>
<Filter>Nes\Input</Filter>
</ClInclude>
<ClInclude Include="PlayerListMessage.h">
<Filter>NetPlay\Messages</Filter>
@ -523,9 +526,6 @@
<ClInclude Include="ForceDisconnectMessage.h">
<Filter>NetPlay\Messages</Filter>
</ClInclude>
<ClInclude Include="ArkanoidController.h">
<Filter>Nes\Controllers</Filter>
</ClInclude>
<ClInclude Include="StereoPanningFilter.h">
<Filter>Nes\APU\Filters</Filter>
</ClInclude>
@ -676,9 +676,6 @@
<ClInclude Include="Mapper227.h">
<Filter>Nes\Mappers\Unnamed</Filter>
</ClInclude>
<ClInclude Include="VsZapper.h">
<Filter>Nes\Controllers</Filter>
</ClInclude>
<ClInclude Include="NsfLoader.h">
<Filter>Nes\RomLoader</Filter>
</ClInclude>
@ -844,9 +841,6 @@
<ClInclude Include="Racermate.h">
<Filter>Nes\Mappers</Filter>
</ClInclude>
<ClInclude Include="OekaKidsTablet.h">
<Filter>Nes\Controllers</Filter>
</ClInclude>
<ClInclude Include="MMC3_114.h">
<Filter>Nes\Mappers\MMC</Filter>
</ClInclude>
@ -1243,9 +1237,123 @@
<ClInclude Include="VirtualFile.h">
<Filter>Nes\RomLoader</Filter>
</ClInclude>
<ClInclude Include="Mapper39.h">
<Filter>Nes\Mappers\Unnamed</Filter>
</ClInclude>
<ClInclude Include="MovieRecorder.h">
<Filter>Movies</Filter>
</ClInclude>
<ClInclude Include="IBarcodeReader.h">
<Filter>Nes\Interfaces</Filter>
</ClInclude>
<ClInclude Include="IBattery.h">
<Filter>Nes\Interfaces</Filter>
</ClInclude>
<ClInclude Include="KeyManager.h">
<Filter>Misc</Filter>
</ClInclude>
<ClInclude Include="IInputProvider.h">
<Filter>Nes\Interfaces</Filter>
</ClInclude>
<ClInclude Include="IInputRecorder.h">
<Filter>Nes\Interfaces</Filter>
</ClInclude>
<ClInclude Include="ControlDeviceState.h">
<Filter>Nes\Input</Filter>
</ClInclude>
<ClInclude Include="BatteryManager.h">
<Filter>Nes</Filter>
</ClInclude>
<ClInclude Include="RotateFilter.h">
<Filter>VideoDecoder</Filter>
</ClInclude>
<ClInclude Include="SystemActionManager.h">
<Filter>Nes\Input\System</Filter>
</ClInclude>
<ClInclude Include="DatachBarcodeReader.h">
<Filter>Nes\Input\BarcodeReaders</Filter>
</ClInclude>
<ClInclude Include="BarcodeBattlerReader.h">
<Filter>Nes\Input\BarcodeReaders</Filter>
</ClInclude>
<ClInclude Include="AsciiTurboFile.h">
<Filter>Nes\Input\Storage</Filter>
</ClInclude>
<ClInclude Include="BattleBox.h">
<Filter>Nes\Input\Storage</Filter>
</ClInclude>
<ClInclude Include="FdsSystemActionManager.h">
<Filter>Nes\Input\System</Filter>
</ClInclude>
<ClInclude Include="VsSystemActionManager.h">
<Filter>Nes\Input\System</Filter>
</ClInclude>
<ClInclude Include="ArkanoidController.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="BandaiMicrophone.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="BandaiHyperShot.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="ExcitingBoxingController.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="FamilyBasicDataRecorder.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="FamilyBasicKeyboard.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="FamilyMatTrainer.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="FourScore.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="HoriTrack.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="JissenMahjongController.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="KonamiHyperShot.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="OekaKidsTablet.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="PachinkoController.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="PartyTap.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="PowerPad.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="SnesController.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="SnesMouse.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="StandardController.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="SuborKeyboard.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="SuborMouse.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="VsZapper.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
<ClInclude Include="Zapper.h">
<Filter>Nes\Input\Controllers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -1350,17 +1458,8 @@
<ClCompile Include="VideoRenderer.cpp">
<Filter>VideoDecoder</Filter>
</ClCompile>
<ClCompile Include="StandardController.cpp">
<Filter>Nes\Controllers</Filter>
</ClCompile>
<ClCompile Include="Zapper.cpp">
<Filter>Nes\Controllers</Filter>
</ClCompile>
<ClCompile Include="BaseControlDevice.cpp">
<Filter>Nes\Controllers</Filter>
</ClCompile>
<ClCompile Include="ArkanoidController.cpp">
<Filter>Nes\Controllers</Filter>
<Filter>Nes\Input</Filter>
</ClCompile>
<ClCompile Include="StereoPanningFilter.cpp">
<Filter>Nes\APU\Filters</Filter>
@ -1395,18 +1494,12 @@
<ClCompile Include="GameDatabase.cpp">
<Filter>Nes\RomLoader</Filter>
</ClCompile>
<ClCompile Include="VsZapper.cpp">
<Filter>Nes\Controllers</Filter>
</ClCompile>
<ClCompile Include="NsfMapper.cpp">
<Filter>Nes\Mappers</Filter>
</ClCompile>
<ClCompile Include="VideoHud.cpp">
<Filter>VideoDecoder</Filter>
</ClCompile>
<ClCompile Include="OekaKidsTablet.cpp">
<Filter>Nes\Controllers</Filter>
</ClCompile>
<ClCompile Include="AutoSaveManager.cpp">
<Filter>Misc</Filter>
</ClCompile>
@ -1482,9 +1575,6 @@
<ClCompile Include="OggMixer.cpp">
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="OggReader.cpp">
<Filter>Misc</Filter>
</ClCompile>
<ClCompile Include="DebugHud.cpp">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClCompile>
@ -1509,9 +1599,21 @@
<ClCompile Include="HdAudioDevice.cpp">
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="OggReader.cpp">
<Filter>HdPacks</Filter>
</ClCompile>
<ClCompile Include="VirtualFile.cpp">
<Filter>Nes\RomLoader</Filter>
</ClCompile>
<ClCompile Include="MovieRecorder.cpp">
<Filter>Movies</Filter>
</ClCompile>
<ClCompile Include="KeyManager.cpp">
<Filter>Misc</Filter>
</ClCompile>
<ClCompile Include="BatteryManager.cpp">
<Filter>Nes</Filter>
</ClCompile>
<ClCompile Include="RotateFilter.cpp">
<Filter>VideoDecoder</Filter>
</ClCompile>

200
Core/DatachBarcodeReader.h Normal file
View File

@ -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<uint8_t> _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<uint8_t> 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<uint8_t> 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)
{
}
};

View File

@ -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<StandardController> controller = std::dynamic_pointer_cast<StandardController>(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);
}
}
}
}
}

View File

@ -87,4 +87,6 @@ enum class EventType
CodeBreak = 5,
StateLoaded = 6,
StateSaved = 7,
InputPolled = 8,
EventTypeSize
};

View File

@ -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);
}

View File

@ -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<uint32_t, KeyCombination> EmulationSettings::_emulatorKeys[2]
std::unordered_map<uint32_t, vector<KeyCombination>> 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<string> NesModelNames = {
"Auto",
"NTSC",
"PAL",
"Dendy"
};
const vector<string> ConsoleTypeNames = {
"Nes",
"Famicom",
};
const vector<string> ControllerTypeNames = {
"None",
"StandardController",
"Zapper",
"ArkanoidController",
"SnesController",
"PowerPad",
"SnesMouse",
"SuborMouse",
"VsZapper"
};
const vector<string> ExpansionPortDeviceNames = {
"None",
"Zapper",
"FourPlayerAdapter",
"ArkanoidController",
"OekaKidsTablet",
"FamilyTrainerMat",
"KonamiHyperShot",
"FamilyBasicKeyboard",
"PartyTap",
"Pachinko",
"ExcitingBoxing",
"JissenMahjong",
"SuborKeyboard",
"BarcodeBattler",
"HoriTrack",
"BandaiHyperShot",
"AsciiTurboFile",
"BattleBox",
};
const vector<string> PpuModelNames = {
"Ppu2C02",
"Ppu2C03",
"Ppu2C04A",
"Ppu2C04B",
"Ppu2C04C",
"Ppu2C04D",
"Ppu2C05A",
"Ppu2C05B",
"Ppu2C05C",
"Ppu2C05D",
"Ppu2C05E"
};

View File

@ -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<string> NesModelNames;
enum class NesModel
{
Auto = 0,
NTSC = 1,
PAL = 2,
Dendy = 3,
};
extern const vector<string> ConsoleTypeNames;
enum class ConsoleType
{
Nes = 0,
Famicom = 1
};
extern const vector<string> 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<string> 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<string> 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<KeyMapping> GetKeyMappingArray()
{
vector<KeyMapping> 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<uint32_t, vector<KeyCombination>> _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;
}
};

View File

@ -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;
}
};

View File

@ -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<uint8_t> ipsData = BatteryManager::LoadBattery(".ips");
LoadDiskData(ipsData);
}
void FDS::LoadDiskData(vector<uint8_t> ipsData)
{
_fdsDiskSides.clear();
_fdsDiskHeaders.clear();
FdsLoader loader;
vector<uint8_t> patchedData;
if(ipsData.size() > 0 && IpsPatcher::PatchBuffer(ipsData, _fdsRawData, patchedData)) {
loader.LoadDiskData(patchedData, _fdsDiskSides, _fdsDiskHeaders);
} else {
loader.LoadDiskData(_fdsRawData, _fdsDiskSides, _fdsDiskHeaders);
}
}
vector<uint8_t> FDS::CreateIpsPatch()
{
FdsLoader loader;
bool needHeader = (memcmp(_fdsRawData.data(), "FDS\x1a", 4) == 0);
vector<uint8_t> newData = loader.RebuildFdsFile(_fdsDiskSides, needHeader);
return IpsPatcher::CreatePatch(_fdsRawData, newData);
}
void FDS::SaveBattery()
{
vector<uint8_t> 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<uint8_t> ipsData = CreateIpsPatch();
uint32_t size = (uint32_t)ipsData.size();
Stream(size);
ArrayInfo<uint8_t> data{ ipsData.data(), (uint32_t)ipsData.size() };
Stream(data);
} else {
uint32_t size = 0;
Stream(size);
if(size > 0) {
vector<uint8_t> ipsData(size, 0);
ArrayInfo<uint8_t> 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);
}

View File

@ -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<FdsAudio> _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<uint8_t> _fdsRawData;
vector<vector<uint8_t>> _fdsDiskSides;
@ -83,6 +77,8 @@ protected:
void InitMapper() override;
void InitMapper(RomData &romData) override;
void LoadDiskData(vector<uint8_t> ipsData = vector<uint8_t>());
vector<uint8_t> 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();
};

View File

@ -0,0 +1,74 @@
#pragma once
#include "stdafx.h"
#include <deque>
#include "BaseControlDevice.h"
#include "CPU.h"
//TODO: Integration with UI
class FamilyBasicDataRecorder : public BaseControlDevice
{
private:
const uint32_t SamplingRate = 88;
vector<uint8_t> _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;
}
}
}
};

117
Core/FamilyBasicKeyboard.h Normal file
View File

@ -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;
}
};

45
Core/FamilyMatTrainer.h Normal file
View File

@ -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;
}
};

View File

@ -1,6 +1,8 @@
#include "stdafx.h"
#include <algorithm>
#include "../Utilities/StringUtilities.h"
#include "../Utilities/HexUtilities.h"
#include "ControlManager.h"
#include "FceuxMovie.h"
#include "Console.h"
@ -26,10 +28,13 @@ vector<uint8_t> 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<string> 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;
}

View File

@ -11,5 +11,5 @@ private:
bool InitializeData(stringstream &filestream);
public:
bool Play(stringstream &filestream, bool autoLoadRom) override;
bool Play(VirtualFile &file) override;
};

View File

@ -2,7 +2,6 @@
#include "stdafx.h"
#include <algorithm>
#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<uint8_t> LoadBios()
{
//For FDS, the PRG ROM is the FDS BIOS (8k)
vector<uint8_t> biosData;
ifstream biosFile("FdsBios.bin", ios::in | ios::binary);
if(biosFile) {
return vector<uint8_t>(std::istreambuf_iterator<char>(biosFile), {});
} else {
MessageManager::SendNotification(ConsoleNotificationType::FdsBiosNotFound);
}
return {};
}
public:
vector<uint8_t> RebuildFdsFile(vector<vector<uint8_t>> diskData, bool needHeader)
{
vector<uint8_t> output;
@ -93,7 +107,7 @@ private:
return output;
}
void LoadDiskData(vector<uint8_t>& romFile, RomData &romData)
void LoadDiskData(vector<uint8_t>& romFile, vector<vector<uint8_t>> &diskData, vector<vector<uint8_t>> &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<uint8_t>());
vector<uint8_t> &fdsDiskImage = romData.FdsDiskData.back();
diskData.push_back(vector<uint8_t>());
vector<uint8_t> &fdsDiskImage = diskData.back();
romData.FdsDiskHeaders.push_back(vector<uint8_t>(romFile.data() + fileOffset + 1, romFile.data() + fileOffset + 57));
diskHeaders.push_back(vector<uint8_t>(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<uint8_t> LoadBios()
RomData LoadRom(vector<uint8_t> &romFile, string filename)
{
//For FDS, the PRG ROM is the FDS BIOS (8k)
vector<uint8_t> biosData;
ifstream biosFile("FdsBios.bin", ios::in | ios::binary);
if(biosFile) {
return vector<uint8_t>(std::istreambuf_iterator<char>(biosFile), {});
} else {
MessageManager::SendNotification(ConsoleNotificationType::FdsBiosNotFound);
}
return {};
}
public:
void SaveIpsFile(string filename, vector<uint8_t> &originalDiskData, vector<vector<uint8_t>> &currentDiskData)
{
bool needHeader = (memcmp(originalDiskData.data(), "FDS\x1a", 4) == 0);
vector<uint8_t> newData = RebuildFdsFile(currentDiskData, needHeader);
vector<uint8_t> 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<uint8_t> 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<uint8_t> 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

View File

@ -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<FDS> _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> console, shared_ptr<BaseMapper> mapper) : SystemActionManager(console)
{
_mapper = std::dynamic_pointer_cast<FDS>(mapper);
_sideCount = std::dynamic_pointer_cast<FDS>(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<FDS> 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<FDS> 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<FDS> mapper = _mapper.lock();
if(mapper) {
InsertDisk(mapper->GetCurrentDisk() ^ 0x01);
}
}
void InsertNextDisk()
{
shared_ptr<FDS> mapper = _mapper.lock();
if(mapper) {
InsertDisk(((mapper->GetCurrentDisk() & 0xFE) + 2) % mapper->GetSideCount());
}
}
uint32_t GetSideCount()
{
shared_ptr<FDS> mapper = _mapper.lock();
if(mapper) {
return mapper->GetSideCount();
} else {
return 0;
}
}
bool IsAutoInsertDiskEnabled()
{
shared_ptr<FDS> mapper = _mapper.lock();
if(mapper) {
return mapper->IsAutoInsertDiskEnabled();
} else {
return false;
}
}
};

48
Core/FourScore.h Normal file
View File

@ -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);
}
};

View File

@ -93,12 +93,6 @@ void GameClient::ProcessNotification(ConsoleNotificationType type, void* paramet
}
}
uint8_t GameClient::GetControllerState(uint8_t port)
{
shared_ptr<GameClientConnection> connection = GetConnection();
return connection ? connection->GetControllerState(port) : 0;
}
void GameClient::SelectController(uint8_t port)
{
shared_ptr<GameClientConnection> connection = GetConnection();

View File

@ -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;
};

View File

@ -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> 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);
}
}

View File

@ -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<uint8_t> _inputData[4];
atomic<uint32_t> _inputSize[4];
AutoResetEvent _waitForInput[4];
std::deque<ControlDeviceState> _inputData[BaseControlDevice::PortCount];
atomic<uint32_t> _inputSize[BaseControlDevice::PortCount];
AutoResetEvent _waitForInput[BaseControlDevice::PortCount];
SimpleLock _writeLock;
atomic<bool> _shutdown;
atomic<bool> _enableControllers;
@ -23,7 +25,7 @@ private:
shared_ptr<BaseControlDevice> _controlDevice;
shared_ptr<BaseControlDevice> _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);

View File

@ -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");
}

View File

@ -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<shared_ptr<GameServerConnection>> 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<GameServerConnection> 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<GameServerConnection> 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);
}
}

View File

@ -3,10 +3,12 @@
#include <thread>
#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<GameServer> Instance;
@ -44,5 +46,6 @@ public:
static list<shared_ptr<GameServerConnection>> GetConnectionList();
virtual void BroadcastInput(uint8_t inputData, uint8_t port);
bool SetInput(BaseControlDevice *device) override;
void RecordInput(BaseControlDevice *device) override;
};

View File

@ -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> 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;
}

View File

@ -3,20 +3,22 @@
#include <deque>
#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<uint32_t> _inputData;
list<ControlDeviceState> _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> 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();

View File

@ -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;

View File

@ -244,7 +244,7 @@ void HdPackLoader::ProcessPatchTag(vector<string> &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 {

View File

@ -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];

51
Core/HoriTrack.h Normal file
View File

@ -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);
}
};

8
Core/IBarcodeReader.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include "stdafx.h"
class IBarcodeReader
{
public:
virtual void InputBarcode(uint64_t barcode, uint32_t digitCount) = 0;
};

7
Core/IBattery.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
class IBattery
{
public:
virtual void SaveBattery() = 0;
};

View File

@ -1,9 +0,0 @@
#pragma once
#include "stdafx.h"
class IGameBroadcaster
{
public:
virtual void BroadcastInput(uint8_t inputData, uint8_t port) = 0;
};

9
Core/IInputProvider.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
class BaseControlDevice;
class IInputProvider
{
public:
virtual bool SetInput(BaseControlDevice* device) = 0;
};

10
Core/IInputRecorder.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
class BaseControlDevice;
class IInputRecorder
{
public:
virtual void RecordInput(BaseControlDevice *device) = 0;
virtual void EndFrame() { }
};

View File

@ -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

View File

@ -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<uint32_t>(_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;
}

View File

@ -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);
}
};

108
Core/KeyManager.cpp Normal file
View File

@ -0,0 +1,108 @@
#include "stdafx.h"
#include "KeyManager.h"
#include "IKeyManager.h"
#include "Types.h"
#include "EmulationSettings.h"
#include "PPU.h"
unique_ptr<IKeyManager> KeyManager::_keyManager;
MousePosition KeyManager::_mousePosition;
atomic<int16_t> KeyManager::_xMouseMovement;
atomic<int16_t> 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<uint32_t> KeyManager::GetPressedKeys()
{
if(_keyManager != nullptr) {
return _keyManager->GetPressedKeys();
}
return vector<uint32_t>();
}
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;
}

32
Core/KeyManager.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include "stdafx.h"
#include "Types.h"
class IKeyManager;
enum class MouseButton;
class KeyManager
{
private:
static unique_ptr<IKeyManager> _keyManager;
static MousePosition _mousePosition;
static atomic<int16_t> _xMouseMovement;
static atomic<int16_t> _yMouseMovement;
public:
static void RegisterKeyManager(IKeyManager* keyManager);
static void RefreshKeyState();
static bool IsKeyPressed(uint32_t keyCode);
static bool IsMouseButtonPressed(MouseButton button);
static vector<uint32_t> 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();
};

84
Core/KonamiHyperShot.h Normal file
View File

@ -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<KeyMapping> _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;
}
};

View File

@ -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<StandardController> controller = std::dynamic_pointer_cast<StandardController>(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<bool> right = l.ReadOptionalBool();
Nullable<bool> left = l.ReadOptionalBool();
Nullable<bool> down = l.ReadOptionalBool();
Nullable<bool> up = l.ReadOptionalBool();
Nullable<bool> select = l.ReadOptionalBool();
Nullable<bool> start = l.ReadOptionalBool();
Nullable<bool> b = l.ReadOptionalBool();
Nullable<bool> 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<StandardController> controller = std::dynamic_pointer_cast<StandardController>(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();
}

View File

@ -50,6 +50,36 @@ bool LuaCallHelper::ReadBool(bool defaultValue)
return value;
}
Nullable<bool> LuaCallHelper::ReadOptionalBool()
{
_paramCount++;
Nullable<bool> 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<uint32_t> LuaCallHelper::ReadOptionalInteger()
{
_paramCount++;
Nullable<uint32_t> 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++;

View File

@ -2,6 +2,13 @@
#include "stdafx.h"
#include "../Lua/lua.hpp"
template<typename T>
struct Nullable
{
bool HasValue = false;
T Value = {};
};
class LuaCallHelper
{
private:
@ -22,6 +29,9 @@ public:
string ReadString();
int GetReference();
Nullable<bool> ReadOptionalBool();
Nullable<uint32_t> ReadOptionalInteger();
void Return(bool value);
void Return(int value);
void Return(uint32_t value);

27
Core/Mapper39.h Normal file
View File

@ -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);
}
};

View File

@ -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);

View File

@ -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();

View File

@ -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<uint8_t> MesenMovie::LoadBattery(string extension)
{
return _recording;
vector<uint8_t> 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<CodeInfo> 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<typename T>
T FromString(string name, const vector<string> &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<ExpansionPortDevice>(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<uint16_t>(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<string, string> &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;
}
bool MesenMovie::LoadBool(std::unordered_map<string, string> &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<string, string> &settings, string name)
{
auto result = settings.find(name);
if(result != settings.end()) {
return result->second;
} else {
return "";
}
}
void MesenMovie::LoadCheats()
{
vector<CodeInfo> 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<string> 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;
}

View File

@ -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<uint16_t> PortData[4];
};
class ZipReader;
class MesenMovie : public IMovie
class MesenMovie : public IMovie, public IBatteryProvider, public std::enable_shared_from_this<MesenMovie>
{
private:
const uint32_t MovieFormatVersion = 5;
bool _recording = false;
VirtualFile _movieFile;
shared_ptr<ZipReader> _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<vector<string>> _inputData;
vector<string> _cheats;
std::unordered_map<string, string> _settings;
string _filename;
stringstream _startState;
MovieData _data;
vector<CodeInfo> _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<string, string> &settings, string name);
bool LoadBool(std::unordered_map<string, string> &settings, string name);
string LoadString(std::unordered_map<string, string> &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<uint8_t> LoadBattery(string extension) override;
};

View File

@ -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<uint8_t>(_portNumber);
Stream<uint8_t>(_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;
}

View File

@ -4,95 +4,68 @@
#include "MesenMovie.h"
#include "BizhawkMovie.h"
#include "FceuxMovie.h"
#include "MovieRecorder.h"
#include "VirtualFile.h"
shared_ptr<IMovie> MovieManager::_instance;
shared_ptr<IMovie> MovieManager::_player;
shared_ptr<MovieRecorder> MovieManager::_recorder;
void MovieManager::Record(string filename, bool reset)
{
shared_ptr<IMovie> 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<MovieRecorder> 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<uint8_t> fileData;
if(file.IsValid() && file.ReadFile(fileData)) {
shared_ptr<IMovie> 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<IMovie> movie(new MesenMovie());
if(movie->Play(filestream, autoLoadRom)) {
_instance = movie;
return true;
vector<string> 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<IMovie> movie(new BizhawkMovie());
if(movie->Play(filestream, autoLoadRom)) {
_instance = movie;
return true;
}
} else if(memcmp(header, "ver", 3) == 0) {
shared_ptr<IMovie> 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<IMovie> 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;
}

View File

@ -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<IMovie> _instance;
static shared_ptr<IMovie> _player;
static shared_ptr<MovieRecorder> _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);
};

200
Core/MovieRecorder.cpp Normal file
View File

@ -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<uint8_t> 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<uint8_t> batteryData)
{
_batteryData[extension] = batteryData;
}
vector<uint8_t> MovieRecorder::LoadBattery(string extension)
{
return vector<uint8_t>();
}

74
Core/MovieRecorder.h Normal file
View File

@ -0,0 +1,74 @@
#pragma once
#include "stdafx.h"
#include <unordered_map>
#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<MovieRecorder>
{
private:
static const uint32_t MovieFormatVersion = 1;
string _filename;
unique_ptr<ZipWriter> _writer;
std::unordered_map<string, vector<uint8_t>> _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<uint8_t> batteryData) override;
// Inherited via IBatteryProvider
virtual vector<uint8_t> 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";
};

View File

@ -58,6 +58,26 @@ protected:
}
}
void StreamArray(vector<uint8_t> &data)
{
uint32_t length = (uint32_t)data.size();
Stream<uint32_t>(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<MessageType>(_type);

View File

@ -448,3 +448,8 @@ NsfHeader NsfMapper::GetNsfHeader()
{
return _nsfHeader;
}
ConsoleFeatures NsfMapper::GetAvailableFeatures()
{
return ConsoleFeatures::Nsf;
}

View File

@ -121,6 +121,7 @@ public:
static NsfMapper* GetInstance();
void SetNesModel(NesModel model) override;
ConsoleFeatures GetAvailableFeatures() override;
void SelectTrack(uint8_t trackNumber);
uint8_t GetCurrentTrack();

View File

@ -15,8 +15,5 @@ protected:
}
public:
NsfPpu(BaseMapper* mapper) : PPU(mapper)
{
}
using PPU::PPU;
};

View File

@ -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);
}
}

View File

@ -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);
}
}
};

View File

@ -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) {

View File

@ -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();

60
Core/PachinkoController.h Normal file
View File

@ -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);
}
};

73
Core/PartyTap.h Normal file
View File

@ -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);
}
};

71
Core/PowerPad.h Normal file
View File

@ -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);
}
};

View File

@ -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<ArchiveReader> 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<string> files = reader->GetFileList({ ".nes" });
vector<uint8_t> 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));
}

View File

@ -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<uint8_t> _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<uint8_t> _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();

View File

@ -1,6 +1,7 @@
#pragma once
#include "stdafx.h"
#include <deque>
#include "BaseControlDevice.h"
class RewindData
{
@ -11,7 +12,7 @@ private:
void CompressState(string stateData, vector<uint8_t> &compressedState);
public:
std::deque<uint8_t> InputLogs[4];
std::deque<ControlDeviceState> InputLogs[BaseControlDevice::PortCount];
int32_t FrameCount = 0;
void LoadState();

View File

@ -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;
}
}

View File

@ -3,6 +3,8 @@
#include <deque>
#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);

View File

@ -26,7 +26,7 @@ bool RomLoader::LoadFile(VirtualFile romFile)
bool RomLoader::LoadFile(string filename, vector<uint8_t> &fileData)
{
if(fileData.size() < 10) {
if(fileData.size() < 15) {
return false;
}
@ -104,7 +104,7 @@ string RomLoader::FindMatchingRomInFile(string filePath, HashInfo hashInfo)
vector<uint8_t> 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<string> 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;
}

View File

@ -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);

View File

@ -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);

View File

@ -15,7 +15,8 @@ int ScriptHost::GetScriptId()
const char* ScriptHost::GetLog()
{
return _context ? _context->GetLog() : "";
shared_ptr<ScriptingContext> context = _context;
return context ? context->GetLog() : "";
}
bool ScriptHost::LoadScript(string scriptName, string scriptContent, Debugger* debugger)

View File

@ -36,7 +36,7 @@ protected:
string _scriptName;
vector<int> _callbacks[5][0x10000];
vector<int> _eventCallbacks[8];
vector<int> _eventCallbacks[(int)EventType::EventTypeSize];
virtual void InternalCallMemoryCallback(uint16_t addr, uint8_t &value, CallbackType type) = 0;
virtual int InternalCallEventCallback(EventType type) = 0;

Some files were not shown because too many files have changed in this diff Show More