diff --git a/Quake/Makefile b/Quake/Makefile
index d83ab0e79..3a4e33e3e 100644
--- a/Quake/Makefile
+++ b/Quake/Makefile
@@ -264,6 +264,7 @@ OBJS = strlcat.o \
 	crc.o \
 	cvar.o \
 	cfgfile.o \
+	filesys.o \
 	host.o \
 	host_cmd.o \
 	mathlib.o \
diff --git a/Quake/Makefile.w32 b/Quake/Makefile.w32
index 0d29ecafc..b102d7a41 100644
--- a/Quake/Makefile.w32
+++ b/Quake/Makefile.w32
@@ -271,6 +271,7 @@ OBJS = strlcat.o \
 	crc.o \
 	cvar.o \
 	cfgfile.o \
+	filesys.o \
 	host.o \
 	host_cmd.o \
 	mathlib.o \
diff --git a/Quake/Makefile.w64 b/Quake/Makefile.w64
index 66e15a336..808db2c73 100644
--- a/Quake/Makefile.w64
+++ b/Quake/Makefile.w64
@@ -264,6 +264,7 @@ OBJS = strlcat.o \
 	crc.o \
 	cvar.o \
 	cfgfile.o \
+	filesys.o \
 	host.o \
 	host_cmd.o \
 	mathlib.o \
diff --git a/Quake/bgmusic.c b/Quake/bgmusic.c
index 26a9a485e..61f6280cd 100644
--- a/Quake/bgmusic.c
+++ b/Quake/bgmusic.c
@@ -340,7 +340,7 @@ void BGM_PlayCDtrack (byte track, qboolean looping)
 	//		goto _next;
 		q_snprintf(tmp, sizeof(tmp), "%s/track%02d.%s",
 				MUSIC_DIRNAME, (int)track, handler->ext);
-		if (! COM_FileExists(tmp, &path_id))
+		if (!QFS_FileExists(tmp, &path_id))
 			goto _next;
 		if (path_id > prev_id)
 		{
diff --git a/Quake/cfgfile.c b/Quake/cfgfile.c
index 3373db4dc..5648bd4bb 100644
--- a/Quake/cfgfile.c
+++ b/Quake/cfgfile.c
@@ -21,9 +21,6 @@
 
 #include "quakedef.h"
 
-
-static fshandle_t		*cfg_file;
-
 /*
 ===================
 CFG_ReadCvars
@@ -35,12 +32,17 @@ the num_vars argument must be the exact number of strings in the
 array, otherwise I have nothing against going out of bounds.
 ===================
 */
-void CFG_ReadCvars (const char **vars, int num_vars)
+void CFG_ReadCvars (const char *cfg_name, const char **vars, int num_vars)
 {
 	char	buff[1024], *tmp;
 	int			i, j;
+	qfshandle_t *cfg_file;
 
-	if (!cfg_file || num_vars < 1)
+	if (num_vars < 1)
+		return;
+	
+	cfg_file = QFS_OpenFile (cfg_name, NULL);
+	if (!cfg_file)
 		return;
 
 	j = 0;
@@ -52,7 +54,7 @@ void CFG_ReadCvars (const char **vars, int num_vars)
 		// writes to the config file. although I'm trying to be as
 		// much cautious as possible, if the user screws it up by
 		// editing it, it's his fault.
-		if (FS_fgets(buff, sizeof(buff), cfg_file))
+		if (QFS_GetLine (cfg_file, buff, sizeof(buff)))
 		{
 			// remove end-of-line characters
 			while (buff[i])
@@ -104,9 +106,7 @@ void CFG_ReadCvars (const char **vars, int num_vars)
 		if (j == num_vars)
 			break;
 
-	} while (!FS_feof(cfg_file) && !FS_ferror(cfg_file));
-
-	FS_rewind (cfg_file);
+	} while (!QFS_Eof (cfg_file));
 }
 
 /*
@@ -139,37 +139,3 @@ void CFG_ReadCvarOverrides (const char **vars, int num_vars)
 		}
 	}
 }
-
-void CFG_CloseConfig (void)
-{
-	if (cfg_file)
-	{
-		FS_fclose(cfg_file);
-		Z_Free(cfg_file);
-		cfg_file = NULL;
-	}
-}
-
-int CFG_OpenConfig (const char *cfg_name)
-{
-	FILE	*f;
-	long		length;
-	qboolean	pak;
-
-	CFG_CloseConfig ();
-
-	length = (long) COM_FOpenFile (cfg_name, &f, NULL);
-	pak = file_from_pak;
-	if (length == -1)
-		return -1;
-
-	cfg_file = (fshandle_t *) Z_Malloc(sizeof(fshandle_t));
-	cfg_file->file = f;
-	cfg_file->start = ftell(f);
-	cfg_file->pos = 0;
-	cfg_file->length = length;
-	cfg_file->pak = pak;
-
-	return 0;
-}
-
diff --git a/Quake/cfgfile.h b/Quake/cfgfile.h
index dbe8f39f8..4343b0212 100644
--- a/Quake/cfgfile.h
+++ b/Quake/cfgfile.h
@@ -22,14 +22,7 @@
 #ifndef __CFGFILE_H
 #define __CFGFILE_H
 
-int CFG_OpenConfig (const char *cfg_name);
-// opens the given config file. only one open config file is
-// kept: previosly opened one, if any, will be closed.
-
-void CFG_CloseConfig (void);
-// closes the currently open config file.
-
-void CFG_ReadCvars (const char **vars, int num_vars);
+void CFG_ReadCvars (const char *cfg_name, const char **vars, int num_vars);
 // reads the values of cvars in the given list from the opened
 // config file.
 
diff --git a/Quake/cl_demo.c b/Quake/cl_demo.c
index a18bd2c16..24e238fe1 100644
--- a/Quake/cl_demo.c
+++ b/Quake/cl_demo.c
@@ -104,13 +104,12 @@ void CL_StopPlayback (void)
 	if (!cls.demoplayback)
 		return;
 
-	fclose (cls.demofile);
+	QFS_CloseFile (cls.inpdemo);
 	cls.demoplayback = false;
 	cls.demopaused = false;
 	cls.demospeed = 1.f;
-	cls.demofile = NULL;
+	cls.inpdemo = NULL;
 	cls.demofilesize = 0;
-	cls.demofilestart = 0;
 	cls.demofilename[0] = '\0';
 	cls.state = ca_disconnected;
 
@@ -137,14 +136,14 @@ static void CL_WriteDemoMessage (void)
 	float	f;
 
 	len = LittleLong (net_message.cursize);
-	fwrite (&len, 4, 1, cls.demofile);
+	fwrite (&len, 4, 1, cls.outpdemo);
 	for (i = 0; i < 3; i++)
 	{
 		f = LittleFloat (cl.viewangles[i]);
-		fwrite (&f, 4, 1, cls.demofile);
+		fwrite (&f, 4, 1, cls.outpdemo);
 	}
-	fwrite (net_message.data, net_message.cursize, 1, cls.demofile);
-	fflush (cls.demofile);
+	fwrite (net_message.data, net_message.cursize, 1, cls.outpdemo);
+	fflush (cls.outpdemo);
 }
 
 /*
@@ -257,7 +256,7 @@ static qboolean CL_NextDemoFrame (void)
 			demoframe_t newframe;
 
 			memset (&newframe, 0, sizeof (newframe));
-			newframe.fileofs = Sys_ftell (cls.demofile);
+			newframe.fileofs = QFS_Tell (cls.inpdemo);
 			newframe.intermission = cl.intermission;
 			newframe.forceunderwater = cl.forceunderwater;
 			VEC_PUSH (demo_rewind.frames, newframe);
@@ -276,7 +275,7 @@ static qboolean CL_NextDemoFrame (void)
 		return false;
 
 	lastframe = &demo_rewind.frames[framecount - 1];
-	Sys_fseek (cls.demofile, lastframe->fileofs, SEEK_SET);
+	QFS_Seek (cls.inpdemo, lastframe->fileofs, SEEK_SET);
 
 	if (framecount == 1)
 		demo_rewind.backstop = true;
@@ -448,12 +447,12 @@ static int CL_GetDemoMessage (void)
 	if (!CL_NextDemoFrame ())
 		return 0;
 
-	if (fread (&net_message.cursize, 4, 1, cls.demofile) != 1)
+	if (QFS_ReadFile (cls.inpdemo, &net_message.cursize, 4) != 4)
 		goto readerror;
 	VectorCopy (cl.mviewangles[0], cl.mviewangles[1]);
 	for (i = 0 ; i < 3 ; i++)
 	{
-		if (fread (&f, 4, 1, cls.demofile) != 1)
+		if (QFS_ReadFile (cls.inpdemo, &f, 4) != 4)
 			goto readerror;
 		cl.mviewangles[0][i] = LittleFloat (f);
 	}
@@ -461,7 +460,7 @@ static int CL_GetDemoMessage (void)
 	net_message.cursize = LittleLong (net_message.cursize);
 	if (net_message.cursize > MAX_MSGLEN)
 		Sys_Error ("Demo message > MAX_MSGLEN");
-	if (fread (net_message.data, net_message.cursize, 1, cls.demofile) != 1)
+	if (QFS_ReadFile (cls.inpdemo, net_message.data, net_message.cursize) != (size_t)net_message.cursize)
 	{
 	readerror:
 		CL_StopPlayback ();
@@ -538,8 +537,8 @@ void CL_Stop_f (void)
 	CL_WriteDemoMessage ();
 
 // finish up
-	fclose (cls.demofile);
-	cls.demofile = NULL;
+	fclose (cls.outpdemo);
+	cls.outpdemo = NULL;
 	cls.demorecording = false;
 	Con_Printf ("Completed demo\n");
 	
@@ -629,15 +628,15 @@ void CL_Record_f (void)
 	Con_LinkPrintf (name, "%s", relname);
 	Con_SafePrintf (".\n");
 
-	cls.demofile = Sys_fopen (name, "wb");
-	if (!cls.demofile)
+	cls.outpdemo = Sys_fopen (name, "wb");
+	if (!cls.outpdemo)
 	{
 		Con_Printf ("ERROR: couldn't create %s\n", relname);
 		return;
 	}
 
 	cls.forcetrack = track;
-	fprintf (cls.demofile, "%i\n", cls.forcetrack);
+	fprintf (cls.outpdemo, "%i\n", cls.forcetrack);
 	q_strlcpy (cls.demofilename, name, sizeof (cls.demofilename));
 
 	cls.demorecording = true;
@@ -760,6 +759,7 @@ play [demoname]
 void CL_PlayDemo_f (void)
 {
 	char	name[MAX_OSPATH];
+	char	linebuf[32];
 
 	if (cmd_source != src_command)
 		return;
@@ -779,22 +779,19 @@ void CL_PlayDemo_f (void)
 
 	Con_Printf ("Playing demo from %s.\n", name);
 
-	COM_FOpenFile (name, &cls.demofile, NULL);
-	if (!cls.demofile)
+	cls.inpdemo = QFS_FOpenFile (name, NULL);
+	if (!cls.inpdemo)
 	{
 		Con_Printf ("ERROR: couldn't open %s\n", name);
 		cls.demonum = -1;	// stop demo loop
 		return;
 	}
 
-// ZOID, fscanf is evil
-// O.S.: if a space character e.g. 0x20 (' ') follows '\n',
-// fscanf skips that byte too and screws up further reads.
-//	fscanf (cls.demofile, "%i\n", &cls.forcetrack);
-	if (fscanf (cls.demofile, "%i", &cls.forcetrack) != 1 || fgetc (cls.demofile) != '\n')
+	if (QFS_GetLine (cls.inpdemo, linebuf, sizeof(linebuf)) == 0
+		|| sscanf (linebuf, "%i", &cls.forcetrack) != 1)
 	{
-		fclose (cls.demofile);
-		cls.demofile = NULL;
+		QFS_CloseFile (cls.inpdemo);
+		cls.inpdemo = NULL;
 		cls.demonum = -1;	// stop demo loop
 		Con_Printf ("ERROR: demo \"%s\" is invalid\n", name);
 		return;
@@ -810,8 +807,7 @@ void CL_PlayDemo_f (void)
 	q_strlcpy (cls.demofilename, name, sizeof (cls.demofilename));
 	cls.state = ca_connected;
 	cls.demoloop = Cmd_Argc () >= 3 ? Q_atoi (Cmd_Argv (2)) != 0 : false;
-	cls.demofilestart = Sys_ftell (cls.demofile);
-	cls.demofilesize = com_filesize;
+	cls.demofilesize = QFS_FileSize (cls.inpdemo);
 
 // if this is a player-initiated demo, get rid of the console
 	if (cls.demonum == -1 && key_dest == key_console)
@@ -858,7 +854,7 @@ void CL_TimeDemo_f (void)
 	}
 
 	CL_PlayDemo_f ();
-	if (!cls.demofile)
+	if (!cls.inpdemo)
 		return;
 
 // cls.td_starttime will be grabbed at the second frame of the demo, so
diff --git a/Quake/client.h b/Quake/client.h
index 3b36c3f4c..81e10e85e 100644
--- a/Quake/client.h
+++ b/Quake/client.h
@@ -134,8 +134,8 @@ typedef struct
 	qboolean	timedemo;
 	int		forcetrack;		// -1 = use normal cd track
 	char		demofilename[MAX_OSPATH];
-	FILE		*demofile;
-	qfileofs_t	demofilestart;	// for demos in pak files
+	qfshandle_t	*inpdemo;
+	FILE		*outpdemo;
 	qfileofs_t	demofilesize;
 	int		td_lastframe;		// to meter out one message a frame
 	int		td_startframe;		// host_framecount at start
diff --git a/Quake/cmd.c b/Quake/cmd.c
index 816ef5003..85f716291 100644
--- a/Quake/cmd.c
+++ b/Quake/cmd.c
@@ -303,7 +303,7 @@ void Cmd_Exec_f (void)
 	// "exec config.cfg pls" will execute config.cfg
 	if (Cmd_Argc () == 2 && !strcmp (path, "config.cfg"))
 	{
-		f = (const char *)COM_LoadHunkFile (CONFIG_NAME, NULL);
+		f = (const char *)QFS_LoadHunkFile (CONFIG_NAME, NULL, NULL);
 		if (f)
 		{
 			path = CONFIG_NAME;
@@ -311,7 +311,7 @@ void Cmd_Exec_f (void)
 		}
 	}
 
-	f = (const char *)COM_LoadHunkFile (path, NULL);
+	f = (const char *)QFS_LoadHunkFile (path, NULL, NULL);
 	if (!f && !strcmp(Cmd_Argv(1), "default.cfg")) {
 		f = default_cfg;	/* see above.. */
 	}
diff --git a/Quake/common.c b/Quake/common.c
index cd25e894b..3ae7a7522 100644
--- a/Quake/common.c
+++ b/Quake/common.c
@@ -29,6 +29,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include <errno.h>
 #include "miniz.h"
 #include "unicode_translit.h"
+#include "filesys.h"
 
 static const char	*largv[MAX_NUM_ARGVS + 1];
 static char	argvdummy[] = " ";
@@ -1480,13 +1481,12 @@ being registered.
 */
 static void COM_CheckRegistered (void)
 {
-	int		h;
 	unsigned short	check[128];
-	int		i;
+	size_t		i;
 
-	COM_OpenFile("gfx/pop.lmp", &h, NULL);
+	qfshandle_t* h = QFS_OpenFile("gfx/pop.lmp", NULL);
 
-	if (h == -1)
+	if (h == NULL)
 	{
 		Cvar_SetROM ("registered", "0");
 		Con_Printf ("Playing shareware version.\n");
@@ -1499,9 +1499,9 @@ static void COM_CheckRegistered (void)
 		return;
 	}
 
-	i = Sys_FileRead (h, check, sizeof(check));
-	COM_CloseFile (h);
-	if (i != (int) sizeof(check))
+	i = QFS_ReadFile (h, check, sizeof(check));
+	QFS_CloseFile (h);
+	if (i != sizeof(check))
 		goto corrupt;
 
 	for (i = 0; i < 128; i++)
@@ -1649,38 +1649,17 @@ QUAKE FILESYSTEM
 =============================================================================
 */
 
-THREAD_LOCAL qfileofs_t com_filesize;
-
-
-//
-// on-disk pakfile
-//
-typedef struct
-{
-	char	name[56];
-	int		filepos, filelen;
-} dpackfile_t;
-
-typedef struct
-{
-	char	id[4];
-	int		dirofs;
-	int		dirlen;
-} dpackheader_t;
-
-#define MAX_FILES_IN_PACK	2048
-
 char	com_gamenames[1024];	//eg: "hipnotic;quoth;warp" ... no id1
 char	com_gamedir[MAX_OSPATH];
 char	com_basedirs[MAX_BASEDIRS][MAX_OSPATH];
 int		com_numbasedirs;
 char	com_nightdivedir[MAX_OSPATH];
 char	com_userprefdir[MAX_OSPATH];
-THREAD_LOCAL int	file_from_pak;		// ZOID: global indicating that file came from a pak
 
 searchpath_t	*com_searchpaths;
 searchpath_t	*com_base_searchpaths;
 
+
 /*
 ============
 COM_Path_f
@@ -1693,9 +1672,10 @@ static void COM_Path_f (void)
 	Con_Printf ("Current search path:\n");
 	for (s = com_searchpaths; s; s = s->next)
 	{
-		if (s->pack)
+		const char* pack_filename = QFS_PackInfoName (s->pack);
+		if (pack_filename)
 		{
-			Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
+			Con_Printf ("%s (%i files)\n", pack_filename, QFS_PackInfoNumFiles (s->pack));
 		}
 		else
 			Con_Printf ("%s\n", s->filename);
@@ -1711,21 +1691,22 @@ The filename will be prefixed by the current game directory
 */
 void COM_WriteFile (const char *filename, const void *data, int len)
 {
-	int		handle;
+	FILE*	handle;
 	char	name[MAX_OSPATH];
 
 	q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, filename);
 
-	handle = Sys_FileOpenWrite (name);
-	if (handle == -1)
+	handle = Sys_fopen (name, "wb");
+	if (!handle)
 	{
 		Sys_Printf ("COM_WriteFile: failed on %s\n", name);
 		return;
 	}
 
 	Sys_Printf ("COM_WriteFile: %s\n", name);
-	Sys_FileWrite (handle, data, len);
-	Sys_FileClose (handle);
+	
+	fwrite (data, len, 1, handle);
+	fclose (handle);
 }
 
 /*
@@ -1843,261 +1824,6 @@ void COM_CreatePath (char *path)
 	}
 }
 
-/*
-================
-COM_filelength
-================
-*/
-qfileofs_t COM_filelength (FILE *f)
-{
-	qfileofs_t	pos, end;
-
-	pos = Sys_ftell (f);
-	Sys_fseek (f, 0, SEEK_END);
-	end = Sys_ftell (f);
-	Sys_fseek (f, pos, SEEK_SET);
-
-	return end;
-}
-
-/*
-===========
-COM_FindFile
-
-Finds the file in the search path.
-Sets com_filesize and one of handle or file
-If neither of file or handle is set, this
-can be used for detecting a file's presence.
-===========
-*/
-static int COM_FindFile (const char *filename, int *handle, FILE **file,
-							unsigned int *path_id)
-{
-	searchpath_t	*search;
-	char		netpath[MAX_OSPATH];
-	pack_t		*pak;
-	int			i;
-
-	if (file && handle)
-		Sys_Error ("COM_FindFile: both handle and file set");
-
-	file_from_pak = 0;
-
-//
-// search through the path, one element at a time
-//
-	for (search = com_searchpaths; search; search = search->next)
-	{
-		if (search->pack)	/* look through all the pak file elements */
-		{
-			pak = search->pack;
-			for (i = 0; i < pak->numfiles; i++)
-			{
-				if (strcmp(pak->files[i].name, filename) != 0)
-					continue;
-				// found it!
-				com_filesize = pak->files[i].filelen;
-				file_from_pak = 1;
-				if (path_id)
-					*path_id = search->path_id;
-				if (handle)
-				{
-					*handle = pak->handle;
-					Sys_FileSeek (pak->handle, pak->files[i].filepos);
-					return com_filesize;
-				}
-				else if (file)
-				{ /* open a new file on the pakfile */
-					*file = Sys_fopen (pak->filename, "rb");
-					if (*file)
-						fseek (*file, pak->files[i].filepos, SEEK_SET);
-					return com_filesize;
-				}
-				else /* for COM_FileExists() */
-				{
-					return com_filesize;
-				}
-			}
-		}
-		else	/* check a file in the directory tree */
-		{
-			if (!registered.value)
-			{ /* if not a registered version, don't ever go beyond base */
-				if ( strchr (filename, '/') || strchr (filename,'\\'))
-					continue;
-			}
-
-			q_snprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename);
-			if (! (Sys_FileType(netpath) & FS_ENT_FILE))
-				continue;
-
-			if (path_id)
-				*path_id = search->path_id;
-			if (handle)
-			{
-				com_filesize = Sys_FileOpenRead (netpath, &i);
-				*handle = i;
-				return com_filesize;
-			}
-			else if (file)
-			{
-				*file = Sys_fopen (netpath, "rb");
-				com_filesize = (*file == NULL) ? -1 : COM_filelength (*file);
-				return com_filesize;
-			}
-			else
-			{
-				return 0; /* dummy valid value for COM_FileExists() */
-			}
-		}
-	}
-
-	if (developer.value)
-	{
-		const char *ext = COM_FileGetExtension (filename);
-
-		if (strcmp(ext, "pcx") != 0 &&
-			strcmp(ext, "tga") != 0 &&
-			strcmp(ext, "lit") != 0 &&
-			strcmp(ext, "vis") != 0 &&
-			strcmp(ext, "ent") != 0)
-			Con_DPrintf ("FindFile: can't find %s\n", filename);
-		else
-			Con_DPrintf2 ("FindFile: can't find %s\n", filename);
-	}
-
-	if (handle)
-		*handle = -1;
-	if (file)
-		*file = NULL;
-	com_filesize = -1;
-	return com_filesize;
-}
-
-
-/*
-===========
-COM_FileExists
-
-Returns whether the file is found in the quake filesystem.
-===========
-*/
-qboolean COM_FileExists (const char *filename, unsigned int *path_id)
-{
-	int ret = COM_FindFile (filename, NULL, NULL, path_id);
-	return (ret == -1) ? false : true;
-}
-
-/*
-===========
-COM_OpenFile
-
-filename never has a leading slash, but may contain directory walks
-returns a handle and a length
-it may actually be inside a pak file
-===========
-*/
-int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id)
-{
-	return COM_FindFile (filename, handle, NULL, path_id);
-}
-
-/*
-===========
-COM_FOpenFile
-
-If the requested file is inside a packfile, a new FILE * will be opened
-into the file.
-===========
-*/
-int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id)
-{
-	return COM_FindFile (filename, NULL, file, path_id);
-}
-
-/*
-============
-COM_CloseFile
-
-If it is a pak file handle, don't really close it
-============
-*/
-void COM_CloseFile (int h)
-{
-	searchpath_t	*s;
-
-	for (s = com_searchpaths; s; s = s->next)
-		if (s->pack && s->pack->handle == h)
-			return;
-
-	Sys_FileClose (h);
-}
-
-
-/*
-============
-COM_LoadFile
-
-Filename are reletive to the quake directory.
-Allways appends a 0 byte.
-============
-*/
-#define	LOADFILE_HUNK		0
-#define	LOADFILE_MALLOC		1
-
-byte *COM_LoadFile (const char *path, int usehunk, unsigned int *path_id)
-{
-	int		h;
-	byte	*buf;
-	char	base[32];
-	int	len, nread;
-
-	buf = NULL;	// quiet compiler warning
-
-// look for it in the filesystem or pack files
-	len = COM_OpenFile (path, &h, path_id);
-	if (h == -1)
-		return NULL;
-
-// extract the filename base name for hunk tag
-	COM_FileBase (path, base, sizeof(base));
-
-	switch (usehunk)
-	{
-	case LOADFILE_HUNK:
-		buf = (byte *) Hunk_AllocNameNoFill (len+1, base);
-		break;
-	case LOADFILE_MALLOC:
-		buf = (byte *) malloc (len+1);
-		break;
-	default:
-		Sys_Error ("COM_LoadFile: bad usehunk");
-	}
-
-	if (!buf)
-		Sys_Error ("COM_LoadFile: not enough space for %s", path);
-
-	((byte *)buf)[len] = 0;
-
-	nread = Sys_FileRead (h, buf, len);
-	COM_CloseFile (h);
-	if (nread != len)
-		Sys_Error ("COM_LoadFile: Error reading %s", path);
-
-	return buf;
-}
-
-byte *COM_LoadHunkFile (const char *path, unsigned int *path_id)
-{
-	return COM_LoadFile (path, LOADFILE_HUNK, path_id);
-}
-
-// returns malloc'd memory
-byte *COM_LoadMallocFile (const char *path, unsigned int *path_id)
-{
-	return COM_LoadFile (path, LOADFILE_MALLOC, path_id);
-}
-
 byte *COM_LoadMallocFile_TextMode_OSPath (const char *path, long *len_out)
 {
 	FILE	*f;
@@ -2112,7 +1838,7 @@ byte *COM_LoadMallocFile_TextMode_OSPath (const char *path, long *len_out)
 	if (f == NULL)
 		return NULL;
 
-	len = COM_filelength (f);
+	len = (long)Sys_filelength (f);
 	if (len < 0)
 	{
 		fclose (f);
@@ -2206,87 +1932,6 @@ const char *COM_ParseStringNewline(const char *buffer)
 	return buffer + i;
 }
 
-/*
-=================
-COM_LoadPackFile -- johnfitz -- modified based on topaz's tutorial
-
-Takes an explicit (not game tree related) path to a pak file.
-
-Loads the header and directory, adding the files at the beginning
-of the list so they override previous pack files.
-=================
-*/
-static pack_t *COM_LoadPackFile (const char *packfile)
-{
-	dpackheader_t	header;
-	int		i;
-	packfile_t	*newfiles;
-	int		numpackfiles;
-	pack_t		*pack;
-	int		packhandle;
-	dpackfile_t	info[MAX_FILES_IN_PACK];
-
-	if (Sys_FileOpenRead (packfile, &packhandle) == -1)
-		return NULL;
-
-	if (Sys_FileRead(packhandle, &header, sizeof(header)) != (int) sizeof(header) ||
-	    header.id[0] != 'P' || header.id[1] != 'A' || header.id[2] != 'C' || header.id[3] != 'K')
-		Sys_Error ("%s is not a packfile", packfile);
-
-	header.dirofs = LittleLong (header.dirofs);
-	header.dirlen = LittleLong (header.dirlen);
-
-	numpackfiles = header.dirlen / sizeof(dpackfile_t);
-
-	if (header.dirlen < 0 || header.dirofs < 0)
-	{
-		Sys_Error ("Invalid packfile %s (dirlen: %i, dirofs: %i)",
-					packfile, header.dirlen, header.dirofs);
-	}
-	if (!numpackfiles)
-	{
-		Sys_Printf ("WARNING: %s has no files, ignored\n", packfile);
-		Sys_FileClose (packhandle);
-		return NULL;
-	}
-	if (numpackfiles > MAX_FILES_IN_PACK)
-		Sys_Error ("%s has %i files", packfile, numpackfiles);
-
-	if (numpackfiles != PAK0_COUNT)
-		com_modified = true;	// not the original file
-
-	newfiles = (packfile_t *) Z_Malloc(numpackfiles * sizeof(packfile_t));
-
-	Sys_FileSeek (packhandle, header.dirofs);
-	if (Sys_FileRead(packhandle, info, header.dirlen) != header.dirlen)
-		Sys_Error ("Error reading %s", packfile);
-
-	// crc the directory to check for modifications
-	if (!com_modified)
-	{
-		unsigned short	crc = CRC_Block (info, header.dirlen);
-		if (crc != PAK0_CRC_V106 && crc != PAK0_CRC_V101 && crc != PAK0_CRC_V100)
-			com_modified = true;
-	}
-
-	// parse the directory
-	for (i = 0; i < numpackfiles; i++)
-	{
-		q_strlcpy (newfiles[i].name, info[i].name, sizeof(newfiles[i].name));
-		newfiles[i].filepos = LittleLong(info[i].filepos);
-		newfiles[i].filelen = LittleLong(info[i].filelen);
-	}
-
-	pack = (pack_t *) Z_Malloc (sizeof (pack_t));
-	q_strlcpy (pack->filename, packfile, sizeof(pack->filename));
-	pack->handle = packhandle;
-	pack->numfiles = numpackfiles;
-	pack->files = newfiles;
-
-	//Sys_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
-	return pack;
-}
-
 const char *COM_GetGameNames(qboolean full)
 {
 	if (full)
@@ -2297,7 +1942,6 @@ const char *COM_GetGameNames(qboolean full)
 			return GAMENAME;
 	}
 	return com_gamenames;
-//	return COM_SkipPath(com_gamedir);
 }
 
 /*
@@ -2309,19 +1953,19 @@ static void COM_AddEnginePak (void)
 {
 	int			i;
 	char		pakfile[MAX_OSPATH];
-	pack_t		*pak = NULL;
+	int			pak = 0;
 	qboolean	modified = com_modified;
 
 	if (host_parms->exedir)
 	{
 		q_snprintf (pakfile, sizeof(pakfile), "%s/" ENGINE_PAK, host_parms->exedir);
-		pak = COM_LoadPackFile (pakfile);
+		pak = QFS_LoadPackFile (pakfile);
 	}
 
 	if (!pak)
 	{
 		q_snprintf (pakfile, sizeof(pakfile), "%s/" ENGINE_PAK, host_parms->basedir);
-		pak = COM_LoadPackFile (pakfile);
+		pak = QFS_LoadPackFile (pakfile);
 	}
 
 	if (!pak)
@@ -2329,7 +1973,7 @@ static void COM_AddEnginePak (void)
 		for (i = 0; i < com_numbasedirs; i++)
 		{
 			q_snprintf (pakfile, sizeof(pakfile), "%s/" ENGINE_PAK, com_basedirs[i]);
-			pak = COM_LoadPackFile (pakfile);
+			pak = QFS_LoadPackFile (pakfile);
 			if (pak)
 				break;
 		}
@@ -2355,11 +1999,12 @@ COM_AddGameDirectory -- johnfitz -- modified based on topaz's tutorial
 void COM_AddGameDirectory (const char *dir)
 {
 	const char *base;
-	int i, j;
+	int i, j, k;
 	unsigned int path_id;
 	searchpath_t *search;
-	pack_t *pak;
+	int pak = 0;
 	char pakfile[MAX_OSPATH];
+	static const char* pkext[2] = { "pak", "pk3" };
 
 	if (*com_gamenames)
 		q_strlcat(com_gamenames, ";", sizeof(com_gamenames));
@@ -2397,11 +2042,18 @@ void COM_AddGameDirectory (const char *dir)
 		search->next = com_searchpaths;
 		com_searchpaths = search;
 
-		// add any pak files in the format pak0.pak pak1.pak, ...
+		// add any pak files (or pk3 files) in the format pak0.pak pak1.pak, ...
+		// pak files and pk3 files can not have the same number, if it exists
+		// with both extensions, the pak file will be used and the pk3 file ignored
 		for (i = 0; ; i++)
 		{
-			q_snprintf (pakfile, sizeof(pakfile), "%s/pak%i.pak", com_gamedir, i);
-			pak = COM_LoadPackFile (pakfile);
+			for (k = 0; k < countof(pkext); ++k)
+			{
+				q_snprintf (pakfile, sizeof(pakfile), "%s/pak%i.%s", com_gamedir, i, pkext[k]);
+				pak = QFS_LoadPackFile (pakfile);
+				if (pak)
+					break;
+			}
 			if (!pak)
 				break;
 
@@ -2426,11 +2078,8 @@ void COM_ResetGameDirectories(const char *newgamedirs)
 	while (com_searchpaths != com_base_searchpaths)
 	{
 		if (com_searchpaths->pack)
-		{
-			Sys_FileClose (com_searchpaths->pack->handle);
-			Z_Free (com_searchpaths->pack->files);
-			Z_Free (com_searchpaths->pack);
-		}
+			QFS_FreePack(com_searchpaths->pack);
+
 		search = com_searchpaths->next;
 		Z_Free (com_searchpaths);
 		com_searchpaths = search;
@@ -3298,176 +2947,6 @@ void COM_InitFilesystem (void) //johnfitz -- modified based on topaz's tutorial
 	COM_CheckRegistered ();
 }
 
-
-/* The following FS_*() stdio replacements are necessary if one is
- * to perform non-sequential reads on files reopened on pak files
- * because we need the bookkeeping about file start/end positions.
- * Allocating and filling in the fshandle_t structure is the users'
- * responsibility when the file is initially opened. */
-
-size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh)
-{
-	long byte_size;
-	long bytes_read;
-	size_t nmemb_read;
-
-	if (!fh) {
-		errno = EBADF;
-		return 0;
-	}
-	if (!ptr) {
-		errno = EFAULT;
-		return 0;
-	}
-	if (!size || !nmemb) {	/* no error, just zero bytes wanted */
-		errno = 0;
-		return 0;
-	}
-
-	byte_size = nmemb * size;
-	if (byte_size > fh->length - fh->pos)	/* just read to end */
-		byte_size = fh->length - fh->pos;
-	bytes_read = fread(ptr, 1, byte_size, fh->file);
-	fh->pos += bytes_read;
-
-	/* fread() must return the number of elements read,
-	 * not the total number of bytes. */
-	nmemb_read = bytes_read / size;
-	/* even if the last member is only read partially
-	 * it is counted as a whole in the return value. */
-	if (bytes_read % size)
-		nmemb_read++;
-
-	return nmemb_read;
-}
-
-int FS_fseek(fshandle_t *fh, long offset, int whence)
-{
-/* I don't care about 64 bit off_t or fseeko() here.
- * the quake/hexen2 file system is 32 bits, anyway. */
-	int ret;
-
-	if (!fh) {
-		errno = EBADF;
-		return -1;
-	}
-
-	/* the relative file position shouldn't be smaller
-	 * than zero or bigger than the filesize. */
-	switch (whence)
-	{
-	case SEEK_SET:
-		break;
-	case SEEK_CUR:
-		offset += fh->pos;
-		break;
-	case SEEK_END:
-		offset = fh->length + offset;
-		break;
-	default:
-		errno = EINVAL;
-		return -1;
-	}
-
-	if (offset < 0) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	if (offset > fh->length)	/* just seek to end */
-		offset = fh->length;
-
-	ret = fseek(fh->file, fh->start + offset, SEEK_SET);
-	if (ret < 0)
-		return ret;
-
-	fh->pos = offset;
-	return 0;
-}
-
-int FS_fclose(fshandle_t *fh)
-{
-	if (!fh) {
-		errno = EBADF;
-		return -1;
-	}
-	return fclose(fh->file);
-}
-
-long FS_ftell(fshandle_t *fh)
-{
-	if (!fh) {
-		errno = EBADF;
-		return -1;
-	}
-	return fh->pos;
-}
-
-void FS_rewind(fshandle_t *fh)
-{
-	if (!fh) return;
-	clearerr(fh->file);
-	fseek(fh->file, fh->start, SEEK_SET);
-	fh->pos = 0;
-}
-
-int FS_feof(fshandle_t *fh)
-{
-	if (!fh) {
-		errno = EBADF;
-		return -1;
-	}
-	if (fh->pos >= fh->length)
-		return -1;
-	return 0;
-}
-
-int FS_ferror(fshandle_t *fh)
-{
-	if (!fh) {
-		errno = EBADF;
-		return -1;
-	}
-	return ferror(fh->file);
-}
-
-int FS_fgetc(fshandle_t *fh)
-{
-	if (!fh) {
-		errno = EBADF;
-		return EOF;
-	}
-	if (fh->pos >= fh->length)
-		return EOF;
-	fh->pos += 1;
-	return fgetc(fh->file);
-}
-
-char *FS_fgets(char *s, int size, fshandle_t *fh)
-{
-	char *ret;
-
-	if (FS_feof(fh))
-		return NULL;
-
-	if (size > (fh->length - fh->pos) + 1)
-		size = (fh->length - fh->pos) + 1;
-
-	ret = fgets(s, size, fh->file);
-	fh->pos = ftell(fh->file) - fh->start;
-
-	return ret;
-}
-
-long FS_filelength (fshandle_t *fh)
-{
-	if (!fh) {
-		errno = EBADF;
-		return -1;
-	}
-	return fh->length;
-}
-
 /*
 ============================================================================
 								LOCALIZATION
diff --git a/Quake/common.h b/Quake/common.h
index 972a82a9d..7f1e742ac 100644
--- a/Quake/common.h
+++ b/Quake/common.h
@@ -117,8 +117,8 @@ typedef struct sizebuf_s
 	qboolean	allowoverflow;	// if false, do a Sys_Error
 	qboolean	overflowed;		// set to true if the buffer size failed
 	byte		*data;
-	int		maxsize;
-	int		cursize;
+	int32_t		maxsize;
+	int32_t		cursize;
 } sizebuf_t;
 
 void SZ_Alloc (sizebuf_t *buf, int startsize);
@@ -208,10 +208,10 @@ static inline void ToggleBit (uint32_t *arr, uint32_t i)
 #define host_bigendian 0
 #endif
 
-#define BigShort(s)    ((short)SDL_SwapBE16((s)))
-#define LittleShort(s) ((short)SDL_SwapLE16((s)))
-#define BigLong(l)     ((int)SDL_SwapBE32((l)))
-#define LittleLong(l)  ((int)SDL_SwapLE32((l)))
+#define BigShort(s)    ((int16_t)SDL_SwapBE16((s)))
+#define LittleShort(s) ((int16_t)SDL_SwapLE16((s)))
+#define BigLong(l)     ((int32_t)SDL_SwapBE32((l)))
+#define LittleLong(l)  ((int32_t)SDL_SwapLE32((l)))
 #define BigFloat(f)    SDL_SwapFloatBE((f))
 #define LittleFloat(f) SDL_SwapFloatLE((f))
 
@@ -371,34 +371,19 @@ size_t UTF8_ToQuake (char *dst, size_t maxbytes, const char *src);
 //============================================================================
 
 // QUAKEFS
-typedef struct
-{
-	char	name[MAX_QPATH];
-	int		filepos, filelen;
-} packfile_t;
-
-typedef struct pack_s
-{
-	char	filename[MAX_OSPATH];
-	int		handle;
-	int		numfiles;
-	packfile_t	*files;
-} pack_t;
-
 typedef struct searchpath_s
 {
 	unsigned int path_id;	// identifier assigned to the game directory
 					// Note that <install_dir>/game1 and
 					// <userdir>/game1 have the same id.
 	char	filename[MAX_OSPATH];
-	pack_t	*pack;			// only one of filename / pack will be used
+	int 	pack;			// only one of filename / pack will be used
 	struct searchpath_s	*next;
 } searchpath_t;
 
 extern searchpath_t *com_searchpaths;
 extern searchpath_t *com_base_searchpaths;
 
-extern THREAD_LOCAL qfileofs_t com_filesize;
 struct cache_user_s;
 
 #define MAX_BASEDIRS 64
@@ -407,22 +392,9 @@ extern	int		com_numbasedirs;
 extern	char	com_basedirs[MAX_BASEDIRS][MAX_OSPATH];
 extern	char	com_gamedir[MAX_OSPATH];
 extern	char	com_nightdivedir[MAX_OSPATH];
-extern	THREAD_LOCAL int	file_from_pak;	// global indicating that file came from a pak
 
 void COM_WriteFile (const char *filename, const void *data, int len);
 qboolean COM_WriteFile_OSPath (const char *filename, const void *data, size_t len);
-int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id);
-int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id);
-qboolean COM_FileExists (const char *filename, unsigned int *path_id);
-void COM_CloseFile (int h);
-
-// these procedures open a file using COM_FindFile and loads it into a proper
-// buffer. the buffer is allocated with a total size of com_filesize + 1. the
-// procedures differ by their buffer allocation method.
-byte *COM_LoadHunkFile (const char *path, unsigned int *path_id);
-	// allocates the buffer on the hunk.
-byte *COM_LoadMallocFile (const char *path, unsigned int *path_id);
-	// allocates the buffer on the system mem (malloc).
 
 // Opens the given path directly, ignoring search paths.
 // Returns NULL on failure, or else a '\0'-terminated malloc'ed buffer.
@@ -450,33 +422,6 @@ const char *COM_ParseStringNewline(const char *buffer);
 #define	FS_ENT_FILE		(1 << 0)
 #define	FS_ENT_DIRECTORY	(1 << 1)
 
-/* The following FS_*() stdio replacements are necessary if one is
- * to perform non-sequential reads on files reopened on pak files
- * because we need the bookkeeping about file start/end positions.
- * Allocating and filling in the fshandle_t structure is the users'
- * responsibility when the file is initially opened. */
-
-typedef struct _fshandle_t
-{
-	FILE *file;
-	qboolean pak;	/* is the file read from a pak */
-	long start;	/* file or data start position */
-	long length;	/* file or data size */
-	long pos;	/* current position relative to start */
-} fshandle_t;
-
-size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh);
-int FS_fseek(fshandle_t *fh, long offset, int whence);
-long FS_ftell(fshandle_t *fh);
-void FS_rewind(fshandle_t *fh);
-int FS_feof(fshandle_t *fh);
-int FS_ferror(fshandle_t *fh);
-int FS_fclose(fshandle_t *fh);
-int FS_fgetc(fshandle_t *fh);
-char *FS_fgets(char *s, int size, fshandle_t *fh);
-long FS_filelength (fshandle_t *fh);
-
-
 extern struct cvar_s	registered;
 extern qboolean		standard_quake, rogue, hipnotic;
 extern qboolean		fitzmode;
diff --git a/Quake/filesys.c b/Quake/filesys.c
new file mode 100644
index 000000000..38a0d0fb1
--- /dev/null
+++ b/Quake/filesys.c
@@ -0,0 +1,1040 @@
+/*
+Copyright (C) 1996-2001 Id Software, Inc.
+Copyright (C) 2002-2009 John Fitzgibbons and others
+Copyright (C) 2010-2014 QuakeSpasm developers
+Copyright (C) 2024-2024 ironwail developers
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+
+/*
+Implementation of the Quake File System, a virtual file system that can read
+contents from pack files or file system files in the search directories.
+
+Supported pack files are:
+
+	*Regular Quake .pak files that the original Quake supports
+
+	*Quake 3 .pk3 files (zip files with a different extension).
+
+		This support is limited to either zip entries compressed with
+		the regular DEFLATE method of zip files, or uncompressed entries.
+*/
+#include "quakedef.h"
+#include "filesys.h"
+#include "miniz.h"
+
+typedef struct
+{
+	char	name[MAX_QPATH];
+	int		filepos, filelen;
+} packfile_t;
+
+typedef struct file_handle_s
+{
+	void *data;
+	void *impl_data;
+	int fileno;
+	qboolean owns_data;
+	qboolean from_pak;
+	qfileofs_t offs;
+	qfileofs_t pak_offset;
+	qfileofs_t start, endtrim;
+	qfileofs_t (*filesize)(struct file_handle_s *handle);
+	size_t (*read)(struct file_handle_s *handle, void* buf, size_t sz);
+	void (*close)(struct file_handle_s *handle);
+	int (*seek)(struct file_handle_s *handle, qfileofs_t pos);
+} qfshandle_t;
+
+typedef struct pack_s
+{
+	FILE* 	handle;
+	char	filename[MAX_OSPATH];
+	int		numfiles;
+	packfile_t	*files;
+	void* impl_data;
+	int pakver;
+	qfshandle_t* (*open_file)(struct pack_s* pack, int idx, qboolean reopen_pack);
+} pack_t;
+// on-disk pakfile
+//
+typedef struct
+{
+	char	name[56];
+	int		filepos, filelen;
+} dpackfile_t;
+
+typedef struct
+{
+	char	id[4];
+	int		dirofs;
+	int		dirlen;
+} dpackheader_t;
+
+#define MAX_FILES_IN_PACK	2048
+#define MAX_PACK_FILES 32
+
+//Loaded pack files (.pak or .pk3)
+//Index 0 is just a placeholder so 0 can be used to indicate error. First pack is loaded at index 1.
+static pack_t* packs[1 + MAX_PACK_FILES] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+
+/*
+=================
+QFS_FreePackHandle
+
+Close a pack file and free the data.
+=================
+*/
+static void QFS_FreePackHandle (pack_t *pack)
+{
+	if (pack != NULL)
+	{
+		fclose (pack->handle);
+		if (pack->impl_data)
+		{
+			if (pack->pakver == 3)
+				mz_zip_reader_end ((mz_zip_archive*)pack->impl_data);
+			free (pack->impl_data);
+		}
+		if (pack->files)
+			free (pack->files);
+		free (pack);
+	}
+}
+
+/*
+=================
+QFS_RegisterPack
+
+Add a pack to the program-wide array of packs.
+Will return 0 if the array is already full and the pack
+is closed and freed.
+=================
+*/
+static int QFS_RegisterPack (pack_t *pack)
+{
+	for (size_t i = 1; i < countof(packs); ++i)
+	{
+		if (packs[i] == NULL)
+		{
+			packs[i] = pack;
+			return (int)i;
+		}
+	}
+	QFS_FreePackHandle (pack);
+
+	Sys_Printf ("WARNING: Too many pack files loaded.");
+	return 0;
+}
+
+
+static void QFS_CheckHandle (qfshandle_t* handle)
+{
+	if (handle == NULL || handle->data == NULL)
+		Sys_Error ("Attempting to read from invalid file handle");
+}
+
+static void* QFS_Alloc (size_t sz)
+{
+	void *palloc = calloc (1, sz);
+	if (palloc == NULL)
+		Sys_Error ("QFS_Alloc of size %" SDL_PRIu64 " failed.", (uint64_t)sz);
+	
+	return palloc;
+}
+
+static qfshandle_t* QFS_AllocHandle (void)
+{
+	return (qfshandle_t *)QFS_Alloc (sizeof(qfshandle_t));
+}
+
+/*============
+FS_*
+file_handle_t implementation functions for regular disk files
+============
+*/
+static void FS_Close (qfshandle_t* handle)
+{
+	if (handle)
+	{
+		if (handle->owns_data)
+			fclose ((FILE*)handle->data);
+		free (handle);
+	}
+}
+
+static size_t FS_Read(qfshandle_t* handle, void* buf, size_t sz)
+{
+	QFS_CheckHandle (handle);
+	
+	sz = fread (buf, 1, sz, (FILE*)handle->data);
+	handle->offs += sz;
+	return sz;
+}
+
+static qfileofs_t FS_FileSize (qfshandle_t* handle)
+{
+	return Sys_filelength ((FILE*)handle->data);
+}
+
+int FS_Seek(qfshandle_t* handle, qfileofs_t pos)
+{
+	return Sys_fseek ((FILE*)handle->data, pos, SEEK_SET);
+}
+
+static qfshandle_t* FS_Open (const char* filename)
+{
+	FILE *stdio_handle = fopen (filename, "rb");
+	if (stdio_handle == NULL)
+		return NULL;
+
+	qfshandle_t* handle = QFS_AllocHandle ();
+	if (handle)
+	{
+		handle->data = stdio_handle;
+		handle->close = &FS_Close;
+		handle->read = &FS_Read;
+		handle->filesize = &FS_FileSize;
+		handle->seek = &FS_Seek;
+		handle->owns_data = true;
+	}
+	return handle;
+}
+
+/*
+============
+PAK_*
+qfshandle_t implementation functions for PAK file content files
+============
+*/
+static void PAK_Close (qfshandle_t* handle)
+{
+	if (handle)
+	{
+		if (handle->owns_data)
+		{
+			pack_t* pack = (pack_t*)handle->data;
+			pack->files = NULL;
+			QFS_FreePackHandle (pack);
+		}
+		if (handle->impl_data)
+			free (handle->impl_data);
+		free (handle);
+	}
+}
+
+static size_t PAK_Read (qfshandle_t* handle, void* buf, size_t sz)
+{
+	pack_t* pack;
+	qfileofs_t actualofs;
+	size_t filesize;
+
+	QFS_CheckHandle (handle);
+	pack = (pack_t*)handle->data;
+	actualofs = handle->offs + handle->start;
+	
+	filesize = (size_t)handle->filesize(handle);
+	if (actualofs + sz > filesize)
+		sz = filesize - (size_t)actualofs;
+	
+	if (sz > 0 && Sys_fseek (pack->handle, handle->pak_offset + handle->offs + handle->start, SEEK_SET) == 0)
+	{
+		sz = fread (buf, 1, sz, pack->handle);
+		handle->offs += sz;
+	}
+	
+	return sz;
+}
+
+static qfileofs_t PAK_FileSize (qfshandle_t* handle)
+{
+	pack_t* pack = (pack_t*)handle->data;
+	return pack->files[handle->fileno].filelen;
+}
+
+int PAK_Seek(qfshandle_t* handle, qfileofs_t pos)
+{
+	handle->offs = pos;
+	return 0;
+}
+
+static qfshandle_t* PAK_Open(pack_t* pack, int idx, qboolean reopen_pack)
+{
+	pack_t* refpack;
+	qfshandle_t *handle = QFS_AllocHandle ();
+	handle->fileno = idx;
+	handle->close = &PAK_Close;
+	handle->read = &PAK_Read;
+	handle->filesize = &PAK_FileSize;
+	handle->seek = &PAK_Seek;
+
+	if (reopen_pack)
+	{
+		//This will create a shallow copy that doesn't copy all the file
+		//entries, but references the original. This should be fine
+		handle->owns_data = true;
+		pack_t* newpack = (pack_t*)QFS_Alloc (sizeof(pack_t));
+		*newpack = *pack;
+		newpack->handle = fopen (pack->filename, "rb");
+		if (newpack->handle == NULL)
+			Sys_Error ("%s failed to reopen.", pack->filename);
+		handle->data = newpack;
+	}
+	else
+	{
+		handle->data = (void*)pack;
+	}
+
+	//Position PAK at the selected file
+	refpack = (pack_t*)handle->data;
+	handle->pak_offset = refpack->files[idx].filepos;
+	return handle;
+}
+
+/*
+============
+ZIP_*
+qfshandle_t implementation functions for pk3 (zip) file content files
+============
+*/
+
+typedef struct
+{
+	byte* inbuf;
+	byte* outbuf;
+	qfileofs_t foffs_in, foffs_out;
+	size_t bsz_in, bsz_out;	//Max buffer sizes
+	size_t readsz_in;	//Valid data in input buffer
+	size_t p_out;	//Bytes already read from the out buffer
+	size_t p_in;	//Bytes already consumed from input buffer
+	size_t out_read_ptr;	//indicates how much of the out buffer has been read by user
+	qboolean eof_flag;	//End reached on deflate stream
+	mz_zip_archive_file_stat stat;
+	tinfl_decompressor infl;
+}
+inflbuffers_t;
+
+static size_t ZIP_LowLevelRead (void *opaque, mz_uint64 ofs, void *buf, size_t n)
+{
+	FILE *handle = (FILE*)opaque;
+
+	if (ofs > LONG_MAX || Sys_fseek (handle, ofs, SEEK_SET) != 0)
+		Sys_Error ("Invalid read of at offset %" SDL_PRIu64, (uint64_t)n);
+
+	return fread (buf, 1, n, handle);
+}
+
+static void ZIP_Close (qfshandle_t* handle)
+{
+	inflbuffers_t* zip = (inflbuffers_t*)handle->impl_data;
+	handle->impl_data = NULL;
+	if (zip)
+	{
+		if (zip->inbuf)
+			free (zip->inbuf);
+		if (zip->outbuf)
+			free (zip->outbuf);
+		free (zip);
+	}
+	
+	PAK_Close (handle);
+}
+
+static size_t ZIP_Read (qfshandle_t* handle, void* buf, size_t sz)
+{
+	inflbuffers_t *p = (inflbuffers_t*)handle->impl_data;
+	pack_t *pack = (pack_t*)handle->data;
+	mz_zip_archive *z = (mz_zip_archive*)pack->impl_data;
+	
+	if (p->stat.m_is_directory || p->stat.m_uncomp_size == 0 || sz == 0 )
+	{
+		return 0;
+	}
+
+	byte* outbuf = (byte*)buf;
+	size_t rd = 0;
+
+	tinfl_status status = TINFL_STATUS_DONE;
+	for (;;)
+	{
+		if (p->p_out >= p->bsz_out || p->eof_flag)
+		{
+			size_t ncpy = q_min (p->p_out - p->out_read_ptr, sz - rd);
+			if (outbuf != NULL)
+			{
+				memcpy (outbuf, p->outbuf + p->out_read_ptr, ncpy);
+				outbuf += ncpy;
+			}
+			rd += ncpy;
+			p->out_read_ptr += ncpy;
+
+			handle->offs += ncpy;
+			
+			if (p->out_read_ptr >= p->p_out)
+				p->out_read_ptr = p->p_out = 0;
+			
+			if (rd >= sz || (p->p_out == 0 && p->eof_flag))
+				return rd;
+		}
+
+		if (p->p_in >= p->readsz_in)
+		{
+			size_t sz = (size_t)q_min ((qfileofs_t)p->bsz_in, p->stat.m_comp_size - p->foffs_in);
+			p->readsz_in = z->m_pRead (z->m_pIO_opaque,
+				(mz_uint64)(handle->pak_offset + p->foffs_in),
+				p->inbuf, sz);
+			
+			if (p->readsz_in != sz)
+				Sys_Error ("File I/O error on %s", pack->filename);
+			
+			p->p_in = 0;
+			p->foffs_in += p->readsz_in;
+		}
+
+		size_t szin = p->readsz_in - p->p_in, szout = p->bsz_out - p->p_out;
+
+		status = tinfl_decompress (&p->infl,
+				(mz_uint8*)p->inbuf + p->p_in, &szin,
+				(mz_uint8*)p->outbuf,
+				(mz_uint8*)p->outbuf + p->p_out, &szout,
+				(qfileofs_t)p->stat.m_comp_size >= p->foffs_in ? TINFL_FLAG_HAS_MORE_INPUT : 0 | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
+		
+		p->p_in += szin;
+		p->p_out += szout;
+		p->foffs_out += szout;
+		p->eof_flag = status == TINFL_STATUS_DONE;
+
+		if (status < TINFL_STATUS_DONE)
+			Sys_Error ("Failed to inflate %s in %s", p->stat.m_filename, pack->filename);
+	}
+}
+
+int ZIP_Seek(qfshandle_t* handle, qfileofs_t pos)
+{
+	inflbuffers_t *p = (inflbuffers_t*)handle->impl_data;
+	qfileofs_t buf_start = handle->offs - (qfileofs_t)p->out_read_ptr;
+
+	if (pos >= buf_start && pos - buf_start <= (qfileofs_t)p->p_out)
+	{
+		//good, we're still inside our output buffer
+		p->out_read_ptr = (size_t)(pos - buf_start);
+		return 0;
+	}
+	else if (pos > buf_start + (qfileofs_t)p->p_out)
+	{
+		//We need to skip forward
+		size_t skipcnt = (size_t)(pos - handle->offs);
+		return ZIP_Read (handle, NULL, skipcnt) == skipcnt ? 0 : -1;
+	}
+	else
+	{
+		//Start from the beginning
+		p->out_read_ptr = p->p_out = p->readsz_in = 0;
+		p->foffs_out = p->foffs_in = handle->offs = 0;
+		p->eof_flag = false;
+		tinfl_init (&p->infl);
+		return ZIP_Read (handle, NULL, (size_t)pos) == (size_t)pos ? 0 : -1;
+	}
+}
+
+static qfshandle_t* ZIP_Open (pack_t* pack, int idx, qboolean reopen_pack)
+{
+	mz_zip_archive* za;
+	pack_t* refpack = pack;
+	uint32_t local_header1 = 0;
+	uint16_t local_header2[2];
+	qfshandle_t *handle = PAK_Open (pack, idx, reopen_pack);
+	
+	handle->close = &ZIP_Close;
+
+	inflbuffers_t *zip = (inflbuffers_t*)QFS_Alloc (sizeof(inflbuffers_t));
+
+	if (reopen_pack)
+	{
+		pack_t* newpack = (pack_t*)handle->data;	//PAK_Open created a clone for us
+		za = (mz_zip_archive*)QFS_Alloc (sizeof (mz_zip_archive));
+		za->m_pRead = &ZIP_LowLevelRead;
+		za->m_pIO_opaque = newpack->handle;
+
+		if (!mz_zip_reader_init (za, Sys_filelength (refpack->handle), 0))
+			Sys_Error ("%s failed to reopen.", refpack->filename);
+		newpack->impl_data = za;
+		refpack = newpack;
+	}
+
+	za = (mz_zip_archive*)refpack->impl_data;
+	
+	mz_zip_reader_file_stat (za, refpack->files[idx].filepos, &zip->stat);
+	if (!zip->stat.m_is_supported)
+		Sys_Error("Unsupported zip file entry %s", refpack->files[idx].name);
+	
+	if (za->m_pRead(za->m_pIO_opaque, zip->stat.m_local_header_ofs, &local_header1, sizeof(local_header1)) != sizeof(local_header1)
+		|| SDL_SwapLE32(local_header1) != 0x04034b50
+		|| za->m_pRead(za->m_pIO_opaque, zip->stat.m_local_header_ofs + 26, &local_header2, sizeof(local_header2)) != sizeof(local_header2))
+	{
+		Sys_Error ("Truncated or corrupt directory entry in %s", refpack->filename);
+	}
+	
+	handle->pak_offset = zip->stat.m_local_header_ofs + 30
+		+ SDL_SwapLE16 (local_header2[0])
+		+ SDL_SwapLE16 (local_header2[1]);
+	
+	if (handle->pak_offset + zip->stat.m_comp_size > za->m_archive_size)
+		Sys_Error ("Truncated zip file %s", refpack->filename);
+
+	if (zip->stat.m_method)
+	{
+		handle->impl_data = zip;
+
+		zip->bsz_in = q_min ((size_t)zip->stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE / 2);
+		zip->inbuf = (byte*)malloc(zip->bsz_in);
+
+		zip->bsz_out = MZ_ZIP_MAX_IO_BUF_SIZE;
+		zip->outbuf = (byte*)malloc (zip->bsz_out);
+
+		tinfl_init (&zip->infl);
+		handle->read = &ZIP_Read;
+		handle->seek = &ZIP_Seek;
+	}
+	else
+	{
+		//An uncompressed zip entry - we can just read with the
+		//regular PAK read functions
+		handle->read = &PAK_Read;
+		handle->seek = &PAK_Seek;
+		free (zip);
+	}
+
+	return handle;
+}
+
+/*
+=================
+QFS_GetPack
+
+Get the pack with the specified number, or NULL of there is none.
+If parameter unregister is set, it will be removed from the
+program wide list and the caller becomes the new owner.
+=================
+*/
+static pack_t* QFS_GetPack (int num, qboolean unregister)
+{
+	if (num > 0 && num < MAX_PACK_FILES)
+	{
+		pack_t* pack = packs[num];
+		if (unregister)
+			packs[num] = NULL;
+		return pack;
+	}
+	return NULL;
+}
+
+const char* QFS_PackInfoName (int packid)
+{
+	pack_t* pack = QFS_GetPack (packid, false);
+	return pack ? pack->filename : NULL;
+}
+
+int QFS_PackInfoNumFiles (int packid)
+{
+	pack_t* pack = QFS_GetPack (packid, false);
+	return pack ? pack->numfiles : 0;
+}
+
+void QFS_FreePack (int packid)
+{
+	pack_t* pack = QFS_GetPack (packid, true);
+	QFS_FreePackHandle (pack);
+}
+
+void QFS_Shutdown (void)
+{
+	for (size_t i = 1; i < countof(packs); ++i)
+	{
+		if (packs[i] != NULL)
+		{
+			QFS_FreePackHandle(packs[i]);
+			packs[i] = NULL;
+		}
+	}
+}
+
+qfileofs_t QFS_PackInfoEntrySize (int packid, int idx)
+{
+	pack_t* pack = QFS_GetPack (packid, false);
+	if (pack && idx >= 0 && idx < pack->numfiles)
+		return pack->files[idx].filelen;
+
+	return 0; 
+}
+
+const char* QFS_PackInfoEntryName (int packid, int idx)
+{
+	pack_t* pack = QFS_GetPack (packid, false);
+	if (pack && idx >= 0 && idx < pack->numfiles)
+		return pack->files[idx].name;
+	
+	return NULL;
+}
+/*
+=================
+QFS_LoadPAKFile -- johnfitz -- modified based on topaz's tutorial
+
+Takes an explicit (not game tree related) path to a pak file.
+
+Loads the header and directory, adding the files at the beginning
+of the list so they override previous pack files.
+=================
+*/
+static int QFS_LoadPAKFile (const char *packfile)
+{
+	dpackheader_t	header;
+	int		i;
+	packfile_t	*newfiles;
+	int		numpackfiles;
+	pack_t		*pack;
+	FILE		*packhandle;
+	dpackfile_t	info[MAX_FILES_IN_PACK];
+
+	packhandle = fopen(packfile, "rb");
+	if (packhandle == NULL)
+		return 0;
+
+	if (fread(&header, 1, sizeof(header), packhandle) != sizeof(header) ||
+	    header.id[0] != 'P' || header.id[1] != 'A' || header.id[2] != 'C' || header.id[3] != 'K')
+		Sys_Error ("%s is not a packfile", packfile);
+
+	header.dirofs = (header.dirofs);
+	header.dirlen = (header.dirlen);
+
+	numpackfiles = header.dirlen / sizeof(dpackfile_t);
+
+	if (header.dirlen < 0 || header.dirofs < 0)
+	{
+		Sys_Error ("Invalid packfile %s (dirlen: %i, dirofs: %i)",
+					packfile, header.dirlen, header.dirofs);
+	}
+	if (!numpackfiles)
+	{
+		printf ("WARNING: %s has no files, ignored\n", packfile);
+		fclose (packhandle);
+		return 0;
+	}
+	if (numpackfiles > MAX_FILES_IN_PACK)
+		Sys_Error ("%s has %i files", packfile, numpackfiles);
+
+	newfiles = (packfile_t *) QFS_Alloc(numpackfiles * sizeof(packfile_t));
+
+	fseek (packhandle, header.dirofs, SEEK_SET);
+	if ((int)fread(info, 1, header.dirlen, packhandle) != header.dirlen)
+		Sys_Error ("Error reading %s", packfile);
+
+	// parse the directory
+	for (i = 0; i < numpackfiles; i++)
+	{
+		q_strlcpy (newfiles[i].name, info[i].name, sizeof(newfiles[i].name));
+		newfiles[i].filepos = (info[i].filepos);
+		newfiles[i].filelen = (info[i].filelen);
+	}
+
+	pack = (pack_t *) QFS_Alloc(sizeof (pack_t));
+	q_strlcpy (pack->filename, packfile, sizeof(pack->filename));
+	pack->handle = packhandle;
+	pack->numfiles = numpackfiles;
+	pack->files = newfiles;
+	pack->open_file = &PAK_Open;
+	pack->pakver = 1;
+
+	return QFS_RegisterPack (pack);
+}
+
+static int QFS_LoadPK3File(const char *packfile)
+{
+	mz_zip_archive *pk3;
+	FILE *pk3handle;
+	mz_uint i, numpackfiles;
+	packfile_t	*newfiles;
+	
+	pk3handle = fopen (packfile, "rb");
+	if (!pk3handle)
+		return 0;
+
+	pk3 = (mz_zip_archive*)QFS_Alloc (sizeof(mz_zip_archive));
+	pk3->m_pRead = ZIP_LowLevelRead;
+	pk3->m_pIO_opaque = pk3handle;
+	char buf[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];
+
+	if (!mz_zip_reader_init (pk3, (mz_uint64)Sys_filelength (pk3handle), 0))
+		Sys_Error("%s can not be opened as a .pk3 file.", packfile);
+	
+	mz_uint entrycount = pk3->m_total_files;
+	if (!entrycount)
+	{
+		printf("WARNING: %s has no files, ignored\n", packfile);
+		fclose(pk3handle);
+		free(pk3);
+		return 0;
+	}
+	
+	newfiles = (packfile_t *)QFS_Alloc(entrycount * sizeof(packfile_t));
+
+	for (i = 0, numpackfiles = 0; i < entrycount; ++i)
+	{
+		mz_uint len = mz_zip_reader_get_filename (pk3, i, buf, countof(buf));
+		if (len >= sizeof(newfiles[numpackfiles].name))
+			Sys_Error ("File name %s in %s exceeds maximum allowed length.", buf, packfile);
+
+		mz_zip_archive_file_stat st;
+		if (!mz_zip_reader_file_stat(pk3, i, &st))
+			Sys_Error ("Failed to get status of %s in %s.", buf, packfile);
+
+		if (!st.m_is_directory)
+		{
+			if (st.m_uncomp_size > INT_MAX)
+				Sys_Error("File %s in %s is too large.", buf, packfile);
+
+			newfiles[numpackfiles].filelen = (int)st.m_uncomp_size;
+			newfiles[numpackfiles].filepos = (int)st.m_file_index;
+
+			q_strlcpy (newfiles[numpackfiles].name, buf, sizeof(newfiles[numpackfiles].name));
+			++numpackfiles;
+		}
+	}
+
+	pack_t* pack = (pack_t*) QFS_Alloc(sizeof(pack_t));
+	q_strlcpy(pack->filename, packfile, sizeof(pack->filename));
+	pack->handle = pk3handle;
+	pack->numfiles = numpackfiles;
+	pack->files = newfiles;
+	pack->open_file = &ZIP_Open;
+	pack->impl_data = pk3;
+	pack->pakver = 3;
+
+	return QFS_RegisterPack (pack);
+}
+
+int QFS_LoadPackFile(const char *packfile)
+{
+	if (q_strcasecmp (COM_FileGetExtension (packfile), "pk3") == 0)
+		return QFS_LoadPK3File (packfile);
+	else
+		return QFS_LoadPAKFile (packfile);		
+}
+
+/*
+===========
+QFS_FindFile
+
+Finds the file in the search path.
+Sets file to a new file handle, if reopen is true and file is in a pak,
+a new handle to the pak will be opened.
+If file is not set, it can be used for detecting a file's presence.
+===========
+*/
+static qfileofs_t QFS_FindFile (const char *filename, qfshandle_t** file, qboolean reopen, unsigned int *path_id)
+{
+	searchpath_t	*search;
+	char		netpath[MAX_OSPATH];
+	pack_t		*pak;
+	int			i;
+
+	if (file)
+		*file = NULL;
+//
+// search through the path, one element at a time
+//
+	for (search = com_searchpaths; search; search = search->next)
+	{
+		if (search->pack)	/* look through all the pak file elements */
+		{
+			pak = QFS_GetPack (search->pack, false);
+			if (!pak)
+				Sys_Error ("QFS_FindFile: invalid pack id.");
+
+			for (i = 0; i < pak->numfiles; i++)
+			{
+				if (strcmp(pak->files[i].name, filename) != 0)
+					continue;
+				// found it!
+				if (path_id)
+					*path_id = search->path_id;
+
+				if (file)
+					*file = pak->open_file(pak, i, reopen);
+				
+				return pak->files[i].filelen;
+			}
+		}
+		else	/* check a file in the directory tree */
+		{
+			if (!registered.value)
+			{ /* if not a registered version, don't ever go beyond base */
+				if ( strchr (filename, '/') || strchr (filename,'\\'))
+					continue;
+			}
+
+			q_snprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename);
+			if (! (Sys_FileType(netpath) & FS_ENT_FILE))
+				continue;
+
+			if (path_id)
+				*path_id = search->path_id;
+			
+			if (file)
+			{
+				*file = FS_Open (netpath);
+				if (*file == NULL)
+					return -1;
+				return Sys_filelength ((FILE*)(*file)->data);
+			}
+			else
+			{
+				return 0; /* dummy valid value for QFS_FileExists() */
+			}
+		}
+	}
+
+	if (developer.value)
+	{
+		const char *ext = COM_FileGetExtension (filename);
+
+		if (strcmp(ext, "pcx") != 0 &&
+			strcmp(ext, "tga") != 0 &&
+			strcmp(ext, "lit") != 0 &&
+			strcmp(ext, "vis") != 0 &&
+			strcmp(ext, "ent") != 0)
+			Con_DPrintf ("FindFile: can't find %s\n", filename);
+		else
+			Con_DPrintf2 ("FindFile: can't find %s\n", filename);
+	}
+
+	if (file)
+		*file = NULL;
+		
+	return -1;
+}
+
+typedef enum
+{
+	LOADFILE_HUNK,
+	LOADFILE_MALLOC
+} loadfile_alloc_t;
+
+byte *QFS_LoadFile (const char *path, loadfile_alloc_t method, unsigned int *path_id, size_t* ldsize)
+{
+	byte	*buf;
+	char	base[32];
+	size_t	len, nread;
+
+	buf = NULL;	// quiet compiler warning
+
+// look for it in the filesystem or pack files
+	qfshandle_t *h = QFS_OpenFile (path, path_id);
+	if (h == NULL)
+		return NULL;
+	
+	len = (size_t)QFS_FileSize (h);
+	if (ldsize)
+		*ldsize = len;
+
+// extract the filename base name for hunk tag
+	COM_FileBase (path, base, sizeof(base));
+
+	switch (method)
+	{
+	case LOADFILE_HUNK:
+		buf = (byte *) Hunk_AllocNameNoFill (len+1, base);
+		break;
+	case LOADFILE_MALLOC:
+		buf = (byte *) malloc (len+1);
+		break;
+	default:
+		Sys_Error ("QFS_LoadFile: bad usehunk");
+	}
+
+	if (!buf)
+		Sys_Error ("QFS_LoadFile: not enough space for %s", path);
+
+	((byte *)buf)[len] = 0;
+
+	nread = QFS_ReadFile (h, buf, len);
+	QFS_CloseFile (h);
+	if (nread != len)
+		Sys_Error ("QFS_LoadFile: Error reading %s", path);
+
+	return buf;
+}
+
+byte *QFS_LoadHunkFile (const char *path, unsigned int *path_id, size_t* ldsize)
+{
+	return QFS_LoadFile (path, LOADFILE_HUNK, path_id, ldsize);
+}
+
+// returns malloc'd memory
+byte *QFS_LoadMallocFile (const char *path, unsigned int *path_id, size_t* ldsize)
+{
+	return QFS_LoadFile (path, LOADFILE_MALLOC, path_id, ldsize);
+}
+
+qboolean QFS_FileExists (const char *filename, unsigned int *path_id)
+{
+	qfileofs_t ret = QFS_FindFile (filename, NULL, false, path_id);
+	return (ret == -1) ? false : true;
+}
+
+qfshandle_t* QFS_OpenFile (const char *filename, unsigned int *path_id)
+{
+	qfshandle_t* handle;
+	if (QFS_FindFile (filename, &handle, false, path_id) >= 0)
+		return handle;
+	return NULL;
+}
+
+qfshandle_t* QFS_FOpenFile (const char *filename, unsigned int *path_id)
+{
+	qfshandle_t* handle;
+	if (QFS_FindFile (filename, &handle, true, path_id) >= 0)
+		return handle;
+	return NULL;
+}
+
+qboolean QFS_Eof (qfshandle_t* handle)
+{
+	if (!handle || handle->offs - handle->endtrim >= handle->filesize(handle) - handle->start)
+		return true;
+	return false;
+}
+
+size_t QFS_ReadFile(qfshandle_t* handle, void* buf, size_t size)
+{
+	if (handle)
+	{
+		qfileofs_t filesize = handle->filesize(handle);
+		if (handle->offs + (qfileofs_t)size > filesize - handle->endtrim)
+			size = (size_t)(filesize - handle->endtrim);
+
+		return handle->read(handle, buf, size);
+	}
+	return 0;
+}
+
+qfileofs_t QFS_FileSize (qfshandle_t* handle)
+{
+	return handle ? handle->filesize(handle) - handle->start - handle->endtrim : 0;
+}
+
+void QFS_CloseFile (qfshandle_t *handle)
+{
+	if (handle)
+		handle->close (handle);
+}
+
+qfileofs_t QFS_Seek (qfshandle_t* handle, qfileofs_t offs, int whence)
+{
+	qfileofs_t actual_pos;
+	if (!handle)
+		return -1;
+
+	switch (whence)
+	{
+	case SEEK_SET:
+		actual_pos = handle->start + offs;
+		break;
+	case SEEK_CUR:
+		actual_pos = handle->start + handle->offs + offs;
+		break;
+	case SEEK_END:
+		actual_pos = handle->filesize(handle) - handle->endtrim + offs;
+		break;
+	default:
+		return -1;
+	}
+	if (actual_pos < handle->start || actual_pos > handle->filesize(handle) - (handle->start + handle->endtrim))
+		return -1;
+	
+	if (handle->seek(handle, actual_pos) == 0)
+	{
+		handle->offs = actual_pos;
+		return 0;
+	}
+	return -1;
+}
+
+qfileofs_t QFS_Tell (qfshandle_t* handle)
+{
+	return handle ? handle->offs - handle->start : 0;
+}
+
+qboolean QFS_IgnoreBytes (qfshandle_t* handle, qfileofs_t cut, int whence)
+{
+	if (handle)
+	{
+		qfileofs_t filesize = handle->filesize(handle);
+		if (whence == SEEK_SET && cut <= filesize - handle->endtrim)
+			handle->start = cut;
+		else if (whence == SEEK_END && cut <= filesize - handle->start)
+			handle->endtrim = cut;
+		else if (whence == SEEK_SET && cut == 0)
+			handle->endtrim = handle->start = 0;
+		else
+			return false;
+		
+		if (handle->offs < handle->start)
+			return handle->seek (handle, handle->start) == 0;
+		if (handle->offs > handle->start - handle->endtrim)
+			return handle->seek (handle, filesize - handle->start - handle->endtrim) == 0;
+	}
+	return false;
+}
+
+char QFS_GetChar (qfshandle_t* handle, qboolean* eof_flag)
+{
+	char ch = '\0';
+	if (QFS_ReadFile (handle, &ch, 1) == 1)
+	{
+		if (eof_flag)
+			*eof_flag = false;
+		return ch;
+	}
+	if (eof_flag)
+		*eof_flag = true;
+	return '\0';
+}
+
+size_t QFS_GetLine (qfshandle_t* handle, char *buf, size_t bufsz)
+{
+	size_t i, o;
+	qboolean eof_flag = false;
+	if (bufsz < 1)
+		return 0;
+
+	for (i = 0, o = 0; o < bufsz - 1; ++i)
+	{
+		char ch = QFS_GetChar (handle, &eof_flag);
+		if (ch == '\n' || ch == '\0' || eof_flag)
+			break;
+		else if (ch != '\r')
+			buf[o++] = ch;
+	}
+
+	buf[o] = '\0';
+	return o;
+}
+
diff --git a/Quake/filesys.h b/Quake/filesys.h
new file mode 100644
index 000000000..41f87a9d6
--- /dev/null
+++ b/Quake/filesys.h
@@ -0,0 +1,242 @@
+/*
+Copyright (C) 1996-2001 Id Software, Inc.
+Copyright (C) 2002-2009 John Fitzgibbons and others
+Copyright (C) 2010-2014 QuakeSpasm developers
+Copyright (C) 2024-2024 ironwail developers
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+#ifndef QUAKE_FILESYSTEM_H
+#define QUAKE_FILESYSTEM_H
+
+//Opaque file handle that is used for Quake file system operations
+typedef struct file_handle_s qfshandle_t;
+
+/*
+===========
+QFS_OpenFile
+
+Attempts to open the requested file, returns NULL if it is not found.
+Filename never has a leading slash, but may contain directory walks
+
+Files opened with this will all use the same FILE* for the pack file
+===========
+*/
+qfshandle_t* QFS_OpenFile (const char* filename, unsigned int* path_id);
+
+/*
+===========
+QFS_FOpenFile
+
+If the requested file is inside a packfile, a new FILE* will be opened
+into the file pack. This can be a good idea if the file is used from
+another thread.
+===========
+*/
+qfshandle_t* QFS_FOpenFile (const char *filename, unsigned int *path_id);
+
+
+/*
+===========
+QFS_FileExists
+
+Returns whether the file is found in the quake filesystem.
+===========
+*/
+qboolean QFS_FileExists (const char *filename, unsigned int *path_id);
+
+/*
+============
+QFS_CloseFile
+
+Must be called when you are done with the file to free used resources
+============
+*/
+void QFS_CloseFile (qfshandle_t *handle);
+
+/*
+============
+QFS_ReadFile
+
+Read binary data from the file, returns the number of bytes read from the file.
+============
+*/
+size_t QFS_ReadFile (qfshandle_t* handle, void* buf, size_t size);
+
+/*
+============
+QFS_Eof
+
+Returns true if end of file has been reached on the handle
+============
+*/
+qboolean QFS_Eof (qfshandle_t* handle);
+
+/*
+============
+QFS_FileSize
+
+Returns the total size, in bytes, of the opened file.
+============
+*/
+qfileofs_t QFS_FileSize (qfshandle_t* handle);
+
+/*
+============
+QFS_Seek
+
+Move to a specific position in the file.
+whence can be one of SEEK_SET, SEEK_CUR, or SEEK_END and works like fseek in C.
+Like fseek, it returns 0 on success and -1 on failure.
+Unlike fseek, errno is not set on failure.
+
+When using .pk3 files this is a more expensive operation than .pak or regular files,
+especially when seeking backwards. For optimal results it is recommended to store
+already compressed music files without deflate compression inside pk3 files - in
+these cases the seek will be as efficient as a regular .pak file.
+============
+*/
+qfileofs_t QFS_Seek (qfshandle_t* handle, qfileofs_t offs, int whence);
+
+/*
+============
+QFS_Tell
+
+Determine the current seek position in the file.
+============
+*/
+qfileofs_t QFS_Tell (qfshandle_t* handle);
+
+/*
+============
+QFS_IgnoreBytes
+
+Specify a number of bytes that the file length should be shortened with.
+This could be useful for ignoring garbage at the end of a file such as id3 tags.
+
+whence can be either SEEK_END or SEEK_SET, SEEK_END will cut off at the end
+and SEEK_SET will cut off in the beginning.
+
+If the current file position is inside the removed area, the file cursor
+will be moved either to the beginning (SEEK_SET) or the end (SEEK_END)
+
+If you specify whence as SEEK_CUR and cut as 0, the ignore effect will be reset.
+============
+*/
+qboolean QFS_IgnoreBytes (qfshandle_t* handle, qfileofs_t cut, int whence);
+
+
+// these procedures open a file using COM_FindFile and loads it into a proper
+// buffer. the buffer is allocated with a total size of file size + 1. the
+// procedures differ by their buffer allocation method.
+// If you don't need the file size, you can give ldsize as NULL. 
+byte *QFS_LoadHunkFile (const char *path, unsigned int *path_id, size_t* ldsize);
+	// allocates the buffer on the hunk.
+byte *QFS_LoadMallocFile (const char *path, unsigned int *path_id, size_t* ldsize);
+	// allocates the buffer on the system mem (malloc).
+
+/*
+============
+QFS_LoadPackFile
+
+Load a pack file and return the id of the new pack
+============
+*/
+int QFS_LoadPackFile (const char *packfile);
+
+/*
+============
+QFS_FreePack
+
+Close handle and release all resources associated with the pack
+============
+*/
+void QFS_FreePack (int packid);
+
+/*
+============
+QFS_Shutdown
+
+Close all packs that are open
+============
+*/
+void QFS_Shutdown (void);
+
+/*
+============
+QFS_PackInfoName
+
+Returns the pack filename of the pack with the id or NULL if the pack doesn't exist
+============
+*/
+const char* QFS_PackInfoName (int packid);
+
+/*
+============
+QFS_PackInfoName
+
+Returns the number of files in the pack with the id or 0 if the pack doesn't exist
+============
+*/
+int QFS_PackInfoNumFiles (int packid);
+
+/*
+============
+QFS_PackInfoEntrySize
+
+Returns the total size, in bytes, of specified file index in the pack or 0 if it does not exist
+============
+*/
+qfileofs_t QFS_PackInfoEntrySize (int packid, int idx);
+
+/*
+============
+QFS_PackInfoEntryName
+
+Returns the file name, of the specified file index in the pack or NULL if it does not exist
+============
+*/
+const char* QFS_PackInfoEntryName (int packid, int idx);
+
+/*
+============
+QFS_GetChar
+
+Reads a single text character from the file. It is the basis
+for implementing other text mode read functions.
+returns 0 and sets eof_flag if there are no characters to read
+============
+*/
+char QFS_GetChar (qfshandle_t* handle, qboolean* eof_flag);
+
+/*
+============
+QFS_GetLine
+
+Reads a single line of text from the file. Returns the number of
+characters copied to buf. This will skip '\r' always.
+Newline '\n' is considered end of the line, '\n' will
+not be copied to buf.
+
+If buf is full before encountering '\n' the string will be truncated.
+The buf will always be NUL-terminated so it can at most
+extract bufsz-1 characters.
+============
+*/
+size_t QFS_GetLine (qfshandle_t* handle, char *buf, size_t bufsz);
+
+#endif 	/* QUAKE_FILESYSTEM_H */
diff --git a/Quake/gl_draw.c b/Quake/gl_draw.c
index 3ef986902..d0ec2dcb3 100644
--- a/Quake/gl_draw.c
+++ b/Quake/gl_draw.c
@@ -371,7 +371,7 @@ qpic_t	*Draw_TryCachePic (const char *path, unsigned int texflags)
 //
 // load the pic from disk
 //
-	dat = (qpic_t *)COM_LoadMallocFile (path, NULL);
+	dat = (qpic_t *)QFS_LoadMallocFile (path, NULL, NULL);
 	if (!dat)
 		return NULL;
 	SwapPic (dat);
diff --git a/Quake/gl_model.c b/Quake/gl_model.c
index d6e06c043..368bde060 100644
--- a/Quake/gl_model.c
+++ b/Quake/gl_model.c
@@ -373,7 +373,7 @@ static qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash)
 //
 // load the file
 //
-	buf = COM_LoadMallocFile (mod->name, &mod->path_id);
+	buf = QFS_LoadMallocFile (mod->name, &mod->path_id, NULL);
 	if (!buf)
 	{
 		if (crash)
@@ -829,6 +829,7 @@ static void Mod_LoadLighting (lump_t *l)
 	byte d, q64_b0, q64_b1;
 	char litfilename[MAX_OSPATH];
 	unsigned int path_id;
+	size_t filesize;
 
 	loadmodel->lightdata = NULL;
 	loadmodel->litfile = false;
@@ -837,7 +838,7 @@ static void Mod_LoadLighting (lump_t *l)
 	COM_StripExtension(litfilename, litfilename, sizeof(litfilename));
 	q_strlcat(litfilename, ".lit", sizeof(litfilename));
 	mark = Hunk_LowMark();
-	data = (byte*) COM_LoadHunkFile (litfilename, &path_id);
+	data = (byte*) QFS_LoadHunkFile (litfilename, &path_id, &filesize);
 	if (data)
 	{
 		// use lit file only from the same gamedir as the map
@@ -853,7 +854,7 @@ static void Mod_LoadLighting (lump_t *l)
 			i = LittleLong(((int *)data)[1]);
 			if (i == 1)
 			{
-				if (8+l->filelen*3 == com_filesize)
+				if (8+l->filelen*3 == (int)filesize)
 				{
 					Con_DPrintf2("%s loaded\n", litfilename);
 					loadmodel->lightdata = data + 8;
@@ -861,7 +862,7 @@ static void Mod_LoadLighting (lump_t *l)
 					return;
 				}
 				Hunk_FreeToLowMark(mark);
-				Con_Printf("Outdated .lit file (%s should be %u bytes, not %" SDL_PRIs64 "\n", litfilename, 8+l->filelen*3, com_filesize);
+				Con_Printf("Outdated .lit file (%s should be %" SDL_PRIs32 " bytes, not %" SDL_PRIu64 "\n", litfilename, 8+l->filelen*3, (uint64_t)filesize);
 			}
 			else
 			{
@@ -960,13 +961,13 @@ static void Mod_LoadEntities (lump_t *l)
 
 	q_snprintf(entfilename, sizeof(entfilename), "%s@%04x.ent", basemapname, crc);
 	Con_DPrintf2("trying to load %s\n", entfilename);
-	ents = (char *) COM_LoadHunkFile (entfilename, &path_id);
+	ents = (char *) QFS_LoadHunkFile (entfilename, &path_id, NULL);
 
 	if (!ents)
 	{
 		q_snprintf(entfilename, sizeof(entfilename), "%s.ent", basemapname);
 		Con_DPrintf2("trying to load %s\n", entfilename);
-		ents = (char *) COM_LoadHunkFile (entfilename, &path_id);
+		ents = (char *) QFS_LoadHunkFile (entfilename, &path_id, NULL);
 	}
 
 	if (ents)
@@ -2276,23 +2277,25 @@ typedef struct vispatch_s
 } vispatch_t;
 #define VISPATCH_HEADER_LEN 36
 
-static FILE *Mod_FindVisibilityExternal(void)
+static qfshandle_t *Mod_FindVisibilityExternal(void)
 {
 	vispatch_t header;
 	char visfilename[MAX_QPATH];
 	const char* shortname;
 	unsigned int path_id;
-	FILE *f;
+	qfshandle_t *f;
 	long pos;
 	size_t r;
 
 	q_snprintf(visfilename, sizeof(visfilename), "maps/%s.vis", loadname);
-	if (COM_FOpenFile(visfilename, &f, &path_id) < 0)
+	f = QFS_FOpenFile(visfilename, &path_id);
+	if (f == NULL)
 	{
 		Con_DPrintf("%s not found, trying ", visfilename);
 		q_snprintf(visfilename, sizeof(visfilename), "%s.vis", COM_SkipPath(com_gamedir));
 		Con_DPrintf("%s\n", visfilename);
-		if (COM_FOpenFile(visfilename, &f, &path_id) < 0)
+		f = QFS_FOpenFile(visfilename, &path_id);
+		if (f == NULL)
 		{
 			Con_DPrintf("external vis not found\n");
 			return NULL;
@@ -2300,7 +2303,7 @@ static FILE *Mod_FindVisibilityExternal(void)
 	}
 	if (path_id < loadmodel->path_id)
 	{
-		fclose(f);
+		QFS_CloseFile(f);
 		Con_DPrintf("ignored %s from a gamedir with lower priority\n", visfilename);
 		return NULL;
 	}
@@ -2309,20 +2312,20 @@ static FILE *Mod_FindVisibilityExternal(void)
 
 	shortname = COM_SkipPath(loadmodel->name);
 	pos = 0;
-	while ((r = fread(&header, 1, VISPATCH_HEADER_LEN, f)) == VISPATCH_HEADER_LEN)
+	while ((r = QFS_ReadFile(f, &header, VISPATCH_HEADER_LEN)) == VISPATCH_HEADER_LEN)
 	{
 		header.filelen = LittleLong(header.filelen);
 		if (header.filelen <= 0) {	/* bad entry -- don't trust the rest. */
-			fclose(f);
+			QFS_CloseFile(f);
 			return NULL;
 		}
 		if (!q_strcasecmp(header.mapname, shortname))
 			break;
 		pos += header.filelen + VISPATCH_HEADER_LEN;
-		fseek(f, pos, SEEK_SET);
+		QFS_Seek(f, pos, SEEK_SET);
 	}
 	if (r != VISPATCH_HEADER_LEN) {
-		fclose(f);
+		QFS_CloseFile(f);
 		Con_DPrintf("%s not found in %s\n", shortname, visfilename);
 		return NULL;
 	}
@@ -2330,20 +2333,20 @@ static FILE *Mod_FindVisibilityExternal(void)
 	return f;
 }
 
-static byte *Mod_LoadVisibilityExternal(FILE* f)
+static byte *Mod_LoadVisibilityExternal(qfshandle_t* f)
 {
-	int		mark, filelen;
+	int32_t	mark, filelen;
 	byte*	visdata;
 
 	filelen = 0;
-	if (fread(&filelen, 4, 1, f) != 1)
+	if (QFS_ReadFile(f, &filelen, 4) != 4)
 		return NULL;
 	filelen = LittleLong(filelen);
 	if (filelen <= 0) return NULL;
-	Con_DPrintf("...%d bytes visibility data\n", filelen);
+	Con_DPrintf("...%" SDL_PRIs32 " bytes visibility data\n", filelen);
 	mark = Hunk_LowMark ();
 	visdata = (byte *) Hunk_AllocNameNoFill (filelen, "EXT_VIS");
-	if (!fread(visdata, filelen, 1, f))
+	if (QFS_ReadFile(f, visdata, filelen) != (size_t)filelen)
 	{
 		Hunk_FreeToLowMark (mark);
 		return NULL;
@@ -2351,23 +2354,23 @@ static byte *Mod_LoadVisibilityExternal(FILE* f)
 	return visdata;
 }
 
-static void Mod_LoadLeafsExternal(FILE* f)
+static void Mod_LoadLeafsExternal(qfshandle_t* f)
 {
-	int		mark, filelen;
+	int32_t	mark, filelen;
 	void*	in;
 
 	filelen = 0;
-	if (fread(&filelen, 4, 1, f) != 1)
+	if (QFS_ReadFile(f, &filelen, 4) != 4)
 	{
 		Con_Warning ("Couldn't read external leaf data length\n");
 		return;
 	}
 	filelen = LittleLong(filelen);
 	if (filelen <= 0) return;
-	Con_DPrintf("...%d bytes leaf data\n", filelen);
+	Con_DPrintf("...%" SDL_PRIs32 " bytes leaf data\n", filelen);
 	mark = Hunk_LowMark ();
 	in = Hunk_AllocNameNoFill (filelen, "EXT_LEAF");
-	if (!fread(in, filelen, 1, f))
+	if (QFS_ReadFile(f, in, filelen) != (size_t)filelen)
 	{
 		Hunk_FreeToLowMark (mark);
 		return;
@@ -2433,7 +2436,7 @@ static void Mod_LoadBrushModel (qmodel_t *mod, void *buffer)
 
 	if (mod->bspversion == BSPVERSION && external_vis.value && sv.modelname[0] && !q_strcasecmp(loadname, sv.name))
 	{
-		FILE* fvis;
+		qfshandle_t* fvis;
 		Con_DPrintf("trying to open external vis file\n");
 		fvis = Mod_FindVisibilityExternal();
 		if (fvis) {
@@ -2445,7 +2448,7 @@ static void Mod_LoadBrushModel (qmodel_t *mod, void *buffer)
 			if (loadmodel->visdata) {
 				Mod_LoadLeafsExternal(fvis);
 			}
-			fclose(fvis);
+			QFS_CloseFile(fvis);
 			if (loadmodel->visdata && loadmodel->leafs && loadmodel->numleafs) {
 				goto visdone;
 			}
@@ -2593,7 +2596,7 @@ qboolean Mod_LoadMapDescription (char *desc, size_t maxchars, const char *map)
 	char		buf[4 * 1024];
 	char		path[MAX_QPATH];
 	const char	*data;
-	FILE		*f;
+	qfshandle_t	*f;
 	lump_t		*entlump;
 	dheader_t	header;
 	int			i, filesize;
@@ -2606,17 +2609,18 @@ qboolean Mod_LoadMapDescription (char *desc, size_t maxchars, const char *map)
 	if ((size_t) q_snprintf (path, sizeof (path), "maps/%s.bsp", map) >= sizeof (path))
 		return false;
 
-	filesize = COM_FOpenFile (path, &f, NULL);
+	f = QFS_FOpenFile(path, NULL);
+	filesize = f ? (int)QFS_FileSize(f) : -1;
 	if (filesize <= (int) sizeof (header))
 	{
 		if (filesize != -1)
-			fclose (f);
+			QFS_CloseFile (f);
 		return false;
 	}
 
-	if (fread (&header, sizeof (header), 1, f) != 1)
+	if (QFS_ReadFile (f, &header, sizeof (header)) != sizeof (header))
 	{
-		fclose (f);
+		QFS_CloseFile (f);
 		return false;
 	}
 
@@ -2630,7 +2634,7 @@ qboolean Mod_LoadMapDescription (char *desc, size_t maxchars, const char *map)
 	case BSPVERSION_QUAKE64:
 		break;
 	default:
-		fclose (f);
+		QFS_CloseFile (f);
 		return false;
 	}
 
@@ -2641,7 +2645,7 @@ qboolean Mod_LoadMapDescription (char *desc, size_t maxchars, const char *map)
 	if (entlump->filelen < 0 || entlump->filelen >= filesize ||
 		entlump->fileofs < 0 || entlump->fileofs + entlump->filelen > filesize)
 	{
-		fclose (f);
+		QFS_CloseFile (f);
 		return false;
 	}
 
@@ -2653,9 +2657,9 @@ qboolean Mod_LoadMapDescription (char *desc, size_t maxchars, const char *map)
 		entlump->filelen = sizeof (buf) - 1;
 	}
 
-	fseek (f, entlump->fileofs - sizeof (header), SEEK_CUR);
-	i = fread (buf, 1, entlump->filelen, f);
-	fclose (f);
+	QFS_Seek (f, entlump->fileofs - sizeof (header), SEEK_CUR);
+	i = (int)QFS_ReadFile (f, buf, entlump->filelen);
+	QFS_CloseFile (f);
 
 	if (i <= 0)
 		return false;
@@ -3216,9 +3220,9 @@ static void Mod_LoadAliasModel (qmodel_t *mod, void *buffer)
 		COM_StripExtension (mod->name, path, sizeof (path));
 		COM_AddExtension (path, ".md5mesh", sizeof (path));
 
-		if (COM_FileExists (path, &md5_path_id) && md5_path_id >= mod->path_id)
+		if (QFS_FileExists (path, &md5_path_id) && md5_path_id >= mod->path_id)
 		{
-			char *md5buffer = (char *) COM_LoadMallocFile (path, NULL);
+			char *md5buffer = (char *) QFS_LoadMallocFile (path, NULL, NULL);
 			if (md5buffer)
 			{
 				Mod_LoadMD5MeshModel (mod, md5buffer);
@@ -3900,7 +3904,7 @@ static void MD5Anim_Begin(md5animctx_t *ctx, const char *fname)
 	COM_StripExtension(fname, ctx->fname, sizeof(ctx->fname));
 	COM_AddExtension(ctx->fname, ".md5anim", sizeof(ctx->fname));
 	fname = ctx->fname;
-	ctx->animfile = (char *) COM_LoadMallocFile(fname, NULL);
+	ctx->animfile = (char *) QFS_LoadMallocFile(fname, NULL, NULL);
 	ctx->numposes = 0;
 
 	if (ctx->animfile)
diff --git a/Quake/gl_rmisc.c b/Quake/gl_rmisc.c
index 471651f36..ef847d487 100644
--- a/Quake/gl_rmisc.c
+++ b/Quake/gl_rmisc.c
@@ -527,7 +527,7 @@ void R_NewMap (void)
 
 	// Load pointfile if map has no vis data and either developer mode is on or the game was started from a map editing tool
 	if (developer.value || map_checks.value)
-		if (!cl.worldmodel->visdata && COM_FileExists (va ("maps/%s.pts", cl.mapname), NULL))
+		if (!cl.worldmodel->visdata && QFS_FileExists (va ("maps/%s.pts", cl.mapname), NULL))
 			Cbuf_AddText ("pointfile\n");
 }
 
diff --git a/Quake/gl_screen.c b/Quake/gl_screen.c
index 753348723..c2ecae2b0 100644
--- a/Quake/gl_screen.c
+++ b/Quake/gl_screen.c
@@ -885,8 +885,7 @@ void SCR_DrawDemoControls (void)
 
 	// Approximate the fraction of the demo that's already been played back
 	// based on the current file offset and total demo size
-	// Note: we need to take into account the starting offset for pak files
-	frac = (Sys_ftell (cls.demofile) - cls.demofilestart) / (double)cls.demofilesize;
+	frac = QFS_Tell (cls.inpdemo) / (double)cls.demofilesize;
 	frac = CLAMP (0.f, frac, 1.f);
 
 	if (cl.intermission)
diff --git a/Quake/gl_sky.c b/Quake/gl_sky.c
index de418bb7f..f209e0af3 100644
--- a/Quake/gl_sky.c
+++ b/Quake/gl_sky.c
@@ -215,7 +215,7 @@ static void Skywind_Load_f (void)
 	}
 
 	q_snprintf (relname, sizeof (relname), "gfx/env/%s" SKYWIND_CFG, skybox->name);
-	buf = (char *) COM_LoadMallocFile (relname, NULL);
+	buf = (char *) QFS_LoadMallocFile (relname, NULL, NULL);
 	if (!buf)
 	{
 		Con_DPrintf ("Sky wind config not found '%s'.\n", relname);
diff --git a/Quake/gl_texmgr.c b/Quake/gl_texmgr.c
index af474cd8f..227da4664 100644
--- a/Quake/gl_texmgr.c
+++ b/Quake/gl_texmgr.c
@@ -736,25 +736,25 @@ void TexMgr_LoadPalette (void)
 {
 	byte *pal, *src, *colormap;
 	int i, j, mark, numfb;
-	FILE *f;
+	qfshandle_t *f;
 
-	COM_FOpenFile ("gfx/palette.lmp", &f, NULL);
+	f = QFS_FOpenFile ("gfx/palette.lmp", NULL);
 	if (!f)
 		Sys_Error ("Couldn't load gfx/palette.lmp");
 
 	mark = Hunk_LowMark ();
 	pal = (byte *) Hunk_AllocNoFill (768);
-	if (fread (pal, 768, 1, f) != 1)
+	if (QFS_ReadFile (f, pal, 768) != 768)
 		Sys_Error ("Failed reading gfx/palette.lmp");
-	fclose(f);
+	QFS_CloseFile(f);
 
-	COM_FOpenFile ("gfx/colormap.lmp", &f, NULL);
+	f = QFS_FOpenFile ("gfx/colormap.lmp", NULL);
 	if (!f)
 		Sys_Error ("Couldn't load gfx/colormap.lmp");
 	colormap = (byte *) Hunk_AllocNoFill (256 * 64);
-	if (fread (colormap, 256 * 64, 1, f) != 1)
+	if (QFS_ReadFile (f, colormap, 256 * 64) != 256 * 64)
 		Sys_Error ("TexMgr_LoadPalette: colormap read error");
-	fclose(f);
+	QFS_CloseFile(f);
 
 	//find fullbright colors
 	memset (is_fullbright, 0, sizeof (is_fullbright));
@@ -1602,11 +1602,11 @@ void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants)
 
 	if (glt->source_file[0] && glt->source_offset) {
 		//lump inside file
-		FILE *f;
+		qfshandle_t *f;
 		int sz;
-		COM_FOpenFile(glt->source_file, &f, NULL);
+		f = QFS_FOpenFile(glt->source_file, NULL);
 		if (!f) goto invalid;
-		fseek (f, glt->source_offset, SEEK_CUR);
+		QFS_Seek (f, glt->source_offset, SEEK_CUR);
 		size = glt->source_width * glt->source_height;
 		/* should be SRC_INDEXED, but no harm being paranoid:  */
 		if (glt->source_format == SRC_RGBA) {
@@ -1616,8 +1616,8 @@ void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants)
 			size *= lightmap_bytes;
 		}
 		data = (byte *) Hunk_AllocNoFill (size);
-		sz = (int) fread (data, 1, size, f);
-		fclose (f);
+		sz = (int) QFS_ReadFile (f, data, size);
+		QFS_CloseFile (f);
 		if (sz != size) {
 			Hunk_FreeToLowMark(mark);
 			Host_Error("Read error for %s", glt->name);
diff --git a/Quake/gl_vidsdl.c b/Quake/gl_vidsdl.c
index 012ed0f5d..070dd127f 100644
--- a/Quake/gl_vidsdl.c
+++ b/Quake/gl_vidsdl.c
@@ -1558,6 +1558,11 @@ static void VID_InitModelist (void)
 	}
 }
 
+static const char* config_names[] =
+{
+	CONFIG_NAME, "config.cfg"
+};
+
 /*
 ===================
 VID_Init
@@ -1570,6 +1575,7 @@ void	VID_Init (void)
 	int		display_width, display_height, display_refreshrate;
 	qboolean	fullscreen;
 	cmd_function_t	*cmd;
+	size_t idxcfg;
 	const char	*read_vars[] =
 	{
 		"vid_fullscreen",
@@ -1585,7 +1591,6 @@ void	VID_Init (void)
 		"r_softemu_metric",
 		"scr_pixelaspect",
 	};
-#define num_readvars	Q_COUNTOF(read_vars)
 
 	Con_SafePrintf ("\nVideo initialization\n");
 
@@ -1649,12 +1654,16 @@ void	VID_Init (void)
 	Cvar_SetValueQuick (&vid_height, (float)display_height);
 	Cvar_SetValueQuick (&vid_refreshrate, (float)display_refreshrate);
 
-	if (CFG_OpenConfig(CONFIG_NAME) == 0 || CFG_OpenConfig("config.cfg") == 0)
+	for (idxcfg = 0; idxcfg < Q_COUNTOF(config_names); ++idxcfg)
 	{
-		CFG_ReadCvars(read_vars, num_readvars);
-		CFG_CloseConfig();
+		if (QFS_FileExists (config_names[idxcfg], NULL))
+		{
+			CFG_ReadCvars (config_names[idxcfg], read_vars, Q_COUNTOF(read_vars));
+			break;
+		}
 	}
-	CFG_ReadCvarOverrides(read_vars, num_readvars);
+
+	CFG_ReadCvarOverrides(read_vars, Q_COUNTOF(read_vars));
 
 	VID_InitModelist();
 	VID_InitMouseCursors();
diff --git a/Quake/host.c b/Quake/host.c
index 7f50a6c8f..6d753e377 100644
--- a/Quake/host.c
+++ b/Quake/host.c
@@ -1416,7 +1416,7 @@ void Host_Init (void)
 
 	if (cls.state != ca_dedicated)
 	{
-		host_colormap = (byte *)COM_LoadHunkFile ("gfx/colormap.lmp", NULL);
+		host_colormap = (byte *)QFS_LoadHunkFile ("gfx/colormap.lmp", NULL, NULL);
 		if (!host_colormap)
 			Sys_Error ("Couldn't load gfx/colormap.lmp");
 
@@ -1511,6 +1511,8 @@ void Host_Shutdown(void)
 
 	NET_Shutdown ();
 
+	QFS_Shutdown();
+
 	if (cls.state != ca_dedicated)
 	{
 		if (con_initialized)
diff --git a/Quake/host_cmd.c b/Quake/host_cmd.c
index d9a9cde20..f66c4971d 100644
--- a/Quake/host_cmd.c
+++ b/Quake/host_cmd.c
@@ -444,7 +444,6 @@ void ExtraMaps_Init (void)
 	char			mapname[32];
 	char			ignorepakdir[32];
 	searchpath_t	*search;
-	pack_t			*pak;
 	int				i;
 
 	// we don't want to list the maps in id1 pakfiles,
@@ -469,15 +468,19 @@ void ExtraMaps_Init (void)
 		}
 		else //pakfile
 		{
-			qboolean isbase = (strstr(search->pack->filename, ignorepakdir) != NULL);
-			for (i = 0, pak = search->pack; i < pak->numfiles; i++)
+			const char* pack_filename = QFS_PackInfoName(search->pack);
+			qboolean isbase = (pack_filename && strstr(pack_filename, ignorepakdir) != NULL);
+			int filecnt = QFS_PackInfoNumFiles(search->pack);
+			for (i = 0; i < filecnt; ++i)
 			{
-				if (pak->files[i].filelen > 32*1024 &&				// don't list files under 32k (ammo boxes etc)
-					!strncmp (pak->files[i].name, "maps/", 5) &&	// don't list files outside of maps/
-					!strchr (pak->files[i].name + 5, '/') &&		// don't list files in subdirectories
-					!strcmp (COM_FileGetExtension (pak->files[i].name), "bsp"))
+				const char* entry_filename = QFS_PackInfoEntryName(search->pack, i);
+				if (entry_filename &&
+					QFS_PackInfoEntrySize(search->pack, i) > 32*1024 &&				// don't list files under 32k (ammo boxes etc)
+					!strncmp (entry_filename, "maps/", 5) &&	// don't list files outside of maps/
+					!strchr (entry_filename + 5, '/') &&		// don't list files in subdirectories
+					!strcmp (COM_FileGetExtension (entry_filename), "bsp"))
 				{
-					COM_StripExtension (pak->files[i].name + 5, mapname, sizeof (mapname));
+					COM_StripExtension (entry_filename + 5, mapname, sizeof (mapname));
 					ExtraMaps_Add (mapname, isbase ? NULL : search);
 				}
 			}
@@ -1129,7 +1132,7 @@ static void Modlist_Add (const char *name)
 	// look for mapdb.json file
 	if (!info->full_name)
 	{
-		char *mapdb = (char *) COM_LoadMallocFile ("mapdb.json", &path_id);
+		char *mapdb = (char *) QFS_LoadMallocFile ("mapdb.json", &path_id, NULL);
 		if (mapdb)
 		{
 			qboolean is_base_mapdb = !com_searchpaths || path_id < com_searchpaths->path_id;
@@ -1197,6 +1200,10 @@ static qboolean Modlist_Check (const char *modname, const char *base)
 	q_snprintf (itempath, sizeof (itempath), "%s/pak0.pak", modpath);
 	if (Sys_FileExists (itempath))
 		return true;
+	
+	q_snprintf (itempath, sizeof (itempath), "%s/pak0.pk3", modpath);
+	if (Sys_FileExists (itempath))
+		return true;
 
 	q_snprintf (itempath, sizeof (itempath), "%s/progs.dat", modpath);
 	if (Sys_FileExists (itempath))
@@ -1339,7 +1346,6 @@ void DemoList_Init (void)
 	char		demname[32];
 	char		ignorepakdir[32];
 	searchpath_t	*search;
-	pack_t		*pak;
 	int		i;
 
 	// we don't want to list the demos in id1 pakfiles,
@@ -1361,13 +1367,16 @@ void DemoList_Init (void)
 		}
 		else //pakfile
 		{
-			if (!strstr(search->pack->filename, ignorepakdir))
+			const char* pack_filename = QFS_PackInfoName(search->pack);
+			if (!strstr(pack_filename, ignorepakdir))
 			{ //don't list standard id demos
-				for (i = 0, pak = search->pack; i < pak->numfiles; i++)
+				int filecnt = QFS_PackInfoNumFiles(search->pack);
+				for (i = 0; i < filecnt; ++i)
 				{
-					if (!strcmp (COM_FileGetExtension (pak->files[i].name), "dem"))
+					const char* entry_filename = QFS_PackInfoEntryName(search->pack, i);
+					if (!strcmp (COM_FileGetExtension (entry_filename), "dem"))
 					{
-						COM_StripExtension (pak->files[i].name, demname, sizeof (demname));
+						COM_StripExtension (entry_filename, demname, sizeof (demname));
 						FileList_Add (demname, &demolist);
 					}
 				}
@@ -1498,7 +1507,6 @@ static void SkyList_AddDirRec (const char *root, const char *relpath)
 void SkyList_Init (void)
 {
 	searchpath_t	*search;
-	pack_t			*pak;
 	int				i;
 
 	for (search = com_searchpaths; search; search = search->next)
@@ -1506,8 +1514,11 @@ void SkyList_Init (void)
 		if (*search->filename) //directory
 			SkyList_AddDirRec (search->filename, "gfx/env");
 		else //pakfile
-			for (i = 0, pak = search->pack; i < pak->numfiles; i++)
-				SkyList_AddFile (pak->files[i].name);
+		{
+			int entry_count = QFS_PackInfoNumFiles(search->pack);
+			for (i = 0; i < entry_count; ++i)
+				SkyList_AddFile (QFS_PackInfoEntryName(search->pack, i));
+		}
 	}
 }
 
@@ -2097,7 +2108,7 @@ static void Host_Changelevel_f (void)
 
 	//johnfitz -- check for client having map before anything else
 	q_snprintf (level, sizeof(level), "maps/%s.bsp", Cmd_Argv(1));
-	if (!COM_FileExists(level, NULL))
+	if (!QFS_FileExists(level, NULL))
 		Host_Error ("cannot find map %s", level);
 	//johnfitz
 
diff --git a/Quake/image.c b/Quake/image.c
index f2ecc9e41..5459089fd 100644
--- a/Quake/image.c
+++ b/Quake/image.c
@@ -23,8 +23,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #include "quakedef.h"
 
-static byte *Image_LoadPCX (FILE *f, int *width, int *height);
-static byte *Image_LoadLMP (FILE *f, int *width, int *height);
+static byte *Image_LoadPCX (qfshandle_t *f, int *width, int *height);
+static byte *Image_LoadLMP (qfshandle_t *f, int *width, int *height);
 
 #ifdef __GNUC__
 	// Suppress unused function warnings on GCC/clang
@@ -61,32 +61,32 @@ static byte *Image_LoadLMP (FILE *f, int *width, int *height);
 
 static char loadfilename[MAX_OSPATH]; //file scope so that error messages can use it
 
-typedef struct stdio_buffer_s {
-	FILE *f;
+typedef struct io_buffer_s {
+	qfshandle_t *f;
 	unsigned char buffer[1024];
-	int size;
-	int pos;
-} stdio_buffer_t;
+	size_t size;
+	size_t pos;
+} io_buffer_t;
 
-static stdio_buffer_t *Buf_Alloc(FILE *f)
+static io_buffer_t *Buf_Alloc(qfshandle_t *f)
 {
-	stdio_buffer_t *buf = (stdio_buffer_t *) calloc(1, sizeof(stdio_buffer_t));
+	io_buffer_t *buf = (io_buffer_t *) calloc(1, sizeof(io_buffer_t));
 	if (!buf)
 		Sys_Error ("Buf_Alloc: out of memory");
 	buf->f = f;
 	return buf;
 }
 
-static void Buf_Free(stdio_buffer_t *buf)
+static void Buf_Free(io_buffer_t *buf)
 {
 	free(buf);
 }
 
-static inline int Buf_GetC(stdio_buffer_t *buf)
+static inline int Buf_GetC(io_buffer_t *buf)
 {
 	if (buf->pos >= buf->size)
 	{
-		buf->size = fread(buf->buffer, 1, sizeof(buf->buffer), buf->f);
+		buf->size = QFS_ReadFile(buf->f, buf->buffer, sizeof(buf->buffer));
 		buf->pos = 0;
 		
 		if (buf->size == 0)
@@ -96,6 +96,25 @@ static inline int Buf_GetC(stdio_buffer_t *buf)
 	return buf->buffer[buf->pos++];
 }
 
+/*
+Callback functions that stb can use to read from QFS files
+*/
+
+static int STBCB_Read(void *f, char *data, int size)
+{
+	return (int)QFS_ReadFile((qfshandle_t*)f, data, (size_t)size);
+}
+
+static void STBCB_Skip(void *f, int n)
+{
+	QFS_Seek((qfshandle_t*)f, (qfileofs_t)n, SEEK_CUR);
+}
+
+static int STBCB_Eof(void* f)
+{
+	return QFS_Eof((qfshandle_t*)f) ? 1 : 0;
+}
+
 /*
 ============
 Image_LoadImage
@@ -106,17 +125,22 @@ returns a pointer to hunk allocated RGBA data
 byte *Image_LoadImage (const char *name, int *width, int *height, enum srcformat *fmt)
 {
 	static const char *const stbi_formats[] = {"png", "tga", "jpg", NULL};
-	FILE	*f;
+	qfshandle_t	*f;
+	stbi_io_callbacks callbacks;
 	int		i;
 
 	for (i = 0; stbi_formats[i]; i++)
 	{
 		const char *ext = stbi_formats[i];
 		q_snprintf (loadfilename, sizeof(loadfilename), "%s.%s", name, ext);
-		COM_FOpenFile (loadfilename, &f, NULL);
+		f = QFS_FOpenFile (loadfilename, NULL);
 		if (f)
 		{
-			byte *data = stbi_load_from_file (f, width, height, NULL, 4);
+			callbacks.read = &STBCB_Read;
+			callbacks.skip = &STBCB_Skip;
+			callbacks.eof = &STBCB_Eof;
+
+			byte *data = stbi_load_from_callbacks (&callbacks, f, width, height, NULL, 4);
 			if (data)
 			{
 				int numbytes = (*width) * (*height) * 4;
@@ -130,13 +154,13 @@ byte *Image_LoadImage (const char *name, int *width, int *height, enum srcformat
 			}
 			else
 				Con_Warning ("couldn't load %s (%s)\n", loadfilename, stbi_failure_reason ());
-			fclose (f);
+			QFS_CloseFile (f);
 			return data;
 		}
 	}
 
 	q_snprintf (loadfilename, sizeof(loadfilename), "%s.pcx", name);
-	COM_FOpenFile (loadfilename, &f, NULL);
+	f = QFS_FOpenFile (loadfilename, NULL);
 	if (f)
 	{
 		*fmt = SRC_RGBA;
@@ -144,7 +168,7 @@ byte *Image_LoadImage (const char *name, int *width, int *height, enum srcformat
 	}
 
 	q_snprintf (loadfilename, sizeof(loadfilename), "%s.lmp", name);
-	COM_FOpenFile (loadfilename, &f, NULL);
+	f = QFS_FOpenFile (loadfilename, NULL);
 	if (f)
 	{
 		*fmt = SRC_INDEXED;
@@ -241,17 +265,15 @@ typedef struct
 Image_LoadPCX
 ============
 */
-static byte *Image_LoadPCX (FILE *f, int *width, int *height)
+static byte *Image_LoadPCX (qfshandle_t *f, int *width, int *height)
 {
 	pcxheader_t	pcx;
-	int			x, y, w, h, readbyte, runlength, start;
+	int			x, y, w, h, readbyte, runlength;
 	byte		*p, *data;
 	byte		palette[768];
-	stdio_buffer_t  *buf;
-
-	start = ftell (f); //save start of file (since we might be inside a pak file, SEEK_SET might not be the start of the pcx)
+	io_buffer_t  *buf;
 
-	if (fread(&pcx, sizeof(pcx), 1, f) != 1)
+	if (QFS_ReadFile(f, &pcx, sizeof(pcx)) != sizeof(pcx))
 		Sys_Error ("Failed reading header from '%s'", loadfilename);
 
 	pcx.xmin = (unsigned short)LittleShort (pcx.xmin);
@@ -275,12 +297,14 @@ static byte *Image_LoadPCX (FILE *f, int *width, int *height)
 	data = (byte *) Hunk_AllocNoFill ((w*h+1)*4); //+1 to allow reading padding byte on last line
 
 	//load palette
-	fseek (f, start + com_filesize - 768, SEEK_SET);
-	if (fread (palette, 768, 1, f) != 1)
+	if (QFS_Seek (f, QFS_FileSize(f) - sizeof(palette), SEEK_SET) != 0
+		|| QFS_ReadFile (f, palette, sizeof(palette)) != sizeof(palette))
+	{
 		Sys_Error ("Failed reading palette from '%s'", loadfilename);
+	}
 
 	//back to start of image data
-	fseek (f, start + sizeof(pcx), SEEK_SET);
+	QFS_Seek (f, sizeof(pcx), SEEK_SET);
 
 	buf = Buf_Alloc(f);
 
@@ -313,7 +337,7 @@ static byte *Image_LoadPCX (FILE *f, int *width, int *height)
 	}
 
 	Buf_Free(buf);
-	fclose(f);
+	QFS_CloseFile(f);
 
 	*width = w;
 	*height = h;
@@ -336,16 +360,16 @@ typedef struct
 Image_LoadLMP
 ============
 */
-static byte *Image_LoadLMP (FILE *f, int *width, int *height)
+static byte *Image_LoadLMP (qfshandle_t *f, int *width, int *height)
 {
 	lmpheader_t	qpic;
 	size_t		pix;
 	int			mark;
 	void		*data;
 
-	if (fread (&qpic, sizeof(qpic), 1, f) != 1)
+	if (QFS_ReadFile (f, &qpic, sizeof(qpic)) != sizeof(qpic))
 	{
-		fclose (f);
+		QFS_CloseFile (f);
 		return NULL;
 	}
 	qpic.width = LittleLong (qpic.width);
@@ -353,21 +377,21 @@ static byte *Image_LoadLMP (FILE *f, int *width, int *height)
 
 	pix = qpic.width*qpic.height;
 
-	if (com_filesize != sizeof (qpic) + pix)
+	if (QFS_FileSize(f) != (qfileofs_t)(sizeof (qpic) + pix))
 	{
-		fclose (f);
+		QFS_CloseFile (f);
 		return NULL;
 	}
 
 	mark = Hunk_LowMark ();
 	data = (byte *) Hunk_AllocNoFill (pix);
-	if (fread (data, 1, pix, f) != pix)
+	if (QFS_ReadFile (f, data, pix) != pix)
 	{
 		Hunk_FreeToLowMark (mark);
-		fclose (f);
+		QFS_CloseFile (f);
 		return NULL;
 	}
-	fclose (f);
+	QFS_CloseFile (f);
 
 	*width = qpic.width;
 	*height = qpic.height;
diff --git a/Quake/menu.c b/Quake/menu.c
index 355a3cf26..51ad52b68 100644
--- a/Quake/menu.c
+++ b/Quake/menu.c
@@ -7499,20 +7499,22 @@ void M_ConfigureNetSubsystem(void)
 static qboolean M_CheckCustomGfx (const char *custompath, const char *basepath, int knownlength, const unsigned int *hashes, int numhashes)
 {
 	unsigned int id_custom, id_base;
-	int h, length;
+	qfshandle_t* h;
+	int length;
 	qboolean ret = false;
 
-	if (!COM_FileExists (custompath, &id_custom))
+	if (!QFS_FileExists (custompath, &id_custom))
 		return false;
 
-	length = COM_OpenFile (basepath, &h, &id_base);
+	h = QFS_OpenFile (basepath, &id_base);
+	length = (int)QFS_FileSize (h);
 	if (id_custom >= id_base)
 		ret = true;
 	else if (length == knownlength)
 	{
 		int mark = Hunk_LowMark ();
 		byte* data = (byte*) Hunk_Alloc (length);
-		if (length == Sys_FileRead (h, data, length))
+		if (length == (int)QFS_ReadFile (h, data, length))
 		{
 			unsigned int hash = COM_HashBlock (data, length);
 			while (numhashes-- > 0 && !ret)
@@ -7522,7 +7524,7 @@ static qboolean M_CheckCustomGfx (const char *custompath, const char *basepath,
 		Hunk_FreeToLowMark (mark);
 	}
 
-	COM_CloseFile (h);
+	QFS_CloseFile (h);
 
 	return ret;
 }
diff --git a/Quake/miniz.c b/Quake/miniz.c
index a9318862d..97a0777bc 100644
--- a/Quake/miniz.c
+++ b/Quake/miniz.c
@@ -2247,7 +2247,7 @@ mz_bool mz_zip_end(mz_zip_archive *pZip)
 
     return MZ_FALSE;
 }
-
+#endif /* unused */
 mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size)
 {
     mz_uint n;
@@ -2268,7 +2268,6 @@ mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, cha
     }
     return n + 1;
 }
-#endif /* unused */
 
 mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
 {
diff --git a/Quake/pr_edict.c b/Quake/pr_edict.c
index 115fc3af2..c71b02769 100644
--- a/Quake/pr_edict.c
+++ b/Quake/pr_edict.c
@@ -2098,15 +2098,16 @@ PR_LoadProgs
 qboolean PR_LoadProgs (const char *filename, qboolean fatal)
 {
 	int			i;
+	size_t		filesize;
 
 	PR_ClearProgs(qcvm);	//just in case.
 
-	qcvm->progs = (dprograms_t *)COM_LoadHunkFile (filename, NULL);
+	qcvm->progs = (dprograms_t *)QFS_LoadHunkFile (filename, NULL, &filesize);
 	if (!qcvm->progs)
 		return false;
-	Con_DPrintf ("Programs occupy %" SDL_PRIs64 "K.\n", com_filesize/1024);
+	Con_DPrintf ("Programs occupy %" SDL_PRIs64 "K.\n", (int64_t)(filesize/1024));
 
-	qcvm->crc = CRC_Block (qcvm->progs, com_filesize);
+	qcvm->crc = CRC_Block (qcvm->progs, filesize);
 
 	// byte swap the header
 	for (i = 0; i < (int) sizeof(*qcvm->progs) / 4; i++)
@@ -2164,7 +2165,7 @@ qboolean PR_LoadProgs (const char *filename, qboolean fatal)
 
 	qcvm->functions = (dfunction_t *)((byte *)qcvm->progs + qcvm->progs->ofs_functions);
 	qcvm->strings = (char *)qcvm->progs + qcvm->progs->ofs_strings;
-	if (qcvm->progs->ofs_strings + qcvm->progs->numstrings >= com_filesize)
+	if (qcvm->progs->ofs_strings + qcvm->progs->numstrings >= (int64_t)filesize)
 		Host_Error ("progs.dat strings go past end of file\n");
 
 	// initialize the strings
diff --git a/Quake/quakedef.h b/Quake/quakedef.h
index f598c1d10..a47253cb9 100644
--- a/Quake/quakedef.h
+++ b/Quake/quakedef.h
@@ -242,6 +242,7 @@ typedef struct
 
 #include "sys.h"
 #include "common.h"
+#include "filesys.h"
 #include "bspfile.h"
 #include "zone.h"
 #include "mathlib.h"
diff --git a/Quake/r_part.c b/Quake/r_part.c
index ece94287e..7cec4acd1 100644
--- a/Quake/r_part.c
+++ b/Quake/r_part.c
@@ -193,19 +193,20 @@ R_ReadPointFile_f
 */
 void R_ReadPointFile_f (void)
 {
-	FILE	*f;
+	qfshandle_t *f;
 	vec3_t	org;
 	int		r;
 	int		c;
 	particle_t	*p;
 	char	name[MAX_QPATH];
+	char	rdbuf[256];
 
 	if (cls.state != ca_connected)
 		return;			// need an active map.
 
 	q_snprintf (name, sizeof(name), "maps/%s.pts", cl.mapname);
 
-	COM_FOpenFile (name, &f, NULL);
+	f = QFS_FOpenFile (name, NULL);
 	if (!f)
 	{
 		Con_Printf ("couldn't open %s\n", name);
@@ -215,9 +216,11 @@ void R_ReadPointFile_f (void)
 	Con_Printf ("Reading %s...\n", name);
 	c = 0;
 	org[0] = org[1] = org[2] = 0; // silence pesky compiler warnings
-	for ( ;; )
+	while (!QFS_Eof (f))
 	{
-		r = fscanf (f,"%f %f %f\n", &org[0], &org[1], &org[2]);
+		memset (rdbuf, 0, sizeof(rdbuf));
+		QFS_GetLine (f, rdbuf, sizeof(rdbuf));
+		r = sscanf (rdbuf,"%f %f %f", &org[0], &org[1], &org[2]);
 		if (r != 3)
 			break;
 		c++;
@@ -235,7 +238,7 @@ void R_ReadPointFile_f (void)
 		VectorCopy (org, p->org);
 	}
 
-	fclose (f);
+	QFS_CloseFile (f);
 	Con_Printf ("%i points read\n", c);
 }
 
diff --git a/Quake/snd_codec.c b/Quake/snd_codec.c
index 7b138d45c..64be0bb4b 100644
--- a/Quake/snd_codec.c
+++ b/Quake/snd_codec.c
@@ -293,14 +293,10 @@ int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer)
 snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec, qboolean loop)
 {
 	snd_stream_t *stream;
-	FILE *handle;
-	qboolean pak;
-	long length;
 
 	/* Try to open the file */
-	length = (long) COM_FOpenFile(filename, &handle, NULL);
-	pak = file_from_pak;
-	if (length == -1)
+	qfshandle_t* handle = QFS_FOpenFile(filename, NULL);
+	if (handle == NULL)
 	{
 		Con_DPrintf("Couldn't open %s\n", filename);
 		return NULL;
@@ -310,11 +306,7 @@ snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec, qboolean
 	stream = (snd_stream_t *) Z_Malloc(sizeof(snd_stream_t));
 	stream->codec = codec;
 	stream->loop = loop;
-	stream->fh.file = handle;
-	stream->fh.start = ftell(handle);
-	stream->fh.pos = 0;
-	stream->fh.length = length;
-	stream->fh.pak = stream->pak = pak;
+	stream->fh = handle;
 	q_strlcpy(stream->name, filename, MAX_QPATH);
 
 	return stream;
@@ -322,7 +314,7 @@ snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec, qboolean
 
 void S_CodecUtilClose(snd_stream_t **stream)
 {
-	fclose((*stream)->fh.file);
+	QFS_CloseFile((*stream)->fh);
 	Z_Free(*stream);
 	*stream = NULL;
 }
diff --git a/Quake/snd_codec.h b/Quake/snd_codec.h
index 1136c010f..a84d1f276 100644
--- a/Quake/snd_codec.h
+++ b/Quake/snd_codec.h
@@ -48,8 +48,7 @@ typedef struct snd_codec_s snd_codec_t;
 
 typedef struct snd_stream_s
 {
-	fshandle_t fh;
-	qboolean pak;
+	qfshandle_t *fh;
 	char name[MAX_QPATH];	/* name of the source file */
 	snd_info_t info;
 	stream_status_t status;
diff --git a/Quake/snd_flac.c b/Quake/snd_flac.c
index 838404b3b..d57873073 100644
--- a/Quake/snd_flac.c
+++ b/Quake/snd_flac.c
@@ -69,7 +69,7 @@ typedef size_t   FLAC_SIZE_T;
 
 typedef struct {
 	FLAC__StreamDecoder *decoder;
-	fshandle_t *file;
+	qfshandle_t *file;
 	snd_info_t *info;
 	byte *buffer;
 	int size, pos, error;
@@ -92,9 +92,7 @@ flac_read_func (const FLAC__StreamDecoder *decoder, FLAC__byte buffer[],
 	flacfile_t *ff = (flacfile_t *) client_data;
 	if (*bytes > 0)
 	{
-		*bytes = FS_fread(buffer, 1, *bytes, ff->file);
-		if (FS_ferror(ff->file))
-			return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+		*bytes = (FLAC_SIZE_T)QFS_ReadFile(ff->file, buffer, (size_t)*bytes);
 		if (*bytes == 0)
 			return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
 		return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
@@ -107,7 +105,7 @@ flac_seek_func (const FLAC__StreamDecoder *decoder,
 		FLAC__uint64 absolute_byte_offset, void *client_data)
 {
 	flacfile_t *ff = (flacfile_t *) client_data;
-	if (FS_fseek(ff->file, (long)absolute_byte_offset, SEEK_SET) < 0)
+	if (QFS_Seek(ff->file, (qfileofs_t)absolute_byte_offset, SEEK_SET) < 0)
 		return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
 	return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
 }
@@ -117,7 +115,7 @@ flac_tell_func (const FLAC__StreamDecoder *decoder,
 		FLAC__uint64 *absolute_byte_offset, void *client_data)
 {
 	flacfile_t *ff = (flacfile_t *) client_data;
-	long pos = FS_ftell (ff->file);
+	qfileofs_t pos = QFS_Tell (ff->file);
 	if (pos < 0) return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
 	*absolute_byte_offset = (FLAC__uint64) pos;
 	return FLAC__STREAM_DECODER_TELL_STATUS_OK;
@@ -128,7 +126,7 @@ flac_length_func (const FLAC__StreamDecoder *decoder,
 		  FLAC__uint64 *stream_length, void *client_data)
 {
 	flacfile_t *ff = (flacfile_t *) client_data;
-	*stream_length = (FLAC__uint64) FS_filelength (ff->file);
+	*stream_length = (FLAC__uint64) QFS_FileSize (ff->file);
 	return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
 }
 
@@ -136,7 +134,7 @@ static FLAC__bool
 flac_eof_func (const FLAC__StreamDecoder *decoder, void *client_data)
 {
 	flacfile_t *ff = (flacfile_t *) client_data;
-	if (FS_feof (ff->file)) return true;
+	if (QFS_Eof (ff->file)) return true;
 	return false;
 }
 
@@ -249,7 +247,7 @@ static qboolean S_FLAC_CodecOpenStream (snd_stream_t *stream)
 
 	stream->priv = ff;
 	ff->info = & stream->info;
-	ff->file = & stream->fh;
+	ff->file = stream->fh;
 	ff->info->dataofs = -1; /* check for STREAMINFO metadata existence */
 
 #ifdef LEGACY_FLAC
diff --git a/Quake/snd_mem.c b/Quake/snd_mem.c
index b7951b486..28256f47f 100644
--- a/Quake/snd_mem.c
+++ b/Quake/snd_mem.c
@@ -99,6 +99,7 @@ sfxcache_t *S_LoadSound (sfx_t *s)
 	wavinfo_t	info;
 	int		len;
 	float	stepscale;
+	size_t	filesize;
 	sfxcache_t	*sc;
 
 // see if still in memory
@@ -114,7 +115,7 @@ sfxcache_t *S_LoadSound (sfx_t *s)
 
 //	Con_Printf ("loading %s\n",namebuffer);
 
-	data = COM_LoadMallocFile (namebuffer, NULL);
+	data = QFS_LoadMallocFile (namebuffer, NULL, &filesize);
 
 	if (!data)
 	{
@@ -122,7 +123,7 @@ sfxcache_t *S_LoadSound (sfx_t *s)
 		return NULL;
 	}
 
-	info = GetWavinfo (s->name, data, com_filesize);
+	info = GetWavinfo (s->name, data, filesize);
 	if (info.channels != 1)
 	{
 		free (data);
diff --git a/Quake/snd_mikmod.c b/Quake/snd_mikmod.c
index bc0859b2d..6ea4faa51 100644
--- a/Quake/snd_mikmod.c
+++ b/Quake/snd_mikmod.c
@@ -52,33 +52,41 @@ typedef struct _mik_priv {
 	/* no iobase members in libmikmod <= 3.2.0-beta2 */
 	long iobase, prev_iobase;
 
-	fshandle_t *fh;
+	qfshandle_t *fh;
 	MODULE *module;
 } mik_priv_t;
 
 static int MIK_Seek (MREADER *r, long ofs, int whence)
 {
-	return FS_fseek(((mik_priv_t *)r)->fh, ofs, whence);
+	mik_priv_t* pmik = (mik_priv_t*)r;
+	return (int)QFS_Seek(pmik->fh, (qfileofs_t)ofs, whence);
 }
 
 static long MIK_Tell (MREADER *r)
 {
-	return FS_ftell(((mik_priv_t *)r)->fh);
+	mik_priv_t* pmik = (mik_priv_t*)r;
+	return (long)QFS_Tell(pmik->fh);
 }
 
 static BOOL MIK_Read (MREADER *r, void *ptr, size_t siz)
 {
-	return !!FS_fread(ptr, siz, 1, ((mik_priv_t *)r)->fh);
+	mik_priv_t* pmik = (mik_priv_t*)r;
+	return QFS_ReadFile(pmik->fh, ptr, siz) == siz;
 }
 
 static int MIK_Get (MREADER *r)
 {
-	return FS_fgetc(((mik_priv_t *)r)->fh);
+	char c;
+	qboolean eof_flag;
+	mik_priv_t* pmik = (mik_priv_t*)r;
+	c = QFS_GetChar(pmik->fh, &eof_flag);
+	return eof_flag ? EOF : (int)c;
 }
 
 static BOOL MIK_Eof (MREADER *r)
 {
-	return FS_feof(((mik_priv_t *)r)->fh);
+	mik_priv_t* pmik = (mik_priv_t*)r;
+	return QFS_Eof(pmik->fh) ? 1 : 0;
 }
 
 static qboolean S_MIKMOD_CodecInitialize (void)
@@ -140,7 +148,7 @@ static qboolean S_MIKMOD_CodecOpenStream (snd_stream_t *stream)
 	priv->Read = MIK_Read;
 	priv->Get  = MIK_Get;
 	priv->Eof  = MIK_Eof;
-	priv->fh = &stream->fh;
+	priv->fh = stream->fh;
 
 	priv->module = Player_LoadGeneric((MREADER *)stream->priv, 64, 0);
 	if (!priv->module)
diff --git a/Quake/snd_modplug.c b/Quake/snd_modplug.c
index 806c09a79..48227705c 100644
--- a/Quake/snd_modplug.c
+++ b/Quake/snd_modplug.c
@@ -61,13 +61,13 @@ static qboolean S_MODPLUG_CodecOpenStream (snd_stream_t *stream)
 {
 /* need to load the whole file into memory and pass it to libmodplug */
 	byte *moddata;
-	long len;
+	qfileofs_t len;
 	int mark;
 
-	len = FS_filelength (&stream->fh);
+	len = QFS_FileSize (stream->fh);
 	mark = Hunk_LowMark();
 	moddata = (byte *) Hunk_Alloc(len);
-	FS_fread(moddata, 1, len, &stream->fh);
+	QFS_ReadFile(stream->fh, moddata, len);
 
 	S_MODPLUG_SetSettings(stream);
 	stream->priv = ModPlug_Load(moddata, len);
diff --git a/Quake/snd_mp3.c b/Quake/snd_mp3.c
index b3bf9a145..31f33badb 100644
--- a/Quake/snd_mp3.c
+++ b/Quake/snd_mp3.c
@@ -84,8 +84,7 @@ static int mp3_inputdata(snd_stream_t *stream)
 	 */
 	memmove(p->mp3_buffer, p->Stream.next_frame, remaining);
 
-	bytes_read = FS_fread(p->mp3_buffer + remaining, 1,
-				MP3_BUFFER_SIZE - remaining, &stream->fh);
+	bytes_read = QFS_ReadFile(stream->fh, p->mp3_buffer + remaining, MP3_BUFFER_SIZE - remaining);
 	if (bytes_read == 0)
 		return -1;
 
@@ -109,8 +108,8 @@ static int mp3_startread(snd_stream_t *stream)
 	 * format.  The decoded frame will be saved off so that it
 	 * can be processed later.
 	 */
-	ReadSize = FS_fread(p->mp3_buffer, 1, MP3_BUFFER_SIZE, &stream->fh);
-	if (!ReadSize || FS_ferror(&stream->fh))
+	ReadSize = QFS_ReadFile(stream->fh, p->mp3_buffer, MP3_BUFFER_SIZE);
+	if (!ReadSize)
 		return -1;
 
 	mad_stream_buffer(&p->Stream, p->mp3_buffer, ReadSize);
@@ -279,7 +278,7 @@ static int mp3_madseek(snd_stream_t *stream, unsigned long offset)
 	unsigned long to_skip_samples = 0;
 
 	/* Reset all */
-	FS_rewind(&stream->fh);
+	QFS_Seek(stream->fh, 0, SEEK_SET);
 	mad_timer_reset(&p->Timer);
 	p->FrameCount = 0;
 
@@ -301,8 +300,8 @@ static int mp3_madseek(snd_stream_t *stream, unsigned long offset)
 		size_t leftover = p->Stream.bufend - p->Stream.next_frame;
 
 		memcpy(p->mp3_buffer, p->Stream.this_frame, leftover);
-		bytes_read = FS_fread(p->mp3_buffer + leftover, (size_t) 1,
-					MP3_BUFFER_SIZE - leftover, &stream->fh);
+		bytes_read = QFS_ReadFile(stream->fh, p->mp3_buffer + leftover,
+					MP3_BUFFER_SIZE - leftover);
 		if (bytes_read <= 0)
 		{
 			Con_DPrintf("seek failure. unexpected EOF (frames=%lu leftover=%lu)\n",
@@ -362,7 +361,7 @@ static int mp3_madseek(snd_stream_t *stream, unsigned long offset)
 			{
 				p->FrameCount = offset / samples;
 				to_skip_samples = offset % samples;
-				if (0 != FS_fseek(&stream->fh, (p->FrameCount * consumed / 64), SEEK_SET))
+				if (0 != QFS_Seek(stream->fh, (p->FrameCount * consumed / 64), SEEK_SET))
 					return -1;
 
 				/* Reset Stream for refilling buffer */
diff --git a/Quake/snd_mp3tag.c b/Quake/snd_mp3tag.c
index d778d10af..673f54fbe 100644
--- a/Quake/snd_mp3tag.c
+++ b/Quake/snd_mp3tag.c
@@ -100,13 +100,16 @@ static inline int is_lyrics3tag(const unsigned char *data, long length) {
     return 0;
 }
 static long get_lyrics3v1_len(snd_stream_t *stream) {
-    const char *p; long i, len;
+    const char *p; long i;
+    qfileofs_t len;
     char buf[5104];
     /* needs manual search:  http://id3.org/Lyrics3 */
-    if (stream->fh.length < 20) return -1;
-    len = (stream->fh.length > 5109)? 5109 : stream->fh.length;
-    FS_fseek(&stream->fh, -len, SEEK_END);
-    FS_fread(buf, 1, (len -= 9), &stream->fh); /* exclude footer */
+    qfileofs_t filesize = QFS_FileSize(stream->fh);
+    if (filesize < 20)
+        return -1;
+    len = (filesize > 5109)? 5109 : (long)filesize;
+    QFS_Seek(stream->fh, -len, SEEK_END);
+    QFS_ReadFile(stream->fh, buf, (len -= 9)); /* exclude footer */
     /* strstr() won't work here. */
     for (i = len - 11, p = buf; i >= 0; --i, ++p) {
         if (memcmp(p, "LYRICSBEGIN", 11) == 0)
@@ -126,7 +129,7 @@ static inline qboolean verify_lyrics3v2(const unsigned char *data, long length)
     if (memcmp(data,"LYRICSBEGIN",11) == 0) return true;
     return false;
 }
-#define MMTAG_PARANOID
+
 static qboolean is_musicmatch(const unsigned char *data, long length) {
   /* From docs/musicmatch.txt in id3lib: https://sourceforge.net/projects/id3lib/
      Overall tag structure:
@@ -162,12 +165,12 @@ static qboolean is_musicmatch(const unsigned char *data, long length) {
         !q_isdigit(data[34]) ||!q_isdigit(data[35])) {
         return false;
     }
-    #ifdef MMTAG_PARANOID
+
     /* [36..47]: 12 bytes trailing space */
     for (length = 36; length < 48; ++length) {
         if (data[length] != ' ') return false;
     }
-    #endif
+
     return true;
 }
 static long get_musicmatch_len(snd_stream_t *stream) {
@@ -175,10 +178,13 @@ static long get_musicmatch_len(snd_stream_t *stream) {
     const unsigned char syncstr[10] = {'1','8','2','7','3','6','4','5',0,0};
     unsigned char buf[256];
     int i, j, imgext_ofs, version_ofs;
-    long len;
+    qfileofs_t len, filesize;
+
+    memset(buf, 0, sizeof(buf));
+    filesize = QFS_FileSize(stream->fh);
 
-    FS_fseek(&stream->fh, -68, SEEK_END);
-    FS_fread(buf, 1, 20, &stream->fh);
+    QFS_Seek(stream->fh, -68, SEEK_END);
+    QFS_ReadFile(stream->fh, buf, 20);
     imgext_ofs  = (int)((buf[3] <<24) | (buf[2] <<16) | (buf[1] <<8) | buf[0] );
     version_ofs = (int)((buf[15]<<24) | (buf[14]<<16) | (buf[13]<<8) | buf[12]);
     if (version_ofs <= imgext_ofs) return -1;
@@ -189,58 +195,64 @@ static long get_musicmatch_len(snd_stream_t *stream) {
      * bytes), we can _not_ directly calculate using deltas from the offsets
      * section. */
     for (i = 0; i < 4; ++i) {
-    /* 48: footer, 20: offsets, 256: version info */
+        memset(buf, 0, sizeof(buf));
+        /* 48: footer, 20: offsets, 256: version info */
         len = metasizes[i] + 48 + 20 + 256;
-        if (stream->fh.length < len) return -1;
-        FS_fseek(&stream->fh, -len, SEEK_END);
-        FS_fread(buf, 1, 256, &stream->fh);
+        if (filesize < len)
+            return -1;
+        QFS_Seek(stream->fh, -len, SEEK_END);
+        QFS_ReadFile(stream->fh, buf, 256);
         /* [0..9]: sync string, [30..255]: 0x20 */
-        #ifdef MMTAG_PARANOID
+
         for (j = 30; j < 256; ++j) {
             if (buf[j] != ' ') break;
         }
         if (j < 256) continue;
-        #endif
+
         if (memcmp(buf, syncstr, 10) == 0) {
             break;
         }
     }
     if (i == 4) return -1; /* no luck. */
-    #ifdef MMTAG_PARANOID
+    memset(buf, 0, sizeof(buf));
     /* unused section: (4 bytes of 0x00) */
-    FS_fseek(&stream->fh, -(len + 4), SEEK_END);
-    FS_fread(buf, 1, 4, &stream->fh); j = 0;
+    QFS_Seek(stream->fh, -(len + 4), SEEK_END);
+    QFS_ReadFile(stream->fh, buf, 4);
+    j = 0;
     if (memcmp(buf, &j, 4) != 0) return -1;
-    #endif
+
     len += (version_ofs - imgext_ofs);
-    if (stream->fh.length < len) return -1;
-    FS_fseek(&stream->fh, -len, SEEK_END);
-    FS_fread(buf, 1, 8, &stream->fh);
+    if (filesize < len) return -1;
+    memset(buf, 0, sizeof(buf));
+    QFS_Seek(stream->fh, -len, SEEK_END);
+    QFS_ReadFile(stream->fh, buf, 8);
     j = (int)((buf[7] <<24) | (buf[6] <<16) | (buf[5] <<8) | buf[4]);
     if (j < 0) return -1;
     /* verify image size: */
     /* without this, we may land at a wrong place. */
     if (j + 12 != version_ofs - imgext_ofs) return -1;
     /* try finding the optional header */
-    if (stream->fh.length < len + 256) return len;
-    FS_fseek(&stream->fh, -(len + 256), SEEK_END);
-    FS_fread(buf, 1, 256, &stream->fh);
+    if (filesize < len + 256) return len;
+    QFS_Seek(stream->fh, -(len + 256), SEEK_END);
+    QFS_ReadFile(stream->fh, buf, 256);
     /* [0..9]: sync string, [30..255]: 0x20 */
     if (memcmp(buf, syncstr, 10) != 0) {
         return len;
     }
-    #ifdef MMTAG_PARANOID
+
     for (j = 30; j < 256; ++j) {
         if (buf[j] != ' ') return len;
     }
-    #endif
+
     return len + 256; /* header is present. */
 }
 
-static int probe_id3v1(snd_stream_t *stream, unsigned char *buf, int atend) {
-    if (stream->fh.length >= 128) {
-        FS_fseek(&stream->fh, -128, SEEK_END);
-        if (FS_fread(buf, 1, 128, &stream->fh) != 128)
+static int probe_id3v1(snd_stream_t *stream, unsigned char *buf, int atend)
+{
+    qfileofs_t filesize = QFS_FileSize(stream->fh);
+    if (filesize >= 128) {
+        QFS_Seek(stream->fh, -128, SEEK_END);
+        if (QFS_ReadFile(stream->fh, buf, 128) != 128)
             return -1;
         if (is_id3v1(buf, 128)) {
             if (!atend) { /* possible false positive? */
@@ -250,7 +262,7 @@ static int probe_id3v1(snd_stream_t *stream, unsigned char *buf, int atend) {
                     return 0;
                 }
             }
-            stream->fh.length -= 128;
+            QFS_IgnoreBytes(stream->fh, 128, SEEK_END);
             Con_DPrintf("MP3: skipped %ld bytes ID3v1 tag\n", 128L);
             return 1;
             /* FIXME: handle possible double-ID3v1 tags? */
@@ -258,33 +270,37 @@ static int probe_id3v1(snd_stream_t *stream, unsigned char *buf, int atend) {
     }
     return 0;
 }
-static int probe_mmtag(snd_stream_t *stream, unsigned char *buf) {
+static int probe_mmtag(snd_stream_t *stream, unsigned char *buf)
+{
     long len;
-    if (stream->fh.length >= 68) {
-        FS_fseek(&stream->fh, -48, SEEK_END);
-        if (FS_fread(buf, 1, 48, &stream->fh) != 48)
+    qfileofs_t filesize = QFS_FileSize(stream->fh);
+    if (filesize >= 68)
+    {
+        QFS_Seek(stream->fh, -48, SEEK_END);
+        if (QFS_ReadFile(stream->fh, buf, 48) != 48)
             return -1;
         if (is_musicmatch(buf, 48)) {
             len = get_musicmatch_len(stream);
-            if (len < 0) return -1;
-            if (len >= stream->fh.length) return -1;
-            stream->fh.length -= len;
+            if (len < 0)
+                return -1;
+            QFS_IgnoreBytes(stream->fh, (qfileofs_t)len, SEEK_END);
             Con_DPrintf("MP3: skipped %ld bytes MusicMatch tag\n", len);
             return 1;
         }
     }
     return 0;
 }
-static int probe_apetag(snd_stream_t *stream, unsigned char *buf) {
+static int probe_apetag(snd_stream_t *stream, unsigned char *buf)
+{
     long len;
-    if (stream->fh.length >= 32) {
-        FS_fseek(&stream->fh, -32, SEEK_END);
-        if (FS_fread(buf, 1, 32, &stream->fh) != 32)
+    qfileofs_t filesize = QFS_FileSize(stream->fh);
+    if (filesize >= 32) {
+        QFS_Seek(stream->fh, -32, SEEK_END);
+        if (QFS_ReadFile(stream->fh, buf, 32) != 32)
             return -1;
         if (is_apetag(buf, 32)) {
             len = get_ape_len(buf);
-            if (len >= stream->fh.length) return -1;
-            stream->fh.length -= len;
+            QFS_IgnoreBytes(stream->fh, (qfileofs_t)len, SEEK_END);
             Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
             return 1;
         }
@@ -293,27 +309,31 @@ static int probe_apetag(snd_stream_t *stream, unsigned char *buf) {
 }
 static int probe_lyrics3(snd_stream_t *stream, unsigned char *buf) {
     long len;
-    if (stream->fh.length >= 15) {
-        FS_fseek(&stream->fh, -15, SEEK_END);
-        if (FS_fread(buf, 1, 15, &stream->fh) != 15)
+    qfileofs_t filesize = QFS_FileSize(stream->fh);
+    if (filesize >= 15) {
+        QFS_Seek(stream->fh, -15, SEEK_END);
+        if (QFS_ReadFile(stream->fh, buf, 15) != 15)
             return -1;
         len = is_lyrics3tag(buf, 15);
         if (len == 2) {
             len = get_lyrics3v2_len(buf, 6);
-            if (len >= stream->fh.length) return -1;
-            if (len < 15) return -1;
-            FS_fseek(&stream->fh, -len, SEEK_END);
-            if (FS_fread(buf, 1, 11, &stream->fh) != 11)
+            if (len >= filesize)
+                return -1;
+            if (len < 15)
                 return -1;
-            if (!verify_lyrics3v2(buf, 11)) return -1;
-            stream->fh.length -= len;
+            QFS_Seek(stream->fh, -len, SEEK_END);
+            if (QFS_ReadFile(stream->fh, buf, 11) != 11)
+                return -1;
+            if (!verify_lyrics3v2(buf, 11))
+                return -1;
+            QFS_IgnoreBytes(stream->fh, len, SEEK_END);
             Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
             return 1;
         }
         else if (len == 1) {
             len = get_lyrics3v1_len(stream);
             if (len < 0) return -1;
-            stream->fh.length -= len;
+            QFS_IgnoreBytes(stream->fh, len, SEEK_END);
             Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
             return 1;
         }
@@ -327,9 +347,6 @@ int mp3_skiptags(snd_stream_t *stream)
     long len; size_t readsize;
     int c_id3, c_ape, c_lyr, c_mm;
     int rc = -1;
-    /* failsafe */
-    long oldlength = stream->fh.length;
-    long oldstart = stream->fh.start;
 
     /* MP3 standard has no metadata format, so everyone invented
      * their own thing, even with extensions, until ID3v2 became
@@ -339,24 +356,21 @@ int mp3_skiptags(snd_stream_t *stream)
      * double tags. -- O.S.
      */
 
-    readsize = FS_fread(buf, 1, 128, &stream->fh);
-    if (!readsize || FS_ferror(&stream->fh)) goto fail;
+    readsize = QFS_ReadFile(stream->fh, buf, 128);
+    if (readsize != 128)
+        goto fail;
 
     /* ID3v2 tag is at the start */
     if (is_id3v2(buf, readsize)) {
         len = get_id3v2_len(buf, (long)readsize);
-        if (len >= stream->fh.length) goto fail;
-        stream->fh.start += len;
-        stream->fh.length -= len;
+        QFS_IgnoreBytes(stream->fh, len, SEEK_SET);
         Con_DPrintf("MP3: skipped %ld bytes ID3v2 tag\n", len);
     }
     /* APE tag _might_ be at the start (discouraged
      * but not forbidden, either.)  read the header. */
     else if (is_apetag(buf, readsize)) {
         len = get_ape_len(buf);
-        if (len >= stream->fh.length) goto fail;
-        stream->fh.start += len;
-        stream->fh.length -= len;
+        QFS_IgnoreBytes(stream->fh, len, SEEK_SET);
         Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
     }
 
@@ -393,13 +407,12 @@ int mp3_skiptags(snd_stream_t *stream)
         break;
     } /* for (;;) */
 
-    rc = (stream->fh.length > 0)? 0 : -1;
-    fail:
-    if (rc < 0) {
-        stream->fh.start = oldstart;
-        stream->fh.length = oldlength;
-    }
-    FS_rewind(&stream->fh);
+    rc = (QFS_FileSize(stream->fh) > 0)? 0 : -1;
+fail:
+	if (rc < 0)
+        QFS_IgnoreBytes(stream->fh, 0, SEEK_CUR);
+
+    QFS_Seek(stream->fh, 0, SEEK_SET);
     return rc;
 }
 #endif /* USE_CODEC_MP3 */
diff --git a/Quake/snd_mpg123.c b/Quake/snd_mpg123.c
index 74508f23d..878b3ba6a 100644
--- a/Quake/snd_mpg123.c
+++ b/Quake/snd_mpg123.c
@@ -42,17 +42,14 @@ typedef struct _mp3_priv_t
 /* CALLBACKS: libmpg123 expects POSIX read/lseek() behavior! */
 static ssize_t mp3_read (void *f, void *buf, size_t size)
 {
-	ssize_t ret = (ssize_t) FS_fread(buf, 1, size, (fshandle_t *)f);
-	if (ret == 0 && errno != 0)
-		return -1;
-	return ret;
+	return (ssize_t) QFS_ReadFile((qfshandle_t*)f,  buf, size);
 }
 static off_t mp3_seek (void *f, off_t offset, int whence)
 {
 	if (f == NULL) return -1;
-	if (FS_fseek((fshandle_t *)f, (long) offset, whence) < 0)
+	if (QFS_Seek((qfshandle_t *)f, (qfileofs_t) offset, whence) < 0)
 		return (off_t)-1;
-	return (off_t) FS_ftell((fshandle_t *)f);
+	return (off_t) QFS_Tell((qfshandle_t *)f);
 }
 
 static qboolean S_MP3_CodecInitialize (void)
@@ -100,7 +97,7 @@ static qboolean S_MP3_CodecOpenStream (snd_stream_t *stream)
 	}
 
 	if (mpg123_replace_reader_handle(priv->handle, mp3_read, mp3_seek, NULL) != MPG123_OK ||
-	    mpg123_open_handle(priv->handle, &stream->fh) != MPG123_OK)
+	    mpg123_open_handle(priv->handle, stream->fh) != MPG123_OK)
 	{
 		Con_Printf("Unable to open mpg123 handle\n");
 		goto _fail;
diff --git a/Quake/snd_opus.c b/Quake/snd_opus.c
index ea5e4f248..8f5f81d84 100644
--- a/Quake/snd_opus.c
+++ b/Quake/snd_opus.c
@@ -41,29 +41,24 @@ static int opc_fclose (void *f)
 
 static int opc_fread (void *f, unsigned char *buf, int size)
 {
-	int ret;
-
 	if (size < 0)
 	{
 		errno = EINVAL;
 		return -1;
 	}
 
-	ret = (int) FS_fread(buf, 1, (size_t)size, (fshandle_t *)f);
-	if (ret == 0 && errno != 0)
-		ret = -1;
-	return ret;
+	return (int) QFS_ReadFile((qfshandle_t*)f, buf, (size_t)size);
 }
 
 static int opc_fseek (void *f, opus_int64 off, int whence)
 {
 	if (f == NULL) return (-1);
-	return FS_fseek((fshandle_t *)f, (long) off, whence);
+	return QFS_Seek((qfshandle_t*)f, (long) off, whence);
 }
 
 static opus_int64 opc_ftell (void *f)
 {
-	return (opus_int64) FS_ftell((fshandle_t *)f);
+	return (opus_int64) QFS_Tell((qfshandle_t *)f);
 }
 
 static const OpusFileCallbacks opc_qfs =
@@ -90,7 +85,7 @@ static qboolean S_OPUS_CodecOpenStream (snd_stream_t *stream)
 	long numstreams;
 	int res;
 
-	opFile = op_open_callbacks(&stream->fh, &opc_qfs, NULL, 0, &res);
+	opFile = op_open_callbacks(stream->fh, &opc_qfs, NULL, 0, &res);
 	if (!opFile)
 	{
 		Con_Printf("%s is not a valid Opus file (error %i).\n",
diff --git a/Quake/snd_umx.c b/Quake/snd_umx.c
index 11bfbcd35..4d1b06c43 100644
--- a/Quake/snd_umx.c
+++ b/Quake/snd_umx.c
@@ -116,13 +116,13 @@ static fci_t get_fci (const char *in, int *pos)
 	return a;
 }
 
-static int get_objtype (fshandle_t *f, int32_t ofs, int type)
+static int get_objtype (qfshandle_t *f, int32_t ofs, int type)
 {
 	char sig[16];
 _retry:
 	memset(sig, 0, sizeof(sig));
-	FS_fseek(f, ofs, SEEK_SET);
-	FS_fread(sig, 16, 1, f);
+	QFS_Seek(f, ofs, SEEK_SET);
+	QFS_ReadFile(f, sig, 16);
 	if (type == UMUSIC_IT) {
 		if (memcmp(sig, "IMPM", 4) == 0)
 			return UMUSIC_IT;
@@ -131,9 +131,11 @@ static int get_objtype (fshandle_t *f, int32_t ofs, int type)
 	if (type == UMUSIC_XM) {
 		if (memcmp(sig, "Extended Module:", 16) != 0)
 			return -1;
-		FS_fread(sig, 16, 1, f);
+		memset(sig, 0, sizeof(sig));
+		QFS_ReadFile(f, sig, 16);
 		if (sig[0] != ' ') return -1;
-		FS_fread(sig, 16, 1, f);
+		memset(sig, 0, sizeof(sig));
+		QFS_ReadFile(f, sig, 16);
 		if (sig[5] != 0x1a) return -1;
 		return UMUSIC_XM;
 	}
@@ -150,8 +152,9 @@ static int get_objtype (fshandle_t *f, int32_t ofs, int type)
 		return -1;
 	}
 
-	FS_fseek(f, ofs + 44, SEEK_SET);
-	FS_fread(sig, 4, 1, f);
+	memset(sig, 0, sizeof(sig));
+	QFS_Seek(f, ofs + 44, SEEK_SET);
+	QFS_ReadFile(f, sig, 4);
 	if (type == UMUSIC_S3M) {
 		if (memcmp(sig, "SCRM", 4) == 0)
 			return UMUSIC_S3M;
@@ -161,9 +164,9 @@ static int get_objtype (fshandle_t *f, int32_t ofs, int type)
 		type = UMUSIC_IT;
 		goto _retry;
 	}
-
-	FS_fseek(f, ofs + 1080, SEEK_SET);
-	FS_fread(sig, 4, 1, f);
+	memset(sig, 0, sizeof(sig));
+	QFS_Seek(f, ofs + 1080, SEEK_SET);
+	QFS_ReadFile(f, sig, 4);
 	if (type == UMUSIC_MOD) {
 		if (memcmp(sig, "M.K.", 4) == 0 || memcmp(sig, "M!K!", 4) == 0)
 			return UMUSIC_MOD;
@@ -173,14 +176,14 @@ static int get_objtype (fshandle_t *f, int32_t ofs, int type)
 	return -1;
 }
 
-static int read_export (fshandle_t *f, const struct upkg_hdr *hdr,
+static int read_export (qfshandle_t *f, const struct upkg_hdr *hdr,
 			int32_t *ofs, int32_t *objsize)
 {
 	char buf[40];
 	int idx = 0, t;
 
-	FS_fseek(f, *ofs, SEEK_SET);
-	if (FS_fread(buf, 4, 10, f) < 10)
+	QFS_Seek(f, *ofs, SEEK_SET);
+	if (QFS_ReadFile(f, buf, sizeof(buf)) < sizeof(buf))
 		return -1;
 
 	if (hdr->file_version < 40) idx += 8;	/* 00 00 00 00 00 00 00 00 */
@@ -194,7 +197,7 @@ static int read_export (fshandle_t *f, const struct upkg_hdr *hdr,
 	return t;	/* return type_name index */
 }
 
-static int read_typname(fshandle_t *f, const struct upkg_hdr *hdr,
+static int read_typname(qfshandle_t *f, const struct upkg_hdr *hdr,
 			int idx, char *out)
 {
 	int i, s;
@@ -202,10 +205,10 @@ static int read_typname(fshandle_t *f, const struct upkg_hdr *hdr,
 	char buf[64];
 
 	if (idx >= hdr->name_count) return -1;
-	memset(buf, 0, 64);
+	memset(buf, 0, sizeof(buf));
 	for (i = 0, l = 0; i <= idx; i++) {
-		if (FS_fseek(f, hdr->name_offset + l, SEEK_SET) < 0) return -1;
-		if (!FS_fread(buf, 1, 63, f)) return -1;
+		if (QFS_Seek(f, hdr->name_offset + l, SEEK_SET) < 0) return -1;
+		if (!QFS_ReadFile(f, buf, 63)) return -1;
 		if (hdr->file_version >= 64) {
 			s = *(signed char *)buf; /* numchars *including* terminator */
 			if (s <= 0) return -1;
@@ -220,16 +223,16 @@ static int read_typname(fshandle_t *f, const struct upkg_hdr *hdr,
 	return 0;
 }
 
-static int probe_umx   (fshandle_t *f, const struct upkg_hdr *hdr,
+static int probe_umx   (qfshandle_t *f, const struct upkg_hdr *hdr,
 			int32_t *ofs, int32_t *objsize)
 {
 	int i, idx, t;
 	int32_t s, pos;
-	long fsiz;
+	qfileofs_t fsiz;
 	char buf[64];
 
 	idx = 0;
-	fsiz = FS_filelength (f);
+	fsiz = QFS_FileSize (f);
 
 	if (hdr->name_offset	>= fsiz ||
 	    hdr->export_offset	>= fsiz ||
@@ -243,9 +246,9 @@ static int probe_umx   (fshandle_t *f, const struct upkg_hdr *hdr,
 	 * have only one export. Kran32.umx from Unreal has two,
 	 * but both pointing to the same music. */
 	if (hdr->export_offset >= fsiz) return -1;
-	memset(buf, 0, 64);
-	FS_fseek(f, hdr->export_offset, SEEK_SET);
-	FS_fread(buf, 1, 64, f);
+	memset(buf, 0, sizeof(buf));
+	QFS_Seek(f, hdr->export_offset, SEEK_SET);
+	QFS_ReadFile(f, buf, sizeof(buf));
 
 	get_fci(&buf[idx], &idx);	/* skip class_index */
 	get_fci(&buf[idx], &idx);	/* skip super_index */
@@ -276,9 +279,9 @@ static int probe_umx   (fshandle_t *f, const struct upkg_hdr *hdr,
 	return t;
 }
 
-static int32_t probe_header (fshandle_t *f, struct upkg_hdr *hdr)
+static int32_t probe_header (qfshandle_t *f, struct upkg_hdr *hdr)
 {
-	if (FS_fread(hdr, 1, UPKG_HDR_SIZE, f) < UPKG_HDR_SIZE)
+	if (QFS_ReadFile(f, hdr, UPKG_HDR_SIZE) < UPKG_HDR_SIZE)
 		return -1;
 	/* byte swap the header - all members are 32 bit LE values */
 	hdr->tag           = (uint32_t) LittleLong(hdr->tag);
@@ -329,7 +332,7 @@ static int32_t probe_header (fshandle_t *f, struct upkg_hdr *hdr)
 #endif /* #if 0  */
 }
 
-static int process_upkg (fshandle_t *f, int32_t *ofs, int32_t *objsize)
+static int process_upkg (qfshandle_t *f, int32_t *ofs, int32_t *objsize)
 {
 	struct upkg_hdr header;
 
@@ -354,18 +357,16 @@ static qboolean S_UMX_CodecOpenStream (snd_stream_t *stream)
 	int type;
 	int32_t ofs = 0, size = 0;
 
-	type = process_upkg(&stream->fh, &ofs, &size);
+	type = process_upkg(stream->fh, &ofs, &size);
 	if (type < 0) {
 		Con_DPrintf("%s: unrecognized umx\n", stream->name);
 		return false;
 	}
 
 	Con_DPrintf("%s: %s data @ 0x%x, %d bytes\n", stream->name, mustype[type], ofs, size);
-	/* hack the fshandle_t start pos and length members so
-	 * that only the relevant data is accessed from now on */
-	stream->fh.start += ofs;
-	stream->fh.length = size;
-	FS_fseek(&stream->fh, 0, SEEK_SET);
+	QFS_IgnoreBytes(stream->fh, (qfileofs_t)ofs, SEEK_SET);
+	QFS_IgnoreBytes(stream->fh, QFS_FileSize(stream->fh) - size, SEEK_END);
+	QFS_Seek(stream->fh, 0, SEEK_SET);
 
 	switch (type) {
 	case UMUSIC_IT:
diff --git a/Quake/snd_vorbis.c b/Quake/snd_vorbis.c
index 7ea02bd9a..568aeacb4 100644
--- a/Quake/snd_vorbis.c
+++ b/Quake/snd_vorbis.c
@@ -50,18 +50,28 @@ static int ovc_fclose (void *f)
 	return 0;		/* we fclose() elsewhere. */
 }
 
+static size_t ovc_fread(void* buf, size_t size, size_t nmemb, void* f)
+{
+	return QFS_ReadFile((qfshandle_t*)f, buf, size * nmemb);
+}
+
 static int ovc_fseek (void *f, ogg_int64_t off, int whence)
 {
 	if (f == NULL) return (-1);
-	return FS_fseek((fshandle_t *)f, (long) off, whence);
+	return (int)QFS_Seek((qfshandle_t *)f, (qfileofs_t) off, whence);
+}
+
+static long ovc_ftell(void* f)
+{
+	return (long)QFS_Tell((qfshandle_t*)f);
 }
 
 static ov_callbacks ovc_qfs =
 {
-	(size_t (*)(void *, size_t, size_t, void *))	FS_fread,
-	(int (*)(void *, ogg_int64_t, int))		ovc_fseek,
-	(int (*)(void *))				ovc_fclose,
-	(long (*)(void *))				FS_ftell
+	&ovc_fread,
+	&ovc_fseek,
+	&ovc_fclose,
+	&ovc_ftell
 };
 
 static qboolean S_VORBIS_CodecInitialize (void)
@@ -82,7 +92,7 @@ static qboolean S_VORBIS_CodecOpenStream (snd_stream_t *stream)
 
 	ovFile = (OggVorbis_File *) Z_Malloc(sizeof(OggVorbis_File));
 	stream->priv = ovFile;
-	res = ov_open_callbacks(&stream->fh, ovFile, NULL, 0, ovc_qfs);
+	res = ov_open_callbacks(stream->fh, ovFile, NULL, 0, ovc_qfs);
 	if (res != 0)
 	{
 		Con_Printf("%s is not a valid Ogg Vorbis file (error %i).\n",
diff --git a/Quake/snd_wave.c b/Quake/snd_wave.c
index b494b6af9..ab8c690f1 100644
--- a/Quake/snd_wave.c
+++ b/Quake/snd_wave.c
@@ -34,10 +34,10 @@
 FGetLittleLong
 =================
 */
-static int FGetLittleLong (FILE *f, qboolean *ok)
+static int32_t FGetLittleLong (qfshandle_t *f, qboolean *ok)
 {
-	int		v;
-	*ok &= fread(&v, sizeof(v), 1, f) == 1;
+	int32_t v;
+	*ok &= QFS_ReadFile(f, &v, sizeof(v)) == sizeof(v);
 	return LittleLong(v);
 }
 
@@ -46,10 +46,10 @@ static int FGetLittleLong (FILE *f, qboolean *ok)
 FGetLittleShort
 =================
 */
-static short FGetLittleShort(FILE *f, qboolean *ok)
+static short FGetLittleShort(qfshandle_t *f, qboolean *ok)
 {
-	short	v;
-	*ok &= fread(&v, sizeof(v), 1, f) == 1;
+	int16_t v;
+	*ok &= QFS_ReadFile(f, &v, sizeof(v)) == sizeof(v);
 	return LittleShort(v);
 }
 
@@ -58,14 +58,14 @@ static short FGetLittleShort(FILE *f, qboolean *ok)
 WAV_ReadChunkInfo
 =================
 */
-static int WAV_ReadChunkInfo(FILE *f, char *name)
+static int WAV_ReadChunkInfo(qfshandle_t *f, char *name)
 {
 	int len, r;
 	qboolean ok = true;
 
 	name[4] = 0;
 
-	r = fread(name, 1, 4, f);
+	r = QFS_ReadFile(f, name, 4);
 	if (r != 4)
 		return -1;
 
@@ -92,7 +92,7 @@ WAV_FindRIFFChunk
 Returns the length of the data in the chunk, or -1 if not found
 =================
 */
-static int WAV_FindRIFFChunk(FILE *f, const char *chunk)
+static int WAV_FindRIFFChunk(qfshandle_t *f, const char *chunk)
 {
 	char	name[5];
 	int		len;
@@ -105,7 +105,7 @@ static int WAV_FindRIFFChunk(FILE *f, const char *chunk)
 		len = ((len + 1) & ~1);	/* pad by 2 . */
 
 		/* Not the right chunk - skip it */
-		fseek(f, len, SEEK_CUR);
+		QFS_Seek(f, len, SEEK_CUR);
 	}
 
 	return -1;
@@ -116,14 +116,14 @@ static int WAV_FindRIFFChunk(FILE *f, const char *chunk)
 WAV_ReadRIFFHeader
 =================
 */
-static qboolean WAV_ReadRIFFHeader(const char *name, FILE *file, snd_info_t *info)
+static qboolean WAV_ReadRIFFHeader(const char *name, qfshandle_t *file, snd_info_t *info)
 {
 	char dump[16];
 	int wav_format;
 	int fmtlen = 0;
 	qboolean ok = true;
 
-	if (fread(dump, 1, 12, file) < 12 ||
+	if (QFS_ReadFile(file, dump, 12) < 12 ||
 	    strncmp(dump, "RIFF", 4) != 0 ||
 	    strncmp(&dump[8], "WAVE", 4) != 0)
 	{
@@ -170,7 +170,7 @@ static qboolean WAV_ReadRIFFHeader(const char *name, FILE *file, snd_info_t *inf
 	if (fmtlen > 16)
 	{
 		fmtlen -= 16;
-		fseek(file, fmtlen, SEEK_CUR);
+		QFS_Seek(file, fmtlen, SEEK_CUR);
 	}
 
 	/* Scan for the data chunk */
@@ -203,17 +203,12 @@ S_WAV_CodecOpenStream
 */
 static qboolean S_WAV_CodecOpenStream(snd_stream_t *stream)
 {
-	long start = stream->fh.start;
-
 	/* Read the RIFF header */
-	/* The file reads are sequential, therefore no need
-	 * for the FS_*() functions: We will manipulate the
-	 * file by ourselves from now on. */
-	if (!WAV_ReadRIFFHeader(stream->name, stream->fh.file, &stream->info))
+
+	if (!WAV_ReadRIFFHeader(stream->name, stream->fh, &stream->info))
 		return false;
 
-	stream->fh.start = ftell(stream->fh.file); /* reset to data position */
-	if (stream->fh.start - start + stream->info.size > stream->fh.length)
+	if (QFS_Tell(stream->fh) + stream->info.size > QFS_FileSize(stream->fh))
 	{
 		Con_Printf("%s data size mismatch\n", stream->name);
 		return false;
@@ -229,15 +224,15 @@ S_WAV_CodecReadStream
 */
 int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer)
 {
-	int remaining = stream->info.size - stream->fh.pos;
+	int remaining = stream->info.size - (int)QFS_Tell(stream->fh);
 	int i, samples;
 
 	if (remaining <= 0)
 		return 0;
 	if (bytes > remaining)
 		bytes = remaining;
-	stream->fh.pos += bytes;
-	if (fread(buffer, 1, bytes, stream->fh.file) != bytes)
+
+	if (QFS_ReadFile(stream->fh, buffer, bytes) != (qfileofs_t)bytes)
 		Sys_Error ("S_WAV_CodecReadStream: read error on %d bytes (%s)", bytes, stream->name);
 	if (stream->info.width == 2)
 	{
@@ -255,7 +250,7 @@ static void S_WAV_CodecCloseStream (snd_stream_t *stream)
 
 static int S_WAV_CodecRewindStream (snd_stream_t *stream)
 {
-	FS_rewind(&stream->fh);
+	QFS_Seek(stream->fh, 0, SEEK_SET);
 	return 0;
 }
 
diff --git a/Quake/snd_xmp.c b/Quake/snd_xmp.c
index 3e9fd9b4a..5ce170121 100644
--- a/Quake/snd_xmp.c
+++ b/Quake/snd_xmp.c
@@ -1,4 +1,4 @@
-/* tracker music (module file) decoding support using libxmp >= v4.2.0
+/* tracker music (module file) decoding support using libxmp >= v4.5.0
  * https://sourceforge.net/projects/xmp/
  * https://github.com/libxmp/libxmp.git
  *
@@ -30,8 +30,8 @@
 #define BUILDING_STATIC
 #endif
 #include <xmp.h>
-#if ((XMP_VERCODE+0) < 0x040200)
-#error libxmp version 4.2 or newer is required
+#if ((XMP_VERCODE+0) < 0x040500)
+#error libxmp version 4.5 or newer is required
 #endif
 
 static qboolean S_XMP_CodecInitialize (void)
@@ -43,62 +43,38 @@ static void S_XMP_CodecShutdown (void)
 {
 }
 
-#if (XMP_VERCODE >= 0x040500)
 static unsigned long xmp_fread(void *dest, unsigned long len, unsigned long nmemb, void *f)
 {
-	return FS_fread(dest, len, nmemb, (fshandle_t *)f);
+	return (unsigned long)QFS_ReadFile((qfshandle_t*)f, dest, (size_t)len);
 }
 static int xmp_fseek(void *f, long offset, int whence)
 {
-	return FS_fseek((fshandle_t *)f, offset, whence);
+	return QFS_Seek((qfshandle_t*)f, (qfileofs_t)offset, whence);
 }
 static long xmp_ftell(void *f)
 {
-	return FS_ftell((fshandle_t *)f);
+	return (long)QFS_Tell((qfshandle_t*)f);
 }
-#endif
+
 
 static qboolean S_XMP_CodecOpenStream (snd_stream_t *stream)
 {
-/* need to load the whole file into memory and pass it to libxmp
- * using xmp_load_module_from_memory() which requires libxmp >= 4.2.
- * libxmp-4.0/4.1 only have xmp_load_module() which accepts a file
- * name which isn't good with files in containers like paks, etc.
- * On the other hand, libxmp >= 4.5 introduces file callbacks: use
- * if available. */
+/* libxmp >= 4.5 introduces file callbacks, we now require this feature. */
 	xmp_context c;
-#if (XMP_VERCODE >= 0x040500)
 	struct xmp_callbacks file_callbacks = {
 		xmp_fread, xmp_fseek, xmp_ftell, NULL
 	};
-#else
-	byte *moddata;
-	long len;
-	int mark;
-#endif
+
 	int fmt;
 
 	c = xmp_create_context();
 	if (c == NULL)
 		return false;
 
-#if (XMP_VERCODE >= 0x040500)
-	if (xmp_load_module_from_callbacks(c, &stream->fh, file_callbacks) < 0) {
+	if (xmp_load_module_from_callbacks(c, stream->fh, file_callbacks) < 0) {
 		Con_DPrintf("Could not load module %s\n", stream->name);
 		goto err1;
 	}
-#else
-	len = FS_filelength (&stream->fh);
-	mark = Hunk_LowMark();
-	moddata = (byte *) Hunk_Alloc(len);
-	FS_fread(moddata, 1, len, &stream->fh);
-	if (xmp_load_module_from_memory(c, moddata, len) < 0) {
-		Hunk_FreeToLowMark(mark);
-		Con_DPrintf("Could not load module %s\n", stream->name);
-		goto err1;
-	}
-	Hunk_FreeToLowMark(mark); /* free original file data */
-#endif
 
 	stream->priv = c;
 	if (shm->speed > XMP_MAX_SRATE)
diff --git a/Quake/sv_main.c b/Quake/sv_main.c
index 89631c5ec..eece4689e 100644
--- a/Quake/sv_main.c
+++ b/Quake/sv_main.c
@@ -1784,7 +1784,7 @@ static void SV_PrintMapChecklist (void)
 	{
 		char pointfile[MAX_OSPATH];
 		q_snprintf (pointfile, sizeof (pointfile), "maps/%s.pts", sv.name);
-		if (COM_FileExists (pointfile, NULL))
+		if (QFS_FileExists (pointfile, NULL))
 			SV_PrintMapCheck (MAPCHECK_FAILED, "vis data (unsealed map?)");
 		else
 			SV_PrintMapCheck (MAPCHECK_FAILED, "vis data");
diff --git a/Quake/sys.h b/Quake/sys.h
index 57c9791ab..dfcb1b0e2 100644
--- a/Quake/sys.h
+++ b/Quake/sys.h
@@ -72,23 +72,13 @@ qboolean Sys_Explore (const char *path);
 
 typedef int64_t qfileofs_t;
 
-// returns the file size or -1 if file is not present.
-// the file should be in BINARY mode for stupid OSs that care
-qfileofs_t Sys_FileOpenRead (const char *path, int *hndl);
-
-// Returns a file handle
-int Sys_FileOpenWrite (const char *path);
-
-void Sys_FileClose (int handle);
-void Sys_FileSeek (int handle, int position);
-int Sys_FileRead (int handle, void *dest, int count);
-int Sys_FileWrite (int handle,const void *data, int count);
 qboolean Sys_FileExists (const char *path);
 qboolean Sys_GetFileTime (const char *path, time_t *out);
 void Sys_mkdir (const char *path);
 FILE *Sys_fopen (const char *path, const char *mode);
 int Sys_fseek (FILE *file, qfileofs_t ofs, int origin);
 qfileofs_t Sys_ftell (FILE *file);
+qfileofs_t Sys_filelength (FILE *f);
 int Sys_remove (const char *path);
 int Sys_rename (const char *oldname, const char *newname);
 
diff --git a/Quake/sys_sdl_unix.c b/Quake/sys_sdl_unix.c
index 15fed2f9b..96fc769f9 100644
--- a/Quake/sys_sdl_unix.c
+++ b/Quake/sys_sdl_unix.c
@@ -53,25 +53,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 qboolean		isDedicated;
 
-#define	MAX_HANDLES		32	/* johnfitz -- was 10 */
-static FILE		*sys_handles[MAX_HANDLES];
 static qboolean		stdinIsATTY;	/* from ioquake3 source */
 
 static double rcp_counter_freq;
 
-static int findhandle (void)
-{
-	int i;
-
-	for (i = 1; i < MAX_HANDLES; i++)
-	{
-		if (!sys_handles[i])
-			return i;
-	}
-	Sys_Error ("out of handles");
-	return -1;
-}
-
 FILE *Sys_fopen (const char *path, const char *mode)
 {
 	if (strchr (mode, 'w'))
@@ -134,66 +119,6 @@ qfileofs_t Sys_filelength (FILE *f)
 	return end;
 }
 
-qfileofs_t Sys_FileOpenRead (const char *path, int *hndl)
-{
-	FILE		*f;
-	int			i;
-	qfileofs_t	retval;
-
-	i = findhandle ();
-	f = fopen(path, "rb");
-
-	if (!f)
-	{
-		*hndl = -1;
-		retval = -1;
-	}
-	else
-	{
-		sys_handles[i] = f;
-		*hndl = i;
-		retval = Sys_filelength(f);
-	}
-
-	return retval;
-}
-
-int Sys_FileOpenWrite (const char *path)
-{
-	FILE	*f;
-	int		i;
-
-	i = findhandle ();
-	f = Sys_fopen(path, "wb");
-
-	if (!f)
-		Sys_Error ("Error opening %s: %s", path, strerror(errno));
-
-	sys_handles[i] = f;
-	return i;
-}
-
-void Sys_FileClose (int handle)
-{
-	fclose (sys_handles[handle]);
-	sys_handles[handle] = NULL;
-}
-
-void Sys_FileSeek (int handle, int position)
-{
-	fseek (sys_handles[handle], position, SEEK_SET);
-}
-
-int Sys_FileRead (int handle, void *dest, int count)
-{
-	return fread (dest, 1, count, sys_handles[handle]);
-}
-
-int Sys_FileWrite (int handle, const void *data, int count)
-{
-	return fwrite (data, 1, count, sys_handles[handle]);
-}
-
 qboolean Sys_FileExists (const char *path)
 {
 	return access (path, F_OK) == 0;
diff --git a/Quake/sys_sdl_win.c b/Quake/sys_sdl_win.c
index 5474a8dc8..e60f450f2 100644
--- a/Quake/sys_sdl_win.c
+++ b/Quake/sys_sdl_win.c
@@ -68,24 +68,8 @@ qboolean		isDedicated;
 
 static HANDLE		hinput, houtput;
 
-#define	MAX_HANDLES		32	/* johnfitz -- was 10 */
-static FILE		*sys_handles[MAX_HANDLES];
-
 static double rcp_counter_freq;
 
-static int findhandle (void)
-{
-	int i;
-
-	for (i = 1; i < MAX_HANDLES; i++)
-	{
-		if (!sys_handles[i])
-			return i;
-	}
-	Sys_Error ("out of handles");
-	return -1;
-}
-
 static void UTF8ToWideString (const char *src, wchar_t *dst, size_t maxchars)
 {
 	if (!MultiByteToWideChar (CP_UTF8, 0, src, -1, dst, maxchars))
@@ -187,66 +171,6 @@ qfileofs_t Sys_filelength (FILE *f)
 	return end;
 }
 
-qfileofs_t Sys_FileOpenRead (const char *path, int *hndl)
-{
-	FILE		*f;
-	int			i;
-	qfileofs_t	retval;
-
-	i = findhandle ();
-	f = Sys_fopen (path, "rb");
-
-	if (!f)
-	{
-		*hndl = -1;
-		retval = -1;
-	}
-	else
-	{
-		sys_handles[i] = f;
-		*hndl = i;
-		retval = Sys_filelength(f);
-	}
-
-	return retval;
-}
-
-int Sys_FileOpenWrite (const char *path)
-{
-	FILE	*f;
-	int		i;
-
-	i = findhandle ();
-	f = Sys_fopen (path, "wb");
-
-	if (!f)
-		Sys_Error ("Error opening %s: %s", path, strerror(errno));
-
-	sys_handles[i] = f;
-	return i;
-}
-
-void Sys_FileClose (int handle)
-{
-	fclose (sys_handles[handle]);
-	sys_handles[handle] = NULL;
-}
-
-void Sys_FileSeek (int handle, int position)
-{
-	fseek (sys_handles[handle], position, SEEK_SET);
-}
-
-int Sys_FileRead (int handle, void *dest, int count)
-{
-	return fread (dest, 1, count, sys_handles[handle]);
-}
-
-int Sys_FileWrite (int handle, const void *data, int count)
-{
-	return fwrite (data, 1, count, sys_handles[handle]);
-}
-
 #ifndef INVALID_FILE_ATTRIBUTES
 #define INVALID_FILE_ATTRIBUTES	((DWORD)-1)
 #endif
diff --git a/Quake/wad.c b/Quake/wad.c
index cf0144215..678af35a2 100644
--- a/Quake/wad.c
+++ b/Quake/wad.c
@@ -77,7 +77,7 @@ void W_LoadWadFile (void) //johnfitz -- filename is now hard-coded for honesty
 	//TODO: use cache_alloc
 	if (wad_base)
 		free (wad_base);
-	wad_base = COM_LoadMallocFile (filename, NULL);
+	wad_base = QFS_LoadMallocFile (filename, NULL, NULL);
 	if (!wad_base)
 		Sys_Error ("W_LoadWadFile: couldn't load %s\n\n"
 			   "Basedir is: %s\n\n"
diff --git a/Windows/VisualStudio/ironwail.vcxproj b/Windows/VisualStudio/ironwail.vcxproj
index 21dd6ec89..0f90a93bf 100644
--- a/Windows/VisualStudio/ironwail.vcxproj
+++ b/Windows/VisualStudio/ironwail.vcxproj
@@ -237,6 +237,7 @@ copy "$(SolutionDir)..\zlib\$(PlatformShortName)\*.dll" "$(TargetDir)"</Command>
     <ClCompile Include="..\..\Quake\console.c" />
     <ClCompile Include="..\..\Quake\crc.c" />
     <ClCompile Include="..\..\Quake\cvar.c" />
+    <ClCompile Include="..\..\Quake\filesys.c" />
     <ClCompile Include="..\..\Quake\gl_draw.c" />
     <ClCompile Include="..\..\Quake\gl_fog.c" />
     <ClCompile Include="..\..\Quake\gl_mesh.c" />
@@ -363,6 +364,7 @@ copy "$(SolutionDir)..\zlib\$(PlatformShortName)\*.dll" "$(TargetDir)"</Command>
     <ClInclude Include="..\..\Quake\crc.h" />
     <ClInclude Include="..\..\Quake\cvar.h" />
     <ClInclude Include="..\..\Quake\draw.h" />
+    <ClInclude Include="..\..\Quake\filesys.h" />
     <ClInclude Include="..\..\Quake\glquake.h" />
     <ClInclude Include="..\..\Quake\gl_model.h" />
     <ClInclude Include="..\..\Quake\gl_shaders.h" />
diff --git a/Windows/VisualStudio/ironwail.vcxproj.filters b/Windows/VisualStudio/ironwail.vcxproj.filters
index d71c1415f..b02b223b9 100644
--- a/Windows/VisualStudio/ironwail.vcxproj.filters
+++ b/Windows/VisualStudio/ironwail.vcxproj.filters
@@ -267,6 +267,9 @@
     <ClCompile Include="..\..\Quake\cd_null.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\Quake\filesys.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\Quake\anorm_dots.h">
@@ -479,6 +482,9 @@
     <ClInclude Include="..\..\Quake\jsmn.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\Quake\filesys.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\QuakeSpasm.rc">