Added sync checking and network byte ordering code for Qt net play.

This commit is contained in:
harry 2024-02-25 08:40:36 -05:00
parent 8d93015a56
commit 0123e1fbf8
4 changed files with 417 additions and 22 deletions

View File

@ -24,6 +24,8 @@
#include "../../fceu.h"
#include "../../state.h"
#include "../../movie.h"
#include "../../debug.h"
#include "utils/crc32.h"
#include "Qt/main.h"
#include "Qt/dface.h"
#include "Qt/input.h"
@ -33,6 +35,102 @@
#include "Qt/ConsoleUtilities.h"
#include "Qt/NetPlayMsgDef.h"
//-----------------------------------------------------------------------------
//--- NetPlay State Monitoring Metrics
//-----------------------------------------------------------------------------
static uint32_t opsCrc32 = 0;
struct NetPlayFrameData
{
uint32_t frameNum = 0;
uint32_t opsCrc32 = 0;
uint32_t ramCrc32 = 0;
void reset()
{
frameNum = 0;
opsCrc32 = 0;
ramCrc32 = 0;
}
};
struct NetPlayFrameDataHist_t
{
static constexpr int numFrames = 10;
void push( NetPlayFrameData& newData )
{
data[bufHead] = newData;
bufHead = (bufHead + 1) % numFrames;
}
int getLast( NetPlayFrameData& out )
{
int i;
if (bufHead == 0)
{
i = numFrames - 1;
}
else
{
i = bufHead - 1;
}
out = data[i];
return 0;
}
int find( uint32_t frame, NetPlayFrameData& out )
{
int i, retval = -1;
if (bufHead == 0)
{
i = numFrames - 1;
}
else
{
i = bufHead - 1;
}
while (i != bufHead)
{
if (data[i].frameNum == frame)
{
out = data[i];
retval = 0;
break;
}
if (i == 0)
{
i = numFrames - 1;
}
else
{
i = i - 1;
}
}
return retval;
}
void reset(void)
{
bufHead = 0;
for (int i=0; i<numFrames; i++)
{
data[i].reset();
}
}
NetPlayFrameData data[numFrames];
int bufHead = 0;
};
static NetPlayFrameDataHist_t netPlayFrameData;
//-----------------------------------------------------------------------------
//--- NetPlayServer
//-----------------------------------------------------------------------------
@ -108,6 +206,7 @@ void NetPlayServer::processPendingConnections(void)
netPlayAuthReq msg;
msg.toNetworkByteOrder();
sendMsg( client, &msg, sizeof(msg) );
}
}
@ -193,6 +292,7 @@ int NetPlayServer::sendRomLoadReq( NetPlayClient *client )
printf("Sending ROM Load Request: %s %lu\n", filepath, fileSize );
FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED);
msg.toNetworkByteOrder();
sendMsg( client, &msg, sizeof(netPlayLoadRomReq) );
while ( (bytesRead = fread( buf, 1, sizeof(buf), fp )) > 0 )
@ -209,7 +309,7 @@ int NetPlayServer::sendStateSyncReq( NetPlayClient *client )
{
EMUFILE_MEMORY em;
int compressionLevel = 1;
netPlayMsgHdr hdr(NETPLAY_SYNC_STATE);
netPlayMsgHdr hdr(NETPLAY_SYNC_STATE_RESP);
if ( GameInfo == nullptr )
{
@ -222,9 +322,12 @@ int NetPlayServer::sendStateSyncReq( NetPlayClient *client )
printf("Sending ROM Sync Request\n");
FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED);
hdr.toNetworkByteOrder();
sendMsg( client, &hdr, sizeof(netPlayMsgHdr) );
sendMsg( client, em.buf(), em.size() );
opsCrc32 = 0;
return 0;
}
//-----------------------------------------------------------------------------
@ -272,14 +375,17 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s
{
netPlayMsgHdr *hdr = (netPlayMsgHdr*)msgBuf;
//printf("Server Received Message: ID: %i Bytes:%zu\n", hdr->msgId, msgSize );
const uint32_t msgId = netPlayByteSwap(hdr->msgId);
switch (hdr->msgId)
//printf("Server Received Message: ID: %i Bytes:%zu\n", msgId, msgSize );
switch (msgId)
{
case NETPLAY_AUTH_RESP:
{
bool authentication_passed = false;
netPlayAuthResp *msg = static_cast<netPlayAuthResp*>(msgBuf);
msg->toHostByteOrder();
printf("Authorize: Player: %i Passwd: %s\n", msg->playerId, msg->pswd);
if (sessionPasswd.isEmpty())
@ -295,6 +401,7 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s
netPlayErrorMsg<128> errorMsg;
errorMsg.setDisconnectFlag();
errorMsg.printf("Invalid Password");
errorMsg.toNetworkByteOrder();
sendMsg( client, &errorMsg, errorMsg.hdr.msgSize );
client->flushData();
}
@ -315,6 +422,7 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s
netPlayErrorMsg<128> errorMsg;
errorMsg.setDisconnectFlag();
errorMsg.printf("Player %i role is not available", msg->playerId+1);
errorMsg.toNetworkByteOrder();
sendMsg( client, &errorMsg, errorMsg.hdr.msgSize );
client->flushData();
}
@ -324,16 +432,55 @@ void NetPlayServer::serverProcessMessage( NetPlayClient *client, void *msgBuf, s
case NETPLAY_CLIENT_STATE:
{
netPlayClientState *msg = static_cast<netPlayClientState*>(msgBuf);
msg->toHostByteOrder();
client->currentFrame = msg->frameRun;
client->gpData[0] = msg->ctrlState[0];
client->gpData[1] = msg->ctrlState[1];
client->gpData[2] = msg->ctrlState[2];
client->gpData[3] = msg->ctrlState[3];
NetPlayFrameData data;
if ( (msg->opsFrame == 0) || netPlayFrameData.find( msg->opsFrame, data ) )
{
//printf("Error: Server Could not find data for frame: %u\n", msg->opsFrame );
}
else
{
bool opsSync = (data.opsCrc32 == msg->opsChkSum);
bool ramSync = (data.ramCrc32 == msg->ramChkSum);
client->syncOk = opsSync && ramSync;
if (!client->syncOk)
{
printf("Frame:%u is NOT in Sync: OPS:%i RAM:%i\n", msg->frameRun, opsSync, ramSync);
client->desyncCount++;
if (client->desyncCount > forceResyncCount)
{
sendStateSyncReq( client );
client->desyncCount = 0;
}
}
else
{
if (client->desyncCount > 0)
{
client->desyncCount--;
}
else
{
client->desyncCount = 0;
}
}
}
}
break;
default:
printf("Unknown Msg: %08X\n", msgId);
break;
}
@ -438,6 +585,8 @@ void NetPlayServer::update(void)
pushBackInput( inputFrame );
runFrameReq.toNetworkByteOrder();
for (auto it = clientList.begin(); it != clientList.end(); it++)
{
NetPlayClient *client = *it;
@ -642,15 +791,26 @@ void NetPlayClient::update(void)
uint32_t ctlrData = GetGamepadPressedImmediate();
uint32_t currFrame = static_cast<uint32_t>(currFrameCounter);
NetPlayFrameData lastFrameData;
netPlayFrameData.getLast( lastFrameData );
netPlayClientState statusMsg;
statusMsg.flags = 0;
if (FCEUI_EmulationPaused())
{
statusMsg.flags |= 0x0001;
}
statusMsg.frameRdy = inputFrameBack();
statusMsg.frameRun = currFrame;
statusMsg.opsFrame = lastFrameData.frameNum;
statusMsg.opsChkSum = lastFrameData.opsCrc32;
statusMsg.ramChkSum = lastFrameData.ramCrc32;
statusMsg.ctrlState[0] = (ctlrData ) & 0x000000ff;
statusMsg.ctrlState[1] = (ctlrData >> 8) & 0x000000ff;
statusMsg.ctrlState[2] = (ctlrData >> 16) & 0x000000ff;
statusMsg.ctrlState[3] = (ctlrData >> 24) & 0x000000ff;
statusMsg.toNetworkByteOrder();
sock->write( reinterpret_cast<const char*>(&statusMsg), sizeof(statusMsg) );
}
}
@ -702,18 +862,22 @@ int NetPlayClient::readMessages( void (*msgCallback)( void *userData, void *msgB
hdr = (netPlayMsgHdr*)recvMsgBuf;
recvMsgId = hdr->msgId;
recvMsgSize = hdr->msgSize - sizeof(netPlayMsgHdr);
recvMsgId = netPlayByteSwap(hdr->msgId);
recvMsgSize = netPlayByteSwap(hdr->msgSize) - sizeof(netPlayMsgHdr);
recvMsgBytesLeft = recvMsgSize;
if (hdr->msgSize > recvMsgBufSize)
if (netPlayByteSwap(hdr->msgSize) > recvMsgBufSize)
{
printf("Error: Message size too large\n");
printf("Error: Message size too large: %08X\n", recvMsgId);
}
//printf("HDR: Id: %u Size: %u\n", hdr->msgId, hdr->msgSize );
//printf("HDR: Id: %u Size: %u\n", netPlayByteSwap(hdr->msgId), netPlayByteSwap(hdr->msgSize) );
recvMsgByteIndex = sizeof(netPlayMsgHdr);
if (recvMsgBytesLeft == 0)
{
msgCallback( userData, recvMsgBuf, recvMsgSize );
}
readReq = (sock->bytesAvailable() >= recvMsgSize);
}
else
@ -730,11 +894,14 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
{
netPlayMsgHdr *hdr = (netPlayMsgHdr*)msgBuf;
switch (hdr->msgId)
const uint32_t msgId = netPlayByteSwap(hdr->msgId);
switch (msgId)
{
case NETPLAY_ERROR_MSG:
{
auto *msg = static_cast<netPlayErrorMsg<256>*>(msgBuf);
msg->toHostByteOrder();
printf("Error: 0x%X %s\n", msg->code, msg->getBuffer());
if (msg->isDisconnectFlagSet())
@ -751,6 +918,8 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
strncpy( msg.userName, userName.toLocal8Bit().constData(), sizeof(msg.userName));
strncpy( msg.pswd, password.toLocal8Bit().constData(), sizeof(msg.pswd) );
printf("Authentication Request Received\n");
msg.toNetworkByteOrder();
sock->write( (const char*)&msg, sizeof(netPlayAuthResp) );
}
break;
@ -759,11 +928,14 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
FILE *fp;
std::string filepath = QDir::tempPath().toStdString();
netPlayLoadRomReq *msg = static_cast<netPlayLoadRomReq*>(msgBuf);
msg->toHostByteOrder();
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");
@ -780,19 +952,27 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
FCEU_WRAPPER_UNLOCK();
}
break;
case NETPLAY_SYNC_STATE:
case NETPLAY_SYNC_STATE_RESP:
{
char *stateData = &static_cast<char*>(msgBuf)[ sizeof(netPlayMsgHdr) ];
printf("Sync state Request Received\n");
EMUFILE_MEMORY em( stateData, msgSize );
FCEU_WRAPPER_LOCK();
FCEUSS_LoadFP( &em, SSLOADPARAM_NOBACKUP );
FCEU_WRAPPER_UNLOCK();
opsCrc32 = 0;
netPlayFrameData.reset();
}
break;
case NETPLAY_RUN_FRAME_REQ:
{
NetPlayFrameInput inputFrame;
netPlayRunFrameReq *msg = static_cast<netPlayRunFrameReq*>(msgBuf);
msg->toHostByteOrder();
inputFrame.frameCounter = msg->frameNum;
inputFrame.ctrl[0] = msg->ctrlState[0];
@ -807,7 +987,7 @@ void NetPlayClient::clientProcessMessage( void *msgBuf, size_t msgSize )
}
break;
default:
printf("Unknown Msg: %08X\n", msgId);
break;
}
}
@ -977,6 +1157,10 @@ NetPlayJoinDialog::NetPlayJoinDialog(QWidget *parent)
QString hostAddress = "localhost";
g_config->getOption("SDL.NetworkIP", &hostAddress);
if (hostAddress.isEmpty())
{
hostAddress = "localhost";
}
hostEntry = new QLineEdit();
hostEntry->setText(hostAddress);
grid->addWidget( hostEntry, 0, 1 );
@ -1202,6 +1386,8 @@ void NetPlayReadInputFrame(uint8_t* joy)
joy[1] = netPlayInputFrame.ctrl[1];
joy[2] = netPlayInputFrame.ctrl[2];
joy[3] = netPlayInputFrame.ctrl[3];
NetPlayOnFrameBegin();
}
//----------------------------------------------------------------------------
void NetPlayCloseSession(void)
@ -1244,3 +1430,97 @@ void openNetPlayJoinDialog(QWidget* parent)
}
}
//----------------------------------------------------------------------------
//---- Network Byte Swap Utilities
//----------------------------------------------------------------------------
#ifdef __BIG_ENDIAN__
#define NETPLAY_HOST_BE 1
#define NETPLAY_HOST_LE 0
#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define NETPLAY_HOST_BE 1
#define NETPLAY_HOST_LE 0
#else
#define NETPLAY_HOST_BE 0
#define NETPLAY_HOST_LE 1
#endif
//----------------------------------------------------------------------------
uint16_t netPlayByteSwap(uint16_t in)
{
uint16_t out = in;
#if NETPLAY_HOST_LE
unsigned char *in8 = (unsigned char*)&in;
unsigned char *out8 = (unsigned char*)&out;
out8[0] = in8[1];
out8[1] = in8[0];
#endif
return out;
}
//----------------------------------------------------------------------------
uint32_t netPlayByteSwap(uint32_t in)
{
uint32_t out = in;
#if NETPLAY_HOST_LE
unsigned char *in8 = (unsigned char*)&in;
unsigned char *out8 = (unsigned char*)&out;
out8[0] = in8[3];
out8[1] = in8[2];
out8[2] = in8[1];
out8[3] = in8[0];
#endif
return out;
}
//----------------------------------------------------------------------------
uint64_t netPlayByteSwap(uint64_t in)
{
uint64_t out = in;
#if NETPLAY_HOST_LE
unsigned char *in8 = (unsigned char*)&in;
unsigned char *out8 = (unsigned char*)&out;
out8[0] = in8[7];
out8[1] = in8[6];
out8[2] = in8[5];
out8[3] = in8[4];
out8[4] = in8[3];
out8[5] = in8[2];
out8[6] = in8[1];
out8[7] = in8[0];
#endif
return out;
}
//----------------------------------------------------------------------------
uint32_t netPlayCalcRamChkSum()
{
uint32_t crc = 0;
uint8_t ram[256];
for (int i=0; i<256; i++)
{
ram[i] = GetMem(i);
}
crc = CalcCRC32( crc, ram, sizeof(ram));
return crc;
}
//----------------------------------------------------------------------------
void NetPlayTraceInstruction(uint8_t *opcode, int size)
{
opsCrc32 = CalcCRC32( opsCrc32, opcode, size);
}
//----------------------------------------------------------------------------
void NetPlayOnFrameBegin()
{
NetPlayFrameData data;
data.frameNum = static_cast<uint32_t>(currFrameCounter);
data.opsCrc32 = opsCrc32;
data.ramCrc32 = netPlayCalcRamChkSum();
netPlayFrameData.push( data );
//printf("Frame: %u Ops:%08X \n", data.frameNum, data.opsCrc32 );
}
//----------------------------------------------------------------------------

View File

@ -134,6 +134,7 @@ class NetPlayServer : public QTcpServer
int role = -1;
int roleMask = 0;
NetPlayClient* clientPlayer[4] = { nullptr };
int forceResyncCount = 10;
public slots:
void newConnectionRdy(void);
@ -211,6 +212,8 @@ class NetPlayClient : public QObject
QString password;
int role = -1;
int state = 0;
int desyncCount = 0;
bool syncOk = false;
unsigned int currentFrame = 0;
uint8_t gpData[4];
@ -303,7 +306,9 @@ bool isNetPlayHost(void);
void NetPlayPeriodicUpdate(void);
bool NetPlaySkipWait(void);
int NetPlayFrameWait(void);
void NetPlayOnFrameBegin(void);
void NetPlayReadInputFrame(uint8_t* joy);
void NetPlayCloseSession(void);
void NetPlayTraceInstruction(uint8_t *opcode, int size);
void openNetPlayHostDialog(QWidget* parent = nullptr);
void openNetPlayJoinDialog(QWidget* parent = nullptr);

View File

@ -5,6 +5,11 @@
#include <string.h>
#include <stdint.h>
// Network Byte Swap Functions
uint16_t netPlayByteSwap(uint16_t);
uint32_t netPlayByteSwap(uint32_t);
uint64_t netPlayByteSwap(uint64_t);
#pragma pack(push,4)
enum netPlayMsgType
@ -12,7 +17,8 @@ enum netPlayMsgType
NETPLAY_AUTH_REQ,
NETPLAY_AUTH_RESP,
NETPLAY_LOAD_ROM_REQ,
NETPLAY_SYNC_STATE,
NETPLAY_SYNC_STATE_REQ,
NETPLAY_SYNC_STATE_RESP,
NETPLAY_RUN_FRAME_REQ,
NETPLAY_CLIENT_STATE,
NETPLAY_ERROR_MSG,
@ -41,18 +47,41 @@ struct netPlayMsgHdr
magic[1] = NETPLAY_MAGIC_NUMBER;
msgId = id; msgSize = size;
}
void toHostByteOrder()
{
magic[0] = netPlayByteSwap(magic[0]);
magic[1] = netPlayByteSwap(magic[1]);
msgId = netPlayByteSwap(msgId);
msgSize = netPlayByteSwap(msgSize);
};
void toNetworkByteOrder()
{
magic[0] = netPlayByteSwap(magic[0]);
magic[1] = netPlayByteSwap(magic[1]);
msgId = netPlayByteSwap(msgId);
msgSize = netPlayByteSwap(msgSize);
}
};
struct netPlayAuthReq
{
netPlayMsgHdr hdr;
uint8_t ctrlMask;
netPlayAuthReq(void)
: hdr(NETPLAY_AUTH_REQ, sizeof(netPlayAuthReq)), ctrlMask(0)
: hdr(NETPLAY_AUTH_REQ, sizeof(netPlayAuthReq))
{
}
void toHostByteOrder()
{
hdr.toHostByteOrder();
}
void toNetworkByteOrder()
{
hdr.toNetworkByteOrder();
}
};
struct netPlayAuthResp
@ -68,6 +97,16 @@ struct netPlayAuthResp
{
memset(pswd, 0, sizeof(pswd));
}
void toHostByteOrder()
{
hdr.toHostByteOrder();
}
void toNetworkByteOrder()
{
hdr.toNetworkByteOrder();
}
};
template <size_t N=8>
@ -114,6 +153,20 @@ struct netPlayErrorMsg
return retval;
}
void toHostByteOrder()
{
hdr.toHostByteOrder();
code = netPlayByteSwap(code);
flags = netPlayByteSwap(flags);
}
void toNetworkByteOrder()
{
hdr.toNetworkByteOrder();
code = netPlayByteSwap(code);
flags = netPlayByteSwap(flags);
}
};
struct netPlayLoadRomReq
@ -128,6 +181,18 @@ struct netPlayLoadRomReq
{
memset(fileName, 0, sizeof(fileName));
}
void toHostByteOrder()
{
hdr.toHostByteOrder();
fileSize = netPlayByteSwap(fileSize);
}
void toNetworkByteOrder()
{
hdr.toNetworkByteOrder();
fileSize = netPlayByteSwap(fileSize);
}
};
@ -144,6 +209,20 @@ struct netPlayRunFrameReq
{
memset( ctrlState, 0, sizeof(ctrlState) );
}
void toHostByteOrder()
{
hdr.toHostByteOrder();
flags = netPlayByteSwap(flags);
frameNum = netPlayByteSwap(frameNum);
}
void toNetworkByteOrder()
{
hdr.toNetworkByteOrder();
flags = netPlayByteSwap(flags);
frameNum = netPlayByteSwap(frameNum);
}
};
struct netPlayClientState
@ -151,15 +230,41 @@ struct netPlayClientState
netPlayMsgHdr hdr;
uint32_t flags;
uint32_t frameRdy;
uint32_t frameRdy; // What frame we have input ready for
uint32_t frameRun;
uint32_t opsFrame; // Last frame for ops data
uint32_t opsChkSum;
uint32_t ramChkSum;
uint8_t ctrlState[4];
netPlayClientState(void)
: hdr(NETPLAY_CLIENT_STATE, sizeof(netPlayClientState)), flags(0), frameRdy(0), frameRun(0)
: hdr(NETPLAY_CLIENT_STATE, sizeof(netPlayClientState)), flags(0),
frameRdy(0), frameRun(0), opsChkSum(0), ramChkSum(0)
{
memset( ctrlState, 0, sizeof(ctrlState) );
}
void toHostByteOrder()
{
hdr.toHostByteOrder();
flags = netPlayByteSwap(flags);
frameRdy = netPlayByteSwap(frameRdy);
frameRun = netPlayByteSwap(frameRun);
opsFrame = netPlayByteSwap(opsFrame);
opsChkSum = netPlayByteSwap(opsChkSum);
ramChkSum = netPlayByteSwap(ramChkSum);
}
void toNetworkByteOrder()
{
hdr.toNetworkByteOrder();
flags = netPlayByteSwap(flags);
frameRdy = netPlayByteSwap(frameRdy);
frameRun = netPlayByteSwap(frameRun);
opsFrame = netPlayByteSwap(opsFrame);
opsChkSum = netPlayByteSwap(opsChkSum);
ramChkSum = netPlayByteSwap(ramChkSum);
}
};

View File

@ -59,6 +59,7 @@
#include "common/os_utils.h"
#include "utils/StringBuilder.h"
#include "Qt/NetPlay.h"
#include "Qt/ConsoleDebugger.h"
#include "Qt/ConsoleWindow.h"
#include "Qt/ConsoleUtilities.h"
@ -1188,9 +1189,13 @@ void FCEUD_FlushTrace()
}
//----------------------------------------------------
//todo: really speed this up
void FCEUD_TraceInstruction(uint8 *opcode, int size)
{
if (NetPlayActive())
{
NetPlayTraceInstruction(opcode, size);
}
if (!logging)
return;