diff --git a/dep/ggpo-x/LICENSE b/dep/ggpo-x/LICENSE
new file mode 100644
index 000000000..0fae5e81c
--- /dev/null
+++ b/dep/ggpo-x/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2009-2019 GroundStorm Studios, LLC. (http://ggpo.net)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/dep/ggpo-x/ggpo-x.vcxproj b/dep/ggpo-x/ggpo-x.vcxproj
new file mode 100644
index 000000000..cf78e9bdf
--- /dev/null
+++ b/dep/ggpo-x/ggpo-x.vcxproj
@@ -0,0 +1,53 @@
+
+
+
+
+ {EDF3634A-CE8A-4625-92BD-27BAD5D30A9A}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TurnOffAllWarnings
+ _WINDOWS;%(PreprocessorDefinitions)
+ $(ProjectDir)src;$(ProjectDir)include;%(AdditionalIncludeDirectories)
+
+
+
+
\ No newline at end of file
diff --git a/dep/ggpo-x/ggpo-x.vcxproj.filters b/dep/ggpo-x/ggpo-x.vcxproj.filters
new file mode 100644
index 000000000..6f2e6dfd4
--- /dev/null
+++ b/dep/ggpo-x/ggpo-x.vcxproj.filters
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ backends
+
+
+ backends
+
+
+ backends
+
+
+ backends
+
+
+ network
+
+
+ network
+
+
+ network
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ backends
+
+
+ backends
+
+
+ backends
+
+
+ network
+
+
+ network
+
+
+
+
+ {9734509d-391e-44d7-bbce-e3bc73dfcea3}
+
+
+ {1618c8a6-e50c-419c-8476-cb1c09fd8dce}
+
+
+
\ No newline at end of file
diff --git a/dep/ggpo-x/include/ggponet.h b/dep/ggpo-x/include/ggponet.h
new file mode 100644
index 000000000..0b7dbf35b
--- /dev/null
+++ b/dep/ggpo-x/include/ggponet.h
@@ -0,0 +1,607 @@
+/* -----------------------------------------------------------------------
+ * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
+ *
+ * Use of this software is governed by the MIT license that can be found
+ * in the LICENSE file.
+ */
+
+#ifndef _GGPONET_H_
+#define _GGPONET_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+// On windows, export at build time and import at runtime.
+// ELF systems don't need an explicit export/import.
+#ifdef _WIN32
+# if defined(GGPO_SHARED_LIB)
+# ifdef GGPO_SDK_EXPORT
+# define GGPO_API __declspec(dllexport)
+# else
+# define GGPO_API __declspec(dllimport)
+# endif
+# else
+# define GGPO_API
+# endif
+#else
+# define GGPO_API
+#endif
+
+#define GGPO_MAX_PLAYERS 4
+//#define GGPO_MAX_PREDICTION_FRAMES 8
+#define GGPO_MAX_SPECTATORS 32
+
+#define GGPO_SPECTATOR_INPUT_INTERVAL 4
+
+typedef class GGPOSession GGPOSession;
+
+typedef int GGPOPlayerHandle;
+
+typedef enum {
+ GGPO_PLAYERTYPE_LOCAL,
+ GGPO_PLAYERTYPE_REMOTE,
+ GGPO_PLAYERTYPE_SPECTATOR,
+} GGPOPlayerType;
+
+/*
+ * The GGPOPlayer structure used to describe players in ggpo_add_player
+ *
+ * size: Should be set to the sizeof(GGPOPlayer)
+ *
+ * type: One of the GGPOPlayerType values describing how inputs should be handled
+ * Local players must have their inputs updated every frame via
+ * ggpo_add_local_inputs. Remote players values will come over the
+ * network.
+ *
+ * player_num: The player number. Should be between 1 and the number of players
+ * In the game (e.g. in a 2 player game, either 1 or 2).
+ *
+ * If type == GGPO_PLAYERTYPE_REMOTE:
+ *
+ * u.remote.ip_address: The ip address of the ggpo session which will host this
+ * player.
+ *
+ * u.remote.port: The port where udp packets should be sent to reach this player.
+ * All the local inputs for this session will be sent to this player at
+ * ip_address:port.
+ *
+ */
+
+typedef struct GGPOPlayer {
+ int size;
+ GGPOPlayerType type;
+ int player_num;
+ union {
+ struct {
+ } local;
+ struct {
+ char ip_address[32];
+ unsigned short port;
+ } remote;
+ } u;
+} GGPOPlayer;
+
+typedef struct GGPOLocalEndpoint {
+ int player_num;
+} GGPOLocalEndpoint;
+
+
+#define GGPO_ERRORLIST \
+ GGPO_ERRORLIST_ENTRY(GGPO_OK, 0) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_SUCCESS, 0) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_GENERAL_FAILURE, -1) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INVALID_SESSION, 1) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INVALID_PLAYER_HANDLE, 2) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_PLAYER_OUT_OF_RANGE, 3) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_PREDICTION_THRESHOLD, 4) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_UNSUPPORTED, 5) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_NOT_SYNCHRONIZED, 6) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_IN_ROLLBACK, 7) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INPUT_DROPPED, 8) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_PLAYER_DISCONNECTED, 9) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_TOO_MANY_SPECTATORS, 10) \
+ GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INVALID_REQUEST, 11) \
+ GGPO_ERRORLIST_ENTRY(GGPO_CHAT_MESSAGE_TOO_LONG, 12)
+
+#define GGPO_ERRORLIST_ENTRY(name, value) name = value,
+typedef enum {
+ GGPO_ERRORLIST
+} GGPOErrorCode;
+#undef GGPO_ERRORLIST_ENTRY
+
+#define GGPO_SUCCEEDED(result) ((result) == GGPO_ERRORCODE_SUCCESS)
+
+
+#define GGPO_INVALID_HANDLE (-1)
+
+
+/*
+ * The GGPOEventCode enumeration describes what type of event just happened.
+ *
+ * GGPO_EVENTCODE_CONNECTED_TO_PEER - Handshake with the game running on the
+ * other side of the network has been completed.
+ *
+ * GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER - Beginning the synchronization
+ * process with the client on the other end of the networking. The count
+ * and total fields in the u.synchronizing struct of the GGPOEvent
+ * object indicate progress.
+ *
+ * GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER - The synchronziation with this
+ * peer has finished.
+ *
+ * GGPO_EVENTCODE_RUNNING - All the clients have synchronized. You may begin
+ * sending inputs with ggpo_synchronize_inputs.
+ *
+ * GGPO_EVENTCODE_DISCONNECTED_FROM_PEER - The network connection on
+ * the other end of the network has closed.
+ *
+ * GGPO_EVENTCODE_TIMESYNC - The time synchronziation code has determined
+ * that this client is too far ahead of the other one and should slow
+ * down to ensure fairness. The u.timesync.frames_ahead parameter in
+ * the GGPOEvent object indicates how many frames the client is.
+ *
+ */
+typedef enum {
+ GGPO_EVENTCODE_CONNECTED_TO_PEER = 1000,
+ GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER = 1001,
+ GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER = 1002,
+ GGPO_EVENTCODE_RUNNING = 1003,
+ GGPO_EVENTCODE_DISCONNECTED_FROM_PEER = 1004,
+ GGPO_EVENTCODE_TIMESYNC = 1005,
+ GGPO_EVENTCODE_CONNECTION_INTERRUPTED = 1006,
+ GGPO_EVENTCODE_CONNECTION_RESUMED = 1007,
+ GGPO_EVENTCODE_CHAT = 1008,
+ GGPO_EVENTCODE_DESYNC = 1009
+} GGPOEventCode;
+
+/*
+ * The GGPOEvent structure contains an asynchronous event notification sent
+ * by the on_event callback. See GGPOEventCode, above, for a detailed
+ * explanation of each event.
+ */
+typedef struct {
+ GGPOEventCode code;
+ union {
+ struct {
+ GGPOPlayerHandle player;
+ } connected;
+ struct {
+ GGPOPlayerHandle player;
+ int count;
+ int total;
+ } synchronizing;
+ struct {
+ GGPOPlayerHandle player;
+ } synchronized;
+ struct {
+ GGPOPlayerHandle player;
+ } disconnected;
+ struct {
+ float frames_ahead;
+ int timeSyncPeriodInFrames;
+ } timesync;
+ struct {
+ GGPOPlayerHandle player;
+ int disconnect_timeout;
+ } connection_interrupted;
+ struct {
+ GGPOPlayerHandle player;
+ } connection_resumed;
+ struct {
+ int senderID;
+ const char* msg;
+ } chat;
+ struct {
+ int nFrameOfDesync;
+ uint16_t ourCheckSum;
+ uint16_t remoteChecksum;
+ } desync;
+ } u;
+} GGPOEvent;
+
+/*
+ * The GGPOSessionCallbacks structure contains the callback functions that
+ * your application must implement. GGPO.net will periodically call these
+ * functions during the game. All callback functions must be implemented.
+ */
+typedef struct {
+ /*
+ * begin_game callback - This callback has been deprecated. You must
+ * implement it, but should ignore the 'game' parameter.
+ */
+ bool (__cdecl *begin_game)(void* context, const char *game);
+
+ /*
+ * save_game_state - The client should allocate a buffer, copy the
+ * entire contents of the current game state into it, and copy the
+ * length into the *len parameter. Optionally, the client can compute
+ * a checksum of the data and store it in the *checksum argument.
+ */
+ bool (__cdecl *save_game_state)(void* context, unsigned char **buffer, int *len, int *checksum, int frame);
+
+ /*
+ * load_game_state - GGPO.net will call this function at the beginning
+ * of a rollback. The buffer and len parameters contain a previously
+ * saved state returned from the save_game_state function. The client
+ * should make the current game state match the state contained in the
+ * buffer.
+ */
+ bool (__cdecl *load_game_state)(void* context, unsigned char *buffer, int len, int framesToRollback);
+
+ /*
+ * log_game_state - Used in diagnostic testing. The client should use
+ * the ggpo_log function to write the contents of the specified save
+ * state in a human readible form.
+ */
+ bool (__cdecl *log_game_state)(void* context, char *filename, unsigned char *buffer, int len);
+
+ /*
+ * free_buffer - Frees a game state allocated in save_game_state. You
+ * should deallocate the memory contained in the buffer.
+ */
+ void (__cdecl *free_buffer)(void* context, void *buffer);
+
+ /*
+ * advance_frame - Called during a rollback. You should advance your game
+ * state by exactly one frame. Before each frame, call ggpo_synchronize_input
+ * to retrieve the inputs you should use for that frame. After each frame,
+ * you should call ggpo_advance_frame to notify GGPO.net that you're
+ * finished.
+ *
+ * The flags parameter is reserved. It can safely be ignored at this time.
+ */
+ bool (__cdecl *advance_frame)(void* context, int flags);
+
+ /*
+ * on_event - Notification that something has happened. See the GGPOEventCode
+ * structure above for more information.
+ */
+ bool (__cdecl *on_event)(void* context, GGPOEvent *info);
+
+ /*
+ * Calling context
+ */
+ void* context;
+} GGPOSessionCallbacks;
+
+/*
+ * The GGPONetworkStats function contains some statistics about the current
+ * session.
+ *
+ * network.send_queue_len - The length of the queue containing UDP packets
+ * which have not yet been acknowledged by the end client. The length of
+ * the send queue is a rough indication of the quality of the connection.
+ * The longer the send queue, the higher the round-trip time between the
+ * clients. The send queue will also be longer than usual during high
+ * packet loss situations.
+ *
+ * network.recv_queue_len - The number of inputs currently buffered by the
+ * GGPO.net network layer which have yet to be validated. The length of
+ * the prediction queue is roughly equal to the current frame number
+ * minus the frame number of the last packet in the remote queue.
+ *
+ * network.ping - The roundtrip packet transmission time as calcuated
+ * by GGPO.net. This will be roughly equal to the actual round trip
+ * packet transmission time + 2 the interval at which you call ggpo_idle
+ * or ggpo_advance_frame.
+ *
+ * network.kbps_sent - The estimated bandwidth used between the two
+ * clients, in kilobits per second.
+ *
+ * timesync.local_frames_behind - The number of frames GGPO.net calculates
+ * that the local client is behind the remote client at this instant in
+ * time. For example, if at this instant the current game client is running
+ * frame 1002 and the remote game client is running frame 1009, this value
+ * will mostly likely roughly equal 7.
+ *
+ * timesync.remote_frames_behind - The same as local_frames_behind, but
+ * calculated from the perspective of the remote player.
+ *
+ */
+typedef struct GGPONetworkStats {
+ struct {
+ int send_queue_len;
+ int recv_queue_len;
+ int ping;
+ int kbps_sent;
+ } network;
+ struct {
+ float local_frames_behind;
+ float remote_frames_behind;
+ float avg_local_frames_behind;
+ float avg_remote_frames_behind;
+ } timesync;
+} GGPONetworkStats;
+
+/*
+ * ggpo_start_session --
+ *
+ * Used to being a new GGPO.net session. The ggpo object returned by ggpo_start_session
+ * uniquely identifies the state for this session and should be passed to all other
+ * functions.
+ *
+ * session - An out parameter to the new ggpo session object.
+ *
+ * cb - A GGPOSessionCallbacks structure which contains the callbacks you implement
+ * to help GGPO.net synchronize the two games. You must implement all functions in
+ * cb, even if they do nothing but 'return true';
+ *
+ * game - The name of the game. This is used internally for GGPO for logging purposes only.
+ *
+ * num_players - The number of players which will be in this game. The number of players
+ * per session is fixed. If you need to change the number of players or any player
+ * disconnects, you must start a new session.
+ *
+ * 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.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_start_session(GGPOSession **session,
+ GGPOSessionCallbacks *cb,
+ const char *game,
+ int num_players,
+ int input_size,
+ unsigned short localport,
+ int maxPrediction);
+
+
+/*
+ * ggpo_add_player --
+ *
+ * Must be called for each player in the session (e.g. in a 3 player session, must
+ * be called 3 times).
+ *
+ * player - A GGPOPlayer struct used to describe the player.
+ *
+ * handle - An out parameter to a handle used to identify this player in the future.
+ * (e.g. in the on_event callbacks).
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_add_player(GGPOSession *session,
+ GGPOPlayer *player,
+ GGPOPlayerHandle *handle);
+
+
+/*
+ * ggpo_start_synctest --
+ *
+ * Used to being a new GGPO.net sync test session. During a sync test, every
+ * frame of execution is run twice: once in prediction mode and once again to
+ * verify the result of the prediction. If the checksums of your save states
+ * do not match, the test is aborted.
+ *
+ * cb - A GGPOSessionCallbacks structure which contains the callbacks you implement
+ * to help GGPO.net synchronize the two games. You must implement all functions in
+ * cb, even if they do nothing but 'return true';
+ *
+ * game - The name of the game. This is used internally for GGPO for logging purposes only.
+ *
+ * num_players - The number of players which will be in this game. The number of players
+ * per session is fixed. If you need to change the number of players or any player
+ * disconnects, you must start a new session.
+ *
+ * input_size - The size of the game inputs which will be passsed to ggpo_add_local_input.
+ *
+ * frames - The number of frames to run before verifying the prediction. The
+ * recommended value is 1.
+ *
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_start_synctest(GGPOSession **session,
+ GGPOSessionCallbacks *cb,
+ char *game,
+ int num_players,
+ int input_size,
+ int frames);
+
+
+/*
+ * ggpo_start_spectating --
+ *
+ * Start a spectator session.
+ *
+ * cb - A GGPOSessionCallbacks structure which contains the callbacks you implement
+ * to help GGPO.net synchronize the two games. You must implement all functions in
+ * cb, even if they do nothing but 'return true';
+ *
+ * game - The name of the game. This is used internally for GGPO for logging purposes only.
+ *
+ * num_players - The number of players which will be in this game. The number of players
+ * per session is fixed. If you need to change the number of players or any player
+ * disconnects, you must start a new session.
+ *
+ * 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.
+ *
+ * host_ip - The IP address of the host who will serve you the inputs for the game. Any
+ * player partcipating in the session can serve as a host.
+ *
+ * host_port - The port of the session on the host
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_start_spectating(GGPOSession **session,
+ GGPOSessionCallbacks *cb,
+ const char *game,
+ int num_players,
+ int input_size,
+ unsigned short local_port,
+ char *host_ip,
+ unsigned short host_port);
+
+/*
+ * ggpo_close_session --
+ * Used to close a session. You must call ggpo_close_session to
+ * free the resources allocated in ggpo_start_session.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_close_session(GGPOSession *);
+
+
+/*
+ * ggpo_set_frame_delay --
+ *
+ * Change the amount of frames ggpo will delay local input. Must be called
+ * before the first call to ggpo_synchronize_input.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_set_frame_delay(GGPOSession *,
+ GGPOPlayerHandle player,
+ int frame_delay);
+
+/*
+ * ggpo_idle --
+ * Should be called periodically by your application to give GGPO.net
+ * a chance to do some work. Most packet transmissions and rollbacks occur
+ * in ggpo_idle.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_idle(GGPOSession *);
+
+/*
+ * ggpo_add_local_input --
+ *
+ * Used to notify GGPO.net of inputs that should be trasmitted to remote
+ * players. ggpo_add_local_input must be called once every frame for
+ * all player of type GGPO_PLAYERTYPE_LOCAL.
+ *
+ * player - The player handle returned for this player when you called
+ * ggpo_add_local_player.
+ *
+ * values - The controller inputs for this player.
+ *
+ * size - The size of the controller inputs. This must be exactly equal to the
+ * size passed into ggpo_start_session.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_add_local_input(GGPOSession *,
+ GGPOPlayerHandle player,
+ void *values,
+ int size);
+/*
+ * ggpo_add_local_input --
+ *
+ * Used to notify GGPO.net of inputs that should be trasmitted to remote
+ * players. ggpo_add_local_input must be called once every frame for
+ * all player of type GGPO_PLAYERTYPE_LOCAL.
+ *
+ * player - The player handle returned for this player when you called
+ * ggpo_add_local_player.
+ *
+ * values - The controller inputs for this player.
+ *
+ * size - The size of the controller inputs. This must be exactly equal to the
+ * size passed into ggpo_start_session.
+ */
+
+GGPO_API GGPOErrorCode __cdecl ggpo_client_chat(GGPOSession *,
+ const char* message);
+
+/*
+ * ggpo_synchronize_input --
+ *
+ * You should call ggpo_synchronize_input before every frame of execution,
+ * including those frames which happen during rollback.
+ *
+ * values - When the function returns, the values parameter will contain
+ * inputs for this frame for all players. The values array must be at
+ * least (size * players) large.
+ *
+ * size - The size of the values array.
+ *
+ * disconnect_flags - Indicated whether the input in slot (1 << flag) is
+ * valid. If a player has disconnected, the input in the values array for
+ * that player will be zeroed and the i-th flag will be set. For example,
+ * if only player 3 has disconnected, disconnect flags will be 8 (i.e. 1 << 3).
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_synchronize_input(GGPOSession *,
+ void *values,
+ int size,
+ int *disconnect_flags);
+
+/*
+ * ggpo_disconnect_player --
+ *
+ * Disconnects a remote player from a game. Will return GGPO_ERRORCODE_PLAYER_DISCONNECTED
+ * if you try to disconnect a player who has already been disconnected.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_disconnect_player(GGPOSession *,
+ GGPOPlayerHandle player);
+
+/*
+ * ggpo_advance_frame --
+ *
+ * You should call ggpo_advance_frame to notify GGPO.net that you have
+ * advanced your gamestate by a single frame. You should call this everytime
+ * you advance the gamestate by a frame, even during rollbacks. GGPO.net
+ * may call your save_state callback before this function returns.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_advance_frame(GGPOSession *, uint16_t checksum);
+
+/*
+ * ggpo_get_current_frame -- current frame GGPO is dealing with
+ *
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_get_current_frame(GGPOSession* ggpo, int& nFrame);
+/*
+ * ggpo_get_network_stats --
+ *
+ * Used to fetch some statistics about the quality of the network connection.
+ *
+ * player - The player handle returned from the ggpo_add_player function you used
+ * to add the remote player.
+ *
+ * stats - Out parameter to the network statistics.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_get_network_stats(GGPOSession *,
+ GGPOPlayerHandle player,
+ GGPONetworkStats *stats);
+
+/*
+ * ggpo_set_disconnect_timeout --
+ *
+ * Sets the disconnect timeout. The session will automatically disconnect
+ * from a remote peer if it has not received a packet in the timeout window.
+ * You will be notified of the disconnect via a GGPO_EVENTCODE_DISCONNECTED_FROM_PEER
+ * event.
+ *
+ * Setting a timeout value of 0 will disable automatic disconnects.
+ *
+ * timeout - The time in milliseconds to wait before disconnecting a peer.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_timeout(GGPOSession *,
+ int timeout);
+
+/*
+ * ggpo_set_disconnect_notify_start --
+ *
+ * The time to wait before the first GGPO_EVENTCODE_NETWORK_INTERRUPTED timeout
+ * will be sent.
+ *
+ * timeout - The amount of time which needs to elapse without receiving a packet
+ * before the GGPO_EVENTCODE_NETWORK_INTERRUPTED event is sent.
+ */
+GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_notify_start(GGPOSession *,
+ int timeout);
+
+/*
+ * ggpo_log --
+ *
+ * Used to write to the ggpo.net log. In the current versions of the
+ * SDK, a log file is only generated if the "quark.log" environment
+ * variable is set to 1. This will change in future versions of the
+ * SDK.
+ */
+GGPO_API void __cdecl ggpo_log(GGPOSession *,
+ const char *fmt, ...);
+/*
+ * ggpo_logv --
+ *
+ * A varargs compatible version of ggpo_log. See ggpo_log for
+ * more details.
+ */
+GGPO_API void __cdecl ggpo_logv(GGPOSession *,
+ const char *fmt,
+ va_list args);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/dep/ggpo-x/src/backends/backend.h b/dep/ggpo-x/src/backends/backend.h
new file mode 100644
index 000000000..9b2466a8d
--- /dev/null
+++ b/dep/ggpo-x/src/backends/backend.h
@@ -0,0 +1,35 @@
+/* -----------------------------------------------------------------------
+ * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
+ *
+ * Use of this software is governed by the MIT license that can be found
+ * in the LICENSE file.
+ */
+
+#ifndef _BACKEND_H
+#define _BACKEND_H
+
+#include "ggponet.h"
+#include "types.h"
+
+class GGPOSession {
+public:
+ virtual ~GGPOSession() { }
+ virtual GGPOErrorCode DoPoll() = 0;
+ virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) = 0;
+ virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) = 0;
+ virtual GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags) = 0;
+ virtual GGPOErrorCode IncrementFrame(uint16_t checksum) = 0;
+ virtual GGPOErrorCode CurrentFrame(int& current) =0;
+ virtual GGPOErrorCode Chat(const char* text) = 0;// { return GGPO_OK; }
+ virtual GGPOErrorCode DisconnectPlayer(GGPOPlayerHandle handle) = 0;// { return GGPO_OK; }
+ virtual GGPOErrorCode GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle handle) { return GGPO_OK; }
+ virtual GGPOErrorCode Logv(const char *fmt, va_list list) { ::Logv(fmt, list); return GGPO_OK; }
+
+ virtual GGPOErrorCode SetFrameDelay(GGPOPlayerHandle player, int delay) { return GGPO_ERRORCODE_UNSUPPORTED; }
+ virtual GGPOErrorCode SetDisconnectTimeout(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; }
+ virtual GGPOErrorCode SetDisconnectNotifyStart(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; }
+};
+
+
+#endif
+
diff --git a/dep/ggpo-x/src/backends/p2p.cpp b/dep/ggpo-x/src/backends/p2p.cpp
new file mode 100644
index 000000000..ab090f9c9
--- /dev/null
+++ b/dep/ggpo-x/src/backends/p2p.cpp
@@ -0,0 +1,821 @@
+/* -----------------------------------------------------------------------
+ * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
+ *
+ * Use of this software is governed by the MIT license that can be found
+ * in the LICENSE file.
+ */
+
+#include "p2p.h"
+
+static const int RECOMMENDATION_INTERVAL = 120;
+static const int DEFAULT_DISCONNECT_TIMEOUT = 5000;
+static const int DEFAULT_DISCONNECT_NOTIFY_START = 750;
+
+Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb,
+ const char *gamename,
+ uint16 localport,
+ int num_players,
+ int input_size, int nframes) :
+ _num_players(num_players),
+ _input_size(input_size),
+ _sync(_local_connect_status, nframes),
+ _disconnect_timeout(DEFAULT_DISCONNECT_TIMEOUT),
+ _disconnect_notify_start(DEFAULT_DISCONNECT_NOTIFY_START),
+ _num_spectators(0),
+ _next_spectator_frame(0)
+{
+ _callbacks = *cb;
+ _synchronizing = true;
+ _next_recommended_sleep = 0;
+
+ /*
+ * Initialize the synchronziation layer
+ */
+ Sync::Config config = { 0 };
+ config.num_players = num_players;
+ config.input_size = input_size;
+ config.callbacks = _callbacks;
+ config.num_prediction_frames = nframes;
+ _sync.Init(config);
+
+ /*
+ * Initialize the UDP port
+ */
+ _udp.Init(localport, &_poll, this);
+
+ _endpoints.resize(_num_players);
+ memset(_local_connect_status, 0, sizeof(_local_connect_status));
+ for (int i = 0; i < ARRAY_SIZE(_local_connect_status); i++) {
+ _local_connect_status[i].last_frame = -1;
+ }
+
+ /*
+ * Preload the ROM
+ */
+ _callbacks.begin_game(_callbacks.context, gamename);
+}
+
+Peer2PeerBackend::~Peer2PeerBackend()
+{
+}
+
+void
+Peer2PeerBackend::AddRemotePlayer(char *ip,
+ uint16 port,
+ int queue)
+{
+ /*
+ * Start the state machine (xxx: no)
+ */
+ _synchronizing = true;
+
+ _endpoints[queue].Init(&_udp, _poll, queue, ip, port, _local_connect_status);
+ _endpoints[queue].SetDisconnectTimeout(_disconnect_timeout);
+ _endpoints[queue].SetDisconnectNotifyStart(_disconnect_notify_start);
+ _endpoints[queue].Synchronize();
+}
+
+GGPOErrorCode Peer2PeerBackend::AddSpectator(char *ip,
+ uint16 port)
+{
+ if (_num_spectators == GGPO_MAX_SPECTATORS) {
+ return GGPO_ERRORCODE_TOO_MANY_SPECTATORS;
+ }
+ /*
+ * Currently, we can only add spectators before the game starts.
+ */
+ if (!_synchronizing) {
+ return GGPO_ERRORCODE_INVALID_REQUEST;
+ }
+ int queue = _num_spectators++;
+
+ _spectators[queue].Init(&_udp, _poll, queue + 1000, ip, port, _local_connect_status);
+ _spectators[queue].SetDisconnectTimeout(_disconnect_timeout);
+ _spectators[queue].SetDisconnectNotifyStart(_disconnect_notify_start);
+ _spectators[queue].Synchronize();
+
+ return GGPO_OK;
+}
+void Peer2PeerBackend::CheckDesync()
+{
+ std::vector keysToRemove;
+ for (auto& ep : _endpoints)
+ {
+ for (const auto& pair : ep._remoteCheckSums)
+ {
+ auto checkSumFrame = pair.first;
+ auto remoteChecksum = pair.second;
+
+ if (_confirmedCheckSums.count(checkSumFrame))
+ {
+ keysToRemove.push_back(checkSumFrame);
+ auto localChecksum = _confirmedCheckSums.at(checkSumFrame);
+
+ if (remoteChecksum != localChecksum)
+ {
+ GGPOEvent info;
+ info.code = GGPO_EVENTCODE_DESYNC;
+ info.u.desync.nFrameOfDesync = checkSumFrame;
+ info.u.desync.ourCheckSum = localChecksum;
+ info.u.desync.remoteChecksum = remoteChecksum;
+ _callbacks.on_event(_callbacks.context, &info);
+
+ char buf[256];
+ sprintf_s<256>(buf, "DESYNC Checksum frame %d, local: %d, remote %d, size of checksum maps: %d,%d", checkSumFrame, localChecksum, remoteChecksum, (int)_confirmedCheckSums.size(), (int)ep._remoteCheckSums.size());
+ // OutputDebugStringA(buf);
+ }
+
+ if (checkSumFrame % 100 == 0)
+ {
+ char buf[256];
+ sprintf_s<256>(buf, "Checksum frame %d, local: %d, remote %d, size of checksum maps: %d,%d\n", checkSumFrame, localChecksum, remoteChecksum, (int)_confirmedCheckSums.size(), (int)ep._remoteCheckSums.size());
+ //OutputDebugStringA(buf);
+ }
+ }
+ }
+ for (auto k : keysToRemove)
+ {
+ ep._remoteCheckSums.erase(k);
+ }
+ }
+ for (auto k : keysToRemove)
+ {
+ char buf[256];
+ sprintf_s<256>(buf, "Erase checksums for frame %d\n",k);
+ // OutputDebugStringA(buf);
+ for (auto itr = _confirmedCheckSums.cbegin(); itr != _confirmedCheckSums.cend(); )
+ itr = (itr->first <=k) ? _confirmedCheckSums.erase(itr) : std::next(itr);
+
+ // ep._remoteCheckSums.erase(k);
+ }
+
+}
+GGPOErrorCode
+Peer2PeerBackend::DoPoll()
+{
+ // Pass on chat
+ for (int i = 0; i < _num_players; i++) {
+ _endpoints[i].ConsumeChat([&](const char* msg) {
+ GGPOEvent info;
+ info.u.chat.senderID = i;
+ info.code = GGPO_EVENTCODE_CHAT;
+ info.u.chat.msg = msg;
+ _callbacks.on_event(_callbacks.context, &info);
+ });
+ }
+
+ if (!_sync.InRollback()) {
+ _poll.Pump(0);
+
+ PollUdpProtocolEvents();
+ CheckDesync();
+ if (!_synchronizing) {
+ _sync.CheckSimulation();
+
+ // notify all of our endpoints of their local frame number for their
+ // next connection quality report
+ int current_frame = _sync.GetFrameCount();
+ for (int i = 0; i < _num_players; i++) {
+ _endpoints[i].SetLocalFrameNumber(current_frame);
+ }
+
+ int total_min_confirmed;
+ if (_num_players <= 2) {
+ total_min_confirmed = Poll2Players(current_frame);
+ } else {
+ total_min_confirmed = PollNPlayers(current_frame);
+ }
+
+ Log("last confirmed frame in p2p backend is %d.\n", total_min_confirmed);
+ if (total_min_confirmed >= 0) {
+ ASSERT(total_min_confirmed != INT_MAX);
+ if (_num_spectators > 0) {
+ while (_next_spectator_frame <= total_min_confirmed) {
+ Log("pushing frame %d to spectators.\n", _next_spectator_frame);
+
+ GameInput input;
+ input.frame = _next_spectator_frame;
+ input.size = _input_size * _num_players;
+ _sync.GetConfirmedInputs(input.bits, _input_size * _num_players, _next_spectator_frame);
+ for (int i = 0; i < _num_spectators; i++) {
+ _spectators[i].SendInput(input);
+ }
+ _next_spectator_frame++;
+ }
+ }
+ Log("setting confirmed frame in sync to %d.\n", total_min_confirmed);
+ _sync.SetLastConfirmedFrame(total_min_confirmed);
+ }
+
+ // send timesync notifications if now is the proper time
+ if (current_frame > _next_recommended_sleep) {
+ float interval = 0;
+ for (int i = 0; i < _num_players; i++) {
+ interval = BIGGEST(interval, _endpoints[i].RecommendFrameDelay());
+ }
+
+ //if (interval > 0)
+ {
+ GGPOEvent info;
+ info.code = GGPO_EVENTCODE_TIMESYNC;
+ info.u.timesync.frames_ahead = interval;
+ info.u.timesync.timeSyncPeriodInFrames = RECOMMENDATION_INTERVAL;
+ _callbacks.on_event(_callbacks.context, &info);
+ _next_recommended_sleep = current_frame + RECOMMENDATION_INTERVAL;// RECOMMENDATION_INTERVAL;// RECOMMENDATION_INTERVAL;
+ }
+ }
+ }
+ }
+ return GGPO_OK;
+}
+
+int Peer2PeerBackend::Poll2Players(int current_frame)
+{
+ int i;
+
+ // discard confirmed frames as appropriate
+ int total_min_confirmed = MAX_INT;
+ for (i = 0; i < _num_players; i++) {
+ bool queue_connected = true;
+ if (_endpoints[i].IsRunning()) {
+ int ignore;
+ queue_connected = _endpoints[i].GetPeerConnectStatus(i, &ignore);
+ }
+ if (!_local_connect_status[i].disconnected) {
+ total_min_confirmed = MIN(_local_connect_status[i].last_frame, total_min_confirmed);
+ }
+ Log(" local endp: connected = %d, last_received = %d, total_min_confirmed = %d.\n", !_local_connect_status[i].disconnected, _local_connect_status[i].last_frame, total_min_confirmed);
+ if (!queue_connected && !_local_connect_status[i].disconnected) {
+ Log("disconnecting i %d by remote request.\n", i);
+ DisconnectPlayerQueue(i, total_min_confirmed);
+ }
+ Log(" total_min_confirmed = %d.\n", total_min_confirmed);
+ }
+ return total_min_confirmed;
+}
+
+int Peer2PeerBackend::PollNPlayers(int current_frame)
+{
+ int i, queue, last_received;
+
+ // discard confirmed frames as appropriate
+ int total_min_confirmed = MAX_INT;
+ for (queue = 0; queue < _num_players; queue++) {
+ bool queue_connected = true;
+ int queue_min_confirmed = MAX_INT;
+ Log("considering queue %d.\n", queue);
+ for (i = 0; i < _num_players; i++) {
+ // we're going to do a lot of logic here in consideration of endpoint i.
+ // keep accumulating the minimum confirmed point for all n*n packets and
+ // throw away the rest.
+ if (_endpoints[i].IsRunning()) {
+ bool connected = _endpoints[i].GetPeerConnectStatus(queue, &last_received);
+
+ queue_connected = queue_connected && connected;
+ queue_min_confirmed = MIN(last_received, queue_min_confirmed);
+ Log(" endpoint %d: connected = %d, last_received = %d, queue_min_confirmed = %d.\n", i, connected, last_received, queue_min_confirmed);
+ } else {
+ Log(" endpoint %d: ignoring... not running.\n", i);
+ }
+ }
+ // merge in our local status only if we're still connected!
+ if (!_local_connect_status[queue].disconnected) {
+ queue_min_confirmed = MIN(_local_connect_status[queue].last_frame, queue_min_confirmed);
+ }
+ Log(" local endp: connected = %d, last_received = %d, queue_min_confirmed = %d.\n", !_local_connect_status[queue].disconnected, _local_connect_status[queue].last_frame, queue_min_confirmed);
+
+ if (queue_connected) {
+ total_min_confirmed = MIN(queue_min_confirmed, total_min_confirmed);
+ } else {
+ // check to see if this disconnect notification is further back than we've been before. If
+ // so, we need to re-adjust. This can happen when we detect our own disconnect at frame n
+ // and later receive a disconnect notification for frame n-1.
+ if (!_local_connect_status[queue].disconnected || _local_connect_status[queue].last_frame > queue_min_confirmed) {
+ Log("disconnecting queue %d by remote request.\n", queue);
+ DisconnectPlayerQueue(queue, queue_min_confirmed);
+ }
+ }
+ Log(" total_min_confirmed = %d.\n", total_min_confirmed);
+ }
+ return total_min_confirmed;
+}
+
+
+GGPOErrorCode
+Peer2PeerBackend::AddPlayer(GGPOPlayer *player,
+ GGPOPlayerHandle *handle)
+{
+ if (player->type == GGPO_PLAYERTYPE_SPECTATOR) {
+ return AddSpectator(player->u.remote.ip_address, player->u.remote.port);
+ }
+
+ int queue = player->player_num - 1;
+ if (player->player_num < 1 || player->player_num > _num_players) {
+ return GGPO_ERRORCODE_PLAYER_OUT_OF_RANGE;
+ }
+ *handle = QueueToPlayerHandle(queue);
+
+ if (player->type == GGPO_PLAYERTYPE_REMOTE) {
+ AddRemotePlayer(player->u.remote.ip_address, player->u.remote.port, queue);
+ }
+ return GGPO_OK;
+}
+
+GGPOErrorCode
+Peer2PeerBackend::AddLocalInput(GGPOPlayerHandle player,
+ void *values,
+ int size)
+{
+ int queue;
+ GameInput input;
+ GGPOErrorCode result;
+
+ if (_sync.InRollback()) {
+ return GGPO_ERRORCODE_IN_ROLLBACK;
+ }
+ if (_synchronizing) {
+ return GGPO_ERRORCODE_NOT_SYNCHRONIZED;
+ }
+
+ result = PlayerHandleToQueue(player, &queue);
+ if (!GGPO_SUCCEEDED(result)) {
+ return result;
+ }
+
+ input.init(-1, (char *)values, size);
+
+ // Feed the input for the current frame into the synchronzation layer.
+ if (!_sync.AddLocalInput(queue, input)) {
+ return GGPO_ERRORCODE_PREDICTION_THRESHOLD;
+ }
+
+ if (input.frame != GameInput::NullFrame) { // xxx: <- comment why this is the case
+ // Update the local connect status state to indicate that we've got a
+ // confirmed local frame for this player. this must come first so it
+ // gets incorporated into the next packet we send.
+
+ // Send checksum for frames old enough to be confirmed (ie older then current - MaxPredictionFrames())
+
+
+
+ _confirmedCheckSumFrame = input.frame - HowFarBackForChecksums();
+
+
+ input.checksum = 0;
+ if (_confirmedCheckSumFrame >= 0) {
+ char buf[128];
+ input.checksum = _pendingCheckSums.at(_confirmedCheckSumFrame);
+ _confirmedCheckSums[_confirmedCheckSumFrame] = input.checksum;
+ _pendingCheckSums.erase(_confirmedCheckSumFrame);
+ sprintf_s<128>(buf, "Frame %d: Send checksum for frame %d, val %d\n", input.frame, _confirmedCheckSumFrame, input.checksum);
+ //OutputDebugStringA(buf);
+ }
+ Log("setting local connect status for local queue %d to %d", queue, input.frame);
+ _local_connect_status[queue].last_frame = input.frame;
+
+ // Send the input to all the remote players.
+ for (int i = 0; i < _num_players; i++) {
+ if (_endpoints[i].IsInitialized()) {
+ _endpoints[i].SendInput(input);
+ }
+ }
+ }
+
+ return GGPO_OK;
+}
+
+
+GGPOErrorCode
+Peer2PeerBackend::SyncInput(void *values,
+ int size,
+ int *disconnect_flags)
+{
+ int flags;
+
+ // Wait until we've started to return inputs.
+ if (_synchronizing) {
+ return GGPO_ERRORCODE_NOT_SYNCHRONIZED;
+ }
+ flags = _sync.SynchronizeInputs(values, size);
+ if (disconnect_flags) {
+ *disconnect_flags = flags;
+ }
+ return GGPO_OK;
+}
+GGPOErrorCode Peer2PeerBackend::CurrentFrame(int& current)
+{
+ current = _sync.GetFrameCount();
+ return GGPO_OK;
+}
+GGPOErrorCode
+Peer2PeerBackend::IncrementFrame(uint16_t checksum1)
+{
+ auto currentFrame = _sync.GetFrameCount();
+ char buf[256];
+ uint16_t cSum = checksum1;
+ Log("End of frame (%d)...\n", _sync.GetFrameCount());
+ static int maxDif = 0;
+ if (_pendingCheckSums.count(_sync.GetFrameCount()))
+ {
+ auto max = _pendingCheckSums.rbegin()->first;
+ auto diff = max - currentFrame;
+ maxDif = max(maxDif, diff);
+ int oldChecksum = _pendingCheckSums[_sync.GetFrameCount()];
+ _pendingCheckSums[_sync.GetFrameCount()] = cSum;
+ sprintf_s<256>(buf, "Replace local checksum for frame %d: %d with %d, newest frame is %d, max diff %d\n", _sync.GetFrameCount(), oldChecksum, _pendingCheckSums[_sync.GetFrameCount()], max, maxDif);
+
+
+ if (currentFrame <= _confirmedCheckSumFrame)
+ {
+ sprintf_s<256>(buf, "Changing frame %d in a rollback, but we've already sent frame %d\n", currentFrame, _confirmedCheckSumFrame);
+
+ OutputDebugStringA(buf);
+ throw std::exception(buf);
+ }
+ if (diff >= (_sync.MaxPredictionFrames())) {
+
+ sprintf_s<256>(buf, "diff is bigger than max prediction\n");
+
+ OutputDebugStringA(buf);
+ throw std::exception(buf);
+ }
+ }
+ else
+ {
+ sprintf_s<256>(buf, "Added local checksum for frame %d: %d\n", _sync.GetFrameCount(), cSum);
+ //OutputDebugStringA(buf);
+ }
+
+ _pendingCheckSums[_sync.GetFrameCount()]= cSum ;
+
+
+
+ _sync.IncrementFrame();
+ DoPoll();
+ PollSyncEvents();
+
+ return GGPO_OK;
+}
+
+
+void
+Peer2PeerBackend::PollSyncEvents(void)
+{
+ Sync::Event e;
+ while (_sync.GetEvent(e)) {
+ OnSyncEvent(e);
+ }
+ return;
+}
+
+void
+Peer2PeerBackend::PollUdpProtocolEvents(void)
+{
+ UdpProtocol::Event evt;
+ for (int i = 0; i < _num_players; i++) {
+ _endpoints[i].StartPollLoop();
+ while (_endpoints[i].GetEvent(evt)) {
+ OnUdpProtocolPeerEvent(evt, i);
+ }
+ _endpoints[i].EndPollLoop();
+ }
+ for (int i = 0; i < _num_spectators; i++) {
+ while (_spectators[i].GetEvent(evt)) {
+ OnUdpProtocolSpectatorEvent(evt, i);
+ }
+ }
+
+ //for (int i = 0; i < _num_players; i++) {
+// _endpoints[i].ApplyToEvents([&](UdpProtocol::Event& e) {
+// OnUdpProtocolPeerEvent(evt, i);
+// });
+//}
+}
+
+void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint16 cs)
+{
+ if (framenumber <= _sync.MaxPredictionFrames())
+ return;
+ framenumber; cs;
+ //auto frameOfChecksumToSend = framenumber - (_sync.MaxPredictionFrames() + 1);
+
+}
+
+int Peer2PeerBackend::HowFarBackForChecksums()const
+{
+ return 16;
+}/*
+uint16 Peer2PeerBackend::GetChecksumForConfirmedFrame(int frameNumber) const
+{
+
+
+ auto frameOfChecksumToSend = frameNumber - HowFarBackForChecksums();
+ if (frameOfChecksumToSend < 0)
+ return 0;
+
+ if (_checkSums.count(frameOfChecksumToSend) == 0)
+ {
+ char s[128];
+ sprintf_s<128>(s, "No local checksum found, remote frame is %d, adjusted is %d, most recent we have is %d\n", frameNumber, frameOfChecksumToSend, _checkSums.rbegin()->first);
+ OutputDebugStringA(s);
+ throw std::exception("s");
+ }
+ return _checkSums.at(frameOfChecksumToSend);
+}*/
+void
+Peer2PeerBackend::OnUdpProtocolPeerEvent(UdpProtocol::Event &evt, int queue)
+{
+ OnUdpProtocolEvent(evt, QueueToPlayerHandle(queue));
+ switch (evt.type) {
+ case UdpProtocol::Event::Input:
+ if (!_local_connect_status[queue].disconnected) {
+ int current_remote_frame = _local_connect_status[queue].last_frame;
+ int new_remote_frame = evt.u.input.input.frame;
+ ASSERT(current_remote_frame == -1 || new_remote_frame == (current_remote_frame + 1));
+
+ _sync.AddRemoteInput(queue, evt.u.input.input);
+ // Notify the other endpoints which frame we received from a peer
+ Log("setting remote connect status for queue %d to %d\n", queue, evt.u.input.input.frame);
+ _local_connect_status[queue].last_frame = evt.u.input.input.frame;
+
+ auto remoteChecksum = evt.u.input.input.checksum;
+ int checkSumFrame = new_remote_frame - HowFarBackForChecksums();
+ if (checkSumFrame >= _endpoints[queue].RemoteFrameDelay()-1)
+ _endpoints[queue]._remoteCheckSumsThisFrame[checkSumFrame] = remoteChecksum;
+ // auto localChecksum = GetChecksumForConfirmedFrame(new_remote_frame);
+ //
+
+ if (checkSumFrame %120==0)
+ {
+ char buf[256];
+ sprintf_s<256>(buf, "Received checksum for frame %d, remote cs is %d\n", checkSumFrame, remoteChecksum);
+ //OutputDebugStringA(buf);
+ }
+ }
+ break;
+
+ case UdpProtocol::Event::Disconnected:
+ DisconnectPlayer(QueueToPlayerHandle(queue));
+ break;
+ }
+}
+
+
+void
+Peer2PeerBackend::OnUdpProtocolSpectatorEvent(UdpProtocol::Event &evt, int queue)
+{
+ GGPOPlayerHandle handle = QueueToSpectatorHandle(queue);
+ OnUdpProtocolEvent(evt, handle);
+
+ GGPOEvent info;
+
+ switch (evt.type) {
+ case UdpProtocol::Event::Disconnected:
+ _spectators[queue].Disconnect();
+
+ info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER;
+ info.u.disconnected.player = handle;
+ _callbacks.on_event(_callbacks.context, &info);
+
+ break;
+ }
+}
+
+void
+Peer2PeerBackend::OnUdpProtocolEvent(UdpProtocol::Event &evt, GGPOPlayerHandle handle)
+{
+ GGPOEvent info;
+
+ switch (evt.type) {
+ case UdpProtocol::Event::Connected:
+ info.code = GGPO_EVENTCODE_CONNECTED_TO_PEER;
+ info.u.connected.player = handle;
+ _callbacks.on_event(_callbacks.context, &info);
+ break;
+ case UdpProtocol::Event::Synchronizing:
+ info.code = GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER;
+ info.u.synchronizing.player = handle;
+ info.u.synchronizing.count = evt.u.synchronizing.count;
+ info.u.synchronizing.total = evt.u.synchronizing.total;
+ _callbacks.on_event(_callbacks.context, &info);
+ break;
+ case UdpProtocol::Event::Synchronzied:
+ info.code = GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER;
+ info.u.synchronized.player = handle;
+ _callbacks.on_event(_callbacks.context, &info);
+
+ CheckInitialSync();
+ break;
+
+ case UdpProtocol::Event::NetworkInterrupted:
+ info.code = GGPO_EVENTCODE_CONNECTION_INTERRUPTED;
+ info.u.connection_interrupted.player = handle;
+ info.u.connection_interrupted.disconnect_timeout = evt.u.network_interrupted.disconnect_timeout;
+ _callbacks.on_event(_callbacks.context, &info);
+ break;
+
+ case UdpProtocol::Event::NetworkResumed:
+ info.code = GGPO_EVENTCODE_CONNECTION_RESUMED;
+ info.u.connection_resumed.player = handle;
+ _callbacks.on_event(_callbacks.context, &info);
+ break;
+ }
+}
+
+/*
+ * Called only as the result of a local decision to disconnect. The remote
+ * decisions to disconnect are a result of us parsing the peer_connect_settings
+ * blob in every endpoint periodically.
+ */
+GGPOErrorCode
+Peer2PeerBackend::DisconnectPlayer(GGPOPlayerHandle player)
+{
+ int queue;
+ GGPOErrorCode result;
+
+ result = PlayerHandleToQueue(player, &queue);
+ if (!GGPO_SUCCEEDED(result)) {
+ return result;
+ }
+
+ if (_local_connect_status[queue].disconnected) {
+ return GGPO_ERRORCODE_PLAYER_DISCONNECTED;
+ }
+
+ if (!_endpoints[queue].IsInitialized()) {
+ int current_frame = _sync.GetFrameCount();
+ // xxx: we should be tracking who the local player is, but for now assume
+ // that if the endpoint is not initalized, this must be the local player.
+ Log("Disconnecting local player %d at frame %d by user request.\n", queue, _local_connect_status[queue].last_frame);
+ for (int i = 0; i < _num_players; i++) {
+ if (_endpoints[i].IsInitialized()) {
+ DisconnectPlayerQueue(i, current_frame);
+ }
+ }
+ } else {
+ Log("Disconnecting queue %d at frame %d by user request.\n", queue, _local_connect_status[queue].last_frame);
+ DisconnectPlayerQueue(queue, _local_connect_status[queue].last_frame);
+ }
+ return GGPO_OK;
+}
+
+void
+Peer2PeerBackend::DisconnectPlayerQueue(int queue, int syncto)
+{
+ GGPOEvent info;
+ int framecount = _sync.GetFrameCount();
+
+ _endpoints[queue].Disconnect();
+
+ Log("Changing queue %d local connect status for last frame from %d to %d on disconnect request (current: %d).\n",
+ queue, _local_connect_status[queue].last_frame, syncto, framecount);
+
+ _local_connect_status[queue].disconnected = 1;
+ _local_connect_status[queue].last_frame = syncto;
+
+ /*if (syncto < framecount) {
+ Log("adjusting simulation to account for the fact that %d disconnected @ %d.\n", queue, syncto);
+ _sync.AdjustSimulation(syncto);
+ Log("finished adjusting simulation.\n");
+ }*/
+
+ info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER;
+ info.u.disconnected.player = QueueToPlayerHandle(queue);
+ _callbacks.on_event(_callbacks.context, &info);
+
+ CheckInitialSync();
+}
+
+
+GGPOErrorCode
+Peer2PeerBackend::GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle player)
+{
+ int queue;
+ GGPOErrorCode result;
+
+ result = PlayerHandleToQueue(player, &queue);
+ if (!GGPO_SUCCEEDED(result)) {
+ return result;
+ }
+
+ memset(stats, 0, sizeof *stats);
+ _endpoints[queue].GetNetworkStats(stats);
+
+ return GGPO_OK;
+}
+
+GGPOErrorCode
+Peer2PeerBackend::SetFrameDelay(GGPOPlayerHandle player, int delay)
+{
+ int queue;
+ GGPOErrorCode result;
+
+ result = PlayerHandleToQueue(player, &queue);
+ if (!GGPO_SUCCEEDED(result)) {
+ return result;
+ } _sync.SetFrameDelay(queue, delay);
+
+ for (int i = 0; i < _num_players; i++) {
+ if (_endpoints[i].IsInitialized()) {
+ _endpoints[i].SetFrameDelay(delay);
+
+ }
+ }
+ ;
+ return GGPO_OK;
+}
+
+GGPOErrorCode
+Peer2PeerBackend::SetDisconnectTimeout(int timeout)
+{
+ _disconnect_timeout = timeout;
+ for (int i = 0; i < _num_players; i++) {
+ if (_endpoints[i].IsInitialized()) {
+ _endpoints[i].SetDisconnectTimeout(_disconnect_timeout);
+ }
+ }
+ return GGPO_OK;
+}
+
+GGPOErrorCode
+Peer2PeerBackend::SetDisconnectNotifyStart(int timeout)
+{
+ _disconnect_notify_start = timeout;
+ for (int i = 0; i < _num_players; i++) {
+ if (_endpoints[i].IsInitialized()) {
+ _endpoints[i].SetDisconnectNotifyStart(_disconnect_notify_start);
+ }
+ }
+ return GGPO_OK;
+}
+
+GGPOErrorCode
+Peer2PeerBackend::Chat(const char* text)
+{
+ if (strlen(text) >= MAX_CHAT_LENGTH)
+ return GGPO_CHAT_MESSAGE_TOO_LONG;
+
+ // Send the input to all the remote players.
+ for (int i = 0; i < _num_players; i++) {
+ if (_endpoints[i].IsInitialized()) {
+ _endpoints[i].SendChat(text);
+ }
+ }
+ return GGPO_OK;
+}
+
+GGPOErrorCode
+Peer2PeerBackend::PlayerHandleToQueue(GGPOPlayerHandle player, int *queue)
+{
+ int offset = ((int)player - 1);
+ if (offset < 0 || offset >= _num_players) {
+ return GGPO_ERRORCODE_INVALID_PLAYER_HANDLE;
+ }
+ *queue = offset;
+ return GGPO_OK;
+}
+
+
+void
+Peer2PeerBackend::OnMsg(sockaddr_in &from, UdpMsg *msg, int len)
+{
+ for (int i = 0; i < _num_players; i++) {
+ if (_endpoints[i].HandlesMsg(from, msg)) {
+ _endpoints[i].OnMsg(msg, len);
+ return;
+ }
+ }
+ for (int i = 0; i < _num_spectators; i++) {
+ if (_spectators[i].HandlesMsg(from, msg)) {
+ _spectators[i].OnMsg(msg, len);
+ return;
+ }
+ }
+}
+
+void
+Peer2PeerBackend::CheckInitialSync()
+{
+ int i;
+
+ if (_synchronizing) {
+ // Check to see if everyone is now synchronized. If so,
+ // go ahead and tell the client that we're ok to accept input.
+ for (i = 0; i < _num_players; i++) {
+ // xxx: IsInitialized() must go... we're actually using it as a proxy for "represents the local player"
+ if (_endpoints[i].IsInitialized() && !_endpoints[i].IsSynchronized() && !_local_connect_status[i].disconnected) {
+ return;
+ }
+ }
+ for (i = 0; i < _num_spectators; i++) {
+ if (_spectators[i].IsInitialized() && !_spectators[i].IsSynchronized()) {
+ return;
+ }
+ }
+
+ GGPOEvent info;
+ info.code = GGPO_EVENTCODE_RUNNING;
+ _callbacks.on_event(_callbacks.context, &info);
+ _synchronizing = false;
+ }
+}
diff --git a/dep/ggpo-x/src/backends/p2p.h b/dep/ggpo-x/src/backends/p2p.h
new file mode 100644
index 000000000..62cfea0f9
--- /dev/null
+++ b/dep/ggpo-x/src/backends/p2p.h
@@ -0,0 +1,90 @@
+/* -----------------------------------------------------------------------
+ * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
+ *
+ * Use of this software is governed by the MIT license that can be found
+ * in the LICENSE file.
+ */
+
+#ifndef _P2P_H
+#define _P2P_H
+
+#include "types.h"
+#include "poll.h"
+#include "sync.h"
+#include "backend.h"
+#include "timesync.h"
+#include "network/udp_proto.h"
+#include