ggpo: exchange verification data during sync

This commit is contained in:
Flyinghead 2021-10-01 16:07:32 +02:00
parent a6248905a0
commit 45ebc2239f
10 changed files with 95 additions and 21 deletions

View File

@ -114,7 +114,8 @@ typedef struct GGPOLocalEndpoint {
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_TOO_MANY_SPECTATORS, 10) \
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INVALID_REQUEST, 11) \
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INPUT_SIZE_DIFF, 12) \
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_NETWORK_ERROR, 13)
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_NETWORK_ERROR, 13) \
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_VERIFICATION_ERROR, 14)
#define GGPO_ERRORLIST_ENTRY(name, value) name = value,
typedef enum {
@ -332,13 +333,22 @@ typedef struct GGPONetworkStats {
* input_size - The size of the game inputs which will be passsed to ggpo_add_local_input.
*
* local_port - The port GGPO should bind to for UDP traffic.
*
* verification - Some optional data that will be matched with the peers during initial sync.
* Can be set to null if verification_size is zero. This can be used to check that all peers are
* running the same version of the application and of the game, are using the same settings, etc.
*
* verification_size - Size of the verification data. Can be set to zero to not exchange verification data.
* Maximum size is 256 bytes.
*/
GGPO_API GGPOErrorCode __cdecl ggpo_start_session(GGPOSession **session,
GGPOSessionCallbacks *cb,
const char *game,
int num_players,
int input_size,
unsigned short localport);
unsigned short localport,
const void *verification,
int verification_size);
/*
@ -412,6 +422,13 @@ GGPO_API GGPOErrorCode __cdecl ggpo_start_synctest(GGPOSession **session,
* player partcipating in the session can serve as a host.
*
* host_port - The port of the session on the host
*
* verification - Some optional data that will be matched with the peers during initial sync.
* Can be set to null if verification_size is zero. This can be used to check that all peers are
* running the same version of the application and of the game, are using the same settings, etc.
*
* verification_size - Size of the verification data. Can be set to zero to not exchange verification data.
* Maximum size is 256 bytes.
*/
GGPO_API GGPOErrorCode __cdecl ggpo_start_spectating(GGPOSession **session,
GGPOSessionCallbacks *cb,
@ -420,7 +437,9 @@ GGPO_API GGPOErrorCode __cdecl ggpo_start_spectating(GGPOSession **session,
int input_size,
unsigned short local_port,
char *host_ip,
unsigned short host_port);
unsigned short host_port,
const void *verification,
int verification_size);
/*
* ggpo_close_session --

View File

@ -17,7 +17,9 @@ Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb,
const char *gamename,
uint16 localport,
int num_players,
int input_size) :
int input_size,
const void *verification,
int verification_size) :
_sync(_local_connect_status),
_endpoints(nullptr),
_num_spectators(0),
@ -47,6 +49,8 @@ Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb,
_udp.Init(localport, &_poll, this);
_endpoints = new UdpProtocol[_num_players];
for (int i = 0; i < _num_players; i++)
_endpoints[i].SetVerificationData(verification, verification_size);
memset(_local_connect_status, 0, sizeof(_local_connect_status));
for (unsigned i = 0; i < ARRAY_SIZE(_local_connect_status); i++) {
_local_connect_status[i].last_frame = -1;

View File

@ -17,7 +17,8 @@
class Peer2PeerBackend : public IQuarkBackend, IPollSink, Udp::Callbacks {
public:
Peer2PeerBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size);
Peer2PeerBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size,
const void *verification, int verification_size);
virtual ~Peer2PeerBackend();

View File

@ -13,7 +13,9 @@ SpectatorBackend::SpectatorBackend(GGPOSessionCallbacks *cb,
int num_players,
int input_size,
char *hostip,
u_short hostport) :
u_short hostport,
const void *verification,
int verification_size) :
_input_size(input_size),
_num_players(num_players),
_next_input_to_send(0)
@ -33,6 +35,7 @@ SpectatorBackend::SpectatorBackend(GGPOSessionCallbacks *cb,
/*
* Init the host endpoint
*/
_host.SetVerificationData(verification, verification_size);
_host.Init(&_udp, _poll, 0, hostip, hostport, NULL);
_host.Synchronize();

View File

@ -19,7 +19,8 @@
class SpectatorBackend : public IQuarkBackend, IPollSink, Udp::Callbacks {
public:
SpectatorBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size, char *hostip, u_short hostport);
SpectatorBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size, char *hostip, u_short hostport,
const void *verification, int verification_size);
virtual ~SpectatorBackend();

View File

@ -45,14 +45,18 @@ ggpo_start_session(GGPOSession **session,
const char *game,
int num_players,
int input_size,
unsigned short localport)
unsigned short localport,
const void *verification,
int verification_size)
{
try {
*session= (GGPOSession *)new Peer2PeerBackend(cb,
game,
localport,
num_players,
input_size);
input_size,
verification,
verification_size);
return GGPO_OK;
} catch (const GGPOException& e) {
Log("GGPOException in ggpo_start_session: %s", e.what());
@ -252,7 +256,9 @@ GGPOErrorCode ggpo_start_spectating(GGPOSession **session,
int input_size,
unsigned short local_port,
char *host_ip,
unsigned short host_port)
unsigned short host_port,
const void *verification,
int verification_size)
{
try {
*session= (GGPOSession *)new SpectatorBackend(cb,
@ -261,7 +267,9 @@ GGPOErrorCode ggpo_start_spectating(GGPOSession **session,
num_players,
input_size,
host_ip,
host_port);
host_port,
verification,
verification_size);
return GGPO_OK;
} catch (const GGPOException& e) {
Log("GGPOException in ggpo_start_spectating: %s", e.what());

View File

@ -10,6 +10,7 @@
#define MAX_COMPRESSED_BITS 4096
#define UDP_MSG_MAX_PLAYERS 4
#define MAX_VERIFICATION_SIZE 256
#pragma pack(push, 1)
@ -41,10 +42,12 @@ struct UdpMsg
uint32 random_request; /* please reply back with this random data */
uint16 remote_magic;
uint8 remote_endpoint;
uint8 verification[MAX_VERIFICATION_SIZE];
} sync_request;
struct {
uint32 random_reply; /* OK, here's your random data back */
uint8 verification_failure; /* set to one by peer if verification failed */
} sync_reply;
struct {
@ -75,6 +78,8 @@ struct UdpMsg
} u;
int verification_size = 0;
public:
int PacketSize() {
return sizeof(hdr) + PayloadSize();
@ -84,7 +89,7 @@ public:
int size;
switch (hdr.type) {
case SyncRequest: return sizeof(u.sync_request);
case SyncRequest: return (int)(&u.sync_request.verification[0] - (uint8 *)&u) + verification_size;
case SyncReply: return sizeof(u.sync_reply);
case QualityReport: return sizeof(u.quality_report);
case QualityReply: return sizeof(u.quality_reply);

View File

@ -272,6 +272,9 @@ UdpProtocol::SendSyncRequest()
_state.sync.random = rand() & 0xFFFF;
UdpMsg *msg = new UdpMsg(UdpMsg::SyncRequest);
msg->u.sync_request.random_request = _state.sync.random;
msg->verification_size = verification.size();
if (!verification.empty())
memcpy(&msg->u.sync_request.verification[0], &verification[0], verification.size());
SendMsg(msg);
}
@ -477,14 +480,25 @@ UdpProtocol::OnSyncRequest(UdpMsg *msg, int len)
msg->hdr.magic, _remote_magic_number);
return false;
}
UdpMsg *reply = new UdpMsg(UdpMsg::SyncReply);
reply->u.sync_reply.random_reply = msg->u.sync_request.random_request;
int msgVerifSize = len - msg->PacketSize();
if (msgVerifSize != (int)verification.size()
|| (msgVerifSize != 0 && memcmp(&msg->u.sync_request.verification[0], &verification[0], msgVerifSize)))
{
Log("Verification mismatch: size received %d expected %d", msgVerifSize, (int)verification.size());
reply->u.sync_reply.verification_failure = 1;
SendMsg(reply);
throw GGPOException("Verification mismatch", GGPO_ERRORCODE_VERIFICATION_ERROR);
}
// FIXME
if (_state.sync.roundtrips_remaining == NUM_SYNC_PACKETS && msg->hdr.sequence_number == 0) {
Log("Sync request 0 received... Re-queueing sync packet.\n");
SendSyncRequest();
}
UdpMsg *reply = new UdpMsg(UdpMsg::SyncReply);
reply->u.sync_reply.random_reply = msg->u.sync_request.random_request;
reply->u.sync_reply.verification_failure = 0;
SendMsg(reply);
return true;
@ -503,6 +517,8 @@ UdpProtocol::OnSyncReply(UdpMsg *msg, int len)
msg->u.sync_reply.random_reply, _state.sync.random);
return false;
}
if (msg->u.sync_reply.verification_failure == 1)
throw GGPOException("Peer reported verification failure", GGPO_ERRORCODE_VERIFICATION_ERROR);
if (!_connected) {
QueueEvent(Event(Event::Connected));

View File

@ -15,6 +15,7 @@
#include "timesync.h"
#include "ggponet.h"
#include "ring_buffer.h"
#include <vector>
class UdpProtocol : public IPollSink
{
@ -81,6 +82,11 @@ public:
void GGPONetworkStats(Stats *stats);
void SetLocalFrameNumber(int num);
int RecommendFrameDelay();
void SetVerificationData(const void *verification, int verification_size) {
ASSERT(verification_size <= MAX_VERIFICATION_SIZE);
this->verification.resize(verification_size);
memcpy(&this->verification[0], verification, verification_size);
}
void SetDisconnectTimeout(int timeout);
void SetDisconnectNotifyStart(int timeout);
@ -141,6 +147,8 @@ protected:
} _oo_packet;
RingBuffer<QueueEntry, 64> _send_queue;
std::vector<uint8> verification;
/*
* Stats
*/

View File

@ -27,9 +27,6 @@ void UpdateInputState();
namespace ggpo
{
constexpr u32 BTN_TRIGGER_LEFT = DC_BTN_RELOAD << 1;
constexpr u32 BTN_TRIGGER_RIGHT = DC_BTN_RELOAD << 2;
static void getLocalInput(MapleInputState inputState[4])
{
if (!config::ThreadedRendering)
@ -74,9 +71,13 @@ namespace ggpo
{
using namespace std::chrono;
constexpr int ProtocolVersion = 1;
constexpr int MAX_PLAYERS = 2;
constexpr int SERVER_PORT = 19713;
constexpr u32 BTN_TRIGGER_LEFT = DC_BTN_RELOAD << 1;
constexpr u32 BTN_TRIGGER_RIGHT = DC_BTN_RELOAD << 2;
static GGPOSession *ggpoSession;
static int localPlayerNum;
static GGPOPlayerHandle localPlayer;
@ -408,7 +409,8 @@ void startSession(int localPort, int localPlayerNum)
NOTICE_LOG(NETWORK, "GGPO: Using %d full analog axes", analogAxes);
}
u32 inputSize = sizeof(kcode[0]) + analogAxes + (int)absPointerPos * 4;
GGPOErrorCode result = ggpo_start_session(&ggpoSession, &cb, settings.content.gameId.c_str(), MAX_PLAYERS, inputSize, localPort);
GGPOErrorCode result = ggpo_start_session(&ggpoSession, &cb, settings.content.gameId.c_str(), MAX_PLAYERS, inputSize, localPort,
&ProtocolVersion, sizeof(ProtocolVersion));
if (result != GGPO_OK)
{
WARN_LOG(NETWORK, "GGPO start session failed: %d", result);
@ -635,7 +637,14 @@ std::future<bool> startNetwork()
std::lock_guard<std::recursive_mutex> lock(ggpoMutex);
if (ggpoSession == nullptr)
break;
ggpo_idle(ggpoSession, 0);
GGPOErrorCode result = ggpo_idle(ggpoSession, 0);
if (result == GGPO_ERRORCODE_VERIFICATION_ERROR)
throw FlycastException("Peer verification failed");
else if (result != GGPO_OK)
{
WARN_LOG(NETWORK, "ggpo_idle failed %d", result);
throw FlycastException("GGPO error");
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
@ -643,8 +652,8 @@ std::future<bool> startNetwork()
// save initial state (frame 0)
if (active())
{
u32 k[4];
getInput(k);
MapleInputState state[4];
getInput(state);
}
#endif
emu.setNetworkState(active());