1
0
Fork 0
forked from fte/fteqw
fteqw/engine/common/net_wins.c
2006-03-23 19:22:12 +00:00

1790 lines
39 KiB
C

/*
Copyright (C) 1996-1997 Id Software, Inc.
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.
*/
// net_wins.c
struct sockaddr;
#include "quakedef.h"
#include "netinc.h"
netadr_t net_local_cl_ipadr;
netadr_t net_local_cl_ip6adr;
netadr_t net_local_cl_ipxadr;
netadr_t net_local_sv_ipadr;
netadr_t net_local_sv_ip6adr;
netadr_t net_local_sv_ipxadr;
netadr_t net_local_sv_tcpipadr;
netadr_t net_from;
sizebuf_t net_message;
//#define MAX_UDP_PACKET (MAX_MSGLEN*2) // one more than msg + header
#define MAX_UDP_PACKET 8192 // one more than msg + header
qbyte net_message_buffer[MAX_UDP_PACKET];
#ifdef _WIN32
WSADATA winsockdata;
#endif
#ifdef IPPROTO_IPV6
#ifdef _WIN32
int (WINAPI *pgetaddrinfo) (
const char* nodename,
const char* servname,
const struct addrinfo* hints,
struct addrinfo** res
);
void (WSAAPI *pfreeaddrinfo) (struct addrinfo*);
#else
#define pgetaddrinfo getaddrinfo
#define pfreeaddrinfo freeaddrinfo
/*int (*pgetaddrinfo)
(
const char* nodename,
const char* servname,
const struct addrinfo* hints,
struct addrinfo** res
);
void (*pfreeaddrinfo) (struct addrinfo*);
*/
#endif
#endif
void NET_GetLocalAddress (int socket, netadr_t *out);
int TCP_OpenListenSocket (int port);
extern cvar_t sv_public, sv_listen;
extern cvar_t sv_tcpport;
#define MAX_LOOPBACK 4
typedef struct
{
qbyte data[MAX_UDP_PACKET];
int datalen;
} loopmsg_t;
typedef struct
{
loopmsg_t msgs[MAX_LOOPBACK];
int get, send;
} loopback_t;
loopback_t loopbacks[2];
//=============================================================================
int NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s)
{
switch(a->type)
{
case NA_BROADCAST_IP:
memset (s, 0, sizeof(struct sockaddr_in));
((struct sockaddr_in*)s)->sin_family = AF_INET;
*(int *)&((struct sockaddr_in*)s)->sin_addr = INADDR_BROADCAST;
((struct sockaddr_in*)s)->sin_port = a->port;
return sizeof(struct sockaddr_in);
case NA_IP:
memset (s, 0, sizeof(struct sockaddr_in));
((struct sockaddr_in*)s)->sin_family = AF_INET;
*(int *)&((struct sockaddr_in*)s)->sin_addr = *(int *)&a->address.ip;
((struct sockaddr_in*)s)->sin_port = a->port;
return sizeof(struct sockaddr_in);
#ifdef IPPROTO_IPV6
case NA_BROADCAST_IP6:
memset (s, 0, sizeof(struct sockaddr_in));
((struct sockaddr_in6*)s)->sin6_family = AF_INET6;
memset((int *)&((struct sockaddr_in6*)s)->sin6_addr, 0, sizeof(*(int *)&((struct sockaddr_in6*)s)->sin6_addr));
((struct sockaddr_in6*)s)->sin6_addr.s6_addr[0] = 0xff;
((struct sockaddr_in6*)s)->sin6_addr.s6_addr[1] = 0x02;
((struct sockaddr_in6*)s)->sin6_addr.s6_addr[15] = 0x01;
((struct sockaddr_in6*)s)->sin6_port = a->port;
return sizeof(struct sockaddr_in6);
case NA_IPV6:
memset (s, 0, sizeof(struct sockaddr_in));
((struct sockaddr_in6*)s)->sin6_family = AF_INET6;
memcpy(&((struct sockaddr_in6*)s)->sin6_addr, a->address.ip6, sizeof(struct in6_addr));
((struct sockaddr_in6*)s)->sin6_port = a->port;
return sizeof(struct sockaddr_in6);
#endif
#ifdef USEIPX
case NA_IPX:
((struct sockaddr_ipx *)s)->sa_family = AF_IPX;
memcpy(((struct sockaddr_ipx *)s)->sa_netnum, &a->address.ipx[0], 4);
memcpy(((struct sockaddr_ipx *)s)->sa_nodenum, &a->address.ipx[4], 6);
((struct sockaddr_ipx *)s)->sa_socket = a->port;
return sizeof(struct sockaddr_ipx);
case NA_BROADCAST_IPX:
memset (s, 0, sizeof(struct sockaddr_ipx));
((struct sockaddr_ipx*)s)->sa_family = AF_IPX;
memset(&((struct sockaddr_ipx*)s)->sa_netnum, 0, 4);
memset(&((struct sockaddr_ipx*)s)->sa_nodenum, 0xff, 6);
((struct sockaddr_ipx*)s)->sa_socket = a->port;
return sizeof(struct sockaddr_ipx);
#endif
default:
Sys_Error("Bad type - needs fixing");
return 0;
}
}
void SockadrToNetadr (struct sockaddr_qstorage *s, netadr_t *a)
{
switch (((struct sockaddr*)s)->sa_family)
{
case AF_INET:
a->type = NA_IP;
*(int *)&a->address.ip = ((struct sockaddr_in *)s)->sin_addr.s_addr;
a->port = ((struct sockaddr_in *)s)->sin_port;
break;
#ifdef IPPROTO_IPV6
case AF_INET6:
a->type = NA_IPV6;
memcpy(&a->address.ip6, &((struct sockaddr_in6 *)s)->sin6_addr, sizeof(a->address.ip6));
a->port = ((struct sockaddr_in6 *)s)->sin6_port;
break;
#endif
#ifdef USEIPX
case AF_IPX:
a->type = NA_IPX;
*(int *)a->address.ip = 0xffffffff;
memcpy(&a->address.ipx[0], ((struct sockaddr_ipx *)s)->sa_netnum, 4);
memcpy(&a->address.ipx[4], ((struct sockaddr_ipx *)s)->sa_nodenum, 6);
a->port = ((struct sockaddr_ipx *)s)->sa_socket;
break;
#endif
case AF_UNSPEC:
memset(a, 0, sizeof(*a));
a->type = NA_INVALID;
break;
default:
Sys_Error("SockadrToNetadr: bad socket family");
}
}
qboolean NET_CompareAdr (netadr_t a, netadr_t b)
{
if (a.type != b.type)
return false;
if (a.type == NA_LOOPBACK)
return true;
if (a.type == NA_IP || a.type == NA_BROADCAST_IP)
{
if ((memcmp(a.address.ip, b.address.ip, sizeof(a.address.ip)) == 0) && a.port == b.port)
return true;
return false;
}
#ifdef IPPROTO_IPV6
if (a.type == NA_IPV6 || a.type == NA_BROADCAST_IP6)
{
if ((memcmp(a.address.ip6, b.address.ip6, sizeof(a.address.ip6)) == 0) && a.port == b.port)
return true;
return false;
}
#endif
if (a.type == NA_IPX || a.type == NA_BROADCAST_IPX)
{
if ((memcmp(a.address.ipx, b.address.ipx, sizeof(a.address.ipx)) == 0) && a.port == b.port)
return true;
return false;
}
Sys_Error("NET_CompareAdr: Bad address type");
return false;
}
/*
===================
NET_CompareBaseAdr
Compares without the port
===================
*/
qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b)
{
if (a.type != b.type)
return false;
if (a.type == NA_LOOPBACK)
return true;
if (a.type == NA_IP)
{
if ((memcmp(a.address.ip, b.address.ip, sizeof(a.address.ip)) == 0))
return true;
return false;
}
#ifdef IPPROTO_IPV6
if (a.type == NA_IPV6 || a.type == NA_BROADCAST_IP6)
{
if ((memcmp(a.address.ip6, b.address.ip6, 16) == 0))
return true;
return false;
}
#endif
if (a.type == NA_IPX)
{
if ((memcmp(a.address.ipx, b.address.ipx, 10) == 0))
return true;
return false;
}
Sys_Error("NET_CompareBaseAdr: Bad address type");
return false;
}
char *NET_AdrToString (netadr_t a)
{
static char s[64];
switch(a.type)
{
case NA_BROADCAST_IP:
case NA_IP:
sprintf (s, "%i.%i.%i.%i:%i",
a.address.ip[0],
a.address.ip[1],
a.address.ip[2],
a.address.ip[3],
ntohs(a.port));
break;
#ifdef IPPROTO_IPV6
case NA_BROADCAST_IP6:
case NA_IPV6:
sprintf (s, "[%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%i",
a.address.ip6[0],
a.address.ip6[1],
a.address.ip6[2],
a.address.ip6[3],
a.address.ip6[4],
a.address.ip6[5],
a.address.ip6[6],
a.address.ip6[7],
a.address.ip6[8],
a.address.ip6[9],
a.address.ip6[10],
a.address.ip6[11],
a.address.ip6[12],
a.address.ip6[13],
a.address.ip6[14],
a.address.ip6[15],
ntohs(a.port));
break;
#endif
#ifdef USEIPX
case NA_BROADCAST_IPX:
case NA_IPX:
sprintf (s, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%i",
a.address.ipx[0],
a.address.ipx[1],
a.address.ipx[2],
a.address.ipx[3],
a.address.ipx[4],
a.address.ipx[5],
a.address.ipx[6],
a.address.ipx[7],
a.address.ipx[8],
a.address.ipx[9],
ntohs(a.port));
break;
#endif
case NA_LOOPBACK:
sprintf (s, "LocalHost");
break;
default:
sprintf (s, "invalid netadr_t type");
// Sys_Error("NET_AdrToString: Bad netadr_t type");
}
return s;
}
char *NET_BaseAdrToString (netadr_t a)
{
static char s[64];
switch(a.type)
{
case NA_BROADCAST_IP:
case NA_IP:
sprintf (s, "%i.%i.%i.%i",
a.address.ip[0],
a.address.ip[1],
a.address.ip[2],
a.address.ip[3]);
break;
#ifdef IPPROTO_IPV6
case NA_BROADCAST_IP6:
case NA_IPV6:
sprintf (s, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
a.address.ip6[0],
a.address.ip6[1],
a.address.ip6[2],
a.address.ip6[3],
a.address.ip6[4],
a.address.ip6[5],
a.address.ip6[6],
a.address.ip6[7],
a.address.ip6[8],
a.address.ip6[9],
a.address.ip6[10],
a.address.ip6[11],
a.address.ip6[12],
a.address.ip6[13],
a.address.ip6[14],
a.address.ip6[15]);
break;
#endif
#ifdef USEIPX
case NA_BROADCAST_IPX:
case NA_IPX:
sprintf (s, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x",
a.address.ipx[0],
a.address.ipx[1],
a.address.ipx[2],
a.address.ipx[3],
a.address.ipx[4],
a.address.ipx[5],
a.address.ipx[6],
a.address.ipx[7],
a.address.ipx[8],
a.address.ipx[9]);
break;
#endif
case NA_LOOPBACK:
sprintf (s, "LocalHost");
break;
default:
Sys_Error("NET_BaseAdrToString: Bad netadr_t type");
}
return s;
}
/*
=============
NET_StringToAdr
idnewt
idnewt:28000
192.246.40.70
192.246.40.70:28000
any form of ipv6, including port number.
=============
*/
#define DO(src,dest) \
copy[0] = s[src]; \
copy[1] = s[src + 1]; \
sscanf (copy, "%x", &val); \
((struct sockaddr_ipx *)sadr)->dest = val
qboolean NET_StringToSockaddr (char *s, struct sockaddr_qstorage *sadr)
{
struct hostent *h;
char *colon;
char copy[128];
memset (sadr, 0, sizeof(*sadr));
#ifdef USEIPX
if ((strlen(s) >= 23) && (s[8] == ':') && (s[21] == ':')) // check for an IPX address
{
unsigned int val;
((struct sockaddr_ipx *)sadr)->sa_family = AF_IPX;
copy[2] = 0;
DO(0, sa_netnum[0]);
DO(2, sa_netnum[1]);
DO(4, sa_netnum[2]);
DO(6, sa_netnum[3]);
DO(9, sa_nodenum[0]);
DO(11, sa_nodenum[1]);
DO(13, sa_nodenum[2]);
DO(15, sa_nodenum[3]);
DO(17, sa_nodenum[4]);
DO(19, sa_nodenum[5]);
sscanf (&s[22], "%u", &val);
((struct sockaddr_ipx *)sadr)->sa_socket = htons((unsigned short)val);
}
else
#endif
#ifdef IPPROTO_IPV6
if (pgetaddrinfo)
{
struct addrinfo *addrinfo, *pos;
struct addrinfo udp6hint;
int error;
char *port;
char dupbase[256];
int len;
memset(&udp6hint, 0, sizeof(udp6hint));
udp6hint.ai_family = 0;//Any... we check for AF_INET6 or 4
udp6hint.ai_socktype = SOCK_DGRAM;
udp6hint.ai_protocol = IPPROTO_UDP;
if (*s == '[')
{
port = strstr(s, "]:");
if (!port)
error = EAI_NONAME;
else
{
error = pgetaddrinfo(s+1, port+2, &udp6hint, &addrinfo);
}
}
else
{
port = s + strlen(s);
while(port >= s)
{
if (*port == ':')
break;
port--;
}
if (port == s)
port = NULL;
if (port)
{
len = port - s;
if (len >= sizeof(dupbase))
len = sizeof(dupbase)-1;
strncpy(dupbase, s, len);
dupbase[len] = '\0';
error = pgetaddrinfo(dupbase, port+1, &udp6hint, &addrinfo);
}
else
error = EAI_NONAME;
if (error) //failed, try string with no port.
error = pgetaddrinfo(s, NULL, &udp6hint, &addrinfo); //remember, this func will return any address family that could be using the udp protocol... (ip4 or ip6)
}
if (error)
{
return false;
}
((struct sockaddr*)sadr)->sa_family = 0;
for (pos = addrinfo; pos; pos = pos->ai_next)
{
switch(pos->ai_family)
{
case AF_INET6:
if (((struct sockaddr_in *)sadr)->sin_family == AF_INET6)
break; //first one should be best...
//fallthrough
case AF_INET:
memcpy(sadr, addrinfo->ai_addr, addrinfo->ai_addrlen);
if (pos->ai_family == AF_INET)
goto dblbreak; //don't try finding any more, this is quake, they probably prefer ip4...
break;
}
}
dblbreak:
pfreeaddrinfo (addrinfo);
if (!((struct sockaddr*)sadr)->sa_family) //none suitablefound
return false;
}
else
#endif
{
((struct sockaddr_in *)sadr)->sin_family = AF_INET;
((struct sockaddr_in *)sadr)->sin_port = 0;
strcpy (copy, s);
// strip off a trailing :port if present
for (colon = copy ; *colon ; colon++)
if (*colon == ':')
{
*colon = 0;
((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon+1));
}
if (copy[0] >= '0' && copy[0] <= '9') //this is the wrong way to test. a server name may start with a number.
{
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy);
}
else
{
if (! (h = gethostbyname(copy)) )
return 0;
if (h->h_addrtype != AF_INET)
return 0;
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0];
}
}
return true;
}
#undef DO
qboolean NET_StringToAdr (char *s, netadr_t *a)
{
struct sockaddr_qstorage sadr;
if (!strcmp (s, "internalserver"))
{
memset (a, 0, sizeof(*a));
a->type = NA_LOOPBACK;
return true;
}
if (!NET_StringToSockaddr (s, &sadr))
return false;
SockadrToNetadr (&sadr, a);
return true;
}
// Returns true if we can't bind the address locally--in other words,
// the IP is NOT one of our interfaces.
qboolean NET_IsClientLegal(netadr_t *adr)
{
#if 0
struct sockaddr_in sadr;
int newsocket;
if (adr->ip[0] == 127)
return false; // no local connections period
NetadrToSockadr (adr, &sadr);
if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
Sys_Error ("NET_IsClientLegal: socket:", strerror(qerrno));
sadr.sin_port = 0;
if( bind (newsocket, (void *)&sadr, sizeof(sadr)) == -1)
{
// It is not a local address
close(newsocket);
return true;
}
close(newsocket);
return false;
#else
return true;
#endif
}
qboolean NET_IsLoopBackAddress (netadr_t adr)
{
// return (!strcmp(cls.servername, NET_AdrToString(net_local_adr)) || !strcmp(cls.servername, "local");
return adr.type == NA_LOOPBACK;
}
/////////////////////////////////////////////
//loopback stuff
qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *from, sizebuf_t *message)
{
int i;
loopback_t *loop;
loop = &loopbacks[sock];
if (loop->send - loop->get > MAX_LOOPBACK)
loop->get = loop->send - MAX_LOOPBACK;
if (loop->get >= loop->send)
return false;
i = loop->get & (MAX_LOOPBACK-1);
loop->get++;
if (message->maxsize < loop->msgs[i].datalen)
Sys_Error("NET_SendLoopPacket: Loopback buffer was too big");
memcpy (message->data, loop->msgs[i].data, loop->msgs[i].datalen);
message->cursize = loop->msgs[i].datalen;
memset (from, 0, sizeof(*from));
from->type = NA_LOOPBACK;
message->packing = SZ_RAWBYTES;
message->currentbit = 0;
return true;
}
void NET_SendLoopPacket (netsrc_t sock, int length, void *data, netadr_t to)
{
int i;
loopback_t *loop;
loop = &loopbacks[sock^1];
i = loop->send & (MAX_LOOPBACK-1);
loop->send++;
if (length > sizeof(loop->msgs[i].data))
Sys_Error("NET_SendLoopPacket: Loopback buffer is too small");
memcpy (loop->msgs[i].data, data, length);
loop->msgs[i].datalen = length;
}
//=============================================================================
qboolean NET_GetPacket (netsrc_t netsrc)
{
int ret;
struct sockaddr_qstorage from;
int fromlen;
int i;
int socket;
int err;
if (NET_GetLoopPacket(netsrc, &net_from, &net_message))
return true;
for (i = 0; i < 3; i++)
{
if (netsrc == NS_SERVER)
{
#ifdef CLIENTONLY
Sys_Error("NET_GetPacket: Bad netsrc");
socket = 0;
#else
if (i == 0)
socket = svs.socketip;
else if (i == 1)
socket = svs.socketip6;
else
socket = svs.socketipx;
#endif
}
else
{
#ifdef SERVERONLY
Sys_Error("NET_GetPacket: Bad netsrc");
socket = 0;
#else
if (i == 0)
socket = cls.socketip;
else if (i == 1)
socket = cls.socketip6;
else
socket = cls.socketipx;
#endif
}
if (socket == INVALID_SOCKET)
continue;
fromlen = sizeof(from);
ret = recvfrom (socket, (char *)net_message_buffer, sizeof(net_message_buffer), 0, (struct sockaddr*)&from, &fromlen);
if (ret == -1)
{
err = qerrno;
if (err == EWOULDBLOCK)
continue;
if (err == EMSGSIZE)
{
SockadrToNetadr (&from, &net_from);
Con_TPrintf (TL_OVERSIZEPACKETFROM,
NET_AdrToString (net_from));
continue;
}
if (err == ECONNABORTED || err == ECONNRESET)
{
Con_TPrintf (TL_CONNECTIONLOSTORABORTED); //server died/connection lost.
#ifndef SERVERONLY
if (cls.state != ca_disconnected && netsrc == NS_CLIENT)
{
if (cls.lastarbiatarypackettime+5 < Sys_DoubleTime()) //too many mvdsv
Cbuf_AddText("disconnect\nreconnect\n", RESTRICT_LOCAL); //retry connecting.
else
Con_Printf("Packet was not delivered - server might be badly configured\n");
break;
}
#endif
continue;
}
Con_Printf ("NET_GetPacket: Error (%i): %s\n", err, strerror(err));
continue;
}
SockadrToNetadr (&from, &net_from);
net_message.packing = SZ_RAWBYTES;
net_message.currentbit = 0;
net_message.cursize = ret;
if (net_message.cursize == sizeof(net_message_buffer) )
{
Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (net_from));
continue;
}
return ret;
}
#ifdef TCPCONNECT
#ifndef SERVERONLY
if (netsrc == NS_CLIENT)
{
if (cls.sockettcp != INVALID_SOCKET)
{//client receiving only via tcp
ret = recv(cls.sockettcp, cls.tcpinbuffer+cls.tcpinlen, sizeof(cls.tcpinbuffer)-cls.tcpinlen, 0);
if (ret == -1)
{
err = qerrno;
if (err == EWOULDBLOCK)
ret = 0;
else
{
if (err == ECONNABORTED || err == ECONNRESET)
{
closesocket(cls.sockettcp);
cls.sockettcp = INVALID_SOCKET;
Con_TPrintf (TL_CONNECTIONLOSTORABORTED); //server died/connection lost.
if (cls.state != ca_disconnected)
{
if (cls.lastarbiatarypackettime+5 < Sys_DoubleTime()) //too many mvdsv
Cbuf_AddText("disconnect\nreconnect\n", RESTRICT_LOCAL); //retry connecting.
else
Con_Printf("Packet was not delivered - server might be badly configured\n");
return false;
}
return false;
}
closesocket(cls.sockettcp);
cls.sockettcp = INVALID_SOCKET;
Con_Printf ("NET_GetPacket: Error (%i): %s\n", err, strerror(err));
return false;
}
}
cls.tcpinlen += ret;
if (cls.tcpinlen < 2)
return false;
net_message.cursize = BigShort(*(short*)cls.tcpinbuffer);
if (net_message.cursize >= sizeof(net_message_buffer) )
{
closesocket(cls.sockettcp);
cls.sockettcp = INVALID_SOCKET;
Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (net_from));
return false;
}
if (net_message.cursize+2 > cls.tcpinlen)
{ //not enough buffered to read a packet out of it.
return false;
}
memcpy(net_message_buffer, cls.tcpinbuffer+2, net_message.cursize);
memmove(cls.tcpinbuffer, cls.tcpinbuffer+net_message.cursize+2, cls.tcpinlen - (net_message.cursize+2));
cls.tcpinlen -= net_message.cursize+2;
net_message.packing = SZ_RAWBYTES;
net_message.currentbit = 0;
net_from = cls.sockettcpdest;
return true;
}
}
#endif
#ifndef CLIENTONLY
if (netsrc == NS_SERVER)
{
float timeval = Sys_DoubleTime();
svtcpstream_t *st;
st = svs.tcpstreams;
while (svs.tcpstreams && svs.tcpstreams->socketnum == INVALID_SOCKET)
{
st = svs.tcpstreams;
svs.tcpstreams = svs.tcpstreams->next;
BZ_Free(st);
}
for (st = svs.tcpstreams; st; st = st->next)
{//client receiving only via tcp
while (st->next && st->next->socketnum == INVALID_SOCKET)
{
svtcpstream_t *temp;
temp = st->next;
st->next = st->next->next;
BZ_Free(temp);
}
//due to the above checks about invalid sockets, the socket is always open for st below.
if (st->timeouttime < timeval)
goto closesvstream;
ret = recv(st->socketnum, st->inbuffer+st->inlen, sizeof(st->inbuffer)-st->inlen, 0);
if (ret == 0)
goto closesvstream;
else if (ret == -1)
{
err = qerrno;
if (err == EWOULDBLOCK)
ret = 0;
else
{
if (err == ECONNABORTED || err == ECONNRESET)
{
Con_TPrintf (TL_CONNECTIONLOSTORABORTED); //server died/connection lost.
}
else
Con_Printf ("NET_GetPacket: Error (%i): %s\n", err, strerror(err));
closesvstream:
closesocket(st->socketnum);
st->socketnum = INVALID_SOCKET;
continue;
}
}
st->inlen += ret;
if (st->waitingforprotocolconfirmation)
{
if (st->inlen < 6)
continue;
if (strncmp(st->inbuffer, "qizmo\n", 6))
{
Con_Printf ("Unknown TCP client\n");
goto closesvstream;
}
memmove(st->inbuffer, st->inbuffer+6, st->inlen - (6));
st->inlen -= 6;
st->waitingforprotocolconfirmation = false;
}
if (st->inlen < 2)
continue;
net_message.cursize = BigShort(*(short*)st->inbuffer);
if (net_message.cursize >= sizeof(net_message_buffer) )
{
Con_TPrintf (TL_OVERSIZEPACKETFROM, NET_AdrToString (net_from));
goto closesvstream;
}
if (net_message.cursize+2 > st->inlen)
{ //not enough buffered to read a packet out of it.
continue;
}
memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize);
memmove(st->inbuffer, st->inbuffer+net_message.cursize+2, st->inlen - (net_message.cursize+2));
st->inlen -= net_message.cursize+2;
net_message.packing = SZ_RAWBYTES;
net_message.currentbit = 0;
net_from = st->remoteaddr;
return true;
}
#ifdef TCPCONNECT
if (sv_tcpport.modified)
{
if (svs.sockettcp == INVALID_SOCKET && sv_tcpport.value)
{
svs.sockettcp = TCP_OpenListenSocket(sv_tcpport.value);
if (svs.sockettcp != INVALID_SOCKET)
NET_GetLocalAddress (svs.sockettcp, &net_local_sv_tcpipadr);
else
Con_Printf("Failed to open TCP port %i\n", (int)sv_tcpport.value);
}
else
{
UDP_CloseSocket(svs.sockettcp);
svs.sockettcp = INVALID_SOCKET;
}
sv_tcpport.modified = false;
}
#endif
if (svs.sockettcp != INVALID_SOCKET)
{
int newsock;
newsock = accept(svs.sockettcp, (struct sockaddr*)&from, &fromlen);
if (newsock != INVALID_SOCKET)
{
int _true = true;
setsockopt(newsock, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true));
st = Z_Malloc(sizeof(svtcpstream_t));
st->waitingforprotocolconfirmation = true;
st->next = svs.tcpstreams;
svs.tcpstreams = st;
st->socketnum = newsock;
st->inlen = 0;
SockadrToNetadr(&from, &st->remoteaddr);
send(newsock, "qizmo\n", 6, 0);
st->timeouttime = timeval + 30;
}
}
}
#endif
#endif
return false;
}
//=============================================================================
void NET_SendPacket (netsrc_t netsrc, int length, void *data, netadr_t to)
{
int ret;
struct sockaddr_qstorage addr;
int socket;
int size;
if (to.type == NA_LOOPBACK)
{
// if (Cvar_Get("drop", "0", 0, "network debugging")->value)
// if ((rand()&15)==15) //simulate PL
// return;
NET_SendLoopPacket(netsrc, length, data, to);
return;
}
if (netsrc == NS_SERVER)
{
#ifdef CLIENTONLY
Sys_Error("NET_SendPacket: bad netsrc");
socket = 0;
#else
#ifdef TCPCONNECT
svtcpstream_t *st;
for (st = svs.tcpstreams; st; st = st->next)
{
if (st->socketnum == INVALID_SOCKET)
continue;
if (NET_CompareAdr(to, st->remoteaddr))
{
unsigned short slen = BigShort((unsigned short)length);
send(st->socketnum, (char*)&slen, sizeof(slen), 0);
send(st->socketnum, data, length, 0);
st->timeouttime = Sys_DoubleTime() + 20;
return;
}
}
#endif
#ifdef USEIPX
if (to.type == NA_BROADCAST_IPX || to.type == NA_IPX)
socket = svs.socketipx;
else
#endif
#ifdef IPPROTO_IPV6
if (to.type == NA_IPV6)
socket = svs.socketip6;
else
#endif
socket = svs.socketip;
#endif
}
else
{
#ifdef SERVERONLY
Sys_Error("NET_SendPacket: bad netsrc");
socket = 0;
#else
#ifdef TCPCONNECT
if (cls.sockettcp != -1)
{
if (NET_CompareAdr(to, cls.sockettcpdest))
{ //this goes to the server
//so send it via tcp
unsigned short slen = BigShort((unsigned short)length);
send(cls.sockettcp, (char*)&slen, sizeof(slen), 0);
send(cls.sockettcp, data, length, 0);
return;
}
}
#endif
#ifdef USEIPX
if (to.type == NA_BROADCAST_IPX || to.type == NA_IPX)
socket = cls.socketipx;
else
#endif
#ifdef IPPROTO_IPV6
if (to.type == NA_BROADCAST_IP6 || to.type == NA_IPV6)
socket = cls.socketip6;
else
#endif
socket = cls.socketip;
#endif
}
NetadrToSockadr (&to, &addr);
switch(to.type)
{
default:
size = 0; //should cause an error. :)
break;
#ifdef USEIPX //who uses ipx nowadays anyway?
case NA_BROADCAST_IPX:
case NA_IPX:
size = sizeof(struct sockaddr_ipx);
break;
#endif
case NA_BROADCAST_IP:
case NA_IP:
size = sizeof(struct sockaddr_in);
break;
#ifdef IPPROTO_IPV6
case NA_BROADCAST_IP6:
case NA_IPV6:
size = sizeof(struct sockaddr_in6);
break;
#endif
}
ret = sendto (socket, data, length, 0, (struct sockaddr*)&addr, size );
if (ret == -1)
{
// wouldblock is silent
if (qerrno == EWOULDBLOCK)
return;
if (qerrno == ECONNREFUSED)
return;
#ifndef SERVERONLY
if (qerrno == EADDRNOTAVAIL)
Con_DPrintf("NET_SendPacket Warning: %i\n", qerrno);
else
#endif
Con_TPrintf (TL_NETSENDERROR, qerrno);
}
}
//=============================================================================
int TCP_OpenStream (netadr_t remoteaddr)
{
unsigned long _true = true;
int newsocket;
int temp;
struct sockaddr_qstorage qs;
temp = NetadrToSockadr(&remoteaddr, &qs);
if ((newsocket = socket (((struct sockaddr_in*)&qs)->sin_family, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
return INVALID_SOCKET;
if (connect(newsocket, (struct sockaddr *)&qs, temp) == INVALID_SOCKET)
{
closesocket(newsocket);
return INVALID_SOCKET;
}
if (ioctlsocket (newsocket, FIONBIO, &_true) == -1)
Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno));
return newsocket;
}
int TCP_OpenListenSocket (int port)
{
int newsocket;
struct sockaddr_in address;
unsigned long _true = true;
int i;
int maxport = port + 100;
if ((newsocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
return INVALID_SOCKET;
if (ioctlsocket (newsocket, FIONBIO, &_true) == -1)
Sys_Error ("TCP_OpenListenSocket: ioctl FIONBIO: %s", strerror(qerrno));
address.sin_family = AF_INET;
//ZOID -- check for interface binding option
if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) {
address.sin_addr.s_addr = inet_addr(com_argv[i+1]);
Con_TPrintf(TL_NETBINDINTERFACE,
inet_ntoa(address.sin_addr));
} else
address.sin_addr.s_addr = INADDR_ANY;
for(;;)
{
if (port == PORT_ANY)
address.sin_port = 0;
else
address.sin_port = htons((short)port);
if( bind (newsocket, (void *)&address, sizeof(address)) == -1)
{
if (!port)
{
Con_Printf("Cannot bind tcp socket\n");
closesocket(newsocket);
return INVALID_SOCKET;
}
port++;
if (port > maxport)
{
Con_Printf("Cannot bind tcp socket\n");
closesocket(newsocket);
return INVALID_SOCKET;
}
}
else
break;
}
if (listen(newsocket, 1) == INVALID_SOCKET)
{
Con_Printf("Cannot listen on tcp socket\n");
closesocket(newsocket);
return INVALID_SOCKET;
}
return newsocket;
}
int UDP_OpenSocket (int port, qboolean bcast)
{
int newsocket;
struct sockaddr_in address;
unsigned long _true = true;
int i;
int maxport = port + 100;
if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
return INVALID_SOCKET;
if (ioctlsocket (newsocket, FIONBIO, &_true) == -1)
Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno));
if (bcast)
{
_true = true;
if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1)
{
Con_Printf("Cannot create broadcast socket\n");
return INVALID_SOCKET;
}
}
address.sin_family = AF_INET;
//ZOID -- check for interface binding option
if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) {
address.sin_addr.s_addr = inet_addr(com_argv[i+1]);
Con_TPrintf(TL_NETBINDINTERFACE,
inet_ntoa(address.sin_addr));
} else
address.sin_addr.s_addr = INADDR_ANY;
for(;;)
{
if (port == PORT_ANY)
address.sin_port = 0;
else
address.sin_port = htons((short)port);
if( bind (newsocket, (void *)&address, sizeof(address)) == -1)
{
if (!port)
Sys_Error ("UDP_OpenSocket: bind: %s", strerror(qerrno));
port++;
if (port > maxport)
Sys_Error ("UDP_OpenSocket: bind: %s", strerror(qerrno));
}
else
break;
}
return newsocket;
}
#ifdef IPPROTO_IPV6
int UDP6_OpenSocket (int port, qboolean bcast)
{
int newsocket;
struct sockaddr_in6 address;
unsigned long _true = true;
// int i;
int maxport = port + 100;
if ((newsocket = socket (PF_INET6, SOCK_DGRAM, 0)) == -1)
{
Con_Printf("IPV6 is not supported: %s\n", strerror(qerrno));
return INVALID_SOCKET;
}
if (ioctlsocket (newsocket, FIONBIO, &_true) == -1)
Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(qerrno));
if (bcast)
{
// address.sin6_addr
// _true = true;
// if (setsockopt(newsocket, SOL_SOCKET, IP_ADD_MEMBERSHIP, (char *)&_true, sizeof(_true)) == -1)
// {
Con_Printf("Cannot create broadcast socket\n");
closesocket(newsocket);
return INVALID_SOCKET;
// }
}
address.sin6_family = AF_INET6;
//ZOID -- check for interface binding option
// if ((i = COM_CheckParm("-ip6")) != 0 && i < com_argc) {
// address.sin6_addr = inet_addr(com_argv[i+1]);
/// Con_TPrintf(TL_NETBINDINTERFACE,
// inet_ntoa(address.sin6_addr));
// } else
memset(&address.sin6_addr, 0, sizeof(struct in6_addr));
for(;;)
{
if (port == PORT_ANY)
address.sin6_port = 0;
else
address.sin6_port = htons((short)port);
if( bind (newsocket, (void *)&address, sizeof(address)) == -1)
{
if (!port)
Sys_Error ("UDP6_OpenSocket: bind: %s", strerror(qerrno));
port++;
if (port > maxport)
Sys_Error ("UDP6_OpenSocket: bind: %s", strerror(qerrno));
}
else
break;
}
return newsocket;
}
#endif
void UDP_CloseSocket (int socket)
{
closesocket(socket);
}
int IPX_OpenSocket (int port, qboolean bcast)
{
#ifndef USEIPX
return 0;
#else
int newsocket;
struct sockaddr_ipx address;
u_long _true = 1;
if ((newsocket = socket (PF_IPX, SOCK_DGRAM, NSPROTO_IPX)) == -1)
{
if (qerrno != EAFNOSUPPORT)
Con_Printf ("WARNING: IPX_Socket: socket: %i\n", qerrno);
return INVALID_SOCKET;
}
// make it non-blocking
if (ioctlsocket (newsocket, FIONBIO, &_true) == -1)
{
Con_Printf ("WARNING: IPX_Socket: ioctl FIONBIO: %i\n", qerrno);
return INVALID_SOCKET;
}
if (bcast)
{
// make it broadcast capable
if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1)
{
Con_Printf ("WARNING: IPX_Socket: setsockopt SO_BROADCAST: %i\n", qerrno);
return INVALID_SOCKET;
}
}
address.sa_family = AF_IPX;
memset (address.sa_netnum, 0, 4);
memset (address.sa_nodenum, 0, 6);
if (port == PORT_ANY)
address.sa_socket = 0;
else
address.sa_socket = htons((short)port);
if( bind (newsocket, (void *)&address, sizeof(address)) == -1)
{
Con_Printf ("WARNING: IPX_Socket: bind: %i\n", qerrno);
closesocket (newsocket);
return INVALID_SOCKET;
}
return newsocket;
#endif
}
void IPX_CloseSocket (int socket)
{
#ifdef USEIPX
closesocket(socket);
#endif
}
// sleeps msec or until net socket is ready
//stdin can sometimes be a socket. As a result,
//we give the option to select it for nice console imput with timeouts.
#ifndef CLIENTONLY
qboolean NET_Sleep(int msec, qboolean stdinissocket)
{
struct timeval timeout;
fd_set fdset;
int i;
FD_ZERO(&fdset);
if (stdinissocket)
FD_SET(0, &fdset); //stdin tends to be socket 0
i = 0;
if (svs.socketip!=INVALID_SOCKET)
{
FD_SET(svs.socketip, &fdset); // network socket
i = svs.socketip;
}
#ifdef IPPROTO_IPV6
if (svs.socketip6!=INVALID_SOCKET)
{
FD_SET(svs.socketip6, &fdset); // network socket
if (svs.socketip6 > i)
i = svs.socketip6;
i = svs.socketip6;
}
#endif
#ifdef USEIPX
if (svs.socketipx!=INVALID_SOCKET)
{
FD_SET(svs.socketipx, &fdset); // network socket
if (svs.socketipx > i)
i = svs.socketipx;
}
#endif
timeout.tv_sec = msec/1000;
timeout.tv_usec = (msec%1000)*1000;
select(i+1, &fdset, NULL, NULL, &timeout);
if (stdinissocket)
return FD_ISSET(0, &fdset);
return true;
}
#endif
void NET_GetLocalAddress (int socket, netadr_t *out)
{
char buff[512];
struct sockaddr_qstorage address;
int namelen;
netadr_t adr = {0};
qboolean notvalid = false;
strcpy(buff, "localhost");
gethostname(buff, 512);
buff[512-1] = 0;
if (!NET_StringToAdr (buff, &adr)) //urm
NET_StringToAdr ("127.0.0.1", &adr);
namelen = sizeof(address);
if (getsockname (socket, (struct sockaddr *)&address, &namelen) == -1)
{
notvalid = true;
NET_StringToSockaddr("0.0.0.0", (struct sockaddr_qstorage *)&address);
// Sys_Error ("NET_Init: getsockname:", strerror(qerrno));
}
SockadrToNetadr(&address, out);
if (!*(int*)out->address.ip) //socket was set to auto
*(int *)out->address.ip = *(int *)adr.address.ip; //change it to what the machine says it is, rather than the socket.
if (notvalid)
Con_Printf("Couldn't detect local ip\n");
else
Con_TPrintf(TL_IPADDRESSIS, NET_AdrToString (*out) );
}
/*
====================
NET_Init
====================
*/
void NET_Init (void)
{
#ifdef _WIN32
WORD wVersionRequested;
int r;
#ifdef IPPROTO_IPV6
HMODULE ws2_32dll;
ws2_32dll = LoadLibrary("ws2_32.dll");
if (ws2_32dll)
{
pfreeaddrinfo = (void *)GetProcAddress(ws2_32dll, "freeaddrinfo");
pgetaddrinfo = (void *)GetProcAddress(ws2_32dll, "getaddrinfo");
if (!pgetaddrinfo || !pfreeaddrinfo)
{
pgetaddrinfo = NULL;
pfreeaddrinfo = NULL;
FreeLibrary(ws2_32dll);
}
}
else
pgetaddrinfo = NULL;
#endif
wVersionRequested = MAKEWORD(1, 1);
r = WSAStartup (MAKEWORD(1, 1), &winsockdata);
if (r)
Sys_Error ("Winsock initialization failed.");
#endif
Con_TPrintf(TL_UDPINITED);
#ifndef SERVERONLY
cls.socketip = INVALID_SOCKET;
cls.socketip6 = INVALID_SOCKET;
cls.socketipx = INVALID_SOCKET;
#ifdef TCPCONNECT
cls.sockettcp = INVALID_SOCKET;
#endif
#endif
#ifndef CLIENTONLY
svs.socketip = INVALID_SOCKET;
svs.socketip6 = INVALID_SOCKET;
svs.socketipx = INVALID_SOCKET;
#ifdef TCPCONNECT
svs.sockettcp = INVALID_SOCKET;
#endif
#endif
}
#ifndef SERVERONLY
void NET_InitClient(void)
{
int port;
int p;
port = PORT_CLIENT;
p = COM_CheckParm ("-port");
if (p && p < com_argc)
{
port = atoi(com_argv[p+1]);
}
p = COM_CheckParm ("-clport");
if (p && p < com_argc)
{
port = atoi(com_argv[p+1]);
}
//
// open the single socket to be used for all communications
//
cls.socketip = UDP_OpenSocket (port, false);
#ifdef IPPROTO_IPV6
cls.socketip6 = UDP6_OpenSocket (port, false);
#endif
#ifdef USEIPX
cls.socketipx = IPX_OpenSocket (port, false);
#endif
//
// init the message buffer
//
net_message.maxsize = sizeof(net_message_buffer);
net_message.data = net_message_buffer;
//
// determine my name & address
//
NET_GetLocalAddress (cls.socketip, &net_local_cl_ipadr);
Con_TPrintf(TL_CLIENTPORTINITED);
}
#endif
#ifndef CLIENTONLY
void NET_CloseServer(void)
{
if (svs.socketip != INVALID_SOCKET)
{
UDP_CloseSocket(svs.socketip);
svs.socketip = INVALID_SOCKET;
}
#ifdef IPPROTO_IPV6
if (svs.socketip6 != INVALID_SOCKET)
{
UDP_CloseSocket(svs.socketip6);
svs.socketip6 = INVALID_SOCKET;
}
#endif
#ifdef USEIPX
if (svs.socketipx != INVALID_SOCKET)
{
IPX_CloseSocket(svs.socketipx);
svs.socketipx = INVALID_SOCKET;
}
#endif
#ifdef TCPCONNECT
if (svs.sockettcp != INVALID_SOCKET)
{
UDP_CloseSocket(svs.sockettcp );
svs.sockettcp = INVALID_SOCKET;
sv_tcpport.modified = true;
}
#endif
net_local_sv_ipadr.type = NA_LOOPBACK;
net_local_sv_ip6adr.type = NA_LOOPBACK;
net_local_sv_ipxadr.type = NA_LOOPBACK;
}
void NET_InitServer(void)
{
int port;
int p;
port = PORT_SERVER;
if (sv_listen.value)
{
p = COM_CheckParm ("-port");
if (p && p < com_argc)
{
port = atoi(com_argv[p+1]);
}
p = COM_CheckParm ("-svport");
if (p && p < com_argc)
{
port = atoi(com_argv[p+1]);
}
//
// open the single socket to be used for all communications
//
if (svs.socketip == INVALID_SOCKET)
{
svs.socketip = UDP_OpenSocket (port, false);
if (svs.socketip != INVALID_SOCKET)
NET_GetLocalAddress (svs.socketip, &net_local_sv_ipadr);
}
#ifdef IPPROTO_IPV6
if (svs.socketip6 == INVALID_SOCKET)
{
svs.socketip6 = UDP6_OpenSocket (port, false);
if (svs.socketip6 != INVALID_SOCKET)
NET_GetLocalAddress (svs.socketip6, &net_local_sv_ip6adr);
}
#endif
#ifdef USEIPX
if (svs.socketipx == INVALID_SOCKET)
{
svs.socketipx = IPX_OpenSocket (port, false);
if (svs.socketipx != INVALID_SOCKET)
NET_GetLocalAddress (svs.socketipx, &net_local_sv_ipxadr);
}
#endif
}
else
NET_CloseServer();
//
// init the message buffer
//
net_message.maxsize = sizeof(net_message_buffer);
net_message.data = net_message_buffer;
}
#endif
/*
====================
NET_Shutdown
====================
*/
void NET_Shutdown (void)
{
#ifndef CLIENTONLY
NET_CloseServer();
#endif
#ifndef SERVERONLY
UDP_CloseSocket (cls.socketip);
#ifdef IPPROTO_IPV6
UDP_CloseSocket (cls.socketip6);
#endif
#ifdef USEIPX
IPX_CloseSocket (cls.socketipx);
#endif
#endif
#ifdef _WIN32
#ifdef SERVERTONLY
if (!serverthreadID) //running as subsystem of client. Don't close all of it's sockets too.
#endif
WSACleanup ();
#endif
}
typedef struct {
vfsfile_t funcs;
int sock;
char readbuffer[65536];
int readbuffered;
} tcpfile_t;
void VFSTCP_Error(tcpfile_t *f)
{
if (f->sock != INVALID_SOCKET)
{
closesocket(f->sock);
f->sock = INVALID_SOCKET;
}
}
int VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread)
{
tcpfile_t *tf = (tcpfile_t*)file;
int len;
if (tf->sock != INVALID_SOCKET)
{
len = recv(tf->sock, tf->readbuffer + tf->readbuffered, sizeof(tf->readbuffer) - tf->readbuffered, 0);
if (len == -1)
{
//fixme: figure out wouldblock or error
}
else if (len == 0)
VFSTCP_Error(tf);
else
tf->readbuffered += len;
}
if (bytestoread <= tf->readbuffered)
{
memcpy(buffer, tf->readbuffer, bytestoread);
tf->readbuffered -= bytestoread;
memmove(tf->readbuffer, tf->readbuffer+bytestoread, tf->readbuffered);
return bytestoread;
}
else
return 0;
}
int VFSTCP_WriteBytes (struct vfsfile_s *file, void *buffer, int bytestoread)
{
tcpfile_t *tf = (tcpfile_t*)file;
int len;
if (tf->sock == INVALID_SOCKET)
return 0;
len = send(tf->sock, buffer, bytestoread, 0);
if (len == -1 || len == 0)
{
VFSTCP_Error(tf);
return 0;
}
return len;
}
qboolean VFSTCP_Seek (struct vfsfile_s *file, unsigned long pos)
{
VFSTCP_Error((tcpfile_t*)file);
return false;
}
unsigned long VFSTCP_Tell (struct vfsfile_s *file)
{
VFSTCP_Error((tcpfile_t*)file);
return 0;
}
unsigned long VFSTCP_GetLen (struct vfsfile_s *file)
{
return 0;
}
void VFSTCP_Close (struct vfsfile_s *file)
{
VFSTCP_Error((tcpfile_t*)file);
Z_Free(file);
}
vfsfile_t *FS_OpenTCP(char *name)
{
tcpfile_t *newf;
int sock;
netadr_t adr = {0};
if (NET_StringToAdr(name, &adr))
{
sock = TCP_OpenStream(adr);
if (sock == INVALID_SOCKET)
return NULL;
newf = Z_Malloc(sizeof(*newf));
newf->sock = sock;
newf->funcs.Close = VFSTCP_Close;
newf->funcs.Flush = NULL;
newf->funcs.GetLen = VFSTCP_GetLen;
newf->funcs.ReadBytes = VFSTCP_ReadBytes;
newf->funcs.Seek = VFSTCP_Seek;
newf->funcs.Tell = VFSTCP_Tell;
newf->funcs.WriteBytes = VFSTCP_WriteBytes;
newf->funcs.seekingisabadplan = true;
return &newf->funcs;
}
else
return NULL;
}