ggpo: exchange verification data during sync
This commit is contained in:
parent
a6248905a0
commit
45ebc2239f
|
@ -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 --
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue