diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a093236c..bca9391c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,11 +40,12 @@ if ( ${QT} EQUAL 6 ) set( Qt Qt6 ) find_package( Qt6 REQUIRED COMPONENTS Widgets OpenGL OpenGLWidgets) find_package( Qt6 COMPONENTS Help QUIET) + find_package( Qt6 COMPONENTS Network) find_package( Qt6 COMPONENTS Qml) find_package( Qt6 COMPONENTS UiTools) - add_definitions( ${Qt6Widgets_DEFINITIONS} ${Qt6Qml_DEFINITIONS} ${Qt6Help_DEFINITIONS} ${Qt6OpenGLWidgets_DEFINITIONS} ) + add_definitions( ${Qt6Widgets_DEFINITIONS} ${Qt6Qml_DEFINITIONS} ${Qt6Network_DEFINITIONS} ${Qt6Help_DEFINITIONS} ${Qt6OpenGLWidgets_DEFINITIONS} ) # add_definitions(${Qt6UiTools_DEFINITIONS}) # Leave ${Qt6UiTools_DEFINITIONS} out as this is causing a build error - include_directories( ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Qml_INCLUDE_DIRS} ${Qt6UiTools_INCLUDE_DIRS} ${Qt6Help_INCLUDE_DIRS} ${Qt6OpenGLWidgets_INCLUDE_DIRS} ) + include_directories( ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Qml_INCLUDE_DIRS} ${Qt6UiTools_INCLUDE_DIRS} ${Qt6Network_INCLUDE_DIRS} ${Qt6Help_INCLUDE_DIRS} ${Qt6OpenGLWidgets_INCLUDE_DIRS} ) if (${Qt6Help_FOUND}) message( STATUS "Qt6 Help Module Found") @@ -55,6 +56,13 @@ if ( ${QT} EQUAL 6 ) message( STATUS "Qt6 Help Module Not Found") endif() + if (${Qt6Network_FOUND}) + message( STATUS "Qt6 Network Module Found") + add_definitions( -D__FCEU_QNETWORK_ENABLE__ ) + else() + message( STATUS "Qt6 Network Module Not Found") + endif() + if (${Qt6Qml_FOUND}) message( STATUS "Qt6 Qml Module Found") add_definitions( -D__FCEU_QSCRIPT_ENABLE__ ) @@ -73,10 +81,11 @@ else() set( Qt Qt5 ) find_package( Qt5 REQUIRED COMPONENTS Widgets OpenGL) find_package( Qt5 COMPONENTS Help QUIET) + find_package( Qt5 COMPONENTS Network) find_package( Qt5 COMPONENTS Qml) find_package( Qt5 COMPONENTS UiTools) - add_definitions( ${Qt5Widgets_DEFINITIONS} ${Qt5Qml_DEFINITIONS} ${Qt5UiTools_DEFINITIONS} ${Qt5Help_DEFINITIONS} ) - include_directories( ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Qml_INCLUDE_DIRS} ${Qt5UiTools_INCLUDE_DIRS} ${Qt5Help_INCLUDE_DIRS} ) + add_definitions( ${Qt5Widgets_DEFINITIONS} ${Qt5Qml_DEFINITIONS} ${Qt5UiTools_DEFINITIONS} ${Qt5Network_DEFINITIONS} ${Qt5Help_DEFINITIONS} ) + include_directories( ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Qml_INCLUDE_DIRS} ${Qt5UiTools_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Help_INCLUDE_DIRS} ) if (${Qt5Help_FOUND}) message( STATUS "Qt5 Help Module Found") @@ -87,6 +96,13 @@ else() message( STATUS "Qt5 Help Module Not Found") endif() + if (${Qt5Network_FOUND}) + message( STATUS "Qt5 Network Module Found") + add_definitions( -D__FCEU_NETWORK_ENABLE__ ) + else() + message( STATUS "Qt5 Network Module Not Found") + endif() + if (${Qt5Qml_FOUND}) message( STATUS "Qt5 Qml Module Found") add_definitions( -D__FCEU_QSCRIPT_ENABLE__ ) @@ -661,6 +677,7 @@ set(SRC_DRIVERS_SDL ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/sdl-joystick.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/sdl-throttle.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/unix-netplay.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/NetPlay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/AviRecord.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/AviRiffViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/avi/avi-utils.cpp @@ -733,6 +750,7 @@ target_link_libraries( ${APP_NAME} ${${Qt}Help_LIBRARIES} ${${Qt}Qml_LIBRARIES} ${${Qt}UiTools_LIBRARIES} + ${${Qt}Network_LIBRARIES} ${${Qt}OpenGL_LIBRARIES} ${${Qt}OpenGLWidgets_LIBRARIES} ${OPENGL_LDFLAGS} diff --git a/src/driver.h b/src/driver.h index efc1cd77..51334c9c 100644 --- a/src/driver.h +++ b/src/driver.h @@ -275,6 +275,8 @@ void FCEUI_SetEmulationPaused(int val); void FCEUI_ToggleEmulationPause(); void FCEUI_PauseForDuration(int secs); int FCEUI_PauseFramesRemaining(); +void FCEUI_SetNetPlayPause(bool value); +bool FCEUI_GetNetPlayPause(); //indicates whether input aids should be drawn (such as crosshairs, etc; usually in fullscreen mode) bool FCEUD_ShouldDrawInputAids(); diff --git a/src/drivers/Qt/ConsoleWindow.cpp b/src/drivers/Qt/ConsoleWindow.cpp index 466abd9e..59ff4c4d 100644 --- a/src/drivers/Qt/ConsoleWindow.cpp +++ b/src/drivers/Qt/ConsoleWindow.cpp @@ -107,6 +107,7 @@ #include "Qt/RamSearch.h" #include "Qt/keyscan.h" #include "Qt/nes_shm.h" +#include "Qt/NetPlay.h" #include "Qt/TasEditor/TasEditorWindow.h" #ifdef __APPLE__ @@ -926,13 +927,14 @@ void consoleWin_t::createMainMenu(void) menubar->setNativeMenuBar( useNativeMenuBar ? true : false ); // Top Level Menu Iterms - fileMenu = menubar->addMenu(tr("&File")); - movieMenu = menubar->addMenu(tr("&Movie")); - optMenu = menubar->addMenu(tr("&Options")); - emuMenu = menubar->addMenu(tr("&Emulation")); - toolsMenu = menubar->addMenu(tr("&Tools")); - debugMenu = menubar->addMenu(tr("&Debug")); - helpMenu = menubar->addMenu(tr("&Help")); + fileMenu = menubar->addMenu(tr("&File")); + movieMenu = menubar->addMenu(tr("&Movie")); + optMenu = menubar->addMenu(tr("&Options")); + emuMenu = menubar->addMenu(tr("&Emulation")); + netPlayMenu = menubar->addMenu(tr("&NetPlay")); + toolsMenu = menubar->addMenu(tr("&Tools")); + debugMenu = menubar->addMenu(tr("&Debug")); + helpMenu = menubar->addMenu(tr("&Help")); //----------------------------------------------------------------------- // File @@ -1657,6 +1659,29 @@ void consoleWin_t::createMainMenu(void) subMenu->addAction(act); + //----------------------------------------------------------------------- + // NetPlay + + connect( netPlayMenu, SIGNAL(aboutToShow(void)), this, SLOT(mainMenuOpen(void)) ); + connect( netPlayMenu, SIGNAL(aboutToHide(void)), this, SLOT(mainMenuClose(void)) ); + + // NetPlay -> Host + act = new QAction(tr("&Host"), this); + //act->setShortcut( QKeySequence(tr("Shift+F7"))); + act->setStatusTip(tr("Host Game Window")); + connect(act, SIGNAL(triggered()), this, SLOT(openNetPlayHostWindow(void)) ); + + netPlayMenu->addAction(act); + + // NetPlay -> Join + act = new QAction(tr("&Join"), this); + //act->setShortcut( QKeySequence(tr("Shift+F7"))); + act->setStatusTip(tr("Join Game Window")); + connect(act, SIGNAL(triggered()), this, SLOT(openNetPlayJoinWindow(void)) ); + + netPlayMenu->addAction(act); + + netPlayMenu->setEnabled(false); //----------------------------------------------------------------------- // Tools @@ -3112,6 +3137,28 @@ void consoleWin_t::openPaletteEditorWin(void) win->show(); } +void consoleWin_t::openNetPlayHostWindow(void) +{ + NetPlayHostDialog *win; + + //printf("Open NetPlay Host Window\n"); + + win = new NetPlayHostDialog(this); + + win->show(); +} + +void consoleWin_t::openNetPlayJoinWindow(void) +{ + NetPlayJoinDialog *win; + + //printf("Open NetPlay Join Window\n"); + + win = new NetPlayJoinDialog(this); + + win->show(); +} + void consoleWin_t::openAviRiffViewer(void) { AviRiffViewerDialog *win; @@ -4692,6 +4739,8 @@ void consoleWin_t::updatePeriodic(void) closeRequested = false; } + NetPlayPeriodicUpdate(); + updateCounter++; #ifdef __FCEU_PROFILER_ENABLE__ diff --git a/src/drivers/Qt/ConsoleWindow.h b/src/drivers/Qt/ConsoleWindow.h index 22d02dfe..d7ac9119 100644 --- a/src/drivers/Qt/ConsoleWindow.h +++ b/src/drivers/Qt/ConsoleWindow.h @@ -193,6 +193,7 @@ class consoleWin_t : public QMainWindow QMenu *toolsMenu; QMenu *debugMenu; QMenu *movieMenu; + QMenu *netPlayMenu; QMenu *helpMenu; QMenu *recentRomMenu; @@ -351,6 +352,8 @@ class consoleWin_t : public QMainWindow void openTimingConfWin(void); void openStateRecorderConfWin(void); void openPaletteEditorWin(void); + void openNetPlayHostWindow(void); + void openNetPlayJoinWindow(void); void openAviRiffViewer(void); void openTimingStatWin(void); void openMovieOptWin(void); diff --git a/src/drivers/Qt/NetPlay.cpp b/src/drivers/Qt/NetPlay.cpp new file mode 100644 index 00000000..b2dfeb0f --- /dev/null +++ b/src/drivers/Qt/NetPlay.cpp @@ -0,0 +1,1009 @@ +/* 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 + +#include "../../fceu.h" +#include "../../state.h" +#include "../../movie.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" + +//----------------------------------------------------------------------------- +//--- NetPlayServer +//----------------------------------------------------------------------------- +NetPlayServer *NetPlayServer::instance = nullptr; + +NetPlayServer::NetPlayServer(QObject *parent) + : QTcpServer(parent) +{ + instance = this; + + connect(this, SIGNAL(newConnection(void)), this, SLOT(newConnectionRdy(void))); +} + + +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"); + } + } + return 0; +} + +void NetPlayServer::newConnectionRdy(void) +{ + printf("New Connection Ready!\n"); + + processPendingConnections(); +} + +void NetPlayServer::processPendingConnections(void) +{ + QTcpSocket *newSock; + + newSock = nextPendingConnection(); + + while (newSock) + { + NetPlayClient *client = new NetPlayClient(this); + + client->setSocket(newSock); + + clientList.push_back(client); + + newSock = nextPendingConnection(); + + printf("Added Client: %p %zu\n", client, clientList.size() ); + + netPlayAuthReq msg; + + sendMsg( client, &msg, sizeof(msg) ); + } +} + +bool NetPlayServer::removeClient(NetPlayClient *client, bool markForDelete) +{ + bool removed = false; + std::list ::iterator it; + + it = clientList.begin(); + + while (it != clientList.end()) + { + if (client == *it) + { + if (markForDelete) + { + client->deleteLater(); + } + + it = clientList.erase(it); + } + else + { + it++; + } + } + + for (int i=0; i<4; i++) + { + if (clientPlayer[i] == client) + { + clientPlayer[i] = nullptr; + } + } + return removed; +} + +int NetPlayServer::closeAllConnections(void) +{ + std::list ::iterator it; + + for (it = clientList.begin(); it != clientList.end(); it++) + { + delete *it; + } + clientList.clear(); + + return 0; +} + +//----------------------------------------------------------------------------- +int NetPlayServer::sendMsg( NetPlayClient *client, void *msg, size_t msgSize) +{ + QTcpSocket* sock; + + sock = client->getSocket(); + + sock->write( static_cast(msg), msgSize ); + + return 0; +} +//----------------------------------------------------------------------------- +int NetPlayServer::sendRomLoadReq( NetPlayClient *client ) +{ + constexpr size_t BufferSize = 64 * 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; + } + } + printf("Prep ROM Load Request: %s \n", filepath ); + + if (filepath == nullptr) + { + return -1; + } + FILE *fp = ::fopen( filepath, "r"); + + if (fp == nullptr) + { + return -1; + } + fseek( fp, 0, SEEK_END); + + fileSize = ftell(fp); + + rewind(fp); + + msg.hdr.msgSize += fileSize; + msg.fileSize = fileSize; + strncpy( msg.fileName, GameInfo->filename, sizeof(msg.fileName) ); + + printf("Sending ROM Load Request: %s %lu\n", filepath, fileSize ); + FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); + + sendMsg( client, &msg, sizeof(netPlayLoadRomReq) ); + + while ( (bytesRead = fread( buf, 1, sizeof(buf), fp )) > 0 ) + { + sendMsg( client, buf, bytesRead ); + } + + ::fclose(fp); + + return 0; +} +//----------------------------------------------------------------------------- +int NetPlayServer::sendStateSyncReq( NetPlayClient *client ) +{ + EMUFILE_MEMORY em; + int compressionLevel = 1; + netPlayMsgHdr hdr(NETPLAY_SYNC_STATE); + + if ( GameInfo == nullptr ) + { + return -1; + } + FCEUSS_SaveMS( &em, compressionLevel ); + + hdr.msgSize += em.size(); + + printf("Sending ROM Sync Request\n"); + FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); + + sendMsg( client, &hdr, sizeof(netPlayMsgHdr) ); + sendMsg( client, em.buf(), em.size() ); + + return 0; +} +//----------------------------------------------------------------------------- +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; + } + } + return success; +} +//----------------------------------------------------------------------------- +static void serverMessageCallback( void *userData, void *msgBuf, size_t msgSize ) +{ + NetPlayServer *server = NetPlayServer::GetInstance(); + + server->serverProcessMessage( static_cast(userData), msgBuf, msgSize ); +} +//----------------------------------------------------------------------------- +void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, size_t msgSize ) +{ + netPlayMsgHdr *hdr = (netPlayMsgHdr*)msgBuf; + + //printf("Server Received Message: ID: %i Bytes:%zu\n", hdr->msgId, msgSize ); + + switch (hdr->msgId) + { + case NETPLAY_AUTH_RESP: + { + netPlayAuthResp *msg = static_cast(msgBuf); + printf("Authorize: Player: %i Passwd: %s\n", msg->playerId, msg->pswd); + + if ( claimRole(client, msg->playerId) ) + { + sendRomLoadReq( client ); + sendStateSyncReq( client ); + client->state = 1; + } + else + { + netPlayErrorMsg<128> errorMsg; + errorMsg.printf("Player %i role is not available", msg->playerId+1); + sendMsg( client, &errorMsg, errorMsg.hdr.msgSize ); + //client->disconnect(); + } + } + break; + case NETPLAY_RUN_FRAME_RESP: + { + netPlayRunFrameResp *msg = static_cast(msgBuf); + + client->currentFrame = msg->frameRun; + } + break; + case NETPLAY_CLIENT_STATE: + { + netPlayClientState *msg = static_cast(msgBuf); + + client->currentFrame = msg->frameRun; + } + break; + default: + + break; + } + +} +//----------------------------------------------------------------------------- +void NetPlayServer::update(void) +{ + bool shouldRunFrame = false; + unsigned int clientMinFrame = 0xFFFFFFFF; + unsigned int clientMaxFrame = 0; + const uint32_t maxLead = 5u; + const uint32_t currFrame = static_cast(currFrameCounter); + const uint32_t leadFrame = currFrame + maxLead; + const uint32_t lastFrame = inputFrameBack(); + uint32_t lagFrame = 0; + + if (currFrame > maxLead) + { + lagFrame = currFrame - maxLead; + } + + // Input Processing + for (auto it = clientList.begin(); it != clientList.end(); it++) + { + NetPlayClient *client = *it; + + client->readMessages( serverMessageCallback, client ); + + if (client->currentFrame < clientMinFrame) + { + clientMinFrame = client->currentFrame; + } + if (client->currentFrame > clientMaxFrame) + { + clientMaxFrame = client->currentFrame; + } + } + + shouldRunFrame = (clientMinFrame != 0xFFFFFFFF) && + (clientMinFrame >= lagFrame ) && + (clientMaxFrame < leadFrame) && + (currFrame > lastFrame); + + //printf("Client Frame: Min:%u Max:%u\n", clientMinFrame, clientMaxFrame); + + if (shouldRunFrame) + { + // Output Processing + NetPlayFrameInput inputFrame; + netPlayRunFrameReq runFrameReq; + + uint32_t ctlrData = GetGamepadPressedImmediate(); + + inputFrame.frameCounter = static_cast(currFrameCounter) + 1; + inputFrame.ctrl[0] = (ctlrData ) & 0x000000ff; + inputFrame.ctrl[1] = (ctlrData >> 8) & 0x000000ff; + inputFrame.ctrl[2] = (ctlrData >> 16) & 0x000000ff; + inputFrame.ctrl[3] = (ctlrData >> 24) & 0x000000ff; + + 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]; + + pushBackInput( inputFrame ); + + for (auto it = clientList.begin(); it != clientList.end(); it++) + { + NetPlayClient *client = *it; + + if (client->state > 0) + { + sendMsg( client, &runFrameReq, sizeof(runFrameReq) ); + } + } + } + +} +//----------------------------------------------------------------------------- +//--- 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) +{ + if (outGoing) + { + instance = this; + } + + recvMsgBuf = static_cast( ::malloc( recvMsgBufSize ) ); + + if (recvMsgBuf == nullptr) + { + printf("Error: NetPlayClient failed to allocate recvMsgBuf\n"); + } +} + + +NetPlayClient::~NetPlayClient(void) +{ + if (instance == this) + { + instance = nullptr; + } + + if (sock != nullptr) + { + delete sock; sock = nullptr; + } + if (recvMsgBuf) + { + ::free(recvMsgBuf); recvMsgBuf = nullptr; + } + 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"); + } + } + return 0; +} +//----------------------------------------------------------------------------- +void NetPlayClient::disconnect() +{ + sock->close(); +} +//----------------------------------------------------------------------------- +void NetPlayClient::setSocket(QTcpSocket *s) +{ + sock = s; + + if (sock != nullptr) + { + connect(sock, SIGNAL(connected(void)) , this, SLOT(onConnect(void))); + connect(sock, SIGNAL(disconnected(void)), this, SLOT(onDisconnect(void))); + } +} +//----------------------------------------------------------------------------- +int NetPlayClient::createSocket(void) +{ + if (sock == nullptr) + { + sock = new QTcpSocket(this); + } + + connect(sock, SIGNAL(connected(void)) , this, SLOT(onConnect(void))); + connect(sock, SIGNAL(disconnected(void)), this, SLOT(onDisconnect(void))); + + return 0; +} +//----------------------------------------------------------------------------- +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"); +} +//----------------------------------------------------------------------------- +void NetPlayClient::onDisconnect(void) +{ + printf("Client Disconnected!!!\n"); + + NetPlayServer *server = NetPlayServer::GetInstance(); + + if (server) + { + if (server->removeClient(this)) + { + deleteLater(); + } + } +} +//----------------------------------------------------------------------------- +static void clientMessageCallback( void *userData, void *msgBuf, size_t msgSize ) +{ + NetPlayClient *client = static_cast(userData); + + client->clientProcessMessage( msgBuf, msgSize ); +} +//----------------------------------------------------------------------------- +void NetPlayClient::update(void) +{ + readMessages( clientMessageCallback, this ); + + uint32_t ctlrData = GetGamepadPressedImmediate(); + uint32_t currFrame = static_cast(currFrameCounter); + + netPlayClientState statusMsg; + statusMsg.flags = 0; + statusMsg.frameRdy = inputFrameBack(); + statusMsg.frameRun = currFrame; + statusMsg.ctrlState[0] = (ctlrData ) & 0x000000ff; + statusMsg.ctrlState[1] = (ctlrData >> 8) & 0x000000ff; + statusMsg.ctrlState[2] = (ctlrData >> 16) & 0x000000ff; + statusMsg.ctrlState[3] = (ctlrData >> 24) & 0x000000ff; + + sock->write( reinterpret_cast(&statusMsg), sizeof(statusMsg) ); + +} +//----------------------------------------------------------------------------- +int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData ) +{ + if (sock) + { + bool readReq; + netPlayMsgHdr *hdr; + const int netPlayMsgHdrSize = sizeof(netPlayMsgHdr); + + readReq = sock->bytesAvailable() > 0; + + while (readReq) + { + if (recvMsgBytesLeft > 0) + { + readReq = (sock->bytesAvailable() >= recvMsgBytesLeft); + + if (readReq) + { + int dataRead; + //size_t readSize = recvMsgBytesLeft; + + dataRead = sock->read( &recvMsgBuf[recvMsgByteIndex], recvMsgBytesLeft ); + + if (dataRead > 0) + { + recvMsgBytesLeft -= dataRead; + recvMsgByteIndex += dataRead; + } + //printf(" Data: Id: %u Size: %zu Read: %i\n", recvMsgId, readSize, dataRead ); + + if (recvMsgBytesLeft > 0) + { + readReq = (sock->bytesAvailable() >= recvMsgBytesLeft); + } + else + { + msgCallback( userData, recvMsgBuf, recvMsgSize ); + readReq = (sock->bytesAvailable() > 0); + } + } + } + else if (sock->bytesAvailable() >= netPlayMsgHdrSize) + { + sock->read( recvMsgBuf, netPlayMsgHdrSize ); + + hdr = (netPlayMsgHdr*)recvMsgBuf; + + recvMsgId = hdr->msgId; + recvMsgSize = hdr->msgSize - sizeof(netPlayMsgHdr); + recvMsgBytesLeft = recvMsgSize; + + if (hdr->msgSize > recvMsgBufSize) + { + printf("Error: Message size too large\n"); + } + //printf("HDR: Id: %u Size: %u\n", hdr->msgId, hdr->msgSize ); + + recvMsgByteIndex = sizeof(netPlayMsgHdr); + + readReq = (sock->bytesAvailable() >= recvMsgSize); + } + else + { + readReq = false; + } + } + } + + return 0; +} +//----------------------------------------------------------------------------- +void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize ) +{ + netPlayMsgHdr *hdr = (netPlayMsgHdr*)msgBuf; + + switch (hdr->msgId) + { + case NETPLAY_AUTH_REQ: + { + netPlayAuthResp msg; + msg.playerId = role; + strncpy( msg.pswd, "TODO: Dummy Password", sizeof(msg.pswd) ); + + sock->write( (const char*)&msg, sizeof(netPlayAuthResp) ); + } + break; + case NETPLAY_LOAD_ROM_REQ: + { + FILE *fp; + std::string filepath = QDir::tempPath().toStdString(); + netPlayLoadRomReq *msg = static_cast(msgBuf); + const char *romData = &static_cast(msgBuf)[ sizeof(netPlayLoadRomReq) ]; + + filepath.append( "/" ); + filepath.append( msg->fileName ); + + //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 ); + FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED); + FCEU_WRAPPER_UNLOCK(); + } + break; + case NETPLAY_SYNC_STATE: + { + char *stateData = &static_cast(msgBuf)[ sizeof(netPlayMsgHdr) ]; + + EMUFILE_MEMORY em( stateData, msgSize ); + + FCEUSS_LoadFP( &em, SSLOADPARAM_NOBACKUP ); + } + break; + case NETPLAY_RUN_FRAME_REQ: + { + NetPlayFrameInput inputFrame; + netPlayRunFrameReq *msg = static_cast(msgBuf); + + uint32_t currFrame = static_cast(currFrameCounter); + + 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]; + + if (inputFrame.frameCounter > inputFrameBack()) + { + pushBackInput( inputFrame ); + } + + netPlayRunFrameResp resp; + resp.flags = msg->flags; + resp.frameNum = msg->frameNum; + resp.frameRun = currFrame; + resp.ctrlState[0] = msg->ctrlState[0]; + resp.ctrlState[1] = msg->ctrlState[1]; + resp.ctrlState[2] = msg->ctrlState[2]; + resp.ctrlState[3] = msg->ctrlState[3]; + + sock->write( reinterpret_cast(&resp), sizeof(resp) ); + } + break; + default: + + break; + } +} +//----------------------------------------------------------------------------- +//--- NetPlayHostDialog +//----------------------------------------------------------------------------- +NetPlayHostDialog::NetPlayHostDialog(QWidget *parent) + : QDialog(parent) +{ + QVBoxLayout *mainLayout; + QHBoxLayout *hbox; + QGridLayout *grid; + QPushButton *cancelButton, *startButton; + QLabel *lbl; + + setWindowTitle("NetPlay Host Game"); + + mainLayout = new QVBoxLayout(); + grid = new QGridLayout(); + + lbl = new QLabel( tr("Server Name:") ); + grid->addWidget( lbl, 0, 0 ); + + serverNameEntry = new QLineEdit(); + grid->addWidget( serverNameEntry, 0, 1 ); + + lbl = new QLabel( tr("Port:") ); + grid->addWidget( lbl, 1, 0 ); + + portEntry = new QSpinBox(); + portEntry->setRange(0,65535); + portEntry->setValue(5050); + 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 ); + + mainLayout->addLayout(grid); + + 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) +{ + //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::onStartClicked(void) +{ + NetPlayServer *server = NetPlayServer::GetInstance(); + + if (server != nullptr) + { + delete server; + server = nullptr; + } + NetPlayServer::Create(consoleWindow); + + server = NetPlayServer::GetInstance(); + server->setRole( playerRoleBox->currentData().toInt() ); + + if (server->listen( QHostAddress::Any, portEntry->value() ) == false) + { + printf("Error: TCP server failed to listen\n"); + } + + done(0); + deleteLater(); +} +//----------------------------------------------------------------------------- +//--- NetPlayJoinDialog +//----------------------------------------------------------------------------- +NetPlayJoinDialog::NetPlayJoinDialog(QWidget *parent) + : QDialog(parent) +{ + QVBoxLayout *mainLayout; + QHBoxLayout *hbox; + QGridLayout *grid; + QPushButton *cancelButton, *startButton; + QLabel *lbl; + + setWindowTitle("NetPlay Host Game"); + + mainLayout = new QVBoxLayout(); + grid = new QGridLayout(); + + lbl = new QLabel( tr("Host:") ); + grid->addWidget( lbl, 0, 0 ); + + hostEntry = new QLineEdit(); + hostEntry->setText("localhost"); + grid->addWidget( hostEntry, 0, 1 ); + + lbl = new QLabel( tr("Port:") ); + grid->addWidget( lbl, 1, 0 ); + + portEntry = new QSpinBox(); + portEntry->setRange(0,65535); + portEntry->setValue(5050); + 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 ); + + 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) +{ + //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); + + client = NetPlayClient::GetInstance(); + client->role = playerRoleBox->currentData().toInt(); + + if (client->connectToHost( hostEntry->text(), portEntry->value() )) + { + printf("Failed to connect to Host\n"); + } + + //printf("Close Window\n"); + done(0); + deleteLater(); +} +//---------------------------------------------------------------------------- +//---- Global Functions +//---------------------------------------------------------------------------- +bool NetPlayActive(void) +{ + return (NetPlayClient::GetInstance() != nullptr) || (NetPlayServer::GetInstance() != nullptr); +} +//---------------------------------------------------------------------------- +void NetPlayPeriodicUpdate(void) +{ + NetPlayClient *client = NetPlayClient::GetInstance(); + + if (client) + { + client->update(); + } + + 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->inputAvailable() > 1; + } + 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]; +} +//---------------------------------------------------------------------------- diff --git a/src/drivers/Qt/NetPlay.h b/src/drivers/Qt/NetPlay.h new file mode 100644 index 00000000..5d9714ef --- /dev/null +++ b/src/drivers/Qt/NetPlay.h @@ -0,0 +1,275 @@ +// NetPlay.h +// + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "utils/mutex.h" + +class NetPlayClient; + +struct NetPlayFrameInput +{ + static constexpr uint32_t ownsData = 0x01; + + NetPlayFrameInput(void) + { + flags = 0; frameCounter = 0; + ctrl[0] = ctrl[1] = ctrl[2] = ctrl[3] = 0; + data = nullptr; + } + + ~NetPlayFrameInput() + { + if (data) + { + if (flags & ownsData) + { + ::free(data); + data = nullptr; + } + } + } + + uint32_t flags; + uint32_t frameCounter; + uint8_t ctrl[4]; + uint8_t *data; +}; + +class NetPlayServer : public QTcpServer +{ + Q_OBJECT + + public: + NetPlayServer(QObject *parent = 0); + ~NetPlayServer(void); + + static NetPlayServer *GetInstance(void){ return instance; }; + + static int Create(QObject *parent = 0); + + bool removeClient(NetPlayClient *client, bool markForDelete = false); + + int closeAllConnections(void); + + void update(void); + + size_t inputAvailable(void) + { + FCEU::autoScopedLock alock(inputMtx); + return input.size(); + }; + + void pushBackInput( NetPlayFrameInput &in ) + { + FCEU::autoScopedLock alock(inputMtx); + input.push_back(in); + }; + + NetPlayFrameInput getNextInput(void) + { + NetPlayFrameInput in; + FCEU::autoScopedLock alock(inputMtx); + if (!input.empty()) + { + in = input.front(); + input.pop_front(); + } + return in; + }; + + uint32_t inputFrameBack() + { + uint32_t frame = 0; + FCEU::autoScopedLock alock(inputMtx); + if (!input.empty()) + { + NetPlayFrameInput &in = input.back(); + frame = in.frameCounter; + } + return frame; + } + + int sendMsg( NetPlayClient *client, void *msg, size_t msgSize); + int sendRomLoadReq( NetPlayClient *client ); + int sendStateSyncReq( NetPlayClient *client ); + void setRole(int _role); + bool claimRole(NetPlayClient* client, int _role); + + void serverProcessMessage( NetPlayClient *client, void *msgBuf, size_t msgSize ); + + private: + static NetPlayServer *instance; + + void processPendingConnections(void); + + std::list clientList; + std::list input; + FCEU::mutex inputMtx; + int role = -1; + int roleMask = 0; + NetPlayClient* clientPlayer[4] = { nullptr }; + + public slots: + void newConnectionRdy(void); +}; + +class NetPlayClient : public QObject +{ + Q_OBJECT + + public: + NetPlayClient(QObject *parent = 0, bool outGoing = false); + ~NetPlayClient(void); + + static NetPlayClient *GetInstance(void){ return instance; }; + + static int Create(QObject *parent = 0); + + int connectToHost( const QString host, int port ); + + bool isConnected(void); + void disconnect(); + + void setSocket(QTcpSocket *s); + QTcpSocket* getSocket(void){ return sock; }; + + void update(void); + int readMessages( void (*msgCallback)( void *userData, void *msgBuf, size_t msgSize ), void *userData ); + void clientProcessMessage( void *msgBuf, size_t msgSize ); + + size_t inputAvailable(void) + { + FCEU::autoScopedLock alock(inputMtx); + return !input.empty(); + }; + + void pushBackInput( NetPlayFrameInput &in ) + { + FCEU::autoScopedLock alock(inputMtx); + input.push_back(in); + }; + + NetPlayFrameInput getNextInput(void) + { + NetPlayFrameInput in; + FCEU::autoScopedLock alock(inputMtx); + if (!input.empty()) + { + in = input.front(); + input.pop_front(); + } + return in; + }; + + uint32_t inputFrameBack() + { + uint32_t frame = 0; + FCEU::autoScopedLock alock(inputMtx); + if (!input.empty()) + { + NetPlayFrameInput &in = input.back(); + frame = in.frameCounter; + } + return frame; + } + + + QString userName; + int role; + int state; + unsigned int currentFrame; + + private: + int createSocket(void); + + static NetPlayClient *instance; + + QTcpSocket *sock; + int recvMsgId; + int recvMsgSize; + int recvMsgBytesLeft; + int recvMsgByteIndex; + char *recvMsgBuf; + + std::list input; + FCEU::mutex inputMtx; + + static constexpr size_t recvMsgBufSize = 2 * 1024 * 1024; + + public slots: + void onConnect(void); + void onDisconnect(void); +}; + + +class NetPlayHostDialog : public QDialog +{ + Q_OBJECT + +public: + NetPlayHostDialog(QWidget *parent = 0); + ~NetPlayHostDialog(void); + +protected: + void closeEvent(QCloseEvent *event); + + QLineEdit *serverNameEntry; + QSpinBox *portEntry; + QComboBox *playerRoleBox; + +public slots: + void closeWindow(void); + void onStartClicked(void); + +}; + +class NetPlayJoinDialog : public QDialog +{ + Q_OBJECT + +public: + NetPlayJoinDialog(QWidget *parent = 0); + ~NetPlayJoinDialog(void); + +protected: + void closeEvent(QCloseEvent *event); + + QLineEdit *hostEntry; + QSpinBox *portEntry; + QComboBox *playerRoleBox; + +public slots: + void closeWindow(void); + void onJoinClicked(void); + +}; + +bool NetPlayActive(void); +void NetPlayPeriodicUpdate(void); +bool NetPlaySkipWait(void); +int NetPlayFrameWait(void); +void NetPlayReadInputFrame(uint8_t* joy); diff --git a/src/drivers/Qt/NetPlayMsgDef.h b/src/drivers/Qt/NetPlayMsgDef.h new file mode 100644 index 00000000..dcec13e7 --- /dev/null +++ b/src/drivers/Qt/NetPlayMsgDef.h @@ -0,0 +1,167 @@ +// NetPlayMsgDef.h + +#pragma once + +#include +#include + +#pragma pack(push,4) + +enum netPlayMsgType +{ + NETPLAY_AUTH_REQ, + NETPLAY_AUTH_RESP, + NETPLAY_LOAD_ROM_REQ, + NETPLAY_SYNC_STATE, + NETPLAY_RUN_FRAME_REQ, + NETPLAY_RUN_FRAME_RESP, + NETPLAY_CLIENT_STATE, + NETPLAY_ERROR_MSG, +}; + +enum netPlayerId +{ + NETPLAY_SPECTATOR = -1, + NETPLAY_PLAYER1, + NETPLAY_PLAYER2, + NETPLAY_PLAYER3, + NETPLAY_PLAYER4 +}; + +static const uint32_t NETPLAY_MAGIC_NUMBER = 0xaa55aa55; + +struct netPlayMsgHdr +{ + uint32_t magic[2]; + uint32_t msgId; + uint32_t msgSize; + + netPlayMsgHdr( uint32_t id, uint32_t size = sizeof(netPlayMsgHdr) ) + { + magic[0] = NETPLAY_MAGIC_NUMBER; + magic[1] = NETPLAY_MAGIC_NUMBER; + msgId = id; msgSize = size; + } +}; + +struct netPlayAuthReq +{ + netPlayMsgHdr hdr; + + uint8_t ctrlMask; + + netPlayAuthReq(void) + : hdr(NETPLAY_AUTH_REQ, sizeof(netPlayAuthReq)), ctrlMask(0) + { + } +}; + +struct netPlayAuthResp +{ + netPlayMsgHdr hdr; + + char playerId; + char pswd[128]; + + netPlayAuthResp(void) + : hdr(NETPLAY_AUTH_RESP, sizeof(netPlayAuthResp)), playerId(NETPLAY_SPECTATOR) + { + memset(pswd, 0, sizeof(pswd)); + } +}; + +template +struct netPlayErrorMsg +{ + netPlayMsgHdr hdr; + + char data[N]; + + netPlayErrorMsg(void) + : hdr(NETPLAY_ERROR_MSG, sizeof(netPlayErrorMsg)) + { + } + + char *getMsgBuffer() + { + return &data[0]; + } + + int printf(const char* format, ...) + { + int retval; + va_list args; + va_start(args, format); + retval = ::vsnprintf(data, sizeof(data), format, args); + va_end(args); + + hdr.msgSize = sizeof(netPlayMsgHdr) + strlen(data); + + return retval; + } +}; + +struct netPlayLoadRomReq +{ + netPlayMsgHdr hdr; + + uint32_t fileSize; + char fileName[256]; + + netPlayLoadRomReq(void) + : hdr(NETPLAY_LOAD_ROM_REQ, sizeof(netPlayLoadRomReq)), fileSize(0) + { + memset(fileName, 0, sizeof(fileName)); + } +}; + + +struct netPlayRunFrameReq +{ + netPlayMsgHdr hdr; + + uint32_t flags; + uint32_t frameNum; + uint8_t ctrlState[4]; + + netPlayRunFrameReq(void) + : hdr(NETPLAY_RUN_FRAME_REQ, sizeof(netPlayRunFrameReq)), flags(0), frameNum(0) + { + memset( ctrlState, 0, sizeof(ctrlState) ); + } +}; + +struct netPlayRunFrameResp +{ + netPlayMsgHdr hdr; + + uint32_t flags; + uint32_t frameNum; + uint32_t frameRun; + uint8_t ctrlState[4]; + + netPlayRunFrameResp(void) + : hdr(NETPLAY_RUN_FRAME_RESP, sizeof(netPlayRunFrameResp)), flags(0), frameNum(0), frameRun(0) + { + memset( ctrlState, 0, sizeof(ctrlState) ); + } +}; + +struct netPlayClientState +{ + netPlayMsgHdr hdr; + + uint32_t flags; + uint32_t frameRdy; + uint32_t frameRun; + uint8_t ctrlState[4]; + + netPlayClientState(void) + : hdr(NETPLAY_CLIENT_STATE, sizeof(netPlayClientState)), flags(0), frameRdy(0), frameRun(0) + { + memset( ctrlState, 0, sizeof(ctrlState) ); + } +}; + + +#pragma pack(pop) diff --git a/src/drivers/Qt/QtScriptManager.cpp b/src/drivers/Qt/QtScriptManager.cpp index d85c7f15..298b3ba3 100644 --- a/src/drivers/Qt/QtScriptManager.cpp +++ b/src/drivers/Qt/QtScriptManager.cpp @@ -77,7 +77,6 @@ // File Base Name from Core extern char FileBase[]; extern uint8 joy[4]; -extern uint32 GetGamepadPressedImmediate(); static thread_local FCEU::JSEngine* currentEngine = nullptr; diff --git a/src/drivers/Qt/fceuWrapper.cpp b/src/drivers/Qt/fceuWrapper.cpp index f2f1952e..aef58cca 100644 --- a/src/drivers/Qt/fceuWrapper.cpp +++ b/src/drivers/Qt/fceuWrapper.cpp @@ -37,6 +37,7 @@ #include "Qt/sdl-video.h" #include "Qt/nes_shm.h" #include "Qt/unix-netplay.h" +#include "Qt/NetPlay.h" #include "Qt/AviRecord.h" #include "Qt/HexEditor.h" #include "Qt/CheatsConf.h" @@ -1475,6 +1476,23 @@ int fceuWrapperUpdate( void ) } mutexLockFail = false; emulatorHasMutex = 1; + + // For netplay, set pause if we do not have input ready for all players + if (NetPlayActive()) + { + if (NetPlayFrameWait()) + { + FCEUI_SetNetPlayPause(true); + } + else + { + FCEUI_SetNetPlayPause(false); + } + } + else + { + FCEUI_SetNetPlayPause(false); + } if ( GameInfo ) { diff --git a/src/drivers/Qt/input.h b/src/drivers/Qt/input.h index da154d39..a470518b 100644 --- a/src/drivers/Qt/input.h +++ b/src/drivers/Qt/input.h @@ -144,6 +144,7 @@ void UpdateInput(Config *config); const char* ButtonName(const ButtConfig* bc); void pollEventsSDL(); +uint32 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-throttle.cpp b/src/drivers/Qt/sdl-throttle.cpp index 72637de5..1aacecd5 100644 --- a/src/drivers/Qt/sdl-throttle.cpp +++ b/src/drivers/Qt/sdl-throttle.cpp @@ -21,6 +21,7 @@ /// \brief Handles emulation speed throttling using the SDL timing functions. #include "Qt/sdl.h" +#include "Qt/NetPlay.h" #include "Qt/throttle.h" #include "utils/timeStamp.h" @@ -367,7 +368,7 @@ SpeedThrottle(void) { bool isEmuPaused = FCEUI_EmulationPaused() ? true : false; bool noWaitActive = (NoWaiting & 0x01) ? true : false; - bool turboActive = (turbo || noWaitActive); + bool turboActive = (turbo || noWaitActive || NetPlaySkipWait()); // If Emulator is paused, don't waste CPU cycles spinning on nothing. if ( !isEmuPaused && ((g_fpsScale >= 32) || turboActive) ) diff --git a/src/fceu.cpp b/src/fceu.cpp index 37d61a53..a44fed7d 100644 --- a/src/fceu.cpp +++ b/src/fceu.cpp @@ -806,7 +806,7 @@ void FCEUI_Emulate(uint8 **pXBuf, int32 **SoundBuf, int32 *SoundBufSize, int ski RefreshThrottleFPS(); } #endif - if (EmulationPaused & (EMULATIONPAUSED_PAUSED | EMULATIONPAUSED_TIMER) ) + if (EmulationPaused & (EMULATIONPAUSED_PAUSED | EMULATIONPAUSED_TIMER | EMULATIONPAUSED_NETPLAY) ) { // emulator is paused memcpy(XBuf, XBackBuf, 256*256); @@ -1309,6 +1309,23 @@ int FCEUI_PauseFramesRemaining(void) return (EmulationPaused & EMULATIONPAUSED_TIMER) ? pauseTimer : 0; } +bool FCEUI_GetNetPlayPause() +{ + return (EmulationPaused & EMULATIONPAUSED_NETPLAY) ? true : false; +} + +void FCEUI_SetNetPlayPause(bool value) +{ + if (value) + { + EmulationPaused |= EMULATIONPAUSED_NETPLAY; + } + else + { + EmulationPaused &= ~EMULATIONPAUSED_NETPLAY; + } +} + static int AutosaveCounter = 0; void UpdateAutosave(void) { diff --git a/src/fceu.h b/src/fceu.h index b35bf4ab..e74d03c7 100644 --- a/src/fceu.h +++ b/src/fceu.h @@ -184,6 +184,7 @@ extern uint8 vsdip; #define EMULATIONPAUSED_PAUSED 0x01 #define EMULATIONPAUSED_TIMER 0x02 #define EMULATIONPAUSED_FA 0x04 +#define EMULATIONPAUSED_NETPLAY 0x08 #define FRAMEADVANCE_DELAY_DEFAULT 10 #define NES_HEADER_SIZE 16 diff --git a/src/input.cpp b/src/input.cpp index d31619d9..dc00b1a3 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -274,7 +274,6 @@ static void UpdateGP(int w, void *data, int arg) joy[3]= FCEU_JSReadJoypad(3,joy[3]); #endif } - } static void LogGP(int w, MovieRecord* mr) @@ -435,6 +434,10 @@ void FCEU_DrawInput(uint8 *buf) portFC.driver->Draw(buf,portFC.attrib); } +#ifdef __FCEU_QNETWORK_ENABLE__ +extern bool NetPlayActive(void); +void NetPlayReadInputFrame(uint8_t* joy); +#endif void FCEU_UpdateInput(void) { @@ -452,6 +455,12 @@ void FCEU_UpdateInput(void) if (coinon2) coinon2--; if (service) service--; } + #ifdef __FCEU_QNETWORK_ENABLE__ + if (NetPlayActive()) + { + NetPlayReadInputFrame(joy); + } + #endif if(FCEUnetplay) NetplayUpdate(joy); diff --git a/src/version.h b/src/version.h index c810c1fd..91200c3f 100644 --- a/src/version.h +++ b/src/version.h @@ -61,15 +61,15 @@ #endif #define FCEU_VERSION_MAJOR 2 -#define FCEU_VERSION_MINOR 6 -#define FCEU_VERSION_PATCH 6 +#define FCEU_VERSION_MINOR 7 +#define FCEU_VERSION_PATCH 0 #define FCEU_VERSION_NUMERIC ( (FCEU_VERSION_MAJOR*10000) + (FCEU_VERSION_MINOR*100) + (FCEU_VERSION_PATCH) ) #define FCEU_VERSION_MAJOR_DECODE(x) ( (x / 10000) ) #define FCEU_VERSION_MINOR_DECODE(x) ( (x / 100) % 100 ) #define FCEU_VERSION_PATCH_DECODE(x) (x % 100) -#define FCEU_VERSION_STRING "2.6.6" FCEU_SUBVERSION_STRING FCEU_FEATURE_STRING FCEU_COMPILER +#define FCEU_VERSION_STRING "2.7.0" FCEU_SUBVERSION_STRING FCEU_FEATURE_STRING FCEU_COMPILER #define FCEU_NAME_AND_VERSION FCEU_NAME " " FCEU_VERSION_STRING #endif