Files
fceux/src/drivers/Qt/NetPlay.cpp
T

3168 lines
83 KiB
C++

/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file:
* Copyright (C) 2002 Xodnizel
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <QDir>
#include <QFileInfo>
#include <QMessageBox>
#include <QTemporaryFile>
#include "../../fceu.h"
#include "../../cart.h"
#include "../../cheat.h"
#include "../../state.h"
#include "../../movie.h"
#include "../../debug.h"
#include "utils/crc32.h"
#include "utils/timeStamp.h"
#include "utils/StringUtils.h"
#include "Qt/main.h"
#include "Qt/dface.h"
#include "Qt/input.h"
#include "Qt/NetPlay.h"
#include "Qt/fceuWrapper.h"
#include "Qt/ConsoleWindow.h"
#include "Qt/ConsoleUtilities.h"
#include "Qt/NetPlayMsgDef.h"
//-----------------------------------------------------------------------------
//--- NetPlay State Monitoring Metrics
//-----------------------------------------------------------------------------
static uint32_t opsCrc32 = 0;
static void *traceRegistrationHandle = nullptr;
static bool serverRequestedStateLoad = false;
struct NetPlayFrameData
{
uint32_t frameNum = 0;
uint32_t opsCrc32 = 0;
uint32_t ramCrc32 = 0;
void reset()
{
frameNum = 0;
opsCrc32 = 0;
ramCrc32 = 0;
}
};
struct NetPlayFrameDataHist_t
{
static constexpr int numFrames = 10;
void push( NetPlayFrameData& newData )
{
data[bufHead] = newData;
bufHead = (bufHead + 1) % numFrames;
}
int getLast( NetPlayFrameData& out )
{
int i;
const int head = bufHead;
if (head == 0)
{
i = numFrames - 1;
}
else
{
i = head - 1;
}
out = data[i];
return 0;
}
int find( uint32_t frame, NetPlayFrameData& out )
{
int i, retval = -1;
const int head = bufHead;
if (head == 0)
{
i = numFrames - 1;
}
else
{
i = head - 1;
}
while (i != head)
{
if (data[i].frameNum == frame)
{
out = data[i];
retval = 0;
break;
}
if (i == 0)
{
i = numFrames - 1;
}
else
{
i = i - 1;
}
}
return retval;
}
void reset(void)
{
bufHead = 0;
for (int i=0; i<numFrames; i++)
{
data[i].reset();
}
}
NetPlayFrameData data[numFrames];
int bufHead = 0;
};
static NetPlayFrameDataHist_t netPlayFrameData;
//-----------------------------------------------------------------------------
const char* NetPlayPlayerRoleToString(int role)
{
const char* roleString = nullptr;
switch (role)
{
default:
case netPlayerId::NETPLAY_SPECTATOR:
roleString = "Spectator";
break;
case netPlayerId::NETPLAY_PLAYER1:
roleString = "Player 1";
break;
case netPlayerId::NETPLAY_PLAYER2:
roleString = "Player 2";
break;
case netPlayerId::NETPLAY_PLAYER3:
roleString = "Player 3";
break;
case netPlayerId::NETPLAY_PLAYER4:
roleString = "Player 4";
break;
}
return roleString;
}
//-----------------------------------------------------------------------------
//--- NetPlayServer
//-----------------------------------------------------------------------------
NetPlayServer *NetPlayServer::instance = nullptr;
NetPlayServer::NetPlayServer(QObject *parent)
: QTcpServer(parent)
{
instance = this;
connect(this, SIGNAL(newConnection(void)), this, SLOT(newConnectionRdy(void)));
connect(consoleWindow, SIGNAL(romLoaded(void)), this, SLOT(onRomLoad(void)));
connect(consoleWindow, SIGNAL(romUnload(void)), this, SLOT(onRomUnload(void)));
connect(consoleWindow, SIGNAL(stateLoaded(void)), this, SLOT(onStateLoad(void)));
connect(consoleWindow, SIGNAL(nesResetOccurred(void)), this, SLOT(onNesReset(void)));
connect(consoleWindow, SIGNAL(cheatsChanged(void)), this, SLOT(onCheatsChanged(void)));
connect(consoleWindow, SIGNAL(pauseToggled(bool)), this, SLOT(onPauseToggled(bool)));
FCEU_WRAPPER_LOCK();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
if (currCartInfo != nullptr)
{
romCrc32 = currCartInfo->CRC32;
}
FCEU_WRAPPER_UNLOCK();
}
NetPlayServer::~NetPlayServer(void)
{
instance = nullptr;
closeAllConnections();
printf("NetPlayServer Destructor\n");
}
int NetPlayServer::Create(QObject *parent)
{
if (NetPlayServer::GetInstance() == nullptr)
{
NetPlayServer *server = new NetPlayServer(parent);
if (server == nullptr)
{
printf("Error Creating Netplay Server!!!\n");
}
}
FCEU_WRAPPER_LOCK();
if (traceRegistrationHandle == nullptr)
{
traceRegistrationHandle = FCEUI_TraceInstructionRegister( NetPlayTraceInstruction );
}
FCEU_WRAPPER_UNLOCK();
if (consoleWindow != nullptr)
{
consoleWindow->onNetPlayChange();
}
return 0;
}
int NetPlayServer::Destroy()
{
NetPlayServer* server = NetPlayServer::GetInstance();
if (server != nullptr)
{
delete server;
server = nullptr;
}
FCEU_WRAPPER_LOCK();
if (traceRegistrationHandle != nullptr)
{
if ( !FCEUI_TraceInstructionUnregisterHandle( traceRegistrationHandle ) )
{
printf("Unregister Trace Callback Error\n");
}
traceRegistrationHandle = nullptr;
}
FCEU_WRAPPER_UNLOCK();
if (consoleWindow != nullptr)
{
consoleWindow->onNetPlayChange();
}
return 0;
}
void NetPlayServer::newConnectionRdy(void)
{
printf("New Connection Ready!\n");
processPendingConnections();
}
void NetPlayServer::processPendingConnections(void)
{
QTcpSocket *newSock;
newSock = nextPendingConnection();
while (newSock != nullptr)
{
NetPlayClient *client = new NetPlayClient(this);
if (debugMode)
{
// File is owned by the server
QTemporaryFile* debugFile = new QTemporaryFile( QDir::tempPath() + QString("/NetPlayServerConnection_XXXXXX.log"), this);
debugFile->open();
client->setDebugLog( debugFile );
printf("Client Debug File: %s\n", debugFile->fileName().toLocal8Bit().constData());
}
client->setSocket(newSock);
clientList.push_back(client);
connect( newSock, SIGNAL(readyRead(void)), client, SLOT(serverReadyRead(void)) );
newSock = nextPendingConnection();
printf("Added Client: %p %zu\n", client, clientList.size() );
netPlayAuthReq msg;
sendMsg( client, &msg, sizeof(msg), [&msg]{ msg.toNetworkByteOrder(); } );
emit clientConnected();
}
}
int NetPlayServer::closeAllConnections(void)
{
std::list <NetPlayClient*>::iterator it;
for (int i=0; i<4; i++)
{
clientPlayer[i] = nullptr;
}
for (it = clientList.begin(); it != clientList.end(); it++)
{
auto* client = *it;
delete client;
}
clientList.clear();
emit clientDisconnected();
roleMask = 0;
return 0;
}
//-----------------------------------------------------------------------------
int NetPlayServer::sendMsg( NetPlayClient *client, const void *msg, size_t msgSize, std::function<void(void)> netByteOrderConvertFunc)
{
QTcpSocket* sock;
sock = client->getSocket();
netByteOrderConvertFunc();
sock->write( static_cast<const char *>(msg), msgSize );
return 0;
}
//-----------------------------------------------------------------------------
int NetPlayServer::sendRomLoadReq( NetPlayClient *client )
{
constexpr size_t BufferSize = 32 * 1024;
char buf[BufferSize];
size_t bytesRead;
long fileSize = 0;
netPlayLoadRomReq msg;
const char *filepath = nullptr;
if ( GameInfo )
{
printf("filename: '%s' \n", GameInfo->filename );
printf("archiveFilename: '%s' \n", GameInfo->archiveFilename );
if (GameInfo->archiveFilename)
{
filepath = GameInfo->archiveFilename;
}
else
{
filepath = GameInfo->filename;
}
}
if (filepath == nullptr)
{
return -1;
}
printf("Prep ROM Load Request: %s \n", filepath );
FILE *fp = ::fopen( filepath, "rb");
if (fp == nullptr)
{
return -1;
}
fseek( fp, 0, SEEK_END);
fileSize = ftell(fp);
rewind(fp);
msg.hdr.msgSize += fileSize;
msg.fileSize = fileSize;
Strlcpy( msg.fileName, GameInfo->filename, sizeof(msg.fileName) );
printf("Sending ROM Load Request: %s %lu\n", filepath, fileSize );
sendMsg( client, &msg, sizeof(netPlayLoadRomReq), [&msg]{ msg.toNetworkByteOrder(); } );
while ( (bytesRead = fread( buf, 1, sizeof(buf), fp )) > 0 )
{
sendMsg( client, buf, bytesRead );
}
client->flushData();
::fclose(fp);
return 0;
}
//-----------------------------------------------------------------------------
struct NetPlayServerCheatQuery
{
int numLoaded = 0;
netPlayLoadStateResp::CheatData data[netPlayLoadStateResp::MaxCheats];
};
static int serverActiveCheatListCB(const char *name, uint32 a, uint8 v, int c, int s, int type, void *data)
{
NetPlayServerCheatQuery* query = static_cast<NetPlayServerCheatQuery*>(data);
const int i = query->numLoaded;
if (i < netPlayLoadStateResp::MaxCheats)
{
auto& cheat = query->data[i];
cheat.addr = a;
cheat.val = v;
cheat.cmp = c;
cheat.type = type;
cheat.stat = s;
Strlcpy( cheat.name, name, sizeof(cheat.name));
query->numLoaded++;
}
return 1;
}
//-----------------------------------------------------------------------------
int NetPlayServer::sendStateSyncReq( NetPlayClient *client )
{
EMUFILE_MEMORY em;
int numCtrlFrames = 0, numCheats = 0, compressionLevel = 1;
static constexpr size_t maxBytesPerWrite = 32 * 1024;
netPlayLoadStateResp resp;
netPlayLoadStateResp::CtrlData ctrlData[netPlayLoadStateResp::MaxCtrlFrames];
NetPlayServerCheatQuery cheatQuery;
if ( GameInfo == nullptr )
{
return -1;
}
FCEUSS_SaveMS( &em, compressionLevel );
resp.stateSize = em.size();
resp.opsCrc32 = opsCrc32;
resp.romCrc32 = romCrc32;
NetPlayFrameData lastFrameData;
netPlayFrameData.getLast( lastFrameData );
resp.lastFrame.num = lastFrameData.frameNum;
resp.lastFrame.opsCrc32 = lastFrameData.opsCrc32;
resp.lastFrame.ramCrc32 = lastFrameData.ramCrc32;
resp.numCtrlFrames = 0;
{
int i = 0;
FCEU::autoScopedLock alock(inputMtx);
for (auto& inputFrame : input)
{
if (i < netPlayLoadStateResp::MaxCtrlFrames)
{
ctrlData[i].frameNum = netPlayByteSwap(inputFrame.frameCounter);
ctrlData[i].ctrlState[0] = inputFrame.ctrl[0];
ctrlData[i].ctrlState[1] = inputFrame.ctrl[1];
ctrlData[i].ctrlState[2] = inputFrame.ctrl[2];
ctrlData[i].ctrlState[3] = inputFrame.ctrl[3];
i++;
}
}
resp.numCtrlFrames = numCtrlFrames = i;
}
FCEUI_ListCheats(::serverActiveCheatListCB, (void *)&cheatQuery);
resp.numCheats = numCheats = cheatQuery.numLoaded;
resp.calcTotalSize();
printf("Sending ROM Sync Request: %zu\n", em.size());
sendMsg( client, &resp, sizeof(netPlayLoadStateResp), [&resp]{ resp.toNetworkByteOrder(); } );
if (numCtrlFrames > 0)
{
sendMsg( client, ctrlData, numCtrlFrames * sizeof(netPlayLoadStateResp::CtrlData) );
}
if (numCheats > 0)
{
sendMsg( client, &cheatQuery.data, numCheats * sizeof(netPlayLoadStateResp::CheatData) );
}
//sendMsg( client, em.buf(), em.size() );
const unsigned char* bufPtr = em.buf();
size_t dataSize = em.size();
while (dataSize > 0)
{
size_t bytesToWrite = dataSize;
if (bytesToWrite > maxBytesPerWrite)
{
bytesToWrite = maxBytesPerWrite;
}
sendMsg( client, bufPtr, bytesToWrite );
bufPtr += bytesToWrite;
dataSize -= bytesToWrite;
}
client->flushData();
return 0;
}
//-----------------------------------------------------------------------------
int NetPlayServer::sendPause( NetPlayClient *client )
{
netPlayMsgHdr msg(NETPLAY_CLIENT_PAUSE_REQ);
sendMsg( client, &msg, sizeof(netPlayMsgHdr), [&msg]{ msg.toNetworkByteOrder(); } );
return 0;
}
//-----------------------------------------------------------------------------
int NetPlayServer::sendUnpause( NetPlayClient *client )
{
netPlayMsgHdr msg(NETPLAY_CLIENT_UNPAUSE_REQ);
sendMsg( client, &msg, sizeof(netPlayMsgHdr), [&msg]{ msg.toNetworkByteOrder(); } );
return 0;
}
//-----------------------------------------------------------------------------
int NetPlayServer::sendPauseAll()
{
int ret = 0;
for (auto& client : clientList )
{
ret |= sendPause( client );
}
return ret;
}
//-----------------------------------------------------------------------------
int NetPlayServer::sendUnpauseAll()
{
int ret = 0;
for (auto& client : clientList )
{
ret |= sendUnpause( client );
}
return ret;
}
//-----------------------------------------------------------------------------
void NetPlayServer::setRole(int _role)
{
role = _role;
if ( (role >= NETPLAY_PLAYER1) && (role <= NETPLAY_PLAYER4) )
{
roleMask |= (0x01 << role);
}
}
//-----------------------------------------------------------------------------
bool NetPlayServer::claimRole(NetPlayClient* client, int _role)
{
bool success = true; // Default to spectator
if ( (_role >= NETPLAY_PLAYER1) && (_role <= NETPLAY_PLAYER4) )
{
int mask = (0x01 << _role);
if (roleMask & mask)
{
// Already Taken
success = false;
}
else
{
success = true;
roleMask |= mask;
clientPlayer[_role] = client;
client->role = _role;
}
}
else
{
client->role = NETPLAY_SPECTATOR;
}
return success;
}
//-----------------------------------------------------------------------------
void NetPlayServer::releaseRole(NetPlayClient* client)
{
int role = client->role;
if ( (role >= NETPLAY_PLAYER1) && (role <= NETPLAY_PLAYER4) )
{
int mask = (0x01 << role);
roleMask = roleMask & ~mask;
}
}
//-----------------------------------------------------------------------------
void NetPlayServer::onRomLoad()
{
//printf("New ROM Loaded!\n");
FCEU_WRAPPER_LOCK();
if (currCartInfo != nullptr)
{
romCrc32 = currCartInfo->CRC32;
}
opsCrc32 = 0;
netPlayFrameData.reset();
inputClear();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
sendPauseAll();
// New ROM has been loaded by server, signal clients to load and sync
for (auto& client : clientList )
{
sendRomLoadReq( client );
sendStateSyncReq( client );
}
FCEU_WRAPPER_UNLOCK();
}
//-----------------------------------------------------------------------------
void NetPlayServer::onRomUnload()
{
//printf("ROM UnLoaded!\n");
netPlayMsgHdr unloadMsg(NETPLAY_UNLOAD_ROM_REQ);
romCrc32 = 0;
unloadMsg.toNetworkByteOrder();
sendPauseAll();
// New ROM has been loaded by server, signal clients to load and sync
for (auto& client : clientList )
{
sendMsg( client, &unloadMsg, sizeof(unloadMsg) );
}
}
//-----------------------------------------------------------------------------
void NetPlayServer::onStateLoad()
{
//printf("New State Loaded!\n");
FCEU_WRAPPER_LOCK();
opsCrc32 = 0;
netPlayFrameData.reset();
inputClear();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
sendPauseAll();
// New State has been loaded by server, signal clients to load and sync
for (auto& client : clientList )
{
sendRomLoadReq( client );
sendStateSyncReq( client );
}
FCEU_WRAPPER_UNLOCK();
}
//-----------------------------------------------------------------------------
void NetPlayServer::onNesReset()
{
//printf("NES Reset Event!\n");
FCEU_WRAPPER_LOCK();
opsCrc32 = 0;
netPlayFrameData.reset();
inputClear();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
sendPauseAll();
// NES Reset has occurred on server, signal clients sync
for (auto& client : clientList )
{
sendRomLoadReq( client );
sendStateSyncReq( client );
}
FCEU_WRAPPER_UNLOCK();
}
//-----------------------------------------------------------------------------
void NetPlayServer::onCheatsChanged()
{
//printf("NES Cheats Event!\n");
if (romCrc32 == 0)
{
return;
}
FCEU_WRAPPER_LOCK();
opsCrc32 = 0;
netPlayFrameData.reset();
inputClear();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
sendPauseAll();
// NES Reset has occurred on server, signal clients sync
for (auto& client : clientList )
{
//sendRomLoadReq( client );
sendStateSyncReq( client );
}
FCEU_WRAPPER_UNLOCK();
}
//-----------------------------------------------------------------------------
void NetPlayServer::onPauseToggled( bool isPaused )
{
if (isPaused)
{
sendPauseAll();
}
else
{
sendUnpauseAll();
}
}
//-----------------------------------------------------------------------------
void NetPlayServer::resyncClient( NetPlayClient *client )
{
FCEU_WRAPPER_LOCK();
sendRomLoadReq( client );
sendStateSyncReq( client );
FCEU_WRAPPER_UNLOCK();
}
//-----------------------------------------------------------------------------
void NetPlayServer::resyncAllClients()
{
FCEU_WRAPPER_LOCK();
opsCrc32 = 0;
netPlayFrameData.reset();
inputClear();
inputFrameCount = static_cast<uint32_t>(currFrameCounter);
for (auto& client : clientList )
{
resyncClient( client );
}
FCEU_WRAPPER_UNLOCK();
}
//-----------------------------------------------------------------------------
static void serverMessageCallback( void *userData, void *msgBuf, size_t msgSize )
{
NetPlayServer *server = NetPlayServer::GetInstance();
server->serverProcessMessage( static_cast<NetPlayClient*>(userData), msgBuf, msgSize );
}
//-----------------------------------------------------------------------------
void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, size_t msgSize )
{
netPlayMsgHdr *hdr = (netPlayMsgHdr*)msgBuf;
const uint32_t msgId = netPlayByteSwap(hdr->msgId);
//printf("Server Received Message: ID: %i Bytes:%zu\n", msgId, msgSize );
switch (msgId)
{
case NETPLAY_AUTH_RESP:
{
bool authentication_passed = false;
netPlayAuthResp *msg = static_cast<netPlayAuthResp*>(msgBuf);
msg->toHostByteOrder();
printf("Authorize: Player: %i Passwd: %s\n", msg->playerId, msg->pswd);
bool version_chk_ok = true;
if (enforceAppVersionCheck)
{
version_chk_ok = (msg->appVersionMajor == FCEU_VERSION_MAJOR) &&
(msg->appVersionMinor == FCEU_VERSION_MINOR) &&
(msg->appVersionPatch == FCEU_VERSION_PATCH);
}
if (!version_chk_ok)
{
netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG);
errorMsg.setFlag(netPlayTextMsgFlags::Disconnect);
errorMsg.setFlag(netPlayTextMsgFlags::Error);
errorMsg.printf("Client/Host Version Mismatch:\nHost version is %i.%i.%i\nClient version is %i.%i.%i",
FCEU_VERSION_MAJOR, FCEU_VERSION_MINOR, FCEU_VERSION_PATCH,
msg->appVersionMajor, msg->appVersionMinor, msg->appVersionPatch);
sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } );
client->flushData();
}
else if (sessionPasswd.isEmpty())
{
authentication_passed = true;
}
else
{
authentication_passed = sessionPasswd.compare(msg->pswd, Qt::CaseSensitive) == 0;
if (!authentication_passed)
{
netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG);
errorMsg.setFlag(netPlayTextMsgFlags::Disconnect);
errorMsg.setFlag(netPlayTextMsgFlags::Error);
errorMsg.printf("Invalid Password");
sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } );
client->flushData();
}
}
if (authentication_passed)
{
if ( claimRole(client, msg->playerId) )
{
client->userName = msg->userName;
FCEU_WRAPPER_LOCK();
resyncClient(client);
client->state = 1;
if (FCEUI_EmulationPaused())
{
sendPauseAll();
}
else
{
sendUnpauseAll();
}
FCEU_WRAPPER_UNLOCK();
FCEU_DispMessage("%s Joined",0, client->userName.toLocal8Bit().constData());
}
else
{
netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG);
errorMsg.setFlag(netPlayTextMsgFlags::Disconnect);
errorMsg.setFlag(netPlayTextMsgFlags::Error);
errorMsg.printf("Player %i role is not available", msg->playerId+1);
sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } );
client->flushData();
}
}
else
{
// If authentication failed, force disconnect client.
client->forceDisconnect();
}
}
break;
case NETPLAY_CLIENT_STATE:
{
netPlayClientState *msg = static_cast<netPlayClientState*>(msgBuf);
msg->toHostByteOrder();
client->currentFrame = msg->frameRun;
client->readyFrame = msg->frameRdy;
client->gpData[0] = msg->ctrlState[0];
client->gpData[1] = msg->ctrlState[1];
client->gpData[2] = msg->ctrlState[2];
client->gpData[3] = msg->ctrlState[3];
client->setPaused( (msg->flags & netPlayClientState::PauseFlag ) ? true : false );
client->setDesync( (msg->flags & netPlayClientState::DesyncFlag) ? true : false );
client->romMatch = (romCrc32 == msg->romCrc32);
NetPlayFrameData data;
if ( (msg->opsFrame == 0) || netPlayFrameData.find( msg->opsFrame, data ) )
{
//printf("Error: Server Could not find data for frame: %u\n", msg->opsFrame );
}
else
{
bool opsSync = (data.opsCrc32 == msg->opsChkSum);
bool ramSync = (data.ramCrc32 == msg->ramChkSum);
client->syncOk = client->romMatch && opsSync && ramSync;
if (!client->syncOk)
{
printf("Client %s Frame:%u is NOT in Sync: OPS:%i RAM:%i\n",
client->userName.toLocal8Bit().constData(), msg->frameRun, opsSync, ramSync);
client->desyncCount++;
if (client->desyncCount > forceResyncCount)
{
FCEU_WRAPPER_LOCK();
resyncClient( client );
FCEU_WRAPPER_UNLOCK();
client->desyncCount = 0;
}
}
else
{
if (client->desyncCount > 0)
{
client->desyncCount--;
}
else
{
client->desyncCount = 0;
}
}
}
}
break;
case NETPLAY_PING_RESP:
{
netPlayPingResp *ping = static_cast<netPlayPingResp*>(msgBuf);
ping->toHostByteOrder();
FCEU::timeStampRecord ts;
ts.readNew();
uint64_t diff = ts.toMilliSeconds() - ping->hostTimeStamp;
client->recordPingResult( diff );
//printf("Ping Latency ms: %llu Avg:%f\n", static_cast<unsigned long long>(diff), client->getAvgPingDelay());
}
break;
case NETPLAY_LOAD_ROM_REQ:
{
netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(msgBuf);
msg->toHostByteOrder();
if (allowClientRomLoadReq)
{
if (client->romLoadData.buf != nullptr)
{
::free(client->romLoadData.buf);
client->romLoadData.buf = nullptr;
}
client->romLoadData.size = 0;
client->romLoadData.fileName.clear();
const uint32_t fileSize = msg->fileSize;
constexpr uint32_t maxRomDataSize = 1024 * 1024;
const char *romData = &static_cast<const char*>(msgBuf)[ sizeof(netPlayLoadRomReq) ];
if ( (fileSize >= 16) && (fileSize < maxRomDataSize) )
{
client->romLoadData.fileName = msg->fileName;
client->romLoadData.size = fileSize;
client->romLoadData.buf = static_cast<char*>(::malloc(fileSize));
::memcpy( client->romLoadData.buf, romData, fileSize );
QTimer::singleShot( 100, this, SLOT(processClientRomLoadRequests(void)) );
}
}
}
break;
case NETPLAY_SYNC_STATE_REQ:
{
FCEU_WRAPPER_LOCK();
resyncClient( client );
FCEU_WRAPPER_UNLOCK();
}
break;
case NETPLAY_SYNC_STATE_RESP:
{
netPlayLoadStateResp* msg = static_cast<netPlayLoadStateResp*>(msgBuf);
msg->toHostByteOrder();
FCEU_printf("Sync state request received from client '%s'\n", client->userName.toLocal8Bit().constData());
if (allowClientStateLoadReq)
{
const char *stateData = msg->stateDataBuf();
const uint32_t stateDataSize = msg->stateDataSize();
constexpr uint32_t maxStateDataSize = 1024*1024;
if (client->stateLoadData.buf != nullptr)
{
::free(client->stateLoadData.buf);
client->stateLoadData.buf = nullptr;
}
client->stateLoadData.size = 0;
if ( (stateDataSize >= 16) && (stateDataSize < maxStateDataSize) )
{
client->stateLoadData.size = stateDataSize;
client->stateLoadData.buf = static_cast<char*>(::malloc(stateDataSize));
::memcpy( client->stateLoadData.buf, stateData, stateDataSize );
QTimer::singleShot( 100, this, SLOT(processClientStateLoadRequests(void)) );
}
}
}
break;
default:
printf("Unknown Msg: %08X\n", msgId);
break;
}
}
//-----------------------------------------------------------------------------
void NetPlayServer::processClientRomLoadRequests(void)
{
for (auto& client : clientList )
{
if (client->romLoadData.pending())
{
bool acceptRomLoadReq = false;
if (allowClientRomLoadReq)
{
QString msgBoxTxt = tr("Client '") + client->userName + tr("' has requested to load this ROM:\n\n");
msgBoxTxt += client->romLoadData.fileName + tr("\n\nDo you want to load it?");
int ans = QMessageBox::question( consoleWindow, tr("Client ROM Load Request"), msgBoxTxt, QMessageBox::Yes | QMessageBox::No );
if (ans == QMessageBox::Yes)
{
acceptRomLoadReq = true;
}
}
if (acceptRomLoadReq)
{
FILE *fp;
QString filepath = QDir::tempPath();
QFileInfo rompath = QFileInfo(client->romLoadData.fileName);
const char *romData = client->romLoadData.buf;
const size_t romSize = client->romLoadData.size;
filepath.append( "/" );
filepath.append( rompath.fileName() );
//printf("Load ROM Request Received: %s\n", filepath.c_str());
//printf("Dumping Temp Rom to: %s\n", filepath.c_str());
fp = ::fopen( filepath.toLocal8Bit().constData(), "wb");
if (fp == nullptr)
{
return;
}
::fwrite( romData, 1, romSize, fp );
::fclose(fp);
FCEU_WRAPPER_LOCK();
LoadGame( filepath.toLocal8Bit().constData(), true, true );
FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED);
FCEU_WRAPPER_UNLOCK();
sendPauseAll();
resyncAllClients();
}
else
{
netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG);
errorMsg.setFlag(netPlayTextMsgFlags::Warning);
errorMsg.printf("Host has rejected ROM load request");
sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } );
}
::free(client->romLoadData.buf);
client->romLoadData.buf = nullptr;
client->romLoadData.size = 0;
client->romLoadData.fileName.clear();
}
}
}
//-----------------------------------------------------------------------------
void NetPlayServer::processClientStateLoadRequests(void)
{
for (auto& client : clientList )
{
if (client->stateLoadData.pending())
{
EMUFILE_MEMORY em( client->stateLoadData.buf, client->stateLoadData.size );
bool acceptStateLoadReq = false;
if (allowClientStateLoadReq)
{
QString msgBoxTxt = tr("Client '") + client->userName + tr("' has requested to load a new state:\n");
msgBoxTxt += tr("\nDo you want to load it?");
int ans = QMessageBox::question( consoleWindow, tr("Client State Load Request"), msgBoxTxt, QMessageBox::Yes | QMessageBox::No );
if (ans == QMessageBox::Yes)
{
acceptStateLoadReq = true;
}
}
if (acceptStateLoadReq)
{
FCEU_WRAPPER_LOCK();
serverRequestedStateLoad = true;
// Clients will be resync'd during this load call.
FCEUSS_LoadFP( &em, SSLOADPARAM_NOBACKUP );
serverRequestedStateLoad = false;
FCEU_WRAPPER_UNLOCK();
}
::free(client->stateLoadData.buf);
client->stateLoadData.buf = nullptr;
client->stateLoadData.size = 0;
}
}
}
//-----------------------------------------------------------------------------
void NetPlayServer::update(void)
{
bool hostRdyFrame = false;
bool shouldRunFrame = false;
unsigned int clientMinFrame = 0xFFFFFFFF;
unsigned int clientMaxFrame = 0;
const uint32_t maxLead = maxLeadFrames;
const uint32_t currFrame = static_cast<uint32_t>(currFrameCounter);
const uint32_t leadFrame = currFrame + maxLead;
//const uint32_t lastFrame = inputFrameBack();
uint32_t lagFrame = 0;
uint8_t localGP[4] = { 0 };
uint8_t gpData[4] = { 0 };
int numClientsPaused = 0;
if (currFrame > maxLead)
{
lagFrame = currFrame - maxLead;
}
uint32_t ctlrData = GetGamepadPressedImmediate();
localGP[0] = (ctlrData ) & 0x000000ff;
localGP[1] = (ctlrData >> 8) & 0x000000ff;
localGP[2] = (ctlrData >> 16) & 0x000000ff;
localGP[3] = (ctlrData >> 24) & 0x000000ff;
if ( (role >= NETPLAY_PLAYER1) && (role <= NETPLAY_PLAYER4) )
{
gpData[role] = localGP[role];
}
// Input Processing
for (auto it = clientList.begin(); it != clientList.end(); )
{
NetPlayClient *client = *it;
client->readMessages( serverMessageCallback, client );
if (client->isAuthenticated())
{
if (client->currentFrame < clientMinFrame)
{
clientMinFrame = client->currentFrame;
}
if (client->currentFrame > clientMaxFrame)
{
clientMaxFrame = client->currentFrame;
}
if (client->isPlayerRole())
{
gpData[client->role] = client->gpData[client->role];
}
if (client->isPaused())
{
numClientsPaused++;
}
}
if (client->shouldDestroy())
{
it = clientList.erase(it);
for (int i=0; i<4; i++)
{
if (client == clientPlayer[i])
{
clientPlayer[i] = nullptr;
}
}
releaseRole(client);
delete client;
//printf("Emit clientDisconnected!\n");
emit clientDisconnected();
}
else
{
it++;
}
}
hostRdyFrame = (currFrame >= inputFrameCount);
shouldRunFrame = (clientMinFrame != 0xFFFFFFFF) &&
(clientMinFrame >= lagFrame ) &&
(clientMaxFrame < leadFrame) &&
(numClientsPaused == 0) &&
hostRdyFrame;
if (hostRdyFrame && !shouldRunFrame)
{
clientWaitCounter++;
}
else
{
clientWaitCounter = 0;
}
//printf("Host Frame: Run:%u Input:%u Last:%u\n", currFrame, inputFrameCount, lastFrame);
//printf("Client Frame: Min:%u Max:%u\n", clientMinFrame, clientMaxFrame);
if (shouldRunFrame)
{
// Output Processing
NetPlayFrameInput inputFrame;
netPlayRunFrameReq runFrameReq;
inputFrame.frameCounter = ++inputFrameCount;
inputFrame.ctrl[0] = gpData[0];
inputFrame.ctrl[1] = gpData[1];
inputFrame.ctrl[2] = gpData[2];
inputFrame.ctrl[3] = gpData[3];
runFrameReq.frameNum = inputFrame.frameCounter;
runFrameReq.ctrlState[0] = inputFrame.ctrl[0];
runFrameReq.ctrlState[1] = inputFrame.ctrl[1];
runFrameReq.ctrlState[2] = inputFrame.ctrl[2];
runFrameReq.ctrlState[3] = inputFrame.ctrl[3];
uint32_t catchUpThreshold = maxLead;
if (catchUpThreshold < 3)
{
catchUpThreshold = 3;
}
runFrameReq.catchUpThreshold = catchUpThreshold;
pushBackInput( inputFrame );
runFrameReq.toNetworkByteOrder();
for (auto& client : clientList )
{
if (client->state > 0)
{
sendMsg( client, &runFrameReq, sizeof(runFrameReq) );
}
}
}
bool shouldRunPing = (cycleCounter % 120) == 0;
if (shouldRunPing)
{
FCEU::timeStampRecord ts;
ts.readNew();
netPlayPingReq ping;
ping.hostTimeStamp = ts.toMilliSeconds();
ping.toNetworkByteOrder();
for (auto& client : clientList )
{
if (client->state > 0)
{
sendMsg( client, &ping, sizeof(ping) );
}
}
}
bool shouldFlushOutput = true;
if (shouldFlushOutput)
{
for (auto& client : clientList )
{
client->flushData();
}
}
cycleCounter++;
}
//-----------------------------------------------------------------------------
//--- NetPlayClient
//-----------------------------------------------------------------------------
NetPlayClient *NetPlayClient::instance = nullptr;
NetPlayClient::NetPlayClient(QObject *parent, bool outGoing)
: role(0), state(0), currentFrame(0), sock(nullptr), recvMsgId(-1), recvMsgSize(0),
recvMsgBytesLeft(0), recvMsgByteIndex(0), recvMsgBuf(nullptr)
{
FCEU::timeStampRecord ts;
ts.readNew();
spawnTimeStampMs = ts.toMilliSeconds();
if (outGoing)
{
instance = this;
}
recvMsgBuf = static_cast<char*>( ::malloc( recvMsgBufSize ) );
if (recvMsgBuf == nullptr)
{
printf("Error: NetPlayClient failed to allocate recvMsgBuf\n");
}
if (outGoing)
{
connect(consoleWindow, SIGNAL(romLoaded(void)), this, SLOT(onRomLoad(void)));
connect(consoleWindow, SIGNAL(romUnload(void)), this, SLOT(onRomUnload(void)));
FCEU_WRAPPER_LOCK();
if (currCartInfo != nullptr)
{
romCrc32 = currCartInfo->CRC32;
}
FCEU_WRAPPER_UNLOCK();
}
}
NetPlayClient::~NetPlayClient(void)
{
if (instance == this)
{
instance = nullptr;
}
if (romLoadData.buf != nullptr)
{
::free(romLoadData.buf);
romLoadData.buf = nullptr;
}
romLoadData.size = 0;
romLoadData.fileName.clear();
if (stateLoadData.buf != nullptr)
{
::free(stateLoadData.buf);
stateLoadData.buf = nullptr;
}
stateLoadData.size = 0;
if (sock != nullptr)
{
sock->close();
delete sock; sock = nullptr;
}
if (recvMsgBuf)
{
::free(recvMsgBuf); recvMsgBuf = nullptr;
}
if (numMsgBoxObjs != 0)
{
printf("Warning: Client has leaked message dialogs\n");
}
if (debugLog != nullptr)
{
debugLog->close();
}
printf("NetPlayClient Destructor\n");
}
int NetPlayClient::Create(QObject *parent)
{
if (NetPlayClient::GetInstance() == nullptr)
{
NetPlayClient *client = new NetPlayClient(parent, true);
if (client == nullptr)
{
printf("Error Creating Netplay Client!!!\n");
}
}
FCEU_WRAPPER_LOCK();
if (traceRegistrationHandle == nullptr)
{
traceRegistrationHandle = FCEUI_TraceInstructionRegister( NetPlayTraceInstruction );
}
FCEU_WRAPPER_UNLOCK();
if (consoleWindow != nullptr)
{
consoleWindow->onNetPlayChange();
}
return 0;
}
int NetPlayClient::Destroy()
{
NetPlayClient* client = NetPlayClient::GetInstance();
if (client != nullptr)
{
delete client;
client = nullptr;
}
FCEU_WRAPPER_LOCK();
if (traceRegistrationHandle != nullptr)
{
if ( !FCEUI_TraceInstructionUnregisterHandle( traceRegistrationHandle ) )
{
printf("Unregister Trace Callback Error\n");
}
traceRegistrationHandle = nullptr;
}
FCEU_WRAPPER_UNLOCK();
if (consoleWindow != nullptr)
{
consoleWindow->onNetPlayChange();
}
return 0;
}
//-----------------------------------------------------------------------------
void NetPlayClient::onRomLoad()
{
FCEU_WRAPPER_LOCK();
if (currCartInfo != nullptr)
{
romCrc32 = currCartInfo->CRC32;
}
else
{
romCrc32 = 0;
}
FCEU_WRAPPER_UNLOCK();
}
//-----------------------------------------------------------------------------
void NetPlayClient::onRomUnload()
{
romCrc32 = 0;
}
//-----------------------------------------------------------------------------
void NetPlayClient::forceDisconnect()
{
disconnectPending = true;
needsDestroy = true;
}
//-----------------------------------------------------------------------------
bool NetPlayClient::isAuthenticated()
{
return (state > 0);
}
//-----------------------------------------------------------------------------
bool NetPlayClient::isPlayerRole()
{
return (role >= NETPLAY_PLAYER1) && (role <= NETPLAY_PLAYER4);
}
//-----------------------------------------------------------------------------
bool NetPlayClient::flushData()
{
bool success = false;
if (sock != nullptr)
{
success = sock->flush();
}
return success;
}
//-----------------------------------------------------------------------------
void NetPlayClient::setSocket(QTcpSocket *s)
{
sock = s;
if (sock != nullptr)
{
if (debugLog != nullptr)
{
FCEU::FixedString<256> logMsg;
if (isNetPlayHost())
{
QHostAddress addr = sock->peerAddress();
int port = sock->peerPort();
logMsg.sprintf("Client Socket Connected: %s:%i\n", addr.toString().toLocal8Bit().constData(), port );
}
else
{
QHostAddress addr = sock->localAddress();
int port = sock->localPort();
logMsg.sprintf("Client Socket Connected: %s:%i\n", addr.toString().toLocal8Bit().constData(), port );
}
//printf("LogMsg: %zu : %s\n", logMsg.size(), logMsg.c_str());
debugLog->write( logMsg.c_str(), logMsg.size() );
debugLog->flush();
}
sock->setSocketOption( QAbstractSocket::QAbstractSocket::LowDelayOption, 1);
sock->setSocketOption( QAbstractSocket::SendBufferSizeSocketOption, static_cast<int>(recvMsgBufSize) );
sock->setSocketOption( QAbstractSocket::ReceiveBufferSizeSocketOption, static_cast<int>(recvMsgBufSize) );
connect(sock, SIGNAL(connected(void)) , this, SLOT(onConnect(void)));
connect(sock, SIGNAL(disconnected(void)), this, SLOT(onDisconnect(void)));
}
}
//-----------------------------------------------------------------------------
QTcpSocket* NetPlayClient::createSocket(void)
{
if (sock == nullptr)
{
sock = new QTcpSocket(this);
sock->setSocketOption( QAbstractSocket::QAbstractSocket::LowDelayOption, 1);
sock->setSocketOption( QAbstractSocket::SendBufferSizeSocketOption, static_cast<int>(recvMsgBufSize) );
sock->setSocketOption( QAbstractSocket::ReceiveBufferSizeSocketOption, static_cast<int>(recvMsgBufSize) );
connect(sock, SIGNAL(readyRead(void)) , this, SLOT(clientReadyRead(void)) );
connect(sock, SIGNAL(connected(void)) , this, SLOT(onConnect(void)));
connect(sock, SIGNAL(disconnected(void)), this, SLOT(onDisconnect(void)));
connect(sock, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
}
return sock;
}
//-----------------------------------------------------------------------------
int NetPlayClient::connectToHost( const QString host, int port )
{
createSocket();
sock->connectToHost( host, port );
//if (sock->waitForConnected(10000))
//{
// qDebug("Connected!");
//}
//else
//{
// qDebug("Failed to Connect!");
//}
return 0;
}
//-----------------------------------------------------------------------------
void NetPlayClient::onConnect(void)
{
printf("Client Connected!!!\n");
FCEU_DispMessage("Joined Host",0);
_connected = true;
emit connected();
}
//-----------------------------------------------------------------------------
void NetPlayClient::onDisconnect(void)
{
printf("Client Disconnected!!!\n");
NetPlayServer *server = NetPlayServer::GetInstance();
if (server)
{
FCEU_DispMessage("%s Disconnected",0, userName.toLocal8Bit().constData());
}
else
{
FCEU_DispMessage("Host Disconnected",0);
}
needsDestroy = true;
}
//-----------------------------------------------------------------------------
void NetPlayClient::onSocketError(QAbstractSocket::SocketError error)
{
//FCEU_DispMessage("Socket Error",0);
QString errorMsg = sock->errorString();
printf("Error: %s\n", errorMsg.toLocal8Bit().constData());
FCEU_DispMessage("Socket Error: %s", 0, errorMsg.toLocal8Bit().constData());
emit errorOccurred(errorMsg);
}
//-----------------------------------------------------------------------------
int NetPlayClient::requestRomLoad( const char *romPath )
{
constexpr size_t BufferSize = 32 * 1024;
char buf[BufferSize];
size_t bytesRead;
long fileSize = 0;
netPlayLoadRomReq msg;
QFileInfo fi( romPath );
printf("Prep ROM Load Request: %s \n", romPath );
FILE *fp = ::fopen( romPath, "rb");
if (fp == nullptr)
{
return -1;
}
fseek( fp, 0, SEEK_END);
fileSize = ftell(fp);
rewind(fp);
msg.hdr.msgSize += fileSize;
msg.fileSize = fileSize;
Strlcpy( msg.fileName, fi.fileName().toLocal8Bit().constData(), sizeof(msg.fileName) );
printf("Sending ROM Load Request: %s %lu\n", romPath, fileSize );
msg.toNetworkByteOrder();
sock->write( reinterpret_cast<const char*>(&msg), sizeof(netPlayLoadRomReq) );
while ( (bytesRead = fread( buf, 1, sizeof(buf), fp )) > 0 )
{
sock->write( buf, bytesRead );
}
sock->flush();
::fclose(fp);
return 0;
}
//-----------------------------------------------------------------------------
int NetPlayClient::requestStateLoad(EMUFILE *is)
{
size_t dataSize;
char *dataBuf;
static constexpr size_t maxBytesPerWrite = 32 * 1024;
netPlayLoadStateResp resp;
dataSize = is->size();
resp.hdr.msgSize += dataSize;
resp.stateSize = dataSize;
if (dataSize == 0)
{
return -1;
}
dataBuf = static_cast<char*>(::malloc(dataSize));
if (dataBuf == nullptr)
{
return -1;
}
is->fseek( 0, SEEK_SET );
size_t readResult = is->fread( dataBuf, dataSize );
if (readResult != dataSize )
{
printf("Read Error\n");
}
if (currCartInfo != nullptr)
{
resp.romCrc32 = romCrc32;
}
printf("Sending Client ROM Sync Request: %u\n", resp.stateSize);
resp.toNetworkByteOrder();
sock->write( reinterpret_cast<const char*>(&resp), sizeof(netPlayLoadStateResp));
const char* bufPtr = dataBuf;
while (dataSize > 0)
{
size_t bytesToWrite = dataSize;
if (bytesToWrite > maxBytesPerWrite)
{
bytesToWrite = maxBytesPerWrite;
}
sock->write( bufPtr, bytesToWrite );
bufPtr += bytesToWrite;
dataSize -= bytesToWrite;
}
sock->flush();
::free(dataBuf);
return 0;
}
//-----------------------------------------------------------------------------
int NetPlayClient::requestSync(void)
{
netPlayMsgHdr hdr(NETPLAY_SYNC_STATE_REQ);
hdr.toNetworkByteOrder();
sock->write( reinterpret_cast<const char*>(&hdr), sizeof(netPlayMsgHdr));
return 0;
}
//-----------------------------------------------------------------------------
void NetPlayClient::recordPingResult( uint64_t delay_ms )
{
pingNumSamples++;
pingDelayLast = delay_ms;
pingDelaySum += delay_ms;
}
//-----------------------------------------------------------------------------
void NetPlayClient::resetPingData()
{
pingNumSamples = 0;
pingDelayLast = 0;
pingDelaySum = 0;
}
//-----------------------------------------------------------------------------
double NetPlayClient::getAvgPingDelay()
{
double ms = 0.0;
if (pingNumSamples > 0)
{
ms = static_cast<double>(pingDelaySum) / static_cast<double>(pingNumSamples);
}
return ms;
}
//-----------------------------------------------------------------------------
static void clientMessageCallback( void *userData, void *msgBuf, size_t msgSize )
{
NetPlayClient *client = static_cast<NetPlayClient*>(userData);
client->clientProcessMessage( msgBuf, msgSize );
}
//-----------------------------------------------------------------------------
void NetPlayClient::update(void)
{
readMessages( clientMessageCallback, this );
if (_connected)
{
uint32_t ctlrData = GetGamepadPressedImmediate();
uint32_t currFrame = static_cast<uint32_t>(currFrameCounter);
NetPlayFrameData lastFrameData;
netPlayFrameData.getLast( lastFrameData );
netPlayClientState statusMsg;
statusMsg.flags = 0;
if (FCEUI_EmulationPaused())
{
statusMsg.flags |= netPlayClientState::PauseFlag;
}
if (desyncCount > 0)
{
statusMsg.flags |= netPlayClientState::DesyncFlag;
}
statusMsg.frameRdy = inputFrameBack();
statusMsg.frameRun = currFrame;
statusMsg.opsFrame = lastFrameData.frameNum;
statusMsg.opsChkSum = lastFrameData.opsCrc32;
statusMsg.ramChkSum = lastFrameData.ramCrc32;
statusMsg.romCrc32 = romCrc32;
statusMsg.ctrlState[0] = (ctlrData ) & 0x000000ff;
statusMsg.ctrlState[1] = (ctlrData >> 8) & 0x000000ff;
statusMsg.ctrlState[2] = (ctlrData >> 16) & 0x000000ff;
statusMsg.ctrlState[3] = (ctlrData >> 24) & 0x000000ff;
statusMsg.toNetworkByteOrder();
sock->write( reinterpret_cast<const char*>(&statusMsg), sizeof(statusMsg) );
flushData();
}
}
//-----------------------------------------------------------------------------
int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData )
{
FCEU::FixedString<256> logMsg;
if (readMessageProcessing)
{
printf("Read Message is Processing in callstack, don't allow re-entrantancy.\n");
return 0;
}
readMessageProcessing = true;
if (sock)
{
bool readReq;
netPlayMsgHdr *hdr;
constexpr int netPlayMsgHdrSize = sizeof(netPlayMsgHdr);
long int ts = 0;
int bytesAvailable = sock->bytesAvailable();
readReq = bytesAvailable > 0;
if (debugLog != nullptr)
{
FCEU::timeStampRecord tsRec;
tsRec.readNew();
ts = tsRec.toMilliSeconds() - spawnTimeStampMs;
}
while (readReq)
{
//printf("Read Bytes Available: %lu %i %i\n", ts.toMilliSeconds(), bytesAvailable, recvMsgBytesLeft);
if (recvMsgBytesLeft > 0)
{
bytesAvailable = sock->bytesAvailable();
readReq = (bytesAvailable > 0);
if (readReq)
{
int dataRead;
size_t readSize = recvMsgBytesLeft;
dataRead = sock->read( &recvMsgBuf[recvMsgByteIndex], readSize );
if (dataRead > 0)
{
recvMsgBytesLeft -= dataRead;
recvMsgByteIndex += dataRead;
}
//printf(" Data: Id: %u Size: %zu Read: %i\n", recvMsgId, readSize, dataRead );
if (debugLog != nullptr)
{
logMsg.sprintf("%lu: Recv Chunk: Size:%i Left:%i\n", ts, dataRead, recvMsgBytesLeft);
debugLog->write( logMsg.c_str(), logMsg.size() );
}
if (recvMsgBytesLeft > 0)
{
bytesAvailable = sock->bytesAvailable();
readReq = (bytesAvailable > 0);
}
else
{
if (debugLog != nullptr)
{
logMsg.sprintf("%lu: Recv End: Size:%i \n", ts, recvMsgSize);
debugLog->write( logMsg.c_str(), logMsg.size() );
}
msgCallback( userData, recvMsgBuf, recvMsgSize );
bytesAvailable = sock->bytesAvailable();
readReq = (bytesAvailable > 0);
}
}
}
else if (bytesAvailable >= netPlayMsgHdrSize)
{
sock->read( recvMsgBuf, netPlayMsgHdrSize );
hdr = (netPlayMsgHdr*)recvMsgBuf;
recvMsgId = netPlayByteSwap(hdr->msgId);
recvMsgSize = netPlayByteSwap(hdr->msgSize) - sizeof(netPlayMsgHdr);
recvMsgBytesLeft = recvMsgSize;
if ( (netPlayByteSwap(hdr->magic[0]) != NETPLAY_MAGIC_NUMBER) ||
(netPlayByteSwap(hdr->magic[1]) != NETPLAY_MAGIC_NUMBER) )
{
FCEU_printf("Netplay Error: Message Header Validity Check Failed: %08X\n", recvMsgId);
forceDisconnect();
return -1;
}
if (netPlayByteSwap(hdr->msgSize) > recvMsgBufSize)
{
FCEU_printf("Netplay Error: Message size too large: %08X\n", recvMsgId);
forceDisconnect();
return -1;
}
if (debugLog != nullptr)
{
logMsg.sprintf("%lu: Recv Start: HDR Info: Id:%u ExpectedSize:%i\n", ts, netPlayByteSwap(hdr->msgId), recvMsgSize );
debugLog->write( logMsg.c_str(), logMsg.size() );
}
//printf("HDR: Id: %u Size: %u\n", netPlayByteSwap(hdr->msgId), netPlayByteSwap(hdr->msgSize) );
recvMsgByteIndex = sizeof(netPlayMsgHdr);
if (recvMsgBytesLeft == 0)
{
msgCallback( userData, recvMsgBuf, recvMsgSize );
}
bytesAvailable = sock->bytesAvailable();
readReq = (bytesAvailable > 0);
}
else
{
readReq = false;
}
}
}
readMessageProcessing = false;
return 0;
}
//-----------------------------------------------------------------------------
void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
{
netPlayMsgHdr *hdr = (netPlayMsgHdr*)msgBuf;
const uint32_t msgId = netPlayByteSwap(hdr->msgId);
switch (msgId)
{
case NETPLAY_INFO_MSG:
{
auto *msg = static_cast<netPlayTextMsg<256>*>(msgBuf);
msg->toHostByteOrder();
FCEU_printf("NetPlay Info: %s\n", msg->getBuffer());
if (msg->isFlagSet(netPlayTextMsgFlags::Disconnect))
{
sock->disconnectFromHost();
}
FCEU_DispMessage("NetPlay Errors... check message log",0);
}
break;
case NETPLAY_ERROR_MSG:
{
auto *msg = static_cast<netPlayTextMsg<256>*>(msgBuf);
msg->toHostByteOrder();
FCEU_printf("NetPlay Error: %s\n", msg->getBuffer());
numMsgBoxObjs++;
QString msgBoxTxt = tr("Host has replied with an error:\n\n");
msgBoxTxt += tr(msg->getBuffer());
QMessageBox* msgBox = new QMessageBox( QMessageBox::Critical, tr("NetPlay Error"), msgBoxTxt, QMessageBox::Ok, consoleWindow );
connect( msgBox->button(QMessageBox::Ok), SIGNAL(clicked(void)), msgBox, SLOT(accept(void)));
connect( msgBox, &QMessageBox::finished, this, [this, msgBox](){ msgBox->deleteLater(); } );
connect( msgBox, SIGNAL(destroyed(QObject*)), this, SLOT(onMessageBoxDestroy(QObject*)));
msgBox->show();
if (msg->isFlagSet(netPlayTextMsgFlags::Disconnect))
{
sock->disconnectFromHost();
}
FCEU_DispMessage("NetPlay Errors... check message log",0);
}
break;
case NETPLAY_AUTH_REQ:
{
netPlayAuthResp msg;
msg.playerId = role;
Strlcpy( msg.userName, userName.toLocal8Bit().constData(), sizeof(msg.userName));
Strlcpy( msg.pswd, password.toLocal8Bit().constData(), sizeof(msg.pswd) );
FCEU_printf("Authentication Request Received\n");
msg.toNetworkByteOrder();
sock->write( (const char*)&msg, sizeof(netPlayAuthResp) );
}
break;
case NETPLAY_LOAD_ROM_REQ:
{
QTemporaryFile tmpFile;
netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(msgBuf);
msg->toHostByteOrder();
const char *romData = &static_cast<const char*>(msgBuf)[ sizeof(netPlayLoadRomReq) ];
FCEU_printf("Load ROM Request Received: %s\n", msg->fileName);
tmpFile.setFileTemplate(QDir::tempPath() + QString("/tmpRom_XXXXXX.nes"));
tmpFile.open();
QString filepath = tmpFile.fileName();
printf("Dumping Temp Rom to: %s\n", tmpFile.fileName().toLocal8Bit().constData());
tmpFile.write( romData, msgSize );
tmpFile.close();
FCEU_WRAPPER_LOCK();
LoadGame( filepath.toLocal8Bit().constData(), true, true );
opsCrc32 = 0;
netPlayFrameData.reset();
inputClear();
FCEU_WRAPPER_UNLOCK();
}
break;
case NETPLAY_UNLOAD_ROM_REQ:
{
FCEU_WRAPPER_LOCK();
CloseGame();
FCEU_WRAPPER_UNLOCK();
}
break;
case NETPLAY_SYNC_STATE_RESP:
{
netPlayLoadStateResp* msg = static_cast<netPlayLoadStateResp*>(msgBuf);
msg->toHostByteOrder();
const bool romMatch = (msg->romCrc32 = romCrc32);
char *stateData = msg->stateDataBuf();
const uint32_t stateDataSize = msg->stateDataSize();
FCEU_printf("Sync state Request Received: %u\n", stateDataSize);
EMUFILE_MEMORY em( stateData, stateDataSize );
FCEU_WRAPPER_LOCK();
bool dataValid = romMatch;
if (dataValid)
{
serverRequestedStateLoad = true;
FCEUSS_LoadFP( &em, SSLOADPARAM_NOBACKUP );
serverRequestedStateLoad = false;
opsCrc32 = msg->opsCrc32;
netPlayFrameData.reset();
NetPlayFrameData data;
data.frameNum = msg->lastFrame.num;
data.opsCrc32 = msg->lastFrame.opsCrc32;
data.ramCrc32 = msg->lastFrame.ramCrc32;
netPlayFrameData.push( data );
inputClear();
const int numInputFrames = msg->numCtrlFrames;
for (int i=0; i<numInputFrames; i++)
{
NetPlayFrameInput inputFrame;
auto ctrlData = msg->ctrlDataBuf();
ctrlData[i].toHostByteOrder();
inputFrame.frameCounter = ctrlData[i].frameNum;
inputFrame.ctrl[0] = ctrlData[i].ctrlState[0];
inputFrame.ctrl[1] = ctrlData[i].ctrlState[1];
inputFrame.ctrl[2] = ctrlData[i].ctrlState[2];
inputFrame.ctrl[3] = ctrlData[i].ctrlState[3];
pushBackInput( inputFrame );
}
const int numCheats = msg->numCheats;
if (numCheats > 0)
{
const int lastCheatIdx = numCheats - 1;
FCEU_FlushGameCheats(0, 1);
for (int i=0; i<numCheats; i++)
{
auto cheatBuf = msg->cheatDataBuf();
auto& cheatData = cheatBuf[i];
// Set cheat rebuild flag on last item.
bool lastItem = (i == lastCheatIdx);
FCEUI_AddCheat( cheatData.name, cheatData.addr, cheatData.val, cheatData.cmp, cheatData.type, cheatData.stat, lastItem );
}
}
else
{
FCEU_DeleteAllCheats();
}
}
FCEU_WRAPPER_UNLOCK();
}
break;
case NETPLAY_RUN_FRAME_REQ:
{
NetPlayFrameInput inputFrame;
netPlayRunFrameReq *msg = static_cast<netPlayRunFrameReq*>(msgBuf);
msg->toHostByteOrder();
inputFrame.frameCounter = msg->frameNum;
inputFrame.ctrl[0] = msg->ctrlState[0];
inputFrame.ctrl[1] = msg->ctrlState[1];
inputFrame.ctrl[2] = msg->ctrlState[2];
inputFrame.ctrl[3] = msg->ctrlState[3];
catchUpThreshold = msg->catchUpThreshold;
uint32_t lastInputFrame = inputFrameBack();
uint32_t currFrame = static_cast<uint32_t>(currFrameCounter);
if (inputFrame.frameCounter > lastInputFrame)
{
pushBackInput( inputFrame );
}
else
{
printf("Drop Frame: LastRun:%u LastInput:%u NewInput:%u\n", currFrame, lastInputFrame, inputFrame.frameCounter);
}
//printf("Run Frame: LastRun:%u LastInput:%u NewInput:%u\n", currFrame, lastInputFrame, inputFrame.frameCounter);
}
break;
case NETPLAY_PING_REQ:
{
netPlayPingResp pong;
netPlayPingReq *ping = static_cast<netPlayPingReq*>(msgBuf);
ping->toHostByteOrder();
pong.hostTimeStamp = ping->hostTimeStamp;
pong.toNetworkByteOrder();
sock->write( (const char*)&pong, sizeof(netPlayPingResp) );
}
break;
case NETPLAY_CLIENT_PAUSE_REQ:
{
if (!FCEUI_EmulationPaused())
{
FCEUI_ToggleEmulationPause();
}
}
break;
case NETPLAY_CLIENT_UNPAUSE_REQ:
{
if (FCEUI_EmulationPaused())
{
FCEUI_ToggleEmulationPause();
}
}
break;
default:
printf("Unknown Msg: %08X\n", msgId);
break;
}
}
//-----------------------------------------------------------------------------
void NetPlayClient::serverReadyRead()
{
//printf("Server Ready Read\n");
readMessages( serverMessageCallback, this );
}
//-----------------------------------------------------------------------------
void NetPlayClient::clientReadyRead()
{
//printf("Client Ready Read\n");
readMessages( clientMessageCallback, this );
}
//-----------------------------------------------------------------------------
void NetPlayClient::onMessageBoxDestroy(QObject* obj)
{
//printf("MessageBox Destroyed: %p\n", obj);
if (numMsgBoxObjs > 0)
{
numMsgBoxObjs--;
}
else
{
printf("Warning: Unexpected MessageBox Destroy: %p\n", obj);
}
}
//-----------------------------------------------------------------------------
//--- NetPlayHostDialog
//-----------------------------------------------------------------------------
NetPlayHostDialog* NetPlayHostDialog::instance = nullptr;
//-----------------------------------------------------------------------------
NetPlayHostDialog::NetPlayHostDialog(QWidget *parent)
: QDialog(parent)
{
QVBoxLayout *mainLayout;
QHBoxLayout *hbox;
QGridLayout *grid;
QGroupBox *networkGroupBox;
QGroupBox *settingsGroupBox;
QPushButton *cancelButton, *startButton;
QLabel *lbl;
instance = this;
setWindowTitle("NetPlay Host Game");
mainLayout = new QVBoxLayout();
networkGroupBox = new QGroupBox(tr("Network Setup"));
settingsGroupBox = new QGroupBox(tr("Server Settings"));
hbox = new QHBoxLayout();
grid = new QGridLayout();
mainLayout->addLayout(hbox);
hbox->addWidget(networkGroupBox);
hbox->addWidget(settingsGroupBox);
// Network Settings
networkGroupBox->setLayout(grid);
lbl = new QLabel( tr("Server Name:") );
grid->addWidget( lbl, 0, 0 );
sessionNameEntry = new QLineEdit();
sessionNameEntry->setText("My Game");
grid->addWidget( sessionNameEntry, 0, 1 );
lbl = new QLabel( tr("Port:") );
grid->addWidget( lbl, 1, 0 );
int netPort = NetPlayServer::DefaultPort;
g_config->getOption("SDL.NetworkPort", &netPort);
portEntry = new QSpinBox();
portEntry->setRange(0,65535);
portEntry->setValue(netPort);
grid->addWidget( portEntry, 1, 1 );
lbl = new QLabel( tr("Role:") );
grid->addWidget( lbl, 2, 0 );
playerRoleBox = new QComboBox();
playerRoleBox->addItem( tr("Spectator"), NETPLAY_SPECTATOR);
playerRoleBox->addItem( tr("Player 1") , NETPLAY_PLAYER1 );
playerRoleBox->addItem( tr("Player 2") , NETPLAY_PLAYER2 );
playerRoleBox->addItem( tr("Player 3") , NETPLAY_PLAYER3 );
playerRoleBox->addItem( tr("Player 4") , NETPLAY_PLAYER4 );
playerRoleBox->setCurrentIndex(1);
grid->addWidget( playerRoleBox, 2, 1 );
passwordRequiredCBox = new QCheckBox(tr("Password Required"));
grid->addWidget( passwordRequiredCBox, 3, 0, 1, 2 );
lbl = new QLabel( tr("Password:") );
grid->addWidget( lbl, 4, 0 );
passwordEntry = new QLineEdit();
grid->addWidget( passwordEntry, 4, 1 );
passwordEntry->setEnabled( passwordRequiredCBox->isChecked() );
// Misc Settings
grid = new QGridLayout();
settingsGroupBox->setLayout(grid);
lbl = new QLabel( tr("Max Frame Lead:") );
frameLeadSpinBox = new QSpinBox();
frameLeadSpinBox->setRange(5,60);
frameLeadSpinBox->setValue(30);
grid->addWidget( lbl, 0, 0, 1, 1 );
grid->addWidget( frameLeadSpinBox, 0, 1, 1, 1 );
bool enforceAppVersionChk = false;
enforceAppVersionChkCBox = new QCheckBox(tr("Enforce Client Versions Match"));
grid->addWidget( enforceAppVersionChkCBox, 1, 0, 1, 2 );
g_config->getOption("SDL.NetPlayHostEnforceAppVersionChk", &enforceAppVersionChk);
enforceAppVersionChkCBox->setChecked(enforceAppVersionChk);
bool romLoadReqEna = false;
allowClientRomReqCBox = new QCheckBox(tr("Allow Client ROM Load Requests"));
grid->addWidget( allowClientRomReqCBox, 2, 0, 1, 2 );
g_config->getOption("SDL.NetPlayHostAllowClientRomLoadReq", &romLoadReqEna);
allowClientRomReqCBox->setChecked(romLoadReqEna);
bool stateLoadReqEna = false;
allowClientStateReqCBox = new QCheckBox(tr("Allow Client State Load Requests"));
grid->addWidget( allowClientStateReqCBox, 3, 0, 1, 2 );
g_config->getOption("SDL.NetPlayHostAllowClientStateLoadReq", &stateLoadReqEna);
allowClientStateReqCBox->setChecked(stateLoadReqEna);
debugModeCBox = new QCheckBox(tr("Debug Network Messaging"));
grid->addWidget( debugModeCBox, 4, 0, 1, 2 );
connect(passwordRequiredCBox, SIGNAL(stateChanged(int)), this, SLOT(passwordRequiredChanged(int)));
connect(allowClientRomReqCBox, SIGNAL(stateChanged(int)), this, SLOT(allowClientRomReqChanged(int)));
connect(allowClientStateReqCBox, SIGNAL(stateChanged(int)), this, SLOT(allowClientStateReqChanged(int)));
connect(enforceAppVersionChkCBox, SIGNAL(stateChanged(int)), this, SLOT(enforceAppVersionChkChanged(int)));
startButton = new QPushButton( tr("Start") );
startButton->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton));
connect(startButton, SIGNAL(clicked(void)), this, SLOT(onStartClicked(void)));
cancelButton = new QPushButton( tr("Cancel") );
cancelButton->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
connect(cancelButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void)));
hbox = new QHBoxLayout();
hbox->addWidget( cancelButton, 1 );
hbox->addStretch(5);
hbox->addWidget( startButton, 1 );
mainLayout->addLayout( hbox );
setLayout(mainLayout);
}
//----------------------------------------------------------------------------
NetPlayHostDialog::~NetPlayHostDialog(void)
{
instance = nullptr;
//printf("Destroy NetPlay Host Window\n");
}
//----------------------------------------------------------------------------
void NetPlayHostDialog::closeEvent(QCloseEvent *event)
{
//printf("NetPlay Host Close Window Event\n");
done(0);
deleteLater();
event->accept();
}
//----------------------------------------------------------------------------
void NetPlayHostDialog::closeWindow(void)
{
//printf("Close Window\n");
done(0);
deleteLater();
}
//-----------------------------------------------------------------------------
void NetPlayHostDialog::passwordRequiredChanged(int state)
{
passwordEntry->setEnabled( state != Qt::Unchecked );
}
//-----------------------------------------------------------------------------
void NetPlayHostDialog::allowClientRomReqChanged(int state)
{
g_config->setOption("SDL.NetPlayHostAllowClientRomLoadReq", state != Qt::Unchecked);
g_config->save();
}
//-----------------------------------------------------------------------------
void NetPlayHostDialog::allowClientStateReqChanged(int state)
{
g_config->setOption("SDL.NetPlayHostAllowClientStateLoadReq", state != Qt::Unchecked);
g_config->save();
}
//-----------------------------------------------------------------------------
void NetPlayHostDialog::enforceAppVersionChkChanged(int state)
{
g_config->setOption("SDL.NetPlayHostEnforceAppVersionChk", state != Qt::Unchecked);
g_config->save();
}
//-----------------------------------------------------------------------------
void NetPlayHostDialog::onStartClicked(void)
{
NetPlayServer *server = NetPlayServer::GetInstance();
if (server != nullptr)
{
delete server;
server = nullptr;
}
NetPlayServer::Create(consoleWindow);
const int netPort = portEntry->value();
server = NetPlayServer::GetInstance();
server->setRole( playerRoleBox->currentData().toInt() );
server->sessionName = sessionNameEntry->text();
server->setMaxLeadFrames( frameLeadSpinBox->value() );
server->setEnforceAppVersionCheck( enforceAppVersionChkCBox->isChecked() );
server->setAllowClientRomLoadRequest( allowClientRomReqCBox->isChecked() );
server->setAllowClientStateLoadRequest( allowClientStateReqCBox->isChecked() );
server->setDebugMode( debugModeCBox->isChecked() );
if (passwordRequiredCBox->isChecked())
{
server->sessionPasswd = passwordEntry->text();
}
bool listenSucceeded = server->listen( QHostAddress::Any, netPort );
if (listenSucceeded)
{
g_config->setOption("SDL.NetworkPort", netPort);
done(0);
deleteLater();
}
else
{
QString msg = "Failed to start TCP server on port ";
msg += QString::number(netPort);
msg += "\n\nReason: ";
msg += server->errorString();
QMessageBox::warning( this, tr("TCP Server Error"), msg, QMessageBox::Ok );
// Server init failed, destruct server
if (server != nullptr)
{
delete server;
server = nullptr;
}
}
}
//-----------------------------------------------------------------------------
//--- NetPlayJoinDialog
//-----------------------------------------------------------------------------
NetPlayJoinDialog* NetPlayJoinDialog::instance = nullptr;
//-----------------------------------------------------------------------------
NetPlayJoinDialog::NetPlayJoinDialog(QWidget *parent)
: QDialog(parent)
{
QVBoxLayout *mainLayout;
QHBoxLayout *hbox;
QGridLayout *grid;
QPushButton *cancelButton, *startButton;
QLabel *lbl;
instance = this;
setWindowTitle("NetPlay Join Game");
mainLayout = new QVBoxLayout();
grid = new QGridLayout();
lbl = new QLabel( tr("Host:") );
grid->addWidget( lbl, 0, 0 );
QString hostAddress = "localhost";
g_config->getOption("SDL.NetworkIP", &hostAddress);
if (hostAddress.isEmpty())
{
hostAddress = "localhost";
}
hostEntry = new QLineEdit();
hostEntry->setText(hostAddress);
grid->addWidget( hostEntry, 0, 1 );
lbl = new QLabel( tr("Port:") );
grid->addWidget( lbl, 1, 0 );
int netPort = NetPlayServer::DefaultPort;
g_config->getOption("SDL.NetworkPort", &netPort);
portEntry = new QSpinBox();
portEntry->setRange(0,65535);
portEntry->setValue(netPort);
grid->addWidget( portEntry, 1, 1 );
lbl = new QLabel( tr("Role:") );
grid->addWidget( lbl, 2, 0 );
playerRoleBox = new QComboBox();
playerRoleBox->addItem( tr("Spectator"), NETPLAY_SPECTATOR);
playerRoleBox->addItem( tr("Player 1") , NETPLAY_PLAYER1 );
playerRoleBox->addItem( tr("Player 2") , NETPLAY_PLAYER2 );
playerRoleBox->addItem( tr("Player 3") , NETPLAY_PLAYER3 );
playerRoleBox->addItem( tr("Player 4") , NETPLAY_PLAYER4 );
playerRoleBox->setCurrentIndex(2);
grid->addWidget( playerRoleBox, 2, 1 );
lbl = new QLabel( tr("User:") );
grid->addWidget( lbl, 3, 0 );
QString name;
g_config->getOption("SDL.NetworkUsername", &name);
userNameEntry = new QLineEdit();
userNameEntry->setMaxLength(63);
userNameEntry->setText(name);
grid->addWidget( userNameEntry, 3, 1 );
lbl = new QLabel( tr("Password:") );
grid->addWidget( lbl, 4, 0 );
passwordEntry = new QLineEdit();
passwordEntry->setMaxLength(64);
passwordEntry->setEnabled(false);
grid->addWidget( passwordEntry, 4, 1 );
mainLayout->addLayout(grid);
startButton = new QPushButton( tr("Join") );
startButton->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton));
connect(startButton, SIGNAL(clicked(void)), this, SLOT(onJoinClicked(void)));
cancelButton = new QPushButton( tr("Cancel") );
cancelButton->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
connect(cancelButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void)));
hbox = new QHBoxLayout();
hbox->addWidget( cancelButton, 1 );
hbox->addStretch(5);
hbox->addWidget( startButton, 1 );
mainLayout->addLayout( hbox );
setLayout(mainLayout);
}
//----------------------------------------------------------------------------
NetPlayJoinDialog::~NetPlayJoinDialog(void)
{
instance = nullptr;
//printf("Destroy NetPlay Host Window\n");
}
//----------------------------------------------------------------------------
void NetPlayJoinDialog::closeEvent(QCloseEvent *event)
{
//printf("NetPlay Host Close Window Event\n");
done(0);
deleteLater();
event->accept();
}
//----------------------------------------------------------------------------
void NetPlayJoinDialog::closeWindow(void)
{
//printf("Close Window\n");
done(0);
deleteLater();
}
//----------------------------------------------------------------------------
void NetPlayJoinDialog::onJoinClicked(void)
{
NetPlayClient *client = NetPlayClient::GetInstance();
if (client != nullptr)
{
delete client;
client = nullptr;
}
NetPlayClient::Create(consoleWindow);
const int netPort = portEntry->value();
client = NetPlayClient::GetInstance();
client->role = playerRoleBox->currentData().toInt();
client->userName = userNameEntry->text();
client->password = passwordEntry->text();
QString hostAddress = hostEntry->text();
connect(client, SIGNAL(connected(void)) , this, SLOT(onConnect(void)));
connect(client, SIGNAL(errorOccurred(const QString&)), this, SLOT(onSocketError(const QString&)));
if (client->connectToHost( hostAddress, netPort ))
{
FCEU_DispMessage("Host connect failed",0);
}
g_config->setOption("SDL.NetworkIP", hostAddress);
g_config->setOption("SDL.NetworkPort", netPort);
}
//----------------------------------------------------------------------------
void NetPlayJoinDialog::onConnect()
{
//printf("Close Window\n");
done(0);
deleteLater();
}
//----------------------------------------------------------------------------
void NetPlayJoinDialog::onSocketError(const QString& errorMsg)
{
QString msg = "Failed to connect to server";
msg += "\n\nReason: ";
msg += errorMsg;
QMessageBox::warning( this, tr("Connection Error"), msg, QMessageBox::Ok );
NetPlayClient *client = NetPlayClient::GetInstance();
if (client != nullptr)
{
delete client;
client = nullptr;
}
}
//-----------------------------------------------------------------------------
//--- NetPlayClientStatusDialog
//-----------------------------------------------------------------------------
NetPlayClientStatusDialog* NetPlayClientStatusDialog::instance = nullptr;
//-----------------------------------------------------------------------------
NetPlayClientStatusDialog::NetPlayClientStatusDialog(QWidget *parent)
: QDialog(parent)
{
QVBoxLayout *mainLayout;
QHBoxLayout *hbox;
QGridLayout *grid;
QGroupBox *gbox;
QPushButton *closeButton;
//QLabel *lbl;
instance = this;
mainLayout = new QVBoxLayout();
grid = new QGridLayout();
gbox = new QGroupBox(tr("Connection"));
gbox->setLayout(grid);
mainLayout->addWidget(gbox);
hostStateLbl = new QLabel(tr("Unknown"));
grid->addWidget( new QLabel(tr("Host Frame:")), 0, 0 );
grid->addWidget( hostStateLbl, 0, 1 );
requestResyncButton = new QPushButton(tr("Resync State"));
grid->addWidget( requestResyncButton, 1, 0, 1, 2 );
hbox = new QHBoxLayout();
mainLayout->addLayout(hbox);
closeButton = new QPushButton( tr("Close") );
closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton));
connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void)));
hbox->addStretch(3);
hbox->addWidget(closeButton);
setWindowTitle("NetPlay Status");
//resize( 512, 256 );
setLayout(mainLayout);
connect(requestResyncButton, SIGNAL(clicked(void)), this, SLOT(resyncButtonClicked(void)));
periodicTimer = new QTimer(this);
periodicTimer->start(200); // 5hz
connect(periodicTimer, &QTimer::timeout, this, &NetPlayClientStatusDialog::updatePeriodic);
}
//----------------------------------------------------------------------------
NetPlayClientStatusDialog::~NetPlayClientStatusDialog(void)
{
instance = nullptr;
periodicTimer->stop();
delete periodicTimer;
//printf("Destroy NetPlay Status Window\n");
}
//----------------------------------------------------------------------------
void NetPlayClientStatusDialog::closeEvent(QCloseEvent *event)
{
//printf("NetPlay Client Close Window Event\n");
done(0);
deleteLater();
event->accept();
}
//----------------------------------------------------------------------------
void NetPlayClientStatusDialog::closeWindow(void)
{
//printf("Close Window\n");
done(0);
deleteLater();
}
//----------------------------------------------------------------------------
void NetPlayClientStatusDialog::updatePeriodic()
{
updateStatusDisplay();
}
//----------------------------------------------------------------------------
void NetPlayClientStatusDialog::updateStatusDisplay()
{
NetPlayClient* client = NetPlayClient::GetInstance();
if (client == nullptr)
{
return;
}
char stmp[64];
uint32_t inputFrame = client->inputFrameBack();
if (inputFrame == 0)
{
inputFrame = static_cast<uint32_t>(currFrameCounter);
}
snprintf( stmp, sizeof(stmp), "%u", inputFrame);
hostStateLbl->setText(tr(stmp));
}
//----------------------------------------------------------------------------
void NetPlayClientStatusDialog::resyncButtonClicked()
{
NetPlayClient* client = NetPlayClient::GetInstance();
if (client == nullptr)
{
return;
}
client->requestSync();
}
//-----------------------------------------------------------------------------
//--- NetPlayHostStatusDialog
//-----------------------------------------------------------------------------
NetPlayHostStatusDialog* NetPlayHostStatusDialog::instance = nullptr;
//-----------------------------------------------------------------------------
NetPlayHostStatusDialog::NetPlayHostStatusDialog(QWidget *parent)
: QDialog(parent)
{
QVBoxLayout *mainLayout, *vbox1;
QHBoxLayout *hbox;
//QGridLayout *grid;
QGroupBox *gbox;
QPushButton *closeButton;
//QLabel *lbl;
instance = this;
setWindowTitle("NetPlay Status");
resize( 512, 256 );
mainLayout = new QVBoxLayout();
vbox1 = new QVBoxLayout();
gbox = new QGroupBox(tr("Connected Players / Spectators"));
clientTree = new QTreeWidget();
dropPlayerButton = new QPushButton( tr("Drop Player") );
resyncPlayerButton = new QPushButton( tr("Resync Player") );
resyncAllButton = new QPushButton( tr("Resync All") );
dropPlayerButton->setEnabled(false);
resyncPlayerButton->setEnabled(false);
gbox->setLayout(vbox1);
vbox1->addWidget( clientTree );
hbox = new QHBoxLayout();
hbox->addWidget( dropPlayerButton, 1 );
hbox->addStretch(3);
hbox->addWidget( resyncPlayerButton, 1 );
hbox->addWidget( resyncAllButton, 1 );
vbox1->addLayout(hbox);
mainLayout->addWidget( gbox );
clientTree->setColumnCount(3);
auto* item = new QTreeWidgetItem();
item->setText( 0, QString::fromStdString( "Player" ) );
item->setText( 1, QString::fromStdString( "Role" ) );
item->setText( 2, QString::fromStdString( "State" ) );
item->setText( 3, QString::fromStdString( "Frame" ) );
item->setTextAlignment( 0, Qt::AlignLeft);
item->setTextAlignment( 1, Qt::AlignLeft);
item->setTextAlignment( 2, Qt::AlignLeft);
item->setTextAlignment( 3, Qt::AlignLeft);
connect( clientTree, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(clientItemClicked( QTreeWidgetItem*, int)) );
clientTree->setHeaderItem( item );
clientTree->setContextMenuPolicy(Qt::CustomContextMenu);
closeButton = new QPushButton( tr("Close") );
closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton));
connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void)));
hbox = new QHBoxLayout();
hbox->addStretch(5);
hbox->addWidget( closeButton, 1 );
mainLayout->addLayout( hbox );
setLayout(mainLayout);
connect( NetPlayServer::GetInstance(), SIGNAL(clientConnected(void)), this, SLOT(loadClientTree(void)) );
connect( NetPlayServer::GetInstance(), SIGNAL(clientDisconnected(void)), this, SLOT(loadClientTree(void)) );
connect( clientTree, &QTreeWidget::customContextMenuRequested, this, &NetPlayHostStatusDialog::onClientTreeContextMenu);
connect( dropPlayerButton , SIGNAL(clicked(void)), this, SLOT(dropPlayer(void)) );
connect( resyncPlayerButton, SIGNAL(clicked(void)), this, SLOT(resyncPlayer(void)) );
connect( resyncAllButton , SIGNAL(clicked(void)), this, SLOT(resyncAllPlayers(void)) );
loadClientTree();
periodicTimer = new QTimer(this);
periodicTimer->start(200); // 5hz
connect(periodicTimer, &QTimer::timeout, this, &NetPlayHostStatusDialog::updatePeriodic);
}
//----------------------------------------------------------------------------
NetPlayHostStatusDialog::~NetPlayHostStatusDialog(void)
{
instance = nullptr;
periodicTimer->stop();
delete periodicTimer;
//printf("Destroy NetPlay Status Window\n");
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::closeEvent(QCloseEvent *event)
{
//printf("NetPlay Host Close Window Event\n");
done(0);
deleteLater();
event->accept();
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::closeWindow(void)
{
//printf("Close Window\n");
done(0);
deleteLater();
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::dropPlayer(void)
{
auto* clientItem = static_cast<NetPlayClientTreeItem*>(clientTree->currentItem());
if (clientItem != nullptr)
{
clientItem->client->forceDisconnect();
}
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::resyncPlayer(void)
{
auto* clientItem = static_cast<NetPlayClientTreeItem*>(clientTree->currentItem());
if (clientItem != nullptr)
{
auto* server = NetPlayServer::GetInstance();
if ( (server != nullptr) && (clientItem->client != nullptr) )
{
server->resyncClient( clientItem->client );
}
}
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::resyncAllPlayers(void)
{
auto* server = NetPlayServer::GetInstance();
if (server != nullptr)
{
server->sendPauseAll();
server->resyncAllClients();
}
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::clientItemClicked( QTreeWidgetItem* item, int column)
{
auto* clientItem = static_cast<NetPlayClientTreeItem*>(item);
bool hasClient = clientItem->client != nullptr;
dropPlayerButton->setEnabled( hasClient );
resyncPlayerButton->setEnabled( hasClient );
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::onClientTreeContextMenu(const QPoint &pos)
{
QAction *act;
QMenu menu(this);
printf("Custom Menu (%i,%i)\n", pos.x(), pos.y());
act = new QAction(tr("Resync Client"), &menu);
//act->setShortcut( QKeySequence(tr("R")));
//connect( act, SIGNAL(triggered(void)), this, SLOT(openTileEditor(void)) );
menu.addAction( act );
act = new QAction(tr("Drop Client"), &menu);
//act->setShortcut( QKeySequence(tr("D")));
//connect( act, SIGNAL(triggered(void)), this, SLOT(openTileEditor(void)) );
menu.addAction( act );
menu.exec( clientTree->mapToGlobal(pos) );
}
//----------------------------------------------------------------------------
void NetPlayClientTreeItem::updateData()
{
const char *roleString = nullptr;
if (server != nullptr)
{
QString state;
roleString = NetPlayPlayerRoleToString( server->getRole() );
if (FCEUI_EmulationPaused())
{
state = QObject::tr("Paused");
}
else if (server->waitingOnClients())
{
state = QObject::tr("Waiting");
}
else
{
state = QObject::tr("Running");
}
setText( 0, QObject::tr("Host") );
setText( 1, QObject::tr(roleString) );
setText( 2, state);
setText( 3, QString::number(static_cast<uint32_t>(currFrameCounter)));
}
else if (client != nullptr)
{
QString state;
uint32_t currFrame = currFrameCounter;
bool hasInput = (client->readyFrame > client->currentFrame) &&
(client->readyFrame <= currFrame);
roleString = NetPlayPlayerRoleToString( client->role );
if (client->isPaused())
{
state = QObject::tr("Paused");
}
else if (!hasInput)
{
state = QObject::tr("Waiting");
}
else
{
state = QObject::tr("Running");
}
if (client->hasDesync())
{
state += QObject::tr(",Desync");
}
if (!client->romMatch)
{
state += QObject::tr(",ROM Mismatch");
}
setText( 0, client->userName );
setText( 1, QObject::tr(roleString) );
setText( 2, state);
setText( 3, QString::number(client->currentFrame) );
}
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::updatePeriodic()
{
for (int i=0; i<clientTree->topLevelItemCount(); i++)
{
NetPlayClientTreeItem *item = static_cast<NetPlayClientTreeItem*>( clientTree->topLevelItem(i) );
item->updateData();
}
}
//----------------------------------------------------------------------------
void NetPlayHostStatusDialog::loadClientTree()
{
auto* server = NetPlayServer::GetInstance();
//printf("Load Client Connection Tree\n");
clientTree->clear();
if (server == nullptr)
{
return;
}
auto* serverTopLvlItem = new NetPlayClientTreeItem();
serverTopLvlItem->server = server;
serverTopLvlItem->updateData();
clientTree->addTopLevelItem( serverTopLvlItem );
auto& clientList = server->getClientList();
for (auto& client : clientList)
{
auto* clientTopLvlItem = new NetPlayClientTreeItem();
clientTopLvlItem->client = client;
clientTopLvlItem->updateData();
clientTree->addTopLevelItem( clientTopLvlItem );
}
}
//----------------------------------------------------------------------------
//---- Global Functions
//----------------------------------------------------------------------------
bool NetPlayActive(void)
{
return (NetPlayClient::GetInstance() != nullptr) || (NetPlayServer::GetInstance() != nullptr);
}
//----------------------------------------------------------------------------
bool isNetPlayHost(void)
{
return (NetPlayServer::GetInstance() != nullptr);
}
//----------------------------------------------------------------------------
bool isNetPlayClient(void)
{
return (NetPlayClient::GetInstance() != nullptr);
}
//----------------------------------------------------------------------------
void NetPlayPeriodicUpdate(void)
{
NetPlayClient *client = NetPlayClient::GetInstance();
if (client)
{
client->update();
if ( client->shouldDestroy() )
{
NetPlayClient::Destroy();
client = nullptr;
}
}
NetPlayServer *server = NetPlayServer::GetInstance();
if (server)
{
server->update();
}
}
//----------------------------------------------------------------------------
static NetPlayFrameInput netPlayInputFrame;
//----------------------------------------------------------------------------
int NetPlayFrameWait(void)
{
int wait = 0;
NetPlayClient *client = NetPlayClient::GetInstance();
if (client)
{
wait = client->inputAvailable() == 0;
}
else
{
NetPlayServer *server = NetPlayServer::GetInstance();
if (server)
{
wait = server->inputAvailable() == 0;
}
}
return wait;
}
//----------------------------------------------------------------------------
bool NetPlaySkipWait(void)
{
bool skip = false;
NetPlayClient *client = NetPlayClient::GetInstance();
if (client)
{
skip = client->inputAvailableCount() > client->catchUpThreshold;
}
return skip;
}
//----------------------------------------------------------------------------
void NetPlayReadInputFrame(uint8_t* joy)
{
NetPlayClient *client = NetPlayClient::GetInstance();
if (client)
{
netPlayInputFrame = client->getNextInput();
}
else
{
NetPlayServer *server = NetPlayServer::GetInstance();
if (server)
{
NetPlayFrameInput frameInput;
netPlayInputFrame = server->getNextInput();
}
}
joy[0] = netPlayInputFrame.ctrl[0];
joy[1] = netPlayInputFrame.ctrl[1];
joy[2] = netPlayInputFrame.ctrl[2];
joy[3] = netPlayInputFrame.ctrl[3];
NetPlayOnFrameBegin();
}
//----------------------------------------------------------------------------
void NetPlayCloseSession(void)
{
NetPlayClient::Destroy();
NetPlayServer::Destroy();
}
//----------------------------------------------------------------------------
template <typename T> void openSingletonDialog(QWidget* parent)
{
T* win = T::GetInstance();
if (win == nullptr)
{
win = new T(parent);
win->show();
}
else
{
win->activateWindow();
win->raise();
}
}
//----------------------------------------------------------------------------
void openNetPlayHostDialog(QWidget* parent)
{
openSingletonDialog<NetPlayHostDialog>(parent);
}
//----------------------------------------------------------------------------
void openNetPlayJoinDialog(QWidget* parent)
{
openSingletonDialog<NetPlayJoinDialog>(parent);
}
//----------------------------------------------------------------------------
void openNetPlayHostStatusDialog(QWidget* parent)
{
openSingletonDialog<NetPlayHostStatusDialog>(parent);
}
//----------------------------------------------------------------------------
void openNetPlayClientStatusDialog(QWidget* parent)
{
openSingletonDialog<NetPlayClientStatusDialog>(parent);
}
//----------------------------------------------------------------------------
//---- Network Byte Swap Utilities
//----------------------------------------------------------------------------
#ifdef __BIG_ENDIAN__
#define NETPLAY_HOST_BE 1
#define NETPLAY_HOST_LE 0
#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define NETPLAY_HOST_BE 1
#define NETPLAY_HOST_LE 0
#else
#define NETPLAY_HOST_BE 0
#define NETPLAY_HOST_LE 1
#endif
//----------------------------------------------------------------------------
uint16_t netPlayByteSwap(uint16_t in)
{
uint16_t out = in;
#if NETPLAY_HOST_LE
unsigned char *in8 = (unsigned char*)&in;
unsigned char *out8 = (unsigned char*)&out;
out8[0] = in8[1];
out8[1] = in8[0];
#endif
return out;
}
//----------------------------------------------------------------------------
uint32_t netPlayByteSwap(uint32_t in)
{
uint32_t out = in;
#if NETPLAY_HOST_LE
unsigned char *in8 = (unsigned char*)&in;
unsigned char *out8 = (unsigned char*)&out;
out8[0] = in8[3];
out8[1] = in8[2];
out8[2] = in8[1];
out8[3] = in8[0];
#endif
return out;
}
//----------------------------------------------------------------------------
uint64_t netPlayByteSwap(uint64_t in)
{
uint64_t out = in;
#if NETPLAY_HOST_LE
unsigned char *in8 = (unsigned char*)&in;
unsigned char *out8 = (unsigned char*)&out;
out8[0] = in8[7];
out8[1] = in8[6];
out8[2] = in8[5];
out8[3] = in8[4];
out8[4] = in8[3];
out8[5] = in8[2];
out8[6] = in8[1];
out8[7] = in8[0];
#endif
return out;
}
//----------------------------------------------------------------------------
uint32_t netPlayCalcRamChkSum()
{
constexpr int ramSize = 0x800;
uint32_t crc = 0;
uint8_t ram[ramSize];
for (int i=0; i<ramSize; i++)
{
ram[i] = GetMem(i);
}
crc = CalcCRC32( crc, ram, sizeof(ram));
return crc;
}
//----------------------------------------------------------------------------
void NetPlayTraceInstruction(uint8_t *opcode, int size)
{
opsCrc32 = CalcCRC32( opsCrc32, opcode, size);
}
//----------------------------------------------------------------------------
void NetPlayOnFrameBegin()
{
NetPlayFrameData data;
data.frameNum = static_cast<uint32_t>(currFrameCounter);
data.opsCrc32 = opsCrc32;
data.ramCrc32 = netPlayCalcRamChkSum();
netPlayFrameData.push( data );
//printf("Frame: %u Ops:%08X Ram:%08X\n", data.frameNum, data.opsCrc32, data.ramCrc32 );
}
//----------------------------------------------------------------------------
bool NetPlayStateLoadReq(EMUFILE* is)
{
auto* client = NetPlayClient::GetInstance();
bool shouldLoad = (client == nullptr) || serverRequestedStateLoad;
printf("NetPlay Load State: %i\n", shouldLoad);
if ( (client != nullptr) && !serverRequestedStateLoad)
{ // Send state to host
client->requestStateLoad(is);
}
return !shouldLoad;
}
//----------------------------------------------------------------------------