Qt netplay fixes for processing input packet data. Don't attempt spawn dialog windows when in read loop, it will cause event loop re-entrancy issues.

This commit is contained in:
harry 2024-04-04 06:43:10 -04:00
parent 10418f551a
commit f71b912afb
3 changed files with 271 additions and 97 deletions

View File

@ -20,6 +20,7 @@
#include <QDir> #include <QDir>
#include <QMessageBox> #include <QMessageBox>
#include <QTemporaryFile>
#include "../../fceu.h" #include "../../fceu.h"
#include "../../cart.h" #include "../../cart.h"
@ -380,7 +381,7 @@ int NetPlayServer::sendStateSyncReq( NetPlayClient *client )
{ {
EMUFILE_MEMORY em; EMUFILE_MEMORY em;
int compressionLevel = 1; int compressionLevel = 1;
netPlayMsgHdr hdr(NETPLAY_SYNC_STATE_RESP); netPlayLoadStateResp resp;
if ( GameInfo == nullptr ) if ( GameInfo == nullptr )
{ {
@ -388,17 +389,17 @@ int NetPlayServer::sendStateSyncReq( NetPlayClient *client )
} }
FCEUSS_SaveMS( &em, compressionLevel ); FCEUSS_SaveMS( &em, compressionLevel );
hdr.msgSize += em.size(); resp.hdr.msgSize += em.size();
resp.stateSize = em.size();
resp.opsCrc32 = opsCrc32;
printf("Sending ROM Sync Request\n"); printf("Sending ROM Sync Request: %zu\n", em.size());
FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED);
sendMsg( client, &hdr, sizeof(netPlayMsgHdr), [&hdr]{ hdr.toNetworkByteOrder(); } ); sendMsg( client, &resp, sizeof(netPlayLoadStateResp), [&resp]{ resp.toNetworkByteOrder(); } );
sendMsg( client, em.buf(), em.size() ); sendMsg( client, em.buf(), em.size() );
client->flushData(); client->flushData();
opsCrc32 = 0;
inputClear();
return 0; return 0;
} }
@ -434,6 +435,10 @@ bool NetPlayServer::claimRole(NetPlayClient* client, int _role)
client->role = _role; client->role = _role;
} }
} }
else
{
client->role = NETPLAY_SPECTATOR;
}
return success; return success;
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -712,89 +717,59 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s
netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(msgBuf); netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(msgBuf);
msg->toHostByteOrder(); msg->toHostByteOrder();
bool acceptRomLoadReq = false;
if (allowClientRomLoadReq) if (allowClientRomLoadReq)
{ {
QString msgBoxTxt = tr("Client '") + client->userName + tr("' has requested to load this ROM:\n\n"); if (client->romLoadData.buf != nullptr)
msgBoxTxt += tr(msg->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; ::free(client->romLoadData.buf);
client->romLoadData.buf = nullptr;
} }
} client->romLoadData.size = 0;
client->romLoadData.fileName.clear();
if (acceptRomLoadReq) const uint32_t fileSize = msg->fileSize;
{ constexpr uint32_t maxRomDataSize = 1024 * 1024;
FILE *fp;
std::string filepath = QDir::tempPath().toLocal8Bit().constData();
const char *romData = &static_cast<const char*>(msgBuf)[ sizeof(netPlayLoadRomReq) ]; const char *romData = &static_cast<const char*>(msgBuf)[ sizeof(netPlayLoadRomReq) ];
filepath.append( "/" ); if ( (fileSize >= 16) && (fileSize < maxRomDataSize) )
filepath.append( msg->fileName );
printf("Load ROM Request Received: %s\n", filepath.c_str());
//printf("Dumping Temp Rom to: %s\n", filepath.c_str());
fp = ::fopen( filepath.c_str(), "wb");
if (fp == nullptr)
{ {
return; 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)) );
} }
::fwrite( romData, 1, msgSize, fp );
::fclose(fp);
FCEU_WRAPPER_LOCK();
LoadGame( filepath.c_str(), true, true );
FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED);
FCEU_WRAPPER_UNLOCK();
resyncAllClients();
}
else
{
netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG);
errorMsg.setFlag(netPlayTextMsgFlags::Warning);
errorMsg.printf("Host is rejected ROMs load request");
sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } );
} }
} }
break; break;
case NETPLAY_SYNC_STATE_RESP: case NETPLAY_SYNC_STATE_RESP:
{ {
bool acceptStateLoadReq = false; netPlayLoadStateResp* msg = static_cast<netPlayLoadStateResp*>(msgBuf);
msg->toHostByteOrder();
FCEU_printf("Sync state request received from client '%s'\n", client->userName.toLocal8Bit().constData()); FCEU_printf("Sync state request received from client '%s'\n", client->userName.toLocal8Bit().constData());
if (allowClientStateLoadReq) if (allowClientStateLoadReq)
{ {
QString msgBoxTxt = tr("Client '") + client->userName + tr("' has requested to load a new state:\n"); const char *stateData = msg->stateDataBuf();
msgBoxTxt += tr("\nDo you want to load it?"); const uint32_t stateDataSize = msg->stateDataSize();
int ans = QMessageBox::question( consoleWindow, tr("Client State Load Request"), msgBoxTxt, QMessageBox::Yes | QMessageBox::No ); constexpr uint32_t maxStateDataSize = 1024*1024;
if (ans == QMessageBox::Yes) if (client->stateLoadData.buf != nullptr)
{ {
acceptStateLoadReq = true; ::free(client->stateLoadData.buf);
client->stateLoadData.buf = nullptr;
} }
} client->stateLoadData.size = 0;
if (acceptStateLoadReq) if ( (stateDataSize >= 16) && (stateDataSize < maxStateDataSize) )
{ {
char *stateData = &static_cast<char*>(msgBuf)[ sizeof(netPlayMsgHdr) ]; client->stateLoadData.size = stateDataSize;
client->stateLoadData.buf = static_cast<char*>(::malloc(stateDataSize));
FCEU_printf("Sync state request accepted\n"); ::memcpy( client->stateLoadData.buf, stateData, stateDataSize );
QTimer::singleShot( 100, this, SLOT(processClientStateLoadRequests(void)) );
EMUFILE_MEMORY em( stateData, msgSize ); }
FCEU_WRAPPER_LOCK();
serverRequestedStateLoad = true;
// Clients will be resync'd during this load call.
FCEUSS_LoadFP( &em, SSLOADPARAM_NOBACKUP );
serverRequestedStateLoad = false;
FCEU_WRAPPER_UNLOCK();
} }
} }
break; break;
@ -812,6 +787,109 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
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();
const char *romData = client->romLoadData.buf;
const size_t romSize = client->romLoadData.size;
filepath.append( "/" );
filepath.append( client->romLoadData.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();
resyncAllClients();
}
else
{
netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG);
errorMsg.setFlag(netPlayTextMsgFlags::Warning);
errorMsg.printf("Host is rejected ROMs 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) void NetPlayServer::update(void)
{ {
bool hostRdyFrame = false; bool hostRdyFrame = false;
@ -1024,6 +1102,21 @@ NetPlayClient::~NetPlayClient(void)
instance = nullptr; 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) if (sock != nullptr)
{ {
sock->close(); sock->close();
@ -1261,10 +1354,12 @@ int NetPlayClient::requestStateLoad(EMUFILE *is)
{ {
size_t dataSize; size_t dataSize;
char *dataBuf; char *dataBuf;
netPlayMsgHdr hdr(NETPLAY_SYNC_STATE_RESP); static constexpr size_t maxBytesPerWrite = 16 * 1024;
netPlayLoadStateResp resp;
dataSize = is->size(); dataSize = is->size();
hdr.msgSize += dataSize; resp.hdr.msgSize += dataSize;
resp.stateSize = dataSize;
if (dataSize == 0) if (dataSize == 0)
{ {
@ -1284,11 +1379,26 @@ int NetPlayClient::requestStateLoad(EMUFILE *is)
{ {
printf("Read Error\n"); printf("Read Error\n");
} }
printf("Sending Client ROM Sync Request\n"); printf("Sending Client ROM Sync Request: %u\n", resp.stateSize);
hdr.toNetworkByteOrder(); resp.toNetworkByteOrder();
sock->write( reinterpret_cast<const char*>(&hdr), sizeof(netPlayMsgHdr)); sock->write( reinterpret_cast<const char*>(&resp), sizeof(netPlayLoadStateResp));
sock->write( reinterpret_cast<const char*>(dataBuf), dataSize );
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); ::free(dataBuf);
@ -1377,6 +1487,13 @@ void NetPlayClient::update(void)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData ) int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData )
{ {
if (readMessageProcessing)
{
printf("Read Message is Processing in callstack, don't allow re-entrantancy.\n");
return 0;
}
readMessageProcessing = true;
if (sock) if (sock)
{ {
bool readReq; bool readReq;
@ -1461,6 +1578,7 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB
} }
} }
} }
readMessageProcessing = false;
return 0; return 0;
} }
@ -1517,29 +1635,22 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
break; break;
case NETPLAY_LOAD_ROM_REQ: case NETPLAY_LOAD_ROM_REQ:
{ {
FILE *fp; QTemporaryFile tmpFile;
std::string filepath = QDir::tempPath().toLocal8Bit().constData();
netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(msgBuf); netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(msgBuf);
msg->toHostByteOrder(); msg->toHostByteOrder();
const char *romData = &static_cast<const char*>(msgBuf)[ sizeof(netPlayLoadRomReq) ]; const char *romData = &static_cast<const char*>(msgBuf)[ sizeof(netPlayLoadRomReq) ];
filepath.append( "/" ); FCEU_printf("Load ROM Request Received: %s\n", msg->fileName);
filepath.append( msg->fileName );
FCEU_printf("Load ROM Request Received: %s\n", filepath.c_str()); tmpFile.setFileTemplate(QString("tmpRomXXXXXX.nes"));
tmpFile.open();
//printf("Dumping Temp Rom to: %s\n", filepath.c_str()); QString filepath = tmpFile.fileName();
fp = ::fopen( filepath.c_str(), "wb"); printf("Dumping Temp Rom to: %s\n", tmpFile.fileName().toLocal8Bit().constData());
tmpFile.write( romData, msgSize );
if (fp == nullptr) tmpFile.close();
{
return;
}
::fwrite( romData, 1, msgSize, fp );
::fclose(fp);
FCEU_WRAPPER_LOCK(); FCEU_WRAPPER_LOCK();
LoadGame( filepath.c_str(), true, true ); LoadGame( filepath.toLocal8Bit().constData(), true, true );
FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED);
FCEU_WRAPPER_UNLOCK(); FCEU_WRAPPER_UNLOCK();
} }
@ -1553,11 +1664,15 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
break; break;
case NETPLAY_SYNC_STATE_RESP: case NETPLAY_SYNC_STATE_RESP:
{ {
char *stateData = &static_cast<char*>(msgBuf)[ sizeof(netPlayMsgHdr) ]; netPlayLoadStateResp* msg = static_cast<netPlayLoadStateResp*>(msgBuf);
msg->toHostByteOrder();
FCEU_printf("Sync state Request Received\n"); char *stateData = msg->stateDataBuf();
const uint32_t stateDataSize = msg->stateDataSize();
EMUFILE_MEMORY em( stateData, msgSize ); FCEU_printf("Sync state Request Received: %u\n", stateDataSize);
EMUFILE_MEMORY em( stateData, stateDataSize );
FCEU_WRAPPER_LOCK(); FCEU_WRAPPER_LOCK();
serverRequestedStateLoad = true; serverRequestedStateLoad = true;
@ -1565,7 +1680,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
serverRequestedStateLoad = false; serverRequestedStateLoad = false;
FCEU_WRAPPER_UNLOCK(); FCEU_WRAPPER_UNLOCK();
opsCrc32 = 0; opsCrc32 = msg->opsCrc32;
netPlayFrameData.reset(); netPlayFrameData.reset();
inputClear(); inputClear();
} }

View File

@ -181,6 +181,8 @@ class NetPlayServer : public QTcpServer
void onRomUnload(void); void onRomUnload(void);
void onStateLoad(void); void onStateLoad(void);
void onNesReset(void); void onNesReset(void);
void processClientRomLoadRequests(void);
void processClientStateLoadRequests(void);
}; };
class NetPlayClient : public QObject class NetPlayClient : public QObject
@ -286,6 +288,24 @@ class NetPlayClient : public QObject
unsigned int tailTarget = 3; unsigned int tailTarget = 3;
uint8_t gpData[4]; uint8_t gpData[4];
struct RomLoadReqData
{
char* buf = nullptr;
size_t size = 0;
QString fileName;
bool pending(){ return buf != nullptr; }
} romLoadData;
struct StateLoadReqData
{
char* buf = nullptr;
size_t size = 0;
bool pending(){ return buf != nullptr; }
} stateLoadData;
private: private:
static NetPlayClient *instance; static NetPlayClient *instance;
@ -301,6 +321,7 @@ class NetPlayClient : public QObject
bool _connected = false; bool _connected = false;
bool paused = false; bool paused = false;
bool desync = false; bool desync = false;
bool readMessageProcessing = false;
uint64_t pingDelaySum = 0; uint64_t pingDelaySum = 0;
uint64_t pingDelayLast = 0; uint64_t pingDelayLast = 0;

View File

@ -17,17 +17,17 @@ enum netPlayMsgType
{ {
NETPLAY_AUTH_REQ = 0, NETPLAY_AUTH_REQ = 0,
NETPLAY_AUTH_RESP, NETPLAY_AUTH_RESP,
NETPLAY_LOAD_ROM_REQ = 100, NETPLAY_LOAD_ROM_REQ = 10,
NETPLAY_UNLOAD_ROM_REQ, NETPLAY_UNLOAD_ROM_REQ,
NETPLAY_SYNC_STATE_REQ = 200, NETPLAY_SYNC_STATE_REQ = 20,
NETPLAY_SYNC_STATE_RESP, NETPLAY_SYNC_STATE_RESP,
NETPLAY_RUN_FRAME_REQ = 300, NETPLAY_RUN_FRAME_REQ = 30,
NETPLAY_CLIENT_STATE = 400, NETPLAY_CLIENT_STATE = 40,
NETPLAY_CLIENT_SYNC_REQ, NETPLAY_CLIENT_SYNC_REQ,
NETPLAY_INFO_MSG = 500, NETPLAY_INFO_MSG = 50,
NETPLAY_ERROR_MSG, NETPLAY_ERROR_MSG,
NETPLAY_CHAT_MSG, NETPLAY_CHAT_MSG,
NETPLAY_PING_REQ = 1000, NETPLAY_PING_REQ = 100,
NETPLAY_PING_RESP, NETPLAY_PING_RESP,
}; };
@ -256,6 +256,44 @@ struct netPlayLoadRomReq
} }
}; };
struct netPlayLoadStateResp
{
netPlayMsgHdr hdr;
uint32_t stateSize;
uint32_t opsCrc32;
netPlayLoadStateResp(void)
: hdr(NETPLAY_SYNC_STATE_RESP, sizeof(netPlayLoadStateResp)), stateSize(0), opsCrc32(0)
{
}
void toHostByteOrder()
{
hdr.toHostByteOrder();
stateSize = netPlayByteSwap(stateSize);
opsCrc32 = netPlayByteSwap(opsCrc32);
}
void toNetworkByteOrder()
{
hdr.toNetworkByteOrder();
stateSize = netPlayByteSwap(stateSize);
opsCrc32 = netPlayByteSwap(opsCrc32);
}
char* stateDataBuf()
{
uintptr_t buf = ((uintptr_t)this) + sizeof(netPlayLoadStateResp);
return (char*)buf;
}
const uint32_t stateDataSize()
{
return stateSize;
}
};
struct netPlayRunFrameReq struct netPlayRunFrameReq
{ {