diff options
Diffstat (limited to 'src/uqm/supermelee/netplay')
45 files changed, 6316 insertions, 0 deletions
diff --git a/src/uqm/supermelee/netplay/FILES b/src/uqm/supermelee/netplay/FILES new file mode 100644 index 0000000..7b93fd1 --- /dev/null +++ b/src/uqm/supermelee/netplay/FILES @@ -0,0 +1,50 @@ +In netplay/: +crc.{c,h} Generic CRC routines. +checkbuf.{c,h} Buffer of checksums. +checksum.{c,h} Routines for checksumming the game state. +netconnection.{c,h} Definition of NetConnection, being the state of a + network connection, and operations on it. +nc_connect.ci Part of netconnection.c that handles establishing + connections. +netinput.{c,h} Definitions and operations for melee input commands + over a network connection. +netmelee.{c,h} Keeps track of network connections used in the game, + with methods to control them all at once. + Functions that directly access the game data are kept + in the relevant files (melee.c, pickmele.c, battle.c). +netmisc.{c,h} Miscelaneous functions that didn't fit in elsewhere. +netoptions.{c,h} Description of a network connection to be established. +netplay.h Some global netplay definitions. +netrcv.{c,h} Processes incoming packets. Does know about the protocol + and will do consistency checking. + Does not directly manipulate the game state. +netsend.{c,h} Enqueues all sorts of packets for sending. + Does not know about the protocol and hence will do + no checks for protocol sanity. +netstate.{c,h} Definitions of the states of a network connection. +notify.{c,h} Routines for notifying a remote side of local changes. + Knows about the protocal and has assert()s to + check for local consistency. +packet.{c,h} Definition and creation of packets. Create functions + should only be called from netsend.c. +packethandlers.{c,h} Routines for processing each type of incoming packet. +packetq.{c,h} Manages the packet queue. +packetsenders.{c,h} Creates and sends/queues packets. + +In netplay/proto/: +npconfirm.{c,h} Functions for handing the 'confirmation' protocol. +ready.{c,h} Functions for handling the 'ready' protocol. +reset.{c,h} Functions for handling the 'reset' protocol. + + + + + +TODO: +Division: +- files that interface with sockets and knows nothing of the game + (these are in libs/network) +- files that know of sockets and the game but don't directly interface + with either +- files that interface with the game and know nothing of sockets + diff --git a/src/uqm/supermelee/netplay/Makeinfo b/src/uqm/supermelee/netplay/Makeinfo new file mode 100644 index 0000000..ff31011 --- /dev/null +++ b/src/uqm/supermelee/netplay/Makeinfo @@ -0,0 +1,4 @@ +uqm_SUBDIRS="proto" +uqm_CFILES="checkbuf.c checksum.c crc.c netconnection.c netinput.c netmelee.c netmisc.c netoptions.c netrcv.c netsend.c netstate.c notify.c notifyall.c packet.c packethandlers.c packetsenders.c packetq.c" +uqm_HFILES="checkbuf.h checksum.h crc.h netconnection.h netinput.h netmelee.h netmisc.h netoptions.h netplay.h netrcv.h netsend.h netstate.h notifyall.h notify.h packet.h packethandlers.h packetq.h packetsenders.h" + diff --git a/src/uqm/supermelee/netplay/checkbuf.c b/src/uqm/supermelee/netplay/checkbuf.c new file mode 100644 index 0000000..e9c5a32 --- /dev/null +++ b/src/uqm/supermelee/netplay/checkbuf.c @@ -0,0 +1,145 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#include "netplay.h" +#include "checkbuf.h" +#include "libs/log.h" + +#include "../../battle.h" + // for battleFrameCount + + +#include <errno.h> +#include <stdlib.h> + + + +static inline BattleFrameCounter +ChecksumBuffer_getCurrentFrameNr(void) { + return battleFrameCount; +} + +void +ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval) { + // The input buffer lags BattleInput_inputDelay frames behind, + // but only every interval frames will there be a checksum to be + // checked. + + // Checksums will be checked when 'frameNr % interval == 0'. + // (and frameNr is zero-based). + // The checksum of frame n will be processed in frame 'n + delay'. + + // In the worst case, side 1 processes frames 'n' through 'n + delay - 1', + // then blocks in frame 'n + delay' (after sending a checksum, if that's + // pertinent for that frame). + // Then side 2 receives all this input and these checksums, and + // progresses to 'delay' frames after the last frame the received input + // originated from, and blocks in the frame after it (after sending a + // checksum, if that's pertinent for the frame). + // So it sent input and checksums for frames 'n' through + // 'n + delay + delay + 1'. + // The input and checksums for these '2*delay + 2' frames are still + // unhandled by side 1, so it needs buffer space for this. + // With checksums only sent every interval frames, the buffer space + // needed will be 'roundUp(2*delay + 2)' spaces. + + size_t bufSize = ((2 * delay + 2) + (interval - 1)) / interval; + + { +#ifdef NETPLAY_DEBUG + size_t i; +#endif + + cb->checksums = malloc(bufSize * sizeof (ChecksumEntry)); + cb->maxSize = bufSize; + cb->interval = interval; + +#ifdef NETPLAY_DEBUG + for (i = 0; i < bufSize; i++) { + cb->checksums[i].checksum = 0; + cb->checksums[i].frameNr = (BattleFrameCounter) -1; + } +#endif + } +} + +void +ChecksumBuffer_uninit(ChecksumBuffer *cb) { + if (cb->checksums != NULL) { + free(cb->checksums); + cb->checksums = NULL; + } +} + +// Returns the entry that would be used for the checksum for the specified +// frame. Whether the entry is actually valid is not checked. +static ChecksumEntry * +ChecksumBuffer_getChecksumEntry(ChecksumBuffer *cb, + BattleFrameCounter frameNr) { + size_t index; + ChecksumEntry *entry; + + assert(frameNr % cb->interval == 0); + // We only record checksums exactly every 'interval' frames. + + index = (frameNr / cb->interval) % cb->maxSize; + entry = &cb->checksums[index]; + + return entry; +} + +bool +ChecksumBuffer_addChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr, + Checksum checksum) { + ChecksumEntry *entry; + + assert(frameNr % cb->interval == 0); + + entry = ChecksumBuffer_getChecksumEntry(cb, frameNr); + +#ifdef NETPLAY_DEBUG + entry->frameNr = frameNr; +#endif + entry->checksum = checksum; + return true; +} + +// Pre: frameNr is within the range of the checksums stored in cb. +bool +ChecksumBuffer_getChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr, + Checksum *result) { + ChecksumEntry *entry; + + entry = ChecksumBuffer_getChecksumEntry(cb, frameNr); + +#ifdef NETPLAY_DEBUG + if (frameNr != entry->frameNr) { + log_add(log_Error, "Checksum buffer entry for requested frame %u " + "(still?) contains a checksum for frame %u.\n", + frameNr, entry->frameNr); + return false; + } +#endif + + *result = entry->checksum; + return true; +} + diff --git a/src/uqm/supermelee/netplay/checkbuf.h b/src/uqm/supermelee/netplay/checkbuf.h new file mode 100644 index 0000000..f609448 --- /dev/null +++ b/src/uqm/supermelee/netplay/checkbuf.h @@ -0,0 +1,77 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_ +#define UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct ChecksumEntry ChecksumEntry; +typedef struct ChecksumBuffer ChecksumBuffer; + +#if defined(__cplusplus) +} +#endif + +#include "../../battle.h" + // for BattleFrameCounter +#include "checksum.h" + +#if defined(__cplusplus) +extern "C" { +#endif + + +struct ChecksumEntry { +#ifdef NETPLAY_DEBUG + BattleFrameCounter frameNr; + // The number of the frame this checksum originated from. + // If the checksumming code is working correctly, the checksum + // can only come from one frame, so this value is not needed + // for normal operation. + // Its only use is to detect some cases where checksumming code + // is *not* working correctly. +#endif + Checksum checksum; +}; + +struct ChecksumBuffer { + ChecksumEntry *checksums; + // Cyclic buffer. if 'size' > 0, then 'first' is an index to + // the first used entry, 'size' is the number of used + // entries in the buffer, and (first + size) % maxSize is the + // index to just past the end of the buffer. + size_t maxSize; + + size_t interval; +}; + +void ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval); +void ChecksumBuffer_uninit(ChecksumBuffer *cb); +bool ChecksumBuffer_addChecksum(ChecksumBuffer *cb, + BattleFrameCounter frameNr, Checksum checksum); +bool ChecksumBuffer_getChecksum(ChecksumBuffer *cb, + BattleFrameCounter frameNr, Checksum *result); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_ */ diff --git a/src/uqm/supermelee/netplay/checksum.c b/src/uqm/supermelee/netplay/checksum.c new file mode 100644 index 0000000..5d687f0 --- /dev/null +++ b/src/uqm/supermelee/netplay/checksum.c @@ -0,0 +1,302 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef NETPLAY + +#include "checksum.h" +#include "netoptions.h" + +#ifdef NETPLAY_CHECKSUM + +#include "checkbuf.h" +#include "crc.h" + // for DUMP_CRC_OPS +#include "netconnection.h" +#include "netmelee.h" +#include "libs/log.h" +#include "libs/mathlib.h" + +ChecksumBuffer localChecksumBuffer; + +void +crc_processEXTENT(crc_State *state, const EXTENT *val) { +#ifdef DUMP_CRC_OPS + crc_log("START crc_processEXTENT()."); +#endif + crc_processCOORD(state, val->width); + crc_processCOORD(state, val->height); +#ifdef DUMP_CRC_OPS + crc_log("END crc_processEXTENT()."); +#endif +} + +void +crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val) { +#ifdef DUMP_CRC_OPS + crc_log("START crc_processVELOCITY_DESC()."); +#endif + crc_processCOUNT(state, val->TravelAngle); + crc_processEXTENT(state, &val->vector); + crc_processEXTENT(state, &val->fract); + crc_processEXTENT(state, &val->error); + crc_processEXTENT(state, &val->incr); +#ifdef DUMP_CRC_OPS + crc_log("END crc_processVELOCITY_DESC()."); +#endif +} + +void +crc_processPOINT(crc_State *state, const POINT *val) { +#ifdef DUMP_CRC_OPS + crc_log("START crc_processPOINT()."); +#endif + crc_processCOORD(state, val->x); + crc_processCOORD(state, val->y); +#ifdef DUMP_CRC_OPS + crc_log("END crc_processPOINT()."); +#endif +} + +#if 0 +void +crc_processSTAMP(crc_State *state, const STAMP *val) { +#ifdef DUMP_CRC_OPS + crc_log("START crc_processSTAMP()."); +#endif + crc_processPOINT(state, val->origin); + crc_processFRAME(state, val->frame); +#ifdef DUMP_CRC_OPS + crc_log("END crc_processSTAMP()."); +#endif +} + +void +crc_processINTERSECT_CONTROL(crc_State *state, const INTERSECT_CONTROL *val) { +#ifdef DUMP_CRC_OPS + crc_log("START crc_processINTERSECT_CONTROL()."); +#endif + crc_processTIME_VALUE(state, val->last_time_val); + crc_processPOINT(state, &val->EndPoint); +#ifdef DUMP_CRC_OPS + crc_log("END crc_processINTERSECT_CONTROL()."); +#endif +} +#endif + +void +crc_processSTATE(crc_State *state, const STATE *val) { + crc_processPOINT(state, &val->location); +} + +void +crc_processELEMENT(crc_State *state, const ELEMENT *val) { +#ifdef DUMP_CRC_OPS + crc_log("START crc_processELEMENT()."); +#endif + if (val->state_flags & BACKGROUND_OBJECT) { + // The element never influences the state of other elements, + // and is to be excluded from checksums. +#ifdef DUMP_CRC_OPS + crc_log(" BACKGROUND_OBJECT element omited"); +#endif + } else { + crc_processELEMENT_FLAGS(state, val->state_flags); + crc_processCOUNT(state, val->life_span); + crc_processCOUNT(state, val->crew_level); + crc_processBYTE(state, val->mass_points); + crc_processBYTE(state, val->turn_wait); + crc_processBYTE(state, val->thrust_wait); + crc_processVELOCITY_DESC(state, &val->velocity); + crc_processSTATE(state, &val->current); + crc_processSTATE(state, &val->next); + } +#ifdef DUMP_CRC_OPS + crc_log("END crc_processELEMENT()."); +#endif +} + +void +crc_processDispQueue(crc_State *state) { + HELEMENT element; + HELEMENT nextElement; + +#ifdef DUMP_CRC_OPS + size_t i = 0; + crc_log("START crc_processDispQueue()."); +#endif + for (element = GetHeadElement(); element != 0; element = nextElement) { + ELEMENT *elementPtr; + +#ifdef DUMP_CRC_OPS + crc_log("===== disp_q[%d]:", i); +#endif + LockElement(element, &elementPtr); + + crc_processELEMENT(state, elementPtr); + + nextElement = GetSuccElement(elementPtr); + UnlockElement(element); +#ifdef DUMP_CRC_OPS + i++; +#endif + } +#ifdef DUMP_CRC_OPS + crc_log("END crc_processDispQueue()."); +#endif +} + +void +crc_processRNG(crc_State *state) { + DWORD seed; + +#ifdef DUMP_CRC_OPS + crc_log("START crc_processRNG()."); +#endif + + seed = TFB_SeedRandom(0); + // This modifies the seed too. + crc_processDWORD(state, seed); + TFB_SeedRandom(seed); + // Restore the old seed. + +#ifdef DUMP_CRC_OPS + crc_log("END crc_processRNG()."); +#endif +} + +void +crc_processState(crc_State *state) { +#ifdef DUMP_CRC_OPS + crc_log("--------------------\n" + "START crc_processState() (frame %u).", battleFrameCount); +#endif + + crc_processRNG(state); + crc_processDispQueue(state); + +#ifdef DUMP_CRC_OPS + crc_log("END crc_processState() (frame %u).", + battleFrameCount); +#endif +} + +void +initChecksumBuffers(void) { + size_t player; + + for (player = 0; player < NETPLAY_NUM_PLAYERS; player++) + { + NetConnection *conn; + ChecksumBuffer *cb; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + cb = NetConnection_getChecksumBuffer(conn); + ChecksumBuffer_init(cb, getBattleInputDelay(), + NETPLAY_CHECKSUM_INTERVAL); + } + + ChecksumBuffer_init(&localChecksumBuffer, getBattleInputDelay(), + NETPLAY_CHECKSUM_INTERVAL); +} + +void +uninitChecksumBuffers(void) +{ + size_t player; + + for (player = 0; player < NETPLAY_NUM_PLAYERS; player++) + { + NetConnection *conn; + ChecksumBuffer *cb; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + cb = NetConnection_getChecksumBuffer(conn); + + ChecksumBuffer_uninit(cb); + } + + ChecksumBuffer_uninit(&localChecksumBuffer); +} + +void +addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum) { + assert(frameNr == battleFrameCount); + + ChecksumBuffer_addChecksum(&localChecksumBuffer, frameNr, checksum); +} + +void +addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum) { + ChecksumBuffer *cb; + + assert(frameNr <= battleFrameCount + getBattleInputDelay() + 1); + assert(frameNr + getBattleInputDelay() >= battleFrameCount); + + cb = NetConnection_getChecksumBuffer(conn); + ChecksumBuffer_addChecksum(cb, frameNr, checksum); +} + +bool +verifyChecksums(BattleFrameCounter frameNr) { + Checksum localChecksum; + size_t player; + + if (!ChecksumBuffer_getChecksum(&localChecksumBuffer, frameNr, + &localChecksum)) { + // Right now, we require that a checksum is present. + // If/when we move to UDP, and packets may get lost, we may prefer + // not to do any checks in this case. + return false; + } + + for (player = 0; player < NETPLAY_NUM_PLAYERS; player++) + { + NetConnection *conn; + ChecksumBuffer *cb; + Checksum remoteChecksum; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + cb = NetConnection_getChecksumBuffer(conn); + + if (!ChecksumBuffer_getChecksum(cb, frameNr, &remoteChecksum)) + return false; + + if (localChecksum != remoteChecksum) { + log_add(log_Error, "Network connections have gone out of " + "sync.\n"); + return false; + } + } + return true; +} + + +#endif /* NETPLAY_CHECKSUM */ + +#endif /* NETPLAY */ + diff --git a/src/uqm/supermelee/netplay/checksum.h b/src/uqm/supermelee/netplay/checksum.h new file mode 100644 index 0000000..cfb48d6 --- /dev/null +++ b/src/uqm/supermelee/netplay/checksum.h @@ -0,0 +1,99 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_ +#define UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_ + + +#include "types.h" + +typedef uint32 Checksum; + + +#include "netplay.h" +#include "crc.h" + +#include "../../element.h" +#include "libs/gfxlib.h" + +#include "netconnection.h" + +#if defined(__cplusplus) +extern "C" { +#endif + + +static inline void +crc_processELEMENT_FLAGS(crc_State *state, ELEMENT_FLAGS val) { + crc_processUint16(state, (uint16) val); +} + +static inline void +crc_processCOUNT(crc_State *state, COUNT val) { + crc_processUint16(state, (uint16) val); +} + +static inline void +crc_processBYTE(crc_State *state, BYTE val) { + crc_processUint8(state, (uint8) val); +} + +static inline void +crc_processDWORD(crc_State *state, DWORD val) { + crc_processUint32(state, (uint32) val); +} + +static inline void +crc_processCOORD(crc_State *state, COORD val) { + crc_processUint16(state, (uint16) val); +} + +#if 0 +static inline void +crc_processTIME_VALUE(crc_State *state, const TIME_VALUE val) { + crc_processUint16(state, (uint16) val); +} +#endif + +void crc_processEXTENT(crc_State *state, const EXTENT *val); +void crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val); +void crc_processPOINT(crc_State *state, const POINT *val); +#if 0 +void crc_processSTAMP(crc_State *state, const STAMP *val); +void crc_processINTERSECT_CONTROL(crc_State *state, + const INTERSECT_CONTROL *val); +#endif +void crc_processSTATE(crc_State *state, const STATE *val); +void crc_processELEMENT(crc_State *state, const ELEMENT *val); +void crc_processDispQueue(crc_State *state); +void crc_processRNG(crc_State *state); +void crc_processState(crc_State *state); + + +void initChecksumBuffers(void); +void uninitChecksumBuffers(void); +void addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum); +void addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum); +bool verifyChecksums(BattleFrameCounter frameNr); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_ */ diff --git a/src/uqm/supermelee/netplay/crc.c b/src/uqm/supermelee/netplay/crc.c new file mode 100644 index 0000000..677b36f --- /dev/null +++ b/src/uqm/supermelee/netplay/crc.c @@ -0,0 +1,142 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" + // For DUMP_CRC_OPS + +#include "crc.h" + +#ifdef DUMP_CRC_OPS +# include "libs/log.h" +#endif + + +// CRC table for Polynomial 0x04c11db7 (0xedb88320 reversed) +uint32 crcTable[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +void +crc_init(crc_State *state) { + state->crc = 0xffffffff; +} + +void +crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen) { + uint8 *end = buf + bufLen; + uint32 newCrc = state->crc; + + while (buf < end) + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ *buf) & 0xff]; + +#ifdef DUMP_CRC_OPS + crc_log("crc_processBytes(%08x, [%zu bytes]) --> %08x.", + state->crc, bufLen, newCrc); +#endif + state->crc = newCrc; +} + +void +crc_processUint8(crc_State *state, uint8 val) { + uint32 newCrc = state->crc; + + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ val) & 0xff]; +#ifdef DUMP_CRC_OPS + crc_log("crc_processUint8(%08x, %02x) --> %08x.", + state->crc, (int) val, newCrc); +#endif + state->crc = newCrc; +} + +void +crc_processUint16(crc_State *state, uint16 val) { + uint32 newCrc = state->crc; + + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val >> 8) ) & 0xff]; +#ifdef DUMP_CRC_OPS + crc_log("crc_processUint16(%08x, %04x) --> %08x.", + state->crc, (int) val, newCrc); +#endif + state->crc = newCrc; +} + +void +crc_processUint32(crc_State *state, uint32 val) { + uint32 newCrc = state->crc; + + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 8) & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 16) & 0xff)) & 0xff]; + newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 24) )) & 0xff]; + +#ifdef DUMP_CRC_OPS + crc_log("crc_processUint32(%08x, %08x) --> %08x.", + state->crc, (int) val, newCrc); +#endif + state->crc = newCrc; +} + +uint32 +crc_finish(const crc_State *state) { + return ~state->crc; +} + + diff --git a/src/uqm/supermelee/netplay/crc.h b/src/uqm/supermelee/netplay/crc.h new file mode 100644 index 0000000..1744d11 --- /dev/null +++ b/src/uqm/supermelee/netplay/crc.h @@ -0,0 +1,60 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_CRC_H_ +#define UQM_SUPERMELEE_NETPLAY_CRC_H_ + +typedef struct crc_State crc_State; + +#include "types.h" + +#include <stddef.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +struct crc_State { + uint32 crc; +}; + +void crc_init(crc_State *state); +void crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen); +void crc_processUint8(crc_State *state, uint8 val); +void crc_processUint16(crc_State *state, uint16 val); +void crc_processUint32(crc_State *state, uint32 val); +uint32 crc_finish(const crc_State *state); + +#if defined(__cplusplus) +} +#endif + +#ifdef DUMP_CRC_OPS +#include "netconnection.h" + // for netplayDebugFile +//#define crc_log(...) log_add (logDebug, __VA_ARGS__) +#define crc_log(...) if (netplayDebugFile != NULL) \ + { \ + uio_fprintf (netplayDebugFile, __VA_ARGS__); \ + uio_putc ('\n', netplayDebugFile); \ + } else \ + (void) 0 +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_CRC_H_ */ + diff --git a/src/uqm/supermelee/netplay/nc_connect.ci b/src/uqm/supermelee/netplay/nc_connect.ci new file mode 100644 index 0000000..6c67fed --- /dev/null +++ b/src/uqm/supermelee/netplay/nc_connect.ci @@ -0,0 +1,300 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This file is part of netconnection.c, from where it is #included. + +static inline ConnectStateData *ConnectStateData_alloc(void); +static inline ConnectStateData *ConnectStateData_new(void); +static inline void ConnectStateData_free(ConnectStateData *connectStateData); +static void ConnectStateData_delete(ConnectStateData *connectStateData); + +static int NetConnection_go(NetConnection *conn); +static ListenState *NetConnection_serverGo(NetConnection *conn); +static ConnectState *NetConnection_clientGo(NetConnection *conn); +static void NetConnection_connected(NetConnection *conn); +static void NetConnection_connectedServerCallback(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen); +static void NetConnection_connectedClientCallback(ConnectState *connectState, + NetDescriptor *newNd, const struct sockaddr *addr, SOCKLEN_T addrLen); +static void NetConnection_connectedServerErrorCallback( + ListenState *listenState, const ListenError *listenError); +static void NetConnection_connectedClientErrorCallback( + ConnectState *connectState, const ConnectError *connectError); + + +//////////////////////////////////////////////////////////////////////////// + + +static inline ConnectStateData * +ConnectStateData_alloc(void) { + return (ConnectStateData *) malloc(sizeof (ConnectStateData)); +} + +static inline ConnectStateData * +ConnectStateData_new(void) { + ConnectStateData *connectStateData = ConnectStateData_alloc(); + connectStateData->releaseFunction = + (NetConnectionStateData_ReleaseFunction) ConnectStateData_delete; + return connectStateData; +} + +static inline void +ConnectStateData_free(ConnectStateData *connectStateData) { + free(connectStateData); +} + +static void +ConnectStateData_delete(ConnectStateData *connectStateData) { + if (connectStateData->isServer) { + ListenState *listenState = connectStateData->state.listenState; + ListenState_close(listenState); + } else { + ConnectState *connectState = connectStateData->state.connectState; + ConnectState_close(connectState); + } + ConnectStateData_free(connectStateData); +} + + +//////////////////////////////////////////////////////////////////////////// + + +static int +NetConnection_go(NetConnection *conn) { + ConnectStateData *connectStateData; + + if (NetConnection_isConnected(conn)) + return 0; + + if (conn->options->isServer) { + ListenState *listenState; + listenState = NetConnection_serverGo(conn); + if (listenState == NULL) { + // errno is set + return -1; + } + connectStateData = ConnectStateData_new(); + connectStateData->state.listenState = listenState; + } else { + ConnectState *connectState; + connectState = NetConnection_clientGo(conn); + if (connectState == NULL) { + // errno is set + return -1; + } + connectStateData = ConnectStateData_new(); + connectStateData->state.connectState = connectState; + } + connectStateData->isServer = conn->options->isServer; + + NetConnection_setStateData(conn, (void *) connectStateData); + return 0; +} + +static ListenState * +NetConnection_serverGo(NetConnection *conn) { + ListenFlags listenFlags; + ListenState *result; + + assert(conn->state == NetState_unconnected); + assert(conn->options->isServer); + + NetConnection_setState(conn, NetState_connecting); + + memset (&listenFlags, 0, sizeof listenFlags); + listenFlags.familyDemand = +#if NETPLAY == NETPLAY_IPV4 + PF_inet; +#else + PF_unspec; +#endif + listenFlags.familyPrefer = PF_unspec; + listenFlags.backlog = NETPLAY_LISTEN_BACKLOG; + + result = listenPort(conn->options->port, IPProto_tcp, &listenFlags, + NetConnection_connectedServerCallback, + NetConnection_connectedServerErrorCallback, (void *) conn); + + return result; +} + +static ConnectState * +NetConnection_clientGo(NetConnection *conn) { + ConnectFlags connectFlags; + ConnectState *result; + + assert(conn->state == NetState_unconnected); + assert(!conn->options->isServer); + + NetConnection_setState(conn, NetState_connecting); + + memset (&connectFlags, 0, sizeof connectFlags); + connectFlags.familyDemand = +#if NETPLAY == NETPLAY_IPV4 + PF_inet; +#else + PF_unspec; +#endif + connectFlags.familyPrefer = PF_unspec; + connectFlags.timeout = NETPLAY_CONNECTTIMEOUT; + connectFlags.retryDelayMs = NETPLAY_RETRYDELAY; + + result = connectHostByName(conn->options->host, conn->options->port, + IPProto_tcp, &connectFlags, NetConnection_connectedClientCallback, + NetConnection_connectedClientErrorCallback, (void *) conn); + + return result; +} + +// Called when an incoming connection has been established. +// The caller gives up ownership of newNd +static void +NetConnection_connectedServerCallback(ListenState *listenState, + NetDescriptor *listenNd, NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen) { + NetConnection *conn = + (NetConnection *) ListenState_getExtra(listenState); + + assert(conn->nd == NULL); + + conn->nd = newNd; + // No incRef(); the caller gives up ownership. + NetDescriptor_setExtra(conn->nd, (void *) conn); + + (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd)); + // Ignore errors; it's not a big deal. In debug mode, a message + // will already have been printed from the function itself. + + conn->stateFlags.discriminant = true; + + NetConnection_connected(conn); + (void) listenNd; + (void) addr; + (void) addrLen; +} + +// Called when an outgoing connection has been established. +// The caller gives up ownership of newNd +static void +NetConnection_connectedClientCallback(ConnectState *connectState, + NetDescriptor *newNd, + const struct sockaddr *addr, SOCKLEN_T addrLen) { + NetConnection *conn = + (NetConnection *) ConnectState_getExtra(connectState); + + assert(conn->nd == NULL); + + conn->nd = newNd; + // No incRef(); the caller gives up ownership. + NetDescriptor_setExtra(conn->nd, (void *) conn); + + (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd)); + // Ignore errors; it's not a big deal. In debug mode, a message + // will already have been printed from the function itself. + + conn->stateFlags.discriminant = false; + + NetConnection_connected(conn); + (void) addr; + (void) addrLen; +} + +// Called when a connection has been established. +static void +NetConnection_connected(NetConnection *conn) { + ConnectStateData *connectStateData; + + conn->stateFlags.connected = true; + NetConnection_setState(conn, NetState_init); + + connectStateData = (ConnectStateData *) NetConnection_getStateData(conn); + ConnectStateData_delete(connectStateData); + NetConnection_setStateData(conn, NULL); + + NetDescriptor_setReadCallback(conn->nd, dataReadyCallback); + NetDescriptor_setCloseCallback(conn->nd, closeCallback); + + (*conn->connectCallback)(conn); +} + +static void +NetConnection_connectedServerErrorCallback(ListenState *listenState, + const ListenError *listenError) { + NetConnection *conn = + (NetConnection *) ListenState_getExtra(listenState); + NetConnectionError error; + + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = true; + ListenState_close(listenState); + NetConnection_setState(conn, NetState_unconnected); + + { + ConnectStateData *connectStateData; + connectStateData = + (ConnectStateData *) NetConnection_getStateData(conn); + ConnectStateData_free(connectStateData); + NetConnection_setStateData(conn, NULL); + } + + if (conn->errorCallback != NULL) { + error.state = NetState_connecting; + error.err = listenError->err; + error.extra.listenError = listenError; + //NetConnection_incRef(conn); + (*conn->errorCallback)(conn, &error); + //NetConnection_decRef(conn); + } + + NetConnection_close(conn); +} + +static void +NetConnection_connectedClientErrorCallback(ConnectState *connectState, + const ConnectError *connectError) { + NetConnection *conn = + (NetConnection *) ConnectState_getExtra(connectState); + NetConnectionError error; + + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = true; + ConnectState_close(connectState); + NetConnection_setState(conn, NetState_unconnected); + + { + ConnectStateData *connectStateData; + connectStateData = + (ConnectStateData *) NetConnection_getStateData(conn); + ConnectStateData_free(connectStateData); + NetConnection_setStateData(conn, NULL); + } + + if (conn->errorCallback != NULL) { + error.state = NetState_connecting; + error.err = connectError->err; + error.extra.connectError = connectError; + //NetConnection_incRef(conn); + (*conn->errorCallback)(conn, &error); + //NetConnection_decRef(conn); + } + + NetConnection_close(conn); +} + + diff --git a/src/uqm/supermelee/netplay/netconnection.c b/src/uqm/supermelee/netplay/netconnection.c new file mode 100644 index 0000000..48ab46b --- /dev/null +++ b/src/uqm/supermelee/netplay/netconnection.c @@ -0,0 +1,378 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "netconnection.h" + +#include "netrcv.h" + +#if defined(DEBUG) || defined(NETPLAY_DEBUG) +# include "libs/log.h" +#endif +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) +# include "options.h" + // for configDir +#endif + +#include <assert.h> +#include <stdlib.h> +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) +# include <errno.h> +# include <time.h> +#endif + + +static void closeCallback(NetDescriptor *nd); +static void NetConnection_doClose(NetConnection *conn); + + +#include "nc_connect.ci" + +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) +uio_Stream *netplayDebugFile; +#endif + +// Used as initial value for Agreement structures, by structure assignment. +const Agreement Agreement_nothingAgreed; + + +// The NetConnection keeps a pointer to the passed NetplayPeerOptions; +// do not free it as long as the NetConnection exists. +NetConnection * +NetConnection_open(int player, const NetplayPeerOptions *options, + NetConnection_ConnectCallback connectCallback, + NetConnection_CloseCallback closeCallback, + NetConnection_ErrorCallback errorCallback, + NetConnection_DeleteCallback deleteCallback, void *extra) { + NetConnection *conn; + + conn = malloc(sizeof (NetConnection)); + +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) + { + char dumpFileName[PATH_MAX]; + time_t now; + struct tm *nowTm; + size_t strftimeResult; + + now = time (NULL); + if (now == (time_t) -1) { + log_add (log_Fatal, "time() failed: %s.", strerror (errno)); + abort (); + } + + nowTm = localtime(&now); + // XXX: I would like to use localtime_r(), but it isn't very + // portable (yet), and adding a check for it to the build.sh script + // is not worth the effort for a debugging function right now. + + strftimeResult = strftime (dumpFileName, sizeof dumpFileName, + "debug/netlog-%Y%m%d%H%M%S", nowTm); + if (strftimeResult == 0) { + log_add (log_Fatal, "strftime() failed: %s.", strerror (errno)); + abort (); + } + + // The user needs to create the debug/ dir manually. If there + // is no debug/ dir, no log will be created. + conn->debugFile = uio_fopen (configDir, dumpFileName, "wt"); + if (conn->debugFile == NULL) { + log_add (log_Debug, "Not creating a netplay debug log for " + "player %d.", player); + } else { + log_add (log_Debug, "Creating netplay debug log '%s' for " + "player %d.", dumpFileName, player); + if (netplayDebugFile == NULL) { + // Debug info relating to no specific network connection + // is sent to the first opened one. + netplayDebugFile = conn->debugFile; + } + } + } +#endif + + conn->nd = NULL; + conn->player = player; + conn->state = NetState_unconnected; + conn->options = options; + conn->extra = extra; + PacketQueue_init(&conn->queue); + + conn->connectCallback = connectCallback; + conn->closeCallback = closeCallback; + conn->errorCallback = errorCallback; + conn->deleteCallback = deleteCallback; + conn->readyCallback = NULL; + conn->readyCallbackArg = NULL; + conn->resetCallback = NULL; + conn->resetCallbackArg = NULL; + + conn->readBuf = malloc(NETPLAY_READBUFSIZE); + conn->readEnd = conn->readBuf; + + conn->stateData = NULL; + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = false; + conn->stateFlags.discriminant = false; + conn->stateFlags.handshake.localOk = false; + conn->stateFlags.handshake.remoteOk = false; + conn->stateFlags.handshake.canceling = false; + conn->stateFlags.ready.localReady = false; + conn->stateFlags.ready.remoteReady = false; + conn->stateFlags.reset.localReset = false; + conn->stateFlags.reset.remoteReset = false; + conn->stateFlags.agreement = Agreement_nothingAgreed; + conn->stateFlags.inputDelay = 0; +#ifdef NETPLAY_CHECKSUM + conn->stateFlags.checksumInterval = NETPLAY_CHECKSUM_INTERVAL; +#endif + +#ifdef NETPLAY_STATISTICS + { + size_t i; + + conn->statistics.packetsReceived = 0; + conn->statistics.packetsSent = 0; + for (i = 0; i < PACKET_NUM; i++) + { + conn->statistics.packetTypeReceived[i] = 0; + conn->statistics.packetTypeSent[i] = 0; + } + } +#endif + + NetConnection_go(conn); + + return conn; +} + +static void +NetConnection_doDeleteCallback(NetConnection *conn) { + if (conn->deleteCallback != NULL) { + //NetConnection_incRef(conn); + conn->deleteCallback(conn); + //NetConnection_decRef(conn); + } +} + +static void +NetConnection_delete(NetConnection *conn) { + NetConnection_doDeleteCallback(conn); + if (conn->stateData != NULL) { + NetConnectionStateData_release(conn->stateData); + conn->stateData = NULL; + } + free(conn->readBuf); + PacketQueue_uninit(&conn->queue); + +#ifdef NETPLAY_DEBUG_FILE + if (conn->debugFile != NULL) { + if (netplayDebugFile == conn->debugFile) { + // There may be other network connections, with an open + // debug file, but we don't know about that. + // The debugging person just has to work around that. + netplayDebugFile = NULL; + } + uio_fclose(conn->debugFile); + } +#endif + + free(conn); +} + +static void +Netplay_doCloseCallback(NetConnection *conn) { + if (conn->closeCallback != NULL) { + //NetConnection_incRef(conn); + conn->closeCallback(conn); + //NetConnection_decRef(conn); + } +} + +// Auxiliary function for closing, used by both closeCallback() and +// NetConnection_close() +static void +NetConnection_doClose(NetConnection *conn) { + conn->stateFlags.connected = false; + conn->stateFlags.disconnected = true; + + // First the callback, so that it can still use the information + // of what is the current state, and the stateData: + Netplay_doCloseCallback(conn); + + NetConnection_setState(conn, NetState_unconnected); +} + +// Called when the NetDescriptor is shut down. +static void +closeCallback(NetDescriptor *nd) { + NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd); + if (conn == NULL) + return; + conn->nd = NULL; + NetConnection_doClose(conn); +} + +// Close and release a NetConnection. +void +NetConnection_close(NetConnection *conn) { + if (conn->nd != NULL) { + NetDescriptor_setCloseCallback(conn->nd, NULL); + // We're not interested in the close callback of the + // NetDescriptor anymore. + NetDescriptor_close(conn->nd); + // This would queue the close callback. + conn->nd = NULL; + } + if (!conn->stateFlags.disconnected) + NetConnection_doClose(conn); + NetConnection_delete(conn); +} + +void +NetConnection_doErrorCallback(NetConnection *nd, int err) { + NetConnectionError error; + + if (nd->errorCallback != NULL) { + error.state = nd->state; + error.err = err; + } + (*nd->errorCallback)(nd, &error); +} + +void +NetConnection_setStateData(NetConnection *conn, + NetConnectionStateData *stateData) { + conn->stateData = stateData; +} + +NetConnectionStateData * +NetConnection_getStateData(const NetConnection *conn) { + return conn->stateData; +} + +void +NetConnection_setExtra(NetConnection *conn, void *extra) { + conn->extra = extra; +} + +void * +NetConnection_getExtra(const NetConnection *conn) { + return conn->extra; +} + +void +NetConnection_setReadyCallback(NetConnection *conn, + NetConnection_ReadyCallback callback, void *arg) { + conn->readyCallback = callback; + conn->readyCallbackArg = arg; +} + +NetConnection_ReadyCallback +NetConnection_getReadyCallback(const NetConnection *conn) { + return conn->readyCallback; +} + +void * +NetConnection_getReadyCallbackArg(const NetConnection *conn) { + return conn->readyCallbackArg; +} + +void +NetConnection_setResetCallback(NetConnection *conn, + NetConnection_ResetCallback callback, void *arg) { + conn->resetCallback = callback; + conn->resetCallbackArg = arg; +} + +NetConnection_ResetCallback +NetConnection_getResetCallback(const NetConnection *conn) { + return conn->resetCallback; +} + +void * +NetConnection_getResetCallbackArg(const NetConnection *conn) { + return conn->resetCallbackArg; +} + +void +NetConnection_setState(NetConnection *conn, NetState state) { +#ifdef NETPLAY_DEBUG + log_add(log_Debug, "NETPLAY: [%d] +/- Connection state changed to: " + "%s.\n", conn->player, netStateData[state].name); +#endif +#ifdef DEBUG + if (state == conn->state) { + log_add(log_Warning, "NETPLAY: [%d] Connection state set to %s " + "while already in that state.\n", + conn->player, netStateData[state].name); + } +#endif + conn->state = state; +} + +NetState +NetConnection_getState(const NetConnection *conn) { + return conn->state; +} + +bool +NetConnection_getDiscriminant(const NetConnection *conn) { + return conn->stateFlags.discriminant; +} + +const NetplayPeerOptions * +NetConnection_getPeerOptions(const NetConnection *conn) { + return conn->options; +} + +bool +NetConnection_isConnected(const NetConnection *conn) { + return conn->stateFlags.connected; +} + +int +NetConnection_getPlayerNr(const NetConnection *conn) { + return conn->player; +} + +size_t +NetConnection_getInputDelay(const NetConnection *conn) { + return conn->stateFlags.inputDelay; +} + +#ifdef NETPLAY_CHECKSUM +ChecksumBuffer * +NetConnection_getChecksumBuffer(NetConnection *conn) { + return &conn->checksumBuffer; +} + +size_t +NetConnection_getChecksumInterval(const NetConnection *conn) { + return conn->stateFlags.checksumInterval; +} +#endif /* NETPLAY_CHECKSUM */ + +#ifdef NETPLAY_STATISTICS +NetStatistics * +NetConnection_getStatistics(NetConnection *conn) { + return &conn->statistics; +} +#endif + diff --git a/src/uqm/supermelee/netplay/netconnection.h b/src/uqm/supermelee/netplay/netconnection.h new file mode 100644 index 0000000..485d3c4 --- /dev/null +++ b/src/uqm/supermelee/netplay/netconnection.h @@ -0,0 +1,260 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_ +#define UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_ + +#include "netplay.h" + // for NETPLAY_STATISTICS + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct NetConnection NetConnection; +typedef struct NetConnectionError NetConnectionError; +typedef struct ConnectStateData ConnectStateData; +#ifdef NETPLAY_STATISTICS +typedef struct NetStatistics NetStatistics; +#endif + +typedef void (*NetConnection_ConnectCallback)(NetConnection *nd); +typedef void (*NetConnection_CloseCallback)(NetConnection *nd); +typedef void (*NetConnection_ErrorCallback)(NetConnection *nd, + const NetConnectionError *error); +typedef void (*NetConnection_DeleteCallback)(NetConnection *nd); + +typedef void (*NetConnection_ReadyCallback)(NetConnection *conn, void *arg); +typedef void (*NetConnection_ResetCallback)(NetConnection *conn, void *arg); + +#if defined(__cplusplus) +} +#endif + +#include "netstate.h" +#include "netoptions.h" +#ifdef NETPLAY_CHECKSUM +# include "checkbuf.h" +#endif +#if defined(NETPLAY_STATISTICS) || defined(NETCONNECTION_INTERNAL) +# include "packet.h" +#endif +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) +# include "libs/uio.h" +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +struct NetConnectionError { + NetState state; + int err; + union { + const struct ListenError *listenError; + const struct ConnectError *connectError; + } extra; +}; + +#ifdef NETPLAY_STATISTICS +struct NetStatistics { + size_t packetsReceived; + size_t packetTypeReceived[PACKET_NUM]; + size_t packetsSent; + size_t packetTypeSent[PACKET_NUM]; +}; +#endif + +#if defined(__cplusplus) +} +#endif + +#ifdef NETCONNECTION_INTERNAL +#include "libs/net.h" +#include "packetq.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct { + // For actions that require agreement by both parties. + bool localOk : 1; /* Action confirmed by us */ + bool remoteOk : 1; /* Action confirmed by the remote party */ + bool canceling : 1; /* Awaiting cancel confirmation */ +} HandShakeFlags; + +typedef struct { + // For actions that do not require agreement, but for which it + // only is relevant that both sides are ready. + bool localReady : 1; + bool remoteReady : 1; +} ReadyFlags; + +typedef struct { + bool localReset : 1; + bool remoteReset : 1; +} ResetFlags; + +// Which parameters have we both sides of a connection reached agreement on? +typedef struct { + bool randomSeed : 1; +} Agreement; + +typedef struct { + bool connected; + /* This NetConnection is connected. */ + bool disconnected; + /* This NetConnection has been disconnected. This implies + * !connected. It is only set if the NetConnection was once + * connected, but is no longer. */ + bool discriminant; + /* If it is true here, it is false on the remote side + * of the same connection. It may be used to break ties. + * It is guaranteed not to change during a connection. Undefined + * while not connected. */ + HandShakeFlags handshake; + ReadyFlags ready; + ResetFlags reset; + Agreement agreement; + size_t inputDelay; + /* Used during negotiation of the actual inputDelay. This + * field does NOT necessarilly contain the actual input delay, + * which is a property of the game, not of any specific + * connection. Use getBattleInputDelay() to get at it. */ +#ifdef NETPLAY_CHECKSUM + size_t checksumInterval; +#endif +} NetStateFlags; + +struct NetConnection { + NetDescriptor *nd; + int player; + // Number of the player for this connection, as it is + // known locally. For the other player, it may be + // differently. + NetState state; + NetStateFlags stateFlags; + + NetConnection_ReadyCallback readyCallback; + // Called when both sides have indicated that they are ready. + // Set by Netplay_localReady(). + void *readyCallbackArg; + // Extra argument for readyCallback(). + // XXX: when is this cleaned up if a connection is broken? + + NetConnection_ResetCallback resetCallback; + // Called when a reset has been signalled and confirmed. + // Set by Netplay_localReset(). + void *resetCallbackArg; + // Extra argument for resetCallback(). + // XXX: when is this cleaned up if a connection is broken? + + const NetplayPeerOptions *options; + PacketQueue queue; +#ifdef NETPLAY_STATISTICS + NetStatistics statistics; +#endif +#ifdef NETPLAY_CHECKSUM + ChecksumBuffer checksumBuffer; +#endif +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) + uio_Stream *debugFile; +#endif + + NetConnection_ConnectCallback connectCallback; + NetConnection_CloseCallback closeCallback; + // Called when the NetConnection becomes disconnected. + NetConnection_ErrorCallback errorCallback; + NetConnection_DeleteCallback deleteCallback; + // Called when the NetConnection is destroyed. + uint8 *readBuf; + uint8 *readEnd; + NetConnectionStateData *stateData; + // State dependant information. + void *extra; +}; + +struct ConnectStateData { + NETCONNECTION_STATE_DATA_COMMON + + bool isServer; + union { + struct ConnectState *connectState; + struct ListenState *listenState; + } state; +}; + +#endif /* NETCONNECTION_INTERNAL */ + + +NetConnection *NetConnection_open(int player, + const NetplayPeerOptions *options, + NetConnection_ConnectCallback connectCallback, + NetConnection_CloseCallback closeCallback, + NetConnection_ErrorCallback errorCallback, + NetConnection_DeleteCallback deleteCallback, void *extra); +void NetConnection_close(NetConnection *conn); +bool NetConnection_isConnected(const NetConnection *conn); + +void NetConnection_doErrorCallback(NetConnection *nd, int err); + +void NetConnection_setStateData(NetConnection *conn, + NetConnectionStateData *stateData); +NetConnectionStateData *NetConnection_getStateData(const NetConnection *conn); +void NetConnection_setExtra(NetConnection *conn, void *extra); +void *NetConnection_getExtra(const NetConnection *conn); +void NetConnection_setState(NetConnection *conn, NetState state); +NetState NetConnection_getState(const NetConnection *conn); +bool NetConnection_getDiscriminant(const NetConnection *conn); +const NetplayPeerOptions *NetConnection_getPeerOptions( + const NetConnection *conn); +int NetConnection_getPlayerNr(const NetConnection *conn); +size_t NetConnection_getInputDelay(const NetConnection *conn); +#ifdef NETPLAY_CHECKSUM +ChecksumBuffer *NetConnection_getChecksumBuffer(NetConnection *conn); +size_t NetConnection_getChecksumInterval(const NetConnection *conn); +#endif +#ifdef NETPLAY_STATISTICS +NetStatistics *NetConnection_getStatistics(NetConnection *conn); +#endif + +void NetConnection_setReadyCallback(NetConnection *conn, + NetConnection_ReadyCallback callback, void *arg); +NetConnection_ReadyCallback NetConnection_getReadyCallback( + const NetConnection *conn); +void *NetConnection_getReadyCallbackArg(const NetConnection *conn); + +void NetConnection_setResetCallback(NetConnection *conn, + NetConnection_ResetCallback callback, void *arg); +NetConnection_ResetCallback NetConnection_getResetCallback( + const NetConnection *conn); +void *NetConnection_getResetCallbackArg(const NetConnection *conn); + + +#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE) +extern uio_Stream *netplayDebugFile; +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_ */ + + diff --git a/src/uqm/supermelee/netplay/netinput.c b/src/uqm/supermelee/netplay/netinput.c new file mode 100644 index 0000000..9823deb --- /dev/null +++ b/src/uqm/supermelee/netplay/netinput.c @@ -0,0 +1,157 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#include "netplay.h" +#include "netinput.h" + +#include "../../intel.h" + // for NETWORK_CONTROL +#include "../../setup.h" + // For PlayerControl +#include "libs/log.h" + +#include <errno.h> +#include <stdlib.h> + + +static BattleInputBuffer battleInputBuffers[NUM_PLAYERS]; +static size_t BattleInput_inputDelay; + +// Call before initBattleInputBuffers() +void +setBattleInputDelay(size_t delay) { + BattleInput_inputDelay = delay; +} + +size_t +getBattleInputDelay(void) { + return BattleInput_inputDelay; +} + +static void +BattleInputBuffer_init(BattleInputBuffer *bib, size_t bufSize) { + bib->buf = malloc(bufSize * sizeof (BATTLE_INPUT_STATE)); + bib->maxSize = bufSize; + bib->first = 0; + bib->size = 0; +} + +static void +BattleInputBuffer_uninit(BattleInputBuffer *bib) { + if (bib->buf != NULL) { + free(bib->buf); + bib->buf = NULL; + } + bib->maxSize = 0; + bib->first = 0; + bib->size = 0; +} + +void +initBattleInputBuffers(void) { + size_t player; + int bufSize = BattleInput_inputDelay * 2 + 2; + + // The input of frame n will be processed in frame 'n + delay'. + // + // In the worst case, side 1 processes frames 'n' through 'n + delay - 1', + // then blocks in frame 'n + delay'. + // Then side 2 receives all this input, and progresses to 'delay' frames + // after the last frame the received input originated from, and blocks + // in the frame after it. + // So it sent input for frames 'n' through 'n + delay + delay + 1'. + // The input for these '2*delay + 2' frames are still + // unhandled by side 1, so it needs buffer space for this. + // + // Initially the buffer is filled with inputDelay zeroes, + // so that a party can process at least that much frames. + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleInputBuffer *bib = &battleInputBuffers[player]; + BattleInputBuffer_init(bib, bufSize); + + { + // Initially a party must be able to process at least inputDelay + // frames, so we fill the buffer with inputDelay zeros. + size_t i; + for (i = 0; i < BattleInput_inputDelay; i++) + BattleInputBuffer_push(bib, (BATTLE_INPUT_STATE) 0); + } + } +} + +void +uninitBattleInputBuffers(void) +{ + size_t player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleInputBuffer *bib; + + bib = &battleInputBuffers[player]; + BattleInputBuffer_uninit(bib); + } +} + +// On error, returns false and sets errno. +bool +BattleInputBuffer_push(BattleInputBuffer *bib, BATTLE_INPUT_STATE input) +{ + size_t next; + + if (bib->size == bib->maxSize) { + // No more space. + log_add(log_Error, "NETPLAY: battleInputBuffer full.\n"); + errno = ENOBUFS; + return false; + } + + next = (bib->first + bib->size) % bib->maxSize; + bib->buf[next] = input; + bib->size++; + return true; +} + +// On error, returns false and sets errno, and *input remains unchanged. +bool +BattleInputBuffer_pop(BattleInputBuffer *bib, BATTLE_INPUT_STATE *input) +{ + if (bib->size == 0) + { + // Buffer is empty. + errno = EAGAIN; + return false; + } + + *input = bib->buf[bib->first]; + bib->first = (bib->first + 1) % bib->maxSize; + bib->size--; + return true; +} + +BattleInputBuffer * +getBattleInputBuffer(size_t player) { + return &battleInputBuffers[player]; +} + + diff --git a/src/uqm/supermelee/netplay/netinput.h b/src/uqm/supermelee/netplay/netinput.h new file mode 100644 index 0000000..2afdf02 --- /dev/null +++ b/src/uqm/supermelee/netplay/netinput.h @@ -0,0 +1,57 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NETINPUT_H_ +#define UQM_SUPERMELEE_NETPLAY_NETINPUT_H_ + +#include "../../controls.h" + // for BATTLE_INPUT_STATE +#include "../../init.h" + +#if defined(__cplusplus) +extern "C" { +#endif + // for NUM_PLAYERS + +typedef struct BattleInputBuffer { + BATTLE_INPUT_STATE *buf; + // Cyclic buffer. if 'size' > 0, then 'first' is an index to + // the first used entry, 'size' is the number of used + // entries in the buffer, and (first + size) % maxSize is the + // index to just past the end of the buffer. + size_t maxSize; + size_t first; + size_t size; +} BattleInputBuffer; + +void setBattleInputDelay(size_t delay); +size_t getBattleInputDelay(void); +void initBattleInputBuffers(void); +void uninitBattleInputBuffers(void); +bool BattleInputBuffer_push(BattleInputBuffer *bib, + BATTLE_INPUT_STATE input); +bool BattleInputBuffer_pop(BattleInputBuffer *bib, + BATTLE_INPUT_STATE *input); + +BattleInputBuffer *getBattleInputBuffer(size_t player); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETINPUT_H_ */ diff --git a/src/uqm/supermelee/netplay/netmelee.c b/src/uqm/supermelee/netplay/netmelee.c new file mode 100644 index 0000000..e9368fd --- /dev/null +++ b/src/uqm/supermelee/netplay/netmelee.c @@ -0,0 +1,740 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "netmelee.h" +#include "libs/async.h" +#include "libs/callback.h" +#include "libs/log.h" +#include "libs/net.h" +#include "netinput.h" +#include "netmisc.h" +#include "netsend.h" +#include "notify.h" +#include "packetq.h" +#include "proto/npconfirm.h" +#include "proto/ready.h" +#include "proto/reset.h" + +#include "../../battlecontrols.h" + // for NetworkInputContext +#include "../../controls.h" + // for BATTLE_INPUT_STATE +#include "../../init.h" + // for NUM_PLAYERS +#include "../../globdata.h" + // for GLOBAL + +#include <errno.h> +#include <stdlib.h> + + +//////////////////////////////////////////////////////////////////////////// + + +NetConnection *netConnections[NUM_PLAYERS]; +size_t numNetConnections; + +void +addNetConnection(NetConnection *conn, int playerNr) { + netConnections[playerNr] = conn; + numNetConnections++; +} + +void +removeNetConnection(int playerNr) { + netConnections[playerNr] = NULL; + numNetConnections--; +} + +size_t +getNumNetConnections(void) { + return numNetConnections; +} + +// If the callback function returns 'false', the function will immediately +// return with 'false'. Otherwise it will return 'true' after calling +// the callback function for each connected player. +bool +forEachConnectedPlayer(ForEachConnectionCallback callback, void *arg) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + if (!(*callback)(conn, arg)) + return false; + } + return true; +} + +void +closeAllConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + + if (conn != NULL) + closePlayerNetworkConnection(player); + } +} + +void +closeDisconnectedConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + + if (conn != NULL && !NetConnection_isConnected(conn)) + closePlayerNetworkConnection(player); + } +} + +//////////////////////////////////////////////////////////////////////////// + + +struct melee_state * +NetMelee_getMeleeState(NetConnection *conn) { + if (NetConnection_getState(conn) > NetState_connecting) { + BattleStateData *battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + return battleStateData->meleeState; + } else { + return (struct melee_state *) NetConnection_getExtra(conn); + } +} + +struct battlestate_struct * +NetMelee_getBattleState(NetConnection *conn) { + if (NetConnection_getState(conn) > NetState_connecting) { + BattleStateData *battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + return battleStateData->battleState; + } else { + return NULL; + } +} + +//////////////////////////////////////////////////////////////////////////// + +static inline void +netInputAux(uint32 timeoutMs) { + NetManager_process(&timeoutMs); + // This may cause more packets to be queued, hence the + // flushPacketQueues(). + Async_process(); + flushPacketQueues(); + // During the flush, a disconnect may be noticed, which triggers + // another callback. It must be handled immediately, before + // another flushPacketQueue() can occur, which would not know + // that the socket is no longer valid. + // TODO: modify the close handling so this order isn't + // necessary. + Callback_process(); +} + +// Check the network connections for input. +void +netInput(void) { + netInputAux(0); +} + +void +netInputBlocking(uint32 timeoutMs) { + uint32 nextAsyncMs; + + nextAsyncMs = Async_timeBeforeNextMs(); + if (nextAsyncMs < timeoutMs) + timeoutMs = nextAsyncMs; + + netInputAux(timeoutMs); +} + + +//////////////////////////////////////////////////////////////////////////// + + +// Send along all pending network packets. +void +flushPacketQueues(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn; + int flushStatus; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + flushStatus = flushPacketQueue(conn); + if (flushStatus == -1 && errno != EAGAIN && errno != EWOULDBLOCK) + closePlayerNetworkConnection(player); + } +} + +void +confirmConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_confirm(conn); + } +} + +void +cancelConfirmations(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_cancelConfirmation(conn); + } +} + +void +connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_localReady(conn, callback, arg, true); + } +} + +bool +allConnected(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + return false; + } + return true; +} + +void +initBattleStateDataConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleStateData *battleStateData; + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + battleStateData->endFrameCount = 0; + } +} + +void +setBattleStateConnections(struct battlestate_struct *bs) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleStateData *battleStateData; + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + battleStateData->battleState = bs; + } +} + +BATTLE_INPUT_STATE +networkBattleInput(NetworkInputContext *context, STARSHIP *StarShipPtr) { + BattleInputBuffer *bib = getBattleInputBuffer(context->playerNr); + BATTLE_INPUT_STATE result; + + for (;;) { + bool ok; + +#if 0 + // This is a useful debugging trick. By enabling this #if + // block, this side will always lag the maximum number of frames + // behind the other side. When the remote side stops on some event + // (a breakpoint or so), this side will stop too, waiting for input + // in the loop below, but it won't have processed the frame that + // triggered the event yet. If you then jump over this 'if' + // statement here, you can walk through the decisive frames + // manually. Works best with no input delay. + if (bib->size <= getBattleInputDelay() + 1) { + ok = false; + } else +#endif + ok = BattleInputBuffer_pop(bib, &result); + // Get the input from the front of the + // buffer. + if (ok) + break; + + { + NetConnection *conn = netConnections[context->playerNr]; + + // First try whether there is incoming data, without blocking. + // If there isn't any, only then give a warning, and then + // block after all. + netInput(); + if (!NetConnection_isConnected(conn)) + { + // Connection aborted. + GLOBAL(CurrentActivity) |= CHECK_ABORT; + return (BATTLE_INPUT_STATE) 0; + } + + if (GLOBAL(CurrentActivity) & CHECK_ABORT) + return (BATTLE_INPUT_STATE) 0; + +#if 0 + log_add(log_Warning, "NETPLAY: [%d] stalling for " + "network input. Increase the input delay if this " + "happens a lot.\n", context->playerNr); +#endif +#define MAX_BLOCK_TIME 500 + netInputBlocking(MAX_BLOCK_TIME); + if (!NetConnection_isConnected(conn)) + { + // Connection aborted. + GLOBAL(CurrentActivity) |= CHECK_ABORT; + return (BATTLE_INPUT_STATE) 0; + } + } + } + + (void) StarShipPtr; + return result; +} + +static void +deleteConnectionCallback(NetConnection *conn) { + removeNetConnection(NetConnection_getPlayerNr(conn)); +} + +NetConnection * +openPlayerNetworkConnection(COUNT player, void *extra) { + NetConnection *conn; + + assert(netConnections[player] == NULL); + + conn = NetConnection_open(player, + &netplayOptions.peer[player], NetMelee_connectCallback, + NetMelee_closeCallback, NetMelee_errorCallback, + deleteConnectionCallback, extra); + + addNetConnection(conn, player); + return conn; +} + +void +closePlayerNetworkConnection(COUNT player) { + assert(netConnections[player] != NULL); + + NetConnection_close(netConnections[player]); +} + +bool +setupInputDelay(size_t localInputDelay) { + COUNT player; + bool haveNetworkPlayer = false; + // We have at least one network controlled player. + size_t inputDelay = 0; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + haveNetworkPlayer = true; + if (NetConnection_getInputDelay(conn) > inputDelay) + inputDelay = NetConnection_getInputDelay(conn); + } + + if (haveNetworkPlayer && inputDelay < localInputDelay) + inputDelay = localInputDelay; + + setBattleInputDelay(inputDelay); + return true; +} + +static bool +setStateConnection(NetConnection *conn, void *arg) { + const NetState *state = (NetState *) arg; + NetConnection_setState(conn, *state); + return true; +} + +bool +setStateConnections(NetState state) { + return forEachConnectedPlayer(setStateConnection, &state); +} + +static bool +sendAbortConnection(NetConnection *conn, void *arg) { + const NetplayAbortReason *reason = (NetplayAbortReason *) arg; + sendAbort(conn, *reason); + return true; +} + +bool +sendAbortConnections(NetplayAbortReason reason) { + return forEachConnectedPlayer(sendAbortConnection, &reason); +} + +static bool +resetConnection(NetConnection *conn, void *arg) { + const NetplayResetReason *reason = (NetplayResetReason *) arg; + Netplay_localReset(conn, *reason); + return true; +} + +bool +resetConnections(NetplayResetReason reason) { + return forEachConnectedPlayer(resetConnection, &reason); +} + +///////////////////////////////////////////////////////////////////////////// + +typedef struct { + NetConnection_ReadyCallback readyCallback; + void *readyCallbackArg; + bool notifyRemote; +} LocalReadyConnectionArg; + +static bool +localReadyConnection(NetConnection *conn, void *arg) { + LocalReadyConnectionArg *readyArg = (LocalReadyConnectionArg *) arg; + Netplay_localReady(conn, readyArg->readyCallback, + readyArg->readyCallbackArg, readyArg->notifyRemote); + return true; +} + +bool +localReadyConnections(NetConnection_ReadyCallback readyCallback, + void *readyArg, bool notifyRemote) { + LocalReadyConnectionArg arg; + arg.readyCallback = readyCallback; + arg.readyCallbackArg = readyArg; + arg.notifyRemote = notifyRemote; + + return forEachConnectedPlayer(localReadyConnection, &arg); +} + + +///////////////////////////////////////////////////////////////////////////// + +#define NETWORK_POLL_DELAY (ONE_SECOND / 24) + +typedef struct NegotiateReadyState NegotiateReadyState; +struct NegotiateReadyState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN(*InputFunc)(void *pInputState); + + NetConnection *conn; + NetState nextState; + bool done; +}; + +static BOOLEAN +negotiateReadyInputFunc(NegotiateReadyState *state) { + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need negotiateReadyInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +// Called when both sides are ready +static void +negotiateReadyBothReadyCallback(NetConnection *conn, void *arg) { + NegotiateReadyState *state =(NegotiateReadyState *) arg; + + NetConnection_setState(conn, state->nextState); + // This has to be done immediately, as more packets in the + // receive queue may be handled by the netInput() call that + // triggered this callback. + // This is the reason for the nextState argument to + // negotiateReady(); setting the state after the call to + // negotiateReady() would be too late. + state->done = true; +} + +bool +negotiateReady(NetConnection *conn, bool notifyRemote, NetState nextState) { + NegotiateReadyState state; + state.InputFunc = (BOOLEAN(*)(void *)) negotiateReadyInputFunc; + state.conn = conn; + state.nextState = nextState; + state.done = false; + + Netplay_localReady(conn, negotiateReadyBothReadyCallback, + (void *) &state, notifyRemote); + flushPacketQueue(conn); + if (!state.done) + DoInput(&state, FALSE); + + return NetConnection_isConnected(conn); +} + +// Wait for all connections to get ready. +// XXX: Right now all connections are handled one by one. Handling them all +// at once would be faster but would require more work, which is +// not worth it as the time is minimal and this function is not +// time critical. +bool +negotiateReadyConnections(bool notifyRemote, NetState nextState) { + COUNT player; + size_t numDisconnected = 0; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) { + numDisconnected++; + continue; + } + + negotiateReady(conn, notifyRemote, nextState); + } + + return numDisconnected == 0; +} + +typedef struct WaitReadyState WaitReadyState; +struct WaitReadyState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN (*InputFunc)(void *pInputState); + + NetConnection *conn; + NetConnection_ReadyCallback readyCallback; + void *readyCallbackArg; + bool done; +}; + +static void +waitReadyCallback(NetConnection *conn, void *arg) { + WaitReadyState *state =(WaitReadyState *) arg; + state->done = true; + + // Call the original callback. + state->readyCallback(conn, state->readyCallbackArg); +} + +static BOOLEAN +waitReadyInputFunc(WaitReadyState *state) { + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need negotiateReadyInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +bool +waitReady(NetConnection *conn) { + WaitReadyState state; + state.InputFunc =(BOOLEAN(*)(void *)) waitReadyInputFunc; + state.conn = conn; + state.readyCallback = NetConnection_getReadyCallback(conn); + state.readyCallbackArg = NetConnection_getReadyCallbackArg(conn); + state.done = false; + + NetConnection_setReadyCallback(conn, waitReadyCallback, (void *) &state); + + DoInput(&state, FALSE); + + return NetConnection_isConnected(conn); +} + + +//////////////////////////////////////////////////////////////////////////// + +typedef struct WaitResetState WaitResetState; +struct WaitResetState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN(*InputFunc)(void *pInputState); + + NetConnection *conn; + NetState nextState; + bool done; +}; + +static BOOLEAN +waitResetInputFunc(WaitResetState *state) { + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need waitResetInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +// Called when both sides are reset. +static void +waitResetBothResetCallback(NetConnection *conn, void *arg) { + WaitResetState *state = (WaitResetState *) arg; + + if (state->nextState != (NetState) -1) { + NetConnection_setState(conn, state->nextState); + // This has to be done immediately, as more packets in the + // receive queue may be handled by the netInput() call that + // triggered this callback. + // This is the reason for the nextState argument to + // waitReset(); setting the state after the call to + // waitReset() would be too late. + } + state->done = true; +} + +bool +waitReset(NetConnection *conn, NetState nextState) { + WaitResetState state; + state.InputFunc = (BOOLEAN(*)(void *)) waitResetInputFunc; + state.conn = conn; + state.nextState = nextState; + state.done = false; + + Netplay_setResetCallback(conn, waitResetBothResetCallback, + (void *) &state); + if (state.done) + goto out; + + + if (!Netplay_isLocalReset(conn)) { + Netplay_localReset(conn, ResetReason_manualReset); + flushPacketQueue(conn); + } + + if (!state.done) + DoInput(&state, FALSE); + +out: + return NetConnection_isConnected(conn); +} + +// Wait until we have received a reset packet from all connections. If we +// ourselves have not sent a reset packet, one is sent, with reason +// 'manualReset'. +// XXX: Right now all connections are handled one by one. Handling them all +// at once would be faster but would require more work, which is +// not worth it as the time is minimal and this function is not +// time critical. +// Use '(NetState) -1' for nextState to keep the current state. +bool +waitResetConnections(NetState nextState) { + COUNT player; + size_t numDisconnected = 0; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) { + numDisconnected++; + continue; + } + + waitReset(conn, nextState); + } + + return numDisconnected == 0; +} + +//////////////////////////////////////////////////////////////////////////// + diff --git a/src/uqm/supermelee/netplay/netmelee.h b/src/uqm/supermelee/netplay/netmelee.h new file mode 100644 index 0000000..83f3562 --- /dev/null +++ b/src/uqm/supermelee/netplay/netmelee.h @@ -0,0 +1,90 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(UQM_SUPERMELEE_NETPLAY_NETMELEE_H_) && defined(NETPLAY) +#define UQM_SUPERMELEE_NETPLAY_NETMELEE_H_ + +#include "netplay.h" +#include "netinput.h" +#include "netconnection.h" +#include "packetsenders.h" + +#include "../../battlecontrols.h" + // for NetworkInputContext +#include "../../controls.h" + // for BATTLE_INPUT_STATE +#include "../../races.h" + // for STARSHIP + +#if defined(__cplusplus) +extern "C" { +#endif + +extern struct NetConnection *netConnections[]; + + +void addNetConnection(NetConnection *conn, int playerNr); +void removeNetConnection(int playerNr); +void closeAllConnections(void); +void closeDisconnectedConnections(void); +size_t getNumNetConnections(void); +typedef bool(*ForEachConnectionCallback)(NetConnection *conn, void *arg); +bool forEachConnectedPlayer(ForEachConnectionCallback callback, void *arg); + +struct melee_state *NetMelee_getMeleeState(NetConnection *conn); +struct battlestate_struct *NetMelee_getBattleState(NetConnection *conn); + +void netInput(void); +void netInputBlocking(uint32 timeoutMs); +void flushPacketQueues(void); + +void confirmConnections(void); +void cancelConfirmations(void); +void connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg); + +bool allConnected(void); + +void initBattleStateDataConnections(void); +void setBattleStateConnections(struct battlestate_struct *bs); + +BATTLE_INPUT_STATE networkBattleInput(NetworkInputContext *context, + STARSHIP *StarShipPtr); + +NetConnection *openPlayerNetworkConnection(COUNT player, void *extra); +void closePlayerNetworkConnection(COUNT player); + +bool setupInputDelay(size_t localInputDelay); +bool setStateConnections(NetState state); +bool sendAbortConnections(NetplayAbortReason reason); +bool resetConnections(NetplayResetReason reason); +bool localReadyConnections(NetConnection_ReadyCallback readyCallback, + void *arg, bool notifyRemote); + +bool negotiateReady(NetConnection *conn, bool notifyRemote, + NetState nextState); +bool negotiateReadyConnections(bool notifyRemote, NetState nextState); +bool waitReady(NetConnection *conn); + +bool waitReset(NetConnection *conn, NetState nextState); +bool waitResetConnections(NetState nextState); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETMELEE_H_ */ diff --git a/src/uqm/supermelee/netplay/netmisc.c b/src/uqm/supermelee/netplay/netmisc.c new file mode 100644 index 0000000..3ab2f72 --- /dev/null +++ b/src/uqm/supermelee/netplay/netmisc.c @@ -0,0 +1,134 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "netmisc.h" + +#include "netmelee.h" +#include "notifyall.h" +#include "packetsenders.h" +#include "proto/ready.h" + +#include "../melee.h" + // For feedback functions. + +#include <stdlib.h> + + +static BattleStateData *BattleStateData_alloc(void); +static void BattleStateData_free(BattleStateData *battleStateData); +static inline BattleStateData *BattleStateData_new( + struct melee_state *meleeState, + struct battlestate_struct *battleState, + struct getmelee_struct *getMeleeState); +static void BattleStateData_delete(BattleStateData *battleStateData); + + +static BattleStateData * +BattleStateData_alloc(void) { + return (BattleStateData *) malloc(sizeof (BattleStateData)); +} + +static void +BattleStateData_free(BattleStateData *battleStateData) { + free(battleStateData); +} + +static inline BattleStateData * +BattleStateData_new(struct melee_state *meleeState, + struct battlestate_struct *battleState, + struct getmelee_struct *getMeleeState) { + BattleStateData *battleStateData = BattleStateData_alloc(); + battleStateData->releaseFunction = + (NetConnectionStateData_ReleaseFunction) BattleStateData_delete; + battleStateData->meleeState = meleeState; + battleStateData->battleState = battleState; + battleStateData->getMeleeState = getMeleeState; + return battleStateData; +} + +static void +BattleStateData_delete(BattleStateData *battleStateData) { + BattleStateData_free(battleStateData); +} + + +//////////////////////////////////////////////////////////////////////////// + + +static void NetMelee_enterState_inSetup(NetConnection *conn, void *arg); + +// Called when a connection has been established. +void +NetMelee_connectCallback(NetConnection *conn) { + BattleStateData *battleStateData; + struct melee_state *meleeState; + + meleeState = (struct melee_state *) NetConnection_getExtra(conn); + battleStateData = BattleStateData_new(meleeState, NULL, NULL); + NetConnection_setStateData(conn, (void *) battleStateData); + NetConnection_setExtra(conn, NULL); + + // We have sent no teams yet. Initialize the state accordingly. + MeleeSetup_resetSentTeams (meleeState->meleeSetup); + + sendInit(conn); + Netplay_localReady (conn, NetMelee_enterState_inSetup, NULL, false); +} + +// Called when a connection is closed. +void +NetMelee_closeCallback(NetConnection *conn) { + closeFeedback(conn); +} + +// Called when a network error occurs during connect. +void +NetMelee_errorCallback(NetConnection *conn, + const NetConnectionError *error) { + errorFeedback(conn); + (void) error; +} + +// Callback function for when both sides have finished initialisation after +// initial connect. +static void +NetMelee_enterState_inSetup(NetConnection *conn, void *arg) { + BattleStateData *battleStateData; + struct melee_state *meleeState; + int player; + + NetConnection_setState(conn, NetState_inSetup); + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + meleeState = battleStateData->meleeState; + + player = NetConnection_getPlayerNr(conn); + + connectedFeedback(conn); + + // Send our team to the remote side. + // XXX This only works with 2 players atm. + assert (NUM_PLAYERS == 2); + Melee_bootstrapSyncTeam (meleeState, player); + + flushPacketQueues(); + + (void) arg; +} + diff --git a/src/uqm/supermelee/netplay/netmisc.h b/src/uqm/supermelee/netplay/netmisc.h new file mode 100644 index 0000000..ea14921 --- /dev/null +++ b/src/uqm/supermelee/netplay/netmisc.h @@ -0,0 +1,77 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NETMISC_H_ +#define UQM_SUPERMELEE_NETPLAY_NETMISC_H_ + +typedef struct BattleStateData BattleStateData; + +#include "netconnection.h" +#include "netstate.h" +#include "types.h" + +#include "../../battle.h" + // for BattleFrameCounter, BATTLE_FRAME_RATE + +#if defined(__cplusplus) +extern "C" { +#endif + +struct BattleStateData { + NETCONNECTION_STATE_DATA_COMMON + + struct melee_state *meleeState; + struct battlestate_struct *battleState; + struct getmelee_struct *getMeleeState; + BattleFrameCounter endFrameCount; +}; + + +void NetMelee_connectCallback(NetConnection *conn); +void NetMelee_closeCallback(NetConnection *conn); +void NetMelee_errorCallback(NetConnection *conn, + const NetConnectionError *error); + +void NetMelee_reenterState_inSetup(NetConnection *conn); + + +// Returns true iff the connection is in a state where the confirmation +// handshake is meaningful. Right now this is only when we're in the +// pre-game setup menu. +static inline bool +handshakeMeaningful(NetState state) { + return state == NetState_inSetup; +} + +static inline bool +readyFlagsMeaningful(NetState state) { + return state == NetState_init || + state == NetState_preBattle || + state == NetState_selectShip || + state == NetState_interBattle || + state == NetState_inBattle || + state == NetState_endingBattle || + state == NetState_endingBattle2; +} + + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETMISC_H_ */ diff --git a/src/uqm/supermelee/netplay/netoptions.c b/src/uqm/supermelee/netplay/netoptions.c new file mode 100644 index 0000000..a74e1a3 --- /dev/null +++ b/src/uqm/supermelee/netplay/netoptions.c @@ -0,0 +1,39 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netoptions.h" + +NetplayOptions netplayOptions = { + /* .metaServer = */ "uqm.stack.nl", + /* .metaPort = */ "21836", + /* .peer = */ { + /* [0] Player 1 (bottom) */ { + /* .isServer = */ true, + /* .host = */ "localhost", + /* .port = */ "21837" /* 0x554d - "UM" */, + }, + /* [1] Player 2 (top) */ { + /* .isServer = */ true, + /* .host = */ "localhost", + /* .port = */ "21837" /* 0x554d - "UM" */, + }, + }, + /* .inputDelay = */ 2, +}; + + diff --git a/src/uqm/supermelee/netplay/netoptions.h b/src/uqm/supermelee/netplay/netoptions.h new file mode 100644 index 0000000..77b1637 --- /dev/null +++ b/src/uqm/supermelee/netplay/netoptions.h @@ -0,0 +1,56 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_ +#define UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_ + +#include "types.h" + +#include <stddef.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +#define NETPLAY_NUM_PLAYERS 2 + // Not using NUM_PLAYERS because that would mean we'd have + // to include init.h, and all that comes with it. + // XXX: Don't use a hardcoded limit. + +typedef struct { + bool isServer; + const char *host; + const char *port; + // May be given as a service name. +} NetplayPeerOptions; + +typedef struct { + const char *metaServer; + const char *metaPort; + // May be given as a service name. + NetplayPeerOptions peer[NETPLAY_NUM_PLAYERS]; + size_t inputDelay; +} NetplayOptions; +extern NetplayOptions netplayOptions; + + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_ */ diff --git a/src/uqm/supermelee/netplay/netplay.h b/src/uqm/supermelee/netplay/netplay.h new file mode 100644 index 0000000..b78c69a --- /dev/null +++ b/src/uqm/supermelee/netplay/netplay.h @@ -0,0 +1,77 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(UQM_SUPERMELEE_NETPLAY_NETPLAY_H_) && defined(NETPLAY) +#define UQM_SUPERMELEE_NETPLAY_NETPLAY_H_ + +// NETPLAY can either be unset (in which case we will never get here) +// NETPLAY_FULL, or NETPLAY_IPV4 (disables IPv6) +#define NETPLAY_IPV4 1 +#define NETPLAY_FULL 2 + +#define NETPLAY_PROTOCOL_VERSION_MAJOR 0 +#define NETPLAY_PROTOCOL_VERSION_MINOR 4 + +#define NETPLAY_MIN_UQM_VERSION_MAJOR 0 +#define NETPLAY_MIN_UQM_VERSION_MINOR 6 +#define NETPLAY_MIN_UQM_VERSION_PATCH 9 + +#undef NETPLAY_DEBUG + /* Extra debugging for netplay */ +#undef NETPLAY_DEBUG_FILE + /* Dump extra debugging information to file. + * Implies NETPLAY_DEBUG.*/ +#define NETPLAY_STATISTICS + /* Keep some statistics */ +#define NETPLAY_CHECKSUM + /* Send/process checksums to verify that both sides of a network + * connection are still in sync. + * If not enabled, incoming checksum packets will be ignored. + * TODO: make compilation of crc.c and checksum.c conditional. */ +#define NETPLAY_CHECKSUM_INTERVAL 1 + /* If NETPLAY_CHECKSUM is defined, this define determines + * every how many frames a checksum packet is sent. */ + +#define NETPLAY_READBUFSIZE 2048 +#define NETPLAY_CONNECTTIMEOUT 2000 + /* Time to wait for a connect() to succeed. In ms. */ +//#define NETPLAY_LISTENTIMEOUT 30000 +// /* Time to wait for a listen() to succeed. In ms. */ +#define NETPLAY_RETRYDELAY 2000 + /* Time to wait after all addresses of a host have been tried + * before starting retrying them all. In ms. */ +#define NETPLAY_LISTEN_BACKLOG 2 + /* Second argument to listen(). */ + + +#ifdef _MSC_VER +# if _MSC_VER < 1300 + /* NETPLAY_DEBUG_FILE requires the __VA_ARGS__ macro, which is + * not available on MSVC 6.0. */ +# undef NETPLAY_DEBUG_FILE +# endif +#endif + +#ifdef NETPLAY_DEBUG_FILE +# define NETPLAY_DEBUG +# define DUMP_CRC_OPS +#endif + + +#endif /* UQM_SUPERMELEE_NETPLAY_NETPLAY_H_ */ + diff --git a/src/uqm/supermelee/netplay/netrcv.c b/src/uqm/supermelee/netplay/netrcv.c new file mode 100644 index 0000000..b9ea5f7 --- /dev/null +++ b/src/uqm/supermelee/netplay/netrcv.c @@ -0,0 +1,193 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "port.h" + +#include "netconnection.h" +#include "netrcv.h" +#include "packet.h" + +#include "types.h" +#include "libs/log.h" + +#include <errno.h> +#include <string.h> + + +// Try to get a single packet from a stream of data. +// Returns 0 if the packet was successfully processed, or +// -1 on an error, in which case the state is unchanged. +static ssize_t +dataReceivedSingle(NetConnection *conn, const uint8 *data, + size_t dataLen) { + uint32 packetLen; + PacketType type; + int result; + + if (dataLen < sizeof (PacketHeader)) { + // Incomplete packet. We'll have to wait for the rest. + return 0; + } + + packetLen = packetLength((const Packet *) data); + type = packetType((const Packet *) data); + + if (!validPacketType(type)) { + log_add(log_Warning, "Packet with invalid type %d received.\n", type); + errno = EBADMSG; + return -1; + } + + if (packetLen < packetTypeData[type].len) { + // Bad len field of packet. + log_add(log_Warning, "Packet with bad length field received (type=" + "%s, lenfield=%d.\n", packetTypeData[type].name, + packetLen); + errno = EBADMSG; + return -1; + } + + if (dataLen < packetLen) { + // Incomplete packet. We'll have to wait for the rest. + return 0; + } + +#ifdef NETPLAY_STATISTICS + NetConnection_getStatistics(conn)->packetsReceived++; + NetConnection_getStatistics(conn)->packetTypeReceived[type]++; +#endif + +#ifdef NETPLAY_DEBUG + if (type != PACKET_BATTLEINPUT && type != PACKET_CHECKSUM) { + // Reporting BattleInput and Checksum would get so spammy that it + // would slow down the battle. + log_add(log_Debug, "NETPLAY: [%d] <== Received packet of type %s.\n", + NetConnection_getPlayerNr(conn), packetTypeData[type].name); + } +#ifdef NETPLAY_DEBUG_FILE + if (conn->debugFile != NULL) { + uio_fprintf(conn->debugFile, + "NETPLAY: [%d] <== Received packet of type %s.\n", + NetConnection_getPlayerNr(conn), packetTypeData[type].name); + } +#endif /* NETPLAY_DEBUG_FILE */ +#endif /* NETPLAY_DEBUG */ + + result = packetTypeData[type].handler(conn, data); + if (result == -1) { + // An error occured. errno is set by the handler. + return -1; + } + + return packetLen; +} + +// Try to get all the packets from a stream of data. +// Returns the number of bytes processed. +static ssize_t +dataReceivedMulti(NetConnection *conn, const uint8 *data, size_t len) { + size_t processed; + + processed = 0; + while (len > 0) { + ssize_t packetLen = dataReceivedSingle(conn, data, len); + if (packetLen == -1) { + // Bad packet. Errno is set. + return -1; + } + + if (packetLen == 0) { + // No packet was processed. This means that no complete + // packet arrived. + break; + } + + processed += packetLen; + data += packetLen; + len -= packetLen; + } + + return processed; +} + +void +dataReadyCallback(NetDescriptor *nd) { + NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd); + Socket *socket = NetDescriptor_getSocket(nd); + + for (;;) { + ssize_t numRead; + ssize_t numProcessed; + + numRead = Socket_recv(socket, conn->readEnd, + NETPLAY_READBUFSIZE - (conn->readEnd - conn->readBuf), 0); + if (numRead == 0) { + // Other side closed the connection. + NetDescriptor_close(nd); + return; + } + + if (numRead == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + return; // No more data for now. + else if (errno == EINTR) + continue; // System call was interrupted. Retry. + else + { + int savedErrno = errno; + log_add(log_Error, "recv() failed: %s.\n", + strerror(errno)); + NetConnection_doErrorCallback(conn, savedErrno); + NetDescriptor_close(nd); + return; + } + } + + conn->readEnd += numRead; + + numProcessed = dataReceivedMulti(conn, conn->readBuf, + conn->readEnd - conn->readBuf); + if (numProcessed == -1) { + // An error occured during processing. + // errno is set. + NetConnection_doErrorCallback(conn, errno); + NetDescriptor_close(nd); + return; + } + if (numProcessed == 0) { + // No packets could be processed. This means we need to receive + // more data first. + return; + } + + // Some packets have been processed. + // We more any rest to the front of the buffer, to make room + // for more data. + // A cyclic buffer would obviate the need for this move, + // but it would complicate things a lot. + memmove(conn->readBuf, conn->readBuf + numProcessed, + (conn->readEnd - conn->readBuf) - numProcessed); + conn->readEnd -= numProcessed; + } +} + + + diff --git a/src/uqm/supermelee/netplay/netrcv.h b/src/uqm/supermelee/netplay/netrcv.h new file mode 100644 index 0000000..a7577d9 --- /dev/null +++ b/src/uqm/supermelee/netplay/netrcv.h @@ -0,0 +1,34 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NETRCV_H_ +#define UQM_SUPERMELEE_NETPLAY_NETRCV_H_ + +#include "libs/net.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +void dataReadyCallback(NetDescriptor *nd); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETRCV_H_ */ diff --git a/src/uqm/supermelee/netplay/netsend.c b/src/uqm/supermelee/netplay/netsend.c new file mode 100644 index 0000000..b9f371f --- /dev/null +++ b/src/uqm/supermelee/netplay/netsend.c @@ -0,0 +1,95 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "port.h" + +#include "netsend.h" +#include "netconnection.h" +#include "packet.h" +#include "libs/log.h" +#include "libs/net.h" + +#include <assert.h> +#include <errno.h> +#include <string.h> + + +int +sendPacket(NetConnection *conn, Packet *packet) { + ssize_t sendResult; + size_t len; + Socket *socket; + + assert(NetConnection_isConnected(conn)); + +#ifdef NETPLAY_DEBUG + //if (packetType(packet) != PACKET_BATTLEINPUT && + // packetType(packet) != PACKET_CHECKSUM) { + // // Reporting BattleInput or Checksum would get so spammy that it + // // would slow down the battle. + // log_add(log_Debug, "NETPLAY: [%d] ==> Sending packet of type %s.\n", + // conn->player, packetTypeData[packetType(packet)].name); + //} +#ifdef NETPLAY_DEBUG_FILE + if (conn->debugFile != NULL) { + uio_fprintf(conn->debugFile, + "NETPLAY: [%d] ==> Sending packet of type %s.\n", + conn->player, packetTypeData[packetType(packet)].name); + } +#endif /* NETPLAY_DEBUG_FILE */ +#endif /* NETPLAY_DEBUG */ + + socket = NetDescriptor_getSocket(conn->nd); + + len = packetLength(packet); + while (len > 0) { + sendResult = Socket_send(socket, (void *) packet, len, 0); + if (sendResult >= 0) { + len -= sendResult; + continue; + } + + switch (errno) { + case EINTR: // System call interrupted, retry; + continue; + case ECONNRESET: { // Connection reset by peer. + // keep errno + return -1; + } + default: { + // Should not happen. + int savedErrno = errno; + log_add(log_Error, "send() failed: %s.\n", strerror(errno)); + errno = savedErrno; + return -1; + } + } + } + +#ifdef NETPLAY_STATISTICS + NetConnection_getStatistics(conn)->packetsSent++; + NetConnection_getStatistics(conn)->packetTypeSent[packetType(packet)]++; +#endif + + return 0; +} + + diff --git a/src/uqm/supermelee/netplay/netsend.h b/src/uqm/supermelee/netplay/netsend.h new file mode 100644 index 0000000..e005d03 --- /dev/null +++ b/src/uqm/supermelee/netplay/netsend.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NETSEND_H_ +#define UQM_SUPERMELEE_NETPLAY_NETSEND_H_ + +#include "packet.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +int sendPacket(NetConnection *conn, Packet *packet); + + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETSEND_H_ */ diff --git a/src/uqm/supermelee/netplay/netstate.c b/src/uqm/supermelee/netplay/netstate.c new file mode 100644 index 0000000..4382b94 --- /dev/null +++ b/src/uqm/supermelee/netplay/netstate.c @@ -0,0 +1,48 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "netstate.h" + +#include <assert.h> +#include <stdlib.h> + + +#define DEFINE_NETSTATEDATA(stateName) \ + { \ + /* .name = */ #stateName, \ + } +NetStateData netStateData[] = { + DEFINE_NETSTATEDATA(unconnected), + DEFINE_NETSTATEDATA(connecting), + DEFINE_NETSTATEDATA(init), + DEFINE_NETSTATEDATA(inSetup), + DEFINE_NETSTATEDATA(preBattle), + DEFINE_NETSTATEDATA(interBattle), + DEFINE_NETSTATEDATA(selectShip), + DEFINE_NETSTATEDATA(inBattle), + DEFINE_NETSTATEDATA(endingBattle), + DEFINE_NETSTATEDATA(endingBattle2), +}; + +void +NetConnectionStateData_release(NetConnectionStateData *stateData) { + assert(stateData->releaseFunction != NULL); + stateData->releaseFunction(stateData); +} + diff --git a/src/uqm/supermelee/netplay/netstate.h b/src/uqm/supermelee/netplay/netstate.h new file mode 100644 index 0000000..1d46d49 --- /dev/null +++ b/src/uqm/supermelee/netplay/netstate.h @@ -0,0 +1,83 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NETSTATE_H_ +#define UQM_SUPERMELEE_NETPLAY_NETSTATE_H_ + +#include "port.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct NetConnectionStateData NetConnectionStateData; + +// State of a NetConnection. +typedef enum { + NetState_unconnected, /* No connection initiated */ + NetState_connecting, /* Connection being setup */ + NetState_init, /* Initialising the connection */ + NetState_inSetup, /* In the network game setup */ + NetState_preBattle, /* Pre-battle initialisations */ + NetState_interBattle, /* Negotiations between battles. */ + NetState_selectShip, /* Selecting a ship in battle */ + NetState_inBattle, /* Battle has started */ + NetState_endingBattle, /* Both sides are prepared to end */ + NetState_endingBattle2, /* Waiting for the final synchronisation */ +} NetState; + +#if defined(__cplusplus) +} +#endif + +#include "types.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct { + const char *name; +} NetStateData; +extern NetStateData netStateData[]; + +typedef void (*NetConnectionStateData_ReleaseFunction)( + NetConnectionStateData *stateData); + +#define NETCONNECTION_STATE_DATA_COMMON \ + NetConnectionStateData_ReleaseFunction releaseFunction; + +struct +NetConnectionStateData { + NETCONNECTION_STATE_DATA_COMMON +}; + +void NetConnectionStateData_release(NetConnectionStateData *stateData); + +static inline bool +NetState_battleActive(NetState state) { + return state == NetState_inBattle || state == NetState_endingBattle || + state == NetState_endingBattle2; +} + + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NETSTATE_H_ */ diff --git a/src/uqm/supermelee/netplay/notify.c b/src/uqm/supermelee/netplay/notify.c new file mode 100644 index 0000000..8b35ead --- /dev/null +++ b/src/uqm/supermelee/netplay/notify.c @@ -0,0 +1,118 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// This files contains functions that notify the other side of local +// changes. + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "notify.h" + +#include "packetsenders.h" + + +// Convert a local player number to a side indication relative to this +// party. +static inline NetplaySide +netSide(NetConnection *conn, int side) { + if (side == conn->player) + return NetplaySide_remote; + + return NetplaySide_local; +} + +void +Netplay_Notify_shipSelected(NetConnection *conn, FleetShipIndex index) { + assert(NetConnection_getState(conn) == NetState_selectShip); + + sendSelectShip(conn, index); +} + +void +Netplay_Notify_battleInput(NetConnection *conn, BATTLE_INPUT_STATE input) { + assert(NetConnection_getState(conn) == NetState_inBattle || + NetConnection_getState(conn) == NetState_endingBattle || + NetConnection_getState(conn) == NetState_endingBattle2); + + sendBattleInput(conn, input); +} + +void +Netplay_Notify_setTeamName(NetConnection *conn, int player, + const char *name, size_t len) { + assert(NetConnection_getState(conn) == NetState_inSetup); + assert(!conn->stateFlags.handshake.localOk); + + sendTeamName(conn, netSide(conn, player), name, len); +} + +// On initialisation, or load. +void +Netplay_Notify_setFleet(NetConnection *conn, int player, + const MeleeShip *fleet, size_t fleetSize) { + assert(NetConnection_getState(conn) == NetState_inSetup); + assert(!conn->stateFlags.handshake.localOk); + + sendFleet(conn, netSide(conn, player), fleet, fleetSize); +} + +void +Netplay_Notify_setShip(NetConnection *conn, int player, + FleetShipIndex index, MeleeShip ship) { + assert(NetConnection_getState(conn) == NetState_inSetup); + assert(!conn->stateFlags.handshake.localOk); + + sendFleetShip(conn, netSide(conn, player), index, ship); +} + +void +Netplay_Notify_seedRandom(NetConnection *conn, uint32 seed) { + assert(NetConnection_getState(conn) == NetState_preBattle); + + sendSeedRandom(conn, seed); + conn->stateFlags.agreement.randomSeed = true; +} + +void +Netplay_Notify_inputDelay(NetConnection *conn, uint32 delay) { + assert(NetConnection_getState(conn) == NetState_preBattle); + + sendInputDelay(conn, delay); +} + +void +Netplay_Notify_frameCount(NetConnection *conn, + BattleFrameCounter frameCount) { + assert(NetConnection_getState(conn) == NetState_endingBattle); + + sendFrameCount(conn, frameCount); +} + +#ifdef NETPLAY_CHECKSUM +void +Netplay_Notify_checksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum) { + assert(NetState_battleActive(NetConnection_getState(conn))); + + sendChecksum(conn, frameNr, checksum); +} +#endif /* NETPLAY_CHECKSUM */ + + + + diff --git a/src/uqm/supermelee/netplay/notify.h b/src/uqm/supermelee/netplay/notify.h new file mode 100644 index 0000000..60a127d --- /dev/null +++ b/src/uqm/supermelee/netplay/notify.h @@ -0,0 +1,62 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_NOTIFY_H_ +#define UQM_SUPERMELEE_NETPLAY_NOTIFY_H_ + +#include "netplay.h" + // for NETPLAY_CHECKSUM +#include "netconnection.h" +#include "../../controls.h" + // for BATTLE_INPUT_STATE +#ifdef NETPLAY_CHECKSUM +# include "checksum.h" +#endif +#include "../meleeship.h" + // for MeleeShip +#include "../meleesetup.h" + // for FleetShipIndex + +#if defined(__cplusplus) +extern "C" { +#endif + +void Netplay_Notify_shipSelected(NetConnection *conn, FleetShipIndex index); +void Netplay_Notify_battleInput(NetConnection *conn, + BATTLE_INPUT_STATE input); +void Netplay_Notify_setTeamName(NetConnection *conn, int player, + const char *name, size_t len); +void Netplay_Notify_setFleet(NetConnection *conn, int player, + const MeleeShip *fleet, size_t fleetSize); +void Netplay_Notify_setShip(NetConnection *conn, int player, + FleetShipIndex index, MeleeShip ship); +void Netplay_Notify_seedRandom(NetConnection *conn, uint32 seed); +void Netplay_Notify_inputDelay(NetConnection *conn, uint32 delay); +void Netplay_Notify_frameCount(NetConnection *conn, + BattleFrameCounter frameCount); +#ifdef NETPLAY_CHECKSUM +void Netplay_Notify_checksum(NetConnection *conn, + BattleFrameCounter frameCount, Checksum checksum); +#endif + + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_NOTIFY_H_ */ diff --git a/src/uqm/supermelee/netplay/notifyall.c b/src/uqm/supermelee/netplay/notifyall.c new file mode 100644 index 0000000..2d0cc8a --- /dev/null +++ b/src/uqm/supermelee/netplay/notifyall.c @@ -0,0 +1,146 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "notifyall.h" + +#include "netmelee.h" +#include "notify.h" + +// Notify the network connections of a team name change. +void +Netplay_NotifyAll_setTeamName (MELEE_STATE *pMS, size_t playerNr) +{ + const char *name; + size_t len; + size_t playerI; + + name = MeleeSetup_getTeamName (pMS->meleeSetup, playerNr); + len = strlen (name); + for (playerI = 0; playerI < NUM_PLAYERS; playerI++) + { + NetConnection *conn = netConnections[playerI]; + + if (conn == NULL) + continue; + + if (!NetConnection_isConnected (conn)) + continue; + + if (NetConnection_getState (conn) != NetState_inSetup) + continue; + + Netplay_Notify_setTeamName (conn, playerNr, name, len); + } +} + +// Notify the network connections of the configuration of a fleet. +void +Netplay_NotifyAll_setFleet (MELEE_STATE *pMS, size_t playerNr) +{ + MeleeSetup *setup = pMS->meleeSetup; + const MeleeShip *ships = MeleeSetup_getFleet (setup, playerNr); + size_t playerI; + + for (playerI = 0; playerI < NUM_PLAYERS; playerI++) { + NetConnection *conn = netConnections[playerI]; + + if (conn == NULL) + continue; + + if (!NetConnection_isConnected (conn)) + continue; + + if (NetConnection_getState (conn) != NetState_inSetup) + continue; + + Netplay_Notify_setFleet (conn, playerNr, ships, MELEE_FLEET_SIZE); + } +} + +// Notify the network of a change in the configuration of a fleet. +void +Netplay_NotifyAll_setShip (MELEE_STATE *pMS, size_t playerNr, size_t index) +{ + MeleeSetup *setup = pMS->meleeSetup; + MeleeShip ship = MeleeSetup_getShip (setup, playerNr, index); + + size_t playerI; + for (playerI = 0; playerI < NUM_PLAYERS; playerI++) + { + NetConnection *conn = netConnections[playerI]; + + if (conn == NULL) + continue; + + if (!NetConnection_isConnected (conn)) + continue; + + if (NetConnection_getState (conn) != NetState_inSetup) + continue; + + Netplay_Notify_setShip (conn, playerNr, index, ship); + } +} + +static bool +Netplay_NotifyAll_inputDelayCallback(NetConnection *conn, void *arg) { + const size_t *delay = (size_t *) arg; + Netplay_Notify_inputDelay(conn, *delay); + return true; +} + +bool +Netplay_NotifyAll_inputDelay(size_t delay) { + return forEachConnectedPlayer(Netplay_NotifyAll_inputDelayCallback, + &delay); +} + +#ifdef NETPLAY_CHECKSUM +void +Netplay_NotifyAll_checksum(BattleFrameCounter frameNr, Checksum checksum) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_Notify_checksum(conn, frameNr, checksum); + } +} +#endif /* NETPLAY_CHECKSUM */ + +void +Netplay_NotifyAll_battleInput(BATTLE_INPUT_STATE input) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_Notify_battleInput(conn, input); + } +} + diff --git a/src/uqm/supermelee/netplay/notifyall.h b/src/uqm/supermelee/netplay/notifyall.h new file mode 100644 index 0000000..d20ca81 --- /dev/null +++ b/src/uqm/supermelee/netplay/notifyall.h @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef NOTIFYALL_H +#define NOTIFYALL_H + +#include "../../battle.h" +#include "../../battlecontrols.h" +#include "../melee.h" +#ifdef NETPLAY_CHECKSUM +# include "checksum.h" +#endif /* NETPLAY_CHECKSUM */ + +#if defined(__cplusplus) +extern "C" { +#endif + +void Netplay_NotifyAll_setTeamName (MELEE_STATE *pMS, size_t playerNr); +void Netplay_NotifyAll_setFleet (MELEE_STATE *pMS, size_t playerNr); +void Netplay_NotifyAll_setShip (MELEE_STATE *pMS, size_t playerNr, + size_t index); + +bool Netplay_NotifyAll_inputDelay(size_t delay); +#ifdef NETPLAY_CHECKSUM +void Netplay_NotifyAll_checksum(BattleFrameCounter frameNr, + Checksum checksum); +#endif /* NETPLAY_CHECKSUM */ +void Netplay_NotifyAll_battleInput(BATTLE_INPUT_STATE input); + + +#if defined(__cplusplus) +} +#endif + +#endif /* NOTIFYALL_H */ + diff --git a/src/uqm/supermelee/netplay/packet.c b/src/uqm/supermelee/netplay/packet.c new file mode 100644 index 0000000..442be17 --- /dev/null +++ b/src/uqm/supermelee/netplay/packet.c @@ -0,0 +1,263 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "packet.h" + +#include "uqmversion.h" + +#include "netrcv.h" +#include "packethandlers.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + + +#define DEFINE_PACKETDATA(name) \ + { \ + /* .len = */ sizeof (Packet_##name), \ + /* .handler = */ (PacketHandler) PacketHandler_##name, \ + /* .name = */ #name, \ + } +PacketTypeData packetTypeData[PACKET_NUM] = { + DEFINE_PACKETDATA(Init), + DEFINE_PACKETDATA(Ping), + DEFINE_PACKETDATA(Ack), + DEFINE_PACKETDATA(Ready), + DEFINE_PACKETDATA(Fleet), + DEFINE_PACKETDATA(TeamName), + DEFINE_PACKETDATA(Handshake0), + DEFINE_PACKETDATA(Handshake1), + DEFINE_PACKETDATA(HandshakeCancel), + DEFINE_PACKETDATA(HandshakeCancelAck), + DEFINE_PACKETDATA(SeedRandom), + DEFINE_PACKETDATA(InputDelay), + DEFINE_PACKETDATA(SelectShip), + DEFINE_PACKETDATA(BattleInput), + DEFINE_PACKETDATA(FrameCount), + DEFINE_PACKETDATA(Checksum), + DEFINE_PACKETDATA(Abort), + DEFINE_PACKETDATA(Reset), +}; + +static inline void * +Packet_alloc(size_t size) { + return malloc(size); +} + +static Packet * +Packet_create(PacketType type, size_t extraSize) { + Packet *result; + size_t len; + + // Alignment requirement. + assert(extraSize % 4 == 0); + + len = packetTypeData[type].len + extraSize; + result = Packet_alloc(len); + result->header.len = hton16((uint16) len); + result->header.type = hton16((uint16) type); + return result; +} + +void +Packet_delete(Packet *packet) { + free(packet); +} + +Packet_Init * +Packet_Init_create(void) { + Packet_Init *packet = (Packet_Init *) Packet_create(PACKET_INIT, 0); + + packet->protoVersion.major = NETPLAY_PROTOCOL_VERSION_MAJOR; + packet->protoVersion.minor = NETPLAY_PROTOCOL_VERSION_MINOR; + packet->padding0 = 0; + packet->uqmVersion.major = UQM_MAJOR_VERSION; + packet->uqmVersion.minor = UQM_MINOR_VERSION; + packet->uqmVersion.patch = UQM_PATCH_VERSION; + packet->padding1 = 0; + return packet; +} + +Packet_Ping * +Packet_Ping_create(uint32 id) { + Packet_Ping *packet = (Packet_Ping *) Packet_create(PACKET_PING, 0); + + packet->id = hton32(id); + return packet; +} + +Packet_Ack * +Packet_Ack_create(uint32 id) { + Packet_Ack *packet = (Packet_Ack *) Packet_create(PACKET_ACK, 0); + + packet->id = hton32(id); + return packet; +} + +Packet_Ready * +Packet_Ready_create(void) { + Packet_Ready *packet = (Packet_Ready *) Packet_create(PACKET_READY, 0); + + return packet; +} + +// The fleet itself still needs to be filled by the caller. +// This function takes care of the necessary padding; it is allocated, +// and filled with zero chars. +Packet_Fleet * +Packet_Fleet_create(NetplaySide side, size_t numShips) { + Packet_Fleet *packet; + size_t fleetSize; + size_t extraSize; + + fleetSize = numShips * sizeof (FleetEntry); + extraSize = (fleetSize + 3) & ~0x03; + packet = (Packet_Fleet *) Packet_create(PACKET_FLEET, extraSize); + packet->side = (uint8) side; + packet->padding = 0; + packet->numShips = hton16((uint16) numShips); + memset((char *) packet + sizeof (Packet_Fleet) + fleetSize, + '\0', extraSize - fleetSize); + + return packet; +} + +// 'size' is the number of bytes (not characters) in 'name', excluding +// a possible terminating '\0'. A '\0' will be included in the packet though. +// This function takes care of the required padding. +Packet_TeamName * +Packet_TeamName_create(NetplaySide side, const char *name, size_t size) { + Packet_TeamName *packet; + size_t extraSize; + + extraSize = ((size + 1) + 3) & ~0x03; + // The +1 is for the '\0'. + packet = (Packet_TeamName *) Packet_create(PACKET_TEAMNAME, extraSize); + packet->side = (uint8) side; + packet->padding = 0; + memcpy(packet->name, name, size); + memset((char *) packet + sizeof (Packet_TeamName) + size, '\0', + extraSize - size); + // This takes care of the terminating '\0', as well as the + // padding. + + return packet; +} + +Packet_Handshake0 * +Packet_Handshake0_create(void) { + Packet_Handshake0 *packet = + (Packet_Handshake0 *) Packet_create(PACKET_HANDSHAKE0, 0); + return packet; +} + +Packet_Handshake1 * +Packet_Handshake1_create(void) { + Packet_Handshake1 *packet = + (Packet_Handshake1 *) Packet_create(PACKET_HANDSHAKE1, 0); + return packet; +} + +Packet_HandshakeCancel * +Packet_HandshakeCancel_create(void) { + Packet_HandshakeCancel *packet = + (Packet_HandshakeCancel *) Packet_create( + PACKET_HANDSHAKECANCEL, 0); + return packet; +} + +Packet_HandshakeCancelAck * +Packet_HandshakeCancelAck_create(void) { + Packet_HandshakeCancelAck *packet = + (Packet_HandshakeCancelAck *) Packet_create( + PACKET_HANDSHAKECANCELACK, 0); + return packet; +} + +Packet_SeedRandom * +Packet_SeedRandom_create(uint32 seed) { + Packet_SeedRandom *packet = + (Packet_SeedRandom *) Packet_create(PACKET_SEEDRANDOM, 0); + + packet->seed = hton32(seed); + return packet; +} + +Packet_InputDelay * +Packet_InputDelay_create(uint32 delay) { + Packet_InputDelay *packet = + (Packet_InputDelay *) Packet_create(PACKET_INPUTDELAY, 0); + + packet->delay = hton32(delay); + return packet; +} + +Packet_SelectShip * +Packet_SelectShip_create(uint16 ship) { + Packet_SelectShip *packet = + (Packet_SelectShip *) Packet_create(PACKET_SELECTSHIP, 0); + packet->ship = hton16(ship); + packet->padding = 0; + return packet; +} + +Packet_BattleInput * +Packet_BattleInput_create(uint8 state) { + Packet_BattleInput *packet = + (Packet_BattleInput *) Packet_create(PACKET_BATTLEINPUT, 0); + packet->state = (uint8) state; + packet->padding0 = 0; + packet->padding1 = 0; + return packet; +} + +Packet_FrameCount * +Packet_FrameCount_create(uint32 frameCount) { + Packet_FrameCount *packet = + (Packet_FrameCount *) Packet_create(PACKET_FRAMECOUNT, 0); + packet->frameCount = hton32(frameCount); + return packet; +} + +Packet_Checksum * +Packet_Checksum_create(uint32 frameNr, uint32 checksum) { + Packet_Checksum *packet = + (Packet_Checksum *) Packet_create(PACKET_CHECKSUM, 0); + packet->frameNr = hton32(frameNr); + packet->checksum = hton32(checksum); + return packet; +} + +Packet_Abort * +Packet_Abort_create(uint16 reason) { + Packet_Abort *packet = (Packet_Abort *) Packet_create(PACKET_ABORT, 0); + packet->reason = hton16(reason); + return packet; +} + +Packet_Reset * +Packet_Reset_create(uint16 reason) { + Packet_Reset *packet = (Packet_Reset *) Packet_create(PACKET_RESET, 0); + packet->reason = hton16(reason); + return packet; +} + + + diff --git a/src/uqm/supermelee/netplay/packet.h b/src/uqm/supermelee/netplay/packet.h new file mode 100644 index 0000000..f8c9682 --- /dev/null +++ b/src/uqm/supermelee/netplay/packet.h @@ -0,0 +1,299 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_PACKET_H_ +#define UQM_SUPERMELEE_NETPLAY_PACKET_H_ + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct Packet Packet; + +typedef enum PacketType { + PACKET_INIT, + PACKET_PING, + PACKET_ACK, + PACKET_READY, + PACKET_FLEET, + PACKET_TEAMNAME, + PACKET_HANDSHAKE0, + PACKET_HANDSHAKE1, + PACKET_HANDSHAKECANCEL, + PACKET_HANDSHAKECANCELACK, + PACKET_SEEDRANDOM, + PACKET_INPUTDELAY, + PACKET_SELECTSHIP, + PACKET_BATTLEINPUT, + PACKET_FRAMECOUNT, + PACKET_CHECKSUM, + PACKET_ABORT, + PACKET_RESET, + + PACKET_NUM, /* Number of packet types */ +} PacketType; + +// Sent before aborting the connection. +typedef enum NetplayAbortReason { + AbortReason_unspecified, + AbortReason_versionMismatch, + AbortReason_invalidHash, + AbortReason_protocolError, + // Network is in an inconsistent state. +} NetplayAbortReason; + +// Sent before resetting the connection. A game in progress is terminated. +typedef enum NetplayResetReason { + ResetReason_unspecified, + ResetReason_syncLoss, + ResetReason_manualReset, +} NetplayResetReason; + +#if defined(__cplusplus) +} +#endif + +#ifndef PACKET_H_STANDALONE +#include "netconnection.h" + +#include "types.h" +#include "libs/network/bytesex.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* NB: These handlers are expected not to modify the state if an + * error occurs. + * When a handler is called, it has already been validated that the + * a complete packet has arrived. + */ +typedef int (*PacketHandler)(NetConnection *conn, const void *packet); + +typedef struct { + size_t len; /* Minimal length of a packet of this type */ + PacketHandler handler; + const char *name; +} PacketTypeData; + +extern PacketTypeData packetTypeData[]; + +#if defined(__cplusplus) +} +#endif + +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +// When adding new packets, be sure to have all the fields properly aligned, +// and that the size of a packet is a multiple of 4 bytes in length. +// Fields should be no longer than 4 bytes in themselves, as larger +// fields may require a larger alignment. + +typedef struct { + uint16 len; + uint16 type; /* Actually of type PacketType */ +} PacketHeader; + +// "Base class" for all packets. +struct Packet { + PacketHeader header; +}; + +static inline size_t +packetLength(const Packet *packet) { + return (size_t) ntoh16(packet->header.len); +} + +static inline PacketType +packetType(const Packet *packet) { + return (PacketType) (int) ntoh16(packet->header.type); +} + +static inline bool +validPacketType(PacketType type) { + return type < PACKET_NUM; +} + +typedef struct { + PacketHeader header; + struct { + uint8 major; + uint8 minor; + } protoVersion; /* Protocol version */ + uint16 padding0; /* Set to 0 */ + struct { + uint8 major; + uint8 minor; + uint8 patch; + } uqmVersion; /* Protocol version */ + uint8 padding1; /* Set to 0 */ +} Packet_Init; + +typedef struct { + PacketHeader header; + uint32 id; +} Packet_Ping; + +// Acknowledgement of a Ping. +typedef struct { + PacketHeader header; + uint32 id; +} Packet_Ack; + +// Used to signal that a party is ready to continue. +typedef struct { + PacketHeader header; + // No contents. +} Packet_Ready; + +typedef struct { + PacketHeader header; + uint32 seed; +} Packet_SeedRandom; + +typedef struct { + PacketHeader header; + uint32 delay; +} Packet_InputDelay; + +// This enum is used to indicate that a packet containing it relates to +// either the local or the remote player, from the perspective of the +// sender of the message; +typedef enum { + NetplaySide_local, + NetplaySide_remote +} NetplaySide; + +typedef struct { + uint8 index; /* Position in the fleet */ + uint8 ship; /* Ship type index; actually MeleeShip */ +} FleetEntry; +// Structure describing an update to a player's fleet. +// TODO: use strings as ship identifiers, instead of numbers, +// so that adding of new ships doesn't break this. +typedef struct { + PacketHeader header; + uint8 side; + uint8 padding; + uint16 numShips; + FleetEntry ships[]; + // Be sure to add padding to this structure to make it a multiple of + // 4 bytes in length. +} Packet_Fleet; + +typedef struct { + PacketHeader header; + uint8 side; + uint8 padding; + uint8 name[]; + // '\0' terminated. + // Be sure to add padding to this structure to make it a multiple of + // 4 bytes in length. +} Packet_TeamName; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_Handshake0; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_Handshake1; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_HandshakeCancel; + +typedef struct { + PacketHeader header; + // No contents. +} Packet_HandshakeCancelAck; + +typedef struct { + PacketHeader header; + uint16 ship; + // The value '(uint16) ~0' indicates random selection. + uint16 padding; +} Packet_SelectShip; + +typedef struct { + PacketHeader header; + uint8 state; /* Actually BATTLE_INPUT_STATE */ + uint8 padding0; + uint16 padding1; +} Packet_BattleInput; + +typedef struct { + PacketHeader header; + uint32 frameCount; /* Actually BattleFrameCounter */ +} Packet_FrameCount; + +typedef struct { + PacketHeader header; + uint32 frameNr; /* Actually BattleFrameCounter */ + uint32 checksum; /* Actually Checksum */ +} Packet_Checksum; + +typedef struct { + PacketHeader header; + uint16 reason; /* Actually NetplayAbortReason */ + uint16 padding0; +} Packet_Abort; + +typedef struct { + PacketHeader header; + uint16 reason; /* Actually NetplayResetReason */ + uint16 padding0; +} Packet_Reset; + + +#ifndef PACKET_H_STANDALONE +void Packet_delete(Packet *packet); +Packet_Init *Packet_Init_create(void); +Packet_Ping *Packet_Ping_create(uint32 id); +Packet_Ack *Packet_Ack_create(uint32 id); +Packet_Ready *Packet_Ready_create(void); +Packet_Handshake0 *Packet_Handshake0_create(void); +Packet_Handshake1 *Packet_Handshake1_create(void); +Packet_HandshakeCancel *Packet_HandshakeCancel_create(void); +Packet_HandshakeCancelAck *Packet_HandshakeCancelAck_create(void); +Packet_SeedRandom *Packet_SeedRandom_create(uint32 seed); +Packet_InputDelay *Packet_InputDelay_create(uint32 delay); +Packet_Fleet *Packet_Fleet_create(NetplaySide side, size_t numShips); +Packet_TeamName *Packet_TeamName_create(NetplaySide side, const char *name, + size_t size); +Packet_SelectShip *Packet_SelectShip_create(uint16 ship); +Packet_BattleInput *Packet_BattleInput_create(uint8 state); +Packet_FrameCount *Packet_FrameCount_create(uint32 frameCount); +Packet_Checksum *Packet_Checksum_create(uint32 frameNr, uint32 checksum); +Packet_Abort *Packet_Abort_create(uint16 reason); +Packet_Reset *Packet_Reset_create(uint16 reason); +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_PACKET_H_ */ + diff --git a/src/uqm/supermelee/netplay/packethandlers.c b/src/uqm/supermelee/netplay/packethandlers.c new file mode 100644 index 0000000..5d2d8f4 --- /dev/null +++ b/src/uqm/supermelee/netplay/packethandlers.c @@ -0,0 +1,649 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "packethandlers.h" + +#include "netinput.h" +#include "netmisc.h" +#include "packetsenders.h" +#include "proto/npconfirm.h" +#include "proto/ready.h" +#include "proto/reset.h" +#include "libs/log.h" + +#include "../../controls.h" + // for BATTLE_INPUT_STATE +#include "../../init.h" + // for NUM_PLAYERS +#include "../../globdata.h" + // for GLOBAL +#include "../melee.h" + // for various update functions. +#include "../meleeship.h" + // for MeleeShip +#include "../pickmele.h" + // for various update functions. +#include "libs/mathlib.h" + // for TFB_SeedRandom + +#include <errno.h> + + +static bool +testNetState(bool condition, PacketType type) { + if (!condition) { + log_add(log_Error, "Packet of type '%s' received from wrong " + "state.", packetTypeData[type].name); + errno = EBADMSG; + } + return condition; +} + +static int +versionCompare(int major1, int minor1, int patch1, + int major2, int minor2, int patch2) { + if (major1 < major2) + return -1; + if (major1 > major2) + return 1; + + if (minor1 < minor2) + return -1; + if (minor1 > minor2) + return 1; + + if (patch1 < patch2) + return -1; + if (patch1 > patch2) + return 1; + + return 0; +} + +int +PacketHandler_Init(NetConnection *conn, const Packet_Init *packet) { + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_init && + !conn->stateFlags.ready.remoteReady, PACKET_INIT)) + return -1; // errno is set + + if (packet->protoVersion.major != NETPLAY_PROTOCOL_VERSION_MAJOR || + packet->protoVersion.minor != NETPLAY_PROTOCOL_VERSION_MINOR) { + sendAbort (conn, AbortReason_versionMismatch); + abortFeedback(conn, AbortReason_versionMismatch); + log_add(log_Error, "Protocol version %d.%d not supported.", + packet->protoVersion.major, packet->protoVersion.minor); + errno = ENOSYS; + return -1; + } + + if (versionCompare(packet->uqmVersion.major, packet->uqmVersion.minor, + packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR, + NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH) + < 0) { + sendAbort (conn, AbortReason_versionMismatch); + abortFeedback(conn, AbortReason_versionMismatch); + log_add(log_Error, "Remote side is running a version of UQM that " + "is too old (%d.%d.%d; %d.%d.%d is required).", + packet->uqmVersion.major, packet->uqmVersion.minor, + packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR, + NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH); + errno = ENOSYS; + return -1; + } + + Netplay_remoteReady(conn); + + return 0; +} + +int +PacketHandler_Ping(NetConnection *conn, const Packet_Ping *packet) { + if (!testNetState(conn->state > NetState_init, PACKET_PING)) + return -1; // errno is set + + sendAck(conn, packet->id); + return 0; +} + +int +PacketHandler_Ack(NetConnection *conn, const Packet_Ack *packet) { + if (!testNetState(conn->state > NetState_init, PACKET_ACK)) + return -1; // errno is set + + (void) conn; + (void) packet; + return 0; +} + +// Convert the side indication relative to a remote party to +// a local player number. +static inline int +localSide(NetConnection *conn, NetplaySide side) { + if (side == NetplaySide_local) { + // "local" relative to the remote party. + return conn->player; + } + + return 1 - conn->player; +} + +int +PacketHandler_Ready(NetConnection *conn, const Packet_Ready *packet) { + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(readyFlagsMeaningful(conn->state) && + !conn->stateFlags.ready.remoteReady, PACKET_READY)) + return -1; // errno is set + + Netplay_remoteReady(conn); + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_Fleet(NetConnection *conn, const Packet_Fleet *packet) { + uint16 numShips = ntoh16(packet->numShips); + size_t i; + size_t len; + int player; + BattleStateData *battleStateData; + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET)) + return -1; // errno is set + + player = localSide(conn, (NetplaySide) packet->side); + + len = packetLength((const Packet *) packet); + if (sizeof packet + numShips * sizeof(packet->ships[0]) > len) { + // There is not enough room in the packet to contain all + // the ships it says it contains. + log_add(log_Warning, "Invalid fleet size. Specified size is %d, " + "actual size = %d", + numShips, (int) ((len - sizeof packet) / sizeof(packet->ships[0]))); + errno = EBADMSG; + return -1; + } + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + + if (conn->stateFlags.handshake.localOk) { + Netplay_cancelConfirmation(conn); + confirmationCancelled(battleStateData->meleeState, conn->player); + } + + for (i = 0; i < numShips; i++) { + MeleeShip ship = (MeleeShip) packet->ships[i].ship; + FleetShipIndex index = (FleetShipIndex) packet->ships[i].index; + + if (!MeleeShip_valid(ship)) { + log_add (log_Warning, "Invalid ship type number %d (max = %d).\n", + ship, NUM_MELEE_SHIPS - 1); + errno = EBADMSG; + return -1; + } + + if (index >= MELEE_FLEET_SIZE) + { + log_add (log_Warning, "Invalid ship position number %d " + "(max = %d).\n", index, MELEE_FLEET_SIZE - 1); + errno = EBADMSG; + return -1; + } + + Melee_RemoteChange_ship (battleStateData->meleeState, conn, + player, index, ship); + } + + // Padding data may follow; it is ignored. + return 0; +} + +int +PacketHandler_TeamName(NetConnection *conn, const Packet_TeamName *packet) { + size_t nameLen; + int side; + BattleStateData *battleStateData; + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET)) + return -1; // errno is set + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + + if (conn->stateFlags.handshake.localOk) { + Netplay_cancelConfirmation(conn); + confirmationCancelled(battleStateData->meleeState, conn->player); + } + + side = localSide(conn, (NetplaySide) packet->side); + nameLen = packetLength((const Packet *) packet) + - sizeof (Packet_TeamName) - 1; + // The -1 is for not counting the terminating '\0'. + + { + char buf[MAX_TEAM_CHARS + 1]; + + if (nameLen > MAX_TEAM_CHARS) + nameLen = MAX_TEAM_CHARS; + memcpy (buf, (const char *) packet->name, nameLen); + buf[nameLen] = '\0'; + + Melee_RemoteChange_teamName(battleStateData->meleeState, conn, + side, buf); + } + + // Padding data may follow; it is ignored. + return 0; +} + +static void +handshakeComplete(NetConnection *conn) { + assert(!conn->stateFlags.handshake.localOk); + assert(!conn->stateFlags.handshake.remoteOk); + assert(!conn->stateFlags.handshake.canceling); + + assert(conn->state == NetState_inSetup); + NetConnection_setState(conn, NetState_preBattle); +} + +int +PacketHandler_Handshake0(NetConnection *conn, + const Packet_Handshake0 *packet) { + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(handshakeMeaningful(conn->state) + && !conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKE0)) + return -1; // errno is set + + conn->stateFlags.handshake.remoteOk = true; + if (conn->stateFlags.handshake.localOk && + !conn->stateFlags.handshake.canceling) + sendHandshake1(conn); + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_Handshake1(NetConnection *conn, + const Packet_Handshake1 *packet) { + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(handshakeMeaningful(conn->state) && + (conn->stateFlags.handshake.localOk || + conn->stateFlags.handshake.canceling), PACKET_HANDSHAKE1)) + return -1; // errno is set + + if (conn->stateFlags.handshake.canceling) { + conn->stateFlags.handshake.remoteOk = true; + } else { + bool remoteWasOk = conn->stateFlags.handshake.remoteOk; + + conn->stateFlags.handshake.localOk = false; + conn->stateFlags.handshake.remoteOk = false; + + if (!remoteWasOk) { + // Received Handshake1 without prior Handshake0. + // A Handshake0 is implied, but we still need to confirm + // it with a Handshake1. + sendHandshake1(conn); + } + + handshakeComplete(conn); + } + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_HandshakeCancel(NetConnection *conn, + const Packet_HandshakeCancel *packet) { + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(handshakeMeaningful(conn->state) + && conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKECANCEL)) + return -1; // errno is set + + conn->stateFlags.handshake.remoteOk = false; + sendHandshakeCancelAck(conn); + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_HandshakeCancelAck(NetConnection *conn, + const Packet_HandshakeCancelAck *packet) { + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(handshakeMeaningful(conn->state) + && conn->stateFlags.handshake.canceling, + PACKET_HANDSHAKECANCELACK)) + return -1; // errno is set + + conn->stateFlags.handshake.canceling = false; + if (conn->stateFlags.handshake.localOk) { + if (conn->stateFlags.handshake.remoteOk) { + sendHandshake1(conn); + } else + sendHandshake0(conn); + } + + (void) packet; + // Its contents is not interesting. + + return 0; +} + +int +PacketHandler_SeedRandom(NetConnection *conn, + const Packet_SeedRandom *packet) { + BattleStateData *battleStateData; + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_preBattle && + !conn->stateFlags.discriminant, PACKET_SEEDRANDOM)) + return -1; // errno is set + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + updateRandomSeed (battleStateData->meleeState, conn->player, + ntoh32(packet->seed)); + + conn->stateFlags.agreement.randomSeed = true; + return 0; +} + +int +PacketHandler_InputDelay(NetConnection *conn, + const Packet_InputDelay *packet) { + BattleStateData *battleStateData; + uint32 delay; + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_preBattle, PACKET_INPUTDELAY)) + return -1; // errno is set + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + delay = ntoh32(packet->delay); + if (delay > BATTLE_FRAME_RATE) { + log_add(log_Error, "NETPLAY: [%d] Received absurdly large " + "input delay value (%d).", conn->player, delay); + return -1; + } + conn->stateFlags.inputDelay = delay; + + return 0; +} + +int +PacketHandler_SelectShip(NetConnection *conn, + const Packet_SelectShip *packet) { + bool updateResult; + BattleStateData *battleStateData; + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_selectShip, PACKET_SELECTSHIP)) + return -1; // errno is set + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + updateResult = updateMeleeSelection(battleStateData->getMeleeState, + conn->player, ntoh16(packet->ship)); + if (!updateResult) + { + errno = EBADMSG; + return -1; + } + + return 0; +} + +int +PacketHandler_BattleInput(NetConnection *conn, + const Packet_BattleInput *packet) { + BATTLE_INPUT_STATE input; + BattleInputBuffer *bib; + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_inBattle || + conn->state == NetState_endingBattle || + conn->state == NetState_endingBattle2, PACKET_BATTLEINPUT)) + return -1; // errno is set + + input = (BATTLE_INPUT_STATE) packet->state; + bib = getBattleInputBuffer(conn->player); + if (!BattleInputBuffer_push(bib, input)) { + // errno is set + return -1; + } + + return 0; +} + +int +PacketHandler_FrameCount(NetConnection *conn, + const Packet_FrameCount *packet) { + BattleStateData *battleStateData; + BattleFrameCounter frameCount; + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(conn->state == NetState_endingBattle, + PACKET_FRAMECOUNT)) + return -1; // errno is set + + frameCount = (BattleFrameCounter) ntoh32(packet->frameCount); +#ifdef NETPLAY_DEBUG + log_add(log_Debug, "NETPLAY: [%d] <== Received battleFrameCount %u.", + conn->player, (unsigned int) frameCount); +#endif + + battleStateData = (BattleStateData *) NetConnection_getStateData(conn); + if (frameCount > battleStateData->endFrameCount) + battleStateData->endFrameCount = frameCount; + Netplay_remoteReady(conn); + + return 0; +} + +int +PacketHandler_Checksum(NetConnection *conn, const Packet_Checksum *packet) { +#ifdef NETPLAY_CHECKSUM + uint32 frameNr; + uint32 checksum; + size_t delay; + size_t interval; +#endif + + if (conn->stateFlags.reset.localReset) + return 0; + if (conn->stateFlags.reset.remoteReset) { + errno = EBADMSG; + return -1; + } + + if (!testNetState(NetState_battleActive(conn->state), PACKET_CHECKSUM)) + return -1; // errno is set + +#ifdef NETPLAY_CHECKSUM + frameNr = ntoh32(packet->frameNr); + checksum = ntoh32(packet->checksum); + interval = NetConnection_getChecksumInterval(conn); + delay = getBattleInputDelay(); + + if (frameNr % interval != 0) { + log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " + "for frame %u, while we only expect checksums on frames " + "divisable by %u -- discarding.", conn->player, + (unsigned int) frameNr, (unsigned int) interval); + return 0; + // No need to close the connection; checksums are not + // essential. + } + + // The checksum is sent at the beginning of a frame. + // If we're in frame n and have sent our input already, + // the remote side has got enough input to progress delay + 1 frames from + // frame n. The next frame is then n + delay + 1, for which we can + // receive a checksum. + if (frameNr > battleFrameCount + delay + 1) { + log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " + "for a frame too far in the future (frame %u, current " + "is %u, input delay is %u) -- discarding.", conn->player, + (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay); + return 0; + // No need to close the connection; checksums are not + // essential. + } + + // We can progress delay more frames after the last frame for which we + // received input. If we call that frame n, we can complete frames + // n through n + delay - 1. While we are waiting for the next input, + // in frame n + delay, we will first receive the checksum that the + // remote side sent at the start of frame n + 1. + // In this situation frameNr is n + 1, and battleFrameCount is + // n + delay. + if (frameNr + delay < battleFrameCount) { + log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " + "for a frame too far in the past (frame %u, current " + "is %u, input delay is %u) -- discarding.", conn->player, + (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay); + return 0; + // No need to close the connection; checksums are not + // essential. + } + + addRemoteChecksum(conn, frameNr, checksum); +#endif + +#ifndef NETPLAY_CHECKSUM + (void) packet; +#endif + return 0; +} + +int +PacketHandler_Abort(NetConnection *conn, const Packet_Abort *packet) { + abortFeedback(conn, packet->reason); + + return -1; + // Close connection. +} + +int +PacketHandler_Reset(NetConnection *conn, const Packet_Reset *packet) { + NetplayResetReason reason; + + if (!testNetState(!conn->stateFlags.reset.remoteReset, PACKET_RESET)) + return -1; // errno is set + + reason = ntoh16(packet->reason); + + Netplay_remoteReset(conn, reason); + return 0; +} + + diff --git a/src/uqm/supermelee/netplay/packethandlers.h b/src/uqm/supermelee/netplay/packethandlers.h new file mode 100644 index 0000000..7bd686e --- /dev/null +++ b/src/uqm/supermelee/netplay/packethandlers.h @@ -0,0 +1,56 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_ +#define UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_ + +#include "packet.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#define DECLARE_PACKETHANDLER(type) \ + int PacketHandler_##type(NetConnection *conn, \ + const Packet_##type *packet) + +DECLARE_PACKETHANDLER(Init); +DECLARE_PACKETHANDLER(Ping); +DECLARE_PACKETHANDLER(Ack); +DECLARE_PACKETHANDLER(Ready); +DECLARE_PACKETHANDLER(Fleet); +DECLARE_PACKETHANDLER(TeamName); +DECLARE_PACKETHANDLER(Handshake0); +DECLARE_PACKETHANDLER(Handshake1); +DECLARE_PACKETHANDLER(HandshakeCancel); +DECLARE_PACKETHANDLER(HandshakeCancelAck); +DECLARE_PACKETHANDLER(SeedRandom); +DECLARE_PACKETHANDLER(InputDelay); +DECLARE_PACKETHANDLER(SelectShip); +DECLARE_PACKETHANDLER(BattleInput); +DECLARE_PACKETHANDLER(FrameCount); +DECLARE_PACKETHANDLER(Checksum); +DECLARE_PACKETHANDLER(Abort); +DECLARE_PACKETHANDLER(Reset); + + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_ */ diff --git a/src/uqm/supermelee/netplay/packetq.c b/src/uqm/supermelee/netplay/packetq.c new file mode 100644 index 0000000..ee8ec01 --- /dev/null +++ b/src/uqm/supermelee/netplay/packetq.c @@ -0,0 +1,149 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "netplay.h" +#include "netconnection.h" +#include "packetq.h" +#include "netsend.h" +#include "packetsenders.h" +#ifdef NETPLAY_DEBUG +# include "libs/log.h" +#endif + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +static inline PacketQueueLink * +PacketQueueLink_alloc(void) { + // XXX: perhaps keep a pool of links? + return malloc(sizeof (PacketQueueLink)); +} + +static inline void +PacketQueueLink_delete(PacketQueueLink *link) { + free(link); +} + +// 'maxSize' should at least be 1 +void +PacketQueue_init(PacketQueue *queue) { + queue->size = 0; + queue->first = NULL; + queue->end = &queue->first; +} + +static void +PacketQueue_deleteLinks(PacketQueueLink *link) { + while (link != NULL) { + PacketQueueLink *next = link->next; + Packet_delete(link->packet); + PacketQueueLink_delete(link); + link = next; + } +} + +void +PacketQueue_uninit(PacketQueue *queue) { + PacketQueue_deleteLinks(queue->first); +} + +void +queuePacket(NetConnection *conn, Packet *packet) { + PacketQueue *queue; + PacketQueueLink *link; + + assert(NetConnection_isConnected(conn)); + + queue = &conn->queue; + + link = PacketQueueLink_alloc(); + link->packet = packet; + link->next = NULL; + *queue->end = link; + queue->end = &link->next; + + queue->size++; + // XXX: perhaps check that this queue isn't getting too large? + +#ifdef NETPLAY_DEBUG + if (packetType(packet) != PACKET_BATTLEINPUT && + packetType(packet) != PACKET_CHECKSUM) { + // Reporting BattleInput or Checksum would get so spammy that it + // would slow down the battle. + log_add(log_Debug, "NETPLAY: [%d] ==> Queueing packet of type %s.\n", + NetConnection_getPlayerNr(conn), + packetTypeData[packetType(packet)].name); + } +#ifdef NETPLAY_DEBUG_FILE + if (conn->debugFile != NULL) { + uio_fprintf(conn->debugFile, + "NETPLAY: [%d] ==> Queueing packet of type %s.\n", + NetConnection_getPlayerNr(conn), + packetTypeData[packetType(packet)].name); + } +#endif /* NETPLAY_DEBUG_FILE */ +#endif /* NETPLAY_DEBUG */ +} + +// If an error occurs during sending, we leave the unsent packets in +// the queue, and let the caller decide what to do with them. +// This function may return -1 with errno EAGAIN or EWOULDBLOCK +// if we're waiting for the other party to act first. +static int +flushPacketQueueLinks(NetConnection *conn, PacketQueueLink **first) { + PacketQueueLink *link; + PacketQueueLink *next; + PacketQueue *queue = &conn->queue; + + for (link = *first; link != NULL; link = next) { + if (sendPacket(conn, link->packet) == -1) { + // Errno is set. + *first = link; + return -1; + } + + next = link->next; + Packet_delete(link->packet); + PacketQueueLink_delete(link); + queue->size--; + } + + *first = link; + return 0; +} + +int +flushPacketQueue(NetConnection *conn) { + int flushResult; + PacketQueue *queue = &conn->queue; + + assert(NetConnection_isConnected(conn)); + + flushResult = flushPacketQueueLinks(conn, &queue->first); + if (queue->first == NULL) + queue->end = &queue->first; + if (flushResult == -1) { + // errno is set + return -1; + } + + return 0; +} + diff --git a/src/uqm/supermelee/netplay/packetq.h b/src/uqm/supermelee/netplay/packetq.h new file mode 100644 index 0000000..71f2347 --- /dev/null +++ b/src/uqm/supermelee/netplay/packetq.h @@ -0,0 +1,59 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_PACKETQ_H_ +#define UQM_SUPERMELEE_NETPLAY_PACKETQ_H_ + +typedef struct PacketQueue PacketQueue; + +#include "packet.h" +#include "types.h" + +#include <sys/types.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct PacketQueueLink PacketQueueLink; +struct PacketQueueLink { + PacketQueueLink *next; + Packet *packet; +}; + +struct PacketQueue { + size_t size; + PacketQueueLink *first; + PacketQueueLink **end; + + // first points to the first entry in the queue + // end points to the location where the next message should be inserted. +}; + +void PacketQueue_init(PacketQueue *queue); +void PacketQueue_uninit(PacketQueue *queue); +void queuePacket(NetConnection *conn, Packet *packet); +int flushPacketQueue(NetConnection *conn); + + +#if defined(__cplusplus) +} +#endif + +#endif + diff --git a/src/uqm/supermelee/netplay/packetsenders.c b/src/uqm/supermelee/netplay/packetsenders.c new file mode 100644 index 0000000..fb9f232 --- /dev/null +++ b/src/uqm/supermelee/netplay/packetsenders.c @@ -0,0 +1,197 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "netplay.h" +#include "packetsenders.h" + +#include "packet.h" +#include "packetq.h" +#include "netsend.h" + + +void +sendInit(NetConnection *conn) { + Packet_Init *packet; + + packet = Packet_Init_create(); + queuePacket(conn, (Packet *) packet); +} + +void +sendPing(NetConnection *conn, uint32 id) { + Packet_Ping *packet; + + packet = Packet_Ping_create(id); + queuePacket(conn, (Packet *) packet); +} + +void +sendAck(NetConnection *conn, uint32 id) { + Packet_Ack *packet; + + packet = Packet_Ack_create(id); + queuePacket(conn, (Packet *) packet); +} + +void +sendReady(NetConnection *conn) { + Packet_Ready *packet; + + packet = Packet_Ready_create(); + queuePacket(conn, (Packet *) packet); +} + +void +sendHandshake0(NetConnection *conn) { + Packet_Handshake0 *packet; + + packet = Packet_Handshake0_create(); + queuePacket(conn, (Packet *) packet); +} + +void +sendHandshake1(NetConnection *conn) { + Packet_Handshake1 *packet; + + packet = Packet_Handshake1_create(); + queuePacket(conn, (Packet *) packet); +} + +void +sendHandshakeCancel(NetConnection *conn) { + Packet_HandshakeCancel *packet; + + packet = Packet_HandshakeCancel_create(); + queuePacket(conn, (Packet *) packet); +} + +void +sendHandshakeCancelAck(NetConnection *conn) { + Packet_HandshakeCancelAck *packet; + + packet = Packet_HandshakeCancelAck_create(); + queuePacket(conn, (Packet *) packet); +} + +void +sendTeamName(NetConnection *conn, NetplaySide side, const char *name, + size_t len) { + Packet_TeamName *packet; + + packet = Packet_TeamName_create(side, name, len); + queuePacket(conn, (Packet *) packet); +} + +void +sendFleet(NetConnection *conn, NetplaySide side, const MeleeShip *ships, + size_t shipCount) { + size_t i; + Packet_Fleet *packet; + + packet = Packet_Fleet_create(side, shipCount); + + for (i = 0; i < shipCount; i++) { + packet->ships[i].index = (uint8) i; + packet->ships[i].ship = (uint8) ships[i]; + } + + queuePacket(conn, (Packet *) packet); +} + +void +sendFleetShip(NetConnection *conn, NetplaySide side, + FleetShipIndex shipIndex, MeleeShip ship) { + Packet_Fleet *packet; + + packet = Packet_Fleet_create(side, 1); + + packet->ships[0].index = (uint8) shipIndex; + packet->ships[0].ship = (uint8) ship; + + queuePacket(conn, (Packet *) packet); +} + +void +sendSeedRandom(NetConnection *conn, uint32 seed) { + Packet_SeedRandom *packet; + + packet = Packet_SeedRandom_create(seed); + queuePacket(conn, (Packet *) packet); +} + +void +sendInputDelay(NetConnection *conn, uint32 delay) { + Packet_InputDelay *packet; + + packet = Packet_InputDelay_create(delay); + queuePacket(conn, (Packet *) packet); +} + +void +sendSelectShip(NetConnection *conn, FleetShipIndex index) { + Packet_SelectShip *packet; + + packet = Packet_SelectShip_create((uint16) index); + queuePacket(conn, (Packet *) packet); +} + +void +sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input) { + Packet_BattleInput *packet; + + packet = Packet_BattleInput_create((uint8) input); + queuePacket(conn, (Packet *) packet); +} + +void +sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount) { + Packet_FrameCount *packet; + + packet = Packet_FrameCount_create((uint32) frameCount); + queuePacket(conn, (Packet *) packet); +} + +#ifdef NETPLAY_CHECKSUM +void +sendChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum) { + Packet_Checksum *packet; + + packet = Packet_Checksum_create((uint32) frameNr, (uint32) checksum); + queuePacket(conn, (Packet *) packet); +} +#endif + +void +sendAbort(NetConnection *conn, NetplayAbortReason reason) { + Packet_Abort *packet; + + packet = Packet_Abort_create((uint16) reason); + queuePacket(conn, (Packet *) packet); +} + +void +sendReset(NetConnection *conn, NetplayResetReason reason) { + Packet_Reset *packet; + + packet = Packet_Reset_create((uint16) reason); + queuePacket(conn, (Packet *) packet); +} + + + diff --git a/src/uqm/supermelee/netplay/packetsenders.h b/src/uqm/supermelee/netplay/packetsenders.h new file mode 100644 index 0000000..de0bc6d --- /dev/null +++ b/src/uqm/supermelee/netplay/packetsenders.h @@ -0,0 +1,67 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_ +#define UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_ + +#include "types.h" + +#include "netconnection.h" +#include "packet.h" + +#include "../../controls.h" + // for BATTLE_INPUT_STATE +#include "../meleeship.h" + // for MeleeShip +#include "../meleesetup.h" + // for FleetShipIndex + +#if defined(__cplusplus) +extern "C" { +#endif + +void sendInit(NetConnection *conn); +void sendPing(NetConnection *conn, uint32 id); +void sendAck(NetConnection *conn, uint32 id); +void sendReady(NetConnection *conn); +void sendHandshake0(NetConnection *conn); +void sendHandshake1(NetConnection *conn); +void sendHandshakeCancel(NetConnection *conn); +void sendHandshakeCancelAck(NetConnection *conn); +void sendTeamName(NetConnection *conn, NetplaySide side, + const char *name, size_t len); +void sendFleet(NetConnection *conn, NetplaySide side, + const MeleeShip *ships, size_t numShips); +void sendFleetShip(NetConnection *conn, NetplaySide player, + FleetShipIndex shipIndex, MeleeShip ship); +void sendSeedRandom(NetConnection *conn, uint32 seed); +void sendInputDelay(NetConnection *conn, uint32 delay); +void sendSelectShip(NetConnection *conn, FleetShipIndex index); +void sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input); +void sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount); +void sendChecksum(NetConnection *conn, BattleFrameCounter frameNr, + Checksum checksum); +void sendAbort(NetConnection *conn, NetplayAbortReason reason); +void sendReset(NetConnection *conn, NetplayResetReason reason); + + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_ */ diff --git a/src/uqm/supermelee/netplay/proto/Makeinfo b/src/uqm/supermelee/netplay/proto/Makeinfo new file mode 100644 index 0000000..1d9739c --- /dev/null +++ b/src/uqm/supermelee/netplay/proto/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="npconfirm.c ready.c reset.c" +uqm_HFILES="npconfirm.h ready.h reset.h" diff --git a/src/uqm/supermelee/netplay/proto/npconfirm.c b/src/uqm/supermelee/netplay/proto/npconfirm.c new file mode 100644 index 0000000..6929219 --- /dev/null +++ b/src/uqm/supermelee/netplay/proto/npconfirm.c @@ -0,0 +1,81 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "../netplay.h" +#include "npconfirm.h" + +#include "types.h" +#include "../netmisc.h" +#include "../packetsenders.h" + +#include <assert.h> +#include <errno.h> + +int +Netplay_confirm(NetConnection *conn) { + assert(handshakeMeaningful(NetConnection_getState(conn))); + + if (conn->stateFlags.handshake.localOk) { + // Already confirmed + errno = EINVAL; + return -1; + } + + conn->stateFlags.handshake.localOk = true; + + if (conn->stateFlags.handshake.canceling) { + // If a previous confirmation was cancelled, but the cancel + // is not acknowledged yet, we don't have to send anything yet. + // The handshake0 packet will be sent when the acknowledgement + // arrives. + } else if (conn->stateFlags.handshake.remoteOk) { + // A Handshake0 is implied by the following Handshake1. + sendHandshake1(conn); + } else { + sendHandshake0(conn); + } + + return 0; +} + +int +Netplay_cancelConfirmation(NetConnection *conn) { + assert(handshakeMeaningful(NetConnection_getState(conn))); + + if (!conn->stateFlags.handshake.localOk) { + // Not confirmed, or already canceling. + errno = EINVAL; + return -1; + } + + conn->stateFlags.handshake.localOk = false; + if (conn->stateFlags.handshake.canceling) { + // If previous cancellation is still waiting to be acknowledged, + // the confirmation we are cancelling here, has not actually been + // sent yet. By setting the localOk flag to false, it is + // cancelled, without the need for any packets to be sent. + } else { + conn->stateFlags.handshake.canceling = true; + sendHandshakeCancel(conn); + } + + return 0; +} + + diff --git a/src/uqm/supermelee/netplay/proto/npconfirm.h b/src/uqm/supermelee/netplay/proto/npconfirm.h new file mode 100644 index 0000000..1ae58f5 --- /dev/null +++ b/src/uqm/supermelee/netplay/proto/npconfirm.h @@ -0,0 +1,36 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_ +#define UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_ + +#include "../netplay.h" +#include "../netconnection.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +int Netplay_confirm(NetConnection *conn); +int Netplay_cancelConfirmation(NetConnection *conn); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_ */ diff --git a/src/uqm/supermelee/netplay/proto/ready.c b/src/uqm/supermelee/netplay/proto/ready.c new file mode 100644 index 0000000..e9f8c58 --- /dev/null +++ b/src/uqm/supermelee/netplay/proto/ready.c @@ -0,0 +1,106 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NETCONNECTION_INTERNAL +#include "../netplay.h" +#include "ready.h" + +#include "types.h" +#include "../netmisc.h" +#include "../packetsenders.h" + +#include <assert.h> + +static void +Netplay_bothReady(NetConnection *conn) { + NetConnection_ReadyCallback callback; + void *readyArg; + + assert(conn->readyCallback != NULL); + + callback = conn->readyCallback; + readyArg = conn->readyCallbackArg; + + NetConnection_setReadyCallback(conn, NULL, NULL); + // Clear the readyCallback field before performing the callback, + // so that it can be set again from inside the callback + // function. + + callback(conn, readyArg); +} + +// If notifyRemote is set, a 'Ready' message will be sent to the other side. +// returns true iff both sides are ready. +// Inside the callback function, ready flags may be set for a possible +// next Ready communication. +bool +Netplay_localReady(NetConnection *conn, NetConnection_ReadyCallback callback, + void *readyArg, bool notifyRemote) { + assert(readyFlagsMeaningful(NetConnection_getState(conn))); + assert(!conn->stateFlags.ready.localReady); + assert(callback != NULL); + + NetConnection_setReadyCallback(conn, callback, readyArg); + + if (notifyRemote) + sendReady(conn); + if (!conn->stateFlags.ready.remoteReady) { + conn->stateFlags.ready.localReady = true; + return false; + } + + // Reset ready flags: + conn->stateFlags.ready.remoteReady = false; + + // Trigger the callback. + Netplay_bothReady(conn); + return true; +} + +// returns true iff both sides are ready. +bool +Netplay_remoteReady(NetConnection *conn) { + assert(readyFlagsMeaningful(NetConnection_getState(conn))); + // This is supposed to be already verified by the calling + // function. + assert(!conn->stateFlags.ready.remoteReady); + + if (!conn->stateFlags.ready.localReady) { + conn->stateFlags.ready.remoteReady = true; + return false; + } + + // Reset ready flags: + conn->stateFlags.ready.localReady = false; + + // Trigger the callback. + Netplay_bothReady(conn); + return true; +} + +bool +Netplay_isLocalReady(const NetConnection *conn) { + return conn->stateFlags.ready.localReady; +} + +bool +Netplay_isRemoteReady(const NetConnection *conn) { + return conn->stateFlags.ready.remoteReady; +} + + diff --git a/src/uqm/supermelee/netplay/proto/ready.h b/src/uqm/supermelee/netplay/proto/ready.h new file mode 100644 index 0000000..3521557 --- /dev/null +++ b/src/uqm/supermelee/netplay/proto/ready.h @@ -0,0 +1,38 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_ +#define UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_ + +#include "../netconnection.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +bool Netplay_localReady(NetConnection *conn, + NetConnection_ReadyCallback callback, void *arg, bool notifyRemote); +bool Netplay_remoteReady(NetConnection *conn); +bool Netplay_isLocalReady(const NetConnection *conn); +bool Netplay_isRemoteReady(const NetConnection *conn); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_ */ diff --git a/src/uqm/supermelee/netplay/proto/reset.c b/src/uqm/supermelee/netplay/proto/reset.c new file mode 100644 index 0000000..82483b1 --- /dev/null +++ b/src/uqm/supermelee/netplay/proto/reset.c @@ -0,0 +1,166 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// See doc/devel/netplay/protocol + +#define NETCONNECTION_INTERNAL +#include "../netplay.h" +#include "reset.h" + +#include "types.h" +#include "../packetsenders.h" +#include "../../melee.h" + // For resetFeedback. + +#include <assert.h> + +// Reset packets are sent to indicate that a game is to be reset. +// i.e. the game is to return to the SuperMelee fleet setup menu. +// The reset will occur when a reset packet has both been sent and +// received. When a reset packet is received and the local side had not +// sent a reset packet itself, the local side will confirm the reset. +// When both sides initiate a reset simultaneously, the reset packets +// of each side will act as a confirmation for the other side. +// +// When a reset packet has been sent, no further gameplay packets should be +// sent until the game has been reset. Non-gameplay packets such as 'ping' +// are allowed. +// When a reset packet has been received, all further incoming gameplay +// packets are ignored until the game has been reset. +// +// conn->stateFlags.reset.localReset is set when a reset packet is sent. +// conn->stateFlags.reset.remoteReset is set when a reset packet is +// received. +// +// When either localReset or remoteReset gets set and the other flag isn't +// set, Netplay_connectionReset() gets called. +// +// As soon as the following three conditions are met, the reset callback is +// called and the localReset and remoteReset flags are cleared. +// - conn->stateFlags.reset.localReset is set +// - conn->stateFlags.reset.remoteReset is set +// - the reset callback is non-NULL. +// +// Elsewhere in the UQM source: +// When the local side causes a reset, it calls Netplay_localReset(). +// When a remote reset packet is received, Netplay_remoteReset() is called +// (which will sent a reset packet back as confirmation, as required). +// At the end of melee, the reset callback is set (for each connection), +// and the game will wait until the reset callback for each connection has +// been called (when the forementioned conditions have become true) +// (or until the connection is terminated). + + +// This function is called when one side initiates a reset. +static void +Netplay_connectionReset(NetConnection *conn, NetplayResetReason reason, + bool byRemote) { + switch (NetConnection_getState(conn)) { + case NetState_unconnected: + case NetState_connecting: + case NetState_init: + case NetState_inSetup: + break; + case NetState_preBattle: + case NetState_interBattle: + case NetState_selectShip: + case NetState_inBattle: + case NetState_endingBattle: + case NetState_endingBattle2: + resetFeedback(conn, reason, byRemote); + break; + } +} + +static void +Netplay_doConnectionResetCallback(NetConnection *conn) { + NetConnection_ResetCallback callback; + void *resetArg; + + callback = conn->resetCallback; + resetArg = conn->resetCallbackArg; + + NetConnection_setResetCallback(conn, NULL, NULL); + // Clear the resetCallback field before performing the callback, + // so that it can be set again from inside the callback + // function. + callback(conn, resetArg); +} + +static void +Netplay_resetConditionTriggered(NetConnection *conn) { + if (conn->resetCallback == NULL) + return; + + if (!conn->stateFlags.reset.localReset || + !conn->stateFlags.reset.remoteReset) + return; + + conn->stateFlags.reset.localReset = false; + conn->stateFlags.reset.remoteReset = false; + + Netplay_doConnectionResetCallback(conn); +} + +void +Netplay_setResetCallback(NetConnection *conn, + NetConnection_ResetCallback callback, void *resetArg) { + NetConnection_setResetCallback(conn, callback, resetArg); + + Netplay_resetConditionTriggered(conn); +} + +void +Netplay_localReset(NetConnection *conn, NetplayResetReason reason) { + assert(!conn->stateFlags.reset.localReset); + + conn->stateFlags.reset.localReset = true; + if (conn->stateFlags.reset.remoteReset) { + // Both sides have initiated/confirmed the reset. + Netplay_resetConditionTriggered(conn); + } else { + sendReset(conn, reason); + Netplay_connectionReset(conn, reason, false); + } +} + +void +Netplay_remoteReset(NetConnection *conn, NetplayResetReason reason) { + assert(!conn->stateFlags.reset.remoteReset); + // Should already be checked when the packet arrives. + + conn->stateFlags.reset.remoteReset = true; + if (!conn->stateFlags.reset.localReset) { + sendReset(conn, reason); + conn->stateFlags.reset.localReset = true; + Netplay_connectionReset(conn, reason, true); + } + + Netplay_resetConditionTriggered(conn); +} + +bool +Netplay_isLocalReset(const NetConnection *conn) { + return conn->stateFlags.reset.localReset; +} + +bool +Netplay_isRemoteReset(const NetConnection *conn) { + return conn->stateFlags.reset.remoteReset; +} + diff --git a/src/uqm/supermelee/netplay/proto/reset.h b/src/uqm/supermelee/netplay/proto/reset.h new file mode 100644 index 0000000..e16b1d1 --- /dev/null +++ b/src/uqm/supermelee/netplay/proto/reset.h @@ -0,0 +1,41 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_ +#define UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_ + +#include "../netconnection.h" +#include "../packet.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +void Netplay_setResetCallback(NetConnection *conn, + NetConnection_ResetCallback callback, void *resetArg); +void Netplay_localReset(NetConnection *conn, NetplayResetReason reason); +void Netplay_remoteReset(NetConnection *conn, NetplayResetReason reason); +bool Netplay_isLocalReset(const NetConnection *conn); +bool Netplay_isRemoteReset(const NetConnection *conn); + +#if defined(__cplusplus) +} +#endif + +#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_ */ + |