diff --git a/src/drivers/Qt/NetPlay.cpp b/src/drivers/Qt/NetPlay.cpp index b77d009e..19848652 100644 --- a/src/drivers/Qt/NetPlay.cpp +++ b/src/drivers/Qt/NetPlay.cpp @@ -416,38 +416,44 @@ void NetPlayServer::releaseRole(NetPlayClient* client) void NetPlayServer::onRomLoad() { //printf("New ROM Loaded!\n"); - + FCEU_WRAPPER_LOCK(); // 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::onNesReset() { //printf("New ROM Loaded!\n"); - + FCEU_WRAPPER_LOCK(); // NES Reset has occurred on server, signal clients sync for (auto& client : clientList ) { sendStateSyncReq( client ); } + FCEU_WRAPPER_UNLOCK(); } //----------------------------------------------------------------------------- void NetPlayServer::resyncClient( NetPlayClient *client ) { + FCEU_WRAPPER_LOCK(); sendRomLoadReq( client ); sendStateSyncReq( client ); + FCEU_WRAPPER_UNLOCK(); } //----------------------------------------------------------------------------- void NetPlayServer::resyncAllClients() { + FCEU_WRAPPER_LOCK(); for (auto& client : clientList ) { resyncClient( client ); } + FCEU_WRAPPER_UNLOCK(); } //----------------------------------------------------------------------------- static void serverMessageCallback( void *userData, void *msgBuf, size_t msgSize ) @@ -484,8 +490,8 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s if (!authentication_passed) { - netPlayErrorMsg<128> errorMsg; - errorMsg.setDisconnectFlag(); + netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG); + errorMsg.setFlag(netPlayTextMsgFlags::DISCONNECT); errorMsg.printf("Invalid Password"); sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } ); client->flushData(); @@ -497,15 +503,17 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s if ( claimRole(client, msg->playerId) ) { client->userName = msg->userName; + FCEU_WRAPPER_LOCK(); sendRomLoadReq( client ); sendStateSyncReq( client ); + FCEU_WRAPPER_UNLOCK(); client->state = 1; FCEU_DispMessage("%s Joined",0, client->userName.toLocal8Bit().constData()); } else { - netPlayErrorMsg<128> errorMsg; - errorMsg.setDisconnectFlag(); + netPlayTextMsg<128> errorMsg(NETPLAY_ERROR_MSG); + errorMsg.setFlag(netPlayTextMsgFlags::DISCONNECT); errorMsg.printf("Player %i role is not available", msg->playerId+1); sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } ); client->flushData(); @@ -519,6 +527,7 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s 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]; @@ -546,7 +555,9 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s if (client->desyncCount > forceResyncCount) { + FCEU_WRAPPER_LOCK(); sendStateSyncReq( client ); + FCEU_WRAPPER_UNLOCK(); client->desyncCount = 0; } @@ -582,6 +593,60 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s //printf("Ping Latency ms: %llu Avg:%f\n", static_cast(diff), client->getAvgPingDelay()); } break; + case NETPLAY_LOAD_ROM_REQ: + { + 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) + { + acceptRomLoadReq = true; + } + } + + if (acceptRomLoadReq) + { + FILE *fp; + std::string filepath = QDir::tempPath().toLocal8Bit().constData(); + 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(), "w"); + + if (fp == nullptr) + { + return; + } + ::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.printf("Host is rejected ROMs load request"); + sendMsg( client, &errorMsg, errorMsg.hdr.msgSize, [&errorMsg]{ errorMsg.toNetworkByteOrder(); } ); + } + } default: printf("Unknown Msg: %08X\n", msgId); break; @@ -591,6 +656,7 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s //----------------------------------------------------------------------------- void NetPlayServer::update(void) { + bool hostRdyFrame = false; bool shouldRunFrame = false; unsigned int clientMinFrame = 0xFFFFFFFF; unsigned int clientMaxFrame = 0; @@ -669,11 +735,22 @@ void NetPlayServer::update(void) } } + hostRdyFrame = ( (currFrame > lastFrame) || (lastFrame == 0) ); + shouldRunFrame = (clientMinFrame != 0xFFFFFFFF) && (clientMinFrame >= lagFrame ) && (clientMaxFrame < leadFrame) && - ( (currFrame > lastFrame) || (lastFrame == 0) ) && - (numClientsPaused == 0); + (numClientsPaused == 0) && + hostRdyFrame; + + if (hostRdyFrame && !shouldRunFrame) + { + clientWaitCounter++; + } + else + { + clientWaitCounter = 0; + } //printf("Client Frame: Min:%u Max:%u\n", clientMinFrame, clientMaxFrame); @@ -914,6 +991,48 @@ void NetPlayClient::onSocketError(QAbstractSocket::SocketError error) emit errorOccurred(errorMsg); } //----------------------------------------------------------------------------- +int NetPlayClient::requestRomLoad( const char *romPath ) +{ + constexpr size_t BufferSize = 64 * 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, "r"); + + 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 ); + FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); + + msg.toNetworkByteOrder(); + sock->write( reinterpret_cast(&msg), sizeof(netPlayLoadRomReq) ); + + while ( (bytesRead = fread( buf, 1, sizeof(buf), fp )) > 0 ) + { + sock->write( buf, bytesRead ); + } + + ::fclose(fp); + + return 0; +} +//----------------------------------------------------------------------------- void NetPlayClient::recordPingResult( uint64_t delay_ms ) { pingNumSamples++; @@ -1069,15 +1188,15 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) { case NETPLAY_ERROR_MSG: { - auto *msg = static_cast*>(msgBuf); + auto *msg = static_cast*>(msgBuf); msg->toHostByteOrder(); - printf("Error: 0x%X %s\n", msg->code, msg->getBuffer()); + FCEU_printf("NetPlay Error: 0x%X %s\n", msg->code, msg->getBuffer()); - if (msg->isDisconnectFlagSet()) + if (msg->isFlagSet(netPlayTextMsgFlags::DISCONNECT)) { sock->disconnectFromHost(); } - FCEU_DispMessage("Host connect failed",0); + FCEU_DispMessage("NetPlay Errors... check message log",0); } break; case NETPLAY_AUTH_REQ: @@ -1087,7 +1206,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) Strlcpy( msg.userName, userName.toLocal8Bit().constData(), sizeof(msg.userName)); Strlcpy( msg.pswd, password.toLocal8Bit().constData(), sizeof(msg.pswd) ); - printf("Authentication Request Received\n"); + FCEU_printf("Authentication Request Received\n"); msg.toNetworkByteOrder(); sock->write( (const char*)&msg, sizeof(netPlayAuthResp) ); } @@ -1095,7 +1214,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) case NETPLAY_LOAD_ROM_REQ: { FILE *fp; - std::string filepath = QDir::tempPath().toStdString(); + std::string filepath = QDir::tempPath().toLocal8Bit().constData(); netPlayLoadRomReq *msg = static_cast(msgBuf); msg->toHostByteOrder(); const char *romData = &static_cast(msgBuf)[ sizeof(netPlayLoadRomReq) ]; @@ -1103,7 +1222,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) filepath.append( "/" ); filepath.append( msg->fileName ); - printf("Load ROM Request Received: %s\n", filepath.c_str()); + 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(), "w"); @@ -1116,7 +1235,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) ::fclose(fp); FCEU_WRAPPER_LOCK(); - LoadGame( filepath.c_str(), true ); + LoadGame( filepath.c_str(), true, true ); FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); FCEU_WRAPPER_UNLOCK(); } @@ -1125,7 +1244,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) { char *stateData = &static_cast(msgBuf)[ sizeof(netPlayMsgHdr) ]; - printf("Sync state Request Received\n"); + FCEU_printf("Sync state Request Received\n"); EMUFILE_MEMORY em( stateData, msgSize ); @@ -1528,8 +1647,8 @@ NetPlayHostStatusDialog::NetPlayHostStatusDialog(QWidget *parent) auto* item = new QTreeWidgetItem(); item->setText( 0, QString::fromStdString( "Player" ) ); item->setText( 1, QString::fromStdString( "Role" ) ); - item->setText( 2, QString::fromStdString( "Frame" ) ); - item->setText( 3, QString::fromStdString( "State" ) ); + 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); @@ -1665,37 +1784,54 @@ void NetPlayClientTreeItem::updateData() if (FCEUI_EmulationPaused()) { - state += QObject::tr("Paused"); + 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, QString::number(static_cast(currFrameCounter))); - setText( 3, state); + setText( 2, state); + setText( 3, QString::number(static_cast(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"); + state = QObject::tr("Paused"); + } + else if (!hasInput) + { + state = QObject::tr("Waiting"); + } + else + { + state = QObject::tr("Running"); } if (client->hasDesync()) { - if (!state.isEmpty()) - { - state += QObject::tr(","); - } - state += QObject::tr("Desync"); + state += QObject::tr(",Desync"); } setText( 0, client->userName ); setText( 1, QObject::tr(roleString) ); - setText( 2, QString::number(client->currentFrame) ); - setText( 3, state); + setText( 2, state); + setText( 3, QString::number(client->currentFrame) ); } } //---------------------------------------------------------------------------- diff --git a/src/drivers/Qt/NetPlay.h b/src/drivers/Qt/NetPlay.h index 7888a7cc..b96fce17 100644 --- a/src/drivers/Qt/NetPlay.h +++ b/src/drivers/Qt/NetPlay.h @@ -135,6 +135,7 @@ class NetPlayServer : public QTcpServer int getRole(void){ return role; } bool claimRole(NetPlayClient* client, int _role); void releaseRole(NetPlayClient* client); + bool waitingOnClients(){ return clientWaitCounter > 3; } uint32_t getMaxLeadFrames(){ return maxLeadFrames; } void setMaxLeadFrames(uint32_t value){ maxLeadFrames = value; } @@ -159,6 +160,8 @@ class NetPlayServer : public QTcpServer int forceResyncCount = 10; uint32_t cycleCounter = 0; uint32_t maxLeadFrames = 10u; + uint32_t clientWaitCounter = 0; + bool allowClientRomLoadReq = true; public: signals: @@ -190,6 +193,7 @@ class NetPlayClient : public QObject bool disconnectRequested(){ return disconnectPending; } void forceDisconnect(); bool flushData(); + int requestRomLoad( const char *romPath ); QTcpSocket* createSocket(void); void setSocket(QTcpSocket *s); @@ -259,6 +263,7 @@ class NetPlayClient : public QObject int desyncCount = 0; bool syncOk = false; unsigned int currentFrame = 0; + unsigned int readyFrame = 0; uint8_t gpData[4]; private: diff --git a/src/drivers/Qt/NetPlayMsgDef.h b/src/drivers/Qt/NetPlayMsgDef.h index 6d02c905..15dad037 100644 --- a/src/drivers/Qt/NetPlayMsgDef.h +++ b/src/drivers/Qt/NetPlayMsgDef.h @@ -21,7 +21,9 @@ enum netPlayMsgType NETPLAY_SYNC_STATE_RESP, NETPLAY_RUN_FRAME_REQ, NETPLAY_CLIENT_STATE, + NETPLAY_INFO_MSG, NETPLAY_ERROR_MSG, + NETPLAY_CHAT_MSG, NETPLAY_PING_REQ, NETPLAY_PING_RESP, }; @@ -111,32 +113,37 @@ struct netPlayAuthResp } }; +struct netPlayTextMsgFlags +{ + static const uint32_t DISCONNECT = 0x00000001; + static const uint32_t WARNING = 0x00000002; + static const uint32_t INFO = 0x00000004; +}; + template -struct netPlayErrorMsg +struct netPlayTextMsg { netPlayMsgHdr hdr; unsigned short code; unsigned short flags; - char data[N]; + unsigned short dataSize; - static const uint32_t DISCONNECT_FLAG = 0x00000001; - - netPlayErrorMsg(void) - : hdr(NETPLAY_ERROR_MSG, sizeof(netPlayErrorMsg)), code(0), flags(0) + netPlayTextMsg(int type) + : hdr(type, sizeof(netPlayTextMsg)), code(0), flags(0), dataSize(0) { hdr.msgSize = sizeof(*this) - N + 1; memset(data, 0, N); } - void setDisconnectFlag() + void setFlag(uint32_t flag) { - flags |= DISCONNECT_FLAG; + flags |= flag; } - bool isDisconnectFlagSet() + bool isFlagSet(uint32_t flag) { - return (flags & DISCONNECT_FLAG) ? true : false; + return (flags & flag) ? true : false; } const char *getBuffer() @@ -149,19 +156,55 @@ struct netPlayErrorMsg int retval; va_list args; va_start(args, format); - retval = ::vsnprintf(data, sizeof(data), format, args); + retval = ::vsnprintf(data, N, format, args); va_end(args); - hdr.msgSize = sizeof(*this) - N + strlen(data) + 1; + if (retval > static_cast(N-1)) + { + retval = static_cast(N-1); + } + dataSize = retval; + + hdr.msgSize = sizeof(*this) - N + retval + 1; return retval; } + void assign(const char *msg) + { + int i=0; + + while ( (i < (N-1)) && (msg[i] != 0) ) + { + data[i] = msg[i]; i++; + } + + data[i] = 0; + dataSize = i; + + hdr.msgSize = sizeof(*this) - N + i + 1; + } + + void append(const char *msg) + { + int i=dataSize, j=0; + + while ( (i < (N-1)) && (msg[j] != 0) ) + { + data[i] = msg[j]; i++; j++; + } + data[i] = 0; + dataSize = i; + + hdr.msgSize = sizeof(*this) - N + i + 1; + } + void toHostByteOrder() { hdr.toHostByteOrder(); code = netPlayByteSwap(code); flags = netPlayByteSwap(flags); + dataSize = netPlayByteSwap(dataSize); } void toNetworkByteOrder() @@ -169,7 +212,10 @@ struct netPlayErrorMsg hdr.toNetworkByteOrder(); code = netPlayByteSwap(code); flags = netPlayByteSwap(flags); + dataSize = netPlayByteSwap(dataSize); } +private: + char data[N]; }; struct netPlayLoadRomReq diff --git a/src/drivers/Qt/dface.h b/src/drivers/Qt/dface.h index 9ffd876b..10fcb721 100644 --- a/src/drivers/Qt/dface.h +++ b/src/drivers/Qt/dface.h @@ -1,3 +1,5 @@ +#pragma once +#include #include "common/args.h" #include "common/config.h" @@ -9,10 +11,10 @@ extern ARGPSTRUCT DriverArgs[]; void DoDriverArgs(void); int InitSound(); -void WriteSound(int32 *Buffer, int Count); +void WriteSound(int32_t *Buffer, int Count); int KillSound(void); -uint32 GetMaxSound(void); -uint32 GetWriteSound(void); +uint32_t GetMaxSound(void); +uint32_t GetWriteSound(void); bool FCEUD_SoundIsMuted(void); void FCEUD_MuteSoundOutput(bool value); void FCEUD_MuteSoundWindow(bool value); @@ -24,18 +26,19 @@ int KillJoysticks(void); int AddJoystick( int which ); int RemoveJoystick( int which ); int FindJoystickByInstanceID( int which ); -uint32 *GetJSOr(void); +uint32_t *GetJSOr(void); +struct FCEUGI; int InitVideo(FCEUGI *gi); int KillVideo(void); void CalcVideoDimensions(void); -void BlitScreen(uint8 *XBuf); +void BlitScreen(uint8_t *XBuf); void LockConsole(void); void UnlockConsole(void); void ToggleFS(); /* SDL */ -int LoadGame(const char *path, bool silent); -//int CloseGame(void); +int LoadGame(const char *path, bool silent = false, bool netPlayRequested = false); +int CloseGame(void); void Giggles(int); void DoFun(void); diff --git a/src/drivers/Qt/fceuWrapper.cpp b/src/drivers/Qt/fceuWrapper.cpp index eb7efc47..9c070728 100644 --- a/src/drivers/Qt/fceuWrapper.cpp +++ b/src/drivers/Qt/fceuWrapper.cpp @@ -308,11 +308,22 @@ int reloadLastGame(void) * provides data necessary for the driver code(number of scanlines to * render, what virtual input devices to use, etc.). */ -int LoadGame(const char *path, bool silent) +int LoadGame(const char *path, bool silent, bool netPlayRequested) { std::string fullpath; int gg_enabled, autoLoadDebug, autoOpenDebugger, autoInputPreset; + // Check if this application instance has joined a net play session, + // NetPlay clients can only load ROMs retrieved from the host server. + // However, clients can request that a host load their ROM for all players. + auto* netPlayClient = NetPlayClient::GetInstance(); + + if (!netPlayRequested && (netPlayClient != nullptr)) + { + netPlayClient->requestRomLoad( path ); + return 0; + } + if (isloaded){ CloseGame(); } diff --git a/src/drivers/Qt/fceuWrapper.h b/src/drivers/Qt/fceuWrapper.h index 350480a3..def80edc 100644 --- a/src/drivers/Qt/fceuWrapper.h +++ b/src/drivers/Qt/fceuWrapper.h @@ -1,6 +1,7 @@ // fceuWrapper.h // #include "Qt/config.h" +#include "Qt/dface.h" //***************************************************************** // Define Global Variables to be shared with FCEU Core @@ -26,7 +27,7 @@ extern unsigned int emulatorCycleCount; // global configuration object extern Config *g_config; -int LoadGame(const char *path, bool silent = false); +//int LoadGame(const char *path, bool silent = false, bool netPlayRequested = false); int CloseGame(void); int reloadLastGame(void); int LoadGameFromLua( const char *path ); diff --git a/src/drivers/Qt/input.h b/src/drivers/Qt/input.h index a470518b..9dcde26d 100644 --- a/src/drivers/Qt/input.h +++ b/src/drivers/Qt/input.h @@ -10,6 +10,7 @@ #include #include "common/configSys.h" +#include "Qt/main.h" //#define MAXBUTTCONFIG 4 @@ -26,6 +27,7 @@ struct ButtConfig int state; }; +struct FCEUGI; extern int NoWaiting; extern CFGSTRUCT InputConfig[]; extern ARGPSTRUCT InputArgs[]; @@ -144,7 +146,7 @@ void UpdateInput(Config *config); const char* ButtonName(const ButtConfig* bc); void pollEventsSDL(); -uint32 GetGamepadPressedImmediate(void); +uint32_t GetGamepadPressedImmediate(void); int getInputSelection( int port, int *cur, int *usr ); int saveInputSettingsToFile( const char *fileBase = NULL ); int loadInputSettingsFromFile( const char *filename = NULL ); diff --git a/src/drivers/Qt/sdl.h b/src/drivers/Qt/sdl.h index 20c5c58f..d3eeafee 100644 --- a/src/drivers/Qt/sdl.h +++ b/src/drivers/Qt/sdl.h @@ -24,8 +24,6 @@ extern int dendy; extern int pal_emulation; extern bool swapDuty; -int LoadGame(const char *path, bool silent); -int CloseGame(void); void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count); uint64 FCEUD_GetTime();