For NetPlay, added ability for client to request a ROM to load.

This commit is contained in:
harry 2024-03-03 20:30:35 -05:00
parent ee814f99e5
commit 798c5a1d9c
8 changed files with 256 additions and 54 deletions

View File

@ -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<unsigned long long>(diff), client->getAvgPingDelay());
}
break;
case NETPLAY_LOAD_ROM_REQ:
{
netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(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<const char*>(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<const char*>(&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<netPlayErrorMsg<256>*>(msgBuf);
auto *msg = static_cast<netPlayTextMsg<256>*>(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<netPlayLoadRomReq*>(msgBuf);
msg->toHostByteOrder();
const char *romData = &static_cast<const char*>(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<char*>(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<uint32_t>(currFrameCounter)));
setText( 3, state);
setText( 2, state);
setText( 3, QString::number(static_cast<uint32_t>(currFrameCounter)));
}
else if (client != nullptr)
{
QString state;
uint32_t currFrame = currFrameCounter;
bool hasInput = (client->readyFrame > client->currentFrame) &&
(client->readyFrame <= currFrame);
roleString = NetPlayPlayerRoleToString( client->role );
if (client->isPaused())
{
state += QObject::tr("Paused");
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) );
}
}
//----------------------------------------------------------------------------

View File

@ -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:

View File

@ -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 <size_t N=8>
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<int>(N-1))
{
retval = static_cast<int>(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

View File

@ -1,3 +1,5 @@
#pragma once
#include <stdint.h>
#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);

View File

@ -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();
}

View File

@ -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 );

View File

@ -10,6 +10,7 @@
#include <QAction>
#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 );

View File

@ -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();