mirror of
https://github.com/odamex/odamex
synced 2026-08-19 02:23:09 -04:00
1171 lines
25 KiB
C++
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$")
|