From f71b912afb2cdec09281dbc24001b9e1f58d56a9 Mon Sep 17 00:00:00 2001 From: harry Date: Thu, 4 Apr 2024 06:43:10 -0400 Subject: [PATCH] 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. --- src/drivers/Qt/NetPlay.cpp | 297 +++++++++++++++++++++++---------- src/drivers/Qt/NetPlay.h | 21 +++ src/drivers/Qt/NetPlayMsgDef.h | 50 +++++- 3 files changed, 271 insertions(+), 97 deletions(-) diff --git a/src/drivers/Qt/NetPlay.cpp b/src/drivers/Qt/NetPlay.cpp index e9e3d387..970c8b4e 100644 --- a/src/drivers/Qt/NetPlay.cpp +++ b/src/drivers/Qt/NetPlay.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "../../fceu.h" #include "../../cart.h" @@ -380,7 +381,7 @@ int NetPlayServer::sendStateSyncReq( NetPlayClient *client ) { EMUFILE_MEMORY em; int compressionLevel = 1; - netPlayMsgHdr hdr(NETPLAY_SYNC_STATE_RESP); + netPlayLoadStateResp resp; if ( GameInfo == nullptr ) { @@ -388,17 +389,17 @@ int NetPlayServer::sendStateSyncReq( NetPlayClient *client ) } 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); - sendMsg( client, &hdr, sizeof(netPlayMsgHdr), [&hdr]{ hdr.toNetworkByteOrder(); } ); + sendMsg( client, &resp, sizeof(netPlayLoadStateResp), [&resp]{ resp.toNetworkByteOrder(); } ); sendMsg( client, em.buf(), em.size() ); client->flushData(); - opsCrc32 = 0; - inputClear(); return 0; } @@ -434,6 +435,10 @@ bool NetPlayServer::claimRole(NetPlayClient* client, int _role) client->role = _role; } } + else + { + client->role = NETPLAY_SPECTATOR; + } return success; } //----------------------------------------------------------------------------- @@ -712,89 +717,59 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s netPlayLoadRomReq *msg = static_cast(msgBuf); msg->toHostByteOrder(); - bool acceptRomLoadReq = false; - if (allowClientRomLoadReq) { - QString msgBoxTxt = tr("Client '") + client->userName + tr("' has requested to load this ROM:\n\n"); - 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) + if (client->romLoadData.buf != nullptr) { - acceptRomLoadReq = true; + ::free(client->romLoadData.buf); + client->romLoadData.buf = nullptr; } - } + client->romLoadData.size = 0; + client->romLoadData.fileName.clear(); - if (acceptRomLoadReq) - { - FILE *fp; - std::string filepath = QDir::tempPath().toLocal8Bit().constData(); + const uint32_t fileSize = msg->fileSize; + constexpr uint32_t maxRomDataSize = 1024 * 1024; const char *romData = &static_cast(msgBuf)[ sizeof(netPlayLoadRomReq) ]; - filepath.append( "/" ); - 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) + if ( (fileSize >= 16) && (fileSize < maxRomDataSize) ) { - return; + client->romLoadData.fileName = msg->fileName; + client->romLoadData.size = fileSize; + client->romLoadData.buf = static_cast(::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; case NETPLAY_SYNC_STATE_RESP: { - bool acceptStateLoadReq = false; + netPlayLoadStateResp* msg = static_cast(msgBuf); + msg->toHostByteOrder(); FCEU_printf("Sync state request received from client '%s'\n", client->userName.toLocal8Bit().constData()); 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 ); + const char *stateData = msg->stateDataBuf(); + const uint32_t stateDataSize = msg->stateDataSize(); + 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) - { - char *stateData = &static_cast(msgBuf)[ sizeof(netPlayMsgHdr) ]; - - FCEU_printf("Sync state request accepted\n"); - - 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(); + if ( (stateDataSize >= 16) && (stateDataSize < maxStateDataSize) ) + { + client->stateLoadData.size = stateDataSize; + client->stateLoadData.buf = static_cast(::malloc(stateDataSize)); + ::memcpy( client->stateLoadData.buf, stateData, stateDataSize ); + QTimer::singleShot( 100, this, SLOT(processClientStateLoadRequests(void)) ); + } } } 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) { bool hostRdyFrame = false; @@ -1024,6 +1102,21 @@ NetPlayClient::~NetPlayClient(void) 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(); @@ -1261,10 +1354,12 @@ int NetPlayClient::requestStateLoad(EMUFILE *is) { size_t dataSize; char *dataBuf; - netPlayMsgHdr hdr(NETPLAY_SYNC_STATE_RESP); + static constexpr size_t maxBytesPerWrite = 16 * 1024; + netPlayLoadStateResp resp; dataSize = is->size(); - hdr.msgSize += dataSize; + resp.hdr.msgSize += dataSize; + resp.stateSize = dataSize; if (dataSize == 0) { @@ -1284,11 +1379,26 @@ int NetPlayClient::requestStateLoad(EMUFILE *is) { printf("Read Error\n"); } - printf("Sending Client ROM Sync Request\n"); + printf("Sending Client ROM Sync Request: %u\n", resp.stateSize); - hdr.toNetworkByteOrder(); - sock->write( reinterpret_cast(&hdr), sizeof(netPlayMsgHdr)); - sock->write( reinterpret_cast(dataBuf), dataSize ); + resp.toNetworkByteOrder(); + sock->write( reinterpret_cast(&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); @@ -1377,6 +1487,13 @@ void NetPlayClient::update(void) //----------------------------------------------------------------------------- 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) { bool readReq; @@ -1461,6 +1578,7 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB } } } + readMessageProcessing = false; return 0; } @@ -1517,29 +1635,22 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) break; case NETPLAY_LOAD_ROM_REQ: { - FILE *fp; - std::string filepath = QDir::tempPath().toLocal8Bit().constData(); + QTemporaryFile tmpFile; netPlayLoadRomReq *msg = static_cast(msgBuf); msg->toHostByteOrder(); const char *romData = &static_cast(msgBuf)[ sizeof(netPlayLoadRomReq) ]; - filepath.append( "/" ); - filepath.append( msg->fileName ); + FCEU_printf("Load ROM Request Received: %s\n", msg->fileName); - FCEU_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; - } - ::fwrite( romData, 1, msgSize, fp ); - ::fclose(fp); + tmpFile.setFileTemplate(QString("tmpRomXXXXXX.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.c_str(), true, true ); + LoadGame( filepath.toLocal8Bit().constData(), true, true ); FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); FCEU_WRAPPER_UNLOCK(); } @@ -1553,11 +1664,15 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) break; case NETPLAY_SYNC_STATE_RESP: { - char *stateData = &static_cast(msgBuf)[ sizeof(netPlayMsgHdr) ]; + netPlayLoadStateResp* msg = static_cast(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(); serverRequestedStateLoad = true; @@ -1565,7 +1680,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) serverRequestedStateLoad = false; FCEU_WRAPPER_UNLOCK(); - opsCrc32 = 0; + opsCrc32 = msg->opsCrc32; netPlayFrameData.reset(); inputClear(); } diff --git a/src/drivers/Qt/NetPlay.h b/src/drivers/Qt/NetPlay.h index 3ed9390a..67c02a2d 100644 --- a/src/drivers/Qt/NetPlay.h +++ b/src/drivers/Qt/NetPlay.h @@ -181,6 +181,8 @@ class NetPlayServer : public QTcpServer void onRomUnload(void); void onStateLoad(void); void onNesReset(void); + void processClientRomLoadRequests(void); + void processClientStateLoadRequests(void); }; class NetPlayClient : public QObject @@ -286,6 +288,24 @@ class NetPlayClient : public QObject unsigned int tailTarget = 3; 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: static NetPlayClient *instance; @@ -301,6 +321,7 @@ class NetPlayClient : public QObject bool _connected = false; bool paused = false; bool desync = false; + bool readMessageProcessing = false; uint64_t pingDelaySum = 0; uint64_t pingDelayLast = 0; diff --git a/src/drivers/Qt/NetPlayMsgDef.h b/src/drivers/Qt/NetPlayMsgDef.h index b5be2314..f7818426 100644 --- a/src/drivers/Qt/NetPlayMsgDef.h +++ b/src/drivers/Qt/NetPlayMsgDef.h @@ -17,17 +17,17 @@ enum netPlayMsgType { NETPLAY_AUTH_REQ = 0, NETPLAY_AUTH_RESP, - NETPLAY_LOAD_ROM_REQ = 100, + NETPLAY_LOAD_ROM_REQ = 10, NETPLAY_UNLOAD_ROM_REQ, - NETPLAY_SYNC_STATE_REQ = 200, + NETPLAY_SYNC_STATE_REQ = 20, NETPLAY_SYNC_STATE_RESP, - NETPLAY_RUN_FRAME_REQ = 300, - NETPLAY_CLIENT_STATE = 400, + NETPLAY_RUN_FRAME_REQ = 30, + NETPLAY_CLIENT_STATE = 40, NETPLAY_CLIENT_SYNC_REQ, - NETPLAY_INFO_MSG = 500, + NETPLAY_INFO_MSG = 50, NETPLAY_ERROR_MSG, NETPLAY_CHAT_MSG, - NETPLAY_PING_REQ = 1000, + NETPLAY_PING_REQ = 100, 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 {