og-odamex/common/i_net.cpp
2026-01-14 12:38:40 -06:00

1171 lines
25 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id$
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2026 by The Odamex Team.
//
// 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.
//
// DESCRIPTION:
// System specific network interface stuff.
//
//-----------------------------------------------------------------------------
#include "odamex.h"
/* [Petteri] Check if compiling for Win32: */
//#if defined(__WINDOWS__) || defined(__NT__) || defined(_MSC_VER) || defined(WIN32)
//# define WIN32
//#endif
/* Follow #ifdef __WIN32__ marks */
#include <stdlib.h>
#include <sstream>
/* [Petteri] Use Winsock for Win32: */
#include "win32inc.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#endif // WIN32
#ifndef _WIN32
typedef int SOCKET;
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
#define closesocket close
#define ioctlsocket ioctl
#endif
#ifdef _WIN32
#define SETSOCKOPTCAST(x) ((const char *)(x))
#else
#define SETSOCKOPTCAST(x) ((const void *)(x))
#endif
#include <google/protobuf/message.h>
#include "i_system.h"
#include "i_net.h"
#include "svc_map.h"
#include "d_player.h"
#include "m_alloc.h"
#include "minilzo.h"
#ifdef ODA_HAVE_MINIUPNP
#include "miniupnpc/miniwget.h"
#include "miniupnpc/miniupnpc.h"
#include "miniupnpc/upnpcommands.h"
#endif
unsigned int inet_socket;
int localport;
netadr_t net_from; // address of who sent the packet
buf_t net_message(MAX_UDP_PACKET);
extern bool simulated_connection;
// buffer for compression/decompression
// can't be static to a function because some
// of the functions
buf_t compressed, decompressed;
lzo_byte wrkmem[LZO1X_1_MEM_COMPRESS];
EXTERN_CVAR(port)
msg_info_t clc_info[clc_max + 1];
msg_info_t svc_info[svc_max + 1];
#ifdef ODA_HAVE_MINIUPNP
EXTERN_CVAR(sv_upnp)
EXTERN_CVAR(sv_upnp_discovertimeout)
EXTERN_CVAR(sv_upnp_description)
EXTERN_CVAR(sv_upnp_internalip)
EXTERN_CVAR(sv_upnp_externalip)
static struct UPNPUrls urls;
static struct IGDdatas data;
static bool is_upnp_ok = false;
void init_upnp (void)
{
struct UPNPDev * devlist;
struct UPNPDev * dev;
char * descXML;
int descXMLsize = 0;
int res = 0;
char IPAddress[40];
int r;
if (!sv_upnp)
return;
memset(&urls, 0, sizeof(struct UPNPUrls));
memset(&data, 0, sizeof(struct IGDdatas));
PrintFmt(PRINT_HIGH, "UPnP: Discovering router (max 1 unit supported)\n");
#if MINIUPNPC_API_VERSION < 14
devlist = upnpDiscover(sv_upnp_discovertimeout.asInt(), NULL, NULL, 0, 0, &res);
#else
devlist = upnpDiscover(sv_upnp_discovertimeout.asInt(), NULL, NULL, 0, 0, 2, &res);
#endif
if (!devlist || res != UPNPDISCOVER_SUCCESS)
{
PrintFmt(PRINT_WARNING, "UPnP: Router not found or timed out, error {}\n",
res);
is_upnp_ok = false;
return;
}
dev = devlist;
while (dev)
{
if (strstr (dev->st, "InternetGatewayDevice"))
break;
dev = dev->pNext;
}
if (!dev)
dev = devlist; /* defaulting to first device */
//Printf(PRINT_HIGH, "UPnP device :\n"
// " desc: %s\n st: %s\n",
// dev->descURL, dev->st);
#if MINIUPNPC_API_VERSION < 16
descXML = (char *)miniwget(dev->descURL, &descXMLsize, 0);
#else
descXML = (char *)miniwget(dev->descURL, &descXMLsize, 0, &res);
#endif
if (descXML)
{
parserootdesc (descXML, descXMLsize, &data);
M_Free(descXML);
GetUPNPUrls (&urls, &data, dev->descURL, 0);
}
freeUPNPDevlist(devlist);
r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype,
IPAddress);
if (r != 0)
{
PrintFmt(PRINT_HIGH,
"UPnP: Router found but unable to get external IP address\n");
is_upnp_ok = false;
}
else
{
PrintFmt(PRINT_HIGH, "UPnP: Router found, external IP address is: {}\n",
IPAddress);
// Store ip address just in case admin wants it
sv_upnp_externalip.ForceSet(IPAddress);
is_upnp_ok = true;
}
}
void upnp_add_redir (const char * addr, int port)
{
if (!sv_upnp || !is_upnp_ok)
return;
if (urls.controlURL == NULL)
return;
const std::string port_str = fmt::format("{}", port);
// Set a description if none exists
if (!sv_upnp_description.cstring()[0])
{
std::stringstream desc;
desc << "Odasrv " << "(" << addr << ":" << port_str << ")";
sv_upnp_description.Set(desc.str().c_str());
}
const int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
port_str.c_str(), port_str.c_str(), addr, sv_upnp_description.cstring(), "UDP", NULL, 0);
if (r != 0)
{
PrintFmt(PRINT_HIGH, "UPnP: AddPortMapping failed: {}\n", r);
is_upnp_ok = false;
}
else
{
PrintFmt(PRINT_HIGH, "UPnP: Port mapping added to router: {}",
sv_upnp_description.str());
is_upnp_ok = true;
}
}
void upnp_rem_redir (int port)
{
if (!is_upnp_ok)
return;
if(urls.controlURL == NULL)
return;
const std::string port_str = fmt::format("{}", port);
const int r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype,
port_str.c_str(), "UDP", 0);
if (r != 0)
{
PrintFmt(PRINT_HIGH, "UPnP: DeletePortMapping failed: {}\n", r);
is_upnp_ok = false;
}
else
is_upnp_ok = true;
}
#endif
//
// UDPsocket
//
SOCKET UDPsocket (void)
{
SOCKET s;
// allocate a socket
s = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET)
I_FatalError ("can't create socket");
return s;
}
//
// BindToLocalPort
//
void BindToLocalPort (SOCKET s, u_short wanted)
{
int v;
struct sockaddr_in address;
memset (&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
u_short next = wanted;
// denis - try several ports
do
{
address.sin_port = htons(next++);
v = bind (s, (sockaddr *)&address, sizeof(address));
if(next > wanted + 32)
{
I_FatalError ("BindToPort: error");
return;
}
}while (v == SOCKET_ERROR);
char tmp[32] = "";
snprintf(tmp, 32, "%d", next - 1);
port.ForceSet(tmp);
#ifdef ODA_HAVE_MINIUPNP
std::string ip = NET_GetLocalAddress();
if (!ip.empty())
{
sv_upnp_internalip.Set(ip.c_str());
PrintFmt(PRINT_HIGH, "UPnP: Internal IP address is: {}\n", ip);
upnp_add_redir(ip.c_str(), next - 1);
}
else
{
PrintFmt(PRINT_HIGH, "UPnP: Could not get first internal IP address, "
"UPnP will not function\n");
is_upnp_ok = false;
}
#endif
PrintFmt(PRINT_HIGH, "Bound to local port {}\n", next - 1);
}
void CloseNetwork (void)
{
#ifdef ODA_HAVE_MINIUPNP
upnp_rem_redir (port);
#endif
closesocket (inet_socket);
#ifdef _WIN32
WSACleanup ();
#endif
}
// this is from Quake source code :)
void SockadrToNetadr (struct sockaddr_in *s, netadr_t *a)
{
memcpy(&(a->ip), &(s->sin_addr), sizeof(struct in_addr));
a->port = s->sin_port;
}
void NetadrToSockadr (netadr_t *a, struct sockaddr_in *s)
{
memset (s, 0, sizeof(*s));
s->sin_family = AF_INET;
memcpy(&(s->sin_addr), &(a->ip), sizeof(struct in_addr));
s->sin_port = a->port;
}
char *NET_AdrToString (netadr_t a)
{
static char s[64];
snprintf (s, 64, "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs(a.port));
return s;
}
bool NET_StringToAdr (const char *s, netadr_t *a)
{
struct hostent *h;
struct sockaddr_in sadr;
char *colon;
char copy[256];
memset (&sadr, 0, sizeof(sadr));
sadr.sin_family = AF_INET;
sadr.sin_port = 0;
strncpy (copy, s, sizeof(copy) - 1);
copy[sizeof(copy) - 1] = 0;
// strip off a trailing :port if present
for (colon = copy ; *colon ; colon++)
if (*colon == ':')
{
*colon = 0;
sadr.sin_port = htons(atoi(colon+1));
}
if (! (h = gethostbyname(copy)) )
return 0;
*(int *)&sadr.sin_addr = *(int *)h->h_addr_list[0];
SockadrToNetadr (&sadr, a);
return true;
}
bool NET_CompareAdr (netadr_t a, netadr_t b)
{
if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port)
return true;
return false;
}
#ifdef _WIN32
typedef int socklen_t;
#endif
int NET_GetPacket (void)
{
int ret;
struct sockaddr_in from;
socklen_t fromlen;
fromlen = sizeof(from);
net_message.clear();
ret = recvfrom (inet_socket, (char *)net_message.ptr(), net_message.maxsize(), 0, (struct sockaddr *)&from, &fromlen);
if (ret == -1)
{
#ifdef _WIN32
errno = WSAGetLastError();
if (errno == WSAEWOULDBLOCK)
return false;
if (errno == WSAECONNRESET)
return false;
if (errno == WSAEMSGSIZE)
{
PrintFmt(PRINT_HIGH, "Warning: Oversize packet from {}\n",
NET_AdrToString (net_from));
return false;
}
PrintFmt(PRINT_HIGH, "NET_GetPacket: {}\n", strerror(errno));
return false;
#else
if (errno == EWOULDBLOCK)
return false;
if (errno == ECONNREFUSED)
return false;
PrintFmt(PRINT_HIGH, "NET_GetPacket: {}\n", strerror(errno));
return false;
#endif
}
net_message.setcursize(ret);
SockadrToNetadr (&from, &net_from);
return ret;
}
int NET_SendPacket (buf_t &buf, netadr_t &to)
{
int ret;
struct sockaddr_in addr;
// [SL] 2011-07-06 - Don't try to send a packet if we're not really connected
// (eg, a netdemo is being played back)
if (simulated_connection)
{
buf.clear();
return 0;
}
NetadrToSockadr (&to, &addr);
ret = sendto(inet_socket, (const char *)buf.ptr(), buf.size(), 0, (struct sockaddr *)&addr, sizeof(addr));
buf.clear();
if (ret == -1)
{
#ifdef _WIN32
int err = WSAGetLastError();
// wouldblock is silent
if (err == WSAEWOULDBLOCK)
return 0;
#else
if (errno == EWOULDBLOCK)
return 0;
if (errno == ECONNREFUSED)
return 0;
PrintFmt(PRINT_HIGH, "NET_SendPacket: {}\n", strerror(errno));
#endif
}
return ret;
}
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif
std::string NET_GetLocalAddress (void)
{
static char buff[HOST_NAME_MAX];
hostent *ent;
struct in_addr addr;
gethostname(buff, HOST_NAME_MAX);
buff[HOST_NAME_MAX - 1] = 0;
ent = gethostbyname(buff);
// Return the first, IPv4 address
if (ent && ent->h_addrtype == AF_INET && ent->h_addr_list[0] != NULL)
{
addr.s_addr = *(u_long *)ent->h_addr_list[0];
std::string ipstr = inet_ntoa(addr);
PrintFmt(PRINT_HIGH, "Bound to IP: {}\n", ipstr);
return ipstr;
}
else
{
PrintFmt(PRINT_HIGH, "Could not look up host IP address from hostname\n");
return "";
}
}
void SZ_Clear (buf_t *buf)
{
buf->clear();
}
void SZ_Write (buf_t *b, const void *data, int length)
{
b->WriteChunk((const char *)data, length);
}
void SZ_Write (buf_t *b, const byte *data, int startpos, int length)
{
b->WriteChunk((const char *)data, length, startpos);
}
//
// MSG_WriteMarker
//
// denis - use this function to mark the start of your server message
// as it allows for better debugging and optimization of network code
//
// [ML] 8/4/10: Moved to sv_main and slightly modified to provide an adequate
// but temporary fix for bug 594 until netcode_bringup2 is complete.
// Thanks to spleen for providing good brainpower!
//
// [SL] 2011-07-17 - Moved back to i_net.cpp so that it can be used by
// both client & server code. Client has a stub function for SV_SendPackets.
//
void SV_SendPackets(void);
//
// MSG_WriteMarker
//
// denis - use this function to mark the start of your client message
// as it allows for better debugging and optimization of network code
//
void MSG_WriteMarker (buf_t *b, clc_t c)
{
if (simulated_connection)
return;
b->WriteByte((byte)c);
}
void MSG_WriteByte (buf_t *b, byte c)
{
if (simulated_connection)
return;
b->WriteByte((byte)c);
}
void MSG_WriteChunk (buf_t *b, const void *p, unsigned l)
{
if (simulated_connection)
return;
b->WriteChunk((const char *)p, l);
}
void MSG_WriteSVC(buf_t* b, const google::protobuf::Message& msg)
{
if (simulated_connection)
return;
static std::string buffer;
if (!msg.SerializeToString(&buffer))
{
PrintFmt(
PRINT_WARNING,
"WARNING: Could not serialize message \"{}\". This is most likely a bug.\n",
msg.GetDescriptor()->full_name());
return;
}
// Do we actaully have room for this upcoming message?
static constexpr size_t MAX_HEADER_SIZE = 4; // header + 3 bytes for varint size.
if (b->cursize + MAX_HEADER_SIZE + msg.ByteSizeLong() >= MAX_UDP_SIZE)
SV_SendPackets();
svc_t header = SVC_ResolveDescriptor(msg.GetDescriptor());
if (header == svc_noop)
{
PrintFmt(PRINT_WARNING,
"WARNING: Could not find svc header for message \"{}\". This is most "
"likely a bug.\n",
msg.GetDescriptor()->full_name());
return;
}
#if 0
PrintFmt("{} ({})\n, {}\n",
::svc_info[header].getName(), msg.ByteSize(),
msg.ShortDebugString());
#endif
b->WriteByte(header);
b->WriteUnVarint(buffer.size());
b->WriteChunk(buffer.data(), buffer.size());
}
/**
* @brief Broadcast message to all players.
*
* @param buf Type of buffer to broadcast in, per player.
* @param msg Message to broadcast to all players.
* @param skip If passed, skip this player id.
*/
void MSG_BroadcastSVC(const clientBuf_e buf, const google::protobuf::Message& msg,
const int skipPlayer)
{
if (simulated_connection)
return;
static std::string buffer;
if (!msg.SerializeToString(&buffer))
{
PrintFmt(
PRINT_WARNING,
"WARNING: Could not serialize message \"{}\". This is most likely a bug.\n",
msg.GetDescriptor()->full_name());
return;
}
svc_t header = SVC_ResolveDescriptor(msg.GetDescriptor());
if (header == svc_noop)
{
PrintFmt(PRINT_WARNING,
"WARNING: Could not find svc header for message \"{}\". This is most "
"likely a bug.\n",
msg.GetDescriptor()->full_name());
return;
}
for (auto& player : players)
{
if (!player.ingame())
continue;
if (static_cast<int>(player.id) == skipPlayer)
continue;
// Select the correct buffer.
buf_t* b = buf == CLBUF_RELIABLE ? &player.client.reliablebuf : &player.client.netbuf;
// Do we actaully have room for this upcoming message?
static constexpr size_t MAX_HEADER_SIZE = 4; // header + 3 bytes for varint size.
if (b->cursize + MAX_HEADER_SIZE + msg.ByteSizeLong() >= MAX_UDP_SIZE)
SV_SendPackets();
b->WriteByte(header);
b->WriteUnVarint(buffer.size());
b->WriteChunk(buffer.data(), buffer.size());
}
}
void MSG_WriteShort (buf_t *b, short c)
{
if (simulated_connection)
return;
b->WriteShort(c);
}
void MSG_WriteLong (buf_t *b, int c)
{
if (simulated_connection)
return;
b->WriteLong(c);
}
void MSG_WriteUnVarint(buf_t* b, unsigned int uv)
{
if (simulated_connection)
return;
b->WriteUnVarint(uv);
}
void MSG_WriteVarint(buf_t* b, int v)
{
if (simulated_connection)
return;
b->WriteVarint(v);
}
//
// MSG_WriteBool
//
// Write an boolean value to a buffer
void MSG_WriteBool(buf_t *b, bool Boolean)
{
if (simulated_connection)
return;
MSG_WriteByte(b, Boolean ? 1 : 0);
}
//
// MSG_WriteFloat
//
// Write a floating point number to a buffer
void MSG_WriteFloat(buf_t *b, float Float)
{
if (simulated_connection)
return;
std::stringstream StringStream;
StringStream << Float;
MSG_WriteString(b, StringStream.str().c_str());
}
//
// MSG_WriteString
//
// Write a string to a buffer and null terminate it
void MSG_WriteString (buf_t *b, const char *s)
{
if (simulated_connection)
return;
b->WriteString(s);
}
unsigned int toInt(char c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return 10 + c - 'A';
if (c >= 'a' && c <= 'f') return 10 + c - 'a';
return -1;
}
//
// MSG_WriteHexString
//
// Converts a hexidecimal string to its binary representation
void MSG_WriteHexString(buf_t *b, const char *s)
{
byte output[255];
// Nothing to write?
if (!(s && (*s)))
{
MSG_WriteByte(b, 0);
return;
}
const size_t numdigits = strlen(s) / 2;
if (numdigits > ARRAY_LENGTH(output))
{
PrintFmt(PRINT_HIGH, "MSG_WriteHexString: too many digits\n");
return;
}
for (size_t i = 0; i < numdigits; ++i)
{
output[i] = (char)(16 * toInt(s[2*i]) + toInt(s[2*i+1]));
}
MSG_WriteByte(b, (byte)numdigits);
MSG_WriteChunk(b, output, numdigits);
}
int MSG_BytesLeft(void)
{
return net_message.BytesLeftToRead();
}
int MSG_ReadByte (void)
{
return net_message.ReadByte();
}
int MSG_NextByte (void)
{
return net_message.NextByte();
}
void *MSG_ReadChunk (const size_t &size)
{
return net_message.ReadChunk(size);
}
size_t MSG_SetOffset (const size_t &offset, const buf_t::seek_loc_t &loc)
{
return net_message.SetOffset(offset, loc);
}
// Output buffer size for LZO compression, extra space in case uncompressable
#define OUT_LEN(a) ((a) + (a) / 16 + 64 + 3)
// size above which packets get compressed (empirical), does not apply to adaptive compression
#define MINILZO_COMPRESS_MINPACKETSIZE 0xFF
//
// MSG_DecompressMinilzo
//
bool MSG_DecompressMinilzo ()
{
// decompress back onto the receive buffer
size_t left = MSG_BytesLeft();
if(decompressed.maxsize() < net_message.maxsize())
decompressed.resize(net_message.maxsize());
lzo_uint newlen = net_message.maxsize();
unsigned int r = lzo1x_decompress_safe (net_message.ptr() + net_message.BytesRead(), left, decompressed.ptr(), &newlen, NULL);
if(r != LZO_E_OK)
{
PrintFmt(PRINT_HIGH, "Error: minilzo packet decompression failed with error {:X}\n", r);
return false;
}
net_message.clear();
memcpy(net_message.ptr(), decompressed.ptr(), newlen);
net_message.cursize = newlen;
return true;
}
//
// MSG_CompressMinilzo
//
bool MSG_CompressMinilzo (buf_t &buf, size_t start_offset, size_t write_gap)
{
if(buf.size() < MINILZO_COMPRESS_MINPACKETSIZE)
return false;
lzo_uint outlen = OUT_LEN(buf.maxsize() - start_offset - write_gap);
size_t total_len = outlen + start_offset + write_gap;
if(compressed.maxsize() < total_len)
compressed.resize(total_len);
int r = lzo1x_1_compress (buf.ptr() + start_offset,
buf.size() - start_offset,
compressed.ptr() + start_offset + write_gap,
&outlen,
wrkmem);
// worth the effort?
if(r != LZO_E_OK || outlen >= (buf.size() - start_offset - write_gap))
return false;
memcpy(compressed.ptr(), buf.ptr(), start_offset);
SZ_Clear(&buf);
MSG_WriteChunk(&buf, compressed.ptr(), outlen + start_offset + write_gap);
return true;
}
int MSG_ReadShort (void)
{
return net_message.ReadShort();
}
int MSG_ReadLong (void)
{
return net_message.ReadLong();
}
unsigned int MSG_ReadUnVarint()
{
return net_message.ReadUnVarint();
}
int MSG_ReadVarint()
{
return net_message.ReadVarint();
}
//
// MSG_ReadBool
//
// Read a boolean value
bool MSG_ReadBool(void)
{
int Value = net_message.ReadByte();
if (Value < 0 || Value > 1)
{
DPrintFmt("MSG_ReadBool: Value is not 0 or 1, possibly corrupted packet");
return (Value ? true : false);
}
return (Value ? true : false);
}
//
// MSG_ReadString
//
// Read a null terminated string
const char *MSG_ReadString (void)
{
return net_message.ReadString();
}
//
// MSG_ReadFloat
//
// Read a floating point number
float MSG_ReadFloat(void)
{
std::stringstream StringStream;
float Float;
StringStream << MSG_ReadString();
StringStream >> Float;
return Float;
}
/**
* @brief Initialize a svc_info member.
*
* @detail do-while is used to force a semicolon afterwards.
*/
#define SVC_INFO(n) \
do \
{ \
::svc_info[n].id = n; \
::svc_info[n].msgName = #n; \
::svc_info[n].msgFormat = "x"; \
} while (false)
/**
* @brief Initialize a clc_info member.
*
* @detail do-while is used to force a semicolon afterwards.
*/
#define CLC_INFO(n) \
do \
{ \
::clc_info[n].id = n; \
::clc_info[n].msgName = #n; \
::clc_info[n].msgFormat = "x"; \
} while (false)
//
// InitNetMessageFormats
//
static void InitNetMessageFormats()
{
// Server Messages.
SVC_INFO(svc_noop);
SVC_INFO(svc_disconnect);
SVC_INFO(svc_playerinfo);
SVC_INFO(svc_moveplayer);
SVC_INFO(svc_updatelocalplayer);
SVC_INFO(svc_levellocals);
SVC_INFO(svc_pingrequest);
SVC_INFO(svc_updateping);
SVC_INFO(svc_spawnmobj);
SVC_INFO(svc_disconnectclient);
SVC_INFO(svc_loadmap);
SVC_INFO(svc_consoleplayer);
SVC_INFO(svc_explodemissile);
SVC_INFO(svc_removemobj);
SVC_INFO(svc_userinfo);
SVC_INFO(svc_updatemobj);
SVC_INFO(svc_spawnplayer);
SVC_INFO(svc_damageplayer);
SVC_INFO(svc_killmobj);
SVC_INFO(svc_raisemobj);
SVC_INFO(svc_fireweapon);
SVC_INFO(svc_updatesector);
SVC_INFO(svc_print);
SVC_INFO(svc_playermembers);
SVC_INFO(svc_teammembers);
SVC_INFO(svc_activateline);
SVC_INFO(svc_movingsector);
SVC_INFO(svc_playsound);
SVC_INFO(svc_reconnect);
SVC_INFO(svc_exitlevel);
SVC_INFO(svc_touchspecial);
SVC_INFO(svc_forceteam);
SVC_INFO(svc_switch);
SVC_INFO(svc_say);
SVC_INFO(svc_spawnhiddenplayer);
SVC_INFO(svc_updatedeaths);
SVC_INFO(svc_ctfrefresh);
SVC_INFO(svc_ctfevent);
SVC_INFO(svc_serversettings);
SVC_INFO(svc_connectclient);
SVC_INFO(svc_midprint);
SVC_INFO(svc_servergametic);
SVC_INFO(svc_inttimeleft);
SVC_INFO(svc_fullupdatedone);
SVC_INFO(svc_railtrail);
SVC_INFO(svc_playerstate);
SVC_INFO(svc_levelstate);
SVC_INFO(svc_resetmap);
SVC_INFO(svc_playerqueuepos);
SVC_INFO(svc_fullupdatestart);
SVC_INFO(svc_lineupdate);
SVC_INFO(svc_sectorproperties);
SVC_INFO(svc_linesideupdate);
SVC_INFO(svc_mobjstate);
SVC_INFO(svc_damagemobj);
SVC_INFO(svc_executelinespecial);
SVC_INFO(svc_executeacsspecial);
SVC_INFO(svc_thinkerupdate);
SVC_INFO(svc_netdemocap);
SVC_INFO(svc_netdemostop);
SVC_INFO(svc_netdemoloadsnap);
SVC_INFO(svc_vote_update);
SVC_INFO(svc_maplist);
SVC_INFO(svc_maplist_update);
SVC_INFO(svc_maplist_index);
SVC_INFO(svc_toast);
SVC_INFO(svc_hordeinfo);
SVC_INFO(svc_max);
// Client Messages.
CLC_INFO(clc_abort);
CLC_INFO(clc_reserved1);
CLC_INFO(clc_disconnect);
CLC_INFO(clc_say);
CLC_INFO(clc_move);
CLC_INFO(clc_userinfo);
CLC_INFO(clc_pingreply);
CLC_INFO(clc_rate);
CLC_INFO(clc_ack);
CLC_INFO(clc_rcon);
CLC_INFO(clc_rcon_password);
CLC_INFO(clc_changeteam);
CLC_INFO(clc_ctfcommand);
CLC_INFO(clc_spectate);
CLC_INFO(clc_wantwad);
CLC_INFO(clc_kill);
CLC_INFO(clc_cheat);
CLC_INFO(clc_callvote);
CLC_INFO(clc_maplist);
CLC_INFO(clc_maplist_update);
CLC_INFO(clc_getplayerinfo);
CLC_INFO(clc_netcmd);
CLC_INFO(clc_spy);
CLC_INFO(clc_privmsg);
CLC_INFO(clc_max);
}
#undef SVC_INFO
#undef CLC_INFO
CVAR_FUNC_IMPL(net_rcvbuf)
{
int n = var.asInt();
if (setsockopt(inet_socket, SOL_SOCKET, SO_RCVBUF, SETSOCKOPTCAST(&n), (int) sizeof(n)) == -1) {
PrintFmt(PRINT_HIGH, "setsockopt SO_RCVBUF: {}", strerror(errno));
} else {
PrintFmt(PRINT_HIGH, "net_rcvbuf set to {}\n", n);
}
}
CVAR_FUNC_IMPL(net_sndbuf)
{
int n = var.asInt();
if (setsockopt(inet_socket, SOL_SOCKET, SO_SNDBUF, SETSOCKOPTCAST(&n), (int) sizeof(n)) == -1) {
PrintFmt(PRINT_HIGH, "setsockopt SO_SNDBUF: {}", strerror(errno));
} else {
PrintFmt(PRINT_HIGH, "net_sndbuf set to {}\n", n);
}
}
//
// InitNetCommon
//
void InitNetCommon(void)
{
unsigned long _true = true;
#ifdef _WIN32
WSADATA wsad;
WSAStartup( MAKEWORD(2,2), &wsad );
#endif
inet_socket = UDPsocket ();
#ifdef ODA_HAVE_MINIUPNP
init_upnp();
#endif
BindToLocalPort (inet_socket, localport);
if (ioctlsocket(inet_socket, FIONBIO, &_true) == -1)
I_FatalError ("UDPsocket: ioctl FIONBIO: {}", strerror(errno));
// enter message information into message info structs
InitNetMessageFormats();
SZ_Clear(&net_message);
}
//
// NetWaitOrTimeout
//
// denis - yields CPU control briefly; shorter wait when data is available
//
bool NetWaitOrTimeout(size_t ms)
{
struct timeval timeout = {0, int(1000*ms) + 1};
fd_set fds;
FD_ZERO(&fds);
FD_SET(inet_socket, &fds);
int ret = select(inet_socket + 1, &fds, NULL, NULL, &timeout);
if(ret == 1)
return true;
#ifdef _WIN32
// handle SOCKET_ERROR
if(ret == SOCKET_ERROR)
PrintFmt(PRINT_HIGH, "select returned SOCKET_ERROR: {}\n", WSAGetLastError());
#else
// handle -1
if(ret == -1 && ret != EINTR)
PrintFmt(PRINT_HIGH, "select returned -1: {}\n", strerror(errno));
#endif
return false;
}
void I_SetPort(netadr_t &addr, int port)
{
addr.port = htons(port);
}
VERSION_CONTROL (i_net_cpp, "$Id$")