shithub: qk2

Download patch

ref: a96f72a41fed2a20c363865b5860fcbfeec7c90d
parent: 24746fcce9c0733764a9d256884f0bfbe4a808f2
author: Konstantinn Bonnet <[email protected]>
date: Sun Apr 19 14:54:44 EDT 2015

port udp/ip networking

it's pretty ugly, but should/will be rewritten anyway once cl/sv code uses plan9
semantics only.

--- a/README
+++ b/README
@@ -1,19 +1,20 @@
 qk2 - (9) quake 2
 =================
-	- game data is stored in $home/lib/quake2/
+	- game data dirs are stored in $home/lib/quake2/, e.g. $home/lib/quake2/dicks/
 	- 386 and amd64 only
 
 
 horrors
 -------
-	- fault read when warping screen underwater
-	- rendering glitches on screen borders
-	- rendering glitches underwater
-	- #pragma pack on set globally
+	- #pragma pack on set globally!!!
+	- rendering glitches and fault reads on resolutions not originally supported
+	  (4:3 and SXGA)
 	- edict_t definitions merged without much looking
 	- q_sh9: stupid posixish glob stuff
-	- portme: net_udp, cd_9
-	- garbled stdout: Sys_ConsoleOutput
+	- net_udp mostly horrid
+	- portme: cd_9
+	- garbled stdout (Sys_ConsoleOutput): \r instead of \n
+	- dedicated 1: input not working (Sys_ConsoleInput)
 	- race between mproc and IN_Commands
 	- cinematic palette is fucked up if you resize during playback
 	- sound only works thanks to shitty workarounds
--- a/plan9/net_udp.c
+++ b/plan9/net_udp.c
@@ -1,21 +1,32 @@
 #include <u.h>
 #include <libc.h>
 #include <stdio.h>
-#include <ctype.h>
+#include <thread.h>
 #include <bio.h>
 #include <ndb.h>
+#include <ip.h>
 #include "../q_shared.h"
 
-/* FIXME: only loopback works, the rest is complete bullshit */
+/* FIXME: this shit SUCKS, and ipv4 only because of other code */
 
+cvar_t	*svport;	/* server port and copy of string value */
+char	srv[6];
+cvar_t	*clport;	/* "client" port and copy */
+char	clsrv[6];
+
 typedef struct Loopmsg Loopmsg;
 typedef struct Loopback Loopback;
+typedef struct Conmsg Conmsg;
+typedef struct Conlist Conlist;
 
 enum{
-	LOOPBACK	= 0x7f000001,
-	MAX_LOOPBACK	= 4
+	MAX_LOOPBACK	= 4,
+	HDRSZ		= 16+16+16+2+2,	/* sizeof Udphdr w/o padding */
+	BUFSZ		= MAX_MSGLEN,
+	NBUF		= 64,
+	DTHGRP		= 1,
+	CLPORT		= 27909
 };
-
 struct Loopmsg{
 	byte	data[MAX_MSGLEN];
 	int	datalen;
@@ -27,10 +38,29 @@
 };
 Loopback	loopbacks[2];
 
-int	ipfd[2], ipxfd[2];
-netadr_t laddr;
+struct Conlist{
+	Conlist *p;
+	uchar	u[IPaddrlen+2];
+	char	addr[IPaddrlen*2+8+6];	/* ipv6 + separators + port in decimal */
+	int	dfd;
+	Udphdr	h;
+	int	src;	/* q2 assumes broadcast replies are received on NS_CLIENT */
+};
+Conlist *cnroot;
 
+struct Conmsg{
+	Conlist *p;
+	int	n;
+	uchar	buf[BUFSZ];
+};
+Channel *udpchan;
+Channel *clchan;
 
+netadr_t laddr;		/* 0.0.0.0:0 */
+int cfd = -1, ufd = -1, clfd = -1, cldfd = -1;
+QLock cnlock;
+
+
 qboolean NET_CompareAdr (netadr_t a, netadr_t b)
 {
 	return (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);
@@ -55,17 +85,17 @@
 
 char *NET_AdrToString (netadr_t a)
 {
-	static char s[64];
-	
-	Com_sprintf(s, sizeof(s), "%i.%i.%i.%i:%hi", a.ip[0], a.ip[1], a.ip[2], a.ip[3], a.port << 8 | a.port >> 8);
+	static char s[256];
+
+	seprint(s, s+sizeof s, "%ud.%ud.%ud.%ud:%hud", a.ip[0], a.ip[1], a.ip[2], a.ip[3], BigShort(a.port));
 	return s;
 }
 
 char *NET_BaseAdrToString (netadr_t a)
 {
-	static char s[64];
-	
-	Com_sprintf(s, sizeof(s), "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3]);
+	static char s[256];
+
+	seprint(s, s+sizeof s, "%ud.%ud.%ud.%ud", a.ip[0], a.ip[1], a.ip[2], a.ip[3]);
 	return s;
 }
 
@@ -80,249 +110,414 @@
 192.246.40.70:28000
 =============
 */
-qboolean NET_StringToAdr (char *s, netadr_t *a)
+qboolean NET_StringToAdr (char *addr, netadr_t *a)		/* assumes IPv4 */
 {
 	int i;
-	char *e, *p, *ss;
+	char s[256], *p, *pp;
 	Ndb *db;
 	Ndbtuple *nt;
 
-	if(!strcmp(s, "localhost")){
-		memset(a, 0, sizeof(*a));
+	if(!strcmp(addr, "localhost")){
+		memset(a, 0, sizeof *a);
 		a->type = NA_LOOPBACK;
 		return true;
 	}
 
-	/* FIXMEGASHIT */
-	if((ss = smprint("%s", s)) == nil)		/* don't fuck with someone else's s */
-		sysfatal("NET_StringToAdr:smprint: %r");
-	p = ss;
-	if((e = strchr(p, ':')) != nil){
-		*e++ = '\0';
-		a->port = atoi(e);
+	strncpy(s, addr, sizeof s);
+	s[sizeof(s)-1] = 0;
+
+	if((p = strrchr(s, ':')) != nil){
+		*p++ = '\0';
+		a->port = BigShort(atoi(p));
 	}
-	db = ndbopen(nil);
-	if((nt = ndbgetipaddr(db, ss)) == nil){
+
+	if((db = ndbopen(nil)) == nil){
+		fprint(2, "NET_StringToAdr:ndbopen: %r\n");
+		return false;
+	}
+	if((nt = ndbgetipaddr(db, s)) == nil){
 		ndbclose(db);
+		fprint(2, "NET_StringToAdr:ndbgetipaddr: %r\n");
 		return false;
 	}
-	if((ss = smprint("%s", nt->val)) == nil)	/* get copy of first value found */
-		sysfatal("NET_StringToAdr:smprint: %r");
-	p = ss;
-	for(i = 0; i < 4; i++){
-		if((e = strchr(p, '.')) != nil)
-			*e++ = '\0';
-		a->ip[i] = atoi(p);
-		p = e;
-	}
-	a->type = NA_IP;
-
-	free(ss);
+	strncpy(s, nt->val, sizeof(s)-1);	/* just look at first value found */
 	ndbfree(nt);
 	ndbclose(db);
+
+	for(i = 0, pp = s; i < IPv4addrlen; i++){
+		if((p = strchr(pp, '.')) != nil)
+			*p++ = '\0';
+		a->ip[i] = atoi(pp);
+		pp = p;
+	}
+	a->type = NA_IP;
 	return true;
 }
 
 qboolean NET_IsLocalAddress (netadr_t adr)
 {
-	return NET_CompareAdr (adr, laddr);
+	return NET_CompareAdr(adr, laddr);
 }
 
-qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, sizebuf_t *net_message)
+qboolean looprecv (netsrc_t sock, netadr_t *net_from, sizebuf_t *d)
 {
 	int i;
-	Loopback *loop;
+	Loopback *l;
 
-	loop = &loopbacks[sock];
-
-	if(loop->send - loop->get > MAX_LOOPBACK)
-		loop->get = loop->send - MAX_LOOPBACK;
-
-	if (loop->get >= loop->send)
+	l = &loopbacks[sock];
+	if(l->send - l->get > MAX_LOOPBACK)
+		l->get = l->send - MAX_LOOPBACK;
+	if(l->get >= l->send)
 		return false;
+	i = l->get & (MAX_LOOPBACK-1);
+	l->get++;
 
-	i = loop->get & (MAX_LOOPBACK-1);
-	loop->get++;
-
-	memcpy(net_message->data, loop->msgs[i].data, loop->msgs[i].datalen);
-	net_message->cursize = loop->msgs[i].datalen;
+	memcpy(d->data, l->msgs[i].data, l->msgs[i].datalen);
+	d->cursize = l->msgs[i].datalen;
 	*net_from = laddr;
 	return true;
 }
 
-void NET_SendLoopPacket (netsrc_t sock, int length, void *data, netadr_t /*to*/)
+void loopsend (netsrc_t sock, int length, void *data, netadr_t /*to*/)
 {
-	Loopback *loop;
+	Loopback *l;
 	int i;
 
-	loop = &loopbacks[sock^1];
-	i = loop->send & (MAX_LOOPBACK-1);
-	loop->send++;
-	memcpy(loop->msgs[i].data, data, length);
-	loop->msgs[i].datalen = length;
+	l = &loopbacks[sock^1];
+	i = l->send & (MAX_LOOPBACK-1);
+	l->send++;
+	memcpy(l->msgs[i].data, data, length);
+	l->msgs[i].datalen = length;
 }
 
-qboolean NET_GetPacket (netsrc_t src, netadr_t *from, sizebuf_t *d)
+void cninit (void)
 {
-	int n, protocol, fd;
-	NetConnInfo *nci;
-	char addr[21];
+	if(cnroot != nil)
+		return;
+	if((cnroot = malloc(sizeof *cnroot)) == nil)
+		sysfatal("cninit:malloc: %r");
+	cnroot->p = cnroot;
+	memset(cnroot->u, 0, sizeof cnroot->u);
+	memset(cnroot->addr, 0, sizeof cnroot->addr);
+	cnroot->dfd = -1;
+}
 
-	if(NET_GetLoopPacket(src, from, d))
-		return true;
+Conlist *cnins (int fd, char *addr, uchar *u, Udphdr *h, int src)
+{
+	Conlist *p, *l;
 
-	for(protocol = 0 ; protocol < 2 ; protocol++){
-		if(protocol == 0)
-			fd = ipfd[src];
-		else
-			fd = ipxfd[src];
-		if(!fd)
-			continue;
+	l = cnroot;
+	if((p = malloc(sizeof *p)) == nil)
+		sysfatal("cnins:malloc: %r");
 
-		if((n = read(fd, d->data, d->maxsize)) < 0){
-			fprint(2, "NET_GetPacket:read: %r\n");
-			Com_Printf("NET_GetPacket: error reading packet\n");
-			continue;
-		}
-		if(n == d->maxsize){
-			Com_Printf("Oversize packet from %s\n", NET_AdrToString(*from));
-			continue;
-		}
-		d->cursize = n;
+	strncpy(p->addr, addr, sizeof p->addr);
+	memcpy(p->u, u, sizeof p->u);
+	p->dfd = fd;
+	if(h != nil)
+		memcpy(&p->h, h, sizeof p->h);
+	p->src = src;
+	p->p = l->p;
+	l->p = p;
+	return p;
+}
 
-		/* FIXME */
-		if((nci = getnetconninfo(nil, fd)) == nil){
-			fprint(2, "NET_GetPacket:getnetconninfo: %r\n");
-			return true;
+Conlist *cnfind (char *raddr)
+{
+	Conlist *p = cnroot->p;
+
+	while(p != cnroot){
+		if(!strncmp(p->addr, raddr, strlen(p->addr)))
+			return p;
+		p = p->p;
+	}
+	return nil;
+}
+
+void cndel (Conlist *p)
+{
+	Conlist *l = cnroot;
+
+	while(l->p != p){
+		l = l->p;
+		if(l == cnroot)
+			sysfatal("cndel: bad unlink: cnroot 0x%p node 0x%p\n", cnroot, p);
+	}
+	l->p = p->p;
+	if(p->dfd != ufd && p->dfd != cldfd && p->dfd != -1)
+		close(p->dfd);
+	free(p);
+}
+
+void cnnuke (void)
+{
+	Conlist *p, *l = cnroot;
+
+	if(cnroot == nil)
+		return;
+	do{
+		p = l;
+		l = l->p;
+		if(p->dfd != -1)
+			close(p->dfd);
+		free(p);
+	}while(l != cnroot);
+	cnroot = nil;
+}
+
+void dproc (void *me)
+{
+	int n, fd;
+	Conmsg m;
+	Conlist *p;
+	Channel *c;
+
+	if(threadsetgrp(DTHGRP) < 0)
+		sysfatal("dproc:threadsetgrp: %r");
+
+	m.p = p = me;
+	c = p->src == NS_CLIENT ? clchan : udpchan;
+	fd = p->dfd;
+
+	for(;;){
+		if((n = read(fd, m.buf, sizeof m.buf)) <= 0)
+			break;
+		m.n = n;
+		if(send(c, &m) < 0)
+			sysfatal("uproc:send: %r\n");
+	}
+	fprint(2, "dproc %d: %r\n", threadpid(threadid()));
+	cndel(me);
+}
+
+void uproc (void *c)
+{
+	int n, fd;
+	uchar udpbuf[BUFSZ+HDRSZ], u[IPaddrlen+2];
+	char a[IPaddrlen*2+8+6];
+	Udphdr h;
+	Conmsg m;
+	Conlist *p;
+
+	if(threadsetgrp(DTHGRP) < 0)
+		sysfatal("uproc:threadsetgrp: %r");
+
+	fd = ufd;
+	if(c == clchan)
+		fd = cldfd;
+	for(;;){
+		if((n = read(fd, udpbuf, sizeof udpbuf)) <= 0)
+			sysfatal("uproc:read: %r\n");
+		memcpy(&h, udpbuf, HDRSZ);
+
+		memcpy(u, h.raddr, IPaddrlen);
+		memcpy(u+IPaddrlen, h.rport, 2);
+		snprint(a, sizeof a, "%ud.%ud.%ud.%ud:%hud", u[12], u[13], u[14], u[15], u[16]<<8 | u[17]);
+		qlock(&cnlock);
+		if((p = cnfind(a)) == nil)
+			p = cnins(fd, a, u, &h, 0);
+		qunlock(&cnlock);
+		m.p = p;
+
+		if(n - HDRSZ < 0){	/* FIXME */
+			m.n = n;
+			memcpy(m.buf, udpbuf, m.n);
+		}else{
+			m.n = n - HDRSZ;
+			memcpy(m.buf, udpbuf+HDRSZ, m.n);
 		}
-		seprint(addr, addr+sizeof(addr), "%s:%s", nci->rsys, nci->rserv);
-		NET_StringToAdr(addr, from);
+		if(send(c, &m) < 0)
+			sysfatal("uproc:send: %r\n");
+	}
+}
+
+qboolean NET_GetPacket (netsrc_t src, netadr_t *from, sizebuf_t *d)
+{
+	int n;
+	Conmsg m;
+
+	if(looprecv(src, from, d))
 		return true;
+	if(cfd == -1)
+		return false;
+
+	if((n = nbrecv(src == NS_SERVER ? udpchan : clchan, &m)) < 0)
+		sysfatal("NET_GetPacket:nbrecv: %r");
+	if(n == 0)
+		return false;
+
+	memcpy(from->ip, m.p->u+12, 4);
+	from->port = m.p->u[17] << 8 | m.p->u[16];
+	if(m.n == d->maxsize){
+		Com_Printf("Oversize packet from %s\n", NET_AdrToString(*from));
+		return false;
 	}
-	return false;
+	from->type = NA_IP;
+	d->cursize = m.n;
+	memcpy(d->data, m.buf, m.n);
+	return true;
 }
 
 void NET_SendPacket (netsrc_t src, int length, void *data, netadr_t to)
 {
-	int fd = 0;
+	int fd;
+	char *addr, *s, *lport;
+	uchar b[BUFSZ+HDRSZ], u[IPaddrlen+2];
+	Conlist *p;
 
 	switch(to.type){
 	case NA_LOOPBACK:
-		NET_SendLoopPacket(src, length, data, to);
-		return;
-	case NA_BROADCAST:
-	case NA_IP:
-		fd = ipfd[src];
-		if(!fd)
-			return;
+		loopsend(src, length, data, to);
 		break;
-	case NA_IPX:
 	case NA_BROADCAST_IPX:
-		fd = ipxfd[src];
-		if(!fd)
-			return;
+	case NA_IPX:
 		break;
+	case NA_BROADCAST:
+		memset(to.ip, 0xff, 4);
+		addr = NET_AdrToString(to);	/* port is PORT_SERVER */
+		s = strrchr(addr, ':');
+		*s++ = '\0';
+		if((fd = dial(netmkaddr(addr, "udp", s), clsrv, nil, nil)) < 0)
+			sysfatal("NET_SendPacket:dial bcast: %r");
+		if(write(fd, data, length) != length)
+			sysfatal("NET_SendPacket:write bcast: %r");
+		close(fd);
+		break;
+	case NA_IP:
+		if(cfd == -1)
+			break;
+
+		addr = NET_AdrToString(to);
+		qlock(&cnlock);
+		p = cnfind(addr);
+		qunlock(&cnlock);
+		if(p != nil){
+			fd = p->dfd;
+			if(fd == ufd || fd == cldfd){
+				memcpy(b, &p->h, HDRSZ);
+				memcpy(b+HDRSZ, data, length);
+				write(fd, b, length+HDRSZ);
+				break;
+			}
+		}else{
+			lport = strrchr(addr, ':');
+			*lport++ = '\0';
+			s = netmkaddr(addr, "udp", lport);
+			if((fd = dial(s, srv, nil, nil)) < 0)
+				sysfatal("NET_SendPacket:dial: %r");
+
+			memcpy(u, v4prefix, sizeof v4prefix);
+			memcpy(u+IPv4off, to.ip, IPv4addrlen);
+			u[16] = to.port;
+			u[17] = to.port >> 8;
+			*(lport-1) = ':';
+			qlock(&cnlock);
+			p = cnins(fd, addr, u, nil, src);
+			qunlock(&cnlock);
+
+			if(proccreate(dproc, p, 8196) < 0)
+				sysfatal("NET_SendPacket:proccreate: %r");
+		}
+		if(write(fd, data, length) != length)
+			sysfatal("NET_SendPacket:write: %r");
+		break;
 	default:
 		Com_Error(ERR_FATAL, "NET_SendPacket: bad address type");
 	}
-	if(write(fd, data, length) != length){
-		fprint(2, "NET_SendPacket:write: %r\n");
-		Com_Printf("NET_SendPacket: bad write\n");
-	}
 }
 
-int NET_Socket (char *ifc, int port)
+/* sleeps msec or until data is read from dfd */
+void NET_Sleep (int msec)
 {
-	int fd;
-	char addr[21], dir[40], *p;
+	if(cfd == -1 || dedicated != nil && !dedicated->value)
+		return; // we're not a server, just run full speed
 
-	/* FIXMEGAFUCKEDUPSHIT */
-	p = seprint(addr, addr+sizeof(addr), "udp!*");
-	if(ifc != nil || Q_strcasecmp(ifc, "localhost"))
-		p = seprint(p-1, addr+sizeof(addr), "%s", ifc);
-	if(port == PORT_ANY)
-		seprint(p, addr+sizeof(addr), "!%hud", (ushort)port);
-
-	if((fd = announce(addr, dir)) < 0){
-		fprint(2, "NET_Socket:announce: %r\n");
-		Com_Printf("Net_Socket: announce failed!\n");
-		return false;
-	}
-	return fd;	/* FIXME: NO! */
+	/* FIXME */
+	print("NET_Sleep %d: PORTME\n", msec);
 }
 
-void NET_OpenIP (void)
+int openname (char *port, int *dfd, Channel **c)
 {
-	cvar_t	*port, *ip;
+	int fd;
+	char data[64], adir[40];
 
-	port = Cvar_Get("port", va("%i", PORT_SERVER), CVAR_NOSET);
-	ip = Cvar_Get("ip", "localhost", CVAR_NOSET);
-
-	/* FIXME: those are ctl fd's! */
-	if(!ipfd[NS_SERVER])
-		ipfd[NS_SERVER] = NET_Socket(ip->string, port->value);
-	if(!ipfd[NS_CLIENT])
-		ipfd[NS_CLIENT] = NET_Socket(ip->string, PORT_ANY);
+	if((fd = announce(netmkaddr("*", "udp", port), adir)) < 0)
+		sysfatal("openname:announce udp!*!%s: %r", port);
+	if(fprint(fd, "headers") < 0)
+		sysfatal("openname: failed to set header mode: %r");
+	snprint(data, sizeof data, "%s/data", adir);
+	if((*dfd = open(data, ORDWR)) < 0)
+		sysfatal("openname:open %r");
+	if((*c = chancreate(sizeof(Conmsg), NBUF)) == nil)
+		sysfatal("openname:chancreate: %r");
+	if(proccreate(uproc, *c, 8196) < 0)
+		sysfatal("openname:proccreate: %r");
+	return fd;
 }
 
-void NET_OpenIPX (void)
+void openudp (void)
 {
+	if(cfd != -1)
+		return;
+
+	/* svport value can be changed at any time */
+	if(svport->value == PORT_ANY || svport->value > 32767)
+		/* FIXME */
+		strncpy(srv, "*", sizeof(srv)-1);
+	else
+		strncpy(srv, svport->string, sizeof(srv)-1);
+	cfd = openname(srv, &ufd, &udpchan);
+
+	/* broadcast kluge */
+	if(clport->value == PORT_ANY || clport->value > 32767)
+		strncpy(clsrv, "*", sizeof(clsrv)-1);
+	else
+		strncpy(clsrv, clport->string, sizeof(clsrv)-1);
+	clfd = openname(clsrv, &cldfd, &clchan);
 }
 
 /* a single player game will only use the loopback code */
 void NET_Config (qboolean multiplayer)
 {
-	int i;
-
-	if(!multiplayer){	/* shut down any existing sockets */
-		for(i=0 ; i<2 ; i++){
-			if(ipfd[i]){
-				close(ipfd[i]);
-				ipfd[i] = 0;
-			}
-			if(ipxfd[i]){
-				close(ipxfd[i]);
-				ipxfd[i] = 0;
-			}
+	if(!multiplayer){	/* shut down existing udp connections */
+		threadkillgrp(DTHGRP);
+		cnnuke();
+		if(udpchan != nil){
+			chanfree(udpchan);
+			udpchan = nil;
 		}
+		if(clchan != nil){
+			chanfree(clchan);
+			clchan = nil;
+		}
+		if(cfd != -1){
+			close(cfd);
+			cfd = -1;
+		}
+		if(clfd != -1){
+			close(clfd);
+			clfd = -1;
+		}
+		if(ufd != -1){
+			close(ufd);
+			ufd = -1;
+		}
+		if(cldfd != -1){
+			close(cldfd);
+			cldfd = -1;
+		}
+	}else{			/* announce open line and get cfd for it */
+		cninit();
+		openudp();
 	}
-	else{			/* open sockets */
-		NET_OpenIP();
-		NET_OpenIPX();
-	}
 }
 
-void NET_Init (void)
-{
-}
-
 void NET_Shutdown (void)
 {
-	NET_Config(false);	// close sockets
+	NET_Config(false);
 }
 
-/* sleeps msec or until net socket is ready */
-void NET_Sleep(int /*msec*/)
+void NET_Init (void)
 {
-	/* PORTME */
-
-	/*
-    struct timeval timeout;
-	fd_set	fdset;
-	extern cvar_t *dedicated;
-	extern qboolean stdin_active;
-	*/
-
-	if(!ipfd[NS_SERVER] || dedicated && !dedicated->value)
-		return; // we're not a server, just run full speed
-
-	/*
-	FD_ZERO(&fdset);
-	if (stdin_active)
-		FD_SET(0, &fdset); // stdin is processed too
-	FD_SET(ipfd[NS_SERVER], &fdset); // network socket
-	timeout.tv_sec = msec/1000;
-	timeout.tv_usec = (msec%1000)*1000;
-	select(ipfd[NS_SERVER]+1, &fdset, NULL, NULL, &timeout);
-	*/
+	svport = Cvar_Get("port", va("%d", PORT_SERVER), CVAR_NOSET);
+	clport = Cvar_Get("clport", va("%hud", CLPORT), CVAR_NOSET);
 }
--- a/plan9/sys_9.c
+++ b/plan9/sys_9.c
@@ -106,6 +106,7 @@
 	if(!strncmp(note, "sys:", 4)){
 		IN_Shutdown();
 		SNDDMA_Shutdown();
+		NET_Shutdown();
 	}
 	noted(NDFLT);
 }