shithub: qk1

Download patch

ref: 19687e3b456448a7650706ace7bf31f210a6dfde
parent: 05c4f601bbbbe2f1943b25dab7ad7b6eca5e7182
author: Sigrid Solveig Haflínudóttir <[email protected]>
date: Fri Dec 29 10:53:22 EST 2023

rearrange and rename files

--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,7 @@
 	i_resize.o\
 	i_tga.o\
 	i_wad.o\
+	in_sdl.o\
 	keys.o\
 	m_dotproduct.o\
 	mathlib.o\
@@ -56,6 +57,7 @@
 	model_sprite.o\
 	net_loop.o\
 	net_main.o\
+	net_udp_unix.o\
 	pal.o\
 	pr_cmds.o\
 	pr_edict.o\
@@ -77,6 +79,8 @@
 	r_surf.o\
 	sbar.o\
 	screen.o\
+	seprint.o\
+	snd_openal.o\
 	softfloat.o\
 	span.o\
 	span_alpha.o\
@@ -84,12 +88,8 @@
 	sv_move.o\
 	sv_phys.o\
 	sv_user.o\
-	unix/in.o\
-	unix/net_udp.o\
-	unix/qk1.o\
-	unix/seprint.o\
-	unix/snd_openal.o\
-	unix/vid.o\
+	sys_unix.o\
+	vid_sdl.o\
 	view.o\
 	wav.o\
 	world.o\
--- a/in.c
+++ /dev/null
@@ -1,348 +1,0 @@
-#include "quakedef.h"
-#include <bio.h>
-#include <draw.h>
-#include <thread.h>
-#include <mouse.h>
-#include <keyboard.h>
-
-/* vid.c */
-extern int resized;
-extern Point center;
-extern Rectangle grabr;
-
-typedef struct Kev Kev;
-
-enum{
-	Nbuf = 20
-};
-struct Kev{
-	int key;
-	int down;
-};
-static Channel *inchan;
-static QLock mlck;
-
-static cvar_t m_windowed = {"m_windowed", "1", true};
-static cvar_t m_filter = {"m_filter", "0", true};
-static int mouseon, fixms;
-static int oldmwin;
-static float olddx, olddy;
-static int mΔx, mΔy, mb, oldmb;
-
-void
-conscmd(void)
-{
-	char *p;
-
-	if(cls.state != ca_dedicated)
-		return;
-	while(p = nbrecvp(inchan), p != nil){
-		Cbuf_AddText(p);
-		free(p);
-	}
-}
-
-static void
-cproc(void *)
-{
-	char *s;
-	Biobuf *bf;
-
-	if(bf = Bfdopen(0, OREAD), bf == nil)
-		sysfatal("Bfdopen: %r");
-	for(;;){
-		if(s = Brdstr(bf, '\n', 1), s == nil)
-			break;
-		if(sendp(inchan, s) < 0){
-			free(s);
-			break;
-		}
-	}
-	Bterm(bf);
-}
-
-void
-Sys_SendKeyEvents(void)
-{
-	Kev ev;
-	int r;
-
-	if(cls.state == ca_dedicated)
-		return;
-	if(oldmwin != (int)m_windowed.value){
-		oldmwin = (int)m_windowed.value;
-		IN_Grabm(oldmwin);
-	}
-
-	while(r = nbrecv(inchan, &ev), r > 0)
-		Key_Event(ev.key, ev.down);
-	if(r < 0)
-		Con_DPrintf("Sys_SendKeyEvents: %r\n");
-}
-
-void
-IN_Commands(void)
-{
-	int b, i, k, r;
-
-	if(!mouseon || cls.state == ca_dedicated)
-		return;
-	qlock(&mlck);
-	b = mb;
-	qunlock(&mlck);
-	b = b & 0x19 | (b & 2) << 1 | (b & 4) >> 1;
-	for(i=0, k=K_MOUSE1; i<5; i++, k++){
-		if(i == 3)
-			k = K_MWHEELUP;
-		r = b & 1<<i;
-		if(r ^ oldmb & 1<<i)
-			Key_Event(k, r);
-	}
-	oldmb = b & 7;
-}
-
-void
-IN_Move(usercmd_t *cmd)
-{
-	float dx, dy;
-
-	if(!mouseon)
-		return;
-	qlock(&mlck);
-	dx = mΔx;
-	dy = mΔy;
-	mΔx = 0;
-	mΔy = 0;
-	qunlock(&mlck);
-	if(m_filter.value){
-		dx = (dx + olddx) * 0.5;
-		dy = (dy + olddy) * 0.5;
-	}
-	olddx = dx;
-	olddy = dy;
-	dx *= sensitivity.value;
-	dy *= sensitivity.value;
-	if(in_strafe.state & 1 || lookstrafe.value && in_mlook.state & 1)
-		cmd->sidemove += m_side.value * dx;
-	else
-		cl.viewangles[YAW] -= m_yaw.value * dx;
-	if(in_mlook.state & 1)
-		V_StopPitchDrift();
-	if(in_mlook.state & 1 && ~in_strafe.state & 1){
-		cl.viewangles[PITCH] += m_pitch.value * dy;
-		if(cl.viewangles[PITCH] > 80)
-			cl.viewangles[PITCH] = 80;
-		if(cl.viewangles[PITCH] < -70)
-			cl.viewangles[PITCH] = -70;
-	}else{
-		if(in_strafe.state & 1 && noclip_anglehack)
-			cmd->upmove -= m_forward.value * dy;
-		else
-			cmd->forwardmove -= m_forward.value * dy;
-	}
-}
-
-static int
-runetokey(Rune r)
-{
-	int k = 0;
-
-	switch(r){
-	case Kpgup:	k = K_PGUP; break;
-	case Kpgdown:	k = K_PGDN; break;
-	case Khome:	k = K_HOME; break;
-	case Kend:	k = K_END; break;
-	case Kleft:	k = K_LEFTARROW; break;
-	case Kright:	k = K_RIGHTARROW; break;
-	case Kdown:	k = K_DOWNARROW; break;
-	case Kup:	k = K_UPARROW; break;
-	case Kesc:	k = K_ESCAPE; break;
-	case '\n':	k = K_ENTER; break;
-	case '\t':	k = K_TAB; break;
-	case KF|1:	k = K_F1; break;
-	case KF|2:	k = K_F2; break;
-	case KF|3:	k = K_F3; break;
-	case KF|4:	k = K_F4; break;
-	case KF|5:	k = K_F5; break;
-	case KF|6:	k = K_F6; break;
-	case KF|7:	k = K_F7; break;
-	case KF|8:	k = K_F8; break;
-	case KF|9:	k = K_F9; break;
-	case KF|10:	k = K_F10; break;
-	case KF|11:	k = K_F11; break;
-	case KF|12:	k = K_F12; break;
-	case Kbs:	k = K_BACKSPACE; break;
-	case Kdel:	k = K_DEL; break;
-	case Kbreak:	k = K_PAUSE; break;
-	case Kshift:	k = K_SHIFT; break;
-	case Kctl:	k = K_CTRL; break;
-	case Kalt:
-	case Kaltgr:	k = K_ALT; break;
-	case Kins:	k = K_INS; break;
-	case L'§':	k = '~'; break;
-	default:
-		if(r < 0x80)
-			k = r;
-	}
-	return k;
-}
-
-static void
-kproc(void *)
-{
-	int n, k, fd;
-	char buf[256], kdown[128], *s, *p;
-	Rune r;
-	Kev ev, evc;
-
-	fd = open("/dev/kbd", OREAD);
-	if(fd < 0)
-		sysfatal("open /dev/kbd: %r");
-	memset(buf, 0, sizeof buf);
-	memset(kdown, 0, sizeof kdown);
-	evc.key = K_ENTER;
-	evc.down = true;
-	for(;;){
-		if(buf[0] != 0){
-			n = strlen(buf)+1;
-			memmove(buf, buf+n, sizeof(buf)-n);
-		}
-		if(buf[0] == 0){
-			if(n = read(fd, buf, sizeof(buf)-1), n <= 0)
-				break;
-			buf[n-1] = 0;
-			buf[n] = 0;
-		}
-		switch(buf[0]){
-		case 'c':
-			if(send(inchan, &evc) < 0)
-				threadexits(nil);
-		default:
-			continue;
-		case 'k':
-			ev.down = true;
-			s = buf+1;
-			p = kdown+1;
-			break;
-		case 'K':
-			ev.down = false;
-			s = kdown+1;
-			p = buf+1;
-			break;
-		}
-		while(*s != 0){
-			s += chartorune(&r, s);
-			if(utfrune(p, r) == nil){
-				k = runetokey(r);
-				if(k == 0)
-					continue;
-				ev.key = k;
-				if(send(inchan, &ev) < 0)
-					threadexits(nil);
-				if(ev.down)
-					evc.key = k;
-			}
-		}
-		strcpy(kdown, buf);
-	}
-}
-
-static void
-mproc(void *)
-{
-	int b, n, nerr, fd;
-	char buf[1+5*12];
-	Point p, o;
-
-	fd = open("/dev/mouse", ORDWR);
-	if(fd < 0)
-		sysfatal("open /dev/mouse: %r");
-	nerr = 0;
-	o = center;
-	for(;;){
-		if(n = read(fd, buf, sizeof buf), n != 1+4*12){
-			if(n < 0 || ++nerr > 10)
-				break;
-			Con_DPrintf("mproc: bad count %d not 49: %r\n", n);
-			continue;
-		}
-		nerr = 0;
-		switch(*buf){
-		case 'r':
-			resized = 1;
-			/* fall through */
-		case 'm':
-			if(!mouseon)
-				break;
-			if(fixms){
-				fixms = 0;
-				goto res;
-			}
-			p.x = atoi(buf+1+0*12);
-			p.y = atoi(buf+1+1*12);
-			b = atoi(buf+1+2*12);
-			qlock(&mlck);
-			mΔx += p.x - o.x;
-			mΔy += p.y - o.y;
-			mb = b;
-			qunlock(&mlck);
-			if(!ptinrect(p, grabr)){
-		res:
-				fprint(fd, "m%d %d", center.x, center.y);
-				p = center;
-			}
-			o = p;
-			break;
-		}
-	}
-}
-
-void
-IN_Grabm(int on)
-{
-	static char nocurs[2*4+2*2*16];
-	static int fd = -1;
-
-	if(mouseon == on)
-		return;
-	if(mouseon = on && m_windowed.value){
-		fd = open("/dev/cursor", ORDWR|OCEXEC);
-		if(fd < 0){
-			Con_DPrintf("IN_Grabm:open: %r\n");
-			return;
-		}
-		write(fd, nocurs, sizeof nocurs);
-		fixms++;
-	}else if(fd >= 0){
-		close(fd);
-		fd = -1;
-	}
-}
-
-void
-IN_Shutdown(void)
-{
-	if(inchan != nil)
-		chanfree(inchan);
-	IN_Grabm(0);
-}
-
-void
-IN_Init(void)
-{
-	if(cls.state == ca_dedicated){
-		if(inchan = chancreate(sizeof(void *), 2), inchan == nil)
-			sysfatal("chancreate: %r");
-		if(proccreate(cproc, nil, 8192) < 0)
-			sysfatal("proccreate iproc: %r");
-		return;
-	}
-	Cvar_RegisterVariable(&m_windowed);
-	Cvar_RegisterVariable(&m_filter);
-	if(inchan = chancreate(sizeof(Kev), Nbuf), inchan == nil)
-		sysfatal("chancreate: %r");
-	if(proccreate(kproc, nil, 8192) < 0)
-		sysfatal("proccreate kproc: %r");
-	if(proccreate(mproc, nil, 8192) < 0)
-		sysfatal("proccreate mproc: %r");
-}
--- /dev/null
+++ b/in_plan9.c
@@ -1,0 +1,348 @@
+#include "quakedef.h"
+#include <bio.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+
+/* vid.c */
+extern int resized;
+extern Point center;
+extern Rectangle grabr;
+
+typedef struct Kev Kev;
+
+enum{
+	Nbuf = 20
+};
+struct Kev{
+	int key;
+	int down;
+};
+static Channel *inchan;
+static QLock mlck;
+
+static cvar_t m_windowed = {"m_windowed", "1", true};
+static cvar_t m_filter = {"m_filter", "0", true};
+static int mouseon, fixms;
+static int oldmwin;
+static float olddx, olddy;
+static int mΔx, mΔy, mb, oldmb;
+
+void
+conscmd(void)
+{
+	char *p;
+
+	if(cls.state != ca_dedicated)
+		return;
+	while(p = nbrecvp(inchan), p != nil){
+		Cbuf_AddText(p);
+		free(p);
+	}
+}
+
+static void
+cproc(void *)
+{
+	char *s;
+	Biobuf *bf;
+
+	if(bf = Bfdopen(0, OREAD), bf == nil)
+		sysfatal("Bfdopen: %r");
+	for(;;){
+		if(s = Brdstr(bf, '\n', 1), s == nil)
+			break;
+		if(sendp(inchan, s) < 0){
+			free(s);
+			break;
+		}
+	}
+	Bterm(bf);
+}
+
+void
+Sys_SendKeyEvents(void)
+{
+	Kev ev;
+	int r;
+
+	if(cls.state == ca_dedicated)
+		return;
+	if(oldmwin != (int)m_windowed.value){
+		oldmwin = (int)m_windowed.value;
+		IN_Grabm(oldmwin);
+	}
+
+	while(r = nbrecv(inchan, &ev), r > 0)
+		Key_Event(ev.key, ev.down);
+	if(r < 0)
+		Con_DPrintf("Sys_SendKeyEvents: %r\n");
+}
+
+void
+IN_Commands(void)
+{
+	int b, i, k, r;
+
+	if(!mouseon || cls.state == ca_dedicated)
+		return;
+	qlock(&mlck);
+	b = mb;
+	qunlock(&mlck);
+	b = b & 0x19 | (b & 2) << 1 | (b & 4) >> 1;
+	for(i=0, k=K_MOUSE1; i<5; i++, k++){
+		if(i == 3)
+			k = K_MWHEELUP;
+		r = b & 1<<i;
+		if(r ^ oldmb & 1<<i)
+			Key_Event(k, r);
+	}
+	oldmb = b & 7;
+}
+
+void
+IN_Move(usercmd_t *cmd)
+{
+	float dx, dy;
+
+	if(!mouseon)
+		return;
+	qlock(&mlck);
+	dx = mΔx;
+	dy = mΔy;
+	mΔx = 0;
+	mΔy = 0;
+	qunlock(&mlck);
+	if(m_filter.value){
+		dx = (dx + olddx) * 0.5;
+		dy = (dy + olddy) * 0.5;
+	}
+	olddx = dx;
+	olddy = dy;
+	dx *= sensitivity.value;
+	dy *= sensitivity.value;
+	if(in_strafe.state & 1 || lookstrafe.value && in_mlook.state & 1)
+		cmd->sidemove += m_side.value * dx;
+	else
+		cl.viewangles[YAW] -= m_yaw.value * dx;
+	if(in_mlook.state & 1)
+		V_StopPitchDrift();
+	if(in_mlook.state & 1 && ~in_strafe.state & 1){
+		cl.viewangles[PITCH] += m_pitch.value * dy;
+		if(cl.viewangles[PITCH] > 80)
+			cl.viewangles[PITCH] = 80;
+		if(cl.viewangles[PITCH] < -70)
+			cl.viewangles[PITCH] = -70;
+	}else{
+		if(in_strafe.state & 1 && noclip_anglehack)
+			cmd->upmove -= m_forward.value * dy;
+		else
+			cmd->forwardmove -= m_forward.value * dy;
+	}
+}
+
+static int
+runetokey(Rune r)
+{
+	int k = 0;
+
+	switch(r){
+	case Kpgup:	k = K_PGUP; break;
+	case Kpgdown:	k = K_PGDN; break;
+	case Khome:	k = K_HOME; break;
+	case Kend:	k = K_END; break;
+	case Kleft:	k = K_LEFTARROW; break;
+	case Kright:	k = K_RIGHTARROW; break;
+	case Kdown:	k = K_DOWNARROW; break;
+	case Kup:	k = K_UPARROW; break;
+	case Kesc:	k = K_ESCAPE; break;
+	case '\n':	k = K_ENTER; break;
+	case '\t':	k = K_TAB; break;
+	case KF|1:	k = K_F1; break;
+	case KF|2:	k = K_F2; break;
+	case KF|3:	k = K_F3; break;
+	case KF|4:	k = K_F4; break;
+	case KF|5:	k = K_F5; break;
+	case KF|6:	k = K_F6; break;
+	case KF|7:	k = K_F7; break;
+	case KF|8:	k = K_F8; break;
+	case KF|9:	k = K_F9; break;
+	case KF|10:	k = K_F10; break;
+	case KF|11:	k = K_F11; break;
+	case KF|12:	k = K_F12; break;
+	case Kbs:	k = K_BACKSPACE; break;
+	case Kdel:	k = K_DEL; break;
+	case Kbreak:	k = K_PAUSE; break;
+	case Kshift:	k = K_SHIFT; break;
+	case Kctl:	k = K_CTRL; break;
+	case Kalt:
+	case Kaltgr:	k = K_ALT; break;
+	case Kins:	k = K_INS; break;
+	case L'§':	k = '~'; break;
+	default:
+		if(r < 0x80)
+			k = r;
+	}
+	return k;
+}
+
+static void
+kproc(void *)
+{
+	int n, k, fd;
+	char buf[256], kdown[128], *s, *p;
+	Rune r;
+	Kev ev, evc;
+
+	fd = open("/dev/kbd", OREAD);
+	if(fd < 0)
+		sysfatal("open /dev/kbd: %r");
+	memset(buf, 0, sizeof buf);
+	memset(kdown, 0, sizeof kdown);
+	evc.key = K_ENTER;
+	evc.down = true;
+	for(;;){
+		if(buf[0] != 0){
+			n = strlen(buf)+1;
+			memmove(buf, buf+n, sizeof(buf)-n);
+		}
+		if(buf[0] == 0){
+			if(n = read(fd, buf, sizeof(buf)-1), n <= 0)
+				break;
+			buf[n-1] = 0;
+			buf[n] = 0;
+		}
+		switch(buf[0]){
+		case 'c':
+			if(send(inchan, &evc) < 0)
+				threadexits(nil);
+		default:
+			continue;
+		case 'k':
+			ev.down = true;
+			s = buf+1;
+			p = kdown+1;
+			break;
+		case 'K':
+			ev.down = false;
+			s = kdown+1;
+			p = buf+1;
+			break;
+		}
+		while(*s != 0){
+			s += chartorune(&r, s);
+			if(utfrune(p, r) == nil){
+				k = runetokey(r);
+				if(k == 0)
+					continue;
+				ev.key = k;
+				if(send(inchan, &ev) < 0)
+					threadexits(nil);
+				if(ev.down)
+					evc.key = k;
+			}
+		}
+		strcpy(kdown, buf);
+	}
+}
+
+static void
+mproc(void *)
+{
+	int b, n, nerr, fd;
+	char buf[1+5*12];
+	Point p, o;
+
+	fd = open("/dev/mouse", ORDWR);
+	if(fd < 0)
+		sysfatal("open /dev/mouse: %r");
+	nerr = 0;
+	o = center;
+	for(;;){
+		if(n = read(fd, buf, sizeof buf), n != 1+4*12){
+			if(n < 0 || ++nerr > 10)
+				break;
+			Con_DPrintf("mproc: bad count %d not 49: %r\n", n);
+			continue;
+		}
+		nerr = 0;
+		switch(*buf){
+		case 'r':
+			resized = 1;
+			/* fall through */
+		case 'm':
+			if(!mouseon)
+				break;
+			if(fixms){
+				fixms = 0;
+				goto res;
+			}
+			p.x = atoi(buf+1+0*12);
+			p.y = atoi(buf+1+1*12);
+			b = atoi(buf+1+2*12);
+			qlock(&mlck);
+			mΔx += p.x - o.x;
+			mΔy += p.y - o.y;
+			mb = b;
+			qunlock(&mlck);
+			if(!ptinrect(p, grabr)){
+		res:
+				fprint(fd, "m%d %d", center.x, center.y);
+				p = center;
+			}
+			o = p;
+			break;
+		}
+	}
+}
+
+void
+IN_Grabm(int on)
+{
+	static char nocurs[2*4+2*2*16];
+	static int fd = -1;
+
+	if(mouseon == on)
+		return;
+	if(mouseon = on && m_windowed.value){
+		fd = open("/dev/cursor", ORDWR|OCEXEC);
+		if(fd < 0){
+			Con_DPrintf("IN_Grabm:open: %r\n");
+			return;
+		}
+		write(fd, nocurs, sizeof nocurs);
+		fixms++;
+	}else if(fd >= 0){
+		close(fd);
+		fd = -1;
+	}
+}
+
+void
+IN_Shutdown(void)
+{
+	if(inchan != nil)
+		chanfree(inchan);
+	IN_Grabm(0);
+}
+
+void
+IN_Init(void)
+{
+	if(cls.state == ca_dedicated){
+		if(inchan = chancreate(sizeof(void *), 2), inchan == nil)
+			sysfatal("chancreate: %r");
+		if(proccreate(cproc, nil, 8192) < 0)
+			sysfatal("proccreate iproc: %r");
+		return;
+	}
+	Cvar_RegisterVariable(&m_windowed);
+	Cvar_RegisterVariable(&m_filter);
+	if(inchan = chancreate(sizeof(Kev), Nbuf), inchan == nil)
+		sysfatal("chancreate: %r");
+	if(proccreate(kproc, nil, 8192) < 0)
+		sysfatal("proccreate kproc: %r");
+	if(proccreate(mproc, nil, 8192) < 0)
+		sysfatal("proccreate mproc: %r");
+}
--- /dev/null
+++ b/in_sdl.c
@@ -1,0 +1,177 @@
+#include "quakedef.h"
+#include <SDL.h>
+
+/* vid.c */
+extern int resized;
+
+static cvar_t m_windowed = {"m_windowed", "1", true};
+static cvar_t m_filter = {"m_filter", "0", true};
+static cvar_t m_raw = {"m_raw", "1", true};
+static int mouseon, oldmwin, focuslost;
+static float dx, dy, olddx, olddy;
+
+static int mbuttons[] = {
+	K_MOUSE1,
+	K_MOUSE3,
+	K_MOUSE2,
+};
+
+void
+conscmd(void)
+{
+}
+
+void
+Sys_SendKeyEvents(void)
+{
+	SDL_Event event;
+	int key, b;
+
+	if(cls.state == ca_dedicated)
+		return;
+	if(oldmwin != (int)m_windowed.value){
+		oldmwin = (int)m_windowed.value;
+		IN_Grabm(oldmwin);
+	}
+
+	while(SDL_PollEvent(&event)){
+		switch(event.type){
+		case SDL_QUIT:
+			Cbuf_AddText("menu_quit\n");
+			break;
+		case SDL_WINDOWEVENT:
+			switch(event.window.event){
+			case SDL_WINDOWEVENT_RESIZED:
+				resized = 1;
+				break;
+			case SDL_WINDOWEVENT_CLOSE:
+				Cbuf_AddText("menu_quit\n");
+				break;
+			case SDL_WINDOWEVENT_LEAVE:
+				focuslost = mouseon;
+				IN_Grabm(0);
+				break;
+			case SDL_WINDOWEVENT_ENTER:
+				IN_Grabm(focuslost);
+				break;
+			}
+			break;
+		case SDL_MOUSEMOTION:
+			if(mouseon){
+				dx += event.motion.xrel;
+				dy += event.motion.yrel;
+			}
+			break;
+		case SDL_MOUSEBUTTONDOWN:
+		case SDL_MOUSEBUTTONUP:
+			if(mouseon && (b = event.button.button-1) >= 0 && b < nelem(mbuttons))
+				Key_Event(mbuttons[b], event.type == SDL_MOUSEBUTTONDOWN);
+			break;
+		case SDL_KEYDOWN:
+		case SDL_KEYUP:
+			switch(key = event.key.keysym.sym){
+			case SDLK_BACKQUOTE: key = '~'; break;
+			case SDLK_DELETE: key = K_DEL; break;
+			case SDLK_BACKSPACE: key = K_BACKSPACE; break;
+			case SDLK_F1: key = K_F1; break;
+			case SDLK_F2: key = K_F2; break;
+			case SDLK_F3: key = K_F3; break;
+			case SDLK_F4: key = K_F4; break;
+			case SDLK_F5: key = K_F5; break;
+			case SDLK_F6: key = K_F6; break;
+			case SDLK_F7: key = K_F7; break;
+			case SDLK_F8: key = K_F8; break;
+			case SDLK_F9: key = K_F9; break;
+			case SDLK_F10: key = K_F10; break;
+			case SDLK_F11: key = K_F11; break;
+			case SDLK_F12: key = K_F12; break;
+			case SDLK_PAUSE: key = K_PAUSE; break;
+			case SDLK_UP: key = K_UPARROW; break;
+			case SDLK_DOWN: key = K_DOWNARROW; break;
+			case SDLK_RIGHT: key = K_RIGHTARROW; break;
+			case SDLK_LEFT: key = K_LEFTARROW; break;
+			case SDLK_RSHIFT:
+			case SDLK_LSHIFT: key = K_SHIFT; break;
+			case SDLK_RCTRL:
+			case SDLK_LCTRL: key = K_CTRL; break;
+			case SDLK_RALT:
+			case SDLK_LALT: key = K_ALT; break;
+			default:
+				if(key >= 128)
+					key = 0;
+				break;
+			}
+			if(key > 0)
+				Key_Event(key, event.key.state);
+			break;
+		}
+	}
+}
+
+void
+IN_Commands(void)
+{
+}
+
+static void
+m_raw_cb(cvar_t *var)
+{
+	SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, var->value > 0 ? "0" : "1");
+}
+
+void
+IN_Move(usercmd_t *cmd)
+{
+	if(!mouseon)
+		return;
+
+	if(m_filter.value){
+		dx = (dx + olddx) * 0.5;
+		dy = (dy + olddy) * 0.5;
+	}
+	olddx = dx;
+	olddy = dy;
+	dx *= sensitivity.value;
+	dy *= sensitivity.value;
+	if(in_strafe.state & 1 || (lookstrafe.value && in_mlook.state & 1))
+		cmd->sidemove += m_side.value * dx;
+	else
+		cl.viewangles[YAW] -= m_yaw.value * dx;
+	if(in_mlook.state & 1)
+		V_StopPitchDrift();
+	if(in_mlook.state & 1 && ~in_strafe.state & 1){
+		cl.viewangles[PITCH] += m_pitch.value * dy;
+		if(cl.viewangles[PITCH] > 80)
+			cl.viewangles[PITCH] = 80;
+		if(cl.viewangles[PITCH] < -70)
+			cl.viewangles[PITCH] = -70;
+	}else{
+		if(in_strafe.state & 1 && noclip_anglehack)
+			cmd->upmove -= m_forward.value * dy;
+		else
+			cmd->forwardmove -= m_forward.value * dy;
+	}
+	dx = 0;
+	dy = 0;
+}
+
+void
+IN_Grabm(int on)
+{
+	SDL_SetRelativeMouseMode(mouseon = on);
+}
+
+void
+IN_Shutdown(void)
+{
+	IN_Grabm(0);
+}
+
+void
+IN_Init(void)
+{
+	m_raw.cb = m_raw_cb;
+	Cvar_RegisterVariable(&m_windowed);
+	Cvar_RegisterVariable(&m_filter);
+	Cvar_RegisterVariable(&m_raw);
+}
--- a/mkfile
+++ b/mkfile
@@ -5,21 +5,18 @@
 CFLAGS=$CFLAGS -D__plan9__ -D__${objtype}__ -Iplan9
 
 OFILES=\
-	span`{test -f span_$objtype.s && echo -n _$objtype}.$O\
-	span_alpha.$O\
 	cd.$O\
 	cd_plan9.$O\
+	chase.$O\
 	cl_demo.$O\
 	cl_input.$O\
 	cl_main.$O\
 	cl_parse.$O\
 	cl_tent.$O\
-	chase.$O\
 	cmd.$O\
 	common.$O\
 	console.$O\
 	cvar.$O\
-	draw.$O\
 	d_alpha.$O\
 	d_edge.$O\
 	d_init.$O\
@@ -31,6 +28,7 @@
 	d_sprite.$O\
 	d_surf.$O\
 	d_vars.$O\
+	draw.$O\
 	fs.$O\
 	host.$O\
 	host_cmd.$O\
@@ -38,7 +36,7 @@
 	i_resize.$O\
 	i_tga.$O\
 	i_wad.$O\
-	in.$O\
+	in_plan9.$O\
 	isnanf.$O\
 	keys.$O\
 	m_dotproduct`{test -f span_$objtype.s && echo -n _$objtype}.$O\
@@ -52,21 +50,20 @@
 	model_bsp30.$O\
 	model_sprite.$O\
 	nanosec.$O\
-	net_dgrm.$O\
+	net_dgrm_plan9.$O\
 	net_loop.$O\
 	net_main.$O\
-	net_udp.$O\
+	net_udp_plan9.$O\
 	pr_cmds.$O\
 	pr_edict.$O\
 	pr_exec.$O\
 	protocol.$O\
-	qk1.$O\
 	r_aclip.$O\
 	r_alias.$O\
 	r_bsp.$O\
 	r_draw.$O\
-	r_efrag.$O\
 	r_edge.$O\
+	r_efrag.$O\
 	r_fog.$O\
 	r_light.$O\
 	r_main.$O\
@@ -75,16 +72,19 @@
 	r_sky.$O\
 	r_sprite.$O\
 	r_surf.$O\
-	screen.$O\
 	sbar.$O\
-	snd.$O\
+	screen.$O\
+	snd_mix.$O\
 	snd_plan9.$O\
 	softfloat.$O\
+	span_alpha.$O\
+	span`{test -f span_$objtype.s && echo -n _$objtype}.$O\
 	sv_main.$O\
 	sv_move.$O\
 	sv_phys.$O\
 	sv_user.$O\
-	vid.$O\
+	sys_plan9.$O\
+	vid_plan9.$O\
 	view.$O\
 	wav.$O\
 	world.$O\
--- a/net_dgrm.c
+++ /dev/null
@@ -1,766 +1,0 @@
-#include "quakedef.h"
-#include <ip.h>
-
-//#define DEBUG
-
-// these two macros are to make the code more readable
-#define sfunc	landrv[sock->landriver]
-#define dfunc	landrv[net_landriverlevel]
-
-static int net_landriverlevel;
-
-/* statistic counters */
-static int packetsSent;
-static int packetsReSent;
-static int packetsReceived;
-static int receivedDuplicateCount;
-static int shortPacketCount;
-static int droppedDatagrams;
-
-static int myDriverLevel;
-
-static uchar netbuf[4+4+NET_MAXMESSAGE];
-
-extern int m_return_state;
-extern int m_state;
-extern bool m_return_onerror;
-extern char m_return_reason[32];
-
-static int
-netpack(Addr *a, int f, uint seq, uchar *buf, int n)
-{
-	hnputl(netbuf, NET_HEADERSIZE + n | f);
-	hnputl(netbuf+4, seq);
-	if(buf != nil)
-		memcpy(netbuf+8, buf, n);
-	return udpwrite(netbuf, NET_HEADERSIZE + n, a);
-}
-
-int Datagram_SendMessage (qsocket_t *s, sizebuf_t *data)
-{
-	unsigned int	n;
-	unsigned int	eom;
-
-#ifdef DEBUG
-	if (data->cursize == 0)
-		fatal("Datagram_SendMessage: zero length message\n");
-
-	if (data->cursize > NET_MAXMESSAGE)
-		fatal("Datagram_SendMessage: message too big %ud\n", data->cursize);
-
-	if (s->canSend == false)
-		fatal("SendMessage: called with canSend == false\n");
-#endif
-
-	memcpy(s->sendMessage, data->data, data->cursize);
-	s->sendMessageLength = data->cursize;
-	if(data->cursize <= MAX_DATAGRAM){
-		n = data->cursize;
-		eom = NFEOM;
-	}else{
-		n = MAX_DATAGRAM;
-		eom = 0;
-	}
-	s->canSend = false;
-	if(netpack(&s->addr, NFDAT|eom, s->sendSequence++, s->sendMessage, n) < 0)
-		return -1;
-	s->lastSendTime = net_time;
-	packetsSent++;
-	return 1;
-}
-
-
-int SendMessageNext (qsocket_t *s)
-{
-	unsigned int	n;
-	unsigned int	eom;
-
-	if(s->sendMessageLength <= MAX_DATAGRAM){
-		n = s->sendMessageLength;
-		eom = NFEOM;
-	}else{
-		n = MAX_DATAGRAM;
-		eom = 0;
-	}
-	s->sendNext = false;
-	if(netpack(&s->addr, NFDAT|eom, s->sendSequence++, s->sendMessage, n) < 0)
-		return -1;
-	s->lastSendTime = net_time;
-	packetsSent++;
-	return 1;
-}
-
-
-int ReSendMessage (qsocket_t *s)
-{
-	unsigned int	n;
-	unsigned int	eom;
-
-	if (s->sendMessageLength <= MAX_DATAGRAM)
-	{
-		n = s->sendMessageLength;
-		eom = NFEOM;
-	}
-	else
-	{
-		n = MAX_DATAGRAM;
-		eom = 0;
-	}
-	s->sendNext = false;
-	if(netpack(&s->addr, NFDAT|eom, s->sendSequence-1, s->sendMessage, n) < 0)
-		return -1;
-	s->lastSendTime = net_time;
-	packetsReSent++;
-	return 1;
-}
-
-
-bool Datagram_CanSendMessage (qsocket_t *sock)
-{
-	if (sock->sendNext)
-		SendMessageNext (sock);
-
-	return sock->canSend;
-}
-
-
-bool Datagram_CanSendUnreliableMessage (qsocket_t *)
-{
-	return true;
-}
-
-
-int Datagram_SendUnreliableMessage (qsocket_t *s, sizebuf_t *data)
-{
-#ifdef DEBUG
-	if (data->cursize == 0)
-		fatal("Datagram_SendUnreliableMessage: zero length message\n");
-
-	if (data->cursize > MAX_DATAGRAM)
-		fatal("Datagram_SendUnreliableMessage: message too big %ud\n", data->cursize);
-#endif
-
-	if(netpack(&s->addr, NFUNREL, s->unreliableSendSequence++, data->data, data->cursize) < 0)
-		return -1;
-	packetsSent++;
-	return 1;
-}
-
-
-int	Datagram_GetMessage (qsocket_t *sock)
-{
-	int n;
-	unsigned int	flags;
-	int				ret = 0;
-	unsigned int	sequence;
-	unsigned int	count;
-
-	if (!sock->canSend)
-		if ((net_time - sock->lastSendTime) > 1.0)
-			ReSendMessage (sock);
-	while(1)
-	{
-		if((n = udpread(netbuf, NET_MAXMESSAGE, &sock->addr)) == 0)
-			break;
-
-		if (n == -1)
-		{
-			Con_Printf("Read error\n");
-			return -1;
-		}
-
-		if (n < NET_HEADERSIZE)
-		{
-			shortPacketCount++;
-			continue;
-		}
-
-		n = nhgetl(netbuf);
-		flags = n & (~NFMASK);
-		n &= NFMASK;
-
-		if (flags & NFCTL)
-			continue;
-
-		sequence = nhgetl(netbuf+4);
-		packetsReceived++;
-
-		if (flags & NFUNREL)
-		{
-			if (sequence < sock->unreliableReceiveSequence)
-			{
-				Con_DPrintf("Got a stale datagram\n");
-				ret = 0;
-				break;
-			}
-			if (sequence != sock->unreliableReceiveSequence)
-			{
-				count = sequence - sock->unreliableReceiveSequence;
-				droppedDatagrams += count;
-				Con_DPrintf("Dropped %ud datagram(s)\n", count);
-			}
-			sock->unreliableReceiveSequence = sequence + 1;
-
-			n -= NET_HEADERSIZE;
-
-			SZ_Clear (&net_message);
-			SZ_Write (&net_message, netbuf+8, n);
-
-			ret = 2;
-			break;
-		}
-
-		if (flags & NFACK)
-		{
-			if (sequence != (sock->sendSequence - 1))
-			{
-				Con_DPrintf("Stale ACK received\n");
-				continue;
-			}
-			if (sequence == sock->ackSequence)
-			{
-				sock->ackSequence++;
-				if (sock->ackSequence != sock->sendSequence)
-					Con_DPrintf("ack sequencing error\n");
-			}
-			else
-			{
-				Con_DPrintf("Duplicate ACK received\n");
-				continue;
-			}
-			sock->sendMessageLength -= MAX_DATAGRAM;
-			if (sock->sendMessageLength > 0)
-			{
-				memcpy(sock->sendMessage, sock->sendMessage+MAX_DATAGRAM, sock->sendMessageLength);
-				sock->sendNext = true;
-			}
-			else
-			{
-				sock->sendMessageLength = 0;
-				sock->canSend = true;
-			}
-			continue;
-		}
-
-		if (flags & NFDAT)
-		{
-			netpack(&sock->addr, NFACK, sequence, nil, 0);
-			if (sequence != sock->receiveSequence)
-			{
-				receivedDuplicateCount++;
-				continue;
-			}
-			sock->receiveSequence++;
-
-			n -= NET_HEADERSIZE;
-
-			if (flags & NFEOM)
-			{
-				SZ_Clear(&net_message);
-				SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength);
-				SZ_Write(&net_message, netbuf+8, n);
-				sock->receiveMessageLength = 0;
-
-				ret = 1;
-				break;
-			}
-
-			memcpy(sock->receiveMessage + sock->receiveMessageLength, netbuf+8, n);
-			sock->receiveMessageLength += n;
-			continue;
-		}
-	}
-
-	if (sock->sendNext)
-		SendMessageNext (sock);
-
-	return ret;
-}
-
-
-void PrintStats(qsocket_t *s)
-{
-	Con_Printf("canSend = %4d   \n", s->canSend);
-	Con_Printf("sendSeq = %4d   ", s->sendSequence);
-	Con_Printf("recvSeq = %4d   \n", s->receiveSequence);
-	Con_Printf("\n");
-}
-
-void NET_Stats_f (void)
-{
-	qsocket_t	*s;
-
-	if (Cmd_Argc () == 1)
-	{
-		Con_Printf("unreliable messages sent   = %d\n", unreliableMessagesSent);
-		Con_Printf("unreliable messages recv   = %d\n", unreliableMessagesReceived);
-		Con_Printf("reliable messages sent     = %d\n", messagesSent);
-		Con_Printf("reliable messages received = %d\n", messagesReceived);
-		Con_Printf("packetsSent                = %d\n", packetsSent);
-		Con_Printf("packetsReSent              = %d\n", packetsReSent);
-		Con_Printf("packetsReceived            = %d\n", packetsReceived);
-		Con_Printf("receivedDuplicateCount     = %d\n", receivedDuplicateCount);
-		Con_Printf("shortPacketCount           = %d\n", shortPacketCount);
-		Con_Printf("droppedDatagrams           = %d\n", droppedDatagrams);
-	}
-	else if(strcmp(Cmd_Argv(1), "*") == 0)
-	{
-		for (s = net_activeSockets; s; s = s->next)
-			PrintStats(s);
-		for (s = net_freeSockets; s; s = s->next)
-			PrintStats(s);
-	}
-	else
-	{
-		for (s = net_activeSockets; s; s = s->next)
-			if(cistrcmp(Cmd_Argv(1), s->address) == 0)
-				break;
-		if (s == nil)
-			for (s = net_freeSockets; s; s = s->next)
-				if(cistrcmp(Cmd_Argv(1), s->address) == 0)
-					break;
-		if (s == nil)
-			return;
-		PrintStats(s);
-	}
-}
-
-int
-Datagram_Init(void)
-{
-	int i;
-
-	myDriverLevel = net_driverlevel;
-	Cmd_AddCommand("net_stats", NET_Stats_f);
-
-	for(i=0; i<net_numlandrivers; i++){
-		if(landrv[i].Init() < 0)
-			continue;
-		landrv[i].initialized = true;
-	}
-
-	return 0;
-}
-
-
-void Datagram_Shutdown (void)
-{
-	int i;
-
-	// shutdown the lan drivers
-	for (i = 0; i < net_numlandrivers; i++)
-	{
-		if (landrv[i].initialized)
-		{
-			landrv[i].Shutdown ();
-			landrv[i].initialized = false;
-		}
-	}
-}
-
-static qsocket_t *_Datagram_CheckNewConnections (void)
-{
-	Addr clientaddr;
-	int			newsock;
-	qsocket_t	*sock;
-	qsocket_t	*s;
-	int			len;
-	int			command;
-	int			control;
-
-	memset(&clientaddr, 0, sizeof clientaddr);
-	if(getnewcon(&clientaddr) == 0)
-		return nil;
-	SZ_Clear(&net_message);
-	len = udpread(net_message.data, net_message.maxsize, &clientaddr);
-	if (len < sizeof(s32int))
-		goto done;
-	net_message.cursize = len;
-
-	MSG_BeginReading ();
-	control = BigLong(*((int *)net_message.data));
-	MSG_ReadLong();
-	if (control == -1)
-		goto done;
-	if ((control & (~NFMASK)) !=  NFCTL)
-		goto done;
-	if ((control & NFMASK) != len)
-		goto done;
-
-	command = MSG_ReadByte();
-	if (command == CQSVINFO)
-	{
-		if(strcmp(MSG_ReadString(), "QUAKE") != 0)
-			goto done;
-		SZ_Clear(&net_message);
-		// save space for the header, filled in later
-		MSG_WriteLong(&net_message, 0);
-		MSG_WriteByte(&net_message, CPSVINFO);
-		MSG_WriteString(&net_message, dfunc.AddrToString(&myip));
-		MSG_WriteString(&net_message, hostname.string);
-		MSG_WriteString(&net_message, sv.name);
-		MSG_WriteByte(&net_message, net_activeconnections);
-		MSG_WriteByte(&net_message, svs.maxclients);
-		MSG_WriteByte(&net_message, NETVERSION);
-		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
-		SZ_Clear(&net_message);
-		goto done;
-	}
-
-	if (command == CQPLINFO)
-	{
-		int			playerNumber;
-		int			activeNumber;
-		int			clientNumber;
-		client_t	*client;
-
-		playerNumber = MSG_ReadByte();
-		activeNumber = -1;
-		for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++)
-		{
-			if (client->active)
-			{
-				activeNumber++;
-				if (activeNumber == playerNumber)
-					break;
-			}
-		}
-		if (clientNumber == svs.maxclients)
-			goto done;
-
-		SZ_Clear(&net_message);
-		// save space for the header, filled in later
-		MSG_WriteLong(&net_message, 0);
-		MSG_WriteByte(&net_message, CPPLINFO);
-		MSG_WriteByte(&net_message, playerNumber);
-		MSG_WriteString(&net_message, client->name);
-		MSG_WriteLong(&net_message, client->colors);
-		MSG_WriteLong(&net_message, (int)client->edict->v.frags);
-		MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime));
-		MSG_WriteString(&net_message, client->netconnection->address);
-		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
-		SZ_Clear(&net_message);
-
-		goto done;
-	}
-
-	if (command == CQRUINFO)
-	{
-		char	*prevCvarName;
-		cvar_t	*var;
-
-		// find the search start location
-		prevCvarName = MSG_ReadString();
-		if (*prevCvarName)
-		{
-			var = Cvar_FindVar (prevCvarName);
-			if (!var)
-				goto done;
-			var = var->next;
-		}
-		else
-			var = cvar_vars;
-
-		// search for the next server cvar
-		while (var)
-		{
-			if (var->server)
-				break;
-			var = var->next;
-		}
-
-		// send the response
-
-		SZ_Clear(&net_message);
-		// save space for the header, filled in later
-		MSG_WriteLong(&net_message, 0);
-		MSG_WriteByte(&net_message, CPRUINFO);
-		if (var)
-		{
-			MSG_WriteString(&net_message, var->name);
-			MSG_WriteString(&net_message, var->string);
-		}
-		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
-		SZ_Clear(&net_message);
-
-		goto done;
-	}
-
-	if (command != CQCONNECT)
-		goto done;
-
-	if(strcmp(MSG_ReadString(), "QUAKE") != 0)
-		goto done;
-
-	if (MSG_ReadByte() != NETVERSION)
-	{
-		SZ_Clear(&net_message);
-		// save space for the header, filled in later
-		MSG_WriteLong(&net_message, 0);
-		MSG_WriteByte(&net_message, CPREJECT);
-		MSG_WriteString(&net_message, "Incompatible version.\n");
-		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
-		SZ_Clear(&net_message);
-		goto done;
-	}
-
-	// see if this guy is already connected
-	for (s = net_activeSockets; s; s = s->next)
-	{
-		if (s->driver != net_driverlevel)
-			continue;
-		if(strcmp(clientaddr.ip, s->addr.ip) != 0 || strcmp(clientaddr.srv, s->addr.srv) != 0)
-			continue;
-		// is this a duplicate connection reqeust?
-		if(strcmp(clientaddr.srv, s->addr.srv) == 0
-		&& net_time - s->connecttime < 2.0)
-		{
-			// yes, so send a duplicate reply
-			SZ_Clear(&net_message);
-			// save space for the header, filled in later
-			MSG_WriteLong(&net_message, 0);
-			MSG_WriteByte(&net_message, CPACCEPT);
-			MSG_WriteLong(&net_message, dfunc.GetSocketPort(&myip));
-			*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-			dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
-			SZ_Clear(&net_message);
-			goto done;
-		}
-		// it's somebody coming back in from a crash/disconnect
-		// so close the old qsocket and let their retry get them back in
-		NET_Close(s);
-		goto done;
-	}
-
-	// allocate a QSocket
-	sock = NET_NewQSocket ();
-	if (sock == nil)
-	{
-		// no room; try to let him know
-		SZ_Clear(&net_message);
-		// save space for the header, filled in later
-		MSG_WriteLong(&net_message, 0);
-		MSG_WriteByte(&net_message, CPREJECT);
-		MSG_WriteString(&net_message, "Server is full.\n");
-		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
-		SZ_Clear(&net_message);
-		goto done;
-	}
-
-	// allocate a network socket
-	newsock = 1;
-
-	// everything is allocated, just fill in the details
-	sock->socket = newsock;
-	sock->landriver = net_landriverlevel;
-	memcpy(&sock->addr, &clientaddr, sizeof clientaddr);
-	strcpy(sock->address, UDP_AddrToString(&clientaddr));
-
-	// send him back the info about the server connection he has been allocated
-	SZ_Clear(&net_message);
-	// save space for the header, filled in later
-	MSG_WriteLong(&net_message, 0);
-	MSG_WriteByte(&net_message, CPACCEPT);
-	MSG_WriteLong(&net_message, dfunc.GetSocketPort(&myip));
-	*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-	dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
-	SZ_Clear(&net_message);
-
-	return sock;
-done:
-	close(clientaddr.fd);
-	return nil;
-}
-
-qsocket_t *Datagram_CheckNewConnections (void)
-{
-	qsocket_t *ret = nil;
-
-	for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
-		if (landrv[net_landriverlevel].initialized)
-			if ((ret = _Datagram_CheckNewConnections ()) != nil)
-				break;
-	return ret;
-}
-
-static qsocket_t *_Datagram_Connect (char *host)
-{
-	Addr sendaddr;
-	Addr readaddr;
-	qsocket_t	*sock;
-	int			ret = 0;
-	int			reps;
-	double		start_time;
-	int			control;
-	char		*reason;
-
-	memset(&sendaddr, 0, sizeof sendaddr);
-	memset(&readaddr, 0, sizeof readaddr);
-
-	// see if we can resolve the host name
-	if (dfunc.getip(host, &sendaddr) == -1){
-		return nil;
-	}
-
-	sock = NET_NewQSocket ();
-	if (sock == nil)
-		goto ErrorReturn2;
-	sock->socket = 1;
-	sock->landriver = net_landriverlevel;
-
-	// connect to the host
-	if (dfunc.Connect(&sendaddr) == -1)
-		goto ErrorReturn;
-	memcpy(&readaddr, &sendaddr, sizeof readaddr);
-
-	// send the connection request
-	Con_Printf("trying...\n"); SCR_UpdateScreen (false);
-	start_time = net_time;
-
-	UDP_Listen(1);
-	for (reps = 0; reps < 3; reps++)
-	{
-		SZ_Clear(&net_message);
-		// save space for the header, filled in later
-		MSG_WriteLong(&net_message, 0);
-		MSG_WriteByte(&net_message, CQCONNECT);
-		MSG_WriteString(&net_message, "QUAKE");
-		MSG_WriteByte(&net_message, NETVERSION);
-		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
-		dfunc.Write(net_message.data, net_message.cursize, &sendaddr);
-		SZ_Clear(&net_message);
-		do
-		{
-			ret = dfunc.Read(net_message.data, net_message.maxsize, &readaddr);
-			// if we got something, validate it
-			if (ret > 0)
-			{
-				if (ret < sizeof(int))
-				{
-					ret = 0;
-					continue;
-				}
-
-				net_message.cursize = ret;
-				MSG_BeginReading ();
-
-				control = BigLong(*((int *)net_message.data));
-				MSG_ReadLong();
-				if (control == -1)
-				{
-					ret = 0;
-					continue;
-				}
-				if ((control & (~NFMASK)) !=  NFCTL)
-				{
-					ret = 0;
-					continue;
-				}
-				if ((control & NFMASK) != ret)
-				{
-					ret = 0;
-					continue;
-				}
-			}
-		}
-		while (ret == 0 && (SetNetTime() - start_time) < 2.5);
-		if (ret)
-			break;
-		Con_Printf("still trying...\n"); SCR_UpdateScreen (false);
-		start_time = SetNetTime();
-	}
-	/* bullshit workaround for non-plan9 servers replying from different
-	 * ports.  because of this workaround, multiple instances on the same
-	 * host all require different ports prior to connection.  if someone
-	 * has a better solution, i'm all ears. */
-	start_time = SetNetTime();
-	do{
-		if(getnewcon(&sendaddr) > 0){
-			close(readaddr.fd);
-			memcpy(&readaddr, &sendaddr, sizeof readaddr);
-			break;
-		}
-		sleep(1);
-	}while(SetNetTime() - start_time < 2.5);
-	UDP_Listen(0);
-
-	if (ret == 0)
-	{
-		reason = "No Response";
-		Con_Printf("%s\n", reason);
-		strcpy(m_return_reason, reason);
-		goto ErrorReturn;
-	}
-
-	if (ret == -1)
-	{
-		reason = "Network Error";
-		Con_Printf("%s\n", reason);
-		strcpy(m_return_reason, reason);
-		goto ErrorReturn;
-	}
-
-	ret = MSG_ReadByte();
-	if (ret == CPREJECT)
-	{
-		reason = MSG_ReadString();
-		Con_Printf(reason);
-		strncpy(m_return_reason, reason, 31);
-		goto ErrorReturn;
-	}
-
-	if (ret == CPACCEPT)
-	{
-		memcpy(&sock->addr, &readaddr, sizeof readaddr);
-		dfunc.SetSocketPort (&sock->addr, MSG_ReadLong());
-	}
-	else
-	{
-		reason = "Bad Response";
-		Con_Printf("%s\n", reason);
-		strcpy(m_return_reason, reason);
-		goto ErrorReturn;
-	}
-
-	strcpy(sock->address, dfunc.AddrToString(&sendaddr));
-
-	Con_Printf ("Connection accepted\n");
-	sock->lastMessageTime = SetNetTime();
-
-	m_return_onerror = false;
-	return sock;
-
-ErrorReturn:
-	close(readaddr.fd);
-	NET_FreeQSocket(sock);
-ErrorReturn2:
-	if (m_return_onerror)
-	{
-		key_dest = key_menu;
-		m_state = m_return_state;
-		m_return_onerror = false;
-	}
-	return nil;
-}
-
-qsocket_t *Datagram_Connect (char *host)
-{
-	qsocket_t *ret = nil;
-
-	for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
-		if (landrv[net_landriverlevel].initialized)
-			if ((ret = _Datagram_Connect (host)) != nil)
-				break;
-	return ret;
-}
-
-void
-Datagram_Close(qsocket_t *s)
-{
-	close(s->addr.fd);
-}
--- /dev/null
+++ b/net_dgrm_plan9.c
@@ -1,0 +1,766 @@
+#include "quakedef.h"
+#include <ip.h>
+
+//#define DEBUG
+
+// these two macros are to make the code more readable
+#define sfunc	landrv[sock->landriver]
+#define dfunc	landrv[net_landriverlevel]
+
+static int net_landriverlevel;
+
+/* statistic counters */
+static int packetsSent;
+static int packetsReSent;
+static int packetsReceived;
+static int receivedDuplicateCount;
+static int shortPacketCount;
+static int droppedDatagrams;
+
+static int myDriverLevel;
+
+static uchar netbuf[4+4+NET_MAXMESSAGE];
+
+extern int m_return_state;
+extern int m_state;
+extern bool m_return_onerror;
+extern char m_return_reason[32];
+
+static int
+netpack(Addr *a, int f, uint seq, uchar *buf, int n)
+{
+	hnputl(netbuf, NET_HEADERSIZE + n | f);
+	hnputl(netbuf+4, seq);
+	if(buf != nil)
+		memcpy(netbuf+8, buf, n);
+	return udpwrite(netbuf, NET_HEADERSIZE + n, a);
+}
+
+int Datagram_SendMessage (qsocket_t *s, sizebuf_t *data)
+{
+	unsigned int	n;
+	unsigned int	eom;
+
+#ifdef DEBUG
+	if (data->cursize == 0)
+		fatal("Datagram_SendMessage: zero length message\n");
+
+	if (data->cursize > NET_MAXMESSAGE)
+		fatal("Datagram_SendMessage: message too big %ud\n", data->cursize);
+
+	if (s->canSend == false)
+		fatal("SendMessage: called with canSend == false\n");
+#endif
+
+	memcpy(s->sendMessage, data->data, data->cursize);
+	s->sendMessageLength = data->cursize;
+	if(data->cursize <= MAX_DATAGRAM){
+		n = data->cursize;
+		eom = NFEOM;
+	}else{
+		n = MAX_DATAGRAM;
+		eom = 0;
+	}
+	s->canSend = false;
+	if(netpack(&s->addr, NFDAT|eom, s->sendSequence++, s->sendMessage, n) < 0)
+		return -1;
+	s->lastSendTime = net_time;
+	packetsSent++;
+	return 1;
+}
+
+
+int SendMessageNext (qsocket_t *s)
+{
+	unsigned int	n;
+	unsigned int	eom;
+
+	if(s->sendMessageLength <= MAX_DATAGRAM){
+		n = s->sendMessageLength;
+		eom = NFEOM;
+	}else{
+		n = MAX_DATAGRAM;
+		eom = 0;
+	}
+	s->sendNext = false;
+	if(netpack(&s->addr, NFDAT|eom, s->sendSequence++, s->sendMessage, n) < 0)
+		return -1;
+	s->lastSendTime = net_time;
+	packetsSent++;
+	return 1;
+}
+
+
+int ReSendMessage (qsocket_t *s)
+{
+	unsigned int	n;
+	unsigned int	eom;
+
+	if (s->sendMessageLength <= MAX_DATAGRAM)
+	{
+		n = s->sendMessageLength;
+		eom = NFEOM;
+	}
+	else
+	{
+		n = MAX_DATAGRAM;
+		eom = 0;
+	}
+	s->sendNext = false;
+	if(netpack(&s->addr, NFDAT|eom, s->sendSequence-1, s->sendMessage, n) < 0)
+		return -1;
+	s->lastSendTime = net_time;
+	packetsReSent++;
+	return 1;
+}
+
+
+bool Datagram_CanSendMessage (qsocket_t *sock)
+{
+	if (sock->sendNext)
+		SendMessageNext (sock);
+
+	return sock->canSend;
+}
+
+
+bool Datagram_CanSendUnreliableMessage (qsocket_t *)
+{
+	return true;
+}
+
+
+int Datagram_SendUnreliableMessage (qsocket_t *s, sizebuf_t *data)
+{
+#ifdef DEBUG
+	if (data->cursize == 0)
+		fatal("Datagram_SendUnreliableMessage: zero length message\n");
+
+	if (data->cursize > MAX_DATAGRAM)
+		fatal("Datagram_SendUnreliableMessage: message too big %ud\n", data->cursize);
+#endif
+
+	if(netpack(&s->addr, NFUNREL, s->unreliableSendSequence++, data->data, data->cursize) < 0)
+		return -1;
+	packetsSent++;
+	return 1;
+}
+
+
+int	Datagram_GetMessage (qsocket_t *sock)
+{
+	int n;
+	unsigned int	flags;
+	int				ret = 0;
+	unsigned int	sequence;
+	unsigned int	count;
+
+	if (!sock->canSend)
+		if ((net_time - sock->lastSendTime) > 1.0)
+			ReSendMessage (sock);
+	while(1)
+	{
+		if((n = udpread(netbuf, NET_MAXMESSAGE, &sock->addr)) == 0)
+			break;
+
+		if (n == -1)
+		{
+			Con_Printf("Read error\n");
+			return -1;
+		}
+
+		if (n < NET_HEADERSIZE)
+		{
+			shortPacketCount++;
+			continue;
+		}
+
+		n = nhgetl(netbuf);
+		flags = n & (~NFMASK);
+		n &= NFMASK;
+
+		if (flags & NFCTL)
+			continue;
+
+		sequence = nhgetl(netbuf+4);
+		packetsReceived++;
+
+		if (flags & NFUNREL)
+		{
+			if (sequence < sock->unreliableReceiveSequence)
+			{
+				Con_DPrintf("Got a stale datagram\n");
+				ret = 0;
+				break;
+			}
+			if (sequence != sock->unreliableReceiveSequence)
+			{
+				count = sequence - sock->unreliableReceiveSequence;
+				droppedDatagrams += count;
+				Con_DPrintf("Dropped %ud datagram(s)\n", count);
+			}
+			sock->unreliableReceiveSequence = sequence + 1;
+
+			n -= NET_HEADERSIZE;
+
+			SZ_Clear (&net_message);
+			SZ_Write (&net_message, netbuf+8, n);
+
+			ret = 2;
+			break;
+		}
+
+		if (flags & NFACK)
+		{
+			if (sequence != (sock->sendSequence - 1))
+			{
+				Con_DPrintf("Stale ACK received\n");
+				continue;
+			}
+			if (sequence == sock->ackSequence)
+			{
+				sock->ackSequence++;
+				if (sock->ackSequence != sock->sendSequence)
+					Con_DPrintf("ack sequencing error\n");
+			}
+			else
+			{
+				Con_DPrintf("Duplicate ACK received\n");
+				continue;
+			}
+			sock->sendMessageLength -= MAX_DATAGRAM;
+			if (sock->sendMessageLength > 0)
+			{
+				memcpy(sock->sendMessage, sock->sendMessage+MAX_DATAGRAM, sock->sendMessageLength);
+				sock->sendNext = true;
+			}
+			else
+			{
+				sock->sendMessageLength = 0;
+				sock->canSend = true;
+			}
+			continue;
+		}
+
+		if (flags & NFDAT)
+		{
+			netpack(&sock->addr, NFACK, sequence, nil, 0);
+			if (sequence != sock->receiveSequence)
+			{
+				receivedDuplicateCount++;
+				continue;
+			}
+			sock->receiveSequence++;
+
+			n -= NET_HEADERSIZE;
+
+			if (flags & NFEOM)
+			{
+				SZ_Clear(&net_message);
+				SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength);
+				SZ_Write(&net_message, netbuf+8, n);
+				sock->receiveMessageLength = 0;
+
+				ret = 1;
+				break;
+			}
+
+			memcpy(sock->receiveMessage + sock->receiveMessageLength, netbuf+8, n);
+			sock->receiveMessageLength += n;
+			continue;
+		}
+	}
+
+	if (sock->sendNext)
+		SendMessageNext (sock);
+
+	return ret;
+}
+
+
+void PrintStats(qsocket_t *s)
+{
+	Con_Printf("canSend = %4d   \n", s->canSend);
+	Con_Printf("sendSeq = %4d   ", s->sendSequence);
+	Con_Printf("recvSeq = %4d   \n", s->receiveSequence);
+	Con_Printf("\n");
+}
+
+void NET_Stats_f (void)
+{
+	qsocket_t	*s;
+
+	if (Cmd_Argc () == 1)
+	{
+		Con_Printf("unreliable messages sent   = %d\n", unreliableMessagesSent);
+		Con_Printf("unreliable messages recv   = %d\n", unreliableMessagesReceived);
+		Con_Printf("reliable messages sent     = %d\n", messagesSent);
+		Con_Printf("reliable messages received = %d\n", messagesReceived);
+		Con_Printf("packetsSent                = %d\n", packetsSent);
+		Con_Printf("packetsReSent              = %d\n", packetsReSent);
+		Con_Printf("packetsReceived            = %d\n", packetsReceived);
+		Con_Printf("receivedDuplicateCount     = %d\n", receivedDuplicateCount);
+		Con_Printf("shortPacketCount           = %d\n", shortPacketCount);
+		Con_Printf("droppedDatagrams           = %d\n", droppedDatagrams);
+	}
+	else if(strcmp(Cmd_Argv(1), "*") == 0)
+	{
+		for (s = net_activeSockets; s; s = s->next)
+			PrintStats(s);
+		for (s = net_freeSockets; s; s = s->next)
+			PrintStats(s);
+	}
+	else
+	{
+		for (s = net_activeSockets; s; s = s->next)
+			if(cistrcmp(Cmd_Argv(1), s->address) == 0)
+				break;
+		if (s == nil)
+			for (s = net_freeSockets; s; s = s->next)
+				if(cistrcmp(Cmd_Argv(1), s->address) == 0)
+					break;
+		if (s == nil)
+			return;
+		PrintStats(s);
+	}
+}
+
+int
+Datagram_Init(void)
+{
+	int i;
+
+	myDriverLevel = net_driverlevel;
+	Cmd_AddCommand("net_stats", NET_Stats_f);
+
+	for(i=0; i<net_numlandrivers; i++){
+		if(landrv[i].Init() < 0)
+			continue;
+		landrv[i].initialized = true;
+	}
+
+	return 0;
+}
+
+
+void Datagram_Shutdown (void)
+{
+	int i;
+
+	// shutdown the lan drivers
+	for (i = 0; i < net_numlandrivers; i++)
+	{
+		if (landrv[i].initialized)
+		{
+			landrv[i].Shutdown ();
+			landrv[i].initialized = false;
+		}
+	}
+}
+
+static qsocket_t *_Datagram_CheckNewConnections (void)
+{
+	Addr clientaddr;
+	int			newsock;
+	qsocket_t	*sock;
+	qsocket_t	*s;
+	int			len;
+	int			command;
+	int			control;
+
+	memset(&clientaddr, 0, sizeof clientaddr);
+	if(getnewcon(&clientaddr) == 0)
+		return nil;
+	SZ_Clear(&net_message);
+	len = udpread(net_message.data, net_message.maxsize, &clientaddr);
+	if (len < sizeof(s32int))
+		goto done;
+	net_message.cursize = len;
+
+	MSG_BeginReading ();
+	control = BigLong(*((int *)net_message.data));
+	MSG_ReadLong();
+	if (control == -1)
+		goto done;
+	if ((control & (~NFMASK)) !=  NFCTL)
+		goto done;
+	if ((control & NFMASK) != len)
+		goto done;
+
+	command = MSG_ReadByte();
+	if (command == CQSVINFO)
+	{
+		if(strcmp(MSG_ReadString(), "QUAKE") != 0)
+			goto done;
+		SZ_Clear(&net_message);
+		// save space for the header, filled in later
+		MSG_WriteLong(&net_message, 0);
+		MSG_WriteByte(&net_message, CPSVINFO);
+		MSG_WriteString(&net_message, dfunc.AddrToString(&myip));
+		MSG_WriteString(&net_message, hostname.string);
+		MSG_WriteString(&net_message, sv.name);
+		MSG_WriteByte(&net_message, net_activeconnections);
+		MSG_WriteByte(&net_message, svs.maxclients);
+		MSG_WriteByte(&net_message, NETVERSION);
+		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
+		SZ_Clear(&net_message);
+		goto done;
+	}
+
+	if (command == CQPLINFO)
+	{
+		int			playerNumber;
+		int			activeNumber;
+		int			clientNumber;
+		client_t	*client;
+
+		playerNumber = MSG_ReadByte();
+		activeNumber = -1;
+		for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++)
+		{
+			if (client->active)
+			{
+				activeNumber++;
+				if (activeNumber == playerNumber)
+					break;
+			}
+		}
+		if (clientNumber == svs.maxclients)
+			goto done;
+
+		SZ_Clear(&net_message);
+		// save space for the header, filled in later
+		MSG_WriteLong(&net_message, 0);
+		MSG_WriteByte(&net_message, CPPLINFO);
+		MSG_WriteByte(&net_message, playerNumber);
+		MSG_WriteString(&net_message, client->name);
+		MSG_WriteLong(&net_message, client->colors);
+		MSG_WriteLong(&net_message, (int)client->edict->v.frags);
+		MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime));
+		MSG_WriteString(&net_message, client->netconnection->address);
+		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
+		SZ_Clear(&net_message);
+
+		goto done;
+	}
+
+	if (command == CQRUINFO)
+	{
+		char	*prevCvarName;
+		cvar_t	*var;
+
+		// find the search start location
+		prevCvarName = MSG_ReadString();
+		if (*prevCvarName)
+		{
+			var = Cvar_FindVar (prevCvarName);
+			if (!var)
+				goto done;
+			var = var->next;
+		}
+		else
+			var = cvar_vars;
+
+		// search for the next server cvar
+		while (var)
+		{
+			if (var->server)
+				break;
+			var = var->next;
+		}
+
+		// send the response
+
+		SZ_Clear(&net_message);
+		// save space for the header, filled in later
+		MSG_WriteLong(&net_message, 0);
+		MSG_WriteByte(&net_message, CPRUINFO);
+		if (var)
+		{
+			MSG_WriteString(&net_message, var->name);
+			MSG_WriteString(&net_message, var->string);
+		}
+		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
+		SZ_Clear(&net_message);
+
+		goto done;
+	}
+
+	if (command != CQCONNECT)
+		goto done;
+
+	if(strcmp(MSG_ReadString(), "QUAKE") != 0)
+		goto done;
+
+	if (MSG_ReadByte() != NETVERSION)
+	{
+		SZ_Clear(&net_message);
+		// save space for the header, filled in later
+		MSG_WriteLong(&net_message, 0);
+		MSG_WriteByte(&net_message, CPREJECT);
+		MSG_WriteString(&net_message, "Incompatible version.\n");
+		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
+		SZ_Clear(&net_message);
+		goto done;
+	}
+
+	// see if this guy is already connected
+	for (s = net_activeSockets; s; s = s->next)
+	{
+		if (s->driver != net_driverlevel)
+			continue;
+		if(strcmp(clientaddr.ip, s->addr.ip) != 0 || strcmp(clientaddr.srv, s->addr.srv) != 0)
+			continue;
+		// is this a duplicate connection reqeust?
+		if(strcmp(clientaddr.srv, s->addr.srv) == 0
+		&& net_time - s->connecttime < 2.0)
+		{
+			// yes, so send a duplicate reply
+			SZ_Clear(&net_message);
+			// save space for the header, filled in later
+			MSG_WriteLong(&net_message, 0);
+			MSG_WriteByte(&net_message, CPACCEPT);
+			MSG_WriteLong(&net_message, dfunc.GetSocketPort(&myip));
+			*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+			dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
+			SZ_Clear(&net_message);
+			goto done;
+		}
+		// it's somebody coming back in from a crash/disconnect
+		// so close the old qsocket and let their retry get them back in
+		NET_Close(s);
+		goto done;
+	}
+
+	// allocate a QSocket
+	sock = NET_NewQSocket ();
+	if (sock == nil)
+	{
+		// no room; try to let him know
+		SZ_Clear(&net_message);
+		// save space for the header, filled in later
+		MSG_WriteLong(&net_message, 0);
+		MSG_WriteByte(&net_message, CPREJECT);
+		MSG_WriteString(&net_message, "Server is full.\n");
+		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+		dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
+		SZ_Clear(&net_message);
+		goto done;
+	}
+
+	// allocate a network socket
+	newsock = 1;
+
+	// everything is allocated, just fill in the details
+	sock->socket = newsock;
+	sock->landriver = net_landriverlevel;
+	memcpy(&sock->addr, &clientaddr, sizeof clientaddr);
+	strcpy(sock->address, UDP_AddrToString(&clientaddr));
+
+	// send him back the info about the server connection he has been allocated
+	SZ_Clear(&net_message);
+	// save space for the header, filled in later
+	MSG_WriteLong(&net_message, 0);
+	MSG_WriteByte(&net_message, CPACCEPT);
+	MSG_WriteLong(&net_message, dfunc.GetSocketPort(&myip));
+	*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+	dfunc.Write(net_message.data, net_message.cursize, &clientaddr);
+	SZ_Clear(&net_message);
+
+	return sock;
+done:
+	close(clientaddr.fd);
+	return nil;
+}
+
+qsocket_t *Datagram_CheckNewConnections (void)
+{
+	qsocket_t *ret = nil;
+
+	for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
+		if (landrv[net_landriverlevel].initialized)
+			if ((ret = _Datagram_CheckNewConnections ()) != nil)
+				break;
+	return ret;
+}
+
+static qsocket_t *_Datagram_Connect (char *host)
+{
+	Addr sendaddr;
+	Addr readaddr;
+	qsocket_t	*sock;
+	int			ret = 0;
+	int			reps;
+	double		start_time;
+	int			control;
+	char		*reason;
+
+	memset(&sendaddr, 0, sizeof sendaddr);
+	memset(&readaddr, 0, sizeof readaddr);
+
+	// see if we can resolve the host name
+	if (dfunc.getip(host, &sendaddr) == -1){
+		return nil;
+	}
+
+	sock = NET_NewQSocket ();
+	if (sock == nil)
+		goto ErrorReturn2;
+	sock->socket = 1;
+	sock->landriver = net_landriverlevel;
+
+	// connect to the host
+	if (dfunc.Connect(&sendaddr) == -1)
+		goto ErrorReturn;
+	memcpy(&readaddr, &sendaddr, sizeof readaddr);
+
+	// send the connection request
+	Con_Printf("trying...\n"); SCR_UpdateScreen (false);
+	start_time = net_time;
+
+	UDP_Listen(1);
+	for (reps = 0; reps < 3; reps++)
+	{
+		SZ_Clear(&net_message);
+		// save space for the header, filled in later
+		MSG_WriteLong(&net_message, 0);
+		MSG_WriteByte(&net_message, CQCONNECT);
+		MSG_WriteString(&net_message, "QUAKE");
+		MSG_WriteByte(&net_message, NETVERSION);
+		*((int *)net_message.data) = BigLong(NFCTL | (net_message.cursize & NFMASK));
+		dfunc.Write(net_message.data, net_message.cursize, &sendaddr);
+		SZ_Clear(&net_message);
+		do
+		{
+			ret = dfunc.Read(net_message.data, net_message.maxsize, &readaddr);
+			// if we got something, validate it
+			if (ret > 0)
+			{
+				if (ret < sizeof(int))
+				{
+					ret = 0;
+					continue;
+				}
+
+				net_message.cursize = ret;
+				MSG_BeginReading ();
+
+				control = BigLong(*((int *)net_message.data));
+				MSG_ReadLong();
+				if (control == -1)
+				{
+					ret = 0;
+					continue;
+				}
+				if ((control & (~NFMASK)) !=  NFCTL)
+				{
+					ret = 0;
+					continue;
+				}
+				if ((control & NFMASK) != ret)
+				{
+					ret = 0;
+					continue;
+				}
+			}
+		}
+		while (ret == 0 && (SetNetTime() - start_time) < 2.5);
+		if (ret)
+			break;
+		Con_Printf("still trying...\n"); SCR_UpdateScreen (false);
+		start_time = SetNetTime();
+	}
+	/* bullshit workaround for non-plan9 servers replying from different
+	 * ports.  because of this workaround, multiple instances on the same
+	 * host all require different ports prior to connection.  if someone
+	 * has a better solution, i'm all ears. */
+	start_time = SetNetTime();
+	do{
+		if(getnewcon(&sendaddr) > 0){
+			close(readaddr.fd);
+			memcpy(&readaddr, &sendaddr, sizeof readaddr);
+			break;
+		}
+		sleep(1);
+	}while(SetNetTime() - start_time < 2.5);
+	UDP_Listen(0);
+
+	if (ret == 0)
+	{
+		reason = "No Response";
+		Con_Printf("%s\n", reason);
+		strcpy(m_return_reason, reason);
+		goto ErrorReturn;
+	}
+
+	if (ret == -1)
+	{
+		reason = "Network Error";
+		Con_Printf("%s\n", reason);
+		strcpy(m_return_reason, reason);
+		goto ErrorReturn;
+	}
+
+	ret = MSG_ReadByte();
+	if (ret == CPREJECT)
+	{
+		reason = MSG_ReadString();
+		Con_Printf(reason);
+		strncpy(m_return_reason, reason, 31);
+		goto ErrorReturn;
+	}
+
+	if (ret == CPACCEPT)
+	{
+		memcpy(&sock->addr, &readaddr, sizeof readaddr);
+		dfunc.SetSocketPort (&sock->addr, MSG_ReadLong());
+	}
+	else
+	{
+		reason = "Bad Response";
+		Con_Printf("%s\n", reason);
+		strcpy(m_return_reason, reason);
+		goto ErrorReturn;
+	}
+
+	strcpy(sock->address, dfunc.AddrToString(&sendaddr));
+
+	Con_Printf ("Connection accepted\n");
+	sock->lastMessageTime = SetNetTime();
+
+	m_return_onerror = false;
+	return sock;
+
+ErrorReturn:
+	close(readaddr.fd);
+	NET_FreeQSocket(sock);
+ErrorReturn2:
+	if (m_return_onerror)
+	{
+		key_dest = key_menu;
+		m_state = m_return_state;
+		m_return_onerror = false;
+	}
+	return nil;
+}
+
+qsocket_t *Datagram_Connect (char *host)
+{
+	qsocket_t *ret = nil;
+
+	for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
+		if (landrv[net_landriverlevel].initialized)
+			if ((ret = _Datagram_Connect (host)) != nil)
+				break;
+	return ret;
+}
+
+void
+Datagram_Close(qsocket_t *s)
+{
+	close(s->addr.fd);
+}
--- a/net_udp.c
+++ /dev/null
@@ -1,230 +1,0 @@
-#include "quakedef.h"
-#include <ip.h>
-#include <thread.h>
-
-Addr myip;
-
-static int lpid = -1, afd;
-static Channel *lchan;
-
-int
-UDP_Init(void)
-{
-	char *s;
-	uchar ip[IPaddrlen];
-
-	fmtinstall('I', eipfmt);
-	if(strcmp(hostname.string, "UNNAMED") == 0)
-		if((s = getenv("sysname")) != nil){
-			setcvar("hostname", s);
-			free(s);
-		}
-	myipaddr(ip, nil);
-	snprint(myip.ip, sizeof myip.ip, "%I", ip);
-	snprint(myip.srv, sizeof myip.srv, "%hud", Udpport);
-	return 0;
-}
-
-void
-UDP_Shutdown(void)
-{
-	UDP_Listen(false);
-}
-
-static void
-lproc(void *)
-{
-	int fd, lfd;
-	char adir[40], ldir[40], data[100];
-
-	snprint(data, sizeof data, "%s/udp!*!%s", netmtpt, myip.srv);
-	if((afd = announce(data, adir)) < 0)
-		sysfatal("announce: %r");
-	for(;;){
-		if((lfd = listen(adir, ldir)) < 0
-			|| (fd = accept(lfd, ldir)) < 0
-			|| close(lfd) < 0
-			|| send(lchan, &fd) < 0)
-			break;
-	}
-}
-
-static void
-udpname(void)
-{
-	if((lchan = chancreate(sizeof(int), 0)) == nil)
-		sysfatal("chancreate: %r");
-	if((lpid = proccreate(lproc, nil, 8192)) < 0)
-		sysfatal("proccreate lproc: %r");
-}
-
-void
-UDP_Listen(bool on)
-{
-	if(lpid < 0){
-		if(on)
-			udpname();
-		return;
-	}
-	if(on)
-		return;
-	close(afd);
-	chanclose(lchan);
-	threadint(lpid);
-	chanfree(lchan);
-	lpid = -1;
-}
-
-void
-udpinfo(Addr *a)
-{
-	NetConnInfo *nc;
-
-	if((nc = getnetconninfo(nil, a->fd)) == nil){
-		Con_DPrintf("getnetconninfo: %r\n");
-		return;
-	}
-	strncpy(a->ip, nc->raddr, sizeof(a->ip)-1);
-	strncpy(a->srv, nc->rserv, sizeof(a->srv)-1);
-	strncpy(a->sys, nc->rsys, sizeof(a->sys)-1);
-	free(nc);
-}
-
-int
-UDP_Connect(Addr *a)
-{
-	if((a->fd = dial(netmkaddr(a->ip, "udp", a->srv), myip.srv, nil, nil)) < 0){
-		Con_DPrintf("dial: %r\n");
-		return -1;
-	}
-	return 0;
-}
-
-int
-getnewcon(Addr *a)
-{
-	if(lpid < 0)
-		return 0;
-	if(nbrecv(lchan, &a->fd) == 0)
-		return 0;
-	udpinfo(a);
-	return 1;
-}
-
-int
-udpread(byte *buf, int len, Addr *a)
-{
-	int n;
-
-	if(flen(a->fd) < 1)
-		return 0;
-	if((n = read(a->fd, buf, len)) <= 0){
-		Con_DPrintf("udpread: %r\n");
-		return -1;
-	}
-	return n;
-}
-
-int
-udpwrite(uchar *buf, int len, Addr *a)
-{
-	if(write(a->fd, buf, len) != len){
-		Con_DPrintf("udpwrite: %r\n");
-		return -1;
-	}
-	return len;
-}
-
-char *
-UDP_AddrToString(Addr *a)
-{
-	char *p;
-	static char buf[52];
-
-	strncpy(buf, a->sys, sizeof(buf)-1);
-	if((p = strrchr(buf, '!')) != nil)
-		*p = ':';
-	return buf;
-}
-
-int
-UDP_Broadcast(uchar *buf, int len)
-{
-	int fd;
-	char ip[46];
-
-	snprint(ip, sizeof ip, "%I", IPv4bcast);
-	if((fd = dial(netmkaddr(ip, "udp", myip.srv), myip.srv, nil, nil)) < 0){
-		Con_DPrintf("UDP_Broadcast: %r\n");
-		return -1;
-	}
-	if(write(fd, buf, len) != len)
-		Con_DPrintf("write: %r\n");
-	close(fd);
-	return 0;
-}
-
-int
-getip(char *s, Addr *a)
-{
-	int fd, n;
-	char buf[128], *f[4], *p;
-
-	snprint(buf, sizeof buf, "%s/cs", netmtpt);
-	if((fd = open(buf, ORDWR)) < 0)
-		sysfatal("open: %r");
-
-	if((p = strrchr(s, '!')) == nil)
-		p = myip.srv;
-	else
-		p++;
-	snprint(buf, sizeof buf, "udp!%s!%s", s, p);
-	n = strlen(buf);
-	if(write(fd, buf, n) != n){
-		Con_DPrintf("translating %s: %r\n", s);
-		return -1;
-	}
-	seek(fd, 0, 0);
-	if((n = read(fd, buf, sizeof(buf)-1)) <= 0){
-		Con_DPrintf("reading cs tables: %r");
-		return -1;
-	}
-	buf[n] = 0;
-	close(fd);
-	if(getfields(buf, f, 4, 0, " !") < 2)
-		goto err;
-	strncpy(a->ip, f[1], sizeof(a->ip)-1);
-	a->ip[sizeof(a->ip)-1] = 0;
-	strncpy(a->srv, f[2], sizeof(a->srv)-1);
-	a->srv[sizeof(a->srv)-1] = 0;
-	snprint(a->sys, sizeof a->sys, "%s!%s", a->ip, a->srv);
-	return 0;
-err:
-	Con_DPrintf("bad cs entry %s", buf);
-	return -1;
-}
-
-int
-UDP_AddrCompare(Addr *a1, Addr *a2)
-{
-	if(strcmp(a1->ip, a2->ip) != 0)
-		return -1;
-	if(strcmp(a1->srv, a2->srv) != 0)
-		return 1;
-	return 0;
-}
-
-ushort
-UDP_GetSocketPort(Addr *a)
-{
-	ushort p;
-
-	p = atoi(a->srv);
-	return p;
-}
-
-void
-UDP_SetSocketPort(Addr *a, ushort port)
-{
-	snprint(a->srv, sizeof a->srv, "%hud", port);	/* was htons'ed */
-}
--- /dev/null
+++ b/net_udp_plan9.c
@@ -1,0 +1,230 @@
+#include "quakedef.h"
+#include <ip.h>
+#include <thread.h>
+
+Addr myip;
+
+static int lpid = -1, afd;
+static Channel *lchan;
+
+int
+UDP_Init(void)
+{
+	char *s;
+	uchar ip[IPaddrlen];
+
+	fmtinstall('I', eipfmt);
+	if(strcmp(hostname.string, "UNNAMED") == 0)
+		if((s = getenv("sysname")) != nil){
+			setcvar("hostname", s);
+			free(s);
+		}
+	myipaddr(ip, nil);
+	snprint(myip.ip, sizeof myip.ip, "%I", ip);
+	snprint(myip.srv, sizeof myip.srv, "%hud", Udpport);
+	return 0;
+}
+
+void
+UDP_Shutdown(void)
+{
+	UDP_Listen(false);
+}
+
+static void
+lproc(void *)
+{
+	int fd, lfd;
+	char adir[40], ldir[40], data[100];
+
+	snprint(data, sizeof data, "%s/udp!*!%s", netmtpt, myip.srv);
+	if((afd = announce(data, adir)) < 0)
+		sysfatal("announce: %r");
+	for(;;){
+		if((lfd = listen(adir, ldir)) < 0
+			|| (fd = accept(lfd, ldir)) < 0
+			|| close(lfd) < 0
+			|| send(lchan, &fd) < 0)
+			break;
+	}
+}
+
+static void
+udpname(void)
+{
+	if((lchan = chancreate(sizeof(int), 0)) == nil)
+		sysfatal("chancreate: %r");
+	if((lpid = proccreate(lproc, nil, 8192)) < 0)
+		sysfatal("proccreate lproc: %r");
+}
+
+void
+UDP_Listen(bool on)
+{
+	if(lpid < 0){
+		if(on)
+			udpname();
+		return;
+	}
+	if(on)
+		return;
+	close(afd);
+	chanclose(lchan);
+	threadint(lpid);
+	chanfree(lchan);
+	lpid = -1;
+}
+
+void
+udpinfo(Addr *a)
+{
+	NetConnInfo *nc;
+
+	if((nc = getnetconninfo(nil, a->fd)) == nil){
+		Con_DPrintf("getnetconninfo: %r\n");
+		return;
+	}
+	strncpy(a->ip, nc->raddr, sizeof(a->ip)-1);
+	strncpy(a->srv, nc->rserv, sizeof(a->srv)-1);
+	strncpy(a->sys, nc->rsys, sizeof(a->sys)-1);
+	free(nc);
+}
+
+int
+UDP_Connect(Addr *a)
+{
+	if((a->fd = dial(netmkaddr(a->ip, "udp", a->srv), myip.srv, nil, nil)) < 0){
+		Con_DPrintf("dial: %r\n");
+		return -1;
+	}
+	return 0;
+}
+
+int
+getnewcon(Addr *a)
+{
+	if(lpid < 0)
+		return 0;
+	if(nbrecv(lchan, &a->fd) == 0)
+		return 0;
+	udpinfo(a);
+	return 1;
+}
+
+int
+udpread(byte *buf, int len, Addr *a)
+{
+	int n;
+
+	if(flen(a->fd) < 1)
+		return 0;
+	if((n = read(a->fd, buf, len)) <= 0){
+		Con_DPrintf("udpread: %r\n");
+		return -1;
+	}
+	return n;
+}
+
+int
+udpwrite(uchar *buf, int len, Addr *a)
+{
+	if(write(a->fd, buf, len) != len){
+		Con_DPrintf("udpwrite: %r\n");
+		return -1;
+	}
+	return len;
+}
+
+char *
+UDP_AddrToString(Addr *a)
+{
+	char *p;
+	static char buf[52];
+
+	strncpy(buf, a->sys, sizeof(buf)-1);
+	if((p = strrchr(buf, '!')) != nil)
+		*p = ':';
+	return buf;
+}
+
+int
+UDP_Broadcast(uchar *buf, int len)
+{
+	int fd;
+	char ip[46];
+
+	snprint(ip, sizeof ip, "%I", IPv4bcast);
+	if((fd = dial(netmkaddr(ip, "udp", myip.srv), myip.srv, nil, nil)) < 0){
+		Con_DPrintf("UDP_Broadcast: %r\n");
+		return -1;
+	}
+	if(write(fd, buf, len) != len)
+		Con_DPrintf("write: %r\n");
+	close(fd);
+	return 0;
+}
+
+int
+getip(char *s, Addr *a)
+{
+	int fd, n;
+	char buf[128], *f[4], *p;
+
+	snprint(buf, sizeof buf, "%s/cs", netmtpt);
+	if((fd = open(buf, ORDWR)) < 0)
+		sysfatal("open: %r");
+
+	if((p = strrchr(s, '!')) == nil)
+		p = myip.srv;
+	else
+		p++;
+	snprint(buf, sizeof buf, "udp!%s!%s", s, p);
+	n = strlen(buf);
+	if(write(fd, buf, n) != n){
+		Con_DPrintf("translating %s: %r\n", s);
+		return -1;
+	}
+	seek(fd, 0, 0);
+	if((n = read(fd, buf, sizeof(buf)-1)) <= 0){
+		Con_DPrintf("reading cs tables: %r");
+		return -1;
+	}
+	buf[n] = 0;
+	close(fd);
+	if(getfields(buf, f, 4, 0, " !") < 2)
+		goto err;
+	strncpy(a->ip, f[1], sizeof(a->ip)-1);
+	a->ip[sizeof(a->ip)-1] = 0;
+	strncpy(a->srv, f[2], sizeof(a->srv)-1);
+	a->srv[sizeof(a->srv)-1] = 0;
+	snprint(a->sys, sizeof a->sys, "%s!%s", a->ip, a->srv);
+	return 0;
+err:
+	Con_DPrintf("bad cs entry %s", buf);
+	return -1;
+}
+
+int
+UDP_AddrCompare(Addr *a1, Addr *a2)
+{
+	if(strcmp(a1->ip, a2->ip) != 0)
+		return -1;
+	if(strcmp(a1->srv, a2->srv) != 0)
+		return 1;
+	return 0;
+}
+
+ushort
+UDP_GetSocketPort(Addr *a)
+{
+	ushort p;
+
+	p = atoi(a->srv);
+	return p;
+}
+
+void
+UDP_SetSocketPort(Addr *a, ushort port)
+{
+	snprint(a->srv, sizeof a->srv, "%hud", port);	/* was htons'ed */
+}
--- /dev/null
+++ b/net_udp_unix.c
@@ -1,0 +1,361 @@
+#include "quakedef.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+extern cvar_t hostname;
+
+static int net_acceptsocket = -1; // socket for fielding new connections
+static int net_controlsocket;
+static int net_broadcastsocket = 0;
+static Addr broadcastaddr;
+
+static unsigned long myAddr;
+Addr myip;
+
+static int UDP_OpenSocket (int port);
+static int UDP_GetSocketAddr (int socket, Addr *addr);
+static int UDP_CloseSocket (int socket);
+
+//=============================================================================
+
+int UDP_Init (void)
+{
+	struct hostent *local;
+	char buff[MAXHOSTNAMELEN];
+
+	// determine my name & address
+	gethostname(buff, MAXHOSTNAMELEN);
+	local = gethostbyname(buff);
+	myAddr = *(int *)local->h_addr_list[0];
+
+	// if the quake hostname isn't set, set it to the machine name
+	if (strcmp(hostname.string, "UNNAMED") == 0)
+	{
+		buff[15] = 0;
+		setcvar ("hostname", buff);
+	}
+
+	if ((net_controlsocket = UDP_OpenSocket (0)) == -1)
+		Host_Error("UDP_Init: Unable to open control socket\n");
+
+	((struct sockaddr_in *)&broadcastaddr)->sin_family = AF_INET;
+	((struct sockaddr_in *)&broadcastaddr)->sin_addr.s_addr = INADDR_BROADCAST;
+	((struct sockaddr_in *)&broadcastaddr)->sin_port = htons(Udpport);
+
+	UDP_GetSocketAddr (net_controlsocket, &myip);
+
+	return net_controlsocket;
+}
+
+//=============================================================================
+
+void UDP_Shutdown (void)
+{
+	UDP_Listen (false);
+	UDP_CloseSocket (net_controlsocket);
+}
+
+//=============================================================================
+
+void UDP_Listen (bool state)
+{
+	// enable listening
+	if (state)
+	{
+		if (net_acceptsocket != -1)
+			return;
+		if ((net_acceptsocket = UDP_OpenSocket (Udpport)) == -1)
+			Host_Error ("UDP_Listen: Unable to open accept socket\n");
+		return;
+	}
+
+	// disable listening
+	if (net_acceptsocket == -1)
+		return;
+	UDP_CloseSocket (net_acceptsocket);
+	net_acceptsocket = -1;
+}
+
+//=============================================================================
+
+static int UDP_OpenSocket (int port)
+{
+	int newsocket;
+	struct sockaddr_in address;
+
+	if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
+		return -1;
+
+	address.sin_family = AF_INET;
+	address.sin_addr.s_addr = INADDR_ANY;
+	address.sin_port = htons(port);
+	if( bind (newsocket, (void *)&address, sizeof(address)) == -1)
+		goto ErrorReturn;
+
+	return newsocket;
+
+ErrorReturn:
+	close (newsocket);
+	return -1;
+}
+
+//=============================================================================
+
+static int UDP_CloseSocket (int socket)
+{
+	if (socket == net_broadcastsocket)
+		net_broadcastsocket = 0;
+	return close (socket);
+}
+
+//=============================================================================
+/*
+============
+PartialIPAddress
+
+this lets you type only as much of the net address as required, using
+the local network components to fill in the rest
+============
+*/
+static int PartialIPAddress (char *in, Addr *hostaddr)
+{
+	char buff[256];
+	char *b;
+	int addr;
+	int num;
+	int mask;
+	int run;
+	int port;
+
+	buff[0] = '.';
+	b = buff;
+	strcpy(buff+1, in);
+	if (buff[1] == '.')
+		b++;
+
+	addr = 0;
+	mask=-1;
+	while (*b == '.')
+	{
+		b++;
+		num = 0;
+		run = 0;
+		while (!( *b < '0' || *b > '9'))
+		{
+		  num = num*10 + *b++ - '0';
+		  if (++run > 3)
+		  	return -1;
+		}
+		if ((*b < '0' || *b > '9') && *b != '.' && *b != ':' && *b != 0)
+			return -1;
+		if (num < 0 || num > 255)
+			return -1;
+		mask<<=8;
+		addr = (addr<<8) + num;
+	}
+
+	if (*b++ == ':')
+		port = strtol(b, NULL, 0);
+	else
+		port = Udpport;
+
+	((struct sockaddr_in *)hostaddr)->sin_port = htons((short)port);
+	((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = (myAddr & htonl(mask)) | htonl(addr);
+
+	return 0;
+}
+//=============================================================================
+
+int UDP_Connect (Addr *addr)
+{
+	USED(addr);
+	return 0;
+}
+
+//=============================================================================
+
+int UDP_CheckNewConnections (void)
+{
+	unsigned long available;
+
+	if (net_acceptsocket == -1)
+		return -1;
+
+	if (ioctl (net_acceptsocket, FIONREAD, &available) == -1)
+		Host_Error ("UDP: ioctlsocket (FIONREAD) failed\n");
+	if (available)
+		return net_acceptsocket;
+	return -1;
+}
+
+//=============================================================================
+
+int UDP_Read (int socket, uint8_t *buf, int len, Addr *addr)
+{
+	socklen_t addrlen = sizeof (Addr);
+	int ret;
+
+	ret = recvfrom (socket, buf, len, 0, (struct sockaddr *)addr, &addrlen);
+	if (ret == -1 && (errno == EWOULDBLOCK || errno == ECONNREFUSED))
+		return 0;
+	return ret;
+}
+
+//=============================================================================
+
+int UDP_MakeSocketBroadcastCapable (int socket)
+{
+	int i = 1;
+
+	// make this socket broadcast capable
+	if (setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) < 0)
+		return -1;
+	net_broadcastsocket = socket;
+
+	return 0;
+}
+
+//=============================================================================
+
+static int UDP_Write (int socket, uint8_t *buf, int len, Addr *addr)
+{
+	int ret;
+
+	ret = sendto (socket, buf, len, 0, (struct sockaddr *)addr, sizeof(Addr));
+	if (ret == -1 && errno == EWOULDBLOCK)
+		return 0;
+	return ret;
+}
+
+//=============================================================================
+
+int UDP_Broadcast (int socket, uint8_t *buf, int len)
+{
+	int ret;
+
+	if (socket != net_broadcastsocket)
+	{
+		if (net_broadcastsocket != 0)
+			Host_Error("Attempted to use multiple broadcasts sockets\n");
+		ret = UDP_MakeSocketBroadcastCapable (socket);
+		if (ret == -1)
+		{
+			Con_Printf("Unable to make socket broadcast capable\n");
+			return ret;
+		}
+	}
+
+	return UDP_Write (socket, buf, len, &broadcastaddr);
+}
+
+//=============================================================================
+
+char *UDP_AddrToString (Addr *addr)
+{
+	static char buffer[22];
+	int haddr;
+
+	haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr);
+	sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, ntohs(((struct sockaddr_in *)addr)->sin_port));
+	return buffer;
+}
+
+//=============================================================================
+
+int UDP_StringToAddr (char *string, Addr *addr)
+{
+	int ha1, ha2, ha3, ha4, hp;
+	int ipaddr;
+
+	sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp);
+	ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4;
+
+	((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr);
+	((struct sockaddr_in *)addr)->sin_port = htons(hp);
+	return 0;
+}
+
+//=============================================================================
+
+static int UDP_GetSocketAddr (int socket, Addr *addr)
+{
+	socklen_t addrlen = sizeof(Addr);
+	unsigned int a;
+
+	memset(addr, 0, sizeof(Addr));
+	getsockname(socket, (struct sockaddr *)addr, &addrlen);
+	a = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
+	if (a == 0 || a == inet_addr("127.0.0.1"))
+		((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr;
+
+	return 0;
+}
+
+//=============================================================================
+
+int UDP_GetNameFromAddr (Addr *addr, char *name)
+{
+	struct hostent *hostentry;
+
+	hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, sizeof(struct in_addr), AF_INET);
+	if (hostentry)
+	{
+		strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1);
+		return 0;
+	}
+
+	strcpy (name, UDP_AddrToString (addr));
+	return 0;
+}
+
+//=============================================================================
+
+int UDP_GetAddrFromName(char *name, Addr *addr)
+{
+	struct hostent *hostentry;
+
+	if (name[0] >= '0' && name[0] <= '9')
+		return PartialIPAddress (name, addr);
+
+	hostentry = gethostbyname (name);
+	if (!hostentry)
+		return -1;
+
+	((struct sockaddr_in *)addr)->sin_port = htons(Udpport);
+	((struct sockaddr_in *)addr)->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0];
+
+	return 0;
+}
+
+//=============================================================================
+
+int UDP_AddrCompare (Addr *addr1, Addr *addr2)
+{
+	if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != ((struct sockaddr_in *)addr2)->sin_addr.s_addr)
+		return -1;
+
+	if (((struct sockaddr_in *)addr1)->sin_port != ((struct sockaddr_in *)addr2)->sin_port)
+		return 1;
+
+	return 0;
+}
+
+//=============================================================================
+
+u16int UDP_GetSocketPort (Addr *addr)
+{
+	return ntohs(((struct sockaddr_in *)addr)->sin_port);
+}
+
+void UDP_SetSocketPort (Addr *addr, u16int port)
+{
+	((struct sockaddr_in *)addr)->sin_port = htons(port);
+}
+
+//=============================================================================
--- a/qk1.c
+++ /dev/null
@@ -1,168 +1,0 @@
-#include "quakedef.h"
-#include <thread.h>
-
-int mainstacksize = 1*1024*1024;
-char *netmtpt = "/net";
-char *game;
-int debug;
-
-int
-sys_mkdir(char *path)
-{
-	int d;
-
-	if(access(path, AEXIST) == 0)
-		return 0;
-	if((d = create(path, OREAD, DMDIR|0777)) < 0){
-		Con_DPrintf("Sys_mkdir: create: %r\n");
-		return -1;
-	}
-	close(d);
-	return 0;
-}
-
-char *
-sys_timestamp(void)
-{
-	static char ts[32];
-	Tm *tm;
-	long t;
-
-	if((t = time(nil)) < 0 || (tm = localtime(t)) == nil)
-		return nil;
-	snprint(ts, sizeof(ts),
-		"%04d%02d%02d-%02d%02d%02d",
-		tm->year + 1900, tm->mon + 1, tm->mday, tm->hour, tm->min, tm->sec
-	);
-
-	return ts;
-}
-
-char *
-lerr(void)
-{
-	static char err[ERRMAX];
-	rerrstr(err, sizeof(err));
-	return err;
-}
-
-_Noreturn void
-fatal(char *fmt, ...)
-{
-	char s[1024];
-	va_list arg;
-
-	va_start(arg, fmt);
-	vseprint(s, s+sizeof s, fmt, arg);
-	va_end(arg);
-	Host_Shutdown();
-	sysfatal("%s", s);
-}
-
-void *
-emalloc(long n)
-{
-	void *p;
-
-	if(p = mallocz(n, 1), p == nil)
-		sysfatal("emalloc %r");
-	setmalloctag(p, getcallerpc(&n));
-	return p;
-}
-
-vlong
-flen(int fd)
-{
-	vlong l;
-	Dir *d;
-
-	if((d = dirfstat(fd)) == nil)	/* file assumed extant and readable */
-		sysfatal("flen: %r");
-	l = d->length;
-	free(d);
-	return l;
-}
-
-double
-dtime(void)
-{
-	return nanosec() / 1000000000.0;
-}
-
-void
-game_shutdown(void)
-{
-	stopfb();
-	Host_Shutdown();
-	threadexitsall(nil);
-}
-
-static void
-croak(void *, char *note)
-{
-	if(strncmp(note, "sys:", 4) == 0){
-		IN_Grabm(0);
-		threadkillgrp(0);
-	}
-	noted(NDFLT);
-}
-
-static void
-usage(void)
-{
-	fprint(2, "usage: %s [-d] [-g game] [-m kB] [-x netmtpt]\n", argv0);
-	exits("usage");
-}
-
-void
-threadmain(int argc, char **argv)
-{
-	double t, t´, Δt;
-	char *e;
-	static char *paths[] = {
-		"/sys/games/lib/quake",
-		nil,
-		nil,
-	};
-
-	ARGBEGIN{
-	case 'D':
-		debug = 1;
-		break;
-	case 'd':
-		dedicated = 1;
-		break;
-	case 'g':
-		game = EARGF(usage());
-		break;
-	case 'x':
-		netmtpt = EARGF(usage());
-		break;
-	default: usage();
-	}ARGEND
-	srand(getpid());
-	/* ignore fp exceptions: rendering shit assumes they are */
-	setfcr(getfcr() & ~(FPOVFL|FPUNFL|FPINVAL|FPZDIV));
-	notify(croak);
-
-	e = getenv("home");
-	paths[1] = smprint("%s/lib/quake", e);
-	free(e);
-	Host_Init(argc, argv, paths);
-
-	t = dtime() - 1.0 / Fpsmax;
-	for(;;){
-		t´ = dtime();
-		Δt = t´ - t;
-		if(cls.state == ca_dedicated){
-			if(Δt < sys_ticrate.value)
-				continue;
-			Δt = sys_ticrate.value;
-        	}
-		if(Δt > sys_ticrate.value * 2)
-			t = t´;
-		else
-			t += Δt;
-		Host_Frame(Δt);
-	}
-}
--- /dev/null
+++ b/seprint.c
@@ -1,0 +1,19 @@
+#include <stdio.h>
+#include <stdarg.h>
+
+char *
+seprint(char *buf, char *e, char *fmt, ...)
+{
+	va_list a;
+	int n, m;
+
+	if(e <= buf)
+		return e;
+
+	va_start(a, fmt);
+	m = e-buf-1;
+	n = vsnprintf(buf, m, fmt, a);
+	va_end(a);
+
+	return buf + (n < m ? n : m);
+}
--- a/snd.c
+++ /dev/null
@@ -1,689 +1,0 @@
-#include "quakedef.h"
-
-cvar_t volume = {"volume", "0.7", 1};
-
-typedef struct Chan Chan;
-
-enum{
-	Srate = 44100,
-	Ssize = 2,
-	Sch = 2,
-	Sblk = Ssize * Sch,
-	Ssamp = Srate / Fpsmin,
-
-	Nchan = 256,
-	Ndyn = 8,
-	Sstat = Ndyn + Namb
-};
-static float Clipdist = 1000.0;
-
-struct Chan{
-	Sfx *sfx;
-	int chvol;
-	int lvol;
-	int rvol;
-	vec_t attf;
-	vec3_t zp;
-	int entn;
-	int entch;
-	int p;
-	int n;
-};
-
-static Chan *chans, *che;
-
-static int ainit, mixbufi;
-static uchar mixbufs[2][Ssamp*Sblk], *mixbuf;
-static vlong sndt, sampt;
-static int nsamp;
-static int sampbuf[Ssamp*2];
-static int scalt[32][256];
-
-static Sfx *ambsfx[Namb];
-
-typedef struct
-{
-	int 	length;
-	int 	speed;
-	int 	width;
-	int 	stereo;
-	int		loop;
-	byte	data[1];		// variable sized
-} sfxcache_t;
-
-static vec3_t		listener_origin;
-static vec3_t		listener_forward;
-static vec3_t		listener_right;
-static vec3_t		listener_up;
-static Sfx			*known_sfx;		// hunk allocated
-static int			num_sfx;
-static int			map;
-
-static cvar_t precache = {"precache", "1"};
-static cvar_t loadas8bit = {"loadas8bit", "0"};
-static cvar_t ambient_level = {"ambient_level", "0.3"};
-static cvar_t ambient_fade = {"ambient_fade", "100"};
-
-static byte	*data_p;
-static byte 	*iff_end;
-static byte 	*last_chunk;
-static byte 	*iff_data;
-
-/* TODO: refuctor wav loading */
-static void
-resample(sfxcache_t *sc, byte *data, float stepscale)
-{
-	int inwidth;
-	int		outcount;
-	int		srcsample;
-	int		i;
-	int		sample, samplefrac, fracstep;
-
-	inwidth = sc->width;
-	outcount = sc->length / stepscale;
-	sc->length = outcount;
-	sc->speed = Srate;
-	if (loadas8bit.value)
-		sc->width = 1;
-	else
-		sc->width = inwidth;
-	sc->stereo = 0;
-
-	if (stepscale == 1 && inwidth == 1 && sc->width == 1)
-	{
-// fast special case
-		for (i=0 ; i<outcount ; i++)
-			((signed char *)sc->data)[i]
-			= (int)( (unsigned char)(data[i]) - 128);
-	}
-	else
-	{
-// general case
-		samplefrac = 0;
-		fracstep = stepscale*256;
-		for (i=0 ; i<outcount ; i++)
-		{
-			srcsample = samplefrac >> 8;
-			samplefrac += fracstep;
-			if (inwidth == 2)
-				sample = LittleShort ( ((short *)data)[srcsample] );
-			else
-				sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
-			if (sc->width == 2)
-				((short *)sc->data)[i] = sample;
-			else
-				((signed char *)sc->data)[i] = sample >> 8;
-		}
-	}
-}
-
-static sfxcache_t *
-loadsfx(Sfx *sfx)
-{
-	wavinfo_t info;
-	int len;
-	float stepscale;
-	sfxcache_t *sc;
-	uchar *u, buf[1024];	/* avoid dirtying the cache heap */
-
-	if(sc = Cache_Check(&sfx->cu), sc != nil)
-		return sc;
-	u = loadstklmp(va("sound/%s", sfx->s), buf, sizeof buf, &len);
-	if(u == nil){
-		Con_DPrintf("loadsfx: %r\n");
-		return nil;
-	}
-	if(wavinfo(u, len, &info) != 0){
-		Con_Printf("loadsfx: %s: %s\n", sfx->s, lerr());
-		return nil;
-	}
-	if(info.channels != 1){
-		Con_DPrintf("loadsfx: non mono wave %s\n", sfx->s);
-		return nil;
-	}
-	stepscale = (float)info.rate / Srate;
-	len = info.samples / stepscale;
-	len *= info.width * info.channels;
-	if(sc = Cache_Alloc(&sfx->cu, len + sizeof *sc), sc == nil)
-		return nil;
-	sc->length = info.samples;
-	sc->loop = info.loopofs;
-	if(info.loopofs >= 0)
-		sc->loop /= stepscale;
-	sc->speed = info.rate;
-	sc->width = info.width;
-	sc->stereo = info.channels;
-	resample(sc, u + info.dataofs, stepscale);
-	return sc;
-}
-
-static void
-sndout(void)
-{
-	int v, *pb, *pe;
-	uchar *p;
-	double vol;
-
-	vol = volume.value;
-	p = mixbuf;
-	pb = sampbuf;
-	pe = sampbuf + nsamp * 2;
-	while(pb < pe){
-		v = *pb++ * vol;
-		if(v > 0x7fff)
-			v = 0x7fff;
-		else if(v < -0x8000)
-			v = -0x8000;
-		p[0] = v;
-		p[1] = v >> 8;
-		p += 2;
-	}
-}
-
-static void
-sample8(Chan *c, void *d, int n)
-{
-	int v, *pb, *pe, *ls, *rs;
-	uchar *p;
-
-	if(c->lvol > 255)
-		c->lvol = 255;
-	if(c->rvol > 255)
-		c->rvol = 255;
-	ls = scalt[c->lvol >> 3];
-	rs = scalt[c->rvol >> 3];
-	p = (uchar *)d + c->p;
-	pb = sampbuf;
-	pe = sampbuf + n * 2;
-	while(pb < pe){
-		v = *p++;
-		*pb++ += ls[v];
-		*pb++ += rs[v];
-	}
-}
-
-static void
-sample16(Chan *c, void *d, int n)
-{
-	int v, *pb, *pe, lv, rv;
-	short *p;
-
-	lv = c->lvol;
-	rv = c->rvol;
-	p = (short *)d + c->p;
-	pb = sampbuf;
-	pe = sampbuf + n * 2;
-	while(pb < pe){
-		v = *p++;
-		*pb++ += v * lv >> 8;
-		*pb++ += v * rv >> 8;
-	}
-}
-
-static void
-samplesfx(void)
-{
-	int n, m;
-	Chan *c;
-	sfxcache_t *sc;
-	void (*sf)(Chan *, void *, int);
-
-	memset(sampbuf, 0, sizeof sampbuf);
-	for(c=chans; c<che; c++){
-		if(c->sfx == nil)
-			continue;
-		if(c->lvol == 0 && c->rvol == 0)
-			continue;
-		if(sc = loadsfx(c->sfx), sc == nil)
-			continue;
-		sf = sc->width == 1 ? sample8 : sample16;
-		n = nsamp;
-		while(n > 0){
-			m = n < c->n ? n : c->n;
-			if(m > 0)
-				sf(c, sc->data, m);
-			c->p += m;
-			c->n -= m;
-			n -= m;
-			if(c->n <= 0){
-				if(sc->loop >= 0){
-					c->p = sc->loop;
-					c->n = sc->length - c->p;
-				}else{
-					c->sfx = nil;
-					break;
-				}
-			}
-		}
-	}
-	sndout();
-}
-
-static void
-spatialize(Chan *c)
-{
-	vec_t Δr, m;
-	vec3_t src;
-
-	if(c->entn == cl.viewentity){
-		c->lvol = c->chvol;
-		c->rvol = c->chvol;
-		return;
-	}
-	VectorSubtract(c->zp, listener_origin, src);
-	Δr = 1.0 - VectorNormalize(src) * c->attf;
-	m = DotProduct(listener_right, src);
-	c->rvol = Δr * (1.0 + m) * c->chvol;
-	if(c->rvol < 0)
-		c->rvol = 0;
-	c->lvol = Δr * (1.0 - m) * c->chvol;
-	if(c->lvol < 0)
-		c->lvol = 0;
-}
-
-static void
-ambs(void)
-{
-	uchar *av;
-	float vol;
-	Chan *c, *e;
-	mleaf_t *l;
-	Sfx **sfx;
-
-	if(cl.worldmodel == nil)
-		return;
-	c = chans;
-	e = chans + Namb;
-	l = Mod_PointInLeaf(listener_origin, cl.worldmodel);
-	if(l == nil || !ambient_level.value){
-		while(c < e)
-			c++->sfx = nil;
-		return;
-	}
-	sfx = ambsfx;
-	av = l->ambient_sound_level;
-	while(c < e){
-		c->sfx = *sfx++;
-		vol = ambient_level.value * *av++;
-		if(vol < 8)
-			vol = 0;
-		if(c->chvol < vol){
-			c->chvol += host_frametime * ambient_fade.value;
-			if(c->chvol > vol)
-				c->chvol = vol;
-		}else if(c->chvol > vol){
-			c->chvol -= host_frametime * ambient_fade.value;
-			if(c->chvol < vol)
-				c->chvol = vol;
-		}
-		c->lvol = c->chvol;
-		c->rvol = c->chvol;
-		c++;
-	}
-}
-
-void
-stepsnd(vec3_t origin, vec3_t forward, vec3_t right, vec3_t up)
-{
-	long ns;
-	Chan *c, *sum;
-
-	if(!ainit)
-		return;
-
-	if(sndt == 0)
-		sndt = nanosec() - Te9 / Fpsmax;
-	nsamp = (nanosec() - sndt) / (Te9 / Srate);
-	if(!cls.timedemo)
-		nsamp = (nsamp + 15) & ~15;
-	nsamp -= sndqueued()/Sblk - Srate/Fpsmax;
-	if(nsamp < 1)
-		return;
-	if(nsamp > Ssamp)
-		nsamp = Ssamp;
-	ns = nsamp * Sblk;
-	mixbuf = mixbufs[mixbufi];
-	samplesfx();
-	sampt += nsamp;
-	if(ns != 0){
-		sndwrite(mixbuf, ns);
-		mixbufi = (mixbufi + 1) % nelem(mixbufs);
-	}
-	sndt = nanosec();
-
-	VectorCopy(origin, listener_origin);
-	VectorCopy(forward, listener_forward);
-	VectorCopy(right, listener_right);
-	VectorCopy(up, listener_up);
-	ambs();
-	sum = nil;
-	for(c=chans+Namb; c<che; c++){
-		if(c->sfx == nil)
-			continue;
-		spatialize(c);
-		if(c->lvol == 0 && c->rvol == 0)
-			continue;
-		/* sum static sounds to avoid useless remixing */
-		if(c >= chans + Sstat){
-			if(sum != nil && sum->sfx == c->sfx){
-				sum->lvol += c->lvol;
-				sum->rvol += c->rvol;
-				c->lvol = c->rvol = 0;
-				continue;
-			}
-			for(sum=chans + Sstat; sum<c; sum++)
-				if(sum->sfx == c->sfx)
-					break;
-			if(sum == che)
-				sum = nil;
-			else if(sum != c){
-				sum->lvol += c->lvol;
-				sum->rvol += c->rvol;
-				c->lvol = c->rvol = 0;
-			}
-		}
-	}
-}
-
-void
-stopallsfx(void)
-{
-	if(!ainit)
-		return;
-	memset(chans, 0, sizeof(*chans)*Nchan);
-	che = chans + Sstat;
-	sndstop();
-}
-
-void
-stopsfx(int n, int ch)
-{
-	Chan *c, *e;
-
-	if(!ainit)
-		return;
-	c = chans;
-	e = chans + Ndyn;
-	while(c < e){
-		if(c->entn == n && c->entch == ch){
-			c->sfx = nil;
-			return;
-		}
-		c++;
-	}
-}
-
-static Chan *
-pickchan(int entn, int entch)
-{
-	int Δt;
-	Chan *c, *p, *e;
-
-	p = nil;
-	Δt = 0x7fffffff;
-	for(c=chans+Namb, e=c+Ndyn; c<e; c++){
-		if(entch != 0 && c->entn == entn
-		&& (c->entch == entch || entch == -1)){
-			p = c;
-			break;
-		}
-		if(c->entn == cl.viewentity && entn != cl.viewentity && c->sfx != nil)
-			continue;
-		if(c->n < Δt){
-			Δt = c->n;
-			p = c;
-		}
-	}
-	if(p != nil)
-		p->sfx = nil;
-	return p;
-}
-
-void
-startsfx(int entn, int entch, Sfx *sfx, vec3_t zp, float vol, float att)
-{
-	int skip;
-	Chan *c, *c2, *e;
-	sfxcache_t *sc;
-
-	if(!ainit || sfx == nil)
-		return;
-	if(c = pickchan(entn, entch), c == nil)
-		return;
-	memset(c, 0, sizeof *c);
-	VectorCopy(zp, c->zp);
-	c->attf = att / Clipdist;
-	c->chvol = vol * 255;
-	c->entn = entn;
-	c->entch = entch;
-	spatialize(c);
-	if(c->lvol == 0 && c->rvol == 0)
-		return;
-	if(sc = loadsfx(sfx), sc == nil)
-		return;
-	c->sfx = sfx;
-	c->p = 0;
-	c->n = sc->length;
-	/* don't sum identical sfx started on the same frame */
-	for(c2=chans+Namb, e=chans+Sstat; c2<e; c2++){
-		if(c2 == c || c2->sfx != sfx || c2->p != 0)
-			continue;
-		skip = nrand(Srate / Fpsmin);
-		if(skip >= c->n)
-			skip = c->n - 1;
-		c->p += skip;
-		c->n -= skip;
-		break;
-	}
-}
-
-void
-localsfx(char *s)
-{
-	Sfx *sfx;
-
-	if(!ainit)
-		return;
-	sfx = precachesfx(s);
-	startsfx(cl.viewentity, -1, sfx, vec3_origin, 1, 1);
-}
-
-void
-staticsfx(Sfx *sfx, vec3_t zp, float vol, float att)
-{
-	Chan *c;
-	sfxcache_t *sc;
-
-	if(sfx == nil || che >= chans+Nchan)
-		return;
-	c = che++;
-	if(sc = loadsfx(sfx), sc == nil)
-		return;
-	if(sc->loop < 0){
-		Con_DPrintf("staticsfx %s: nonlooped static sound\n", sfx->s);
-		return;
-	}
-	c->sfx = sfx;
-	VectorCopy(zp, c->zp);
-	c->chvol = vol * 255;
-	c->attf = att / Clipdist;
-	c->n = sc->length;
-	spatialize(c);
-}
-
-static Sfx *
-findsfx(char *s)
-{
-	Sfx *sfx, *e;
-
-	if(strlen(s) >= Npath)
-		Host_Error("findsfx: path too long %s", s);
-	sfx = known_sfx;
-	e = known_sfx + num_sfx;
-	while(sfx < e){
-		if(strcmp(sfx->s, s) == 0)
-			return sfx;
-		sfx++;
-	}
-	if(num_sfx == MAX_SOUNDS){
-		sfx = known_sfx;
-		while(sfx < e){
-			if(sfx->map && sfx->map < map)
-				break;
-			sfx++;
-		}
-		if(sfx == e)
-			Host_Error("findsfx: sfx list overflow: %s", s);
-		if(Cache_Check(&sfx->cu))
-			Cache_Free(&sfx->cu);
-	}else
-		num_sfx++;
-	strcpy(sfx->s, s);
-	return sfx;
-}
-
-void
-touchsfx(char *s)
-{
-	Sfx *sfx;
-
-	if(!ainit)
-		return;
-	sfx = findsfx(s);
-	Cache_Check(&sfx->cu);
-}
-
-Sfx *
-precachesfx(char *s)
-{
-	Sfx *sfx;
-
-	if(!ainit)
-		return nil;
-	sfx = findsfx(s);
-	sfx->map = map;
-	if(precache.value)
-		loadsfx(sfx);
-	return sfx;
-}
-
-static void
-playsfx(void)
-{
-	static int hash = 345;
-	int i;
-	char *s;
-	Sfx *sfx;
-
-	if(Cmd_Argc() < 2){
-		Con_Printf("play wav [wav..]: play a wav lump\n");
-		return;
-	}
-	i = 1;
-	while(i < Cmd_Argc()){
-		if(strrchr(Cmd_Argv(i), '.') == nil)
-			s = va("%s.wav", Cmd_Argv(i));
-		else
-			s = Cmd_Argv(i);
-		sfx = precachesfx(s);
-		startsfx(hash++, 0, sfx, listener_origin, 1.0, 1.0);
-		i++;
-	}
-}
-
-static void
-playvolsfx(void)
-{
-	static int hash = 543;
-	int i;
-	float vol;
-	char *s;
-	Sfx *sfx;
-
-	if(Cmd_Argc() < 3){
-		Con_Printf("play wav vol [wav vol]..: play an amplified wav lump\n");
-		return;
-	}
-	i = 1;
-	while(i < Cmd_Argc()){
-		if(strrchr(Cmd_Argv(i), '.') == nil)
-			s = va("%s.wav", Cmd_Argv(i));
-		else
-			s = Cmd_Argv(i);
-		sfx = precachesfx(s);
-		vol = atof(Cmd_Argv(++i));
-		startsfx(hash++, 0, sfx, listener_origin, vol, 1.0);
-		i++;
-	}
-}
-
-static void
-sfxlist(void)
-{
-	char c;
-	int sz, sum;
-	Sfx *sfx, *e;
-	sfxcache_t *sc;
-
-	sum = 0;
-	for(sfx=known_sfx, e=known_sfx+num_sfx; sfx<e; sfx++){
-		if(sc = Cache_Check(&sfx->cu), sc == nil)
-			continue;
-		sz = sc->length * sc->width * (sc->stereo + 1);
-		sum += sz;
-		c = sc->loop >= 0 ? 'L' : ' ';
-		Con_Printf("%c(%2db) %6d : %s\n", c, sc->width * 8, sz, sfx->s);
-	}
-	Con_Printf("Total resident: %d\n", sum);
-}
-
-void
-sfxbegin(void)
-{
-	map++;
-}
-
-void
-sfxend(void)
-{
-	Sfx *sfx;
-	int i;
-
-	for(i = 0, sfx = known_sfx; i < num_sfx; i++, sfx++){
-		if(sfx->map >= map || sfx == ambsfx[Ambsky] || sfx == ambsfx[Ambwater])
-			continue;
-		if(Cache_Check(&sfx->cu) != nil)
-			Cache_Free(&sfx->cu);
-	}
-}
-
-int
-initsnd(void)
-{
-	int i, j, *p;
-
-	if(sndopen() != 0)
-		return -1;
-	ainit = 1;
-	for(p=scalt[1], i=8; i<8*nelem(scalt); i+=8)
-		for(j=0; j<256; j++)
-			*p++ = (char)j * i;
-	Cmd_AddCommand("play", playsfx);
-	Cmd_AddCommand("playvol", playvolsfx);
-	Cmd_AddCommand("stopsound", stopallsfx);
-	Cmd_AddCommand("soundlist", sfxlist);
-	Cvar_RegisterVariable(&volume);
-	Cvar_RegisterVariable(&precache);
-	Cvar_RegisterVariable(&loadas8bit);
-	Cvar_RegisterVariable(&ambient_level);
-	Cvar_RegisterVariable(&ambient_fade);
-
-	chans = calloc(Nchan, sizeof(*chans));
-	known_sfx = Hunk_Alloc(MAX_SOUNDS * sizeof *known_sfx);
-	num_sfx = 0;
-
-	ambsfx[Ambwater] = precachesfx("ambience/water1.wav");
-	ambsfx[Ambsky] = precachesfx("ambience/wind2.wav");
-	stopallsfx();
-	return 0;
-}
--- /dev/null
+++ b/snd_mix.c
@@ -1,0 +1,689 @@
+#include "quakedef.h"
+
+cvar_t volume = {"volume", "0.7", 1};
+
+typedef struct Chan Chan;
+
+enum{
+	Srate = 44100,
+	Ssize = 2,
+	Sch = 2,
+	Sblk = Ssize * Sch,
+	Ssamp = Srate / Fpsmin,
+
+	Nchan = 256,
+	Ndyn = 8,
+	Sstat = Ndyn + Namb
+};
+static float Clipdist = 1000.0;
+
+struct Chan{
+	Sfx *sfx;
+	int chvol;
+	int lvol;
+	int rvol;
+	vec_t attf;
+	vec3_t zp;
+	int entn;
+	int entch;
+	int p;
+	int n;
+};
+
+static Chan *chans, *che;
+
+static int ainit, mixbufi;
+static uchar mixbufs[2][Ssamp*Sblk], *mixbuf;
+static vlong sndt, sampt;
+static int nsamp;
+static int sampbuf[Ssamp*2];
+static int scalt[32][256];
+
+static Sfx *ambsfx[Namb];
+
+typedef struct
+{
+	int 	length;
+	int 	speed;
+	int 	width;
+	int 	stereo;
+	int		loop;
+	byte	data[1];		// variable sized
+} sfxcache_t;
+
+static vec3_t		listener_origin;
+static vec3_t		listener_forward;
+static vec3_t		listener_right;
+static vec3_t		listener_up;
+static Sfx			*known_sfx;		// hunk allocated
+static int			num_sfx;
+static int			map;
+
+static cvar_t precache = {"precache", "1"};
+static cvar_t loadas8bit = {"loadas8bit", "0"};
+static cvar_t ambient_level = {"ambient_level", "0.3"};
+static cvar_t ambient_fade = {"ambient_fade", "100"};
+
+static byte	*data_p;
+static byte 	*iff_end;
+static byte 	*last_chunk;
+static byte 	*iff_data;
+
+/* TODO: refuctor wav loading */
+static void
+resample(sfxcache_t *sc, byte *data, float stepscale)
+{
+	int inwidth;
+	int		outcount;
+	int		srcsample;
+	int		i;
+	int		sample, samplefrac, fracstep;
+
+	inwidth = sc->width;
+	outcount = sc->length / stepscale;
+	sc->length = outcount;
+	sc->speed = Srate;
+	if (loadas8bit.value)
+		sc->width = 1;
+	else
+		sc->width = inwidth;
+	sc->stereo = 0;
+
+	if (stepscale == 1 && inwidth == 1 && sc->width == 1)
+	{
+// fast special case
+		for (i=0 ; i<outcount ; i++)
+			((signed char *)sc->data)[i]
+			= (int)( (unsigned char)(data[i]) - 128);
+	}
+	else
+	{
+// general case
+		samplefrac = 0;
+		fracstep = stepscale*256;
+		for (i=0 ; i<outcount ; i++)
+		{
+			srcsample = samplefrac >> 8;
+			samplefrac += fracstep;
+			if (inwidth == 2)
+				sample = LittleShort ( ((short *)data)[srcsample] );
+			else
+				sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
+			if (sc->width == 2)
+				((short *)sc->data)[i] = sample;
+			else
+				((signed char *)sc->data)[i] = sample >> 8;
+		}
+	}
+}
+
+static sfxcache_t *
+loadsfx(Sfx *sfx)
+{
+	wavinfo_t info;
+	int len;
+	float stepscale;
+	sfxcache_t *sc;
+	uchar *u, buf[1024];	/* avoid dirtying the cache heap */
+
+	if(sc = Cache_Check(&sfx->cu), sc != nil)
+		return sc;
+	u = loadstklmp(va("sound/%s", sfx->s), buf, sizeof buf, &len);
+	if(u == nil){
+		Con_DPrintf("loadsfx: %r\n");
+		return nil;
+	}
+	if(wavinfo(u, len, &info) != 0){
+		Con_Printf("loadsfx: %s: %s\n", sfx->s, lerr());
+		return nil;
+	}
+	if(info.channels != 1){
+		Con_DPrintf("loadsfx: non mono wave %s\n", sfx->s);
+		return nil;
+	}
+	stepscale = (float)info.rate / Srate;
+	len = info.samples / stepscale;
+	len *= info.width * info.channels;
+	if(sc = Cache_Alloc(&sfx->cu, len + sizeof *sc), sc == nil)
+		return nil;
+	sc->length = info.samples;
+	sc->loop = info.loopofs;
+	if(info.loopofs >= 0)
+		sc->loop /= stepscale;
+	sc->speed = info.rate;
+	sc->width = info.width;
+	sc->stereo = info.channels;
+	resample(sc, u + info.dataofs, stepscale);
+	return sc;
+}
+
+static void
+sndout(void)
+{
+	int v, *pb, *pe;
+	uchar *p;
+	double vol;
+
+	vol = volume.value;
+	p = mixbuf;
+	pb = sampbuf;
+	pe = sampbuf + nsamp * 2;
+	while(pb < pe){
+		v = *pb++ * vol;
+		if(v > 0x7fff)
+			v = 0x7fff;
+		else if(v < -0x8000)
+			v = -0x8000;
+		p[0] = v;
+		p[1] = v >> 8;
+		p += 2;
+	}
+}
+
+static void
+sample8(Chan *c, void *d, int n)
+{
+	int v, *pb, *pe, *ls, *rs;
+	uchar *p;
+
+	if(c->lvol > 255)
+		c->lvol = 255;
+	if(c->rvol > 255)
+		c->rvol = 255;
+	ls = scalt[c->lvol >> 3];
+	rs = scalt[c->rvol >> 3];
+	p = (uchar *)d + c->p;
+	pb = sampbuf;
+	pe = sampbuf + n * 2;
+	while(pb < pe){
+		v = *p++;
+		*pb++ += ls[v];
+		*pb++ += rs[v];
+	}
+}
+
+static void
+sample16(Chan *c, void *d, int n)
+{
+	int v, *pb, *pe, lv, rv;
+	short *p;
+
+	lv = c->lvol;
+	rv = c->rvol;
+	p = (short *)d + c->p;
+	pb = sampbuf;
+	pe = sampbuf + n * 2;
+	while(pb < pe){
+		v = *p++;
+		*pb++ += v * lv >> 8;
+		*pb++ += v * rv >> 8;
+	}
+}
+
+static void
+samplesfx(void)
+{
+	int n, m;
+	Chan *c;
+	sfxcache_t *sc;
+	void (*sf)(Chan *, void *, int);
+
+	memset(sampbuf, 0, sizeof sampbuf);
+	for(c=chans; c<che; c++){
+		if(c->sfx == nil)
+			continue;
+		if(c->lvol == 0 && c->rvol == 0)
+			continue;
+		if(sc = loadsfx(c->sfx), sc == nil)
+			continue;
+		sf = sc->width == 1 ? sample8 : sample16;
+		n = nsamp;
+		while(n > 0){
+			m = n < c->n ? n : c->n;
+			if(m > 0)
+				sf(c, sc->data, m);
+			c->p += m;
+			c->n -= m;
+			n -= m;
+			if(c->n <= 0){
+				if(sc->loop >= 0){
+					c->p = sc->loop;
+					c->n = sc->length - c->p;
+				}else{
+					c->sfx = nil;
+					break;
+				}
+			}
+		}
+	}
+	sndout();
+}
+
+static void
+spatialize(Chan *c)
+{
+	vec_t Δr, m;
+	vec3_t src;
+
+	if(c->entn == cl.viewentity){
+		c->lvol = c->chvol;
+		c->rvol = c->chvol;
+		return;
+	}
+	VectorSubtract(c->zp, listener_origin, src);
+	Δr = 1.0 - VectorNormalize(src) * c->attf;
+	m = DotProduct(listener_right, src);
+	c->rvol = Δr * (1.0 + m) * c->chvol;
+	if(c->rvol < 0)
+		c->rvol = 0;
+	c->lvol = Δr * (1.0 - m) * c->chvol;
+	if(c->lvol < 0)
+		c->lvol = 0;
+}
+
+static void
+ambs(void)
+{
+	uchar *av;
+	float vol;
+	Chan *c, *e;
+	mleaf_t *l;
+	Sfx **sfx;
+
+	if(cl.worldmodel == nil)
+		return;
+	c = chans;
+	e = chans + Namb;
+	l = Mod_PointInLeaf(listener_origin, cl.worldmodel);
+	if(l == nil || !ambient_level.value){
+		while(c < e)
+			c++->sfx = nil;
+		return;
+	}
+	sfx = ambsfx;
+	av = l->ambient_sound_level;
+	while(c < e){
+		c->sfx = *sfx++;
+		vol = ambient_level.value * *av++;
+		if(vol < 8)
+			vol = 0;
+		if(c->chvol < vol){
+			c->chvol += host_frametime * ambient_fade.value;
+			if(c->chvol > vol)
+				c->chvol = vol;
+		}else if(c->chvol > vol){
+			c->chvol -= host_frametime * ambient_fade.value;
+			if(c->chvol < vol)
+				c->chvol = vol;
+		}
+		c->lvol = c->chvol;
+		c->rvol = c->chvol;
+		c++;
+	}
+}
+
+void
+stepsnd(vec3_t origin, vec3_t forward, vec3_t right, vec3_t up)
+{
+	long ns;
+	Chan *c, *sum;
+
+	if(!ainit)
+		return;
+
+	if(sndt == 0)
+		sndt = nanosec() - Te9 / Fpsmax;
+	nsamp = (nanosec() - sndt) / (Te9 / Srate);
+	if(!cls.timedemo)
+		nsamp = (nsamp + 15) & ~15;
+	nsamp -= sndqueued()/Sblk - Srate/Fpsmax;
+	if(nsamp < 1)
+		return;
+	if(nsamp > Ssamp)
+		nsamp = Ssamp;
+	ns = nsamp * Sblk;
+	mixbuf = mixbufs[mixbufi];
+	samplesfx();
+	sampt += nsamp;
+	if(ns != 0){
+		sndwrite(mixbuf, ns);
+		mixbufi = (mixbufi + 1) % nelem(mixbufs);
+	}
+	sndt = nanosec();
+
+	VectorCopy(origin, listener_origin);
+	VectorCopy(forward, listener_forward);
+	VectorCopy(right, listener_right);
+	VectorCopy(up, listener_up);
+	ambs();
+	sum = nil;
+	for(c=chans+Namb; c<che; c++){
+		if(c->sfx == nil)
+			continue;
+		spatialize(c);
+		if(c->lvol == 0 && c->rvol == 0)
+			continue;
+		/* sum static sounds to avoid useless remixing */
+		if(c >= chans + Sstat){
+			if(sum != nil && sum->sfx == c->sfx){
+				sum->lvol += c->lvol;
+				sum->rvol += c->rvol;
+				c->lvol = c->rvol = 0;
+				continue;
+			}
+			for(sum=chans + Sstat; sum<c; sum++)
+				if(sum->sfx == c->sfx)
+					break;
+			if(sum == che)
+				sum = nil;
+			else if(sum != c){
+				sum->lvol += c->lvol;
+				sum->rvol += c->rvol;
+				c->lvol = c->rvol = 0;
+			}
+		}
+	}
+}
+
+void
+stopallsfx(void)
+{
+	if(!ainit)
+		return;
+	memset(chans, 0, sizeof(*chans)*Nchan);
+	che = chans + Sstat;
+	sndstop();
+}
+
+void
+stopsfx(int n, int ch)
+{
+	Chan *c, *e;
+
+	if(!ainit)
+		return;
+	c = chans;
+	e = chans + Ndyn;
+	while(c < e){
+		if(c->entn == n && c->entch == ch){
+			c->sfx = nil;
+			return;
+		}
+		c++;
+	}
+}
+
+static Chan *
+pickchan(int entn, int entch)
+{
+	int Δt;
+	Chan *c, *p, *e;
+
+	p = nil;
+	Δt = 0x7fffffff;
+	for(c=chans+Namb, e=c+Ndyn; c<e; c++){
+		if(entch != 0 && c->entn == entn
+		&& (c->entch == entch || entch == -1)){
+			p = c;
+			break;
+		}
+		if(c->entn == cl.viewentity && entn != cl.viewentity && c->sfx != nil)
+			continue;
+		if(c->n < Δt){
+			Δt = c->n;
+			p = c;
+		}
+	}
+	if(p != nil)
+		p->sfx = nil;
+	return p;
+}
+
+void
+startsfx(int entn, int entch, Sfx *sfx, vec3_t zp, float vol, float att)
+{
+	int skip;
+	Chan *c, *c2, *e;
+	sfxcache_t *sc;
+
+	if(!ainit || sfx == nil)
+		return;
+	if(c = pickchan(entn, entch), c == nil)
+		return;
+	memset(c, 0, sizeof *c);
+	VectorCopy(zp, c->zp);
+	c->attf = att / Clipdist;
+	c->chvol = vol * 255;
+	c->entn = entn;
+	c->entch = entch;
+	spatialize(c);
+	if(c->lvol == 0 && c->rvol == 0)
+		return;
+	if(sc = loadsfx(sfx), sc == nil)
+		return;
+	c->sfx = sfx;
+	c->p = 0;
+	c->n = sc->length;
+	/* don't sum identical sfx started on the same frame */
+	for(c2=chans+Namb, e=chans+Sstat; c2<e; c2++){
+		if(c2 == c || c2->sfx != sfx || c2->p != 0)
+			continue;
+		skip = nrand(Srate / Fpsmin);
+		if(skip >= c->n)
+			skip = c->n - 1;
+		c->p += skip;
+		c->n -= skip;
+		break;
+	}
+}
+
+void
+localsfx(char *s)
+{
+	Sfx *sfx;
+
+	if(!ainit)
+		return;
+	sfx = precachesfx(s);
+	startsfx(cl.viewentity, -1, sfx, vec3_origin, 1, 1);
+}
+
+void
+staticsfx(Sfx *sfx, vec3_t zp, float vol, float att)
+{
+	Chan *c;
+	sfxcache_t *sc;
+
+	if(sfx == nil || che >= chans+Nchan)
+		return;
+	c = che++;
+	if(sc = loadsfx(sfx), sc == nil)
+		return;
+	if(sc->loop < 0){
+		Con_DPrintf("staticsfx %s: nonlooped static sound\n", sfx->s);
+		return;
+	}
+	c->sfx = sfx;
+	VectorCopy(zp, c->zp);
+	c->chvol = vol * 255;
+	c->attf = att / Clipdist;
+	c->n = sc->length;
+	spatialize(c);
+}
+
+static Sfx *
+findsfx(char *s)
+{
+	Sfx *sfx, *e;
+
+	if(strlen(s) >= Npath)
+		Host_Error("findsfx: path too long %s", s);
+	sfx = known_sfx;
+	e = known_sfx + num_sfx;
+	while(sfx < e){
+		if(strcmp(sfx->s, s) == 0)
+			return sfx;
+		sfx++;
+	}
+	if(num_sfx == MAX_SOUNDS){
+		sfx = known_sfx;
+		while(sfx < e){
+			if(sfx->map && sfx->map < map)
+				break;
+			sfx++;
+		}
+		if(sfx == e)
+			Host_Error("findsfx: sfx list overflow: %s", s);
+		if(Cache_Check(&sfx->cu))
+			Cache_Free(&sfx->cu);
+	}else
+		num_sfx++;
+	strcpy(sfx->s, s);
+	return sfx;
+}
+
+void
+touchsfx(char *s)
+{
+	Sfx *sfx;
+
+	if(!ainit)
+		return;
+	sfx = findsfx(s);
+	Cache_Check(&sfx->cu);
+}
+
+Sfx *
+precachesfx(char *s)
+{
+	Sfx *sfx;
+
+	if(!ainit)
+		return nil;
+	sfx = findsfx(s);
+	sfx->map = map;
+	if(precache.value)
+		loadsfx(sfx);
+	return sfx;
+}
+
+static void
+playsfx(void)
+{
+	static int hash = 345;
+	int i;
+	char *s;
+	Sfx *sfx;
+
+	if(Cmd_Argc() < 2){
+		Con_Printf("play wav [wav..]: play a wav lump\n");
+		return;
+	}
+	i = 1;
+	while(i < Cmd_Argc()){
+		if(strrchr(Cmd_Argv(i), '.') == nil)
+			s = va("%s.wav", Cmd_Argv(i));
+		else
+			s = Cmd_Argv(i);
+		sfx = precachesfx(s);
+		startsfx(hash++, 0, sfx, listener_origin, 1.0, 1.0);
+		i++;
+	}
+}
+
+static void
+playvolsfx(void)
+{
+	static int hash = 543;
+	int i;
+	float vol;
+	char *s;
+	Sfx *sfx;
+
+	if(Cmd_Argc() < 3){
+		Con_Printf("play wav vol [wav vol]..: play an amplified wav lump\n");
+		return;
+	}
+	i = 1;
+	while(i < Cmd_Argc()){
+		if(strrchr(Cmd_Argv(i), '.') == nil)
+			s = va("%s.wav", Cmd_Argv(i));
+		else
+			s = Cmd_Argv(i);
+		sfx = precachesfx(s);
+		vol = atof(Cmd_Argv(++i));
+		startsfx(hash++, 0, sfx, listener_origin, vol, 1.0);
+		i++;
+	}
+}
+
+static void
+sfxlist(void)
+{
+	char c;
+	int sz, sum;
+	Sfx *sfx, *e;
+	sfxcache_t *sc;
+
+	sum = 0;
+	for(sfx=known_sfx, e=known_sfx+num_sfx; sfx<e; sfx++){
+		if(sc = Cache_Check(&sfx->cu), sc == nil)
+			continue;
+		sz = sc->length * sc->width * (sc->stereo + 1);
+		sum += sz;
+		c = sc->loop >= 0 ? 'L' : ' ';
+		Con_Printf("%c(%2db) %6d : %s\n", c, sc->width * 8, sz, sfx->s);
+	}
+	Con_Printf("Total resident: %d\n", sum);
+}
+
+void
+sfxbegin(void)
+{
+	map++;
+}
+
+void
+sfxend(void)
+{
+	Sfx *sfx;
+	int i;
+
+	for(i = 0, sfx = known_sfx; i < num_sfx; i++, sfx++){
+		if(sfx->map >= map || sfx == ambsfx[Ambsky] || sfx == ambsfx[Ambwater])
+			continue;
+		if(Cache_Check(&sfx->cu) != nil)
+			Cache_Free(&sfx->cu);
+	}
+}
+
+int
+initsnd(void)
+{
+	int i, j, *p;
+
+	if(sndopen() != 0)
+		return -1;
+	ainit = 1;
+	for(p=scalt[1], i=8; i<8*nelem(scalt); i+=8)
+		for(j=0; j<256; j++)
+			*p++ = (char)j * i;
+	Cmd_AddCommand("play", playsfx);
+	Cmd_AddCommand("playvol", playvolsfx);
+	Cmd_AddCommand("stopsound", stopallsfx);
+	Cmd_AddCommand("soundlist", sfxlist);
+	Cvar_RegisterVariable(&volume);
+	Cvar_RegisterVariable(&precache);
+	Cvar_RegisterVariable(&loadas8bit);
+	Cvar_RegisterVariable(&ambient_level);
+	Cvar_RegisterVariable(&ambient_fade);
+
+	chans = calloc(Nchan, sizeof(*chans));
+	known_sfx = Hunk_Alloc(MAX_SOUNDS * sizeof *known_sfx);
+	num_sfx = 0;
+
+	ambsfx[Ambwater] = precachesfx("ambience/water1.wav");
+	ambsfx[Ambsky] = precachesfx("ambience/wind2.wav");
+	stopallsfx();
+	return 0;
+}
--- /dev/null
+++ b/snd_openal.c
@@ -1,0 +1,880 @@
+#include "quakedef.h"
+#include <AL/al.h>
+#include <AL/alc.h>
+#include <AL/alext.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+typedef struct albuf_t albuf_t;
+typedef struct alchan_t alchan_t;
+
+struct albuf_t {
+	ALuint buf;
+	bool loop;
+	bool upsample;
+};
+
+struct alchan_t {
+	int ent;
+	int ch;
+	ALuint src;
+
+	alchan_t *prev, *next;
+};
+
+enum {
+	Srcstatic = -666,
+	Srcamb,
+};
+
+cvar_t volume = {"volume", "0.7", true};
+
+static struct {
+	ALuint src, buf;
+	int pcm;
+	bool playing;
+	pid_t decoder, reader;
+}track;
+
+static cvar_t s_al_dev = {"s_al_device", "0", true};
+static int s_al_dev_prev = -1;
+
+static cvar_t s_al_hrtf = {"s_al_hrtf", "0", true};
+static cvar_t s_al_doppler_factor = {"s_al_doppler_factor", "2", true};
+static cvar_t s_al_resampler_default = {"s_al_resampler_default", "6", true}; // 23rd order Sinc
+static cvar_t s_al_resampler_up = {"s_al_resampler_up", "1", true}; // Linear
+
+static ALCcontext *ctx;
+static ALCdevice *dev;
+static Sfx *known_sfx;
+static int num_sfx;
+static int map;
+static alchan_t *chans;
+
+static cvar_t ambient_level = {"ambient_level", "0.3"};
+static cvar_t ambient_fade = {"ambient_fade", "100"};
+static Sfx *ambsfx[Namb];
+static float ambvol[Namb];
+
+static int al_default_resampler, al_num_resamplers;
+static ALchar *(*qalGetStringiSOFT)(ALenum, ALsizei);
+static ALCchar *(*qalcGetStringiSOFT)(ALCdevice *, ALenum, ALsizei);
+static ALCboolean (*qalcResetDeviceSOFT)(ALCdevice *, const ALCint *attr);
+static ALCboolean *(*qalcReopenDeviceSOFT)(ALCdevice *, const ALCchar *devname, const ALCint *attr);
+static void (*qalBufferCallbackSOFT)(ALuint buf, ALenum fmt, ALsizei freq, ALBUFFERCALLBACKTYPESOFT cb, ALvoid *aux);
+
+#define ALERR() alcheckerr(__FILE__, __LINE__)
+
+static int
+alcheckerr(const char *file, int line)
+{
+	int e, ret;
+	char *s, tmp[32];
+
+	ret = 0;
+	if(ctx != nil && (e = alGetError()) != AL_NO_ERROR){
+		switch(e){
+		case AL_INVALID_NAME: s = "invalid name"; break;
+		case AL_INVALID_ENUM: s = "invalid enum"; break;
+		case AL_INVALID_VALUE: s = "invalid value"; break;
+		case AL_INVALID_OPERATION: s = "invalid operation"; break;
+		case AL_OUT_OF_MEMORY: s = "out of memory"; break;
+		default:
+			snprint(tmp, sizeof(tmp), "unknown (0x%x)", e);
+			s = tmp;
+			break;
+		}
+		ret |= e;
+		fprintf(stderr, "AL: %s:%d: %s\n", file, line, s);
+	}
+	if(dev != nil && (e = alcGetError(dev)) != ALC_NO_ERROR){
+		switch(e){
+		case ALC_INVALID_DEVICE: s = "invalid device"; break;
+		case ALC_INVALID_ENUM: s = "invalid enum"; break;
+		case ALC_INVALID_VALUE: s = "invalid value"; break;
+		case ALC_INVALID_CONTEXT: s = "invalid context"; break;
+		case ALC_OUT_OF_MEMORY: s = "out of memory"; break;
+		default:
+			snprint(tmp, sizeof(tmp), "unknown error (0x%x)", e);
+			s = tmp;
+			break;
+		}
+		ret |= e;
+		fprintf(stderr, "ALC: %s:%d: %s\n", file, line, s);
+	}
+
+	return ret;
+}
+
+static alchan_t *
+getchan(int ent, int ch)
+{
+	alchan_t *c, *stopped;
+	ALint state;
+	ALuint src;
+
+	stopped = nil;
+	for(c = chans; c != nil; c = c->next){
+		if(c->ent == ent && c->ch == ch)
+			return c;
+		if(stopped == nil){
+			alGetSourcei(c->src, AL_SOURCE_STATE, &state);
+			if(!ALERR() && state != AL_PLAYING)
+				stopped = c;
+		}
+	}
+
+	if(stopped != nil){
+		c = stopped;
+		c->ent = ent;
+		c->ch = ch;
+		return c;
+	}
+
+	alGenSources(1, &src);
+	if(ALERR())
+		return nil;
+
+	c = calloc(1, sizeof(*c));
+	c->ent = ent;
+	c->ch = ch;
+	c->src = src;
+	c->next = chans;
+	if(chans != nil)
+		chans->prev = c;
+	chans = c;
+
+	return c;
+}
+
+static void
+delchan(alchan_t *c)
+{
+	if(c->prev != nil)
+		c->prev->next = c->next;
+	if(c->next != nil)
+		c->next->prev = c->prev;
+	if(chans == c)
+		chans = c->next;
+	alSourceStop(c->src); ALERR();
+	alSourcei(c->src, AL_BUFFER, 0); ALERR();
+	alDeleteSources(1, &c->src); ALERR();
+	free(c);
+}
+
+static Sfx *
+findsfx(char *s)
+{
+	Sfx *sfx, *e;
+	albuf_t *b;
+
+	if(strlen(s) >= Npath)
+		Host_Error("findsfx: path too long %s", s);
+	sfx = known_sfx;
+	e = known_sfx + num_sfx;
+	while(sfx < e){
+		if(strcmp(sfx->s, s) == 0){
+			sfx->map = map;
+			return sfx;
+		}
+		sfx++;
+	}
+	if(num_sfx == MAX_SOUNDS){
+		sfx = known_sfx;
+		while(sfx < e){
+			if(sfx->map && sfx->map < map)
+				break;
+			sfx++;
+		}
+		if(sfx == e)
+			Host_Error("findsfx: sfx list overflow: %s", s);
+		if((b = Cache_Check(&sfx->cu)) != nil){
+			alDeleteBuffers(1, &b->buf); ALERR();
+			Cache_Free(&sfx->cu);
+			sfx->map = map;
+		}
+	}else
+		num_sfx++;
+	strcpy(sfx->s, s);
+	return sfx;
+}
+
+static albuf_t *
+loadsfx(Sfx *sfx)
+{
+	ALint loop[2];
+	wavinfo_t info;
+	ALuint buf;
+	ALenum fmt;
+	albuf_t *b;
+	byte *in;
+	int len;
+
+	if((b = Cache_Check(&sfx->cu)) != nil)
+		return b;
+	in = loadstklmp(va("sound/%s", sfx->s), nil, 0, &len);
+	if(in == nil){
+		Con_DPrintf("loadsfx: %s\n", lerr());
+		return nil;
+	}
+	if(wavinfo(in, len, &info) != 0){
+		Con_Printf("loadsfx: %s: %s\n", sfx->s, lerr());
+		// complain but get some silence in place so it looks like it got loaded
+		memset(&info, 0, sizeof(info));
+		info.width = 8;
+		info.channels = 1;
+		info.rate = 11025;
+		info.loopofs = -1;
+	}
+	if(info.channels < 2)
+		fmt = info.width == 1 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16;
+	else
+		fmt = info.width == 1 ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16;
+	alGenBuffers(1, &buf);
+	if(ALERR())
+		return nil;
+	alBufferData(buf, fmt, in+info.dataofs, info.samples*info.width, info.rate);
+	if(ALERR()){
+		alDeleteBuffers(1, &buf); ALERR();
+		return nil;
+	}
+	b = Cache_Alloc(&sfx->cu, sizeof(*b));
+	b->buf = buf;
+	if(info.loopofs >= 0){
+		loop[0] = info.loopofs;
+		loop[1] = info.samples;
+		alBufferiv(b->buf, AL_LOOP_POINTS_SOFT, loop); ALERR();
+		b->loop = true;
+	}
+	b->upsample = info.rate < 22050;
+
+	return b;
+}
+
+static void
+alplay(alchan_t *c, albuf_t *b, vec3_t zp, float vol, float att, bool rel, bool loop)
+{
+	ALint src;
+	int n;
+
+	src = c->src;
+	if(rel){
+		alSourcefv(src, AL_POSITION, vec3_origin); ALERR();
+		alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); ALERR();
+		alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); ALERR();
+		alSourcef(src, AL_REFERENCE_DISTANCE, 0.0f); ALERR();
+	}else{
+		alSourcefv(src, AL_POSITION, zp); ALERR();
+		alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); ALERR();
+		alSourcef(src, AL_ROLLOFF_FACTOR, att * 8.191); ALERR();
+		alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); ALERR();
+		alSourcef(src, AL_MAX_DISTANCE, 8192.0f); ALERR();
+	}
+	alSourcef(src, AL_GAIN, vol); ALERR();
+	if(al_num_resamplers > 0){
+		n = b->upsample ? s_al_resampler_up.value : s_al_resampler_default.value;
+		if(n >= 0){
+			alSourcei(src, AL_SOURCE_RESAMPLER_SOFT, n);
+			ALERR();
+		}
+	}
+	alSourcei(src, AL_BUFFER, b->buf); ALERR();
+	alSourcei(src, AL_LOOPING, (b->loop || loop) ? AL_TRUE : AL_FALSE); ALERR();
+	alSourcePlay(src); ALERR();
+}
+
+
+static void
+ambs(vec3_t org)
+{
+	float vol, *av;
+	ALint state;
+	alchan_t *ch;
+	uchar *asl;
+	mleaf_t *l;
+	albuf_t *b;
+	Sfx *sfx;
+	int i;
+
+	if(cl.worldmodel == nil)
+		return;
+	l = Mod_PointInLeaf(org, cl.worldmodel);
+	asl = l->ambient_sound_level;
+	for(i = 0; i < Namb; i++){
+		if((sfx = ambsfx[i]) == nil || (b = loadsfx(sfx)) == nil)
+			continue;
+		vol = ambient_level.value * asl[i];
+		av = &ambvol[i];
+		if(vol < 8)
+			vol = 0;
+		if(*av < vol){
+			*av += host_frametime * ambient_fade.value;
+			if(*av > vol)
+				*av = vol;
+		}else if(*av > vol){
+			*av -= host_frametime * ambient_fade.value;
+			if(*av < vol)
+				*av = vol;
+		}
+		if((ch = getchan(Srcamb, i)) != nil){
+			alSourcef(ch->src, AL_GAIN, *av / 255.0f); ALERR();
+			alGetSourcei(ch->src, AL_SOURCE_STATE, &state);
+			if(!ALERR() && state != AL_PLAYING)
+				alplay(ch, b, vec3_origin, *av, 0.0f, true, true);
+		}
+	}
+}
+
+void
+stepsnd(vec3_t zp, vec3_t fw, vec3_t rt, vec3_t up)
+{
+	vec_t fwup[6] = {fw[0], fw[1], fw[2], up[0], up[1], up[2]};
+	alchan_t *c, *next;
+	static vec3_t ozp;
+	ALint state;
+	vec3_t vel;
+
+	if(dev == nil)
+		return;
+	if(zp == vec3_origin && fw == vec3_origin && rt == vec3_origin){
+		alListenerf(AL_GAIN, 0);
+		ALERR();
+		return;
+	}
+
+	alListenerfv(AL_POSITION, zp); ALERR();
+	alListenerfv(AL_ORIENTATION, fwup); ALERR();
+	VectorSubtract(zp, ozp, vel);
+	VectorCopy(zp, ozp);
+	alListenerfv(AL_VELOCITY, vel); ALERR();
+	alListenerf(AL_GAIN, volume.value); ALERR();
+
+	ambs(zp);
+
+	for(c = chans; c != nil; c = next){
+		next = c->next;
+		alGetSourcei(c->src, AL_SOURCE_STATE, &state);
+		if(!ALERR() && state != AL_PLAYING)
+			delchan(c);
+	}
+}
+
+void
+stopallsfx(void)
+{
+	alchan_t *c, *next;
+
+	if(dev == nil)
+		return;
+	alListenerf(AL_GAIN, 0); ALERR();
+	for(c = chans; c != nil; c = next){
+		next = c->next;
+		delchan(c);
+	}
+	chans = nil;
+	memset(ambvol, 0, sizeof(ambvol));
+}
+
+void
+stopsfx(int ent, int ch)
+{
+	alchan_t *c;
+
+	if(dev == nil)
+		return;
+	for(c = chans; c != nil; c = c->next){
+		if(c->ent == ent && c->ch == ch)
+			break;
+	}
+	if(c == nil)
+		return;
+	if(c->prev != nil)
+		c->prev->next = c->next;
+	if(c->next != nil)
+		c->next->prev = c->prev;
+	if(chans == c)
+		chans = c->next;
+	delchan(c);
+	if(ent == Srcamb)
+		ambvol[ch] = 0;
+}
+
+void
+startsfx(int ent, int ch, Sfx *sfx, vec3_t zp, float vol, float att)
+{
+	alchan_t *c;
+	albuf_t *b;
+
+	if(dev == nil || (b = loadsfx(sfx)) == nil || (c = getchan(ent, ch)) == nil)
+		return;
+	alSourceStop(c->src); ALERR();
+	alplay(c, b, zp, vol, att, ent == cl.viewentity, ent == Srcstatic);
+}
+
+void
+localsfx(char *name)
+{
+	if(dev == nil)
+		return;
+
+	startsfx(cl.viewentity, -1, findsfx(name), vec3_origin, 1.0f, 1.0f);
+}
+
+void
+staticsfx(Sfx *sfx, vec3_t zp, float vol, float att)
+{
+	static int numst = 0;
+
+	if(dev == nil)
+		return;
+
+	startsfx(Srcstatic, numst++, sfx, zp, vol, att/1.5f);
+}
+
+void
+touchsfx(char *s)
+{
+	Sfx *sfx;
+
+	if(dev == nil)
+		return;
+	sfx = findsfx(s);
+	Cache_Check(&sfx->cu);
+}
+
+Sfx *
+precachesfx(char *s)
+{
+	Sfx *sfx;
+
+	if(dev == nil)
+		return nil;
+	sfx = findsfx(s);
+	sfx->map = map;
+	loadsfx(sfx);
+	return sfx;
+}
+
+static ALCint *
+alcattr(bool hrtf)
+{
+	static ALCint attr[] = {
+		0, 0, 0, 0, 0,
+	};
+
+	attr[0] = 0;
+	if(hrtf){
+		attr[0] = s_al_hrtf.value != 0 ? ALC_HRTF_SOFT : 0;
+		attr[1] = s_al_hrtf.value != 0 ? (s_al_hrtf.value > 0 ? ALC_TRUE : ALC_FALSE) : ALC_DONT_CARE_SOFT;
+		if(attr[1] == ALC_TRUE){
+			attr[2] = ALC_HRTF_ID_SOFT;
+			attr[3] = s_al_hrtf.value - 1;
+		}
+	}
+
+	return attr;
+}
+
+static int
+alinit(const char *devname)
+{
+	ALCcontext *c;
+	int e;
+
+	dev = alcOpenDevice(devname); ALERR();
+	if(dev == nil)
+		return -1;
+
+	c = alcCreateContext(dev, nil); ALERR();
+	if(c == nil){
+closedev:
+		alcCloseDevice(dev); ALERR();
+		dev = nil;
+		return -1;
+	}
+	ctx = c;
+	e = alcMakeContextCurrent(c); ALERR();
+	if(!e){
+		ctx = nil;
+		alcDestroyContext(c); ALERR();
+		goto closedev;
+	}
+	alListenerf(AL_GAIN, volume.value); ALERR();
+	alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); ALERR();
+
+	// assuming 64 Quake units is ~1.7m
+	alSpeedOfSound(343.3 * 64.0 / 1.7); ALERR();
+	alDopplerFactor(s_al_doppler_factor.value); ALERR();
+
+	if(alIsExtensionPresent("AL_SOFT_source_resampler")){
+		al_default_resampler = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
+		if(ALERR())
+			al_default_resampler = 0;
+		al_num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
+		if(ALERR())
+			al_num_resamplers = 0;
+		qalGetStringiSOFT = alGetProcAddress("alGetStringiSOFT");
+	}else{
+		qalGetStringiSOFT = nil;
+	}
+
+	qalBufferCallbackSOFT = nil;
+	if(alIsExtensionPresent("AL_SOFT_callback_buffer") && (alGenSources(1, &track.src), !ALERR())){
+		alSourcei(track.src, AL_SOURCE_SPATIALIZE_SOFT, AL_FALSE); ALERR();
+		qalBufferCallbackSOFT = alGetProcAddress("alBufferCallbackSOFT");
+		alGenBuffers(1, &track.buf); ALERR();
+	}
+
+	return 0;
+}
+
+static void
+alreinit(const char *def, const char *all)
+{
+	const char *s;
+	int i, n;
+
+	n = s_al_dev.value;
+	if(n == s_al_dev_prev)
+		return;
+	if(qalcReopenDeviceSOFT == nil && alcIsExtensionPresent(nil, "ALC_SOFT_reopen_device"))
+		qalcReopenDeviceSOFT = alGetProcAddress("alcReopenDeviceSOFT");
+	if(qalcReopenDeviceSOFT == nil){
+		Con_Printf("AL: can't change device settings on the fly\n");
+		return;
+	}
+	for(i = 1, s = all; s != nil && *s; i++){
+		if((n == 0 && def != nil && strcmp(s, def) == 0) || n == i){
+			if(dev == nil)
+				n = alinit(all);
+			else{
+				n = qalcReopenDeviceSOFT(dev, s, alcattr(false)) ? 0 : -1;
+				ALERR();
+			}
+			if(n != 0)
+				Con_Printf("AL: failed to switch to %s\n", s);
+			else
+				s_al_dev_prev = n;
+			return;
+		}
+		s += strlen(s)+1;
+	}
+	Con_Printf("AL: no such device: %d\n", n);
+}
+
+static void
+alvarcb(cvar_t *var)
+{
+	const char *all, *def, *s;
+	bool hrtf;
+	int i, n;
+
+	def = alcGetString(nil, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
+	if(ALERR())
+		def = nil;
+	all = alcGetString(nil, ALC_ALL_DEVICES_SPECIFIER);
+	if(ALERR())
+		all = nil;
+
+	if(var == &s_al_dev && Cmd_Argc() == 1){
+		Con_Printf("%-2d: default (%s)\n", 0, def ? def : "<invalid>");
+		for(i = 1, s = all; s != nil && *s; i++){
+			Con_Printf("%-2d: %s%s\n", i, s, strcmp(s, def) == 0 ? " (default)" : "");
+			s += strlen(s)+1;
+		}
+		return;
+	}
+
+	alreinit(def, all);
+
+	if(alcIsExtensionPresent(dev, "ALC_SOFT_HRTF")){
+		qalcGetStringiSOFT = alcGetProcAddress(dev, "alcGetStringiSOFT");
+		qalcResetDeviceSOFT = alcGetProcAddress(dev, "alcResetDeviceSOFT");
+		hrtf = true;
+	}else{
+		qalcGetStringiSOFT = nil;
+		qalcResetDeviceSOFT = nil;
+		hrtf = false;
+	}
+	if(var == &s_al_hrtf && Cmd_Argc() == 1){
+		Con_Printf("%-2d: disabled\n", -1);
+		Con_Printf("%-2d: don't care (default)\n", 0);
+		if(qalcGetStringiSOFT == nil)
+			return;
+		alcGetIntegerv(dev, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &n); ALERR();
+		for(i = 0; i < n; i++){
+			s = qalcGetStringiSOFT(dev, ALC_HRTF_SPECIFIER_SOFT, i);
+			if(!ALERR() && s != nil)
+				Con_Printf("%-2d: %s\n", i+1, s);
+		}
+		return;
+	}
+
+	if(qalcResetDeviceSOFT != nil){
+		qalcResetDeviceSOFT(dev, alcattr(hrtf));
+		ALERR();
+	}
+}
+
+static void
+aldopplercb(cvar_t *var)
+{
+	alDopplerFactor(var->value); ALERR();
+}
+
+static void
+sfxlist(void)
+{
+	int sz, sum, w, ch;
+	Sfx *sfx, *e;
+	albuf_t *b;
+
+	sum = 0;
+	for(sfx = known_sfx, e = known_sfx+num_sfx; sfx < e; sfx++){
+		if((b = Cache_Check(&sfx->cu)) == nil)
+			continue;
+		alGetBufferi(b->buf, AL_SIZE, &sz); ALERR();
+		alGetBufferi(b->buf, AL_CHANNELS, &ch); ALERR();
+		alGetBufferi(b->buf, AL_BITS, &w); ALERR();
+		sum += sz * ch * w/8;
+		Con_Printf("%c(%2db) %6d : %s\n", b->loop ? 'L' : ' ', w, sz, sfx->s);
+	}
+	Con_Printf("Total resident: %d\n", sum);
+}
+
+void
+sfxbegin(void)
+{
+	map++;
+}
+
+void
+sfxend(void)
+{
+	albuf_t *b;
+	Sfx *sfx;
+	int i;
+
+	ambsfx[Ambwater] = precachesfx("ambience/water1.wav");
+	ambsfx[Ambsky] = precachesfx("ambience/wind2.wav");
+	for(i = 0, sfx = known_sfx; i < num_sfx; i++, sfx++){
+		if(sfx->map >= map || sfx == ambsfx[Ambsky] || sfx == ambsfx[Ambwater])
+			continue;
+		if((b = Cache_Check(&sfx->cu)) != nil){
+			alDeleteBuffers(1, &b->buf); ALERR();
+			Cache_Free(&sfx->cu);
+		}
+	}
+}
+
+void
+stepcd(void)
+{
+	alSourcef(track.src, AL_GAIN, bgmvolume.value); ALERR();
+}
+
+static ALsizei
+trackcb(ALvoid *aux, ALvoid *sampledata, ALsizei numbytes)
+{
+	ssize_t n;
+	byte *b;
+
+	USED(aux);
+	for(b = sampledata; numbytes > 0; b += n, numbytes -= n){
+		if((n = read(track.pcm, b, numbytes)) <= 0){
+			close(track.pcm);
+			track.pcm = -1;
+			break;
+		}
+	}
+
+	return b - (byte*)sampledata;
+}
+
+void
+playcd(int nt, bool loop)
+{
+	pid_t pid;
+	FILE *f;
+	fpos_t off;
+	int len, left, s[2], in[2];
+
+	stopcd();
+	if(qalBufferCallbackSOFT == nil)
+		return;
+
+	if((f = openlmp(va("music/track%02d.ogg", nt), &len)) == nil){
+		if((f = openlmp(va("music/track%02d.mp3", nt), &len)) == nil)
+			f = openlmp(va("music/track%02d.wav", nt), &len);
+	}
+	if(f == nil)
+		return;
+	if(fgetpos(f, &off) != 0){
+err:
+		close(track.pcm);
+		fclose(f);
+		if(track.decoder > 0)
+			waitpid(track.decoder, nil, 0);
+		if(track.reader > 0)
+			waitpid(track.reader, nil, 0);
+		return;
+	}
+
+	if(pipe(s) != 0)
+		goto err;
+	if(pipe(in) != 0){
+		close(s[0]);
+		close(s[1]);
+		goto err;
+	}
+
+	switch((pid = fork())){
+	case 0:
+		close(s[1]); dup2(s[0], 0);
+		close(in[0]); dup2(in[1], 1);
+		close(s[0]);
+		execl(
+			"/usr/bin/env", "/usr/bin/env",
+			"ffmpeg",
+				"-loglevel", "fatal",
+				"-i", "-",
+				"-acodec", "pcm_s16le",
+				"-f", "s16le",
+				"-ac", "2",
+				"-ar", "44100",
+				"-",
+			nil
+		);
+		perror("execl ffmpeg"); // FIXME(sigrid): add and use Con_Errorf?
+		exit(1);
+	case -1:
+		goto err;
+	}
+	track.decoder = pid;
+
+	close(s[0]);
+	close(in[1]);
+	track.pcm = in[0];
+	cdloop = loop;
+	cdtrk = nt;
+
+	switch((pid = fork())){
+	case 0:
+		close(in[0]);
+		close(0);
+		close(1);
+		left = len;
+		for(;;){
+			byte tmp[32768];
+			ssize_t n;
+			if((n = fread(tmp, 1, min(left, (int)sizeof(tmp)), f)) < 1){
+				if(ferror(f)){
+					perror("fread");
+					break;
+				}
+			}
+			if(write(s[1], tmp, n) != n)
+				break;
+			left -= n;
+			if(left < 1){
+				if(!loop)
+					break;
+				if(fsetpos(f, &off) != 0){
+					perror("fsetpos");
+					break;
+				}
+				left = len;
+			}
+		}
+		close(s[1]);
+		fclose(f);
+		exit(1);
+	case -1:
+		goto err;
+	}
+	track.reader = pid;
+
+	qalBufferCallbackSOFT(track.buf, AL_FORMAT_STEREO16, 44100, trackcb, nil);
+	if(ALERR())
+		goto err;
+	alSourcei(track.src, AL_BUFFER, track.buf); ALERR();
+	alSourcePlay(track.src); ALERR();
+	track.playing = true;
+}
+
+void
+resumecd(void)
+{
+	alSourcePlay(track.src); ALERR();
+}
+
+void
+pausecd(void)
+{
+	alSourcePause(track.src); ALERR();
+}
+
+int
+initcd(void)
+{
+	cdntrk = cdtrk = 0;
+	cdloop = false;
+	return 0;
+}
+
+void
+stopcd(void)
+{
+	if(track.playing){
+		alSourceStop(track.src); ALERR();
+		alSourcei(track.src, AL_BUFFER, 0); ALERR();
+		if(track.pcm >= 0)
+			close(track.pcm);
+		waitpid(track.decoder, nil, 0);
+		waitpid(track.reader, nil, 0);
+	}
+	track.playing = false;
+	track.pcm = -1;
+	track.decoder = track.reader = -1;
+}
+
+void
+shutcd(void)
+{
+	stopcd();
+}
+
+int
+initsnd(void)
+{
+	s_al_dev.cb = s_al_hrtf.cb = alvarcb;
+	s_al_doppler_factor.cb = aldopplercb;
+
+	Cvar_RegisterVariable(&volume);
+	Cvar_RegisterVariable(&bgmvolume);
+	Cvar_RegisterVariable(&ambient_level);
+	Cvar_RegisterVariable(&ambient_fade);
+	Cvar_RegisterVariable(&s_al_dev);
+	Cvar_RegisterVariable(&s_al_resampler_default);
+	Cvar_RegisterVariable(&s_al_resampler_up);
+	Cvar_RegisterVariable(&s_al_hrtf);
+	Cvar_RegisterVariable(&s_al_doppler_factor);
+	Cmd_AddCommand("stopsound", stopallsfx);
+	Cmd_AddCommand("soundlist", sfxlist);
+	Cmd_AddCommand("cd", cdcmd);
+
+	alinit(nil);
+	known_sfx = Hunk_Alloc(MAX_SOUNDS * sizeof *known_sfx);
+	num_sfx = 0;
+
+	return 0;
+}
+
+void
+sndclose(void)
+{
+	if(dev == nil)
+		return;
+	alcDestroyContext(ctx); ctx = nil; ALERR();
+	alcCloseDevice(dev); dev = nil; ALERR();
+}
--- /dev/null
+++ b/sys_plan9.c
@@ -1,0 +1,168 @@
+#include "quakedef.h"
+#include <thread.h>
+
+int mainstacksize = 1*1024*1024;
+char *netmtpt = "/net";
+char *game;
+int debug;
+
+int
+sys_mkdir(char *path)
+{
+	int d;
+
+	if(access(path, AEXIST) == 0)
+		return 0;
+	if((d = create(path, OREAD, DMDIR|0777)) < 0){
+		Con_DPrintf("Sys_mkdir: create: %r\n");
+		return -1;
+	}
+	close(d);
+	return 0;
+}
+
+char *
+sys_timestamp(void)
+{
+	static char ts[32];
+	Tm *tm;
+	long t;
+
+	if((t = time(nil)) < 0 || (tm = localtime(t)) == nil)
+		return nil;
+	snprint(ts, sizeof(ts),
+		"%04d%02d%02d-%02d%02d%02d",
+		tm->year + 1900, tm->mon + 1, tm->mday, tm->hour, tm->min, tm->sec
+	);
+
+	return ts;
+}
+
+char *
+lerr(void)
+{
+	static char err[ERRMAX];
+	rerrstr(err, sizeof(err));
+	return err;
+}
+
+_Noreturn void
+fatal(char *fmt, ...)
+{
+	char s[1024];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(s, s+sizeof s, fmt, arg);
+	va_end(arg);
+	Host_Shutdown();
+	sysfatal("%s", s);
+}
+
+void *
+emalloc(long n)
+{
+	void *p;
+
+	if(p = mallocz(n, 1), p == nil)
+		sysfatal("emalloc %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+vlong
+flen(int fd)
+{
+	vlong l;
+	Dir *d;
+
+	if((d = dirfstat(fd)) == nil)	/* file assumed extant and readable */
+		sysfatal("flen: %r");
+	l = d->length;
+	free(d);
+	return l;
+}
+
+double
+dtime(void)
+{
+	return nanosec() / 1000000000.0;
+}
+
+void
+game_shutdown(void)
+{
+	stopfb();
+	Host_Shutdown();
+	threadexitsall(nil);
+}
+
+static void
+croak(void *, char *note)
+{
+	if(strncmp(note, "sys:", 4) == 0){
+		IN_Grabm(0);
+		threadkillgrp(0);
+	}
+	noted(NDFLT);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-d] [-g game] [-m kB] [-x netmtpt]\n", argv0);
+	exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	double t, t´, Δt;
+	char *e;
+	static char *paths[] = {
+		"/sys/games/lib/quake",
+		nil,
+		nil,
+	};
+
+	ARGBEGIN{
+	case 'D':
+		debug = 1;
+		break;
+	case 'd':
+		dedicated = 1;
+		break;
+	case 'g':
+		game = EARGF(usage());
+		break;
+	case 'x':
+		netmtpt = EARGF(usage());
+		break;
+	default: usage();
+	}ARGEND
+	srand(getpid());
+	/* ignore fp exceptions: rendering shit assumes they are */
+	setfcr(getfcr() & ~(FPOVFL|FPUNFL|FPINVAL|FPZDIV));
+	notify(croak);
+
+	e = getenv("home");
+	paths[1] = smprint("%s/lib/quake", e);
+	free(e);
+	Host_Init(argc, argv, paths);
+
+	t = dtime() - 1.0 / Fpsmax;
+	for(;;){
+		t´ = dtime();
+		Δt = t´ - t;
+		if(cls.state == ca_dedicated){
+			if(Δt < sys_ticrate.value)
+				continue;
+			Δt = sys_ticrate.value;
+        	}
+		if(Δt > sys_ticrate.value * 2)
+			t = t´;
+		else
+			t += Δt;
+		Host_Frame(Δt);
+	}
+}
--- /dev/null
+++ b/sys_unix.c
@@ -1,0 +1,167 @@
+#include "quakedef.h"
+#include "parg.h"
+#include <time.h>
+#include <errno.h>
+#include <fenv.h>
+
+char *game;
+int debug;
+char lasterr[256] = {0};
+
+char *
+lerr(void)
+{
+	static char lasterrcopy[256];
+	if(*lasterr == 0 && errno != 0)
+		return strerror(errno);
+	strcpy(lasterrcopy, lasterr);
+	return lasterrcopy;
+}
+
+int
+sys_mkdir(char *path)
+{
+	return (mkdir(path, 0770) == 0 || errno == EEXIST) ? 0 : -1;
+}
+
+char *
+sys_timestamp(void)
+{
+	static char ts[32];
+	struct tm *tm;
+	time_t t;
+
+	if((t = time(nil)) == (time_t)-1 || (tm = localtime(&t)) == nil)
+		return nil;
+	snprint(ts, sizeof(ts),
+		"%04d%02d%02d-%02d%02d%02d",
+		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec
+	);
+
+	return ts;
+}
+
+int
+nrand(int n)
+{
+	return random() % n;
+}
+
+void
+fatal(char *fmt, ...)
+{
+	va_list arg;
+
+	va_start(arg, fmt);
+	vfprintf(stderr, fmt, arg);
+	va_end(arg);
+	Host_Shutdown();
+	exit(1);
+}
+
+void *
+emalloc(long n)
+{
+	void *p;
+
+	if(p = calloc(1, n), p == nil)
+		fatal("emalloc");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+uvlong
+nanosec(void)
+{
+	static time_t sec0;
+	struct timespec t;
+
+	if(clock_gettime(CLOCK_MONOTONIC, &t) != 0)
+		fatal("clock_gettime");
+	if(sec0 == 0)
+		sec0 = t.tv_sec;
+	t.tv_sec -= sec0;
+	return t.tv_sec*1000000000ULL + t.tv_nsec;
+}
+
+double
+dtime(void)
+{
+	return nanosec()/1000000000.0;
+}
+
+void
+game_shutdown(void)
+{
+	stopfb();
+	Host_Shutdown();
+	exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+	double t, t2, dt;
+	struct parg_state ps;
+	int c, nargs;
+	static char *paths[] = {
+		"/usr/games/quake",
+		nil,
+		nil,
+	};
+
+	parg_init(&ps);
+	nargs = 0;
+	while((c = parg_getopt(&ps, argc, argv, "Ddg:")) >= 0){
+		switch(c){
+		case 1:
+			argv[nargs++] = (char*)ps.optarg;
+			break;
+		case 'D':
+			debug++;
+			break;
+		case 'd':
+			dedicated = 1;
+			break;
+		case 'g':
+			game = (char*)ps.optarg;
+			break;
+		case 'h':
+			fprintf(stderr, "usage: qk1 [-g game]\n");
+			return 0;
+			break;
+		case '?':
+			fprintf(stderr, "unknown option -%c\n", ps.optopt);
+			return 1;
+			break;
+		default:
+			fprintf(stderr, "unhandled option -%c\n", c);
+			return 1;
+			break;
+		}
+	}
+
+	srand(nanosec() + time(nil));
+
+	paths[1] = strdup(va("%s/.quake", getenv("HOME")));
+	Host_Init(nargs, argv, paths);
+
+	fesetround(FE_TOWARDZERO);
+
+	t = dtime() - 1.0 / Fpsmax;
+	for(;;){
+		t2 = dtime();
+		dt = t2 - t;
+		if(cls.state == ca_dedicated){
+			if(dt < sys_ticrate.value)
+				continue;
+			dt = sys_ticrate.value;
+        }
+		if(dt > sys_ticrate.value * 2)
+			t = t2;
+		else
+			t += dt;
+		Host_Frame(dt);
+	}
+	return 0;
+}
--- a/unix/in.c
+++ /dev/null
@@ -1,177 +1,0 @@
-#include "quakedef.h"
-#include <SDL.h>
-
-/* vid.c */
-extern int resized;
-
-static cvar_t m_windowed = {"m_windowed", "1", true};
-static cvar_t m_filter = {"m_filter", "0", true};
-static cvar_t m_raw = {"m_raw", "1", true};
-static int mouseon, oldmwin, focuslost;
-static float dx, dy, olddx, olddy;
-
-static int mbuttons[] = {
-	K_MOUSE1,
-	K_MOUSE3,
-	K_MOUSE2,
-};
-
-void
-conscmd(void)
-{
-}
-
-void
-Sys_SendKeyEvents(void)
-{
-	SDL_Event event;
-	int key, b;
-
-	if(cls.state == ca_dedicated)
-		return;
-	if(oldmwin != (int)m_windowed.value){
-		oldmwin = (int)m_windowed.value;
-		IN_Grabm(oldmwin);
-	}
-
-	while(SDL_PollEvent(&event)){
-		switch(event.type){
-		case SDL_QUIT:
-			Cbuf_AddText("menu_quit\n");
-			break;
-		case SDL_WINDOWEVENT:
-			switch(event.window.event){
-			case SDL_WINDOWEVENT_RESIZED:
-				resized = 1;
-				break;
-			case SDL_WINDOWEVENT_CLOSE:
-				Cbuf_AddText("menu_quit\n");
-				break;
-			case SDL_WINDOWEVENT_LEAVE:
-				focuslost = mouseon;
-				IN_Grabm(0);
-				break;
-			case SDL_WINDOWEVENT_ENTER:
-				IN_Grabm(focuslost);
-				break;
-			}
-			break;
-		case SDL_MOUSEMOTION:
-			if(mouseon){
-				dx += event.motion.xrel;
-				dy += event.motion.yrel;
-			}
-			break;
-		case SDL_MOUSEBUTTONDOWN:
-		case SDL_MOUSEBUTTONUP:
-			if(mouseon && (b = event.button.button-1) >= 0 && b < nelem(mbuttons))
-				Key_Event(mbuttons[b], event.type == SDL_MOUSEBUTTONDOWN);
-			break;
-		case SDL_KEYDOWN:
-		case SDL_KEYUP:
-			switch(key = event.key.keysym.sym){
-			case SDLK_BACKQUOTE: key = '~'; break;
-			case SDLK_DELETE: key = K_DEL; break;
-			case SDLK_BACKSPACE: key = K_BACKSPACE; break;
-			case SDLK_F1: key = K_F1; break;
-			case SDLK_F2: key = K_F2; break;
-			case SDLK_F3: key = K_F3; break;
-			case SDLK_F4: key = K_F4; break;
-			case SDLK_F5: key = K_F5; break;
-			case SDLK_F6: key = K_F6; break;
-			case SDLK_F7: key = K_F7; break;
-			case SDLK_F8: key = K_F8; break;
-			case SDLK_F9: key = K_F9; break;
-			case SDLK_F10: key = K_F10; break;
-			case SDLK_F11: key = K_F11; break;
-			case SDLK_F12: key = K_F12; break;
-			case SDLK_PAUSE: key = K_PAUSE; break;
-			case SDLK_UP: key = K_UPARROW; break;
-			case SDLK_DOWN: key = K_DOWNARROW; break;
-			case SDLK_RIGHT: key = K_RIGHTARROW; break;
-			case SDLK_LEFT: key = K_LEFTARROW; break;
-			case SDLK_RSHIFT:
-			case SDLK_LSHIFT: key = K_SHIFT; break;
-			case SDLK_RCTRL:
-			case SDLK_LCTRL: key = K_CTRL; break;
-			case SDLK_RALT:
-			case SDLK_LALT: key = K_ALT; break;
-			default:
-				if(key >= 128)
-					key = 0;
-				break;
-			}
-			if(key > 0)
-				Key_Event(key, event.key.state);
-			break;
-		}
-	}
-}
-
-void
-IN_Commands(void)
-{
-}
-
-static void
-m_raw_cb(cvar_t *var)
-{
-	SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, var->value > 0 ? "0" : "1");
-}
-
-void
-IN_Move(usercmd_t *cmd)
-{
-	if(!mouseon)
-		return;
-
-	if(m_filter.value){
-		dx = (dx + olddx) * 0.5;
-		dy = (dy + olddy) * 0.5;
-	}
-	olddx = dx;
-	olddy = dy;
-	dx *= sensitivity.value;
-	dy *= sensitivity.value;
-	if(in_strafe.state & 1 || (lookstrafe.value && in_mlook.state & 1))
-		cmd->sidemove += m_side.value * dx;
-	else
-		cl.viewangles[YAW] -= m_yaw.value * dx;
-	if(in_mlook.state & 1)
-		V_StopPitchDrift();
-	if(in_mlook.state & 1 && ~in_strafe.state & 1){
-		cl.viewangles[PITCH] += m_pitch.value * dy;
-		if(cl.viewangles[PITCH] > 80)
-			cl.viewangles[PITCH] = 80;
-		if(cl.viewangles[PITCH] < -70)
-			cl.viewangles[PITCH] = -70;
-	}else{
-		if(in_strafe.state & 1 && noclip_anglehack)
-			cmd->upmove -= m_forward.value * dy;
-		else
-			cmd->forwardmove -= m_forward.value * dy;
-	}
-	dx = 0;
-	dy = 0;
-}
-
-void
-IN_Grabm(int on)
-{
-	SDL_SetRelativeMouseMode(mouseon = on);
-}
-
-void
-IN_Shutdown(void)
-{
-	IN_Grabm(0);
-}
-
-void
-IN_Init(void)
-{
-	m_raw.cb = m_raw_cb;
-	Cvar_RegisterVariable(&m_windowed);
-	Cvar_RegisterVariable(&m_filter);
-	Cvar_RegisterVariable(&m_raw);
-}
--- a/unix/net_udp.c
+++ /dev/null
@@ -1,361 +1,0 @@
-#include "quakedef.h"
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <sys/param.h>
-#include <sys/ioctl.h>
-#include <errno.h>
-
-extern cvar_t hostname;
-
-static int net_acceptsocket = -1; // socket for fielding new connections
-static int net_controlsocket;
-static int net_broadcastsocket = 0;
-static Addr broadcastaddr;
-
-static unsigned long myAddr;
-Addr myip;
-
-static int UDP_OpenSocket (int port);
-static int UDP_GetSocketAddr (int socket, Addr *addr);
-static int UDP_CloseSocket (int socket);
-
-//=============================================================================
-
-int UDP_Init (void)
-{
-	struct hostent *local;
-	char buff[MAXHOSTNAMELEN];
-
-	// determine my name & address
-	gethostname(buff, MAXHOSTNAMELEN);
-	local = gethostbyname(buff);
-	myAddr = *(int *)local->h_addr_list[0];
-
-	// if the quake hostname isn't set, set it to the machine name
-	if (strcmp(hostname.string, "UNNAMED") == 0)
-	{
-		buff[15] = 0;
-		setcvar ("hostname", buff);
-	}
-
-	if ((net_controlsocket = UDP_OpenSocket (0)) == -1)
-		Host_Error("UDP_Init: Unable to open control socket\n");
-
-	((struct sockaddr_in *)&broadcastaddr)->sin_family = AF_INET;
-	((struct sockaddr_in *)&broadcastaddr)->sin_addr.s_addr = INADDR_BROADCAST;
-	((struct sockaddr_in *)&broadcastaddr)->sin_port = htons(Udpport);
-
-	UDP_GetSocketAddr (net_controlsocket, &myip);
-
-	return net_controlsocket;
-}
-
-//=============================================================================
-
-void UDP_Shutdown (void)
-{
-	UDP_Listen (false);
-	UDP_CloseSocket (net_controlsocket);
-}
-
-//=============================================================================
-
-void UDP_Listen (bool state)
-{
-	// enable listening
-	if (state)
-	{
-		if (net_acceptsocket != -1)
-			return;
-		if ((net_acceptsocket = UDP_OpenSocket (Udpport)) == -1)
-			Host_Error ("UDP_Listen: Unable to open accept socket\n");
-		return;
-	}
-
-	// disable listening
-	if (net_acceptsocket == -1)
-		return;
-	UDP_CloseSocket (net_acceptsocket);
-	net_acceptsocket = -1;
-}
-
-//=============================================================================
-
-static int UDP_OpenSocket (int port)
-{
-	int newsocket;
-	struct sockaddr_in address;
-
-	if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
-		return -1;
-
-	address.sin_family = AF_INET;
-	address.sin_addr.s_addr = INADDR_ANY;
-	address.sin_port = htons(port);
-	if( bind (newsocket, (void *)&address, sizeof(address)) == -1)
-		goto ErrorReturn;
-
-	return newsocket;
-
-ErrorReturn:
-	close (newsocket);
-	return -1;
-}
-
-//=============================================================================
-
-static int UDP_CloseSocket (int socket)
-{
-	if (socket == net_broadcastsocket)
-		net_broadcastsocket = 0;
-	return close (socket);
-}
-
-//=============================================================================
-/*
-============
-PartialIPAddress
-
-this lets you type only as much of the net address as required, using
-the local network components to fill in the rest
-============
-*/
-static int PartialIPAddress (char *in, Addr *hostaddr)
-{
-	char buff[256];
-	char *b;
-	int addr;
-	int num;
-	int mask;
-	int run;
-	int port;
-
-	buff[0] = '.';
-	b = buff;
-	strcpy(buff+1, in);
-	if (buff[1] == '.')
-		b++;
-
-	addr = 0;
-	mask=-1;
-	while (*b == '.')
-	{
-		b++;
-		num = 0;
-		run = 0;
-		while (!( *b < '0' || *b > '9'))
-		{
-		  num = num*10 + *b++ - '0';
-		  if (++run > 3)
-		  	return -1;
-		}
-		if ((*b < '0' || *b > '9') && *b != '.' && *b != ':' && *b != 0)
-			return -1;
-		if (num < 0 || num > 255)
-			return -1;
-		mask<<=8;
-		addr = (addr<<8) + num;
-	}
-
-	if (*b++ == ':')
-		port = strtol(b, NULL, 0);
-	else
-		port = Udpport;
-
-	((struct sockaddr_in *)hostaddr)->sin_port = htons((short)port);
-	((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = (myAddr & htonl(mask)) | htonl(addr);
-
-	return 0;
-}
-//=============================================================================
-
-int UDP_Connect (Addr *addr)
-{
-	USED(addr);
-	return 0;
-}
-
-//=============================================================================
-
-int UDP_CheckNewConnections (void)
-{
-	unsigned long available;
-
-	if (net_acceptsocket == -1)
-		return -1;
-
-	if (ioctl (net_acceptsocket, FIONREAD, &available) == -1)
-		Host_Error ("UDP: ioctlsocket (FIONREAD) failed\n");
-	if (available)
-		return net_acceptsocket;
-	return -1;
-}
-
-//=============================================================================
-
-int UDP_Read (int socket, uint8_t *buf, int len, Addr *addr)
-{
-	socklen_t addrlen = sizeof (Addr);
-	int ret;
-
-	ret = recvfrom (socket, buf, len, 0, (struct sockaddr *)addr, &addrlen);
-	if (ret == -1 && (errno == EWOULDBLOCK || errno == ECONNREFUSED))
-		return 0;
-	return ret;
-}
-
-//=============================================================================
-
-int UDP_MakeSocketBroadcastCapable (int socket)
-{
-	int i = 1;
-
-	// make this socket broadcast capable
-	if (setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) < 0)
-		return -1;
-	net_broadcastsocket = socket;
-
-	return 0;
-}
-
-//=============================================================================
-
-static int UDP_Write (int socket, uint8_t *buf, int len, Addr *addr)
-{
-	int ret;
-
-	ret = sendto (socket, buf, len, 0, (struct sockaddr *)addr, sizeof(Addr));
-	if (ret == -1 && errno == EWOULDBLOCK)
-		return 0;
-	return ret;
-}
-
-//=============================================================================
-
-int UDP_Broadcast (int socket, uint8_t *buf, int len)
-{
-	int ret;
-
-	if (socket != net_broadcastsocket)
-	{
-		if (net_broadcastsocket != 0)
-			Host_Error("Attempted to use multiple broadcasts sockets\n");
-		ret = UDP_MakeSocketBroadcastCapable (socket);
-		if (ret == -1)
-		{
-			Con_Printf("Unable to make socket broadcast capable\n");
-			return ret;
-		}
-	}
-
-	return UDP_Write (socket, buf, len, &broadcastaddr);
-}
-
-//=============================================================================
-
-char *UDP_AddrToString (Addr *addr)
-{
-	static char buffer[22];
-	int haddr;
-
-	haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr);
-	sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, ntohs(((struct sockaddr_in *)addr)->sin_port));
-	return buffer;
-}
-
-//=============================================================================
-
-int UDP_StringToAddr (char *string, Addr *addr)
-{
-	int ha1, ha2, ha3, ha4, hp;
-	int ipaddr;
-
-	sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp);
-	ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4;
-
-	((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr);
-	((struct sockaddr_in *)addr)->sin_port = htons(hp);
-	return 0;
-}
-
-//=============================================================================
-
-static int UDP_GetSocketAddr (int socket, Addr *addr)
-{
-	socklen_t addrlen = sizeof(Addr);
-	unsigned int a;
-
-	memset(addr, 0, sizeof(Addr));
-	getsockname(socket, (struct sockaddr *)addr, &addrlen);
-	a = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
-	if (a == 0 || a == inet_addr("127.0.0.1"))
-		((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr;
-
-	return 0;
-}
-
-//=============================================================================
-
-int UDP_GetNameFromAddr (Addr *addr, char *name)
-{
-	struct hostent *hostentry;
-
-	hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, sizeof(struct in_addr), AF_INET);
-	if (hostentry)
-	{
-		strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1);
-		return 0;
-	}
-
-	strcpy (name, UDP_AddrToString (addr));
-	return 0;
-}
-
-//=============================================================================
-
-int UDP_GetAddrFromName(char *name, Addr *addr)
-{
-	struct hostent *hostentry;
-
-	if (name[0] >= '0' && name[0] <= '9')
-		return PartialIPAddress (name, addr);
-
-	hostentry = gethostbyname (name);
-	if (!hostentry)
-		return -1;
-
-	((struct sockaddr_in *)addr)->sin_port = htons(Udpport);
-	((struct sockaddr_in *)addr)->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0];
-
-	return 0;
-}
-
-//=============================================================================
-
-int UDP_AddrCompare (Addr *addr1, Addr *addr2)
-{
-	if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != ((struct sockaddr_in *)addr2)->sin_addr.s_addr)
-		return -1;
-
-	if (((struct sockaddr_in *)addr1)->sin_port != ((struct sockaddr_in *)addr2)->sin_port)
-		return 1;
-
-	return 0;
-}
-
-//=============================================================================
-
-u16int UDP_GetSocketPort (Addr *addr)
-{
-	return ntohs(((struct sockaddr_in *)addr)->sin_port);
-}
-
-void UDP_SetSocketPort (Addr *addr, u16int port)
-{
-	((struct sockaddr_in *)addr)->sin_port = htons(port);
-}
-
-//=============================================================================
--- a/unix/qk1.c
+++ /dev/null
@@ -1,167 +1,0 @@
-#include "quakedef.h"
-#include "parg.h"
-#include <time.h>
-#include <errno.h>
-#include <fenv.h>
-
-char *game;
-int debug;
-char lasterr[256] = {0};
-
-char *
-lerr(void)
-{
-	static char lasterrcopy[256];
-	if(*lasterr == 0 && errno != 0)
-		return strerror(errno);
-	strcpy(lasterrcopy, lasterr);
-	return lasterrcopy;
-}
-
-int
-sys_mkdir(char *path)
-{
-	return (mkdir(path, 0770) == 0 || errno == EEXIST) ? 0 : -1;
-}
-
-char *
-sys_timestamp(void)
-{
-	static char ts[32];
-	struct tm *tm;
-	time_t t;
-
-	if((t = time(nil)) == (time_t)-1 || (tm = localtime(&t)) == nil)
-		return nil;
-	snprint(ts, sizeof(ts),
-		"%04d%02d%02d-%02d%02d%02d",
-		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec
-	);
-
-	return ts;
-}
-
-int
-nrand(int n)
-{
-	return random() % n;
-}
-
-void
-fatal(char *fmt, ...)
-{
-	va_list arg;
-
-	va_start(arg, fmt);
-	vfprintf(stderr, fmt, arg);
-	va_end(arg);
-	Host_Shutdown();
-	exit(1);
-}
-
-void *
-emalloc(long n)
-{
-	void *p;
-
-	if(p = calloc(1, n), p == nil)
-		fatal("emalloc");
-	setmalloctag(p, getcallerpc(&n));
-	return p;
-}
-
-uvlong
-nanosec(void)
-{
-	static time_t sec0;
-	struct timespec t;
-
-	if(clock_gettime(CLOCK_MONOTONIC, &t) != 0)
-		fatal("clock_gettime");
-	if(sec0 == 0)
-		sec0 = t.tv_sec;
-	t.tv_sec -= sec0;
-	return t.tv_sec*1000000000ULL + t.tv_nsec;
-}
-
-double
-dtime(void)
-{
-	return nanosec()/1000000000.0;
-}
-
-void
-game_shutdown(void)
-{
-	stopfb();
-	Host_Shutdown();
-	exit(0);
-}
-
-int
-main(int argc, char **argv)
-{
-	double t, t2, dt;
-	struct parg_state ps;
-	int c, nargs;
-	static char *paths[] = {
-		"/usr/games/quake",
-		nil,
-		nil,
-	};
-
-	parg_init(&ps);
-	nargs = 0;
-	while((c = parg_getopt(&ps, argc, argv, "Ddg:")) >= 0){
-		switch(c){
-		case 1:
-			argv[nargs++] = (char*)ps.optarg;
-			break;
-		case 'D':
-			debug++;
-			break;
-		case 'd':
-			dedicated = 1;
-			break;
-		case 'g':
-			game = (char*)ps.optarg;
-			break;
-		case 'h':
-			fprintf(stderr, "usage: qk1 [-g game]\n");
-			return 0;
-			break;
-		case '?':
-			fprintf(stderr, "unknown option -%c\n", ps.optopt);
-			return 1;
-			break;
-		default:
-			fprintf(stderr, "unhandled option -%c\n", c);
-			return 1;
-			break;
-		}
-	}
-
-	srand(nanosec() + time(nil));
-
-	paths[1] = strdup(va("%s/.quake", getenv("HOME")));
-	Host_Init(nargs, argv, paths);
-
-	fesetround(FE_TOWARDZERO);
-
-	t = dtime() - 1.0 / Fpsmax;
-	for(;;){
-		t2 = dtime();
-		dt = t2 - t;
-		if(cls.state == ca_dedicated){
-			if(dt < sys_ticrate.value)
-				continue;
-			dt = sys_ticrate.value;
-        }
-		if(dt > sys_ticrate.value * 2)
-			t = t2;
-		else
-			t += dt;
-		Host_Frame(dt);
-	}
-	return 0;
-}
--- a/unix/seprint.c
+++ /dev/null
@@ -1,19 +1,0 @@
-#include <stdio.h>
-#include <stdarg.h>
-
-char *
-seprint(char *buf, char *e, char *fmt, ...)
-{
-	va_list a;
-	int n, m;
-
-	if(e <= buf)
-		return e;
-
-	va_start(a, fmt);
-	m = e-buf-1;
-	n = vsnprintf(buf, m, fmt, a);
-	va_end(a);
-
-	return buf + (n < m ? n : m);
-}
--- a/unix/snd_openal.c
+++ /dev/null
@@ -1,880 +1,0 @@
-#include "quakedef.h"
-#include <AL/al.h>
-#include <AL/alc.h>
-#include <AL/alext.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-typedef struct albuf_t albuf_t;
-typedef struct alchan_t alchan_t;
-
-struct albuf_t {
-	ALuint buf;
-	bool loop;
-	bool upsample;
-};
-
-struct alchan_t {
-	int ent;
-	int ch;
-	ALuint src;
-
-	alchan_t *prev, *next;
-};
-
-enum {
-	Srcstatic = -666,
-	Srcamb,
-};
-
-cvar_t volume = {"volume", "0.7", true};
-
-static struct {
-	ALuint src, buf;
-	int pcm;
-	bool playing;
-	pid_t decoder, reader;
-}track;
-
-static cvar_t s_al_dev = {"s_al_device", "0", true};
-static int s_al_dev_prev = -1;
-
-static cvar_t s_al_hrtf = {"s_al_hrtf", "0", true};
-static cvar_t s_al_doppler_factor = {"s_al_doppler_factor", "2", true};
-static cvar_t s_al_resampler_default = {"s_al_resampler_default", "6", true}; // 23rd order Sinc
-static cvar_t s_al_resampler_up = {"s_al_resampler_up", "1", true}; // Linear
-
-static ALCcontext *ctx;
-static ALCdevice *dev;
-static Sfx *known_sfx;
-static int num_sfx;
-static int map;
-static alchan_t *chans;
-
-static cvar_t ambient_level = {"ambient_level", "0.3"};
-static cvar_t ambient_fade = {"ambient_fade", "100"};
-static Sfx *ambsfx[Namb];
-static float ambvol[Namb];
-
-static int al_default_resampler, al_num_resamplers;
-static ALchar *(*qalGetStringiSOFT)(ALenum, ALsizei);
-static ALCchar *(*qalcGetStringiSOFT)(ALCdevice *, ALenum, ALsizei);
-static ALCboolean (*qalcResetDeviceSOFT)(ALCdevice *, const ALCint *attr);
-static ALCboolean *(*qalcReopenDeviceSOFT)(ALCdevice *, const ALCchar *devname, const ALCint *attr);
-static void (*qalBufferCallbackSOFT)(ALuint buf, ALenum fmt, ALsizei freq, ALBUFFERCALLBACKTYPESOFT cb, ALvoid *aux);
-
-#define ALERR() alcheckerr(__FILE__, __LINE__)
-
-static int
-alcheckerr(const char *file, int line)
-{
-	int e, ret;
-	char *s, tmp[32];
-
-	ret = 0;
-	if(ctx != nil && (e = alGetError()) != AL_NO_ERROR){
-		switch(e){
-		case AL_INVALID_NAME: s = "invalid name"; break;
-		case AL_INVALID_ENUM: s = "invalid enum"; break;
-		case AL_INVALID_VALUE: s = "invalid value"; break;
-		case AL_INVALID_OPERATION: s = "invalid operation"; break;
-		case AL_OUT_OF_MEMORY: s = "out of memory"; break;
-		default:
-			snprint(tmp, sizeof(tmp), "unknown (0x%x)", e);
-			s = tmp;
-			break;
-		}
-		ret |= e;
-		fprintf(stderr, "AL: %s:%d: %s\n", file, line, s);
-	}
-	if(dev != nil && (e = alcGetError(dev)) != ALC_NO_ERROR){
-		switch(e){
-		case ALC_INVALID_DEVICE: s = "invalid device"; break;
-		case ALC_INVALID_ENUM: s = "invalid enum"; break;
-		case ALC_INVALID_VALUE: s = "invalid value"; break;
-		case ALC_INVALID_CONTEXT: s = "invalid context"; break;
-		case ALC_OUT_OF_MEMORY: s = "out of memory"; break;
-		default:
-			snprint(tmp, sizeof(tmp), "unknown error (0x%x)", e);
-			s = tmp;
-			break;
-		}
-		ret |= e;
-		fprintf(stderr, "ALC: %s:%d: %s\n", file, line, s);
-	}
-
-	return ret;
-}
-
-static alchan_t *
-getchan(int ent, int ch)
-{
-	alchan_t *c, *stopped;
-	ALint state;
-	ALuint src;
-
-	stopped = nil;
-	for(c = chans; c != nil; c = c->next){
-		if(c->ent == ent && c->ch == ch)
-			return c;
-		if(stopped == nil){
-			alGetSourcei(c->src, AL_SOURCE_STATE, &state);
-			if(!ALERR() && state != AL_PLAYING)
-				stopped = c;
-		}
-	}
-
-	if(stopped != nil){
-		c = stopped;
-		c->ent = ent;
-		c->ch = ch;
-		return c;
-	}
-
-	alGenSources(1, &src);
-	if(ALERR())
-		return nil;
-
-	c = calloc(1, sizeof(*c));
-	c->ent = ent;
-	c->ch = ch;
-	c->src = src;
-	c->next = chans;
-	if(chans != nil)
-		chans->prev = c;
-	chans = c;
-
-	return c;
-}
-
-static void
-delchan(alchan_t *c)
-{
-	if(c->prev != nil)
-		c->prev->next = c->next;
-	if(c->next != nil)
-		c->next->prev = c->prev;
-	if(chans == c)
-		chans = c->next;
-	alSourceStop(c->src); ALERR();
-	alSourcei(c->src, AL_BUFFER, 0); ALERR();
-	alDeleteSources(1, &c->src); ALERR();
-	free(c);
-}
-
-static Sfx *
-findsfx(char *s)
-{
-	Sfx *sfx, *e;
-	albuf_t *b;
-
-	if(strlen(s) >= Npath)
-		Host_Error("findsfx: path too long %s", s);
-	sfx = known_sfx;
-	e = known_sfx + num_sfx;
-	while(sfx < e){
-		if(strcmp(sfx->s, s) == 0){
-			sfx->map = map;
-			return sfx;
-		}
-		sfx++;
-	}
-	if(num_sfx == MAX_SOUNDS){
-		sfx = known_sfx;
-		while(sfx < e){
-			if(sfx->map && sfx->map < map)
-				break;
-			sfx++;
-		}
-		if(sfx == e)
-			Host_Error("findsfx: sfx list overflow: %s", s);
-		if((b = Cache_Check(&sfx->cu)) != nil){
-			alDeleteBuffers(1, &b->buf); ALERR();
-			Cache_Free(&sfx->cu);
-			sfx->map = map;
-		}
-	}else
-		num_sfx++;
-	strcpy(sfx->s, s);
-	return sfx;
-}
-
-static albuf_t *
-loadsfx(Sfx *sfx)
-{
-	ALint loop[2];
-	wavinfo_t info;
-	ALuint buf;
-	ALenum fmt;
-	albuf_t *b;
-	byte *in;
-	int len;
-
-	if((b = Cache_Check(&sfx->cu)) != nil)
-		return b;
-	in = loadstklmp(va("sound/%s", sfx->s), nil, 0, &len);
-	if(in == nil){
-		Con_DPrintf("loadsfx: %s\n", lerr());
-		return nil;
-	}
-	if(wavinfo(in, len, &info) != 0){
-		Con_Printf("loadsfx: %s: %s\n", sfx->s, lerr());
-		// complain but get some silence in place so it looks like it got loaded
-		memset(&info, 0, sizeof(info));
-		info.width = 8;
-		info.channels = 1;
-		info.rate = 11025;
-		info.loopofs = -1;
-	}
-	if(info.channels < 2)
-		fmt = info.width == 1 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16;
-	else
-		fmt = info.width == 1 ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16;
-	alGenBuffers(1, &buf);
-	if(ALERR())
-		return nil;
-	alBufferData(buf, fmt, in+info.dataofs, info.samples*info.width, info.rate);
-	if(ALERR()){
-		alDeleteBuffers(1, &buf); ALERR();
-		return nil;
-	}
-	b = Cache_Alloc(&sfx->cu, sizeof(*b));
-	b->buf = buf;
-	if(info.loopofs >= 0){
-		loop[0] = info.loopofs;
-		loop[1] = info.samples;
-		alBufferiv(b->buf, AL_LOOP_POINTS_SOFT, loop); ALERR();
-		b->loop = true;
-	}
-	b->upsample = info.rate < 22050;
-
-	return b;
-}
-
-static void
-alplay(alchan_t *c, albuf_t *b, vec3_t zp, float vol, float att, bool rel, bool loop)
-{
-	ALint src;
-	int n;
-
-	src = c->src;
-	if(rel){
-		alSourcefv(src, AL_POSITION, vec3_origin); ALERR();
-		alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); ALERR();
-		alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); ALERR();
-		alSourcef(src, AL_REFERENCE_DISTANCE, 0.0f); ALERR();
-	}else{
-		alSourcefv(src, AL_POSITION, zp); ALERR();
-		alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); ALERR();
-		alSourcef(src, AL_ROLLOFF_FACTOR, att * 8.191); ALERR();
-		alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); ALERR();
-		alSourcef(src, AL_MAX_DISTANCE, 8192.0f); ALERR();
-	}
-	alSourcef(src, AL_GAIN, vol); ALERR();
-	if(al_num_resamplers > 0){
-		n = b->upsample ? s_al_resampler_up.value : s_al_resampler_default.value;
-		if(n >= 0){
-			alSourcei(src, AL_SOURCE_RESAMPLER_SOFT, n);
-			ALERR();
-		}
-	}
-	alSourcei(src, AL_BUFFER, b->buf); ALERR();
-	alSourcei(src, AL_LOOPING, (b->loop || loop) ? AL_TRUE : AL_FALSE); ALERR();
-	alSourcePlay(src); ALERR();
-}
-
-
-static void
-ambs(vec3_t org)
-{
-	float vol, *av;
-	ALint state;
-	alchan_t *ch;
-	uchar *asl;
-	mleaf_t *l;
-	albuf_t *b;
-	Sfx *sfx;
-	int i;
-
-	if(cl.worldmodel == nil)
-		return;
-	l = Mod_PointInLeaf(org, cl.worldmodel);
-	asl = l->ambient_sound_level;
-	for(i = 0; i < Namb; i++){
-		if((sfx = ambsfx[i]) == nil || (b = loadsfx(sfx)) == nil)
-			continue;
-		vol = ambient_level.value * asl[i];
-		av = &ambvol[i];
-		if(vol < 8)
-			vol = 0;
-		if(*av < vol){
-			*av += host_frametime * ambient_fade.value;
-			if(*av > vol)
-				*av = vol;
-		}else if(*av > vol){
-			*av -= host_frametime * ambient_fade.value;
-			if(*av < vol)
-				*av = vol;
-		}
-		if((ch = getchan(Srcamb, i)) != nil){
-			alSourcef(ch->src, AL_GAIN, *av / 255.0f); ALERR();
-			alGetSourcei(ch->src, AL_SOURCE_STATE, &state);
-			if(!ALERR() && state != AL_PLAYING)
-				alplay(ch, b, vec3_origin, *av, 0.0f, true, true);
-		}
-	}
-}
-
-void
-stepsnd(vec3_t zp, vec3_t fw, vec3_t rt, vec3_t up)
-{
-	vec_t fwup[6] = {fw[0], fw[1], fw[2], up[0], up[1], up[2]};
-	alchan_t *c, *next;
-	static vec3_t ozp;
-	ALint state;
-	vec3_t vel;
-
-	if(dev == nil)
-		return;
-	if(zp == vec3_origin && fw == vec3_origin && rt == vec3_origin){
-		alListenerf(AL_GAIN, 0);
-		ALERR();
-		return;
-	}
-
-	alListenerfv(AL_POSITION, zp); ALERR();
-	alListenerfv(AL_ORIENTATION, fwup); ALERR();
-	VectorSubtract(zp, ozp, vel);
-	VectorCopy(zp, ozp);
-	alListenerfv(AL_VELOCITY, vel); ALERR();
-	alListenerf(AL_GAIN, volume.value); ALERR();
-
-	ambs(zp);
-
-	for(c = chans; c != nil; c = next){
-		next = c->next;
-		alGetSourcei(c->src, AL_SOURCE_STATE, &state);
-		if(!ALERR() && state != AL_PLAYING)
-			delchan(c);
-	}
-}
-
-void
-stopallsfx(void)
-{
-	alchan_t *c, *next;
-
-	if(dev == nil)
-		return;
-	alListenerf(AL_GAIN, 0); ALERR();
-	for(c = chans; c != nil; c = next){
-		next = c->next;
-		delchan(c);
-	}
-	chans = nil;
-	memset(ambvol, 0, sizeof(ambvol));
-}
-
-void
-stopsfx(int ent, int ch)
-{
-	alchan_t *c;
-
-	if(dev == nil)
-		return;
-	for(c = chans; c != nil; c = c->next){
-		if(c->ent == ent && c->ch == ch)
-			break;
-	}
-	if(c == nil)
-		return;
-	if(c->prev != nil)
-		c->prev->next = c->next;
-	if(c->next != nil)
-		c->next->prev = c->prev;
-	if(chans == c)
-		chans = c->next;
-	delchan(c);
-	if(ent == Srcamb)
-		ambvol[ch] = 0;
-}
-
-void
-startsfx(int ent, int ch, Sfx *sfx, vec3_t zp, float vol, float att)
-{
-	alchan_t *c;
-	albuf_t *b;
-
-	if(dev == nil || (b = loadsfx(sfx)) == nil || (c = getchan(ent, ch)) == nil)
-		return;
-	alSourceStop(c->src); ALERR();
-	alplay(c, b, zp, vol, att, ent == cl.viewentity, ent == Srcstatic);
-}
-
-void
-localsfx(char *name)
-{
-	if(dev == nil)
-		return;
-
-	startsfx(cl.viewentity, -1, findsfx(name), vec3_origin, 1.0f, 1.0f);
-}
-
-void
-staticsfx(Sfx *sfx, vec3_t zp, float vol, float att)
-{
-	static int numst = 0;
-
-	if(dev == nil)
-		return;
-
-	startsfx(Srcstatic, numst++, sfx, zp, vol, att/1.5f);
-}
-
-void
-touchsfx(char *s)
-{
-	Sfx *sfx;
-
-	if(dev == nil)
-		return;
-	sfx = findsfx(s);
-	Cache_Check(&sfx->cu);
-}
-
-Sfx *
-precachesfx(char *s)
-{
-	Sfx *sfx;
-
-	if(dev == nil)
-		return nil;
-	sfx = findsfx(s);
-	sfx->map = map;
-	loadsfx(sfx);
-	return sfx;
-}
-
-static ALCint *
-alcattr(bool hrtf)
-{
-	static ALCint attr[] = {
-		0, 0, 0, 0, 0,
-	};
-
-	attr[0] = 0;
-	if(hrtf){
-		attr[0] = s_al_hrtf.value != 0 ? ALC_HRTF_SOFT : 0;
-		attr[1] = s_al_hrtf.value != 0 ? (s_al_hrtf.value > 0 ? ALC_TRUE : ALC_FALSE) : ALC_DONT_CARE_SOFT;
-		if(attr[1] == ALC_TRUE){
-			attr[2] = ALC_HRTF_ID_SOFT;
-			attr[3] = s_al_hrtf.value - 1;
-		}
-	}
-
-	return attr;
-}
-
-static int
-alinit(const char *devname)
-{
-	ALCcontext *c;
-	int e;
-
-	dev = alcOpenDevice(devname); ALERR();
-	if(dev == nil)
-		return -1;
-
-	c = alcCreateContext(dev, nil); ALERR();
-	if(c == nil){
-closedev:
-		alcCloseDevice(dev); ALERR();
-		dev = nil;
-		return -1;
-	}
-	ctx = c;
-	e = alcMakeContextCurrent(c); ALERR();
-	if(!e){
-		ctx = nil;
-		alcDestroyContext(c); ALERR();
-		goto closedev;
-	}
-	alListenerf(AL_GAIN, volume.value); ALERR();
-	alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); ALERR();
-
-	// assuming 64 Quake units is ~1.7m
-	alSpeedOfSound(343.3 * 64.0 / 1.7); ALERR();
-	alDopplerFactor(s_al_doppler_factor.value); ALERR();
-
-	if(alIsExtensionPresent("AL_SOFT_source_resampler")){
-		al_default_resampler = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
-		if(ALERR())
-			al_default_resampler = 0;
-		al_num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
-		if(ALERR())
-			al_num_resamplers = 0;
-		qalGetStringiSOFT = alGetProcAddress("alGetStringiSOFT");
-	}else{
-		qalGetStringiSOFT = nil;
-	}
-
-	qalBufferCallbackSOFT = nil;
-	if(alIsExtensionPresent("AL_SOFT_callback_buffer") && (alGenSources(1, &track.src), !ALERR())){
-		alSourcei(track.src, AL_SOURCE_SPATIALIZE_SOFT, AL_FALSE); ALERR();
-		qalBufferCallbackSOFT = alGetProcAddress("alBufferCallbackSOFT");
-		alGenBuffers(1, &track.buf); ALERR();
-	}
-
-	return 0;
-}
-
-static void
-alreinit(const char *def, const char *all)
-{
-	const char *s;
-	int i, n;
-
-	n = s_al_dev.value;
-	if(n == s_al_dev_prev)
-		return;
-	if(qalcReopenDeviceSOFT == nil && alcIsExtensionPresent(nil, "ALC_SOFT_reopen_device"))
-		qalcReopenDeviceSOFT = alGetProcAddress("alcReopenDeviceSOFT");
-	if(qalcReopenDeviceSOFT == nil){
-		Con_Printf("AL: can't change device settings on the fly\n");
-		return;
-	}
-	for(i = 1, s = all; s != nil && *s; i++){
-		if((n == 0 && def != nil && strcmp(s, def) == 0) || n == i){
-			if(dev == nil)
-				n = alinit(all);
-			else{
-				n = qalcReopenDeviceSOFT(dev, s, alcattr(false)) ? 0 : -1;
-				ALERR();
-			}
-			if(n != 0)
-				Con_Printf("AL: failed to switch to %s\n", s);
-			else
-				s_al_dev_prev = n;
-			return;
-		}
-		s += strlen(s)+1;
-	}
-	Con_Printf("AL: no such device: %d\n", n);
-}
-
-static void
-alvarcb(cvar_t *var)
-{
-	const char *all, *def, *s;
-	bool hrtf;
-	int i, n;
-
-	def = alcGetString(nil, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
-	if(ALERR())
-		def = nil;
-	all = alcGetString(nil, ALC_ALL_DEVICES_SPECIFIER);
-	if(ALERR())
-		all = nil;
-
-	if(var == &s_al_dev && Cmd_Argc() == 1){
-		Con_Printf("%-2d: default (%s)\n", 0, def ? def : "<invalid>");
-		for(i = 1, s = all; s != nil && *s; i++){
-			Con_Printf("%-2d: %s%s\n", i, s, strcmp(s, def) == 0 ? " (default)" : "");
-			s += strlen(s)+1;
-		}
-		return;
-	}
-
-	alreinit(def, all);
-
-	if(alcIsExtensionPresent(dev, "ALC_SOFT_HRTF")){
-		qalcGetStringiSOFT = alcGetProcAddress(dev, "alcGetStringiSOFT");
-		qalcResetDeviceSOFT = alcGetProcAddress(dev, "alcResetDeviceSOFT");
-		hrtf = true;
-	}else{
-		qalcGetStringiSOFT = nil;
-		qalcResetDeviceSOFT = nil;
-		hrtf = false;
-	}
-	if(var == &s_al_hrtf && Cmd_Argc() == 1){
-		Con_Printf("%-2d: disabled\n", -1);
-		Con_Printf("%-2d: don't care (default)\n", 0);
-		if(qalcGetStringiSOFT == nil)
-			return;
-		alcGetIntegerv(dev, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &n); ALERR();
-		for(i = 0; i < n; i++){
-			s = qalcGetStringiSOFT(dev, ALC_HRTF_SPECIFIER_SOFT, i);
-			if(!ALERR() && s != nil)
-				Con_Printf("%-2d: %s\n", i+1, s);
-		}
-		return;
-	}
-
-	if(qalcResetDeviceSOFT != nil){
-		qalcResetDeviceSOFT(dev, alcattr(hrtf));
-		ALERR();
-	}
-}
-
-static void
-aldopplercb(cvar_t *var)
-{
-	alDopplerFactor(var->value); ALERR();
-}
-
-static void
-sfxlist(void)
-{
-	int sz, sum, w, ch;
-	Sfx *sfx, *e;
-	albuf_t *b;
-
-	sum = 0;
-	for(sfx = known_sfx, e = known_sfx+num_sfx; sfx < e; sfx++){
-		if((b = Cache_Check(&sfx->cu)) == nil)
-			continue;
-		alGetBufferi(b->buf, AL_SIZE, &sz); ALERR();
-		alGetBufferi(b->buf, AL_CHANNELS, &ch); ALERR();
-		alGetBufferi(b->buf, AL_BITS, &w); ALERR();
-		sum += sz * ch * w/8;
-		Con_Printf("%c(%2db) %6d : %s\n", b->loop ? 'L' : ' ', w, sz, sfx->s);
-	}
-	Con_Printf("Total resident: %d\n", sum);
-}
-
-void
-sfxbegin(void)
-{
-	map++;
-}
-
-void
-sfxend(void)
-{
-	albuf_t *b;
-	Sfx *sfx;
-	int i;
-
-	ambsfx[Ambwater] = precachesfx("ambience/water1.wav");
-	ambsfx[Ambsky] = precachesfx("ambience/wind2.wav");
-	for(i = 0, sfx = known_sfx; i < num_sfx; i++, sfx++){
-		if(sfx->map >= map || sfx == ambsfx[Ambsky] || sfx == ambsfx[Ambwater])
-			continue;
-		if((b = Cache_Check(&sfx->cu)) != nil){
-			alDeleteBuffers(1, &b->buf); ALERR();
-			Cache_Free(&sfx->cu);
-		}
-	}
-}
-
-void
-stepcd(void)
-{
-	alSourcef(track.src, AL_GAIN, bgmvolume.value); ALERR();
-}
-
-static ALsizei
-trackcb(ALvoid *aux, ALvoid *sampledata, ALsizei numbytes)
-{
-	ssize_t n;
-	byte *b;
-
-	USED(aux);
-	for(b = sampledata; numbytes > 0; b += n, numbytes -= n){
-		if((n = read(track.pcm, b, numbytes)) <= 0){
-			close(track.pcm);
-			track.pcm = -1;
-			break;
-		}
-	}
-
-	return b - (byte*)sampledata;
-}
-
-void
-playcd(int nt, bool loop)
-{
-	pid_t pid;
-	FILE *f;
-	fpos_t off;
-	int len, left, s[2], in[2];
-
-	stopcd();
-	if(qalBufferCallbackSOFT == nil)
-		return;
-
-	if((f = openlmp(va("music/track%02d.ogg", nt), &len)) == nil){
-		if((f = openlmp(va("music/track%02d.mp3", nt), &len)) == nil)
-			f = openlmp(va("music/track%02d.wav", nt), &len);
-	}
-	if(f == nil)
-		return;
-	if(fgetpos(f, &off) != 0){
-err:
-		close(track.pcm);
-		fclose(f);
-		if(track.decoder > 0)
-			waitpid(track.decoder, nil, 0);
-		if(track.reader > 0)
-			waitpid(track.reader, nil, 0);
-		return;
-	}
-
-	if(pipe(s) != 0)
-		goto err;
-	if(pipe(in) != 0){
-		close(s[0]);
-		close(s[1]);
-		goto err;
-	}
-
-	switch((pid = fork())){
-	case 0:
-		close(s[1]); dup2(s[0], 0);
-		close(in[0]); dup2(in[1], 1);
-		close(s[0]);
-		execl(
-			"/usr/bin/env", "/usr/bin/env",
-			"ffmpeg",
-				"-loglevel", "fatal",
-				"-i", "-",
-				"-acodec", "pcm_s16le",
-				"-f", "s16le",
-				"-ac", "2",
-				"-ar", "44100",
-				"-",
-			nil
-		);
-		perror("execl ffmpeg"); // FIXME(sigrid): add and use Con_Errorf?
-		exit(1);
-	case -1:
-		goto err;
-	}
-	track.decoder = pid;
-
-	close(s[0]);
-	close(in[1]);
-	track.pcm = in[0];
-	cdloop = loop;
-	cdtrk = nt;
-
-	switch((pid = fork())){
-	case 0:
-		close(in[0]);
-		close(0);
-		close(1);
-		left = len;
-		for(;;){
-			byte tmp[32768];
-			ssize_t n;
-			if((n = fread(tmp, 1, min(left, (int)sizeof(tmp)), f)) < 1){
-				if(ferror(f)){
-					perror("fread");
-					break;
-				}
-			}
-			if(write(s[1], tmp, n) != n)
-				break;
-			left -= n;
-			if(left < 1){
-				if(!loop)
-					break;
-				if(fsetpos(f, &off) != 0){
-					perror("fsetpos");
-					break;
-				}
-				left = len;
-			}
-		}
-		close(s[1]);
-		fclose(f);
-		exit(1);
-	case -1:
-		goto err;
-	}
-	track.reader = pid;
-
-	qalBufferCallbackSOFT(track.buf, AL_FORMAT_STEREO16, 44100, trackcb, nil);
-	if(ALERR())
-		goto err;
-	alSourcei(track.src, AL_BUFFER, track.buf); ALERR();
-	alSourcePlay(track.src); ALERR();
-	track.playing = true;
-}
-
-void
-resumecd(void)
-{
-	alSourcePlay(track.src); ALERR();
-}
-
-void
-pausecd(void)
-{
-	alSourcePause(track.src); ALERR();
-}
-
-int
-initcd(void)
-{
-	cdntrk = cdtrk = 0;
-	cdloop = false;
-	return 0;
-}
-
-void
-stopcd(void)
-{
-	if(track.playing){
-		alSourceStop(track.src); ALERR();
-		alSourcei(track.src, AL_BUFFER, 0); ALERR();
-		if(track.pcm >= 0)
-			close(track.pcm);
-		waitpid(track.decoder, nil, 0);
-		waitpid(track.reader, nil, 0);
-	}
-	track.playing = false;
-	track.pcm = -1;
-	track.decoder = track.reader = -1;
-}
-
-void
-shutcd(void)
-{
-	stopcd();
-}
-
-int
-initsnd(void)
-{
-	s_al_dev.cb = s_al_hrtf.cb = alvarcb;
-	s_al_doppler_factor.cb = aldopplercb;
-
-	Cvar_RegisterVariable(&volume);
-	Cvar_RegisterVariable(&bgmvolume);
-	Cvar_RegisterVariable(&ambient_level);
-	Cvar_RegisterVariable(&ambient_fade);
-	Cvar_RegisterVariable(&s_al_dev);
-	Cvar_RegisterVariable(&s_al_resampler_default);
-	Cvar_RegisterVariable(&s_al_resampler_up);
-	Cvar_RegisterVariable(&s_al_hrtf);
-	Cvar_RegisterVariable(&s_al_doppler_factor);
-	Cmd_AddCommand("stopsound", stopallsfx);
-	Cmd_AddCommand("soundlist", sfxlist);
-	Cmd_AddCommand("cd", cdcmd);
-
-	alinit(nil);
-	known_sfx = Hunk_Alloc(MAX_SOUNDS * sizeof *known_sfx);
-	num_sfx = 0;
-
-	return 0;
-}
-
-void
-sndclose(void)
-{
-	if(dev == nil)
-		return;
-	alcDestroyContext(ctx); ctx = nil; ALERR();
-	alcCloseDevice(dev); dev = nil; ALERR();
-}
--- a/unix/vid.c
+++ /dev/null
@@ -1,133 +1,0 @@
-#include "quakedef.h"
-#include <SDL.h>
-
-int resized;
-
-pixel_t q1pal[256];
-
-static SDL_Renderer *rend;
-static SDL_Texture *fbi;
-static SDL_Window *win;
-static pixel_t *vidbuffer;
-extern pixel_t *r_warpbuffer;
-
-static void
-resetfb(void)
-{
-	void *surfcache;
-	int hunkvbuf, scachesz;
-
-	/* lower than 320x240 doesn't really make sense,
-	 * but at least this prevents a crash, beyond that
-	 * it's your funeral */
-	SDL_GetWindowSize(win, &vid.width, &vid.height);
-	if(vid.width < 320)
-		vid.width = 320;
-	if(vid.height < 160)
-		vid.height = 160;
-
-	vid.rowbytes = vid.width;
-	vid.aspect = (float)vid.height / (float)vid.width * (320.0/240.0);
-	vid.conrowbytes = vid.rowbytes;
-	vid.conwidth = vid.width;
-	vid.conheight = vid.height;
-
-	free(vidbuffer);
-	vidbuffer = emalloc((vid.width*vid.height+16)*sizeof(pixel_t));
-	free(r_warpbuffer);
-	r_warpbuffer = emalloc((vid.width*vid.height+16)*sizeof(pixel_t));
-	vid.maxwarpwidth = vid.width;
-	vid.maxwarpheight = vid.height;
-
-	if(fbi != nil)
-		SDL_DestroyTexture(fbi);
-	fbi = SDL_CreateTexture(rend, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, vid.width, vid.height);
-	if(fbi == NULL)
-		fatal("SDL_CreateTexture: %s", SDL_GetError());
-	SDL_SetTextureBlendMode(fbi, SDL_BLENDMODE_NONE);
-	SDL_RenderClear(rend);
-
-	vid.buffer = vidbuffer;
-	vid.conbuffer = vid.buffer;
-
-	if(d_pzbuffer != nil){
-		D_FlushCaches();
-		free(d_pzbuffer);
-		d_pzbuffer = nil;
-	}
-
-	// alloc an extra line in case we want to wrap, and allocate the z-buffer
-	hunkvbuf = vid.width * vid.height * sizeof *d_pzbuffer;
-	scachesz = D_SurfaceCacheForRes(vid.width, vid.height);
-	hunkvbuf += scachesz;
-	d_pzbuffer = emalloc(hunkvbuf);
-	surfcache = (byte *)(d_pzbuffer + vid.width * vid.height);
-	D_InitCaches(surfcache, scachesz);
-}
-
-void
-stopfb(void)
-{
-}
-
-void
-flipfb(void)
-{
-	int pitch;
-	void *p;
-
-	if(resized){		/* skip this frame if window resize */
-		stopfb();
-		resized = 0;
-		resetfb();
-		vid.recalc_refdef = true;	/* force a surface cache flush */
-		Con_CheckResize();
-		Con_Clear_f();
-		return;
-	}
-
-	SDL_LockTexture(fbi, NULL, &p, &pitch);
-	memmove(p, vidbuffer, vid.width*vid.height*4);
-	SDL_UnlockTexture(fbi);
-	SDL_RenderCopy(rend, fbi, NULL, NULL);
-	SDL_RenderPresent(rend);
-}
-
-void
-setpal(uchar *p0)
-{
-	int x;
-	uchar *p;
-
-	for(p = p0, x = 0; x < 256; x++, p += 3)
-		q1pal[x] = (x < 256-32 ? 0xff : 0)<<24 | p[0]<<16 | p[1]<<8 | p[2];
-	q1pal[255] &= 0;
-
-	scr_fullupdate = 0;
-}
-
-void
-initfb(void)
-{
-	vid.numpages = 2;
-	vid.colormap = malloc(256*64*sizeof(pixel_t));
-	torgbx(host_colormap, vid.colormap, 256*64);
-	vid.fullbright = 256 - LittleLong(*((int *)vid.colormap + 2048));
-
-	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0)
-		fatal("SDL_Init: %s", SDL_GetError());
-	win = SDL_CreateWindow("quake", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
-	if(win == nil)
-		fatal("SDL_CreateWindow: %s", SDL_GetError());
-	if((rend = SDL_CreateRenderer(win, -1, 0)) == NULL)
-		fatal("SDL_CreateRenderer: %s", SDL_GetError());
-	SDL_SetRenderDrawColor(rend, 0, 0, 0, 255);
-	SDL_RenderClear(rend);
-	SDL_RenderPresent(rend);
-	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
-
-	resetfb();
-	SDL_SetWindowResizable(win, SDL_TRUE);
-	SDL_SetWindowMinimumSize(win, 320, 240);
-	IN_Grabm(1);
-}
--- a/vid.c
+++ /dev/null
@@ -1,153 +1,0 @@
-#include "quakedef.h"
-#include <draw.h>
-#include <thread.h>
-
-viddef_t vid;		/* global video state */
-int resized;
-Point center;		/* of window */
-Rectangle grabr;
-
-pixel_t q1pal[256];
-static Image *fbi;
-static Rectangle fbr;
-static pixel_t *vidbuffers[2];
-static int bufi = 0;
-static Channel *frame;
-
-void pal2xrgb(int n, s32int *pal, u8int *s, u32int *d);
-
-static void
-resetfb(void)
-{
-	static int highhunk;
-	void *surfcache;
-	int hunkvbuf, scachesz, i;
-	Point p;
-
-	/* lower than 320x240 doesn't really make sense,
-	 * but at least this prevents a crash, beyond that
-	 * it's your funeral */
-	vid.width = Dx(screen->r);
-	if(vid.width < 320)
-		vid.width = 320;
-	vid.height = Dy(screen->r);
-	if(vid.height < 160)
-		vid.height = 160;
-	if(d_pzbuffer != nil)
-		D_FlushCaches();
-
-	// alloc an extra line in case we want to wrap, and allocate the z-buffer
-	hunkvbuf = vid.width * vid.height * sizeof *d_pzbuffer;
-	scachesz = D_SurfaceCacheForRes(vid.width, vid.height);
-	hunkvbuf += scachesz;
-	if((d_pzbuffer = realloc(d_pzbuffer, hunkvbuf)) == nil)
-		sysfatal("%r");
-	surfcache = (byte*)(d_pzbuffer + vid.width * vid.height);
-	D_InitCaches(surfcache, scachesz);
-
-	vid.rowbytes = vid.width;
-	vid.aspect = (float)vid.height / (float)vid.width * (320.0/240.0);
-	vid.conrowbytes = vid.rowbytes;
-	vid.conwidth = vid.width;
-	vid.conheight = vid.height;
-
-	center = divpt(addpt(screen->r.min, screen->r.max), 2);
-	p = Pt(vid.width/2, vid.height/2);
-	fbr = Rpt(subpt(center, p), addpt(center, p));
-	p = Pt(vid.width/4, vid.height/4);
-	grabr = Rpt(subpt(center, p), addpt(center, p));
-	for(i = 0; i < nelem(vidbuffers); i++)
-		vidbuffers[i] = realloc(vidbuffers[i], (vid.width*vid.height+16)*sizeof(pixel_t));
-	free(r_warpbuffer);
-	r_warpbuffer = emalloc((vid.width*vid.height+16)*sizeof(pixel_t));
-	vid.maxwarpwidth = vid.width;
-	vid.maxwarpheight = vid.height;
-	freeimage(fbi);
-	fbi = allocimage(display, Rect(0, 0, vid.width, vid.height), XRGB32, 0, 0);
-	if(fbi == nil)
-		sysfatal("resetfb: %r (%d %d)", vid.width, vid.height);
-	vid.buffer = vidbuffers[bufi = 0];
-	vid.conbuffer = vid.buffer;
-	draw(screen, screen->r, display->black, nil, ZP);
-}
-
-static void
-loader(void *)
-{
-	byte *f;
-	Rectangle r;
-	int n;
-
-	r = Rect(0, 0, vid.width, vid.height);
-	n = vid.width * vid.height;
-	for(;;){
-		if((f = recvp(frame)) == nil)
-			break;
-		if(loadimage(fbi, r, f, n*4) != n*4)
-			sysfatal("%r");
-		draw(screen, fbr, fbi, nil, ZP);
-		if(flushimage(display, 1) < 0)
-			sysfatal("%r");
-	}
-	threadexits(nil);
-}
-
-void
-stopfb(void)
-{
-	if(frame != nil){
-		sendp(frame, nil);
-		chanclose(frame);
-		frame = nil;
-	}
-}
-
-void
-flipfb(void)
-{
-	if(resized){		/* skip this frame if window resize */
-		stopfb();
-		if(getwindow(display, Refnone) < 0)
-			sysfatal("%r");
-		resized = 0;
-		resetfb();
-		vid.recalc_refdef = true;	/* force a surface cache flush */
-		Con_CheckResize();
-		Con_Clear_f();
-		return;
-	}
-	if(frame == nil){
-		frame = chancreate(sizeof(pixel_t*), 0);
-		proccreate(loader, nil, 4096);
-	}
-	if(sendp(frame, vidbuffers[bufi]) > 0){
-		bufi = (bufi+1) % nelem(vidbuffers);
-		vid.buffer = vidbuffers[bufi];
-		vid.conbuffer = vid.buffer;
-	}
-}
-
-void
-setpal(uchar *p0)
-{
-	int x;
-	uchar *p;
-
-	for(p = p0, x = 0; x < 256; x++, p += 3)
-		q1pal[x] = (x < 256-32 ? 0xff : 0)<<24 | p[0]<<16 | p[1]<<8 | p[2];
-	q1pal[255] &= 0;
-
-	scr_fullupdate = 0;
-}
-
-void
-initfb(void)
-{
-	vid.numpages = 2;
-	vid.colormap = malloc(256*64*sizeof(pixel_t));
-	torgbx(host_colormap, vid.colormap, 256*64);
-	vid.fullbright = 256 - LittleLong(*((int *)vid.colormap + 2048));
-	if(initdraw(nil, nil, "quake") < 0)
-		sysfatal("initdraw: %r\n");
-	resetfb();
-}
--- /dev/null
+++ b/vid_plan9.c
@@ -1,0 +1,153 @@
+#include "quakedef.h"
+#include <draw.h>
+#include <thread.h>
+
+viddef_t vid;		/* global video state */
+int resized;
+Point center;		/* of window */
+Rectangle grabr;
+
+pixel_t q1pal[256];
+static Image *fbi;
+static Rectangle fbr;
+static pixel_t *vidbuffers[2];
+static int bufi = 0;
+static Channel *frame;
+
+void pal2xrgb(int n, s32int *pal, u8int *s, u32int *d);
+
+static void
+resetfb(void)
+{
+	static int highhunk;
+	void *surfcache;
+	int hunkvbuf, scachesz, i;
+	Point p;
+
+	/* lower than 320x240 doesn't really make sense,
+	 * but at least this prevents a crash, beyond that
+	 * it's your funeral */
+	vid.width = Dx(screen->r);
+	if(vid.width < 320)
+		vid.width = 320;
+	vid.height = Dy(screen->r);
+	if(vid.height < 160)
+		vid.height = 160;
+	if(d_pzbuffer != nil)
+		D_FlushCaches();
+
+	// alloc an extra line in case we want to wrap, and allocate the z-buffer
+	hunkvbuf = vid.width * vid.height * sizeof *d_pzbuffer;
+	scachesz = D_SurfaceCacheForRes(vid.width, vid.height);
+	hunkvbuf += scachesz;
+	if((d_pzbuffer = realloc(d_pzbuffer, hunkvbuf)) == nil)
+		sysfatal("%r");
+	surfcache = (byte*)(d_pzbuffer + vid.width * vid.height);
+	D_InitCaches(surfcache, scachesz);
+
+	vid.rowbytes = vid.width;
+	vid.aspect = (float)vid.height / (float)vid.width * (320.0/240.0);
+	vid.conrowbytes = vid.rowbytes;
+	vid.conwidth = vid.width;
+	vid.conheight = vid.height;
+
+	center = divpt(addpt(screen->r.min, screen->r.max), 2);
+	p = Pt(vid.width/2, vid.height/2);
+	fbr = Rpt(subpt(center, p), addpt(center, p));
+	p = Pt(vid.width/4, vid.height/4);
+	grabr = Rpt(subpt(center, p), addpt(center, p));
+	for(i = 0; i < nelem(vidbuffers); i++)
+		vidbuffers[i] = realloc(vidbuffers[i], (vid.width*vid.height+16)*sizeof(pixel_t));
+	free(r_warpbuffer);
+	r_warpbuffer = emalloc((vid.width*vid.height+16)*sizeof(pixel_t));
+	vid.maxwarpwidth = vid.width;
+	vid.maxwarpheight = vid.height;
+	freeimage(fbi);
+	fbi = allocimage(display, Rect(0, 0, vid.width, vid.height), XRGB32, 0, 0);
+	if(fbi == nil)
+		sysfatal("resetfb: %r (%d %d)", vid.width, vid.height);
+	vid.buffer = vidbuffers[bufi = 0];
+	vid.conbuffer = vid.buffer;
+	draw(screen, screen->r, display->black, nil, ZP);
+}
+
+static void
+loader(void *)
+{
+	byte *f;
+	Rectangle r;
+	int n;
+
+	r = Rect(0, 0, vid.width, vid.height);
+	n = vid.width * vid.height;
+	for(;;){
+		if((f = recvp(frame)) == nil)
+			break;
+		if(loadimage(fbi, r, f, n*4) != n*4)
+			sysfatal("%r");
+		draw(screen, fbr, fbi, nil, ZP);
+		if(flushimage(display, 1) < 0)
+			sysfatal("%r");
+	}
+	threadexits(nil);
+}
+
+void
+stopfb(void)
+{
+	if(frame != nil){
+		sendp(frame, nil);
+		chanclose(frame);
+		frame = nil;
+	}
+}
+
+void
+flipfb(void)
+{
+	if(resized){		/* skip this frame if window resize */
+		stopfb();
+		if(getwindow(display, Refnone) < 0)
+			sysfatal("%r");
+		resized = 0;
+		resetfb();
+		vid.recalc_refdef = true;	/* force a surface cache flush */
+		Con_CheckResize();
+		Con_Clear_f();
+		return;
+	}
+	if(frame == nil){
+		frame = chancreate(sizeof(pixel_t*), 0);
+		proccreate(loader, nil, 4096);
+	}
+	if(sendp(frame, vidbuffers[bufi]) > 0){
+		bufi = (bufi+1) % nelem(vidbuffers);
+		vid.buffer = vidbuffers[bufi];
+		vid.conbuffer = vid.buffer;
+	}
+}
+
+void
+setpal(uchar *p0)
+{
+	int x;
+	uchar *p;
+
+	for(p = p0, x = 0; x < 256; x++, p += 3)
+		q1pal[x] = (x < 256-32 ? 0xff : 0)<<24 | p[0]<<16 | p[1]<<8 | p[2];
+	q1pal[255] &= 0;
+
+	scr_fullupdate = 0;
+}
+
+void
+initfb(void)
+{
+	vid.numpages = 2;
+	vid.colormap = malloc(256*64*sizeof(pixel_t));
+	torgbx(host_colormap, vid.colormap, 256*64);
+	vid.fullbright = 256 - LittleLong(*((int *)vid.colormap + 2048));
+	if(initdraw(nil, nil, "quake") < 0)
+		sysfatal("initdraw: %r\n");
+	resetfb();
+}
--- /dev/null
+++ b/vid_sdl.c
@@ -1,0 +1,133 @@
+#include "quakedef.h"
+#include <SDL.h>
+
+int resized;
+
+pixel_t q1pal[256];
+
+static SDL_Renderer *rend;
+static SDL_Texture *fbi;
+static SDL_Window *win;
+static pixel_t *vidbuffer;
+extern pixel_t *r_warpbuffer;
+
+static void
+resetfb(void)
+{
+	void *surfcache;
+	int hunkvbuf, scachesz;
+
+	/* lower than 320x240 doesn't really make sense,
+	 * but at least this prevents a crash, beyond that
+	 * it's your funeral */
+	SDL_GetWindowSize(win, &vid.width, &vid.height);
+	if(vid.width < 320)
+		vid.width = 320;
+	if(vid.height < 160)
+		vid.height = 160;
+
+	vid.rowbytes = vid.width;
+	vid.aspect = (float)vid.height / (float)vid.width * (320.0/240.0);
+	vid.conrowbytes = vid.rowbytes;
+	vid.conwidth = vid.width;
+	vid.conheight = vid.height;
+
+	free(vidbuffer);
+	vidbuffer = emalloc((vid.width*vid.height+16)*sizeof(pixel_t));
+	free(r_warpbuffer);
+	r_warpbuffer = emalloc((vid.width*vid.height+16)*sizeof(pixel_t));
+	vid.maxwarpwidth = vid.width;
+	vid.maxwarpheight = vid.height;
+
+	if(fbi != nil)
+		SDL_DestroyTexture(fbi);
+	fbi = SDL_CreateTexture(rend, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, vid.width, vid.height);
+	if(fbi == NULL)
+		fatal("SDL_CreateTexture: %s", SDL_GetError());
+	SDL_SetTextureBlendMode(fbi, SDL_BLENDMODE_NONE);
+	SDL_RenderClear(rend);
+
+	vid.buffer = vidbuffer;
+	vid.conbuffer = vid.buffer;
+
+	if(d_pzbuffer != nil){
+		D_FlushCaches();
+		free(d_pzbuffer);
+		d_pzbuffer = nil;
+	}
+
+	// alloc an extra line in case we want to wrap, and allocate the z-buffer
+	hunkvbuf = vid.width * vid.height * sizeof *d_pzbuffer;
+	scachesz = D_SurfaceCacheForRes(vid.width, vid.height);
+	hunkvbuf += scachesz;
+	d_pzbuffer = emalloc(hunkvbuf);
+	surfcache = (byte *)(d_pzbuffer + vid.width * vid.height);
+	D_InitCaches(surfcache, scachesz);
+}
+
+void
+stopfb(void)
+{
+}
+
+void
+flipfb(void)
+{
+	int pitch;
+	void *p;
+
+	if(resized){		/* skip this frame if window resize */
+		stopfb();
+		resized = 0;
+		resetfb();
+		vid.recalc_refdef = true;	/* force a surface cache flush */
+		Con_CheckResize();
+		Con_Clear_f();
+		return;
+	}
+
+	SDL_LockTexture(fbi, NULL, &p, &pitch);
+	memmove(p, vidbuffer, vid.width*vid.height*4);
+	SDL_UnlockTexture(fbi);
+	SDL_RenderCopy(rend, fbi, NULL, NULL);
+	SDL_RenderPresent(rend);
+}
+
+void
+setpal(uchar *p0)
+{
+	int x;
+	uchar *p;
+
+	for(p = p0, x = 0; x < 256; x++, p += 3)
+		q1pal[x] = (x < 256-32 ? 0xff : 0)<<24 | p[0]<<16 | p[1]<<8 | p[2];
+	q1pal[255] &= 0;
+
+	scr_fullupdate = 0;
+}
+
+void
+initfb(void)
+{
+	vid.numpages = 2;
+	vid.colormap = malloc(256*64*sizeof(pixel_t));
+	torgbx(host_colormap, vid.colormap, 256*64);
+	vid.fullbright = 256 - LittleLong(*((int *)vid.colormap + 2048));
+
+	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0)
+		fatal("SDL_Init: %s", SDL_GetError());
+	win = SDL_CreateWindow("quake", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
+	if(win == nil)
+		fatal("SDL_CreateWindow: %s", SDL_GetError());
+	if((rend = SDL_CreateRenderer(win, -1, 0)) == NULL)
+		fatal("SDL_CreateRenderer: %s", SDL_GetError());
+	SDL_SetRenderDrawColor(rend, 0, 0, 0, 255);
+	SDL_RenderClear(rend);
+	SDL_RenderPresent(rend);
+	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
+
+	resetfb();
+	SDL_SetWindowResizable(win, SDL_TRUE);
+	SDL_SetWindowMinimumSize(win, 320, 240);
+	IN_Grabm(1);
+}