shithub: qk1

Download patch

ref: 68f10f24e4b06bfb645839f517ef8c68b898b393
parent: 9c69d793fb471dbbdff55431cbfdd585ff3e4deb
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Mon Nov 6 20:57:47 EST 2023

portable fs logic

--- a/Makefile
+++ b/Makefile
@@ -37,6 +37,7 @@
 	d_zpoint.o\
 	dotproduct.o\
 	draw.o\
+	fs.o\
 	host.o\
 	host_cmd.o\
 	keys.o\
@@ -72,7 +73,6 @@
 	sv_phys.o\
 	sv_user.o\
 	unix/cd.o\
-	unix/fs.o\
 	unix/in.o\
 	unix/net_udp.o\
 	unix/qk1.o\
--- a/cl_demo.c
+++ b/cl_demo.c
@@ -130,7 +130,7 @@
 	s = va("%s/%s%s", fsdir, a, ext(a, ".dem"));
 	Con_DPrintf("recdemo: writing to file %s\n", s);
 	if(opendm(s, trk) < 0){
-		Con_Printf(va("recdemo: %r\n"));
+		Con_Printf(va("recdemo: %s\n", lerr()));
 		return;
 	}
 	cls.demorecording = 1;
@@ -156,7 +156,7 @@
 	s = va("%s%s", a, ext(a, ".dem"));
 	Con_DPrintf("playdemo: reading file %s\n", s);
 	if(loaddm(s) < 0){
-		Con_Printf(va("playdemo: %r\n"));
+		Con_Printf(va("playdemo: %s\n", lerr()));
 		cls.demonum = -1;
 		return;
 	}
--- a/cmd.c
+++ b/cmd.c
@@ -271,7 +271,7 @@
 	mark = Hunk_Mark ();
 	f = loadhunklmp(Cmd_Argv(1), nil);
 	if(f == nil){
-		Con_Printf(va("exec: %r\n"));
+		Con_Printf(va("exec: %s\n", lerr()));
 		return;
 	}
 	Con_Printf ("execing %s\n",Cmd_Argv(1));
--- a/draw.c
+++ b/draw.c
@@ -66,7 +66,7 @@
 //
 	dat = loadcachelmp(path, &pic->cache);
 	if(dat == nil)
-		fatal("Draw_CachePic: %r");
+		fatal("Draw_CachePic: %s", lerr());
 
 	SwapPic (dat);
 
--- a/fns.h
+++ b/fns.h
@@ -47,7 +47,7 @@
 int	readdm(void);
 int	loaddm(char*);
 int	opendm(char*, int);
-void	initfs(void);
+void	initfs(char **paths);
 #pragma varargck	argpos	fatal	1
 void	fatal(char*, ...);
 void*	emalloc(ulong);
@@ -55,3 +55,6 @@
 double	dtime(void);
 void	game_shutdown(void);
 uvlong	nanosec(void);
+
+char *lerr(void);
+int	sys_mkdir(char *path);
--- a/fs.c
+++ b/fs.c
@@ -1,5 +1,4 @@
 #include "quakedef.h"
-#include <bio.h>
 
 u16int crcn;
 char fsdir[Nfspath];
@@ -25,7 +24,7 @@
 };
 struct Pak{
 	char f[Nfspath];
-	Biobuf *bf;
+	FILE *bf;
 	Lump *l;
 	Lump *e;
 };
@@ -101,7 +100,7 @@
 static int loadsize;
 static uchar *loadbuf;
 static mem_user_t *loadcache;
-static Biobuf *demobf;
+static FILE *demobf;
 static vlong demoofs;
 
 #define	GBIT32(p)	((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
@@ -155,29 +154,43 @@
 			Con_Printf("%s\n", pl->f);
 }
 
-static Biobuf *
-bopen(char *f, int m, int arm)
+static char fline[8192];
+static int flinelen;
+
+static char *
+frdline(FILE *bf)
 {
-	Biobuf *bf;
+	char *s;
 
-	bf = Bopen(f, m);
-	if(bf == nil)
-		return nil;
-	if(arm)
-		Blethal(bf, nil);
-	return bf;
+	flinelen = 0;
+	if((s = fgets(fline, sizeof(fline), bf)) != nil)
+		flinelen = strlen(fline);
+	return s;
 }
 
+static char *
+frdlinedup(FILE *bf)
+{
+	char *s;
+
+	if((s = frdline(bf)) != nil){
+		if(flinelen > 0 && s[flinelen-1] == '\n')
+			s[flinelen-1] = 0;
+		s = strdup(s);
+	}
+	return s;
+}
+
 static long
-eread(Biobuf *bf, void *u, long n)
+eread(FILE *bf, void *u, long n)
 {
-	if(Bread(bf, u, n) != n)
-		fatal("eread: short read: %r");
+	if((long)fread(u, 1, n, bf) != n)
+		fatal("eread: short read: %s", lerr());
 	return n;
 }
 
 static u8int
-get8(Biobuf *bf)
+get8(FILE *bf)
 {
 	u8int v;
 
@@ -186,7 +199,7 @@
 }
 
 static u16int
-get16(Biobuf *bf)
+get16(FILE *bf)
 {
 	u16int v;
 
@@ -195,7 +208,7 @@
 }
 
 static u32int
-get32(Biobuf *bf)
+get32(FILE *bf)
 {
 	u32int v;
 
@@ -204,7 +217,7 @@
 }
 
 static float
-getfl(Biobuf *bf)
+getfl(FILE *bf)
 {
 	union{
 		float v;
@@ -216,7 +229,7 @@
 }
 
 static u16int
-get16b(Biobuf *bf)
+get16b(FILE *bf)
 {
 	u16int v;
 
@@ -225,14 +238,14 @@
 }
 
 static void
-ewrite(Biobuf *bf, void *u, long n)
+ewrite(FILE *bf, void *u, long n)
 {
-	if(Bwrite(bf, u, n) != n)
+	if((long)fwrite(u, 1, n, bf) != n)
 		fatal("eread: short write: %r");
 }
 
 static void
-put32(Biobuf *bf, u32int v)
+put32(FILE *bf, u32int v)
 {
 	uchar u[4];
 
@@ -241,7 +254,7 @@
 }
 
 static void
-putfl(Biobuf *bf, float v)
+putfl(FILE *bf, float v)
 {
 	union{
 		float v;
@@ -252,55 +265,24 @@
 	put32(bf, w.u);
 }
 
-#ifdef __plan9__
 static vlong
-bsize(Biobuf *bf)
+bsize(FILE *bf)
 {
-	vlong n;
-	Dir *d;
+	vlong o, n;
 
-	d = dirfstat(Bfildes(bf));
-	if(d == nil)
-		fatal("bstat: %r");
-	n = d->length;
-	free(d);
+	o = ftell(bf);
+	fseek(bf, 0, SEEK_END);
+	n = ftell(bf);
+	fseek(bf, o, SEEK_SET);
+
 	return n;
 }
-#else
-static vlong
-bsize(Biobuf *bf)
-{
-	struct stat st;
 
-	if(fstat(Bfildes(bf), &st) != 0)
-		fatal("bstat");
-	return st.st_size;
-}
-#endif
-
-#ifdef __plan9__
 static int
-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;
-}
-#else
-#define mkdir(p) mkdir(p, 0770)
-#endif
-
-static int
 mkpath(char *path)
 {
 	char *d;
+	int r;
 
 	d = path;
 	if(d == nil || *d == 0)
@@ -310,17 +292,18 @@
 	while(*d != 0){
 		if(*d == '/'){
 			*d = 0;
-			if(mkdir(path) < 0)
-				return -1;
+			r = sys_mkdir(path);
 			*d = '/';
+			if(r < 0)
+				return -1;
 		}
 		d++;
 	}
-	return mkdir(path);
+	return sys_mkdir(path);
 }
 
 static void
-closelmp(Biobuf *bf)
+closelmp(FILE *bf)
 {
 	Paklist *pl;
 
@@ -327,14 +310,14 @@
 	for(pl=pkl; pl!=nil; pl=pl->pl)
 		if(pl->p && pl->p->bf == bf)
 			return;
-	Bterm(bf);
+	fclose(bf);
 }
 
-static Biobuf *
+static FILE *
 openlmp(char *f, int *len)
 {
 	char d[Nfspath+1];
-	Biobuf *bf;
+	FILE *bf;
 	Paklist *pl;
 	Pak *p;
 	Lump *l;
@@ -345,7 +328,7 @@
 			l = p->l;
 			while(l < p->e){
 				if(strcmp(l->f, f) == 0){
-					Bseek(p->bf, l->ofs, 0);
+					fseek(p->bf, l->ofs, SEEK_SET);
 					if(len != nil)
 						*len = l->len;
 					return p->bf;
@@ -355,7 +338,7 @@
 			continue;
 		}
 		snprint(d, sizeof d, "%s/%s", pl->f, f);
-		if(bf = bopen(d, OREAD, 1), bf == nil)
+		if(bf = fopen(d, "rb"), bf == nil)
 			continue;
 		if(len != nil)
 			*len = bsize(bf);
@@ -371,7 +354,7 @@
 	int m;
 	char r[32];
 	uchar *buf;
-	Biobuf *bf;
+	FILE *bf;
 
 	bf = openlmp(f, &m);
 	if(bf == nil)
@@ -384,7 +367,7 @@
 	case Fstack: buf = m + 1 <= loadsize ? loadbuf : Hunk_TempAlloc(m + 1); break;
 	}
 	if(buf == nil)
-		fatal("loadlmp %s %d: memory allocation failed: %r", f, m + 1);
+		fatal("loadlmp %s %d: memory allocation failed: %s", f, m + 1, lerr());
 	buf[m] = 0;
 	eread(bf, buf, m);
 	closelmp(bf);
@@ -419,7 +402,7 @@
 loadpoints(void)
 {
 	int i, n, nv;
-	Biobuf *bf;
+	FILE *bf;
 	vec3_t v3;
 	vec_t *v;
 	particle_t *p;
@@ -435,7 +418,7 @@
 			break;
 		for(i=0, v=v3; i<3; i++){
 			*v++ = getfl(bf);
-			Bseek(bf, 1, 1);
+			fseek(bf, 1, SEEK_CUR);
 		}
 		n -= 3*4+3;
 		nv++;
@@ -458,24 +441,24 @@
 }
 
 static void
-dumpcvars(Biobuf *bf)
+dumpcvars(FILE *bf)
 {
 	cvar_t *c;
 
 	for(c=cvar_vars; c!=nil; c=c->next)
 		if(c->archive)
-			if(Bprint(bf, "%s \"%s\"\n", c->name, c->string) == Beof)
-				fatal("dumpcvars: %r");
+			if(fprintf(bf, "%s \"%s\"\n", c->name, c->string) < 0)
+				fatal("dumpcvars: %s", lerr());
 }
 
 static void
-dumpkeys(Biobuf *bf)
+dumpkeys(FILE *bf)
 {
 	char **k;
 
 	for(k=keybindings; k<keybindings+256; k++)
 		if(*k != nil && **k != 0)
-			Bprint(bf, "bind \"%s\" \"%s\"\n",
+			fprintf(bf, "bind \"%s\" \"%s\"\n",
 				Key_KeynumToString(k-keybindings), *k);
 }
 
@@ -482,18 +465,18 @@
 void
 dumpcfg(void)
 {
-	Biobuf *bf;
+	FILE *bf;
 
 	if(!host_initialized)
 		return;
-	bf = bopen(va("%s/config.cfg", fsdir), OWRITE, 1);
+	bf = fopen(va("%s/config.cfg", fsdir), "wb");
 	if(bf == nil){
-		Con_DPrintf("dumpcfg: %r\n");
+		Con_DPrintf("dumpcfg: %s\n", lerr());
 		return;
 	}
 	dumpkeys(bf);
 	dumpcvars(bf);
-	Bterm(bf);
+	fclose(bf);
 }
 
 void
@@ -501,7 +484,7 @@
 {
 	int n, *canld;
 	char (*e)[Nsavcm], (*s)[Nsavcm], *p;
-	Biobuf *bf;
+	FILE *bf;
 
 	s = savs;
 	canld = savcanld;
@@ -509,14 +492,14 @@
 		*canld = 0;
 		memset(*s, 0, sizeof *s);
 		strcpy(*s, "--- UNUSED SLOT ---");
-		bf = bopen(va("%s/s%d.sav", fsdir, n), OREAD, 1);
+		bf = fopen(va("%s/s%d.sav", fsdir, n), "rb");
 		if(bf == nil){
-			Con_DPrintf("savnames: %r\n");
+			Con_DPrintf("savnames: %s\n", lerr());
 			continue;
 		}
-		if((p = Brdline(bf, '\n'), p == nil)	/* discard version */
-		|| (p = Brdline(bf, '\n'), p == nil)){
-			Con_DPrintf("savnames: short read: %r\n");
+		if((p = frdline(bf), p == nil)	/* discard version */
+		|| (p = frdline(bf), p == nil)){
+			Con_DPrintf("savnames: short read: %s\n", lerr());
 			continue;
 		}
 		strncpy(*s, p, sizeof(*s)-1);
@@ -524,12 +507,12 @@
 			if(*p == '_')
 				*p = ' ';
 		*canld = 1;
-		Bterm(bf);
+		fclose(bf);
 	}
 }
 
 static void
-dumpedicts(Biobuf *bf, edict_t *ed)
+dumpedicts(FILE *bf, edict_t *ed)
 {
 	int *vp, *ve;
 	char *s;
@@ -537,7 +520,7 @@
 	ddef_t *d, *de;
 	eval_t *v;
 
-	Bprint(bf, "{\n");
+	fprintf(bf, "{\n");
 	if(ed->free)
 		goto end;
 	ev = (uchar *)&ed->v;
@@ -555,20 +538,20 @@
 				break;
 		if(vp == ve)
 			continue;
-		Bprint(bf, "\"%s\" ", s);
-		Bprint(bf, "\"%s\"\n", PR_UglyValueString(d->type, v));
+		fprintf(bf, "\"%s\" ", s);
+		fprintf(bf, "\"%s\"\n", PR_UglyValueString(d->type, v));
 	}
 end:
-	Bprint(bf, "}\n");
+	fprintf(bf, "}\n");
 }
 
 static void
-dumpdefs(Biobuf *bf)
+dumpdefs(FILE *bf)
 {
 	ushort t;
 	ddef_t *d, *de;
 
-	Bprint(bf, "{\n");
+	fprintf(bf, "{\n");
 	de = pr_globaldefs + progs->numglobaldefs;
 	for(d=pr_globaldefs; d<de; d++){
 		t = d->type;
@@ -577,10 +560,10 @@
 		t &= ~DEF_SAVEGLOBAL;
 		if(t != ev_string && t != ev_float && t != ev_entity)
 			continue;
-		Bprint(bf, "\"%s\" \"%s\"\n", PR_Str(d->s_name),
+		fprintf(bf, "\"%s\" \"%s\"\n", PR_Str(d->s_name),
 			PR_UglyValueString(t, (eval_t *)&pr_globals[d->ofs]));
 	}
-	Bprint(bf, "}\n");
+	fprintf(bf, "}\n");
 }
 
 int
@@ -589,32 +572,32 @@
 	int i;
 	char **s, **e;
 	float *fs, *fe;
-	Biobuf *bf;
+	FILE *bf;
 
-	bf = bopen(f, OWRITE, 1);
+	bf = fopen(f, "wb");
 	if(bf == nil)
 		return -1;
-	Bprint(bf, "%d\n%s\n", Nsavver, cm);
+	fprintf(bf, "%d\n%s\n", Nsavver, cm);
 	fs = svs.clients->spawn_parms;
 	fe = fs + nelem(svs.clients->spawn_parms);
 	while(fs < fe)
-		Bprint(bf, "%f\n", *fs++);
-	Bprint(bf, "%d\n%s\n%f\n", current_skill, sv.name, sv.time);
+		fprintf(bf, "%f\n", *fs++);
+	fprintf(bf, "%d\n%s\n%f\n", current_skill, sv.name, sv.time);
 	s = sv.lightstyles;
 	e = s + nelem(sv.lightstyles);
 	while(s < e){
-		Bprint(bf, "%s\n", *s != nil ? *s : "m");
+		fprintf(bf, "%s\n", *s != nil ? *s : "m");
 		s++;
 	}
 	dumpdefs(bf);
 	for(i=0; i<sv.num_edicts; i++)
 		dumpedicts(bf, EDICT_NUM(i));
-	Bterm(bf);
+	fclose(bf);
 	return 0;
 }
 
 static void
-loadedicts(Biobuf *bf)
+loadedicts(FILE *bf)
 {
 	int ent, c;
 	char sb[32768], *s;
@@ -621,11 +604,10 @@
 	edict_t *ed;
 
 	ent = -1;
-	c = 0;
 	do{
 		for(s=sb; s<sb+sizeof(sb)-1; s++){
-			c = Bgetc(bf);
-			if(c == Beof || c == 0)
+			c = fgetc(bf);
+			if(c <= 0 || c == EOF)
 				break;
 			*s = c;
 			if(c == '}'){
@@ -653,12 +635,12 @@
 				SV_LinkEdict(ed, 0);
 		}
 		ent++;
-	}while(c != Beof);
+	}while(!feof(bf) && !ferror(bf));
 	sv.num_edicts = ent;
 }
 
 static int
-loadparms(Biobuf *bf, char *f)
+loadparms(FILE *bf, char *f)
 {
 	int r;
 	float sp[Nparms], *p;
@@ -667,15 +649,15 @@
 	r = -1;
 	p = sp;
 	while(p < sp + nelem(sp)){
-		if(s = Brdline(bf, '\n'), s == nil)
+		if(s = frdline(bf), s == nil)
 			goto exit;
 		*p++ = (float)strtod(s, nil);
 	}
-	if(s = Brdline(bf, '\n'), s == nil)
+	if(s = frdline(bf), s == nil)
 		goto exit;
 	current_skill = (int)(strtod(s, nil) + 0.1);
 	setcvarv("skill", (float)current_skill);
-	if(s = Brdstr(bf, '\n', 1), s == nil)
+	if(s = frdlinedup(bf), s == nil)
 		goto exit;
 	CL_Disconnect_f();
 	SV_SpawnServer(s);
@@ -686,12 +668,12 @@
 	}
 	sv.paused = 1;
 	sv.loadgame = 1;
-	if(s = Brdline(bf, '\n'), s == nil)
+	if(s = frdline(bf), s == nil)
 		goto exit;
 	sv.time = strtod(s, nil);
 	lp = sv.lightstyles;
 	while(lp < sv.lightstyles + nelem(sv.lightstyles)){
-		if(s = Brdstr(bf, '\n', 1), s == nil)
+		if(s = frdlinedup(bf), s == nil)
 			goto exit;
 		*lp = Hunk_Alloc(strlen(s)+1);
 		strcpy(*lp++, s);
@@ -709,13 +691,13 @@
 {
 	int n, r;
 	char *s;
-	Biobuf *bf;
+	FILE *bf;
 
-	bf = bopen(f, OREAD, 0);
+	bf = fopen(f, "rb");
 	if(bf == nil)
 		return -1;
 	r = -1;
-	if(s = Brdline(bf, '\n'), s == nil)
+	if(s = frdline(bf), s == nil)
 		goto exit;
 	n = strtol(s, nil, 10);
 	if(n != Nsavver){
@@ -722,10 +704,10 @@
 		werrstr("invalid version %d", n);
 		goto exit;
 	}
-	Brdline(bf, '\n');
+	frdline(bf);
 	r = loadparms(bf, f);
 exit:
-	Bterm(bf);
+	fclose(bf);
 	return r;
 }
 
@@ -755,7 +737,7 @@
 	int n;
 	vec_t *f;
 
-	Bseek(demobf, demoofs, 0);
+	fseek(demobf, demoofs, SEEK_SET);
 	net_message.cursize = get32(demobf);
 	VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
 	for(n=0, f=cl.mviewangles[0]; n<3; n++)
@@ -762,8 +744,8 @@
 		*f++ = getfl(demobf);
 	if(net_message.cursize > NET_MAXMESSAGE)
 		fatal("readdm: invalid message size %d\n", net_message.cursize);
-	n = Bread(demobf, net_message.data, net_message.cursize);
-	demoofs = Bseek(demobf, 0, 1);
+	n = fread(net_message.data, 1, net_message.cursize, demobf);
+	demoofs = ftell(demobf);
 	if(n < 0)
 		Con_DPrintf("readdm: bad read: %r\n");
 	if(n != net_message.cursize){
@@ -782,14 +764,14 @@
 	demobf = openlmp(f, &n);
 	if(demobf == nil)
 		return -1;
-	s = Brdline(demobf, '\n');
-	n = Blinelen(demobf) - 1;
+	s = frdline(demobf);
+	n = flinelen - 1;
 	if(s == nil || n < 0 || n > 11){
 		Con_DPrintf("loaddm: invalid trk field\n");
 		closelmp(demobf);
 		return -1;
 	}
-	demoofs = Bseek(demobf, 0, 1);
+	demoofs = ftell(demobf);
 	s[n] = 0;
 	cls.forcetrack =  strtol(s, nil, 10);
 	return 0;
@@ -800,7 +782,7 @@
 {
 	char s[16];
 
-	demobf = bopen(f, OWRITE, 0);
+	demobf = fopen(f, "wb");
 	if(demobf == nil)
 		return -1;
 	sprint(s, "%d\n", trk);
@@ -813,11 +795,11 @@
 {
 	int n, ofs, len, nlmp;
 	uchar u[8];
-	Biobuf *bf;
+	FILE *bf;
 	Lump *l;
 	Pak *p;
 
-	bf = bopen(f, OREAD, 1);
+	bf = fopen(f, "rb");
 	if(bf == nil)
 		return nil;
 	memset(u, 0, sizeof u);
@@ -837,7 +819,7 @@
 	p->bf = bf;
 	p->l = l;
 	p->e = l + nlmp;
-	Bseek(bf, ofs, 0);
+	fseek(bf, ofs, SEEK_SET);
 	initcrc();
 	while(l < p->e){
 		eread(bf, l->f, 56);
@@ -883,11 +865,10 @@
 }
 
 static void
-initns(void)
+initns(char **paths)
 {
-	char *home;
+	char **p;
 
-	pakdir("/sys/games/lib/quake/id1");
 	if(game != nil){
 		if(strcmp(game, "rogue") == 0){
 			rogue = 1;
@@ -897,14 +878,13 @@
 			standard_quake = 0;
 		}else
 			notid1 = 1;
-		pakdir(va("/sys/games/lib/quake/%s", game));
 	}
-	if((home = getenv("home")) != nil){
-		pakdir(va("%s/lib/quake/id1", home));
+
+	for(p = paths; *p; p++){
+		pakdir(va("%s/id1", *p));
 		if(game != nil)
-			pakdir(va("%s/lib/quake/%s", home, game));
+			pakdir(va("%s/%s", *p, game));
 		mkpath(fsdir);
-		free(home);
 	}
 }
 
@@ -912,7 +892,7 @@
 chkreg(void)
 {
 	u16int *p;
-	Biobuf *bf;
+	FILE *bf;
 
 	Cvar_RegisterVariable(&registered);
 	bf = openlmp("gfx/pop.lmp", nil);
@@ -984,7 +964,7 @@
 }
 
 void
-initfs(void)
+initfs(char **paths)
 {
 	byte swaptest[2] = {1,0};
 
@@ -1004,7 +984,7 @@
 		BigFloat = FloatNoSwap;
 		LittleFloat = FloatSwap;
 	}
-	initns();
+	initns(paths);
 	chkreg();
 	Cmd_AddCommand("path", path);
 }
--- a/host.c
+++ b/host.c
@@ -574,7 +574,7 @@
 Host_Init
 ====================
 */
-void Host_Init (int argc, char **argv)
+void Host_Init (int argc, char **argv, char **paths)
 {
 	int i;
 
@@ -583,7 +583,7 @@
 	Cmd_Init ();	
 	V_Init ();
 	Chase_Init ();
-	initfs();
+	initfs(paths);
 	Host_InitLocal ();
 	W_LoadWadFile ("gfx.wad");
 	Key_Init ();
@@ -599,10 +599,10 @@
 	{
 		host_basepal = loadhunklmp("gfx/palette.lmp", nil);
 		if(host_basepal == nil)
-			fatal("Host_Init: %r");
+			fatal("Host_Init: %s", lerr());
 		host_colormap = loadhunklmp("gfx/colormap.lmp", nil);
 		if(host_colormap == nil)
-			fatal("Host_Init: %r");
+			fatal("Host_Init: %s", lerr());
 
 		initfb();
 
@@ -610,9 +610,9 @@
 		SCR_Init ();
 		R_Init ();
 		if(initsnd() < 0)
-			Con_DPrintf("initsnd: %r\n");
+			Con_DPrintf("initsnd: %s\n", lerr());
 		if(initcd() < 0)
-			Con_DPrintf("initcd: %r\n");
+			Con_DPrintf("initcd: %s\n", lerr());
 		Sbar_Init ();
 		CL_Init ();
 	}
--- a/host_cmd.c
+++ b/host_cmd.c
@@ -863,7 +863,7 @@
 	Con_Printf("Writing game to %s\n", s);
 	savecomment(cm);
 	if(dumpsav(s, cm) < 0)
-		Con_Printf(va("savegame: %r\n"));
+		Con_Printf(va("savegame: %s\n", lerr()));
 }
 
 static void
@@ -885,7 +885,7 @@
 	SCR_BeginLoadingPlaque();
 	Con_DPrintf("loadgame: reading from %s", s);
 	if(loadsav(s) < 0){
-		Con_Printf(va("loadgame: %r\n"));
+		Con_Printf(va("loadgame: %s\n", lerr()));
 		return;
 	}
 	if(cls.state != ca_dedicated){
--- a/model.c
+++ b/model.c
@@ -257,7 +257,7 @@
 	buf = loadstklmp(mod->name, stackbuf, sizeof stackbuf, nil);
 	if(buf == nil){
 		if(crash)
-			Host_Error("Mod_LoadModel: %r");
+			Host_Error("Mod_LoadModel: %s", lerr());
 		return nil;
 	}
 
--- a/pr_edict.c
+++ b/pr_edict.c
@@ -1035,7 +1035,7 @@
 
 	progs = loadhunklmp("progs.dat", &n);
 	if(progs == nil)
-		fatal("PR_LoadProgs: %r");
+		fatal("PR_LoadProgs: %s", lerr());
 	Con_DPrintf("Programs occupy %dK.\n", n/1024);
 
 	for (i=0 ; i<n ; i++)
--- a/qk1.c
+++ b/qk1.c
@@ -6,6 +6,29 @@
 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 *
+lerr(void)
+{
+	static char err[ERRMAX];
+	rerrstr(err, sizeof(err));
+	return err;
+}
+
 void
 fatal(char *fmt, ...)
 {
@@ -78,6 +101,12 @@
 threadmain(int argc, char **argv)
 {
 	double t, t´, Δt;
+	char *e;
+	static char *paths[] = {
+		"/sys/games/lib/quake",
+		nil,
+		nil,
+	};
 
 	ARGBEGIN{
 	case 'D':
@@ -98,7 +127,12 @@
 	/* ignore fp exceptions: rendering shit assumes they are */
 	setfcr(getfcr() & ~(FPOVFL|FPUNFL|FPINVAL|FPZDIV));
 	notify(croak);
-	Host_Init(argc, argv);
+
+	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();
--- a/quakedef.h
+++ b/quakedef.h
@@ -181,7 +181,7 @@
 void Host_ClearMemory (void);
 void Host_ServerFrame (void);
 void Host_InitCommands (void);
-void Host_Init (int argc, char **argv);
+void Host_Init (int argc, char **argv, char **paths);
 void Host_Shutdown(void);
 void Host_Error (char *error, ...);
 void Host_EndGame (char *message, ...);
--- a/unix/fs.c
+++ /dev/null
@@ -1,959 +1,0 @@
-#include "quakedef.h"
-
-u16int crcn;
-char fsdir[Nfspath];
-
-typedef struct Lump Lump;
-typedef struct Pak Pak;
-typedef struct Paklist Paklist;
-
-enum{
-	Npakhdr = 4+4+4,
-	Npaksz = 56+4+4,
-	Npaklmp = 2048,
-	Npak0lmp = 339,
-	Npak0crc = 0x80d5,
-	Fhunk = 0,
-	Fcache,
-	Fstack
-};
-struct Lump{
-	char fname[Npath];
-	int ofs;
-	int len;
-};
-struct Pak{
-	char fname[Nfspath];
-	FILE *f;
-	Lump *l;
-	Lump *e;
-};
-struct Paklist{
-	char fname[Nfspath];
-	Pak *p;
-	Paklist *pl;
-};
-static Paklist *pkl;
-
-static u16int pop[] = {
-	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
-	0x0000, 0x0000, 0x6600, 0x0000, 0x0000, 0x0000, 0x6600, 0x0000,
-	0x0000, 0x0066, 0x0000, 0x0000, 0x0000, 0x0000, 0x0067, 0x0000,
-	0x0000, 0x6665, 0x0000, 0x0000, 0x0000, 0x0000, 0x0065, 0x6600,
-	0x0063, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6563,
-	0x0064, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6564,
-	0x0064, 0x6564, 0x0000, 0x6469, 0x6969, 0x6400, 0x0064, 0x6564,
-	0x0063, 0x6568, 0x6200, 0x0064, 0x6864, 0x0000, 0x6268, 0x6563,
-	0x0000, 0x6567, 0x6963, 0x0064, 0x6764, 0x0063, 0x6967, 0x6500,
-	0x0000, 0x6266, 0x6769, 0x6a68, 0x6768, 0x6a69, 0x6766, 0x6200,
-	0x0000, 0x0062, 0x6566, 0x6666, 0x6666, 0x6666, 0x6562, 0x0000,
-	0x0000, 0x0000, 0x0062, 0x6364, 0x6664, 0x6362, 0x0000, 0x0000,
-	0x0000, 0x0000, 0x0000, 0x0062, 0x6662, 0x0000, 0x0000, 0x0000,
-	0x0000, 0x0000, 0x0000, 0x0061, 0x6661, 0x0000, 0x0000, 0x0000,
-	0x0000, 0x0000, 0x0000, 0x0000, 0x6500, 0x0000, 0x0000, 0x0000,
-	0x0000, 0x0000, 0x0000, 0x0000, 0x6400, 0x0000, 0x0000, 0x0000
-};
-
-/* this is a 16 bit, non-reflected CRC using the polynomial 0x1021 and the
- * initial and final xor values shown below; in other words, the CCITT standard
- * CRC used by XMODEM */
-enum{
-	Ncrc0 = 0xffff,
-	Nxor = 0
-};
-static u16int crct[] ={
-	0x0000,	0x1021,	0x2042,	0x3063,	0x4084,	0x50a5,	0x60c6,	0x70e7,
-	0x8108,	0x9129,	0xa14a,	0xb16b,	0xc18c,	0xd1ad,	0xe1ce,	0xf1ef,
-	0x1231,	0x0210,	0x3273,	0x2252,	0x52b5,	0x4294,	0x72f7,	0x62d6,
-	0x9339,	0x8318,	0xb37b,	0xa35a,	0xd3bd,	0xc39c,	0xf3ff,	0xe3de,
-	0x2462,	0x3443,	0x0420,	0x1401,	0x64e6,	0x74c7,	0x44a4,	0x5485,
-	0xa56a,	0xb54b,	0x8528,	0x9509,	0xe5ee,	0xf5cf,	0xc5ac,	0xd58d,
-	0x3653,	0x2672,	0x1611,	0x0630,	0x76d7,	0x66f6,	0x5695,	0x46b4,
-	0xb75b,	0xa77a,	0x9719,	0x8738,	0xf7df,	0xe7fe,	0xd79d,	0xc7bc,
-	0x48c4,	0x58e5,	0x6886,	0x78a7,	0x0840,	0x1861,	0x2802,	0x3823,
-	0xc9cc,	0xd9ed,	0xe98e,	0xf9af,	0x8948,	0x9969,	0xa90a,	0xb92b,
-	0x5af5,	0x4ad4,	0x7ab7,	0x6a96,	0x1a71,	0x0a50,	0x3a33,	0x2a12,
-	0xdbfd,	0xcbdc,	0xfbbf,	0xeb9e,	0x9b79,	0x8b58,	0xbb3b,	0xab1a,
-	0x6ca6,	0x7c87,	0x4ce4,	0x5cc5,	0x2c22,	0x3c03,	0x0c60,	0x1c41,
-	0xedae,	0xfd8f,	0xcdec,	0xddcd,	0xad2a,	0xbd0b,	0x8d68,	0x9d49,
-	0x7e97,	0x6eb6,	0x5ed5,	0x4ef4,	0x3e13,	0x2e32,	0x1e51,	0x0e70,
-	0xff9f,	0xefbe,	0xdfdd,	0xcffc,	0xbf1b,	0xaf3a,	0x9f59,	0x8f78,
-	0x9188,	0x81a9,	0xb1ca,	0xa1eb,	0xd10c,	0xc12d,	0xf14e,	0xe16f,
-	0x1080,	0x00a1,	0x30c2,	0x20e3,	0x5004,	0x4025,	0x7046,	0x6067,
-	0x83b9,	0x9398,	0xa3fb,	0xb3da,	0xc33d,	0xd31c,	0xe37f,	0xf35e,
-	0x02b1,	0x1290,	0x22f3,	0x32d2,	0x4235,	0x5214,	0x6277,	0x7256,
-	0xb5ea,	0xa5cb,	0x95a8,	0x8589,	0xf56e,	0xe54f,	0xd52c,	0xc50d,
-	0x34e2,	0x24c3,	0x14a0,	0x0481,	0x7466,	0x6447,	0x5424,	0x4405,
-	0xa7db,	0xb7fa,	0x8799,	0x97b8,	0xe75f,	0xf77e,	0xc71d,	0xd73c,
-	0x26d3,	0x36f2,	0x0691,	0x16b0,	0x6657,	0x7676,	0x4615,	0x5634,
-	0xd94c,	0xc96d,	0xf90e,	0xe92f,	0x99c8,	0x89e9,	0xb98a,	0xa9ab,
-	0x5844,	0x4865,	0x7806,	0x6827,	0x18c0,	0x08e1,	0x3882,	0x28a3,
-	0xcb7d,	0xdb5c,	0xeb3f,	0xfb1e,	0x8bf9,	0x9bd8,	0xabbb,	0xbb9a,
-	0x4a75,	0x5a54,	0x6a37,	0x7a16,	0x0af1,	0x1ad0,	0x2ab3,	0x3a92,
-	0xfd2e,	0xed0f,	0xdd6c,	0xcd4d,	0xbdaa,	0xad8b,	0x9de8,	0x8dc9,
-	0x7c26,	0x6c07,	0x5c64,	0x4c45,	0x3ca2,	0x2c83,	0x1ce0,	0x0cc1,
-	0xef1f,	0xff3e,	0xcf5d,	0xdf7c,	0xaf9b,	0xbfba,	0x8fd9,	0x9ff8,
-	0x6e17,	0x7e36,	0x4e55,	0x5e74,	0x2e93,	0x3eb2,	0x0ed1,	0x1ef0
-};
-
-static int notid1;
-static int loadsize;
-static uchar *loadbuf;
-static mem_user_t *loadcache;
-static FILE *demof;
-static vlong demoofs;
-
-#define	GBIT32(p)	((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
-#define	PBIT32(p,v)	(p)[0]=(v);(p)[1]=(v)>>8;(p)[2]=(v)>>16;(p)[3]=(v)>>24
-
-void
-crc(u8int v)
-{
-	crcn = crcn << 8 ^ crct[crcn >> 8 ^ v];
-}
-
-void
-initcrc(void)
-{
-	crcn = Ncrc0;
-}
-
-char *
-ext(char *f, char *e)
-{
-	return strrchr(f, '.') > strrchr(f, '/') ? "" : e;
-}
-
-void
-radix(char *f, char *d)
-{
-	char *s, *e;
-
-	s = strrchr(f, '/');
-	e = strrchr(f, '.');
-	if(s == nil)
-		s = f;
-	s++;
-	if(e - s < 1)
-		strcpy(d, "?model?");
-	else{
-		strncpy(d, s, e - s);
-		d[e - s] = 0;
-	}
-}
-
-static void
-path(void)
-{
-	Paklist *pl;
-
-	for(pl=pkl; pl!=nil; pl=pl->pl)
-		if(pl->p)
-			Con_Printf(va("%s (%zd files)\n", pl->p->f, pl->p->e - pl->p->l));
-		else
-			Con_Printf("%s\n", pl->fname);
-}
-
-static long
-eread(FILE *f, void *u, long n)
-{
-	if(fread(u, 1, n, f) != n)
-		fatal("eread: short read");
-	return n;
-}
-
-static u8int
-get8(FILE *f)
-{
-	u8int v;
-
-	eread(f, &v, 1);
-	return v;
-}
-
-static u16int
-get16(FILE *f)
-{
-	u16int v;
-
-	v = get8(f);
-	return v | get8(f) << 8;
-}
-
-static u32int
-get32(FILE *f)
-{
-	u32int v;
-
-	v = get16(f);
-	return v | get16(f) << 16;
-}
-
-static float
-getfl(FILE *f)
-{
-	union{
-		float v;
-		u32int u;
-	} u;
-
-	u.u = get32(f);
-	return u.v;
-}
-
-static u16int
-get16b(FILE *f)
-{
-	u16int v;
-
-	v = get8(f);
-	return v << 8 | get8(f);
-}
-
-static void
-ewrite(FILE *f, void *u, long n)
-{
-	if(fwrite(u, 1, n, f) != n)
-		fatal("eread: short write");
-}
-
-static void
-put32(FILE *f, u32int v)
-{
-	uchar u[4];
-
-	PBIT32(u, v);
-	ewrite(f, u, 4);
-}
-
-static void
-putfl(FILE *f, float v)
-{
-	union{
-		float v;
-		u32int u;
-	} w;
-
-	w.v = v;
-	put32(f, w.u);
-}
-
-static vlong
-bsize(FILE *f)
-{
-	struct stat st;
-
-	if(fstat(fileno(f), &st) != 0)
-		fatal("fstat");
-	return st.st_size;
-}
-
-static int
-mkpath(char *path)
-{
-	char *d;
-
-	d = path;
-	if(d == nil || *d == 0)
-		return -1;
-	if(*d == '/')
-		d++;
-	while(*d != 0){
-		if(*d == '/'){
-			*d = 0;
-			if(mkdir(path, 0770) < 0)
-				return -1;
-			*d = '/';
-		}
-		d++;
-	}
-	return mkdir(path, 0770);
-}
-
-static void
-closelmp(FILE *f)
-{
-	Paklist *pl;
-
-	for(pl=pkl; pl!=nil; pl=pl->pl)
-		if(pl->p && pl->p->f == f)
-			return;
-	fclose(f);
-}
-
-static FILE *
-openlmp(char *fname, int *len)
-{
-	char d[Nfspath+1];
-	FILE *f;
-	Paklist *pl;
-	Pak *p;
-	Lump *l;
-
-	for(pl=pkl; pl != nil; pl=pl->pl){
-		if(pl->p != nil){
-			p = pl->p;
-			l = p->l;
-			while(l < p->e){
-				if(strcmp(l->fname, fname) == 0){
-					fseek(p->f, l->ofs, SEEK_SET);
-					if(len != nil)
-						*len = l->len;
-					return p->f;
-				}
-				l++;
-			}
-			continue;
-		}
-		snprint(d, sizeof d, "%s/%s", pl->fname, fname);
-		if(f = fopen(d, "rb"), f == nil)
-			continue;
-		if(len != nil)
-			*len = bsize(f);
-		return f;
-	}
-	//fprintf(stderr, "openlmp %s: not found\n", fname);
-	return nil;
-}
-
-static uchar *
-loadlmp(char *fname, int mth, int *n)
-{
-	int m;
-	char r[32];
-	uchar *buf;
-	FILE *f;
-
-	f = openlmp(fname, &m);
-	if(f == nil)
-		return nil;
-	radix(fname, r);
-	buf = nil;
-	switch(mth){
-	case Fhunk: buf = Hunk_Alloc(m + 1); break;
-	case Fcache: buf = Cache_Alloc(loadcache, m + 1); break;
-	case Fstack: buf = m + 1 <= loadsize ? loadbuf : Hunk_TempAlloc(m + 1); break;
-	}
-	if(buf == nil)
-		fatal("loadlmp %s %d: memory allocation failed", fname, m + 1);
-	buf[m] = 0;
-	eread(f, buf, m);
-	closelmp(f);
-	if(n != nil)
-		*n = m;
-	return buf;
-}
-
-void *
-loadhunklmp(char *f, int *n)
-{
-	return loadlmp(f, Fhunk, n);
-}
-
-void *
-loadcachelmp(char *f, mem_user_t *c)
-{
-	loadcache = c;
-	loadlmp(f, Fcache, nil);
-	return c->data;
-}
-
-void *
-loadstklmp(char *f, void *buf, int nbuf, int *n)
-{
-	loadbuf = buf;
-	loadsize = nbuf;
-	return loadlmp(f, Fstack, n);
-}
-
-void
-loadpoints(void)
-{
-	int i, n, nv;
-	FILE *f;
-	vec3_t v3;
-	vec_t *v;
-	particle_t *p;
-
-	f = openlmp(va("maps/%s.pts", sv.name), &n);
-	if(f == nil){
-		Con_Printf(va("loadpoints failed\n"));
-		return;
-	}
-	nv = 0;
-	for(;;){
-		if(n < 3*4+3)
-			break;
-		for(i=0, v=v3; i<3; i++){
-			*v++ = getfl(f);
-			fseek(f, 1, SEEK_CUR);
-		}
-		n -= 3*4+3;
-		nv++;
-		if(free_particles == nil){
-			Con_Printf("loadpoints: insufficient free particles\n");
-			break;
-		}
-		p = free_particles;
-		free_particles = p->next;
-		p->next = active_particles;
-		active_particles = p;
-		p->die = Q_MAXFLOAT;
-		p->color = -nv & 15;
-		p->type = pt_static;
-		VectorCopy(vec3_origin, p->vel);
-		VectorCopy(v3, p->org);
-	}
-	closelmp(f);
-	Con_Printf("loadpoints: %d points read\n", nv);
-}
-
-static void
-dumpcvars(FILE *f)
-{
-	cvar_t *c;
-
-	for(c=cvar_vars; c!=nil; c=c->next)
-		if(c->archive)
-			if(fprintf(f, "%s \"%s\"\n", c->name, c->string) < 0)
-				fatal("dumpcvars");
-}
-
-static void
-dumpkeys(FILE *f)
-{
-	char **k;
-
-	for(k=keybindings; k<keybindings+256; k++)
-		if(*k != nil && **k != 0)
-			fprintf(f, "bind \"%s\" \"%s\"\n",
-				Key_KeynumToString(k-keybindings), *k);
-}
-
-void
-dumpcfg(void)
-{
-	FILE *f;
-
-	if(!host_initialized)
-		return;
-	f = fopen(va("%s/config.cfg", fsdir), "wb");
-	if(f == nil){
-		Con_DPrintf("dumpcfg failed\n");
-		return;
-	}
-	dumpkeys(f);
-	dumpcvars(f);
-	fclose(f);
-}
-
-void
-savnames(void)
-{
-	int n, *canld;
-	char (*e)[Nsavcm], (*s)[Nsavcm], *p;
-	char tmp[8192];
-	FILE *f;
-
-	s = savs;
-	canld = savcanld;
-	for(n=0, e=savs+Nsav; s<e; n++, s++, canld++){
-		*canld = 0;
-		memset(*s, 0, sizeof *s);
-		strcpy(*s, "--- UNUSED SLOT ---");
-		f = fopen(va("%s/s%d.sav", fsdir, n), "rb");
-		if(f == nil){
-			Con_DPrintf("savnames failed\n");
-			continue;
-		}
-		if((p = fgets(tmp, sizeof(tmp), f), p == nil)	/* discard version */
-		|| (p = fgets(tmp, sizeof(tmp), f), p == nil)){
-			Con_DPrintf("savnames: short read\n");
-			continue;
-		}
-		strncpy(*s, p, sizeof(*s)-1);
-		for(p=*s; p<*(s+1); p++)
-			if(*p == '_')
-				*p = ' ';
-		*canld = 1;
-		fclose(f);
-	}
-}
-
-static void
-dumpedicts(FILE *f, edict_t *ed)
-{
-	int *vp, *ve;
-	char *s;
-	uchar *ev;
-	ddef_t *d, *de;
-	eval_t *v;
-
-	fprintf(f, "{\n");
-	if(ed->free)
-		goto end;
-	ev = (uchar *)&ed->v;
-	de = pr_fielddefs + progs->numfielddefs;
-	for(d=pr_fielddefs+1; d<de; d++){
-		s = PR_Str(d->s_name);
-		if(s[strlen(s)-2] == '_')
-			continue;
-		/* TODO: pragma pack hazard */
-		vp = (int *)(ev + d->ofs * 4);
-		ve = vp + type_size[d->type & ~DEF_SAVEGLOBAL];
-		v = (eval_t *)vp;
-		for(; vp<ve; vp++)
-			if(*vp != 0)
-				break;
-		if(vp == ve)
-			continue;
-		fprintf(f, "\"%s\" ", s);
-		fprintf(f, "\"%s\"\n", PR_UglyValueString(d->type, v));
-	}
-end:
-	fprintf(f, "}\n");
-}
-
-static void
-dumpdefs(FILE *f)
-{
-	ushort t;
-	ddef_t *d, *de;
-
-	fprintf(f, "{\n");
-	de = pr_globaldefs + progs->numglobaldefs;
-	for(d=pr_globaldefs; d<de; d++){
-		t = d->type;
-		if((t & DEF_SAVEGLOBAL) == 0)
-			continue;
-		t &= ~DEF_SAVEGLOBAL;
-		if(t != ev_string && t != ev_float && t != ev_entity)
-			continue;
-		fprintf(f, "\"%s\" \"%s\"\n", PR_Str(d->s_name),
-			PR_UglyValueString(t, (eval_t *)&pr_globals[d->ofs]));
-	}
-	fprintf(f, "}\n");
-}
-
-int
-dumpsav(char *fname, char *cm)
-{
-	int i;
-	char **s, **e;
-	float *fs, *fe;
-	FILE *f;
-
-	f = fopen(fname, "wb");
-	if(f == nil)
-		return -1;
-	fprintf(f, "%d\n%s\n", Nsavver, cm);
-	fs = svs.clients->spawn_parms;
-	fe = fs + nelem(svs.clients->spawn_parms);
-	while(fs < fe)
-		fprintf(f, "%f\n", *fs++);
-	fprintf(f, "%d\n%s\n%f\n", current_skill, sv.name, sv.time);
-	s = sv.lightstyles;
-	e = s + nelem(sv.lightstyles);
-	while(s < e){
-		fprintf(f, "%s\n", *s != nil ? *s : "m");
-		s++;
-	}
-	dumpdefs(f);
-	for(i=0; i<sv.num_edicts; i++)
-		dumpedicts(f, EDICT_NUM(i));
-	fclose(f);
-	return 0;
-}
-
-static void
-loadedicts(FILE *f)
-{
-	int ent, c;
-	char sb[32768], *s;
-	edict_t *ed;
-
-	ent = -1;
-	c = 0;
-	do{
-		for(s=sb; s<sb+sizeof(sb)-1; s++){
-			c = fgetc(f);
-			if(c == EOF || c == 0)
-				break;
-			*s = c;
-			if(c == '}'){
-				s++;
-				break;
-			}
-		}
-		if(s == sb + sizeof(sb) - 1)
-			fatal("loadgame: buffer overflow");
-		*s = 0;
-		s = COM_Parse(sb);
-		if(com_token[0] == 0)
-			break;
-		if(strcmp(com_token, "{") != 0)
-			fatal("loadgame: missing opening brace");
-		if(ent == -1)
-			ED_ParseGlobals(s);
-		else{
-			ed = EDICT_NUM(ent);
-			/* TODO: pragma pack hazard */
-			memset(&ed->v, 0, progs->entityfields * 4);
-			ed->free = 0;
-			ED_ParseEdict(s, ed);
-			if(!ed->free)
-				SV_LinkEdict(ed, 0);
-		}
-		ent++;
-	}while(c != EOF);
-	sv.num_edicts = ent;
-}
-
-static int
-loadparms(FILE *f, char *fname)
-{
-	int r;
-	float sp[Nparms], *p;
-	char *s, **lp;
-	char tmp[8192];
-
-	r = -1;
-	p = sp;
-	while(p < sp + nelem(sp)){
-		if(s = fgets(tmp, sizeof(tmp), f), s == nil)
-			goto exit;
-		*p++ = (float)strtod(s, nil);
-	}
-	if(s = fgets(tmp, sizeof(tmp), f), s == nil)
-		goto exit;
-	current_skill = (int)(strtod(s, nil) + 0.1);
-	setcvarv("skill", (float)current_skill);
-	if(s = fgets(tmp, sizeof(tmp), f), s == nil)
-		goto exit;
-	CL_Disconnect_f();
-	SV_SpawnServer(s);
-	if(!sv.active)
-		goto exit;
-	sv.paused = 1;
-	sv.loadgame = 1;
-	if(s = fgets(tmp, sizeof(tmp), f), s == nil)
-		goto exit;
-	sv.time = strtod(s, nil);
-	lp = sv.lightstyles;
-	while(lp < sv.lightstyles + nelem(sv.lightstyles)){
-		if(s = fgets(tmp, sizeof(tmp), f), s == nil)
-			goto exit;
-		*lp = Hunk_Alloc(strlen(s)+1);
-		strcpy(*lp++, s);
-	}
-	r = 0;
-	loadedicts(f);
-	memcpy(svs.clients->spawn_parms, sp, sizeof sp);
-exit:
-	return r;
-}
-
-int
-loadsav(char *fname)
-{
-	int n, r;
-	char *s;
-	FILE *f;
-	char tmp[8192];
-
-	f = fopen(fname, "rb");
-	if(f == nil)
-		return -1;
-	r = -1;
-	if(s = fgets(tmp, sizeof(tmp), f), s == nil)
-		goto exit;
-	n = strtol(s, nil, 10);
-	if(n != Nsavver){
-		werrstr("invalid version %d", n);
-		goto exit;
-	}
-	s = fgets(tmp, sizeof(tmp), f);
-	r = loadparms(f, fname);
-exit:
-	fclose(f);
-	return r;
-}
-
-void
-closedm(void)
-{
-	if(demof == nil)
-		return;
-	closelmp(demof);
-	demof = nil;
-}
-
-void
-writedm(void)
-{
-	int i;
-
-	put32(demof, net_message.cursize);
-	for(i=0; i<3; i++)
-		putfl(demof, cl.viewangles[i]);
-	ewrite(demof, net_message.data, net_message.cursize);
-}
-
-int
-readdm(void)
-{
-	int n;
-	vec_t *f;
-
-	fseek(demof, demoofs, SEEK_SET);
-	net_message.cursize = get32(demof);
-	VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
-	for(n=0, f=cl.mviewangles[0]; n<3; n++)
-		*f++ = getfl(demof);
-	if(net_message.cursize > NET_MAXMESSAGE)
-		fatal("readdm: invalid message size %d\n", net_message.cursize);
-	n = fread(net_message.data, 1, net_message.cursize, demof);
-	demoofs = ftell(demof);
-	if(n < 0)
-		Con_DPrintf("readdm: bad read\n");
-	if(n != net_message.cursize){
-		Con_DPrintf("readdm: short read\n");
-		n = -1;
-	}
-	return n;
-}
-
-int
-loaddm(char *fname)
-{
-	int n;
-	char *s, tmp[8192];
-
-	demof = openlmp(fname, &n);
-	if(demof == nil)
-		return -1;
-	s = fgets(tmp, sizeof(tmp), demof);
-	n = strlen(s) - 1;
-	if(s == nil || n < 0 || n > 11){
-		Con_DPrintf("loaddm: invalid trk field\n");
-		closelmp(demof);
-		return -1;
-	}
-	demoofs = ftell(demof);
-	s[n] = 0;
-	cls.forcetrack =  strtol(s, nil, 10);
-	return 0;
-}
-
-int
-opendm(char *f, int trk)
-{
-	char s[16];
-
-	demof = fopen(f, "wb");
-	if(demof == nil)
-		return -1;
-	sprint(s, "%d\n", trk);
-	ewrite(demof, s, strlen(s));
-	return 0;
-}
-
-static Pak *
-pak(char *fname)
-{
-	int n, ofs, len, nlmp;
-	uchar u[8];
-	FILE *f;
-	Lump *l;
-	Pak *p;
-
-	f = fopen(fname, "rb");
-	if(f == nil)
-		return nil;
-	memset(u, 0, sizeof u);
-	eread(f, u, 4);
-	if(memcmp(u, "PACK", 4) != 0)
-		fatal("pak %s: invalid pak file", fname);
-	ofs = get32(f);
-	len = get32(f);
-	nlmp = len / Npaksz;
-	if(nlmp > Npaklmp)
-		fatal("pak %s: invalid lump number %d", fname, nlmp);
-	if(nlmp != Npak0lmp)
-		notid1 = 1;
-	l = Hunk_Alloc(nlmp * sizeof *l);
-	p = Hunk_Alloc(sizeof *p);
-	snprint(p->fname, sizeof(p->fname), "%s", fname);
-	p->f = f;
-	p->l = l;
-	p->e = l + nlmp;
-	fseek(f, ofs, SEEK_SET);
-	initcrc();
-	while(l < p->e){
-		eread(f, l->fname, 56);
-		for(n=0; n<56; n++)
-			crc(l->fname[n]);
-		eread(f, u, 8);
-		for(n=0; n<8; n++)
-			crc(u[n]);
-		l->ofs = GBIT32(u);
-		l->len = GBIT32(u+4);
-		l++;
-	}
-	if(crcn != Npak0crc)
-		notid1 = 1;
-	return p;
-}
-
-static void
-pakdir(char *d)
-{
-	int n;
-	char f[Nfspath];
-	Paklist *pl;
-	Pak *p;
-
-	strncpy(fsdir, d, sizeof(fsdir)-1);
-	pl = Hunk_Alloc(sizeof *pl);
-	strncpy(pl->fname, d, sizeof(pl->fname)-1);
-	pl->pl = pkl;
-	pkl = pl;
-	for(n=0; ; n++){
-		snprint(f, sizeof f, "%s/pak%d.pak", d, n);
-		p = pak(f);
-		if(p == nil){
-			Con_DPrintf("pakdir: %r\n");
-			break;
-		}
-		pl = Hunk_Alloc(sizeof *pl);
-		pl->p = p;
-		pl->pl = pkl;
-		pkl = pl;
-	}
-}
-
-static void
-initns(void)
-{
-	char *home;
-
-	pakdir("/usr/games/quake/id1");
-	if(game != nil){
-		if(strcmp(game, "rogue") == 0){
-			rogue = 1;
-			standard_quake = 0;
-		}else if(strcmp(game, "hipnotic") == 0){
-			hipnotic = 1;
-			standard_quake = 0;
-		}else
-			notid1 = 1;
-		pakdir(va("/usr/games/quake/%s", game));
-	}
-	if((home = getenv("HOME")) != nil){
-		pakdir(va("%s/.quake/id1", home));
-		if(game != nil)
-			pakdir(va("%s/.quake/%s", home, game));
-		mkpath(fsdir);
-	}
-}
-
-static void
-chkreg(void)
-{
-	u16int *p;
-	FILE *f;
-
-	Cvar_RegisterVariable(&registered);
-	f = openlmp("gfx/pop.lmp", nil);
-	if(f == nil){
-		Con_DPrintf("chkreg: shareware version\n");
-		if(notid1)
-			fatal("chkreg: phase error");
-		return;
-	}
-	p = pop;
-	while(p < pop + nelem(pop))
-		if(*p++ != get16b(f))
-			fatal("chkreg: corrupted pop lump");
-	closelmp(f);
-	setcvar("registered", "1");
-	Con_DPrintf("chkreg: registered version\n");
-}
-
-/* TODO: nuke these from orbit */
-static short
-ShortSwap(short l)
-{
-	byte    b1,b2;
-
-	b1 = l&255;
-	b2 = (l>>8)&255;
-	return (b1<<8) + b2;
-}
-static short
-ShortNoSwap(short l)
-{
-	return l;
-}
-static int
-LongSwap(int l)
-{
-	byte    b1,b2,b3,b4;
-
-	b1 = l&255;
-	b2 = (l>>8)&255;
-	b3 = (l>>16)&255;
-	b4 = (l>>24)&255;
-	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
-}
-static int
-LongNoSwap(int l)
-{
-	return l;
-}
-static float
-FloatSwap(float f)
-{
-	union{
-		float   f;
-		byte    b[4];
-	} dat1, dat2;
-
-	dat1.f = f;
-	dat2.b[0] = dat1.b[3];
-	dat2.b[1] = dat1.b[2];
-	dat2.b[2] = dat1.b[1];
-	dat2.b[3] = dat1.b[0];
-	return dat2.f;
-}
-static float
-FloatNoSwap(float f)
-{
-	return f;
-}
-
-void
-initfs(void)
-{
-	byte swaptest[2] = {1,0};
-
-	if(*(short *)swaptest == 1)
-	{
-		BigShort = ShortSwap;
-		LittleShort = ShortNoSwap;
-		BigLong = LongSwap;
-		LittleLong = LongNoSwap;
-		BigFloat = FloatSwap;
-		LittleFloat = FloatNoSwap;
-	}else{
-		BigShort = ShortNoSwap;
-		LittleShort = ShortSwap;
-		BigLong = LongNoSwap;
-		LittleLong = LongSwap;
-		BigFloat = FloatNoSwap;
-		LittleFloat = FloatSwap;
-	}
-	initns();
-	chkreg();
-	Cmd_AddCommand("path", path);
-}
--- a/unix/qk1.c
+++ b/unix/qk1.c
@@ -1,10 +1,26 @@
 #include "quakedef.h"
 #include "parg.h"
 #include <time.h>
+#include <errno.h>
 
 char *game;
 int debug;
+char lasterr[256] = {0};
 
+char *
+lerr(void)
+{
+	if(*lasterr == 0 && errno != 0)
+		return strerror(errno);
+	return lasterr;
+}
+
+int
+sys_mkdir(char *path)
+{
+	return mkdir(path, 0770);
+}
+
 void
 fatal(char *fmt, ...)
 {
@@ -62,6 +78,11 @@
 	double t, t2, dt;
 	struct parg_state ps;
 	int c, nargs;
+	static char *paths[] = {
+		"/usr/games/quake",
+		nil,
+		nil,
+	};
 
 	parg_init(&ps);
 	nargs = 0;
@@ -71,7 +92,7 @@
 			argv[nargs++] = (char*)ps.optarg;
 			break;
 		case 'D':
-			debug = 1;
+			debug++;
 			break;
 		case 'd':
 			dedicated = 1;
@@ -95,7 +116,10 @@
 	}
 
 	srand(getpid());
-	Host_Init(nargs, argv);
+
+	paths[1] = strdup(va("%s/.quake", getenv("HOME")));
+	Host_Init(nargs, argv, paths);
+
 	t = dtime() - 1.0 / Fpsmax;
 	for(;;){
 		t2 = dtime();
--- a/unix/u.h
+++ b/unix/u.h
@@ -22,11 +22,6 @@
 typedef uint32_t u32int;
 typedef uintptr_t uintptr;
 
-enum {
-	UTFmax = 4,
-	IOUNIT = 32768,
-};
-
 #define OREAD O_RDONLY
 #define OWRITE O_WRONLY
 #define OCEXEC O_CLOEXEC
@@ -47,6 +42,8 @@
 #define getcallerpc(x) nil
 #define getmalloctag(p) (USED(p), 0)
 #define setmalloctag(p, t) do{USED(p); USED(t);}while(0)
-#define werrstr(fmt, ...) do{}while(0)
+
+extern char lasterr[256];
+#define werrstr(fmt, ...) do{snprint(lasterr, sizeof(lasterr), fmt, __VA_ARGS__); }while(0)
 
 char *seprint(char *, char *, char *, ...);
--- a/wad.c
+++ b/wad.c
@@ -53,7 +53,7 @@
 
 	wad_base = loadhunklmp(filename, nil);
 	if(wad_base == nil)
-		fatal("W_LoadWadFile: %r");
+		fatal("W_LoadWadFile: %s", lerr());
 
 	header = (wadinfo_t *)wad_base;
 	
--- a/zone.c
+++ b/zone.c
@@ -60,7 +60,7 @@
 
 	m = calloc(1, sizeof(*m) + size);
 	if(m == nil)
-		fatal("Hunk_Alloc: %r");
+		fatal("Hunk_Alloc: %s", lerr());
 	m->size = size;
 	m->next = hunk_head;
 	if(hunk_head != nil)
@@ -81,7 +81,7 @@
 	t = getmalloctag(m);
 	n = realloc(m, sizeof(*m) + m->size*2);
 	if(m == nil)
-		fatal("Hunk_Double: %r");
+		fatal("Hunk_Double: %s", lerr());
 	if(hunk_head == m)
 		hunk_head = n;
 	m = n;
@@ -122,7 +122,7 @@
 	if(size > bufsz){
 		buf = realloc(buf, size);
 		if(buf == nil)
-			fatal("Hunk_TempAlloc: %r");
+			fatal("Hunk_TempAlloc: %s", lerr());
 		bufsz = size;
 	}
 	memset(buf, 0, size);
@@ -180,7 +180,7 @@
 
 	cs = calloc(1, sizeof(*cs) + size);
 	if(cs == nil)
-		fatal("Cache_Alloc: %r");
+		fatal("Cache_Alloc: %s", lerr());
 	cs->size = size;
 	cs->next = cache_head;
 	if(cache_head != nil)