Mesen/Core/GameServerConnection.cpp
Sour 5d67994f08 Netplay: Fixed desynchronization issue
When the host loaded a game, the client would end up applying the input with a 1 frame offset, causing some games to eventually desync.
Pausing, loading a state, resetting, etc. would permanently fix the problem (until another game was loading)
2020-04-22 22:30:54 -04:00

248 lines
7 KiB
C++

#include "stdafx.h"
#include <random>
#include "MessageManager.h"
#include "GameServerConnection.h"
#include "HandShakeMessage.h"
#include "InputDataMessage.h"
#include "MovieDataMessage.h"
#include "GameInformationMessage.h"
#include "SaveStateMessage.h"
#include "Console.h"
#include "ControlManager.h"
#include "ClientConnectionData.h"
#include "EmulationSettings.h"
#include "StandardController.h"
#include "SelectControllerMessage.h"
#include "PlayerListMessage.h"
#include "GameServer.h"
#include "ForceDisconnectMessage.h"
#include "BaseControlDevice.h"
#include "ServerInformationMessage.h"
GameServerConnection* GameServerConnection::_netPlayDevices[BaseControlDevice::PortCount] = { };
GameServerConnection::GameServerConnection(shared_ptr<Console> console, shared_ptr<Socket> socket, string serverPassword) : GameConnection(console, socket)
{
//Server-side connection
_serverPassword = serverPassword;
_controllerPort = GameConnection::SpectatorPort;
SendServerInformation();
}
GameServerConnection::~GameServerConnection()
{
if(!_playerName.empty()) {
MessageManager::DisplayMessage("NetPlay", _playerName + " (Player " + std::to_string(_controllerPort + 1) + ") disconnected.");
}
UnregisterNetPlayDevice(this);
}
void GameServerConnection::SendServerInformation()
{
std::random_device rd;
std::mt19937 engine(rd());
std::uniform_int_distribution<> dist((int)' ', (int)'~');
string hash(50, ' ');
for(int i = 0; i < 50; i++) {
int random = dist(engine);
hash[i] = (char)random;
}
_connectionHash = hash;
ServerInformationMessage message(hash);
SendNetMessage(message);
}
void GameServerConnection::SendGameInformation()
{
_console->Pause();
RomInfo romInfo = _console->GetRomInfo();
GameInformationMessage gameInfo(romInfo.RomName, romInfo.Hash.Crc32, _controllerPort, _console->GetSettings()->CheckFlag(EmulationFlags::Paused));
SendNetMessage(gameInfo);
SaveStateMessage saveState(_console);
SendNetMessage(saveState);
_console->Resume();
}
void GameServerConnection::SendMovieData(uint8_t port, ControlDeviceState state)
{
if(_handshakeCompleted) {
MovieDataMessage message(state, port);
SendNetMessage(message);
}
}
void GameServerConnection::SendForceDisconnectMessage(string disconnectMessage)
{
ForceDisconnectMessage message(disconnectMessage);
SendNetMessage(message);
Disconnect();
}
void GameServerConnection::PushState(ControlDeviceState state)
{
if(_inputData.size() == 0 || state != _inputData.back()) {
_inputData.clear();
_inputData.push_back(state);
}
}
ControlDeviceState GameServerConnection::GetState()
{
size_t inputBufferSize = _inputData.size();
ControlDeviceState stateData;
if(inputBufferSize > 0) {
stateData = _inputData.front();
if(inputBufferSize > 1) {
//Always keep the last one the client sent, it will be used until a new one is received
_inputData.pop_front();
}
}
return stateData;
}
void GameServerConnection::ProcessHandshakeResponse(HandShakeMessage* message)
{
//Send the game's current state to the client and register the controller
if(message->IsValid()) {
if(message->CheckPassword(_serverPassword, _connectionHash)) {
_console->Pause();
_controllerPort = message->IsSpectator() ? GameConnection::SpectatorPort : GetFirstFreeControllerPort();
_playerName = message->GetPlayerName();
string playerPortMessage = _controllerPort == GameConnection::SpectatorPort ? "Spectator" : "Player " + std::to_string(_controllerPort + 1);
MessageManager::DisplayMessage("NetPlay", _playerName + " (" + playerPortMessage + ") connected.");
if(_console->GetRomInfo().RomName.size() > 0) {
SendGameInformation();
}
_handshakeCompleted = true;
RegisterNetPlayDevice(this, _controllerPort);
GameServer::SendPlayerList();
_console->Resume();
} else {
SendForceDisconnectMessage("The password you provided did not match - you have been disconnected.");
}
} else {
SendForceDisconnectMessage("Server is using a different version of Mesen (" + EmulationSettings::GetMesenVersionString() + ") - you have been disconnected.");
MessageManager::DisplayMessage("NetPlay", + "NetplayVersionMismatch", message->GetPlayerName());
}
}
void GameServerConnection::ProcessMessage(NetMessage* message)
{
switch(message->GetType()) {
case MessageType::HandShake:
ProcessHandshakeResponse((HandShakeMessage*)message);
break;
case MessageType::InputData:
if(!_handshakeCompleted) {
SendForceDisconnectMessage("Handshake has not been completed - invalid packet");
return;
}
PushState(((InputDataMessage*)message)->GetInputState());
break;
case MessageType::SelectController:
if(!_handshakeCompleted) {
SendForceDisconnectMessage("Handshake has not been completed - invalid packet");
return;
}
SelectControllerPort(((SelectControllerMessage*)message)->GetPortNumber());
break;
default:
break;
}
}
void GameServerConnection::SelectControllerPort(uint8_t port)
{
_console->Pause();
if(port == GameConnection::SpectatorPort) {
//Client wants to be a spectator, make sure we are not using any controller
UnregisterNetPlayDevice(this);
_controllerPort = port;
} else {
GameServerConnection* netPlayDevice = GetNetPlayDevice(port);
if(netPlayDevice == this) {
//Nothing to do, we're already this player
} else if(netPlayDevice == nullptr) {
//This port is free, we can switch
UnregisterNetPlayDevice(this);
RegisterNetPlayDevice(this, port);
_controllerPort = port;
} else {
//Another player is using this port, we can't use it
}
}
SendGameInformation();
GameServer::SendPlayerList();
_console->Resume();
}
void GameServerConnection::ProcessNotification(ConsoleNotificationType type, void* parameter)
{
switch(type) {
case ConsoleNotificationType::GamePaused:
case ConsoleNotificationType::GameResumed:
case ConsoleNotificationType::GameReset:
case ConsoleNotificationType::StateLoaded:
case ConsoleNotificationType::CheatAdded:
case ConsoleNotificationType::ConfigChanged:
case ConsoleNotificationType::GameInitCompleted:
SendGameInformation();
break;
default:
break;
}
}
void GameServerConnection::RegisterNetPlayDevice(GameServerConnection* device, uint8_t port)
{
GameServerConnection::_netPlayDevices[port] = device;
}
void GameServerConnection::UnregisterNetPlayDevice(GameServerConnection* device)
{
if(device != nullptr) {
for(int i = 0; i < BaseControlDevice::PortCount; i++) {
if(GameServerConnection::_netPlayDevices[i] == device) {
GameServerConnection::_netPlayDevices[i] = nullptr;
break;
}
}
}
}
GameServerConnection* GameServerConnection::GetNetPlayDevice(uint8_t port)
{
return GameServerConnection::_netPlayDevices[port];
}
uint8_t GameServerConnection::GetFirstFreeControllerPort()
{
uint8_t hostPost = GameServer::GetHostControllerPort();
for(int i = 0; i < BaseControlDevice::PortCount; i++) {
if(hostPost != i && GameServerConnection::_netPlayDevices[i] == nullptr) {
return i;
}
}
return GameConnection::SpectatorPort;
}
string GameServerConnection::GetPlayerName()
{
return _playerName;
}
uint8_t GameServerConnection::GetControllerPort()
{
return _controllerPort;
}