From 45ebc2239f760d510433cb3ca29bde2044a8ec74 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Fri, 1 Oct 2021 16:07:32 +0200 Subject: [PATCH] ggpo: exchange verification data during sync --- core/deps/ggpo/include/ggponet.h | 25 ++++++++++++++++--- core/deps/ggpo/lib/ggpo/backends/p2p.cpp | 6 ++++- core/deps/ggpo/lib/ggpo/backends/p2p.h | 3 ++- .../deps/ggpo/lib/ggpo/backends/spectator.cpp | 5 +++- core/deps/ggpo/lib/ggpo/backends/spectator.h | 3 ++- core/deps/ggpo/lib/ggpo/main.cpp | 16 +++++++++--- core/deps/ggpo/lib/ggpo/network/udp_msg.h | 7 +++++- core/deps/ggpo/lib/ggpo/network/udp_proto.cpp | 20 +++++++++++++-- core/deps/ggpo/lib/ggpo/network/udp_proto.h | 8 ++++++ core/network/ggpo.cpp | 23 +++++++++++------ 10 files changed, 95 insertions(+), 21 deletions(-) diff --git a/core/deps/ggpo/include/ggponet.h b/core/deps/ggpo/include/ggponet.h index 6da009ff5..cfa7d4f0e 100644 --- a/core/deps/ggpo/include/ggponet.h +++ b/core/deps/ggpo/include/ggponet.h @@ -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 -- diff --git a/core/deps/ggpo/lib/ggpo/backends/p2p.cpp b/core/deps/ggpo/lib/ggpo/backends/p2p.cpp index 4650c8b63..e7f17516e 100644 --- a/core/deps/ggpo/lib/ggpo/backends/p2p.cpp +++ b/core/deps/ggpo/lib/ggpo/backends/p2p.cpp @@ -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; diff --git a/core/deps/ggpo/lib/ggpo/backends/p2p.h b/core/deps/ggpo/lib/ggpo/backends/p2p.h index 9b9ae71c4..83e4acf0c 100644 --- a/core/deps/ggpo/lib/ggpo/backends/p2p.h +++ b/core/deps/ggpo/lib/ggpo/backends/p2p.h @@ -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(); diff --git a/core/deps/ggpo/lib/ggpo/backends/spectator.cpp b/core/deps/ggpo/lib/ggpo/backends/spectator.cpp index 7fe0688f8..91ab3cb6f 100644 --- a/core/deps/ggpo/lib/ggpo/backends/spectator.cpp +++ b/core/deps/ggpo/lib/ggpo/backends/spectator.cpp @@ -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(); diff --git a/core/deps/ggpo/lib/ggpo/backends/spectator.h b/core/deps/ggpo/lib/ggpo/backends/spectator.h index 51eb9c444..fc7f6ac19 100644 --- a/core/deps/ggpo/lib/ggpo/backends/spectator.h +++ b/core/deps/ggpo/lib/ggpo/backends/spectator.h @@ -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(); diff --git a/core/deps/ggpo/lib/ggpo/main.cpp b/core/deps/ggpo/lib/ggpo/main.cpp index 6b4f435e5..dc424c623 100644 --- a/core/deps/ggpo/lib/ggpo/main.cpp +++ b/core/deps/ggpo/lib/ggpo/main.cpp @@ -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()); diff --git a/core/deps/ggpo/lib/ggpo/network/udp_msg.h b/core/deps/ggpo/lib/ggpo/network/udp_msg.h index 495a002e6..852461118 100644 --- a/core/deps/ggpo/lib/ggpo/network/udp_msg.h +++ b/core/deps/ggpo/lib/ggpo/network/udp_msg.h @@ -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); diff --git a/core/deps/ggpo/lib/ggpo/network/udp_proto.cpp b/core/deps/ggpo/lib/ggpo/network/udp_proto.cpp index c206ce41b..f03845a1c 100644 --- a/core/deps/ggpo/lib/ggpo/network/udp_proto.cpp +++ b/core/deps/ggpo/lib/ggpo/network/udp_proto.cpp @@ -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)); diff --git a/core/deps/ggpo/lib/ggpo/network/udp_proto.h b/core/deps/ggpo/lib/ggpo/network/udp_proto.h index 2adb8c6b7..ad3bf8cff 100644 --- a/core/deps/ggpo/lib/ggpo/network/udp_proto.h +++ b/core/deps/ggpo/lib/ggpo/network/udp_proto.h @@ -15,6 +15,7 @@ #include "timesync.h" #include "ggponet.h" #include "ring_buffer.h" +#include 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 _send_queue; + std::vector verification; + /* * Stats */ diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index 5482e8a82..e8f8e8ca4 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -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 startNetwork() std::lock_guard 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 startNetwork() // save initial state (frame 0) if (active()) { - u32 k[4]; - getInput(k); + MapleInputState state[4]; + getInput(state); } #endif emu.setNetworkState(active());