shithub: qk1

Download patch

ref: f47ac12115f687ad7ec5348679fda15b4cfc4009
parent: facd1d4e9b6a5c1cd9c5faf96f7520c62746144b
author: Konstantinn Bonnet <[email protected]>
date: Tue Feb 28 15:15:01 EST 2017

bikeshed file i/o further

- fs: add remaining stdio FILE functions using libbio
- in: close iproc pipe if it was created
- qk1: fatal: parse args before Host_Shutdown
- style changes; use va() where appropriate
- don't print lump name when using %r
- misc fixes

--- a/cl_demo.c
+++ b/cl_demo.c
@@ -4,7 +4,7 @@
 #include "quakedef.h"
 
 static void
-dmtimeend(void)
+timedm(void)
 {
 	int f;
 	float t;
@@ -14,9 +14,37 @@
 	t = realtime - cls.td_starttime;
 	if(t == 0.0)
 		t = 1;
-	Con_Printf("%d frames %5.1f seconds %5.1f fps\n", f, t, f/t);
+	Con_Printf("%d frames %5.1f seconds %5.1f fps\n", f, t, f / t);
 }
 
+void
+abortdemo(void)
+{
+	if(!cls.demoplayback)
+		return;
+	closedm();
+	cls.demoplayback = 0;
+	cls.state = ca_disconnected;
+	if(cls.timedemo)
+		timedm();
+}
+
+void
+stopdemo(void)
+{
+	if(cmd_source != src_command)
+		return;
+	if(!cls.demorecording){
+		Con_Printf("stop: no recording in progress\n");
+		return;
+	}
+	SZ_Clear(&net_message);
+	MSG_WriteByte(&net_message, svc_disconnect);
+	writedm();
+	closedm();
+	cls.demorecording = 0;
+}
+
 static int
 dmmsg(void)
 {
@@ -30,7 +58,7 @@
 		}else if(cl.time <= cl.mtime[0])
 				return 0;
 	}
-	if(rlmpmsg() < 0){
+	if(readdm() < 0){
 		abortdemo();
 		return 0;
 	}
@@ -38,7 +66,7 @@
 }
 
 int
-clmsg(void)
+readcl(void)
 {
 	int r;
 
@@ -54,36 +82,27 @@
 			break;
 	}
 	if(cls.demorecording)
-		wlmpmsg();
+		writedm();
 	return r;
 }
 
 void
-abortdemo(void)
+timedemo(void)
 {
-	if(!cls.demoplayback)
-		return;
-	endlmp();
-	cls.demoplayback = 0;
-	cls.state = ca_disconnected;
-	if(cls.timedemo)
-		dmtimeend();
-}
-
-void
-stopdemo(void)
-{
 	if(cmd_source != src_command)
 		return;
-	if(!cls.demorecording){
-		Con_Printf("stop: no recording in progress\n");
+	if(Cmd_Argc() != 2){
+		Con_Printf("timedemo <demoname> : gets demo speeds\n");
 		return;
 	}
-	SZ_Clear(&net_message);
-	MSG_WriteByte(&net_message, svc_disconnect);
-	wlmpmsg();
-	endlmp();
-	cls.demorecording = 0;
+	playdemo();
+	if(cls.demoplayback != 1)
+		return;
+	/* cls.td_starttime will be grabbed at the second frame of the demo, so
+	 * all the loading time doesn't get counted */
+	cls.timedemo = 1;
+	cls.td_startframe = host_framecount;
+	cls.td_lastframe = -1;	/* get a new message this frame */
 }
 
 void
@@ -90,7 +109,7 @@
 recdemo(void)
 {
 	int c, trk;
-	char f[Nfspath];
+	char *s, *a;
 
 	if(cmd_source != src_command)
 		return;
@@ -108,12 +127,13 @@
 		return;
 	}else if(c == 4)
 		trk = atoi(Cmd_Argv(3));
-	snprint(f, sizeof f, "%s/%s", fsdir, Cmd_Argv(1));
-	ext(f, ".dem");
+	a = Cmd_Argv(1);
 	if(c > 2)
 		Cmd_ExecuteString(va("map %s", Cmd_Argv(2)), src_command);
-	if(reclmp(f, trk) < 0){
-		Con_Printf("recdemo: can't open %s: %r\n", f);
+	s = va("%s/%s%s", fsdir, a, ext(a, ".dem"));
+	dprint("recdemo: writing to file %s\n", s);
+	if(opendm(s, trk) < 0){
+		Con_Printf("recdemo: %r\n");
 		return;
 	}
 	cls.demorecording = 1;
@@ -126,7 +146,7 @@
 void
 playdemo(void)
 {
-	char f[Nfspath];
+	char *s, *a;
 
 	if(cmd_source != src_command)
 		return;
@@ -135,33 +155,14 @@
 		return;
 	}
 	CL_Disconnect();
-	memset(f, 0, sizeof f);
-	strncpy(f, Cmd_Argv(1), sizeof(f)-5);
-	ext(f, ".dem");
-	if(demolmp(f) < 0){
-		Con_Printf("playdemo: can't open %s: %r\n", f);
+	a = Cmd_Argv(1);
+	s = va("%s%s", a, ext(a, ".dem"));
+	dprint("playdemo: writing to file %s\n", s);
+	if(loaddm(s) < 0){
+		Con_Printf("playdemo: %r\n");
 		cls.demonum = -1;
 		return;
 	}
 	cls.demoplayback = 1;
 	cls.state = ca_connected;
-}
-
-void
-timedemo(void)
-{
-	if(cmd_source != src_command)
-		return;
-	if(Cmd_Argc() != 2){
-		Con_Printf("timedemo <demoname> : gets demo speeds\n");
-		return;
-	}
-	playdemo();
-	if(cls.demoplayback != 1)
-		return;
-	/* cls.td_starttime will be grabbed at the second frame of the demo, so
-	 * all the loading time doesn't get counted */
-	cls.timedemo = 1;
-	cls.td_startframe = host_framecount;
-	cls.td_lastframe = -1;	/* get a new message this frame */
 }
--- a/cl_main.c
+++ b/cl_main.c
@@ -31,7 +31,7 @@
 efrag_t			cl_efrags[MAX_EFRAGS];
 entity_t		cl_entities[MAX_EDICTS];
 entity_t		cl_static_entities[MAX_STATIC_ENTITIES];
-lightstyle_t	cl_lightstyle[MAX_LIGHTSTYLES];
+lightstyle_t	cl_lightstyle[Nlights];
 dlight_t		cl_dlights[MAX_DLIGHTS];
 
 int				cl_numvisedicts;
@@ -599,7 +599,7 @@
 	
 	do
 	{
-		ret = clmsg ();
+		ret = readcl ();
 		if (ret == -1)
 			Host_Error ("CL_ReadFromServer: lost server connection");
 		if (!ret)
--- a/cl_parse.c
+++ b/cl_parse.c
@@ -146,11 +146,11 @@
 	
 	do
 	{
-		ret = clmsg ();
+		ret = readcl ();
 		switch (ret)
 		{
 		default:
-			Host_Error ("CL_KeepaliveMessage: clmsg failed");		
+			Host_Error ("CL_KeepaliveMessage: readcl failed");		
 		case 0:
 			break;	// nothing waiting
 		case 1:
@@ -783,8 +783,8 @@
 					
 		case svc_lightstyle:
 			i = MSG_ReadByte ();
-			if (i >= MAX_LIGHTSTYLES)
-				fatal ("svc_lightstyle > MAX_LIGHTSTYLES");
+			if (i >= Nlights)
+				fatal ("svc_lightstyle > Nlights");
 			strcpy(cl_lightstyle[i].map,  MSG_ReadString());
 			cl_lightstyle[i].length = strlen(cl_lightstyle[i].map);
 			break;
--- a/client.h
+++ b/client.h
@@ -242,7 +242,7 @@
 extern	efrag_t			cl_efrags[MAX_EFRAGS];
 extern	entity_t		cl_entities[MAX_EDICTS];
 extern	entity_t		cl_static_entities[MAX_STATIC_ENTITIES];
-extern	lightstyle_t	cl_lightstyle[MAX_LIGHTSTYLES];
+extern	lightstyle_t	cl_lightstyle[Nlights];
 extern	dlight_t		cl_dlights[MAX_DLIGHTS];
 extern	entity_t		cl_temp_entities[MAX_TEMP_ENTITIES];
 extern	beam_t			cl_beams[MAX_BEAMS];
--- a/cvar.c
+++ b/cvar.c
@@ -142,24 +142,6 @@
 	return true;
 }
 
-
-/*
-============
-Cvar_WriteVariables
-
-Writes lines containing "set variable value" for all variables
-with the archive flag set to true.
-============
-*/
-void Cvar_WriteVariables (FILE *f)
-{
-	cvar_t	*var;
-	
-	for (var = cvar_vars ; var ; var = var->next)
-		if (var->archive)
-			fprintf (f, "%s \"%s\"\n", var->name, var->string);
-}
-
 void
 setcvar(char *k, char *v)
 {
--- a/cvar.h
+++ b/cvar.h
@@ -61,10 +61,6 @@
 // command.  Returns true if the command was a variable reference that
 // was handled. (print or change)
 
-void 	Cvar_WriteVariables (FILE *f);
-// Writes lines containing "set variable value" for all variables
-// with the archive flag set to true.
-
 cvar_t *Cvar_FindVar (char *var_name);
 
 extern cvar_t	*cvar_vars;
--- a/dat.h
+++ b/dat.h
@@ -1,8 +1,15 @@
 enum{
 	Npath = 64,
 	Nfspath = 128,
-	Nmsg = 8000
+	Nmsg = 8000,
+	Nsav = 12,
+	Nsavcm = 40,
+	Nsavver = 5,
+	Nparms = 16
 };
 
 extern char fsdir[];
 extern u16int crcn;
+
+extern char savs[][Nsavcm];
+extern int savcanld[];
--- a/fns.h
+++ b/fns.h
@@ -1,25 +1,28 @@
 void	setcvar(char*, char*);
 void	setcvarv(char*, float);
-int	clmsg(void);
 void	abortdemo(void);
 void	stopdemo(void);
+int	readcl(void);
+void	timedemo(void);
 void	recdemo(void);
 void	playdemo(void);
-void	timedemo(void);
-void*	loadhunklmp(char *, int *);
-void*	loadcachelmp(char *, cache_user_t *);
-void*	loadstklmp(char *, void *, int, int *);
-void	pointlmp(void);
-void	endlmp(void);
-int	rlmpmsg(void);
-void	wlmpmsg(void);
-int	reclmp(char*, int);
-int	demolmp(char*);
 void	crc(u8int);
 void	initcrc(void);
-void	ext(char*, char*);
+char*	ext(char*, char*);
 void	radix(char*, char*);
-void	unloadfs(void);
+void*	loadhunklmp(char *, int *);
+void*	loadcachelmp(char *, cache_user_t *);
+void*	loadstklmp(char *, void *, int, int *);
+void	loadpoints(void);
+void	dumpcfg(void);
+void	savnames(void);
+int	dumpsav(char*, char*);
+int	loadsav(char*);
+void	closedm(void);
+void	writedm(void);
+int	readdm(void);
+int	loaddm(char*);
+int	opendm(char*, int);
 void	initfs(void);
 void	dprint(char*, ...);
 void	fatal(char*, ...);
--- a/fs.c
+++ b/fs.c
@@ -99,12 +99,11 @@
 	0xef1f,	0xff3e,	0xcf5d,	0xdf7c,	0xaf9b,	0xbfba,	0x8fd9,	0x9ff8,
 	0x6e17,	0x7e36,	0x4e55,	0x5e74,	0x2e93,	0x3eb2,	0x0ed1,	0x1ef0
 };
-static int notid1;
 
+static int notid1;
 static int loadsize;
 static uchar *loadbuf;
 static cache_user_t *loadcache;
-
 static Biobuf *demobf;
 static vlong demoofs;
 
@@ -111,6 +110,54 @@
 #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("%s (%zd files)\n", pl->p->f, pl->p->e - pl->p->l);
+		else
+			Con_Printf("%s\n", pl->f);
+}
+
 static Biobuf *
 bopen(char *f, int m, int arm)
 {
@@ -225,113 +272,23 @@
 
 	d = dirfstat(Bfildes(bf));
 	if(d == nil)
-		sysfatal("bstat: %r");
+		fatal("bstat: %r");
 	n = d->length;
 	free(d);
 	return n;
 }
 
-static Pak *
-pak(char *f)
-{
-	int n, ofs, len, nlmp;
-	uchar u[8];
-	Biobuf *bf;
-	Lump *l;
-	Pak *p;
-
-	bf = bopen(f, OREAD, 1);
-	if(bf == nil)
-		return nil;
-	memset(u, 0, sizeof u);
-	eread(bf, u, 4);
-	if(memcmp(u, "PACK", 4) != 0)
-		fatal("pak %s: invalid pak file", f);
-	ofs = get32(bf);
-	len = get32(bf);
-	nlmp = len / Npaksz;
-	if(nlmp > Npaklmp)
-		fatal("pak %s: invalid lump number %d", f, nlmp);
-	if(nlmp != Npak0lmp)
-		notid1 = 1;
-	l = Hunk_AllocName(nlmp * sizeof *l, "pak");
-	p = Hunk_Alloc(sizeof *p);
-	strncpy(p->f, f, sizeof(p->f)-1);
-	p->bf = bf;
-	p->l = l;
-	p->e = l + nlmp;
-	Bseek(bf, ofs, 0);
-	initcrc();
-	while(l < p->e){
-		eread(bf, l->f, 56);
-		for(n=0; n<56; n++)
-			crc(l->f[n]);
-		eread(bf, 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)
+closelmp(Biobuf *bf)
 {
-	int n;
-	char f[Nfspath];
 	Paklist *pl;
-	Pak *p;
 
-	strncpy(fsdir, d, sizeof(fsdir)-1);
-	pl = Hunk_Alloc(sizeof *pl);
-	strncpy(pl->f, d, sizeof(pl->f)-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){
-			dprint("pakdir: can't open %s: %r", f);
-			break;
-		}
-		pl = Hunk_Alloc(sizeof *pl);
-		pl->p = p;
-		pl->pl = pkl;
-		pkl = pl;       
-	}
+	for(pl=pkl; pl!=nil; pl=pl->pl)
+		if(pl->p && pl->p->bf == bf)
+			return;
+	Bterm(bf);
 }
 
-static void
-initns(void)
-{
-	int i;
-	char d[Nfspath], *e;
-
-	memset(d, 0, sizeof d);
-	i = COM_CheckParm("-basedir");
-	if(i != 0 && i < com_argc - 1)
-		strncpy(d, com_argv[i+1], sizeof(d)-1);
-	else
-		strncpy(d, host_parms.basedir, sizeof(d)-1);
-	e = d + strlen(d) - 1;
-	if(e > d && *e == '/')
-		*e = 0;
-	pakdir(va("%s%s", d, "/id1"));
-	if(COM_CheckParm("-rogue"))
-		pakdir(va("%s%s", d, "/rogue"));
-	if(COM_CheckParm("-hipnotic"))
-		pakdir(va("%s%s", d, "/hipnotic"));
-	i = COM_CheckParm("-game");
-	if(i != 0 && i < com_argc - 1){
-		notid1 = 1;
-		pakdir(va("%s/%s", d, com_argv[i+1]));
-	}
-}
-
 static Biobuf *
 openlmp(char *f, int *len)
 {
@@ -370,17 +327,6 @@
 	return nil;
 }
 
-static void
-closelmp(Biobuf *bf)
-{
-	Paklist *pl;
-
-	for(pl=pkl; pl!=nil; pl=pl->pl)
-		if(pl->p && pl->p->bf == bf)
-			return;
-	Bterm(bf);
-}
-
 static uchar *
 loadlmp(char *f, int mth, int *n)
 {
@@ -411,41 +357,6 @@
 	return buf;
 }
 
-static void
-path(void)
-{
-	Paklist *pl;
-
-	for(pl=pkl; pl!=nil; pl=pl->pl)
-		if(pl->p)
-			Con_Printf("%s (%zd files)\n", pl->p->f, pl->p->e - pl->p->l);
-		else
-			Con_Printf("%s\n", pl->f);
-}
-
-static void
-chkreg(void)
-{
-	u16int *p;
-	Biobuf *bf;
-
-	Cvar_RegisterVariable(&registered);
-	bf = openlmp("gfx/pop.lmp", nil);
-	if(bf == nil){
-		dprint("chkreg: shareware version");
-		if(notid1)
-			fatal("chkreg: phase error -- %r");
-		return;
-	}
-	p = pop;
-	while(p < pop + nelem(pop))
-		if(*p++ != get16b(bf))
-			fatal("chkreg: corrupted pop lump");
-	closelmp(bf);
-	setcvar("registered", "1");
-	dprint("chkreg: registered version");
-}
-
 void *
 loadhunklmp(char *f, int *n)
 {
@@ -469,19 +380,17 @@
 }
 
 void
-pointlmp(void)
+loadpoints(void)
 {
 	int i, n, nv;
-	char f[Nfspath];
 	Biobuf *bf;
 	vec3_t v3;
 	vec_t *v;
 	particle_t *p;
 
-	snprint(f, sizeof f, "maps/%s.pts", sv.name);
-	bf = openlmp(f, &n);
+	bf = openlmp(va("maps/%s.pts", sv.name), &n);
 	if(bf == nil){
-		Con_Printf("pointlmp: can't open %s: %r\n", f);
+		Con_Printf("loadpoints: %r\n");
 		return;
 	}
 	nv = 0;
@@ -495,7 +404,7 @@
 		n -= 3*4+3;
 		nv++;
 		if(free_particles == nil){
-			Con_Printf("pointlmp: insufficient free particles\n");
+			Con_Printf("loadpoints: insufficient free particles\n");
 			break;
 		}
 		p = free_particles;
@@ -509,18 +418,303 @@
 		VectorCopy(v3, p->org);
 	}
 	closelmp(bf);
-	Con_Printf("pointlmp: %d points read\n", nv);
+	Con_Printf("loadpoints: %d points read\n", nv);
 }
 
+static void
+dumpcvars(Biobuf *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");
+}
+
+static void
+dumpkeys(Biobuf *bf)
+{
+	char **k;
+
+	for(k=keybindings; k<keybindings+256; k++)
+		if(*k != nil && **k != 0)
+			Bprint(bf, "bind \"%s\" \"%s\"\n",
+				Key_KeynumToString(k-keybindings), *k);
+}
+
 void
-endlmp(void)
+dumpcfg(void)
 {
+	Biobuf *bf;
+
+	if(!host_initialized || isDedicated)
+		return;
+	bf = bopen(va("%s/config.cfg", fsdir), OWRITE, 1);
+	if(bf == nil){
+		fprint(2, "dumpcfg: %r\n");
+		return;
+	}
+	dumpkeys(bf);
+	dumpcvars(bf);
+	Bterm(bf);
+}
+
+void
+savnames(void)
+{
+	int n, *canld;
+	char (*e)[Nsavcm], (*s)[Nsavcm], *p;
+	Biobuf *bf;
+
+	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 ---");
+		bf = bopen(va("%s/s%d.sav", fsdir, n), OREAD, 1);
+		if(bf == nil){
+			dprint("savnames: %r");
+			continue;
+		}
+		if((p = Brdline(bf, '\n'), p == nil)	/* discard version */
+		|| (p = Brdline(bf, '\n'), p == nil)){
+			dprint("savnames: short read: %r");
+			continue;
+		}
+		strncpy(*s, p, sizeof(*s)-1);
+		for(p=*s; p<*(s+1); p++)
+			if(*p == '_')
+				*p = ' ';
+		*canld = 1;
+		Bterm(bf);
+	}
+}
+
+static void
+dumpedicts(Biobuf *bf, edict_t *ed)
+{
+	int *vp, *ve;
+	char *s;
+	uchar *ev;
+	ddef_t *d, *de;
+	eval_t *v;
+
+	Bprint(bf, "{\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;
+		Bprint(bf, "\"%s\" ", s);
+		Bprint(bf, "\"%s\"\n", PR_UglyValueString(d->type, v));
+	}
+end:
+	Bprint(bf, "}\n");
+}
+
+static void
+dumpdefs(Biobuf *bf)
+{
+	ushort t;
+	ddef_t *d, *de;
+
+	Bprint(bf, "{\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;
+		Bprint(bf, "\"%s\" \"%s\"\n", PR_Str(d->s_name),
+			PR_UglyValueString(t, (eval_t *)&pr_globals[d->ofs]));		
+	}
+	Bprint(bf, "}\n");
+}
+
+int
+dumpsav(char *f, char *cm)
+{
+	int i;
+	char **s, **e;
+	float *fs, *fe;
+	Biobuf *bf;
+
+	bf = bopen(f, OWRITE, 1);
+	if(bf == nil)
+		return -1;
+	Bprint(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);
+	s = sv.lightstyles;
+	e = s + nelem(sv.lightstyles);
+	while(s < e){
+		Bprint(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);
+	return 0;
+}
+
+static void
+loadedicts(Biobuf *bf)
+{
+	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 = Bgetc(bf);
+			if(c == Beof || 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 != Beof);
+	sv.num_edicts = ent;
+}
+
+static int
+loadparms(Biobuf *bf, char *f)
+{
+	int r;
+	float sp[Nparms], *p;
+	char *s, **lp;
+
+	r = -1;
+	p = sp;
+	while(p < sp + nelem(sp)){
+		if(s = Brdline(bf, '\n'), s == nil)
+			goto exit;
+		*p++ = (float)strtod(s, nil);
+	}
+	if(s = Brdline(bf, '\n'), 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)
+		goto exit;
+	CL_Disconnect_f();
+	SV_SpawnServer(s);
+	free(s);
+	if(!sv.active){
+		werrstr("failed to load map %s", f);
+		goto exit;
+	}
+	sv.paused = 1;
+	sv.loadgame = 1;
+	if(s = Brdline(bf, '\n'), 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)
+			goto exit;
+		*lp = Hunk_Alloc(strlen(s)+1);
+		strcpy(*lp++, s);
+		free(s);
+	}
+	r = 0;
+	loadedicts(bf);
+	memcpy(svs.clients->spawn_parms, sp, sizeof sp);
+exit:
+	return r;
+}
+
+int
+loadsav(char *f)
+{
+	int n, r;
+	char *s;
+	Biobuf *bf;
+
+	bf = bopen(f, OREAD, 0);
+	if(bf == nil)
+		return -1;
+	r = -1;
+	if(s = Brdline(bf, '\n'), s == nil)
+		goto exit;
+	n = strtol(s, nil, 10);
+	if(n != Nsavver){
+		werrstr("invalid version %d\n", n);
+		goto exit;
+	}
+	Brdline(bf, '\n');
+	r = loadparms(bf, f);
+exit:
+	Bterm(bf);
+	return r;
+}
+
+void
+closedm(void)
+{
+	if(demobf == nil)
+		return;
 	closelmp(demobf);
 	demobf = nil;
 }
 
+void
+writedm(void)
+{
+	int i;
+
+	put32(demobf, net_message.cursize);
+	for(i=0; i<3; i++)
+		putfl(demobf, cl.viewangles[i]);
+	ewrite(demobf, net_message.data, net_message.cursize);
+}
+
 int
-rlmpmsg(void)
+readdm(void)
 {
 	int n;
 	vec_t *f;
@@ -531,44 +725,21 @@
 	for(n=0, f=cl.mviewangles[0]; n<3; n++)
 		*f++ = getfl(demobf);
 	if(net_message.cursize > Nmsg)
-		fatal("rlmpmsg: invalid message size %d\n", net_message.cursize);
+		fatal("readdm: invalid message size %d\n", net_message.cursize);
 	n = Bread(demobf, net_message.data, net_message.cursize);
 	demoofs = Bseek(demobf, 0, 1);
 	if(n < 0)
-		dprint("rlmpmsg: bad read: %r");
+		dprint("readdm: bad read: %r");
 	if(n != net_message.cursize){
-		dprint("rlmpmsg: short read: %r");
+		dprint("readdm: short read: %r");
 		n = -1;
 	}
 	return n;
 }
 
-void
-wlmpmsg(void)
-{
-	int i;
-
-	put32(demobf, net_message.cursize);
-	for(i=0; i<3; i++)
-		putfl(demobf, cl.viewangles[i]);
-	ewrite(demobf, net_message.data, net_message.cursize);
-}
-
 int
-reclmp(char *f, int trk)
+loaddm(char *f)
 {
-	char s[16];
-
-	demobf = bopen(f, OWRITE, 0);
-	if(demobf == nil)
-		return -1;
-	sprint(s, "%d\n", trk);
-	ewrite(demobf, s, strlen(s));
-	return 0;
-}
-
-demolmp(char *f)
-{
 	int n;
 	char *s;
 
@@ -578,7 +749,7 @@
 	s = Brdline(demobf, '\n');
 	n = Blinelen(demobf) - 1;
 	if(s == nil || n < 0 || n > 11){
-		dprint("demolmp: invalid trk field");
+		dprint("loaddm: invalid trk field");
 		closelmp(demobf);
 		return -1;
 	}
@@ -588,56 +759,141 @@
 	return 0;
 }
 
-void
-crc(u8int v)
+int
+opendm(char *f, int trk)
 {
-	crcn = crcn << 8 ^ crct[crcn >> 8 ^ v];
+	char s[16];
+
+	demobf = bopen(f, OWRITE, 0);
+	if(demobf == nil)
+		return -1;
+	sprint(s, "%d\n", trk);
+	ewrite(demobf, s, strlen(s));
+	return 0;
 }
 
-void
-initcrc(void)
+static Pak *
+pak(char *f)
 {
-	crcn = Ncrc0;
+	int n, ofs, len, nlmp;
+	uchar u[8];
+	Biobuf *bf;
+	Lump *l;
+	Pak *p;
+
+	bf = bopen(f, OREAD, 1);
+	if(bf == nil)
+		return nil;
+	memset(u, 0, sizeof u);
+	eread(bf, u, 4);
+	if(memcmp(u, "PACK", 4) != 0)
+		fatal("pak %s: invalid pak file", f);
+	ofs = get32(bf);
+	len = get32(bf);
+	nlmp = len / Npaksz;
+	if(nlmp > Npaklmp)
+		fatal("pak %s: invalid lump number %d", f, nlmp);
+	if(nlmp != Npak0lmp)
+		notid1 = 1;
+	l = Hunk_AllocName(nlmp * sizeof *l, "pak");
+	p = Hunk_Alloc(sizeof *p);
+	strncpy(p->f, f, sizeof(p->f)-1);
+	p->bf = bf;
+	p->l = l;
+	p->e = l + nlmp;
+	Bseek(bf, ofs, 0);
+	initcrc();
+	while(l < p->e){
+		eread(bf, l->f, 56);
+		for(n=0; n<56; n++)
+			crc(l->f[n]);
+		eread(bf, 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;
 }
 
-void
-ext(char *f, char *e)
+static void
+pakdir(char *d)
 {
-	char *s, *d;
+	int n;
+	char f[Nfspath];
+	Paklist *pl;
+	Pak *p;
 
-	s = strrchr(f, '/');
-	d = strrchr(f, '.');
-	if(d > s)
-		return;
-	strcat(f, e);
+	strncpy(fsdir, d, sizeof(fsdir)-1);
+	pl = Hunk_Alloc(sizeof *pl);
+	strncpy(pl->f, d, sizeof(pl->f)-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){
+			dprint("pakdir: %r");
+			break;
+		}
+		pl = Hunk_Alloc(sizeof *pl);
+		pl->p = p;
+		pl->pl = pkl;
+		pkl = pl;       
+	}
 }
 
-void
-radix(char *f, char *d)
+static void
+initns(void)
 {
-	char *s, *e;
+	int i;
+	char d[Nfspath], *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;
+	memset(d, 0, sizeof d);
+	i = COM_CheckParm("-basedir");
+	if(i != 0 && i < com_argc - 1)
+		strncpy(d, com_argv[i+1], sizeof(d)-1);
+	else
+		strncpy(d, host_parms.basedir, sizeof(d)-1);
+	e = d + strlen(d) - 1;
+	if(e > d && *e == '/')
+		*e = 0;
+	pakdir(va("%s%s", d, "/id1"));
+	if(COM_CheckParm("-rogue"))
+		pakdir(va("%s%s", d, "/rogue"));
+	if(COM_CheckParm("-hipnotic"))
+		pakdir(va("%s%s", d, "/hipnotic"));
+	i = COM_CheckParm("-game");
+	if(i != 0 && i < com_argc - 1){
+		notid1 = 1;
+		pakdir(va("%s/%s", d, com_argv[i+1]));
 	}
 }
 
-void
-unloadfs(void)
+static void
+chkreg(void)
 {
-	Paklist *pl;
+	u16int *p;
+	Biobuf *bf;
 
-	for(pl=pkl; pl!=nil; pl=pl->pl)
-		if(pl->p != nil)
-			Bterm(pl->p->bf);
+	Cvar_RegisterVariable(&registered);
+	bf = openlmp("gfx/pop.lmp", nil);
+	if(bf == nil){
+		dprint("chkreg: shareware version");
+		if(notid1)
+			fatal("chkreg: phase error -- %r");
+		return;
+	}
+	p = pop;
+	while(p < pop + nelem(pop))
+		if(*p++ != get16b(bf))
+			fatal("chkreg: corrupted pop lump");
+	closelmp(bf);
+	setcvar("registered", "1");
+	dprint("chkreg: registered version");
 }
 
 /* TODO: nuke these from orbit */
--- a/host.c
+++ b/host.c
@@ -207,38 +207,7 @@
 	host_time = 1.0;		// so a think at time 0 won't get called
 }
 
-
 /*
-===============
-Host_WriteConfiguration
-
-Writes key bindings and archived cvars to config.cfg
-===============
-*/
-void Host_WriteConfiguration (void)
-{
-	FILE	*f;
-
-// dedicated servers initialize the host but don't parse and set the
-// config.cfg cvars
-	if (host_initialized & !isDedicated)
-	{
-		f = fopen (va("%s/config.cfg",fsdir), "w");
-		if (!f)
-		{
-			Con_Printf ("Couldn't write config.cfg.\n");
-			return;
-		}
-		
-		Key_WriteBindings (f);
-		Cvar_WriteVariables (f);
-
-		fclose (f);
-	}
-}
-
-
-/*
 =================
 SV_ClientPrintf
 
@@ -781,13 +750,12 @@
 // keep Con_Printf from trying to update the screen
 	scr_disabled_for_loading = true;
 
-	Host_WriteConfiguration (); 
+	dumpcfg(); 
 
 	CDAudio_Shutdown ();
 	NET_Shutdown ();
 	S_Shutdown();
 	IN_Shutdown ();
-	unloadfs();
 
 	if (cls.state != ca_dedicated)
 	{
--- a/host_cmd.c
+++ b/host_cmd.c
@@ -1,14 +1,9 @@
 #include <u.h>
 #include <libc.h>
-#include <stdio.h>
 #include "quakedef.h"
 
-extern cvar_t	pausable;
-
 int	current_skill;
 
-void Mod_Print (void);
-
 /*
 ==================
 Host_Quit_f
@@ -327,18 +322,12 @@
 	SV_SpawnServer (mapname);
 }
 
-/*
-==================
-Host_Reconnect_f
-
-This command causes the client to wait for the signon messages again.
-This is sent just before a server changes levels
-==================
-*/
-void Host_Reconnect_f (void)
+/* wait for signon messages; sent before server level change */
+static void
+reconnect(void)
 {
-	SCR_BeginLoadingPlaque ();
-	cls.signon = 0;		// need new connection messages
+	SCR_BeginLoadingPlaque();
+	cls.signon = 0;
 }
 
 /*
@@ -360,287 +349,10 @@
 	}
 	strcpy (name, Cmd_Argv(1));
 	CL_EstablishConnection (name);
-	Host_Reconnect_f ();
+	reconnect();
 }
 
-
 /*
-===============================================================================
-
-LOAD / SAVE GAME
-
-===============================================================================
-*/
-
-#define	SAVEGAME_VERSION	5
-
-/*
-===============
-Host_SavegameComment
-
-Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current 
-===============
-*/
-void Host_SavegameComment (char *text)
-{
-	int		i;
-	char	kills[20];
-
-	for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
-		text[i] = ' ';
-	memcpy (text, cl.levelname, strlen(cl.levelname));
-	sprint (kills,"kills:%3d/%3d", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]);
-	memcpy (text+22, kills, strlen(kills));
-// convert space to _ to make stdio happy
-	for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
-		if (text[i] == ' ')
-			text[i] = '_';
-	text[SAVEGAME_COMMENT_LENGTH] = '\0';
-}
-
-
-/*
-===============
-Host_Savegame_f
-===============
-*/
-void Host_Savegame_f (void)
-{
-	char	name[256];
-	FILE	*f;
-	int		i;
-	char	comment[SAVEGAME_COMMENT_LENGTH+1];
-
-	if (cmd_source != src_command)
-		return;
-
-	if (!sv.active)
-	{
-		Con_Printf ("Not playing a local game.\n");
-		return;
-	}
-
-	if (cl.intermission)
-	{
-		Con_Printf ("Can't save in intermission.\n");
-		return;
-	}
-
-	if (svs.maxclients != 1)
-	{
-		Con_Printf ("Can't save multiplayer games.\n");
-		return;
-	}
-
-	if (Cmd_Argc() != 2)
-	{
-		Con_Printf ("save <savename> : save a game\n");
-		return;
-	}
-
-	if (strstr(Cmd_Argv(1), ".."))
-	{
-		Con_Printf ("Relative pathnames are not allowed.\n");
-		return;
-	}
-		
-	for (i=0 ; i<svs.maxclients ; i++)
-	{
-		if (svs.clients[i].active && (svs.clients[i].edict->v.health <= 0) )
-		{
-			Con_Printf ("Can't savegame with a dead player\n");
-			return;
-		}
-	}
-
-	sprint (name, "%s/%s", fsdir, Cmd_Argv(1));
-	ext(name, ".sav");
-
-	Con_Printf ("Saving game to %s...\n", name);
-	f = fopen (name, "w");
-	if (!f)
-	{
-		Con_Printf ("ERROR: couldn't open.\n");
-		return;
-	}
-	
-	fprintf (f, "%d\n", SAVEGAME_VERSION);
-	Host_SavegameComment (comment);
-	fprintf (f, "%s\n", comment);
-	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
-		fprintf (f, "%f\n", svs.clients->spawn_parms[i]);
-	fprintf (f, "%d\n", current_skill);
-	fprintf (f, "%s\n", sv.name);
-	fprintf (f, "%f\n",sv.time);
-
-// write the light styles
-
-	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
-	{
-		if (sv.lightstyles[i])
-			fprintf (f, "%s\n", sv.lightstyles[i]);
-		else
-			fprintf (f,"m\n");
-	}
-
-
-	ED_WriteGlobals (f);
-	for (i=0 ; i<sv.num_edicts ; i++)
-	{
-		ED_Write (f, EDICT_NUM(i));
-		fflush (f);
-	}
-	fclose (f);
-	Con_Printf ("done.\n");
-}
-
-
-/*
-===============
-Host_Loadgame_f
-===============
-*/
-void Host_Loadgame_f (void)
-{
-	char	name[Nfspath];
-	FILE	*f;
-	char	mapname[Npath];
-	float	time, tfloat;
-	char	str[32768], *start;
-	int		i, r;
-	edict_t	*ent;
-	int		entnum;
-	int		version;
-	float			spawn_parms[NUM_SPAWN_PARMS];
-
-	if (cmd_source != src_command)
-		return;
-
-	if (Cmd_Argc() != 2)
-	{
-		Con_Printf ("load <savename> : load a game\n");
-		return;
-	}
-
-	cls.demonum = -1;		// stop demo loop in case this fails
-
-	sprint (name, "%s/%s", fsdir, Cmd_Argv(1));
-	ext(name, ".sav");
-
-// we can't call SCR_BeginLoadingPlaque, because too much stack space has
-// been used.  The menu calls it before stuffing loadgame command
-//	SCR_BeginLoadingPlaque ();
-
-	Con_Printf ("Loading game from %s...\n", name);
-	f = fopen (name, "r");
-	if (!f)
-	{
-		Con_Printf ("ERROR: couldn't open.\n");
-		return;
-	}
-
-	fscanf (f, "%d\n", &version);
-	if (version != SAVEGAME_VERSION)
-	{
-		fclose (f);
-		Con_Printf ("Savegame is version %d, not %d\n", version, SAVEGAME_VERSION);
-		return;
-	}
-	fscanf (f, "%s\n", str);
-	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
-		fscanf (f, "%f\n", &spawn_parms[i]);
-// this silliness is so we can load 1.06 save files, which have float skill values
-	fscanf (f, "%f\n", &tfloat);
-	current_skill = (int)(tfloat + 0.1);
-	setcvarv ("skill", (float)current_skill);
-
-	fscanf (f, "%s\n",mapname);
-	fscanf (f, "%f\n",&time);
-
-	CL_Disconnect_f ();
-	
-	SV_SpawnServer (mapname);
-	if (!sv.active)
-	{
-		Con_Printf ("Couldn't load map\n");
-		return;
-	}
-	sv.paused = true;		// pause until all clients connect
-	sv.loadgame = true;
-
-// load the light styles
-
-	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
-	{
-		fscanf (f, "%s\n", str);
-		sv.lightstyles[i] = Hunk_Alloc (strlen(str)+1);
-		strcpy (sv.lightstyles[i], str);
-	}
-
-// load the edicts out of the savegame file
-	entnum = -1;		// -1 is the globals
-	while (!feof(f))
-	{
-		for (i=0 ; i<sizeof(str)-1 ; i++)
-		{
-			r = fgetc (f);
-			if (r == EOF || !r)
-				break;
-			str[i] = r;
-			if (r == '}')
-			{
-				i++;
-				break;
-			}
-		}
-		if (i == sizeof(str)-1)
-			fatal ("Loadgame buffer overflow");
-		str[i] = 0;
-		start = COM_Parse(str);
-		if (!com_token[0])
-			break;		// end of file
-		if(strcmp(com_token, "{") != 0)
-			fatal ("First token isn't a brace");
-			
-		if (entnum == -1)
-		{	// parse the global vars
-			ED_ParseGlobals (start);
-		}
-		else
-		{	// parse an edict
-
-			ent = EDICT_NUM(entnum);
-			memset (&ent->v, 0, progs->entityfields * 4);
-			ent->free = false;
-			ED_ParseEdict (start, ent);
-	
-		// link it into the bsp tree
-			if (!ent->free)
-				SV_LinkEdict (ent, false);
-		}
-
-		entnum++;
-	}
-	
-	sv.num_edicts = entnum;
-	sv.time = time;
-
-	fclose (f);
-
-	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
-		svs.clients->spawn_parms[i] = spawn_parms[i];
-
-	if (cls.state != ca_dedicated)
-	{
-		CL_EstablishConnection ("local");
-		Host_Reconnect_f ();
-	}
-}
-
-
-//============================================================================
-
-/*
 ======================
 Host_Name_f
 ======================
@@ -1046,12 +758,11 @@
 		memset (&ent->v, 0, progs->entityfields * 4);
 		ent->v.colormap = NUM_FOR_EDICT(ent);
 		ent->v.team = (host_client->colors & 15) + 1;
-		/* FIXME: amd64 */
 		ent->v.netname = host_client->name - pr_strings;
 
 		// copy spawn parms out of the client_t
 
-		for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
+		for (i=0 ; i< Nparms ; i++)
 			(&pr_global_struct->parm1)[i] = host_client->spawn_parms[i];
 
 		// call the spawn function
@@ -1088,7 +799,7 @@
 	}
 	
 // send all current light styles
-	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+	for (i=0 ; i<Nlights ; i++)
 	{
 		MSG_WriteByte (&host_client->message, svc_lightstyle);
 		MSG_WriteByte (&host_client->message, (char)i);
@@ -1150,9 +861,97 @@
 	host_client->spawned = true;
 }
 
-//===========================================================================
+static void
+savecomment(char *cm)
+{
+	char *s, *p, *e;
 
+	s = cm;
+	e = cm + Nsavcm - 1;
+	memset(s, '_', e - s);
+	p = seprint(s, e+1, "%s", cl.levelname);
+	if(p < e)
+		*p = '_';
+	while(p >= s){
+		if(*p == ' ')
+			*p = '_';	/* because scanf */
+		p--;
+	}
+	p = seprint(s+22, e+1, "kills:%3d/%3d", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]);
+	if(p < e)
+		*p = '_';
+	*e = 0;
+}
 
+static void
+savegame(void)
+{
+	char *s, *a, cm[Nsavcm];
+	client_t *c, *e;
+
+	if(cmd_source != src_command)
+		return;
+	else if(!sv.active){
+		Con_Printf("savegame: not a local game\n");
+		return;
+	}else if(cl.intermission){
+		Con_Printf("savegame: intermission in progress\n");
+		return;
+	}else if(svs.maxclients != 1){
+		Con_Printf("savegame: multiplayer game in progress\n");
+		return;
+	}else if(Cmd_Argc() != 2){
+		Con_Printf("save <savename> : save a game\n");
+		return;
+	}else if(strstr(Cmd_Argv(1), "..") != nil){
+		Con_Printf("savegame: invalid path\n");
+		return;
+	}
+	c = svs.clients;
+	e = c + svs.maxclients;
+	while(c < e){
+		if(c->active && c->edict->v.health <= 0){
+			Con_Printf("savegame: extant dead client\n");
+			return;
+		}
+		c++;
+	}
+	a = Cmd_Argv(1);
+	s = va("%s/%s%s", fsdir, a, ext(a, ".sav"));
+	Con_Printf("Writing game to %s\n", s);
+	savecomment(cm);
+	if(dumpsav(s, cm) < 0)
+		Con_Printf("savegame: %r\n");
+}
+
+static void
+loadgame(void)
+{
+	char *s, *a;
+
+	if(cmd_source != src_command)
+		return;
+	if(Cmd_Argc() != 2){
+		Con_Printf("load <savename> : load a game\n");
+		return;
+	}
+	cls.demonum = -1;
+	a = Cmd_Argv(1);
+	s = va("%s/%s%s", fsdir, a, ext(a, ".sav"));
+	/* Can't call SCR_BeginLoadingPlaque, because too much stack space has
+	 * been used.  The menu calls it before stuffing loadgame command */
+	//SCR_BeginLoadingPlaque();
+	dprint("loadgame: reading from %s", s);
+	if(loadsav(s) < 0){
+		Con_Printf("loadgame: %r\n");
+		return;
+	}
+	if(cls.state != ca_dedicated){
+		CL_EstablishConnection("local");
+		reconnect();
+	}
+}
+
 /*
 ==================
 Host_Kick_f
@@ -1626,7 +1425,7 @@
 	Cmd_AddCommand ("restart", Host_Restart_f);
 	Cmd_AddCommand ("changelevel", Host_Changelevel_f);
 	Cmd_AddCommand ("connect", Host_Connect_f);
-	Cmd_AddCommand ("reconnect", Host_Reconnect_f);
+	Cmd_AddCommand("reconnect", reconnect);
 	Cmd_AddCommand ("name", Host_Name_f);
 	Cmd_AddCommand ("noclip", Host_Noclip_f);
 	Cmd_AddCommand ("version", Host_Version_f);
@@ -1644,8 +1443,8 @@
 	Cmd_AddCommand ("prespawn", Host_PreSpawn_f);
 	Cmd_AddCommand ("kick", Host_Kick_f);
 	Cmd_AddCommand ("ping", Host_Ping_f);
-	Cmd_AddCommand ("load", Host_Loadgame_f);
-	Cmd_AddCommand ("save", Host_Savegame_f);
+	Cmd_AddCommand("load", loadgame);
+	Cmd_AddCommand("save", savegame);
 	Cmd_AddCommand ("give", Host_Give_f);
 
 	Cmd_AddCommand ("startdemos", Host_Startdemos_f);
--- a/in.c
+++ b/in.c
@@ -309,8 +309,10 @@
 {
 	IN_Grabm(0);
 	threadintgrp(THin);
-	close(pfd[0]);
-	close(pfd[1]);
+	if(pfd[0] > 0)
+		close(pfd[0]);
+	if(pfd[1] > 0)
+		close(pfd[1]);
 	if(kchan != nil){
 		chanfree(kchan);
 		kchan = nil;
--- a/keys.c
+++ b/keys.c
@@ -468,24 +468,6 @@
 }
 
 /*
-============
-Key_WriteBindings
-
-Writes lines containing "bind key value"
-============
-*/
-void Key_WriteBindings (FILE *f)
-{
-	int		i;
-
-	for (i=0 ; i<256 ; i++)
-		if (keybindings[i])
-			if (*keybindings[i])
-				fprintf (f, "bind \"%s\" \"%s\"\n", Key_KeynumToString(i), keybindings[i]);
-}
-
-
-/*
 ===================
 Key_Init
 ===================
--- a/keys.h
+++ b/keys.h
@@ -107,7 +107,6 @@
 
 void Key_Event (int key, qboolean down);
 void Key_Init (void);
-void Key_WriteBindings (FILE *f);
 void Key_SetBinding (int keynum, char *binding);
 void Key_ClearStates (void);
 
--- a/menu.c
+++ b/menu.c
@@ -3,6 +3,10 @@
 #include <stdio.h>
 #include "quakedef.h"
 
+char savs[Nsav][Nsavcm];
+int savcanld[Nsav];
+
+/* FIXME: useless and redefined in vid.c? */
 void (*vid_menudrawfn)(void);
 void (*vid_menukeyfn)(int key);
 
@@ -411,46 +415,14 @@
 //=============================================================================
 /* LOAD/SAVE MENU */
 
-int		load_cursor;		// 0 < load_cursor < MAX_SAVEGAMES
+int		load_cursor;		// 0 < load_cursor < Nsav
 
-#define	MAX_SAVEGAMES		12
-char	m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH+1];
-int		loadable[MAX_SAVEGAMES];
-
-void M_ScanSaves (void)
-{
-	int		i, j;
-	char	name[Nfspath];
-	FILE	*f;
-	int		version;
-
-	for (i=0 ; i<MAX_SAVEGAMES ; i++)
-	{
-		strcpy (m_filenames[i], "--- UNUSED SLOT ---");
-		loadable[i] = false;
-		sprint (name, "%s/s%d.sav", fsdir, i);
-		f = fopen (name, "r");
-		if (!f)
-			continue;
-		fscanf (f, "%i\n", &version);
-		fscanf (f, "%79s\n", name);
-		strncpy (m_filenames[i], name, sizeof(m_filenames[i])-1);
-
-	// change _ back to space
-		for (j=0 ; j<SAVEGAME_COMMENT_LENGTH ; j++)
-			if (m_filenames[i][j] == '_')
-				m_filenames[i][j] = ' ';
-		loadable[i] = true;
-		fclose (f);
-	}
-}
-
 void M_Menu_Load_f (void)
 {
 	m_entersound = true;
 	m_state = m_load;
 	key_dest = key_menu;
-	M_ScanSaves ();
+	savnames();
 }
 
 
@@ -465,7 +437,7 @@
 	m_entersound = true;
 	m_state = m_save;
 	key_dest = key_menu;
-	M_ScanSaves ();
+	savnames();
 }
 
 
@@ -477,8 +449,8 @@
 	p = Draw_CachePic ("gfx/p_load.lmp");
 	M_DrawPic ( (320-p->width)/2, 4, p);
 
-	for (i=0 ; i< MAX_SAVEGAMES; i++)
-		M_Print (16, 32 + 8*i, m_filenames[i]);
+	for (i=0 ; i< Nsav; i++)
+		M_Print (16, 32 + 8*i, savs[i]);
 
 // line cursor
 	M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1));
@@ -493,8 +465,8 @@
 	p = Draw_CachePic ("gfx/p_save.lmp");
 	M_DrawPic ( (320-p->width)/2, 4, p);
 
-	for (i=0 ; i<MAX_SAVEGAMES ; i++)
-		M_Print (16, 32 + 8*i, m_filenames[i]);
+	for (i=0 ; i<Nsav ; i++)
+		M_Print (16, 32 + 8*i, savs[i]);
 
 // line cursor
 	M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1));
@@ -511,7 +483,7 @@
 
 	case K_ENTER:
 		S_LocalSound ("misc/menu2.wav");
-		if (!loadable[load_cursor])
+		if (!savcanld[load_cursor])
 			return;
 		m_state = m_none;
 		key_dest = key_game;
@@ -529,7 +501,7 @@
 		S_LocalSound ("misc/menu1.wav");
 		load_cursor--;
 		if (load_cursor < 0)
-			load_cursor = MAX_SAVEGAMES-1;
+			load_cursor = Nsav-1;
 		break;
 
 	case K_DOWNARROW:
@@ -536,7 +508,7 @@
 	case K_RIGHTARROW:
 		S_LocalSound ("misc/menu1.wav");
 		load_cursor++;
-		if (load_cursor >= MAX_SAVEGAMES)
+		if (load_cursor >= Nsav)
 			load_cursor = 0;
 		break;
 	}
@@ -562,7 +534,7 @@
 		S_LocalSound ("misc/menu1.wav");
 		load_cursor--;
 		if (load_cursor < 0)
-			load_cursor = MAX_SAVEGAMES-1;
+			load_cursor = Nsav-1;
 		break;
 
 	case K_DOWNARROW:
@@ -569,7 +541,7 @@
 	case K_RIGHTARROW:
 		S_LocalSound ("misc/menu1.wav");
 		load_cursor++;
-		if (load_cursor >= MAX_SAVEGAMES)
+		if (load_cursor >= Nsav)
 			load_cursor = 0;
 		break;
 	}
--- a/model.c
+++ b/model.c
@@ -256,7 +256,7 @@
 	buf = loadstklmp(mod->name, stackbuf, sizeof stackbuf, nil);
 	if(buf == nil){
 		if(crash)
-			fatal("Mod_LoadModel %s: not found: %r", mod->name);
+			fatal("Mod_LoadModel: %r");
 		return nil;
 	}
 
--- a/model.h
+++ b/model.h
@@ -357,3 +357,4 @@
 
 mleaf_t *Mod_PointInLeaf (float *p, model_t *model);
 byte	*Mod_LeafPVS (mleaf_t *leaf, model_t *model);
+void	Mod_Print(void);
--- a/pr_cmds.c
+++ b/pr_cmds.c
@@ -1471,7 +1471,7 @@
 	// copy spawn parms out of the client_t
 	client = svs.clients + (i-1);
 
-	for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
+	for (i=0 ; i< Nparms ; i++)
 		(&pr_global_struct->parm1)[i] = client->spawn_parms[i];
 }
 
--- a/pr_edict.c
+++ b/pr_edict.c
@@ -465,53 +465,6 @@
 	}
 }
 
-/*
-=============
-ED_Write
-
-For savegames
-=============
-*/
-void ED_Write (FILE *f, edict_t *ed)
-{
-	ddef_t	*d;
-	int		*v;
-	int		i, j;
-	char	*name;
-	int		type;
-
-	fprintf (f, "{\n");
-
-	if (ed->free)
-	{
-		fprintf (f, "}\n");
-		return;
-	}
-	
-	for (i=1 ; i<progs->numfielddefs ; i++)
-	{
-		d = &pr_fielddefs[i];
-		name = PR_Str(d->s_name);
-		if (name[strlen(name)-2] == '_')
-			continue;	// skip _x, _y, _z vars
-			
-		v = (int *)((char *)&ed->v + d->ofs*4);
-
-	// if the value is still all 0, skip the field
-		type = d->type & ~DEF_SAVEGLOBAL;
-		for (j=0 ; j<type_size[type] ; j++)
-			if (v[j])
-				break;
-		if (j == type_size[type])
-			continue;
-	
-		fprintf (f,"\"%s\" ",name);
-		fprintf (f,"\"%s\"\n", PR_UglyValueString(d->type, (eval_t *)v));		
-	}
-
-	fprintf (f, "}\n");
-}
-
 void ED_PrintNum (int ent)
 {
 	ED_Print (EDICT_NUM(ent));
@@ -600,39 +553,6 @@
 
 /*
 =============
-ED_WriteGlobals
-=============
-*/
-void ED_WriteGlobals (FILE *f)
-{
-	ddef_t		*def;
-	int			i;
-	char		*name;
-	int			type;
-
-	fprintf (f,"{\n");
-	for (i=0 ; i<progs->numglobaldefs ; i++)
-	{
-		def = &pr_globaldefs[i];
-		type = def->type;
-		if ( !(def->type & DEF_SAVEGLOBAL) )
-			continue;
-		type &= ~DEF_SAVEGLOBAL;
-
-		if (type != ev_string
-		&& type != ev_float
-		&& type != ev_entity)
-			continue;
-
-		name = PR_Str(def->s_name);		
-		fprintf (f,"\"%s\" ", name);
-		fprintf (f,"\"%s\"\n", PR_UglyValueString(type, (eval_t *)&pr_globals[def->ofs]));		
-	}
-	fprintf (f,"}\n");
-}
-
-/*
-=============
 ED_ParseGlobals
 =============
 */
@@ -984,7 +904,7 @@
 
 	progs = loadhunklmp("progs.dat", &n);
 	if(progs == nil)
-		fatal("PR_LoadProgs: failed to load progs.dat: %r");
+		fatal("PR_LoadProgs: %r");
 	print("Programs occupy %dK.\n", n/1024);
 
 	for (i=0 ; i<n ; i++)
--- a/progs.h
+++ b/progs.h
@@ -49,6 +49,7 @@
 void PR_LoadProgs (void);
 
 char	*PR_Str (int ofs);
+char *PR_UglyValueString (etype_t, eval_t *);
 
 void PR_Profile_f (void);
 
@@ -59,10 +60,8 @@
 // returns a copy of the string allocated from the server's string heap
 
 void ED_Print (edict_t *ed);
-void ED_Write (FILE *f, edict_t *ed);
 char *ED_ParseEdict (char *data, edict_t *ent);
 
-void ED_WriteGlobals (FILE *f);
 void ED_ParseGlobals (char *data);
 
 void ED_LoadFromFile (char *data);
--- a/qk1.c
+++ b/qk1.c
@@ -19,13 +19,17 @@
 	fprint(2, "%s\n", s);
 }
 
+/* FIXME: merge dprint/fatal? */
 void
 fatal(char *fmt, ...)
 {
+	char s[1024];
 	va_list arg;
 
-	Host_Shutdown();
 	va_start(arg, fmt);
-	sysfatal(fmt, arg);
+	vseprint(s, s+sizeof s, fmt, arg);
 	va_end(arg);
+	Host_Shutdown();
+	fprint(2, "%s: %s\n", argv0, s);
+	exits(s);
 }
--- a/quakedef.h
+++ b/quakedef.h
@@ -36,12 +36,10 @@
 // per-level limits
 //
 #define	MAX_EDICTS		600			// FIXME: ouch! ouch! ouch!
-#define	MAX_LIGHTSTYLES	64
+#define	Nlights	64
 #define	MAX_MODELS		256			// these are sent over the net as bytes
 #define	MAX_SOUNDS		256			// so they cannot be blindly increased
 
-#define	SAVEGAME_COMMENT_LENGTH	39
-
 #define	MAX_STYLESTRING	64
 
 //
@@ -240,6 +238,7 @@
 void Host_ClientCommands (char *fmt, ...);
 void Host_ShutdownServer (qboolean crash);
 
+extern cvar_t	pausable;
 extern qboolean		msg_suppress_1;		// suppresses resolution and cache size console output
 										//  an fullscreen DIB focus gain/loss
 extern int			current_skill;		// skill level for currently loaded level (in case
--- a/r_light.c
+++ b/r_light.c
@@ -19,7 +19,7 @@
 // light animations
 // 'm' is normal light, 'a' is no light, 'z' is double bright
 	i = (int)(cl.time*10);
-	for (j=0 ; j<MAX_LIGHTSTYLES ; j++)
+	for (j=0 ; j<Nlights ; j++)
 	{
 		if (!cl_lightstyle[j].length)
 		{
--- a/r_main.c
+++ b/r_main.c
@@ -171,7 +171,7 @@
 	R_InitTurb ();
 	
 	Cmd_AddCommand ("timerefresh", R_TimeRefresh_f);	
-	Cmd_AddCommand("pointfile", pointlmp);
+	Cmd_AddCommand("pointfile", loadpoints);
 
 	Cvar_RegisterVariable (&r_draworder);
 	Cvar_RegisterVariable (&r_speeds);
--- a/server.h
+++ b/server.h
@@ -29,7 +29,7 @@
 	char		*model_precache[MAX_MODELS];	// NULL terminated
 	struct model_s	*models[MAX_MODELS];
 	char		*sound_precache[MAX_SOUNDS];	// NULL terminated
-	char		*lightstyles[MAX_LIGHTSTYLES];
+	char		*lightstyles[Nlights];
 	int			num_edicts;
 	int			max_edicts;
 	edict_t		*edicts;			// can NOT be array indexed, because
@@ -49,7 +49,6 @@
 
 
 #define	NUM_PING_TIMES		16
-#define	NUM_SPAWN_PARMS		16
 
 typedef struct client_s
 {
@@ -78,7 +77,7 @@
 	int				num_pings;			// ping_times[num_pings%NUM_PING_TIMES]
 
 // spawn parms are carried from level to level
-	float			spawn_parms[NUM_SPAWN_PARMS];
+	float			spawn_parms[Nparms];
 
 // client known data for deltas	
 	int				old_frags;
--- a/sv_main.c
+++ b/sv_main.c
@@ -227,7 +227,7 @@
 void SV_ConnectClient (int clientnum)
 {
 	int i, edictnum;
-	float spawn_parms[NUM_SPAWN_PARMS];
+	float spawn_parms[Nparms];
 	edict_t *ent;
 	client_t *client;
 	struct qsocket_s *netconnection;
@@ -268,7 +268,7 @@
 	else{
 		// call the progs to get default spawn parms for the new client
 		PR_ExecuteProgram(pr_global_struct->SetNewParms);
-		for(i=0; i<NUM_SPAWN_PARMS; i++)
+		for(i=0; i<Nparms; i++)
 			client->spawn_parms[i] = (&pr_global_struct->parm1)[i];
 	}
 
@@ -993,7 +993,7 @@
 	// call the progs to get default spawn parms for the new client
 		pr_global_struct->self = EDICT_TO_PROG(host_client->edict);
 		PR_ExecuteProgram (pr_global_struct->SetChangeParms);
-		for (j=0 ; j<NUM_SPAWN_PARMS ; j++)
+		for (j=0 ; j<Nparms ; j++)
 			host_client->spawn_parms[j] = (&pr_global_struct->parm1)[j];
 	}
 }