Index: fpsgame/client.cpp
===================================================================
--- fpsgame/client.cpp	(revision 918)
+++ fpsgame/client.cpp	(working copy)
@@ -76,9 +76,9 @@
 
     void sendmapinfo() { if(!spectator || player1->privilege || !remote) senditemstoserver = true; }
 
-    void writeclientinfo(FILE *f)
+    void writeclientinfo(stream *f)
     {
-        fprintf(f, "name \"%s\"\nteam \"%s\"\n", player1->name, player1->team);
+        f->printf("name \"%s\"\nteam \"%s\"\n", player1->name, player1->team);
     }
 
     void connectattempt(const char *name, const char *password, const ENetAddress &address)
@@ -1265,11 +1265,11 @@
             case SV_SENDDEMO:
             {
                 s_sprintfd(fname)("%d.dmo", lastmillis);
-                FILE *demo = openfile(fname, "wb");
+                stream *demo = openrawfile(fname, "wb");
                 if(!demo) return;
                 conoutf("received demo \"%s\"", fname);
-                fwrite(data, 1, len, demo);
-                fclose(demo);
+                demo->write(data, len);
+                delete demo;
                 break;
             }
 
@@ -1280,14 +1280,13 @@
                 s_strcpy(oldname, getclientmap());
                 s_sprintfd(mname)("getmap_%d", lastmillis);
                 s_sprintfd(fname)("packages/base/%s.ogz", mname);
-                const char *file = findfile(fname, "wb");
-                FILE *map = fopen(file, "wb");
+                stream *map = openrawfile(fname, "wb");
                 if(!map) return;
                 conoutf("received map");
-                fwrite(data, 1, len, map);
-                fclose(map);
+                map->write(data, len);
+                delete map;
                 load_world(mname, oldname[0] ? oldname : NULL);
-                remove(file);
+                remove(findfile(fname, "rb"));
                 break;
             }
         }
@@ -1366,19 +1365,17 @@
         s_sprintfd(mname)("sendmap_%d", lastmillis);
         save_world(mname, true);
         s_sprintfd(fname)("packages/base/%s.ogz", mname);
-        const char *file = findfile(fname, "rb");
-        FILE *map = fopen(file, "rb");
+        stream *map = openrawfile(fname, "rb");
         if(map)
         {
-            fseek(map, 0, SEEK_END);
-            int len = ftell(map);
+            int len = map->size();
             if(len > 1024*1024) conoutf(CON_ERROR, "map is too large");
-            else if(!len) conoutf(CON_ERROR, "could not read map");
+            else if(len <= 0) conoutf(CON_ERROR, "could not read map");
             else sendfile(-1, 2, map);
-            fclose(map);
+            delete map;
         }
         else conoutf(CON_ERROR, "could not read map");
-        remove(file);
+        remove(findfile(fname, "rb"));
     }
     COMMAND(sendmap, "");
 
Index: fpsgame/server.cpp
===================================================================
--- fpsgame/server.cpp	(revision 918)
+++ fpsgame/server.cpp	(working copy)
@@ -280,7 +280,7 @@
     int currentmaster = -1;
     bool masterupdate = false;
     string masterpass = "";
-    FILE *mapdata = NULL;
+    stream *mapdata = NULL;
 
     vector<uint> allowedips;
     vector<ban> bannedips;
@@ -299,8 +299,7 @@
     vector<demofile> demos;
 
     bool demonextmatch = false;
-    FILE *demotmp = NULL;
-    gzFile demorecord = NULL, demoplayback = NULL;
+    stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL;
     int nextplayback = 0, demomillis = 0;
 
     struct servmode
@@ -564,9 +563,9 @@
     {
         if(!demorecord) return;
         int stamp[3] = { gamemillis, chan, len };
-        endianswap(stamp, sizeof(int), 3);
-        gzwrite(demorecord, stamp, sizeof(stamp));
-        gzwrite(demorecord, data, len);
+        lilswap(stamp, 3);
+        demorecord->write(stamp, sizeof(stamp));
+        demorecord->write(data, len);
     }
 
     void recordpacket(int chan, void *data, int len)
@@ -578,17 +577,11 @@
     {
         if(!demorecord) return;
 
-        gzclose(demorecord);
-        demorecord = NULL;
+        DELETEP(demorecord);
 
-#ifdef WIN32
-        demotmp = fopen("demorecord", "rb");
-#endif    
         if(!demotmp) return;
 
-        fseek(demotmp, 0, SEEK_END);
-        int len = ftell(demotmp);
-        rewind(demotmp);
+        int len = demotmp->size();
         if(demos.length()>=MAXDEMOS)
         {
             delete[] demos[0].data;
@@ -603,9 +596,9 @@
         sendservmsg(msg);
         d.data = new uchar[len];
         d.len = len;
-        fread(d.data, 1, len, demotmp);
-        fclose(demotmp);
-        demotmp = NULL;
+        demotmp->seek(0, SEEK_SET);
+        demotmp->read(d.data, len);
+        DELETEP(demotmp);
     }
 
     int welcomepacket(ucharbuf &p, clientinfo *ci, ENetPacket *packet);
@@ -615,22 +608,11 @@
     {
         if(!m_mp(gamemode) || m_edit) return;
 
-#ifdef WIN32
-        gzFile f = gzopen("demorecord", "wb9");
-        if(!f) return;
-#else
-        demotmp = tmpfile();
+        demotmp = opentempfile("w+b");
         if(!demotmp) return;
-        setvbuf(demotmp, NULL, _IONBF, 0);
 
-        gzFile f = gzdopen(_dup(_fileno(demotmp)), "wb9");
-        if(!f)
-        {
-            fclose(demotmp);
-            demotmp = NULL;
-            return;
-        }
-#endif
+        stream *f = opengzfile(NULL, "wb", demotmp);
+        if(!f) { DELETEP(demotmp); return; } 
 
         sendservmsg("recording demo");
 
@@ -640,9 +622,8 @@
         memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic));
         hdr.version = DEMO_VERSION;
         hdr.protocol = PROTOCOL_VERSION;
-        endianswap(&hdr.version, sizeof(int), 1);
-        endianswap(&hdr.protocol, sizeof(int), 1);
-        gzwrite(demorecord, &hdr, sizeof(demoheader));
+        lilswap(&hdr.version, 2);
+        demorecord->write(&hdr, sizeof(demoheader));
 
         ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
         ucharbuf p(packet->data, packet->dataLength);
@@ -692,8 +673,7 @@
     void enddemoplayback()
     {
         if(!demoplayback) return;
-        gzclose(demoplayback);
-        demoplayback = NULL;
+        DELETEP(demoplayback);
 
         loopv(clients) sendf(clients[i]->clientnum, 1, "ri3", SV_DEMOPLAYBACK, 0, clients[i]->clientnum);
 
@@ -709,20 +689,19 @@
         string msg;
         msg[0] = '\0';
         s_sprintfd(file)("%s.dmo", smapname);
-        demoplayback = opengzfile(file, "rb9");
+        demoplayback = opengzfile(file, "rb");
         if(!demoplayback) s_sprintf(msg)("could not read demo \"%s\"", file);
-        else if(gzread(demoplayback, &hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
+        else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
             s_sprintf(msg)("\"%s\" is not a demo file", file);
         else 
         { 
-            endianswap(&hdr.version, sizeof(int), 1);
-            endianswap(&hdr.protocol, sizeof(int), 1);
+            lilswap(&hdr.version, 2);
             if(hdr.version!=DEMO_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.version<DEMO_VERSION ? "older" : "newer");
             else if(hdr.protocol!=PROTOCOL_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer");
         }
         if(msg[0])
         {
-            if(demoplayback) { gzclose(demoplayback); demoplayback = NULL; }
+            DELETEP(demoplayback);
             sendservmsg(msg);
             return;
         }
@@ -733,12 +712,12 @@
         demomillis = 0;
         sendf(-1, 1, "ri3", SV_DEMOPLAYBACK, 1, -1);
 
-        if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
+        if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
         {
             enddemoplayback();
             return;
         }
-        endianswap(&nextplayback, sizeof(nextplayback), 1);
+        lilswap(&nextplayback, 1);
     }
 
     void readdemo()
@@ -748,16 +727,16 @@
         while(demomillis>=nextplayback)
         {
             int chan, len;
-            if(gzread(demoplayback, &chan, sizeof(chan))!=sizeof(chan) ||
-               gzread(demoplayback, &len, sizeof(len))!=sizeof(len))
+            if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) ||
+               demoplayback->read(&len, sizeof(len))!=sizeof(len))
             {
                 enddemoplayback();
                 return;
             }
-            endianswap(&chan, sizeof(chan), 1);
-            endianswap(&len, sizeof(len), 1);
+            lilswap(&chan, 1);
+            lilswap(&len, 1);
             ENetPacket *packet = enet_packet_create(NULL, len, 0);
-            if(!packet || gzread(demoplayback, packet->data, len)!=len)
+            if(!packet || demoplayback->read(packet->data, len)!=len)
             {
                 if(packet) enet_packet_destroy(packet);
                 enddemoplayback();
@@ -765,12 +744,12 @@
             }
             sendpacket(-1, chan, packet);
             if(!packet->referenceCount) enet_packet_destroy(packet);
-            if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
+            if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
             {
                 enddemoplayback();
                 return;
             }
-            endianswap(&nextplayback, sizeof(nextplayback), 1);
+            lilswap(&nextplayback, 1);
         }
     }
 
@@ -1710,11 +1689,11 @@
         if(!m_edit || len > 1024*1024) return;
         clientinfo *ci = (clientinfo *)getinfo(sender);
         if(ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) return;
-        if(mapdata) { fclose(mapdata); mapdata = NULL; }
+        if(mapdata) DELETEP(mapdata);
         if(!len) return;
-        mapdata = tmpfile();
+        mapdata = opentempfile("w+b");
         if(!mapdata) { sendf(sender, 1, "ris", SV_SERVMSG, "failed to open temporary file for map"); return; }
-        fwrite(data, 1, len, mapdata);
+        mapdata->write(data, len);
         s_sprintfd(msg)("[%s uploaded map to server, \"/getmap\" to receive it]", colorname(ci));
         sendservmsg(msg);
     }
Index: engine/textedit.h
===================================================================
--- engine/textedit.h	(revision 918)
+++ engine/textedit.h	(working copy)
@@ -71,11 +71,11 @@
         len += slen;
     }
 
-    bool read(FILE *f, int chop = -1)
+    bool read(stream *f, int chop = -1)
     {
         if(chop < 0) chop = INT_MAX; else chop++;
         set("");
-        while(len + 1 < chop && fgets(&text[len], min(maxlen, chop) - len, f))
+        while(len + 1 < chop && f->getline(&text[len], min(maxlen, chop) - len))
         {
             len += strlen(&text[len]);
             if(len > 0 && text[len-1] == '\n')
@@ -88,7 +88,7 @@
         if(len + 1 >= chop)
         {
             char buf[CHUNKSIZE];
-            while(fgets(buf, sizeof(buf), f))
+            while(f->getline(buf, sizeof(buf)))
             {
                 int blen = strlen(buf);
                 if(blen > 0 && buf[blen-1] == '\n') return true;
@@ -190,12 +190,12 @@
     {
         if(!filename) return;
         clear(NULL);
-        FILE *file = openfile(filename, "r");
+        stream *file = openfile(filename, "r");
         if(file) 
         {
             while(lines.add().read(file, maxx) && (maxy < 0 || lines.length() <= maxy));
             lines.pop().clear();
-            fclose(file);
+            delete file;
         }
         if(lines.empty()) lines.add().set("");
     }
@@ -203,10 +203,10 @@
     void save()
     {
         if(!filename) return;
-        FILE *file = openfile(filename, "w");
+        stream *file = openfile(filename, "w");
         if(!file) return;
-        loopv(lines) fprintf(file, "%s\n", lines[i].text);
-        fclose(file);
+        loopv(lines) file->putline(lines[i].text);
+        delete file;
     }
    
     void mark(bool enable) 
Index: engine/rendergl.cpp
===================================================================
--- engine/rendergl.cpp	(revision 918)
+++ engine/rendergl.cpp	(working copy)
@@ -1645,11 +1645,11 @@
 
 COMMANDN(loadcrosshair, loadcrosshair_, "si");
 
-void writecrosshairs(FILE *f)
+void writecrosshairs(stream *f)
 {
     loopi(MAXCROSSHAIRS) if(crosshairs[i] && crosshairs[i]!=notexture)
-        fprintf(f, "loadcrosshair \"%s\" %d\n", crosshairs[i]->name, i);
-    fprintf(f, "\n");
+        f->printf("loadcrosshair \"%s\" %d\n", crosshairs[i]->name, i);
+    f->printf("\n");
 }
 
 void drawcrosshair(int w, int h)
Index: engine/server.cpp
===================================================================
--- engine/server.cpp	(revision 918)
+++ engine/server.cpp	(working copy)
@@ -102,7 +102,7 @@
 
 void putfloat(ucharbuf &p, float f)
 {
-    endianswap(&f, sizeof(float), 1);
+    lilswap(&f, 1);
     p.put((uchar *)&f, sizeof(float));
 }
 
@@ -110,8 +110,7 @@
 {
     float f;
     p.get((uchar *)&f, sizeof(float));
-    endianswap(&f, sizeof(float), 1);
-    return f;
+    return lilswap(f);
 }
 
 void getstring(char *text, ucharbuf &p, int len)
@@ -254,7 +253,7 @@
     if(packet->referenceCount==0) enet_packet_destroy(packet);
 }
 
-void sendfile(int cn, int chan, FILE *file, const char *format, ...)
+void sendfile(int cn, int chan, stream *file, const char *format, ...)
 {
     if(cn < 0)
     {
@@ -264,12 +263,12 @@
     }
     else if(!clients.inrange(cn)) return;
 
-    fseek(file, 0, SEEK_END);
-    int len = ftell(file);
+    int len = file->size();
+    if(len <= 0) return;
+
     bool reliable = false;
     if(*format=='r') { reliable = true; ++format; }
     ENetPacket *packet = enet_packet_create(NULL, MAXTRANS+len, ENET_PACKET_FLAG_RELIABLE);
-    rewind(file);
 
     ucharbuf p(packet->data, packet->dataLength);
     va_list args;
@@ -288,7 +287,8 @@
     va_end(args);
     enet_packet_resize(packet, p.length()+len);
 
-    fread(&packet->data[p.length()], 1, len, file);
+    file->seek(0, SEEK_SET);
+    file->read(&packet->data[p.length()], len);
     enet_packet_resize(packet, p.length()+len);
 
     if(cn >= 0)
Index: engine/pvs.cpp
===================================================================
--- engine/pvs.cpp	(revision 918)
+++ engine/pvs.cpp	(working copy)
@@ -1218,91 +1218,63 @@
     return false;
 }
 
-void saveviewcells(gzFile f, viewcellnode &p)
+void saveviewcells(stream *f, viewcellnode &p)
 {
-    gzputc(f, p.leafmask);
+    f->putchar(p.leafmask);
     loopi(8)
     {
-        if(p.leafmask&(1<<i))
-        {
-            int pvsindex = p.children[i].pvs;
-            endianswap(&pvsindex, sizeof(int), 1);
-            gzwrite(f, &pvsindex, sizeof(int));
-        }
+        if(p.leafmask&(1<<i)) f->putlil<int>(p.children[i].pvs);
         else saveviewcells(f, *p.children[i].node);
     }
 }
 
-void savepvs(gzFile f)
+void savepvs(stream *f)
 {
     uint totallen = pvsbuf.length() | (numwaterplanes>0 ? 0x80000000U : 0);
-    endianswap(&totallen, sizeof(uint), 1);
-    gzwrite(f, &totallen, sizeof(uint));
+    f->putlil<uint>(totallen);
     if(numwaterplanes>0)
     {
-        uint numwp = numwaterplanes;
-        endianswap(&numwp, sizeof(uint), 1);
-        gzwrite(f, &numwp, sizeof(uint));
+        f->putlil<uint>(numwaterplanes);
         loopi(numwaterplanes)
         {
-            int height = waterplanes[i].height;
-            endianswap(&height, sizeof(int), 1);
-            gzwrite(f, &height, sizeof(int));
+            f->putlil<int>(waterplanes[i].height);
             if(waterplanes[i].height < 0) break;
         }
     }
-    loopv(pvs)
-    {
-        ushort len = pvs[i].len;
-        endianswap(&len, sizeof(ushort), 1);
-        gzwrite(f, &len, sizeof(ushort));
-    }
-    gzwrite(f, pvsbuf.getbuf(), pvsbuf.length());
+    loopv(pvs) f->putlil<ushort>(pvs[i].len);
+    f->write(pvsbuf.getbuf(), pvsbuf.length());
     saveviewcells(f, *viewcells);
 }
 
-viewcellnode *loadviewcells(gzFile f)
+viewcellnode *loadviewcells(stream *f)
 {
     viewcellnode *p = new viewcellnode;
-    p->leafmask = gzgetc(f);
+    p->leafmask = f->getchar();
     loopi(8)
     {
-        if(p->leafmask&(1<<i))
-        {
-            gzread(f, &p->children[i].pvs, sizeof(int));
-            endianswap(&p->children[i].pvs, sizeof(int), 1);
-        }
+        if(p->leafmask&(1<<i)) p->children[i].pvs = f->getlil<int>();
         else p->children[i].node = loadviewcells(f);
     }
     return p;
 }
 
-void loadpvs(gzFile f, int numpvs)
+void loadpvs(stream *f, int numpvs)
 {
-    uint totallen = pvsbuf.length();
-    gzread(f, &totallen, sizeof(uint));
-    endianswap(&totallen, sizeof(uint), 1);
+    uint totallen = f->getlil<uint>();
     if(totallen & 0x80000000U)
     {
         totallen &= ~0x80000000U;
-        gzread(f, &numwaterplanes, sizeof(uint));
-        endianswap(&numwaterplanes, sizeof(uint), 1);
-        loopi(numwaterplanes)
-        {
-            gzread(f, &waterplanes[i].height, sizeof(int));
-            endianswap(&waterplanes[i].height, sizeof(int), 1);
-        }
+        numwaterplanes = f->getlil<uint>();
+        loopi(numwaterplanes) waterplanes[i].height = f->getlil<int>();
     }
     int offset = 0;
     loopi(numpvs)
     {
-        ushort len;
-        gzread(f, &len, sizeof(ushort));
-        endianswap(&len, sizeof(ushort), 1);
+        ushort len = f->getlil<ushort>();
         pvs.add(pvsdata(offset, len));
         offset += len;
     }
-    gzread(f, pvsbuf.reserve(totallen).buf, totallen);
+    f->read(pvsbuf.reserve(totallen).buf, totallen);
     pvsbuf.advance(totallen);
     viewcells = loadviewcells(f);
 }
Index: engine/engine.h
===================================================================
--- engine/engine.h	(revision 918)
+++ engine/engine.h	(working copy)
@@ -160,8 +160,8 @@
 extern bool pvsoccluded(const ivec &bborigin, const ivec &bbsize);
 extern bool waterpvsoccluded(int height);
 extern void setviewcell(const vec &p);
-extern void savepvs(gzFile f);
-extern void loadpvs(gzFile f, int numpvs);
+extern void savepvs(stream *f);
+extern void loadpvs(stream *f, int numpvs);
 extern int getnumviewcells();
 
 static inline bool pvsoccluded(const ivec &bborigin, int size)
@@ -193,7 +193,7 @@
 extern void setfogplane(float scale = 0, float z = 0, bool flush = false, float fadescale = 0, float fadeoffset = 0);
 extern void recomputecamera();
 extern void findorientation();
-extern void writecrosshairs(FILE *f);
+extern void writecrosshairs(stream *f);
 
 // renderextras
 extern void render3dbox(vec &o, float tofloor, float toceil, float xradius, float yradius = 0);
@@ -419,8 +419,8 @@
 extern void complete(char *s);
 const char *getkeyname(int code);
 extern const char *addreleaseaction(const char *s);
-extern void writebinds(FILE *f);
-extern void writecompletions(FILE *f);
+extern void writebinds(stream *f);
+extern void writecompletions(stream *f);
 
 // main
 enum
@@ -561,7 +561,7 @@
 extern void optimizeblendmap();
 extern void renderblendbrush(GLuint tex, float x, float y, float w, float h);
 extern void renderblendbrush();
-extern bool loadblendmap(gzFile f, int info);
-extern void saveblendmap(gzFile f);
+extern bool loadblendmap(stream *f, int info);
+extern void saveblendmap(stream *f);
 extern uchar shouldsaveblendmap();
 
Index: engine/command.cpp
===================================================================
--- engine/command.cpp	(revision 918)
+++ engine/command.cpp	(working copy)
@@ -657,11 +657,11 @@
 
 void writecfg()
 {
-    FILE *f = openfile(path(game::savedconfig(), true), "w");
+    stream *f = openfile(path(game::savedconfig(), true), "w");
     if(!f) return;
-    fprintf(f, "// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", game::defaultconfig(), game::autoexec());
+    f->printf("// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", game::defaultconfig(), game::autoexec());
     game::writeclientinfo(f);
-    fprintf(f, "\n");
+    f->printf("\n");
     writecrosshairs(f);
     vector<ident *> ids;
     enumerate(*idents, ident, id, ids.add(&id));
@@ -671,25 +671,25 @@
         ident &id = *ids[i];
         if(id.flags&IDF_PERSIST) switch(id.type)
         {
-            case ID_VAR: fprintf(f, "%s %d\n", id.name, *id.storage.i); break;
-            case ID_FVAR: fprintf(f, "%s %s\n", id.name, floatstr(*id.storage.f)); break;
-            case ID_SVAR: fprintf(f, "%s [%s]\n", id.name, *id.storage.s); break;
+            case ID_VAR: f->printf("%s %d\n", id.name, *id.storage.i); break;
+            case ID_FVAR: f->printf("%s %s\n", id.name, floatstr(*id.storage.f)); break;
+            case ID_SVAR: f->printf("%s [%s]\n", id.name, *id.storage.s); break;
         }
     }
-    fprintf(f, "\n");
+    f->printf("\n");
     writebinds(f);
-    fprintf(f, "\n");
+    f->printf("\n");
     loopv(ids)
     {
         ident &id = *ids[i];
         if(id.type==ID_ALIAS && id.flags&IDF_PERSIST && id.override==NO_OVERRIDE && !strstr(id.name, "nextmap_") && id.action[0])
         {
-            fprintf(f, "\"%s\" = [%s]\n", id.name, id.action);
+            f->printf("\"%s\" = [%s]\n", id.name, id.action);
         }
     }
-    fprintf(f, "\n");
+    f->printf("\n");
     writecompletions(f);
-    fclose(f);
+    delete f;
 }
 
 COMMAND(writecfg, "");
Index: engine/obj.h
===================================================================
--- engine/obj.h	(revision 918)
+++ engine/obj.h	(working copy)
@@ -29,7 +29,7 @@
             int len = strlen(filename);
             if(len < 4 || strcasecmp(&filename[len-4], ".obj")) return false;
 
-            FILE *file = openfile(filename, "rb");
+            stream *file = openfile(filename, "rb");
             if(!file) return false;
 
             name = newstring(filename);
@@ -76,10 +76,8 @@
 
             string meshname = "";
             vertmesh *curmesh = NULL;
-            for(;;)
+            while(file->getline(buf, sizeof(buf)))
             {
-                fgets(buf, sizeof(buf), file);
-                if(feof(file)) break;
                 char *c = buf;
                 while(isspace(*c)) c++;
                 switch(*c)
@@ -154,7 +152,7 @@
 
             if(curmesh) FLUSHMESH;
 
-            fclose(file);
+            delete file;
 
             return true;
         }
Index: engine/main.cpp
===================================================================
--- engine/main.cpp	(revision 918)
+++ engine/main.cpp	(working copy)
@@ -87,26 +87,26 @@
 void writeinitcfg()
 {
     if(!restoredinits) return;
-    FILE *f = openfile("init.cfg", "w");
+    stream *f = openfile("init.cfg", "w");
     if(!f) return;
-    fprintf(f, "// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n");
+    f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n");
     extern int fullscreen;
-    fprintf(f, "fullscreen %d\n", fullscreen);
-    fprintf(f, "scr_w %d\n", scr_w);
-    fprintf(f, "scr_h %d\n", scr_h);
-    fprintf(f, "colorbits %d\n", colorbits);
-    fprintf(f, "depthbits %d\n", depthbits);
-    fprintf(f, "stencilbits %d\n", stencilbits);
-    fprintf(f, "fsaa %d\n", fsaa);
-    fprintf(f, "vsync %d\n", vsync);
+    f->printf("fullscreen %d\n", fullscreen);
+    f->printf("scr_w %d\n", scr_w);
+    f->printf("scr_h %d\n", scr_h);
+    f->printf("colorbits %d\n", colorbits);
+    f->printf("depthbits %d\n", depthbits);
+    f->printf("stencilbits %d\n", stencilbits);
+    f->printf("fsaa %d\n", fsaa);
+    f->printf("vsync %d\n", vsync);
     extern int useshaders, shaderprecision;
-    fprintf(f, "shaders %d\n", useshaders);
-    fprintf(f, "shaderprecision %d\n", shaderprecision);
+    f->printf("shaders %d\n", useshaders);
+    f->printf("shaderprecision %d\n", shaderprecision);
     extern int soundchans, soundfreq, soundbufferlen;
-    fprintf(f, "soundchans %d\n", soundchans);
-    fprintf(f, "soundfreq %d\n", soundfreq);
-    fprintf(f, "soundbufferlen %d\n", soundbufferlen);
-    fclose(f);
+    f->printf("soundchans %d\n", soundchans);
+    f->printf("soundfreq %d\n", soundfreq);
+    f->printf("soundbufferlen %d\n", soundbufferlen);
+    delete f;
 }
 
 COMMAND(quit, "");
Index: engine/md2.h
===================================================================
--- engine/md2.h	(revision 918)
+++ engine/md2.h	(working copy)
@@ -126,16 +126,16 @@
         
         bool load(char *filename)
         {
-            FILE *file = openfile(filename, "rb");
+            stream *file = openfile(filename, "rb");
             if(!file) return false;
 
             md2_header header;
-            fread(&header, sizeof(md2_header), 1, file);
-            endianswap(&header, sizeof(int), sizeof(md2_header)/sizeof(int));
+            file->read(&header, sizeof(md2_header));
+            lilswap(&header.magic, sizeof(md2_header)/sizeof(int));
 
             if(header.magic!=844121161 || header.version!=8) 
             {
-                fclose(file);
+                delete file;
                 return false;
             }
           
@@ -148,9 +148,9 @@
             meshes.add(&m);
 
             int *glcommands = new int[header.numglcommands];
-            fseek(file, header.offsetglcommands, SEEK_SET); 
-            int numglcommands = fread(glcommands, sizeof(int), header.numglcommands, file);
-            endianswap(glcommands, sizeof(int), numglcommands);
+            file->seek(header.offsetglcommands, SEEK_SET); 
+            int numglcommands = file->read(glcommands, header.numglcommands*sizeof(int));
+            lilswap(glcommands, numglcommands);
             if(numglcommands < header.numglcommands) memset(&glcommands[numglcommands], 0, (header.numglcommands-numglcommands)*sizeof(int));
 
             vector<tcvert> tcgen;
@@ -175,11 +175,11 @@
             loopi(header.numframes)
             {
                 md2_frame frame;
-                fseek(file, frame_offset, SEEK_SET);
-                fread(&frame, sizeof(md2_frame), 1, file);
-                endianswap(&frame, sizeof(float), 6);
+                file->seek(frame_offset, SEEK_SET);
+                file->read(&frame, sizeof(md2_frame));
+                lilswap(frame.scale, 6);
 
-                fread(tmpverts, sizeof(md2_vertex), header.numvertices, file);
+                file->read(tmpverts, header.numvertices*sizeof(md2_vertex));
                 loopj(m.numverts)
                 {
                     const md2_vertex &v = tmpverts[vgen[j]];
@@ -194,7 +194,7 @@
             }
             delete[] tmpverts;
 
-            fclose(file);
+            delete file;
 
             return true;
         }
Index: engine/md3.h
===================================================================
--- engine/md3.h	(revision 918)
+++ engine/md3.h	(working copy)
@@ -58,15 +58,15 @@
     {
         bool load(char *path)
         {
-            FILE *f = openfile(path, "rb");
+            stream *f = openfile(path, "rb");
             if(!f) return false;
             md3header header;
-            fread(&header, sizeof(md3header), 1, f);
-            endianswap(&header.version, sizeof(int), 1);
-            endianswap(&header.flags, sizeof(int), 9);
+            f->read(&header, sizeof(md3header));
+            lilswap(&header.version, 1);
+            lilswap(&header.flags, 9);
             if(strncmp(header.id, "IDP3", 4) != 0 || header.version != 15) // header check
             { 
-                fclose(f);
+                delete f;
                 conoutf("md3: corrupted header"); 
                 return false; 
             }
@@ -83,36 +83,36 @@
                 meshes.add(&m);
 
                 md3meshheader mheader;
-                fseek(f, mesh_offset, SEEK_SET);
-                fread(&mheader, sizeof(md3meshheader), 1, f);
-                endianswap(&mheader.flags, sizeof(int), 10); 
+                f->seek(mesh_offset, SEEK_SET);
+                f->read(&mheader, sizeof(md3meshheader));
+                lilswap(&mheader.flags, 10); 
 
                 m.name = newstring(mheader.name);
                
                 m.numtris = mheader.numtriangles; 
                 m.tris = new tri[m.numtris];
-                fseek(f, mesh_offset + mheader.ofs_triangles, SEEK_SET);
+                f->seek(mesh_offset + mheader.ofs_triangles, SEEK_SET);
                 loopj(m.numtris)
                 {
                     md3triangle tri;
-                    fread(&tri, sizeof(md3triangle), 1, f); // read the triangles
-                    endianswap(&tri, sizeof(int), 3);
+                    f->read(&tri, sizeof(md3triangle)); // read the triangles
+                    lilswap(tri.vertexindices, 3);
                     loopk(3) m.tris[j].vert[k] = (ushort)tri.vertexindices[k];
                 }
 
                 m.numverts = mheader.numvertices;
                 m.tcverts = new tcvert[m.numverts];
-                fseek(f, mesh_offset + mheader.ofs_uv , SEEK_SET); 
-                fread(m.tcverts, 2*sizeof(float), m.numverts, f); // read the UV data
-                endianswap(m.tcverts, sizeof(float), 2*m.numverts);
+                f->seek(mesh_offset + mheader.ofs_uv , SEEK_SET); 
+                f->read(m.tcverts, m.numverts*2*sizeof(float)); // read the UV data
+                lilswap(m.tcverts, 2*m.numverts);
                 
                 m.verts = new vert[numframes*m.numverts];
-                fseek(f, mesh_offset + mheader.ofs_vertices, SEEK_SET); 
+                f->seek(mesh_offset + mheader.ofs_vertices, SEEK_SET); 
                 loopj(numframes*m.numverts)
                 {
                     md3vertex v;
-                    fread(&v, sizeof(md3vertex), 1, f); // read the vertices
-                    endianswap(&v, sizeof(short), 4);
+                    f->read(&v, sizeof(md3vertex)); // read the vertices
+                    lilswap(v.vertex, 4);
 
                     m.verts[j].pos.x = v.vertex[0]/64.0f;
                     m.verts[j].pos.y = -v.vertex[1]/64.0f;
@@ -132,13 +132,13 @@
             if(numtags)
             {
                 tags = new tag[numframes*numtags];
-                fseek(f, header.ofs_tags, SEEK_SET);
+                f->seek(header.ofs_tags, SEEK_SET);
                 md3tag tag;
 
                 loopi(header.numframes*header.numtags)
                 {
-                    fread(&tag, sizeof(md3tag), 1, f);
-                    endianswap(&tag.pos, sizeof(float), 12);
+                    f->read(&tag, sizeof(md3tag));
+                    lilswap(&tag.pos.x, 12);
                     if(tag.name[0] && i<header.numtags) tags[i].name = newstring(tag.name);
                     matrix3x4 &m = tags[i].transform;
                     tag.pos.y *= -1;
@@ -166,7 +166,7 @@
                 }
             }
 
-            fclose(f);
+            delete f;
             return true;
         }
     };
Index: engine/md5.h
===================================================================
--- engine/md5.h	(revision 918)
+++ engine/md5.h	(working copy)
@@ -95,17 +95,15 @@
             }
         }
 
-        void load(FILE *f, char *buf, size_t bufsize)
+        void load(stream *f, char *buf, size_t bufsize)
         {
             md5weight w;
             md5vert v;
             tri t;
             int index;
 
-            for(;;)
+            while(f->getline(buf, bufsize) && buf[0]!='}')
             {
-                fgets(buf, bufsize, f);
-                if(buf[0]=='}' || feof(f)) break;
                 if(strstr(buf, "// meshes:"))
                 {
                     char *start = strchr(buf, ':')+1;
@@ -171,40 +169,36 @@
 
         bool loadmd5mesh(const char *filename, float smooth)
         {
-            FILE *f = openfile(filename, "r");
+            stream *f = openfile(filename, "r");
             if(!f) return false;
 
             char buf[512];
             vector<md5joint> basejoints;
-            for(;;)
+            while(f->getline(buf, sizeof(buf)))
             {
-                fgets(buf, sizeof(buf), f);
-                if(feof(f)) break;
                 int tmp;
                 if(sscanf(buf, " MD5Version %d", &tmp)==1)
                 {
-                    if(tmp!=10) { fclose(f); return false; }
+                    if(tmp!=10) { delete f; return false; }
                 }
                 else if(sscanf(buf, " numJoints %d", &tmp)==1)
                 {
-                    if(tmp<1) { fclose(f); return false; }
+                    if(tmp<1) { delete f; return false; }
                     if(skel->numbones>0) continue;
                     skel->numbones = tmp;
                     skel->bones = new boneinfo[skel->numbones];
                 }
                 else if(sscanf(buf, " numMeshes %d", &tmp)==1)
                 {
-                    if(tmp<1) { fclose(f); return false; }
+                    if(tmp<1) { delete f; return false; }
                 }
                 else if(strstr(buf, "joints {"))
                 {
                     string name;
                     int parent;
                     md5joint j;
-                    for(;;)
+                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
                     {
-                        fgets(buf, sizeof(buf), f);
-                        if(buf[0]=='}' || feof(f)) break;
                         if(sscanf(buf, " %s %d ( %f %f %f ) ( %f %f %f )",
                             name, &parent, &j.pos.x, &j.pos.y, &j.pos.z,
                             &j.orient.x, &j.orient.y, &j.orient.z)==8)
@@ -225,7 +219,7 @@
                             basejoints.add(j);
                         }
                     }
-                    if(basejoints.length()!=skel->numbones) { fclose(f); return false; }
+                    if(basejoints.length()!=skel->numbones) { delete f; return false; }
                 }
                 else if(strstr(buf, "mesh {"))
                 {
@@ -259,7 +253,7 @@
             
             sortblendcombos();
 
-            fclose(f);
+            delete f;
             return true;
         }
 
@@ -268,7 +262,7 @@
             skelanimspec *sa = skel->findskelanim(filename);
             if(sa) return sa;
 
-            FILE *f = openfile(filename, "r");
+            stream *f = openfile(filename, "r");
             if(!f) return NULL;
 
             vector<md5hierarchy> hierarchy;
@@ -277,22 +271,20 @@
             float *animdata = NULL;
             dualquat *animbones = NULL;
             char buf[512];
-            for(;;)
+            while(f->getline(buf, sizeof(buf)))
             {
-                fgets(buf, sizeof(buf), f);
-                if(feof(f)) break;
                 int tmp;
                 if(sscanf(buf, " MD5Version %d", &tmp)==1)
                 {
-                    if(tmp!=10) { fclose(f); return NULL; }
+                    if(tmp!=10) { delete f; return NULL; }
                 }
                 else if(sscanf(buf, " numJoints %d", &tmp)==1)
                 {
-                    if(tmp!=skel->numbones) { fclose(f); return NULL; }
+                    if(tmp!=skel->numbones) { delete f; return NULL; }
                 }
                 else if(sscanf(buf, " numFrames %d", &animframes)==1)
                 {
-                    if(animframes<1) { fclose(f); return NULL; }
+                    if(animframes<1) { delete f; return NULL; }
                 }
                 else if(sscanf(buf, " frameRate %d", &tmp)==1);
                 else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1)
@@ -301,18 +293,12 @@
                 }
                 else if(strstr(buf, "bounds {"))
                 {
-                    for(;;)
-                    {
-                        fgets(buf, sizeof(buf), f);
-                        if(buf[0]=='}' || feof(f)) break;
-                    }
+                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}');
                 }
                 else if(strstr(buf, "hierarchy {"))
                 {
-                    for(;;)
+                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
                     {
-                        fgets(buf, sizeof(buf), f);
-                        if(buf[0]=='}' || feof(f)) break;
                         md5hierarchy h;
                         if(sscanf(buf, " %s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4)
                             hierarchy.add(h);
@@ -320,10 +306,8 @@
                 }
                 else if(strstr(buf, "baseframe {"))
                 {
-                    for(;;)
+                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
                     {
-                        fgets(buf, sizeof(buf), f);
-                        if(buf[0]=='}' || feof(f)) break;
                         md5joint j;
                         if(sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6)
                         {
@@ -334,7 +318,7 @@
                             basejoints.add(j);
                         }
                     }
-                    if(basejoints.length()!=skel->numbones) { fclose(f); return NULL; }
+                    if(basejoints.length()!=skel->numbones) { delete f; return NULL; }
                     animbones = new dualquat[(skel->numframes+animframes)*skel->numbones];
                     if(skel->bones)
                     {
@@ -352,10 +336,8 @@
                 }
                 else if(sscanf(buf, " frame %d", &tmp)==1)
                 {
-                    for(int numdata = 0;;)
+                    for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';)
                     {
-                        fgets(buf, sizeof(buf), f);
-                        if(buf[0]=='}' || feof(f)) break;
                         for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next)
                         {
                             animdata[numdata] = strtod(src, &next);
@@ -397,7 +379,7 @@
             }
 
             DELETEA(animdata);
-            fclose(f);
+            delete f;
 
 #if 0
             vector<dualquat> invbase;
Index: engine/texture.cpp
===================================================================
--- engine/texture.cpp	(revision 918)
+++ engine/texture.cpp	(working copy)
@@ -629,6 +629,20 @@
     }
     s.replace(d);
 }
+
+SDL_Surface *loadsurface(const char *name)
+{
+    SDL_Surface *s = NULL;
+    stream *z = openzipfile(name, "rb");
+    if(z)
+    {
+        SDL_RWops *rw = z->rwops();
+        if(rw) s = IMG_Load_RW(rw, 1);
+        else delete z;
+    }
+    if(!s) s = IMG_Load(findfile(name, "rb"));
+    return fixsurfaceformat(s);
+}
    
 static vec parsevec(const char *arg)
 {
@@ -717,7 +731,7 @@
         if(!dds) { if(msg) conoutf(CON_ERROR, "could not load texture %s", dfile); return false; }
     }
         
-    SDL_Surface *s = fixsurfaceformat(IMG_Load(findfile(file, "rb")));
+    SDL_Surface *s = loadsurface(file);
     if(!s) { if(msg) conoutf(CON_ERROR, "could not load texture %s", file); return false; }
     int bpp = s->format->BitsPerPixel;
     if(bpp%8 || !texformat(bpp/8)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture must be 8, 16, 24, or 32 bpp: %s", file); return false; }
@@ -1594,15 +1608,15 @@
 
 bool loaddds(const char *filename, ImageData &image)
 {
-    FILE *f = openfile(filename, "rb");
+    stream *f = openfile(filename, "rb");
     if(!f) return false;
     GLenum format = GL_FALSE;
     uchar magic[4];
-    if(fread(magic, 1, 4, f) != 4 || memcmp(magic, "DDS ", 4)) { fclose(f); return false; }
+    if(f->read(magic, 4) != 4 || memcmp(magic, "DDS ", 4)) { delete f; return false; }
     DDSURFACEDESC2 d;
-    if(fread(&d, 1, sizeof(d), f) != sizeof(d)) { fclose(f); return false; }
-    endianswap(&d, sizeof(uint), sizeof(d)/sizeof(uint));
-    if(d.dwSize != sizeof(DDSURFACEDESC2) || d.ddpfPixelFormat.dwSize != sizeof(DDPIXELFORMAT)) { fclose(f); return false; }
+    if(f->read(&d, sizeof(d)) != sizeof(d)) { delete f; return false; }
+    lilswap((uint *)&d, sizeof(d)/sizeof(uint));
+    if(d.dwSize != sizeof(DDSURFACEDESC2) || d.ddpfPixelFormat.dwSize != sizeof(DDPIXELFORMAT)) { delete f; return false; }
     if(d.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
     {
         switch(d.ddpfPixelFormat.dwFourCC)
@@ -1612,7 +1626,7 @@
             case FOURCC_DXT5: format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break;
         }        
     }
-    if(!format) { fclose(f); return false; }
+    if(!format) { delete f; return false; }
     if(dbgdds) conoutf(CON_DEBUG, "%s: format 0x%X, %d x %d, %d mipmaps", filename, format, d.dwWidth, d.dwHeight, d.dwMipMapCount);
     int bpp = 0;
     switch(format)
@@ -1624,8 +1638,8 @@
     }
     image.setdata(NULL, d.dwWidth, d.dwHeight, bpp, d.dwMipMapCount, 4, format); 
     int size = image.calcsize();
-    if((int)fread(image.data, 1, size, f) != size) { fclose(f); image.cleanup(); return false; }
-    fclose(f);
+    if(f->read(image.data, size) != size) { delete f; image.cleanup(); return false; }
+    delete f;
     return true;
 }
 
@@ -1672,7 +1686,7 @@
         outfile = buf;
     }
     
-    FILE *f = openfile(path(outfile, true), "wb");
+    stream *f = openfile(path(outfile, true), "wb");
     if(!f) { conoutf(CON_ERROR, "failed writing to %s", outfile); return; } 
 
     int csize = 0;
@@ -1710,12 +1724,12 @@
         if(lh > 1) lh /= 2;
     }
 
-    endianswap(&d, sizeof(uint), sizeof(d)/sizeof(uint));
+    lilswap((uint *)&d, sizeof(d)/sizeof(uint));
 
-    fwrite("DDS ", 1, 4, f);
-    fwrite(&d, 1, sizeof(d), f);
-    fwrite(data, 1, csize, f);
-    fclose(f);
+    f->write("DDS ", 4);
+    f->write(&d, sizeof(d));
+    f->write(data, csize);
+    delete f;
     
     delete[] data;
 
@@ -1725,18 +1739,16 @@
 }
 COMMAND(gendds, "ss");
 
-void writepngchunk(FILE *f, const char *type, uchar *data = NULL, uint len = 0)
+void writepngchunk(stream *f, const char *type, uchar *data = NULL, uint len = 0)
 {
-    uint clen = SDL_SwapBE32(len);
-    fwrite(&clen, 1, sizeof(clen), f);
-    fwrite(type, 1, 4, f);
-    fwrite(data, 1, len, f);
+    f->putbig<uint>(len);
+    f->write(type, 4);
+    f->write(data, len);
 
     uint crc = crc32(0, Z_NULL, 0);
     crc = crc32(crc, (const Bytef *)type, 4);
     if(data) crc = crc32(crc, data, len);
-    crc = SDL_SwapBE32(crc);
-    fwrite(&crc, 1, 4, f);
+    f->putbig<uint>(crc);
 }
 
 VARP(compresspng, 0, 9, 9);
@@ -1752,20 +1764,20 @@
         case 4: ctype = 6; break;
         default: conoutf(CON_ERROR, "failed saving png to %s", filename); return;
     }
-    FILE *f = openfile(filename, "wb");
+    stream *f = openfile(filename, "wb");
     if(!f) { conoutf(CON_ERROR, "could not write to %s", filename); return; }
 
     uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
-    fwrite(signature, 1, sizeof(signature), f);
+    f->write(signature, sizeof(signature));
 
     uchar ihdr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 8, ctype, 0, 0, 0 };
-    *(uint *)ihdr = SDL_SwapBE32(image.w);
-    *(uint *)(ihdr + 4) = SDL_SwapBE32(image.h);
+    *(uint *)ihdr = bigswap<uint>(image.w);
+    *(uint *)(ihdr + 4) = bigswap<uint>(image.h);
     writepngchunk(f, "IHDR", ihdr, sizeof(ihdr));
 
-    int idat = ftell(f);
+    int idat = f->tell();
     uint len = 0;
-    fwrite("\0\0\0\0IDAT", 1, 8, f);
+    f->write("\0\0\0\0IDAT", 8);
     uint crc = crc32(0, Z_NULL, 0);
     crc = crc32(crc, (const Bytef *)"IDAT", 4);
 
@@ -1795,7 +1807,7 @@
                     int flush = sizeof(buf) - z.avail_out; \
                     crc = crc32(crc, buf, flush); \
                     len += flush; \
-                    fwrite(buf, 1, flush, f); \
+                    f->write(buf, flush); \
                     z.next_out = (Bytef *)buf; \
                     z.avail_out = sizeof(buf); \
                 } while(0)
@@ -1814,23 +1826,21 @@
 
     deflateEnd(&z);
 
-    fseek(f, idat, SEEK_SET);
-    len = SDL_SwapBE32(len);
-    fwrite(&len, 1, 4, f);
-    crc = SDL_SwapBE32(crc);
-    fseek(f, 0, SEEK_END);
-    fwrite(&crc, 1, 4, f);
+    f->seek(idat, SEEK_SET);
+    f->putbig<uint>(len);
+    f->seek(0, SEEK_END);
+    f->putbig<uint>(crc);
 
     writepngchunk(f, "IEND");
 
-    fclose(f);
+    delete f;
     return;
 
 cleanuperror:
     deflateEnd(&z);
 
 error:
-    fclose(f);
+    delete f;
 
     conoutf(CON_ERROR, "failed saving png to %s", filename);
 }
@@ -1861,7 +1871,7 @@
         default: conoutf(CON_ERROR, "failed saving tga to %s", filename); return;
     }
 
-    FILE *f = openfile(filename, "wb");
+    stream *f = openfile(filename, "wb");
     if(!f) { conoutf(CON_ERROR, "could not write to %s", filename); return; }
 
     tgaheader hdr;
@@ -1872,7 +1882,7 @@
     hdr.height[0] = image.h&0xFF;
     hdr.height[1] = (image.h>>8)&0xFF;
     hdr.imagetype = compresstga ? 10 : 2;
-    fwrite(&hdr, 1, sizeof(hdr), f);
+    f->write(&hdr, sizeof(hdr));
 
     uchar buf[128*4];
     loopi(image.h)
@@ -1891,9 +1901,9 @@
                 }
                 if(run > 1)
                 {
-                    fputc(0x80 | (run-1), f);
-                    fputc(src[2], f); fputc(src[1], f); fputc(src[0], f);
-                    if(image.bpp==4) fputc(src[3], f);
+                    f->putchar(0x80 | (run-1));
+                    f->putchar(src[2]); f->putchar(src[1]); f->putchar(src[0]);
+                    if(image.bpp==4) f->putchar(src[3]);
                     src += run*image.bpp;
                     remaining -= run;
                     if(remaining <= 0) break;
@@ -1903,7 +1913,7 @@
                     scan += image.bpp;
                     if(src[0]==scan[0] && src[1]==scan[1] && src[2]==scan[2] && (image.bpp!=4 || src[3]==scan[3])) break;
                 }
-                fputc(raw - 1, f);
+                f->putchar(raw - 1);
             }
             else raw = min(remaining, 128);
             uchar *dst = buf;
@@ -1916,12 +1926,12 @@
                 dst += image.bpp;
                 src += image.bpp;
             }
-            fwrite(buf, image.bpp, raw, f);
+            f->write(buf, raw*image.bpp);
             remaining -= raw;
         }
     }
 
-    fclose(f);
+    delete f;
 }
 
 enum
@@ -1970,7 +1980,7 @@
 
 bool loadimage(const char *filename, ImageData &image)
 {
-    SDL_Surface *s = IMG_Load(findfile(path(filename, true), "rb"));
+    SDL_Surface *s = loadsurface(path(filename, true));
     if(!s) return false;
     image.wrap(s);
     return true;
Index: engine/console.cpp
===================================================================
--- engine/console.cpp	(revision 918)
+++ engine/console.cpp	(working copy)
@@ -600,7 +600,7 @@
     return strcmp((*x)->name, (*y)->name);
 }
 
-void writebinds(FILE *f)
+void writebinds(stream *f)
 {
     static const char *cmds[3] = { "bind", "specbind", "editbind" };
     vector<keym *> binds;
@@ -611,7 +611,7 @@
         loopv(binds)
         {
             keym &km = *binds[i];
-            if(*km.actions[j]) fprintf(f, "%s \"%s\" [%s]\n", cmds[j], km.name, km.actions[j]);
+            if(*km.actions[j]) f->printf("%s \"%s\" [%s]\n", cmds[j], km.name, km.actions[j]);
         }
     }
 }
@@ -770,7 +770,7 @@
     return strcmp(*x, *y);
 }
 
-void writecompletions(FILE *f)
+void writecompletions(stream *f)
 {
     vector<char *> cmds;
     enumeratekt(completions, char *, k, filesval *, v, { if(v) cmds.add(k); });
@@ -779,8 +779,8 @@
     {
         char *k = cmds[i];
         filesval *v = completions[k];
-        if(v->type==FILES_LIST) fprintf(f, "listcomplete \"%s\" [%s]\n", k, v->dir);
-        else fprintf(f, "complete \"%s\" \"%s\" \"%s\"\n", k, v->dir, v->ext ? v->ext : "*");
+        if(v->type==FILES_LIST) f->printf("listcomplete \"%s\" [%s]\n", k, v->dir);
+        else f->printf("complete \"%s\" \"%s\" \"%s\"\n", k, v->dir, v->ext ? v->ext : "*");
     }
 }
 
Index: engine/cubeloader.cpp
===================================================================
--- engine/cubeloader.cpp	(revision 918)
+++ engine/cubeloader.cpp	(working copy)
@@ -243,7 +243,7 @@
         if(!f) { conoutf(CON_ERROR, "could not read cube map %s", cgzname); return; }
         c_header hdr;
         gzread(f, &hdr, sizeof(c_header)-sizeof(int)*16);
-        endianswap(&hdr.version, sizeof(int), 4);
+        lilswap(&hdr.version, 4);
         bool mod = false;
         if(strncmp(hdr.head, "CUBE", 4)) 
         { 
@@ -266,7 +266,7 @@
         if(hdr.version>=4)
         {
             gzread(f, &hdr.waterlevel, sizeof(int)*16);
-            endianswap(&hdr.waterlevel, sizeof(int), 16);
+            lilswap(&hdr.waterlevel, 16);
         }
         else
         {
@@ -277,7 +277,7 @@
         {
             c_persistent_entity e;
             gzread(f, &e, sizeof(c_persistent_entity));
-            endianswap(&e, sizeof(short), 4);
+            lilswap(&e.x, 4);
             if(i < MAXENTS) create_ent(e);
         }
         ssize = 1<<hdr.sfactor;
Index: engine/serverbrowser.cpp
===================================================================
--- engine/serverbrowser.cpp	(revision 918)
+++ engine/serverbrowser.cpp	(working copy)
@@ -527,10 +527,10 @@
 void writeservercfg()
 {
     if(!game::savedservers()) return;
-    FILE *f = openfile(path(game::savedservers(), true), "w");
+    stream *f = openfile(path(game::savedservers(), true), "w");
     if(!f) return;
-    fprintf(f, "// servers connected to are added here automatically\n\n");
-    loopvrev(servers) fprintf(f, "addserver %s\n", servers[i]->name);
-    fclose(f);
+    f->printf("// servers connected to are added here automatically\n\n");
+    loopvrev(servers) f->printf("addserver %s\n", servers[i]->name);
+    delete f;
 }
 
Index: engine/worldio.cpp
===================================================================
--- engine/worldio.cpp	(revision 918)
+++ engine/worldio.cpp	(working copy)
@@ -74,72 +74,30 @@
 
 COMMAND(mapcfgname, "");
 
-ushort readushort(gzFile f)
-{
-    ushort t = 0;
-    gzread(f, &t, sizeof(ushort));
-    endianswap(&t, sizeof(ushort), 1);
-    return t;
-}
-
-int readint(gzFile f)
-{
-    int t = 0;
-    gzread(f, &t, sizeof(int));
-    endianswap(&t, sizeof(int), 1);
-    return t;
-}
-
-float readfloat(gzFile f)
-{
-    int t = 0;
-    gzread(f, &t, sizeof(float));
-    endianswap(&t, sizeof(float), 1);
-    return t;
-}
-
-void writeushort(gzFile f, ushort u)
-{
-    endianswap(&u, sizeof(ushort), 1);
-    gzwrite(f, &u, sizeof(ushort));
-}
-
-void writeint(gzFile f, int i)
-{
-    endianswap(&i, sizeof(int), 1);
-    gzwrite(f, &i, sizeof(int));
-}
-
-void writefloat(gzFile f, float n)
-{
-    endianswap(&n, sizeof(float), 1);
-    gzwrite(f, &n, sizeof(float));
-}
-
 enum { OCTSAV_CHILDREN = 0, OCTSAV_EMPTY, OCTSAV_SOLID, OCTSAV_NORMAL, OCTSAV_LODCUBE };
 
-void savec(cube *c, gzFile f, bool nolms)
+void savec(cube *c, stream *f, bool nolms)
 {
     loopi(8)
     {
         if(c[i].children && (!c[i].ext || !c[i].ext->surfaces))
         {
-            gzputc(f, OCTSAV_CHILDREN);
+            f->putchar(OCTSAV_CHILDREN);
             savec(c[i].children, f, nolms);
         }
         else
         {
             int oflags = 0;
             if(c[i].ext && c[i].ext->merged) oflags |= 0x80;
-            if(c[i].children) gzputc(f, oflags | OCTSAV_LODCUBE);
-            else if(isempty(c[i])) gzputc(f, oflags | OCTSAV_EMPTY);
-            else if(isentirelysolid(c[i])) gzputc(f, oflags | OCTSAV_SOLID);
+            if(c[i].children) f->putchar(oflags | OCTSAV_LODCUBE);
+            else if(isempty(c[i])) f->putchar(oflags | OCTSAV_EMPTY);
+            else if(isentirelysolid(c[i])) f->putchar(oflags | OCTSAV_SOLID);
             else
             {
-                gzputc(f, oflags | OCTSAV_NORMAL);
-                gzwrite(f, c[i].edges, 12);
+                f->putchar(oflags | OCTSAV_NORMAL);
+                f->write(c[i].edges, 12);
             }
-            loopj(6) writeushort(f, c[i].texture[j]);
+            loopj(6) f->putlil<ushort>(c[i].texture[j]);
             uchar mask = 0;
             if(c[i].ext)
             {
@@ -153,14 +111,14 @@
             // save surface info for lighting
             if(!c[i].ext || !c[i].ext->surfaces || nolms)
             {
-                gzputc(f, mask);
+                f->putchar(mask);
                 if(c[i].ext)
                 {
-                    if(c[i].ext->material != MAT_AIR) gzputc(f, c[i].ext->material);
+                    if(c[i].ext->material != MAT_AIR) f->putchar(c[i].ext->material);
                     if(c[i].ext->normals && !nolms) loopj(6) if(mask & (1 << j))
                     {
-                        loopk(sizeof(surfaceinfo)) gzputc(f, 0);
-                        gzwrite(f, &c[i].ext->normals[j], sizeof(surfacenormals));
+                        loopk(sizeof(surfaceinfo)) f->putchar(0);
+                        f->write(&c[i].ext->normals[j], sizeof(surfacenormals));
                     } 
                 }
             }
@@ -176,28 +134,28 @@
                         if(surface.layer&LAYER_BLEND) numsurfs++;
                     }
                 }
-                gzputc(f, mask);
-                if(c[i].ext->material != MAT_AIR) gzputc(f, c[i].ext->material);
+                f->putchar(mask);
+                if(c[i].ext->material != MAT_AIR) f->putchar(c[i].ext->material);
                 loopj(numsurfs) if(j >= 6 || mask & (1 << j))
                 {
                     surfaceinfo tmp = c[i].ext->surfaces[j];
-                    endianswap(&tmp.x, sizeof(ushort), 2);
-                    gzwrite(f, &tmp, sizeof(surfaceinfo));
-                    if(j < 6 && c[i].ext->normals) gzwrite(f, &c[i].ext->normals[j], sizeof(surfacenormals));
+                    lilswap(&tmp.x, 2);
+                    f->write(&tmp, sizeof(surfaceinfo));
+                    if(j < 6 && c[i].ext->normals) f->write(&c[i].ext->normals[j], sizeof(surfacenormals));
                 }
             }
             if(c[i].ext && c[i].ext->merged)
             {
-                gzputc(f, c[i].ext->merged | (c[i].ext->mergeorigin ? 0x80 : 0));
+                f->putchar(c[i].ext->merged | (c[i].ext->mergeorigin ? 0x80 : 0));
                 if(c[i].ext->mergeorigin)
                 {
-                    gzputc(f, c[i].ext->mergeorigin);
+                    f->putchar(c[i].ext->mergeorigin);
                     int index = 0;
                     loopj(6) if(c[i].ext->mergeorigin&(1<<j))
                     {
                         mergeinfo tmp = c[i].ext->merges[index++];
-                        endianswap(&tmp, sizeof(ushort), 4);
-                        gzwrite(f, &tmp, sizeof(mergeinfo));
+                        lilswap(&tmp.u1, 4);
+                        f->write(&tmp, sizeof(mergeinfo));
                     }
                 }
             }
@@ -206,12 +164,12 @@
     }
 }
 
-cube *loadchildren(gzFile f);
+cube *loadchildren(stream *f);
 
-void loadc(gzFile f, cube &c)
+void loadc(stream *f, cube &c)
 {
     bool haschildren = false;
-    int octsav = gzgetc(f);
+    int octsav = f->getchar();
     switch(octsav&0x7)
     {
         case OCTSAV_CHILDREN:
@@ -221,19 +179,19 @@
         case OCTSAV_LODCUBE: haschildren = true;    break;
         case OCTSAV_EMPTY:  emptyfaces(c);          break;
         case OCTSAV_SOLID:  solidfaces(c);          break;
-        case OCTSAV_NORMAL: gzread(f, c.edges, 12); break;
+        case OCTSAV_NORMAL: f->read(c.edges, 12); break;
 
         default:
             fatal("garbage in map");
     }
-    loopi(6) c.texture[i] = mapversion<14 ? gzgetc(f) : readushort(f);
-    if(mapversion < 7) loopi(3) gzgetc(f); //gzread(f, c.colour, 3);
+    loopi(6) c.texture[i] = mapversion<14 ? f->getchar() : f->getlil<ushort>();
+    if(mapversion < 7) f->seek(3, SEEK_CUR);
     else
     {
-        uchar mask = gzgetc(f);
+        uchar mask = f->getchar();
         if(mask & 0x80) 
         {
-            int mat = gzgetc(f);
+            int mat = f->getchar();
             if(mapversion < 27)
             {
                 static uchar matconv[] = { MAT_AIR, MAT_WATER, MAT_CLIP, MAT_GLASS|MAT_CLIP, MAT_NOCLIP, MAT_LAVA|MAT_DEATH, MAT_AICLIP, MAT_DEATH };
@@ -252,8 +210,8 @@
             {
                 if(i >= 6 || mask & (1 << i))
                 {
-                    gzread(f, &surfaces[i], sizeof(surfaceinfo));
-                    endianswap(&surfaces[i].x, sizeof(ushort), 2);
+                    f->read(&surfaces[i], sizeof(surfaceinfo));
+                    lilswap(&surfaces[i].x, 2);
                     if(mapversion < 10) ++surfaces[i].lmid;
                     if(mapversion < 18)
                     {
@@ -266,7 +224,7 @@
                     }
                     if(i < 6)
                     {
-                        if(mask & 0x40) gzread(f, &c.ext->normals[i], sizeof(surfacenormals));
+                        if(mask & 0x40) f->read(&c.ext->normals[i], sizeof(surfacenormals));
                         if(surfaces[i].layer != LAYER_TOP) lit |= 1 << i;
                         else if(surfaces[i].lmid == LMID_BRIGHT) bright |= 1 << i;
                         else if(surfaces[i].lmid != LMID_AMBIENT) lit |= 1 << i;
@@ -282,11 +240,11 @@
         {
             if(octsav&0x80)
             {
-                int merged = gzgetc(f);
+                int merged = f->getchar();
                 ext(c).merged = merged&0x3F;
                 if(merged&0x80)
                 {
-                    c.ext->mergeorigin = gzgetc(f);
+                    c.ext->mergeorigin = f->getchar();
                     int nummerges = 0;
                     loopi(6) if(c.ext->mergeorigin&(1<<i)) nummerges++;
                     if(nummerges)
@@ -295,8 +253,8 @@
                         loopi(nummerges)
                         {
                             mergeinfo *m = &c.ext->merges[i];
-                            gzread(f, m, sizeof(mergeinfo));
-                            endianswap(m, sizeof(ushort), 4);
+                            f->read(m, sizeof(mergeinfo));
+                            lilswap(&m->u1, 4);
                             if(mapversion <= 25)
                             {
                                 int uorigin = m->u1 & 0xE000, vorigin = m->v1 & 0xE000;
@@ -314,7 +272,7 @@
     c.children = (haschildren ? loadchildren(f) : NULL);
 }
 
-cube *loadchildren(gzFile f)
+cube *loadchildren(stream *f)
 {
     cube *c = newcubes();
     loopi(8) loadc(f, c[i]);
@@ -329,7 +287,7 @@
     if(!*mname) mname = game::getclientmap();
     setnames(*mname ? mname : "untitled");
     if(savebak) backup(ogzname, bakname);
-    gzFile f = opengzfile(ogzname, "wb9");
+    stream *f = opengzfile(ogzname, "wb");
     if(!f) { conoutf(CON_WARN, "could not write map to %s", ogzname); return false; }
     octaheader hdr;
     memcpy(hdr.magic, "OCTA", 4);
@@ -347,58 +305,58 @@
     {
         if((id.type == ID_VAR || id.type == ID_FVAR || id.type == ID_SVAR) && id.flags&IDF_OVERRIDE && !(id.flags&IDF_READONLY) && id.override!=NO_OVERRIDE) hdr.numvars++;
     });
-    endianswap(&hdr.version, sizeof(int), 8);
-    gzwrite(f, &hdr, sizeof(hdr));
+    lilswap(&hdr.version, 8);
+    f->write(&hdr, sizeof(hdr));
    
     enumerate(*idents, ident, id, 
     {
         if((id.type!=ID_VAR && id.type!=ID_FVAR && id.type!=ID_SVAR) || !(id.flags&IDF_OVERRIDE) || id.flags&IDF_READONLY || id.override==NO_OVERRIDE) continue;
-        gzputc(f, id.type);
-        writeushort(f, strlen(id.name));
-        gzwrite(f, id.name, strlen(id.name));
+        f->putchar(id.type);
+        f->putlil<ushort>(strlen(id.name));
+        f->write(id.name, strlen(id.name));
         switch(id.type)
         {
             case ID_VAR:
                 if(dbgvars) conoutf(CON_DEBUG, "wrote var %s: %d", id.name, *id.storage.i);
-                writeint(f, *id.storage.i);
+                f->putlil<int>(*id.storage.i);
                 break;
 
             case ID_FVAR:
                 if(dbgvars) conoutf(CON_DEBUG, "wrote fvar %s: %f", id.name, *id.storage.f);
-                writefloat(f, *id.storage.f);
+                f->putlil<float>(*id.storage.f);
                 break;
 
             case ID_SVAR:
                 if(dbgvars) conoutf(CON_DEBUG, "wrote svar %s: %s", id.name, *id.storage.s);
-                writeushort(f, strlen(*id.storage.s));
-                gzwrite(f, *id.storage.s, strlen(*id.storage.s));
+                f->putlil<ushort>(strlen(*id.storage.s));
+                f->write(*id.storage.s, strlen(*id.storage.s));
                 break;
         }
     });
 
     if(dbgvars) conoutf(CON_DEBUG, "wrote %d vars", hdr.numvars);
 
-    gzputc(f, (int)strlen(game::gameident()));
-    gzwrite(f, game::gameident(), (int)strlen(game::gameident())+1);
-    writeushort(f, entities::extraentinfosize());
+    f->putchar((int)strlen(game::gameident()));
+    f->write(game::gameident(), (int)strlen(game::gameident())+1);
+    f->putlil<ushort>(entities::extraentinfosize());
     vector<char> extras;
     game::writegamedata(extras);
-    writeushort(f, extras.length());
-    gzwrite(f, extras.getbuf(), extras.length());
+    f->putlil<ushort>(extras.length());
+    f->write(extras.getbuf(), extras.length());
     
-    writeushort(f, texmru.length());
-    loopv(texmru) writeushort(f, texmru[i]);
+    f->putlil<ushort>(texmru.length());
+    loopv(texmru) f->putlil<ushort>(texmru[i]);
     char *ebuf = new char[entities::extraentinfosize()];
     loopv(ents)
     {
         if(ents[i]->type!=ET_EMPTY || nolms)
         {
             entity tmp = *ents[i];
-            endianswap(&tmp.o, sizeof(int), 3);
-            endianswap(&tmp.attr1, sizeof(short), 5);
-            gzwrite(f, &tmp, sizeof(entity));
+            lilswap(&tmp.o.x, 3);
+            lilswap(&tmp.attr1, 5);
+            f->write(&tmp, sizeof(entity));
             entities::writeent(*ents[i], ebuf);
-            if(entities::extraentinfosize()) gzwrite(f, ebuf, entities::extraentinfosize());
+            if(entities::extraentinfosize()) f->write(ebuf, entities::extraentinfosize());
         }
     }
     delete[] ebuf;
@@ -409,19 +367,19 @@
         loopv(lightmaps)
         {
             LightMap &lm = lightmaps[i];
-            gzputc(f, lm.type | (lm.unlitx>=0 ? 0x80 : 0));
+            f->putchar(lm.type | (lm.unlitx>=0 ? 0x80 : 0));
             if(lm.unlitx>=0)
             {
-                writeushort(f, ushort(lm.unlitx));
-                writeushort(f, ushort(lm.unlity));
+                f->putlil<ushort>(ushort(lm.unlitx));
+                f->putlil<ushort>(ushort(lm.unlity));
             }
-            gzwrite(f, lm.data, lm.bpp*LM_PACKW*LM_PACKH);
+            f->write(lm.data, lm.bpp*LM_PACKW*LM_PACKH);
         }
         if(getnumviewcells()>0) savepvs(f);
         if(shouldsaveblendmap()) saveblendmap(f);
     }
 
-    gzclose(f);
+    delete f;
     conoutf("wrote map file %s", ogzname);
     return true;
 }
@@ -456,20 +414,20 @@
 {
     int loadingstart = SDL_GetTicks();
     setnames(mname, cname);
-    gzFile f = opengzfile(ogzname, "rb9");
+    stream *f = opengzfile(ogzname, "rb");
     if(!f) { conoutf(CON_ERROR, "could not read map %s", ogzname); return false; }
     octaheader hdr;
-    if(gzread(f, &hdr, 5*sizeof(int))!=5*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); gzclose(f); return false; }
-    endianswap(&hdr.version, sizeof(int), 4);
-    if(strncmp(hdr.magic, "OCTA", 4)!=0 || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); gzclose(f); return false; }
-    if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of cube 2", ogzname); gzclose(f); return false; }
+    if(f->read(&hdr, 5*sizeof(int))!=5*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
+    lilswap(&hdr.version, 4);
+    if(strncmp(hdr.magic, "OCTA", 4)!=0 || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
+    if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of cube 2", ogzname); delete f; return false; }
     compatheader chdr;
     if(hdr.version <= 28 || /* hack for obsolete older mapversion 29 */ (size_t)hdr.headersize > sizeof(octaheader))
     {
-        if(gzread(f, &chdr.numpvs, sizeof(chdr) - 5*sizeof(int)) != sizeof(chdr) - 5*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); gzclose(f); return false; }
+        if(f->read(&chdr.numpvs, sizeof(chdr) - 5*sizeof(int)) != sizeof(chdr) - 5*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
         if(hdr.version > 28) conoutf(CON_WARN, "using an obsolete map format: please resave this map before release or your map will fail to load");
     }
-    else if(gzread(f, &hdr.numpvs, sizeof(hdr) - 5*sizeof(int)) != sizeof(hdr) - 5*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); gzclose(f); return false; }
+    else if(f->read(&hdr.numpvs, sizeof(hdr) - 5*sizeof(int)) != sizeof(hdr) - 5*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
 
     resetmap();
 
@@ -480,8 +438,8 @@
 
     if(hdr.version <= 28 || /* hack for obsolete older mapversion 29 */ (size_t)hdr.headersize > sizeof(octaheader))
     {
-        endianswap(&chdr.numpvs, sizeof(int), 3);
-        endianswap(&chdr.waterfog, sizeof(ushort), 3);
+        lilswap(&chdr.numpvs, 3);
+        lilswap(&chdr.waterfog, 3);
         if(hdr.version<=20) conoutf(CON_WARN, "loading older / less efficient map format, may benefit from \"calclight 2\", then \"savecurrentmap\"");
         if(hdr.version<=28)
         {
@@ -513,22 +471,22 @@
         hdr.blendmap = chdr.blendmap;
         hdr.numvars = 0; 
     }
-    else endianswap(&hdr.numpvs, sizeof(int), 3);
+    else lilswap(&hdr.numpvs, 3);
  
     loopi(hdr.numvars)
     {
-        int type = gzgetc(f), ilen = readushort(f);
+        int type = f->getchar(), ilen = f->getlil<ushort>();
         string name;
-        gzread(f, name, min(ilen, MAXSTRLEN-1));
+        f->read(name, min(ilen, MAXSTRLEN-1));
         name[min(ilen, MAXSTRLEN-1)] = '\0';
-        if(ilen >= MAXSTRLEN) gzseek(f, ilen - (MAXSTRLEN-1), SEEK_CUR);
+        if(ilen >= MAXSTRLEN) f->seek(ilen - (MAXSTRLEN-1), SEEK_CUR);
         ident *id = getident(name);
         bool exists = id && id->type == type;
         switch(type)
         {
             case ID_VAR:
             {
-                int val = readint(f);
+                int val = f->getlil<int>();
                 if(exists && id->minval <= id->maxval) setvar(name, val);
                 if(dbgvars) conoutf(CON_DEBUG, "read var %s: %d", name, val);
                 break;
@@ -536,7 +494,7 @@
  
             case ID_FVAR:
             {
-                float val = readfloat(f);
+                float val = f->getlil<float>();
                 if(exists && id->minvalf <= id->maxvalf) setfvar(name, val);
                 if(dbgvars) conoutf(CON_DEBUG, "read fvar %s: %f", name, val);
                 break;
@@ -544,11 +502,11 @@
     
             case ID_SVAR:
             {
-                int slen = readushort(f);
+                int slen = f->getlil<ushort>();
                 string val;
-                gzread(f, val, min(slen, MAXSTRLEN-1));
+                f->read(val, min(slen, MAXSTRLEN-1));
                 val[min(slen, MAXSTRLEN-1)] = '\0';
-                if(slen >= MAXSTRLEN) gzseek(f, slen - (MAXSTRLEN-1), SEEK_CUR);
+                if(slen >= MAXSTRLEN) f->seek(slen - (MAXSTRLEN-1), SEEK_CUR);
                 if(exists) setsvar(name, val);
                 if(dbgvars) conoutf(CON_DEBUG, "read svar %s: %s", name, val);
                 break;
@@ -563,8 +521,8 @@
     int eif = 0;
     if(hdr.version>=16)
     {
-        int len = gzgetc(f);
-        gzread(f, gametype, len+1);
+        int len = f->getchar();
+        f->read(gametype, len+1);
     }
     if(strcmp(gametype, game::gameident())!=0)
     {
@@ -573,10 +531,10 @@
     }
     if(hdr.version>=16)
     {
-        eif = readushort(f);
-        int extrasize = readushort(f);
+        eif = f->getlil<ushort>();
+        int extrasize = f->getlil<ushort>();
         vector<char> extras;
-        loopj(extrasize) extras.add(gzgetc(f));
+        loopj(extrasize) extras.add(f->getchar());
         if(samegame) game::readgamedata(extras);
     }
     
@@ -586,13 +544,13 @@
     if(hdr.version<14)
     {
         uchar oldtl[256];
-        gzread(f, oldtl, sizeof(oldtl));
+        f->read(oldtl, sizeof(oldtl));
         loopi(256) texmru.add(oldtl[i]);
     }
     else
     {
-        ushort nummru = readushort(f);
-        loopi(nummru) texmru.add(readushort(f));
+        ushort nummru = f->getlil<ushort>();
+        loopi(nummru) texmru.add(f->getlil<ushort>());
     }
 
     freeocta(worldroot);
@@ -612,20 +570,17 @@
     {
         extentity &e = *entities::newentity();
         ents.add(&e);
-        gzread(f, &e, sizeof(entity));
-        endianswap(&e.o, sizeof(int), 3);
-        endianswap(&e.attr1, sizeof(short), 5);
+        f->read(&e, sizeof(entity));
+        lilswap(&e.o.x, 3);
+        lilswap(&e.attr1, 5);
         e.spawned = false;
         e.inoctanode = false;
         if(samegame)
         {
-            if(einfosize > 0) gzread(f, ebuf, einfosize);
+            if(einfosize > 0) f->read(ebuf, einfosize);
             entities::readent(e, ebuf); 
         }
-        else
-        {
-            loopj(eif) gzgetc(f);
-        }
+        else f->seek(eif, SEEK_CUR);
         if(hdr.version <= 14 && e.type >= ET_MAPMODEL && e.type <= 16)
         {
             if(e.type == 16) e.type = ET_MAPMODEL;
@@ -662,7 +617,7 @@
     if(hdr.numents > MAXENTS) 
     {
         conoutf(CON_WARN, "warning: map has %d entities", hdr.numents);
-        gzseek(f, (hdr.numents-MAXENTS)*(sizeof(entity) + einfosize), SEEK_CUR);
+        f->seek((hdr.numents-MAXENTS)*(sizeof(entity) + einfosize), SEEK_CUR);
     }
 
     renderprogress(0, "loading octree...");
@@ -686,24 +641,24 @@
         LightMap &lm = lightmaps.add();
         if(hdr.version >= 17)
         {
-            int type = gzgetc(f);
+            int type = f->getchar();
             lm.type = type&0x7F;
             if(hdr.version >= 20 && type&0x80)
             {
-                lm.unlitx = readushort(f);
-                lm.unlity = readushort(f);
+                lm.unlitx = f->getlil<ushort>();
+                lm.unlity = f->getlil<ushort>();
             }
         }
         if(lm.type&LM_ALPHA && (lm.type&LM_TYPE)!=LM_BUMPMAP1) lm.bpp = 4;
         lm.data = new uchar[lm.bpp*LM_PACKW*LM_PACKH];
-        gzread(f, lm.data, lm.bpp * LM_PACKW * LM_PACKH);
+        f->read(lm.data, lm.bpp * LM_PACKW * LM_PACKH);
         lm.finalize();
     }
 
     if(hdr.version >= 25 && hdr.numpvs > 0) loadpvs(f, hdr.numpvs);
     if(hdr.version >= 28 && hdr.blendmap) loadblendmap(f, hdr.blendmap);
 
-    gzclose(f);
+    delete f;
 
     conoutf("read map %s (%.1f seconds)", ogzname, (SDL_GetTicks()-loadingstart)/1000.0f);
     if(maptitle[0]) conoutf(CON_ECHO, "%s", maptitle);
@@ -763,9 +718,9 @@
 void writeobj(char *name)
 {
     s_sprintfd(fname)("%s.obj", name);
-    FILE *f = openfile(path(fname), "w"); 
+    stream *f = openfile(path(fname), "w"); 
     if(!f) return;
-    fprintf(f, "# obj file of sauerbraten level\n");
+    f->printf("# obj file of sauerbraten level\n");
     extern vector<vtxarray *> valist;
     loopv(valist)
     {
@@ -780,23 +735,23 @@
             vec v;
             if(floatvtx) (v = *(vec *)vert).div(1<<VVEC_FRAC); 
             else v = ((vvec *)vert)->tovec(va.o).add(0x8000>>VVEC_FRAC);
-            if(v.x != floor(v.x)) fprintf(f, "v %.3f ", v.x); else fprintf(f, "v %d ", int(v.x));
-            if(v.y != floor(v.y)) fprintf(f, "%.3f ", v.y); else fprintf(f, "%d ", int(v.y));
-            if(v.z != floor(v.z)) fprintf(f, "%.3f\n", v.z); else fprintf(f, "%d\n", int(v.z));
+            if(v.x != floor(v.x)) f->printf("v %.3f ", v.x); else f->printf("v %d ", int(v.x));
+            if(v.y != floor(v.y)) f->printf("%.3f ", v.y); else f->printf("%d ", int(v.y));
+            if(v.z != floor(v.z)) f->printf("%.3f\n", v.z); else f->printf("%d\n", int(v.z));
             vert += vtxsize;
         }
         ushort *tri = edata;
         loopi(va.tris)
         {
-            fprintf(f, "f");
-            for(int k = 0; k<3; k++) fprintf(f, " %d", tri[k]-va.verts-va.voffset);
+            f->printf("f");
+            for(int k = 0; k<3; k++) f->printf(" %d", tri[k]-va.verts-va.voffset);
             tri += 3;
-            fprintf(f, "\n");
+            f->printf("\n");
         }
         delete[] edata;
         delete[] vdata;
     }
-    fclose(f);
+    delete f;
 }  
     
 COMMAND(writeobj, "s"); 
Index: engine/blend.cpp
===================================================================
--- engine/blend.cpp	(revision 918)
+++ engine/blend.cpp	(working copy)
@@ -612,14 +612,14 @@
     renderblendbrush(brush->tex, x1, y1, x2 - x1, y2 - y1);
 }
 
-bool loadblendmap(gzFile f, uchar &type, BlendMapNode &node)
+bool loadblendmap(stream *f, uchar &type, BlendMapNode &node)
 {
-    type = gzgetc(f);
+    type = f->getchar();
     switch(type)
     {
         case BM_SOLID:
         {
-            int val = gzgetc(f);
+            int val = f->getchar();
             if(val<0 || val>0xFF) return false;
             node.solid = &bmsolids[val];
             break;
@@ -627,7 +627,7 @@
 
         case BM_IMAGE:
             node.image = new BlendMapImage;
-            if(gzread(f, node.image->data, sizeof(node.image->data)) != sizeof(node.image->data))
+            if(f->read(node.image->data, sizeof(node.image->data)) != sizeof(node.image->data))
                 return false;
             break;
 
@@ -646,23 +646,23 @@
     return true;
 }
 
-bool loadblendmap(gzFile f, int info)
+bool loadblendmap(stream *f, int info)
 {
     resetblendmap();
     return loadblendmap(f, blendmap.type, blendmap);
 }
 
-void saveblendmap(gzFile f, uchar type, BlendMapNode &node)
+void saveblendmap(stream *f, uchar type, BlendMapNode &node)
 {
-    gzputc(f, type);
+    f->putchar(type);
     switch(type)
     {
         case BM_SOLID:
-            gzputc(f, node.solid->val);
+            f->putchar(node.solid->val);
             break;
         
         case BM_IMAGE:
-            gzwrite(f, node.image->data, sizeof(node.image->data));
+            f->write(node.image->data, sizeof(node.image->data));
             break;
 
         case BM_BRANCH:
@@ -671,7 +671,7 @@
     }
 }
 
-void saveblendmap(gzFile f)
+void saveblendmap(stream *f)
 {
     saveblendmap(f, blendmap.type, blendmap);
 }
Index: engine/sound.cpp
===================================================================
--- engine/sound.cpp	(revision 918)
+++ engine/sound.cpp	(working copy)
@@ -351,6 +351,20 @@
 
 VAR(dbgsound, 0, 0, 1);
 
+static Mix_Chunk *loadwav(const char *name)
+{
+    Mix_Chunk *c = NULL;
+    stream *z = openzipfile(name, "rb");
+    if(z)
+    {
+        SDL_RWops *rw = z->rwops();
+        if(rw) c = Mix_LoadWAV_RW(rw, 1);
+        else delete z;
+    }
+    if(!c) c = Mix_LoadWAV(findfile(name, "rb"));
+    return c;
+}
+
 int playsound(int n, const vec *loc, extentity *ent, int loops, int fade, int chanid, int radius)
 {
     if(nosound || !soundvol) return -1;
@@ -396,8 +410,8 @@
         loopi(sizeof(exts)/sizeof(exts[0]))
         {
             s_sprintf(buf)("packages/sounds/%s%s", slot.sample->name, exts[i]);
-            const char *file = findfile(path(buf), "rb");
-            slot.sample->chunk = Mix_LoadWAV(file);
+            path(buf);
+            slot.sample->chunk = loadwav(buf);
             if(slot.sample->chunk) break;
         }
 
Index: shared/tools.cpp
===================================================================
--- shared/tools.cpp	(revision 918)
+++ shared/tools.cpp	(working copy)
@@ -2,283 +2,6 @@
 
 #include "cube.h"
 
-///////////////////////// file system ///////////////////////
-
-#ifndef WIN32
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <dirent.h>
-#endif
-
-string homedir = "";
-vector<char *> packagedirs;
-
-char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd)
-{
-    static string tmp;
-    if(prefix) s_strcpy(tmp, prefix);
-    else tmp[0] = '\0';
-    if(file[0]=='<')
-    {
-        const char *end = strrchr(file, '>');
-        if(end)
-        {
-            size_t len = strlen(tmp);
-            s_strncpy(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file)));
-            file = end+1;
-        }
-    }
-    if(cmd) s_strcat(tmp, cmd);
-    s_sprintfd(pname)("%s/%s", dir, file);
-    s_strcat(tmp, pname);
-    return tmp;
-}
-
-char *path(char *s)
-{
-    for(char *curpart = s;;)
-    {
-        char *endpart = strchr(curpart, '&');
-        if(endpart) *endpart = '\0';
-        if(curpart[0]=='<')
-        {
-            char *file = strrchr(curpart, '>');
-            if(!file) return s;
-            curpart = file+1;
-        }
-        for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV);
-        for(char *prevdir = NULL, *curdir = s;;)
-        {
-            prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir;
-            curdir = strchr(prevdir, PATHDIV);
-            if(!curdir) break;
-            if(prevdir+1==curdir && prevdir[0]=='.')
-            {
-                memmove(prevdir, curdir+1, strlen(curdir+1)+1);
-                curdir = prevdir;
-            }
-            else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV) 
-            {
-                if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue;
-                memmove(prevdir, curdir+4, strlen(curdir+4)+1); 
-                curdir = prevdir;
-            }
-        }
-        if(endpart)
-        {
-            *endpart = '&';
-            curpart = endpart+1;
-        }
-        else break;
-    }
-    return s;
-}
-
-char *path(const char *s, bool copy)
-{
-    static string tmp;
-    s_strcpy(tmp, s);
-    path(tmp);
-    return tmp;
-}
-
-const char *parentdir(const char *directory)
-{
-    const char *p = directory + strlen(directory);
-    while(p > directory && *p != '/' && *p != '\\') p--;
-    static string parent;
-    size_t len = p-directory+1;
-    s_strncpy(parent, directory, len);
-    return parent;
-}
-
-bool fileexists(const char *path, const char *mode)
-{
-    bool exists = true;
-    if(mode[0]=='w' || mode[0]=='a') path = parentdir(path);
-#ifdef WIN32
-    if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) exists = false;
-#else
-    if(access(path, R_OK | (mode[0]=='w' || mode[0]=='a' ? W_OK : 0)) == -1) exists = false;
-#endif
-    return exists;
-}
-
-bool createdir(const char *path)
-{
-    size_t len = strlen(path);
-    if(path[len-1]==PATHDIV)
-    {
-        static string strip;
-        path = s_strncpy(strip, path, len);
-    }
-#ifdef WIN32
-    return CreateDirectory(path, NULL)!=0;
-#else
-    return mkdir(path, 0777)==0;
-#endif
-}
-
-static void fixdir(char *dir)
-{
-    path(dir);
-    size_t len = strlen(dir);
-    if(dir[len-1]!=PATHDIV)
-    {
-        dir[len] = PATHDIV;
-        dir[len+1] = '\0';
-    }
-}
-
-void sethomedir(const char *dir)
-{
-    fixdir(s_strcpy(homedir, dir));
-}
-
-void addpackagedir(const char *dir)
-{
-    fixdir(packagedirs.add(newstringbuf(dir)));
-}
- 
-const char *findfile(const char *filename, const char *mode)
-{
-    static string s;
-    if(homedir[0])
-    {
-        s_sprintf(s)("%s%s", homedir, filename);
-        if(fileexists(s, mode)) return s;
-        if(mode[0]=='w' || mode[0]=='a')
-        {
-            string dirs;
-            s_strcpy(dirs, s);
-            char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV);
-            while(dir)
-            {
-                *dir = '\0';
-                if(!fileexists(dirs, "r") && !createdir(dirs)) return s;
-                *dir = PATHDIV;
-                dir = strchr(dir+1, PATHDIV);
-            }
-            return s;
-        } 
-    }
-    if(mode[0]=='w' || mode[0]=='a') return filename;
-    loopv(packagedirs)
-    {
-        s_sprintf(s)("%s%s", packagedirs[i], filename);
-        if(fileexists(s, mode)) return s;
-    }
-    return filename;
-}
-
-FILE *openfile(const char *filename, const char *mode)
-{
-    const char *found = findfile(filename, mode);
-    if(!found) return NULL;
-    return fopen(found, mode);
-}
-
-gzFile opengzfile(const char *filename, const char *mode)
-{
-    const char *found = findfile(filename, mode);
-    if(!found) return NULL;
-    return gzopen(found, mode);
-}
-
-char *loadfile(const char *fn, int *size)
-{
-    FILE *f = openfile(fn, "rb");
-    if(!f) return NULL;
-    fseek(f, 0, SEEK_END);
-    int len = ftell(f);
-    if(len<=0) { fclose(f); return NULL; }
-    fseek(f, 0, SEEK_SET);
-    char *buf = new char[len+1];
-    if(!buf) { fclose(f); return NULL; }
-    buf[len] = 0;
-    size_t rlen = fread(buf, 1, len, f);
-    fclose(f);
-    if(size_t(len)!=rlen)
-    {
-        delete[] buf;
-        return NULL;
-    }
-    if(size!=NULL) *size = len;
-    return buf;
-}
-
-bool listdir(const char *dir, const char *ext, vector<char *> &files)
-{
-    int extsize = ext ? (int)strlen(ext)+1 : 0;
-    #if defined(WIN32)
-    s_sprintfd(pathname)("%s\\*.%s", dir, ext ? ext : "*");
-    WIN32_FIND_DATA FindFileData;
-    HANDLE Find = FindFirstFile(path(pathname), &FindFileData);
-    if(Find != INVALID_HANDLE_VALUE)
-    {
-        do {
-            files.add(newstring(FindFileData.cFileName, (int)strlen(FindFileData.cFileName) - extsize));
-        } while(FindNextFile(Find, &FindFileData));
-        return true;
-    }
-    #else
-    string pathname;
-    s_strcpy(pathname, dir);
-    DIR *d = opendir(path(pathname));
-    if(d)
-    {
-        struct dirent *de;
-        while((de = readdir(d)) != NULL)
-        {
-            if(!ext) files.add(newstring(de->d_name));
-            else
-            {
-                int namelength = (int)strlen(de->d_name) - extsize;
-                if(namelength > 0 && de->d_name[namelength] == '.' && strncmp(de->d_name+namelength+1, ext, extsize-1)==0)
-                    files.add(newstring(de->d_name, namelength));
-            }
-        }
-        closedir(d);
-        return true;
-    }
-    #endif
-    else return false;
-}
-
-int listfiles(const char *dir, const char *ext, vector<char *> &files)
-{
-    int dirs = 0;
-    if(listdir(dir, ext, files)) dirs++;
-    string s;
-    if(homedir[0])
-    {
-        s_sprintf(s)("%s%s", homedir, dir);
-        if(listdir(s, ext, files)) dirs++;
-    }
-    loopv(packagedirs)
-    {
-        s_sprintf(s)("%s%s", packagedirs[i], dir);
-        if(listdir(s, ext, files)) dirs++;
-    }
-    return dirs;
-}
-
-///////////////////////// misc tools ///////////////////////
-
-void endianswap(void *memory, int stride, int length)   // little endian as storage format
-{
-    static const int littleendian = 1;
-    if(!*(const char *)&littleendian) loop(w, length) loop(i, stride/2)
-    {
-        uchar *p = (uchar *)memory+w*stride;
-        uchar t = p[i];
-        p[i] = p[stride-i-1];
-        p[stride-i-1] = t;
-    }
-}
-
-
 ////////////////////////// rnd numbers ////////////////////////////////////////
 
 #define N              (624)             
@@ -326,6 +49,8 @@
     return(y ^ (y >> 18));
 }
 
+///////////////////////// cryptography /////////////////////////////////
+
 #include "crypto.h"
 
 const ecjacobian ecjacobian::origin(gfield((gfield::digit)1), gfield((gfield::digit)1), gfield((gfield::digit)0));
Index: shared/zip.cpp
===================================================================
--- shared/zip.cpp	(revision 0)
+++ shared/zip.cpp	(revision 0)
@@ -0,0 +1,488 @@
+#include "cube.h"
+
+enum
+{
+    ZIP_LOCAL_FILE_SIGNATURE = 0x04034B50,
+    ZIP_LOCAL_FILE_SIZE      = 30,
+    ZIP_FILE_SIGNATURE       = 0x02014B50,
+    ZIP_FILE_SIZE            = 46,
+    ZIP_DIRECTORY_SIGNATURE  = 0x06054B50,
+    ZIP_DIRECTORY_SIZE       = 22
+};
+
+struct ziplocalfileheader
+{
+    uint signature;
+    ushort version, flags, compression, modtime, moddate;
+    uint crc32, compressedsize, uncompressedsize;
+    ushort namelength, extralength;
+};
+
+struct zipfileheader
+{
+    uint signature;
+    ushort version, needversion, flags, compression, modtime, moddate;
+    uint crc32, compressedsize, uncompressedsize; 
+    ushort namelength, extralength, commentlength, disknumber, internalattribs;
+    uint externalattribs, offset;
+};
+
+struct zipdirectoryheader
+{
+    uint signature;
+    ushort disknumber, directorydisk, diskentries, entries;
+    uint size, offset;
+    ushort commentlength;
+};
+
+struct zipfile
+{
+    char *name;
+    uint header, offset, size, compressedsize;
+
+    zipfile() : name(NULL), header(0), offset(~0U), size(0), compressedsize(0)
+    {
+    }
+    ~zipfile() 
+    { 
+        DELETEA(name); 
+    }
+};
+
+struct zipstream;
+
+struct ziparchive
+{
+    char *name;
+    FILE *data;
+    hashtable<const char *, zipfile> files;
+    int refcount, openfiles;
+    zipstream *owner;
+
+    ziparchive() : name(NULL), data(NULL), files(512), refcount(0), openfiles(0), owner(NULL)
+    {
+    }
+    ~ziparchive()
+    {
+        DELETEA(name);
+        if(data) { fclose(data); data = NULL; }
+    }
+};
+
+static bool findzipdirectory(FILE *f, zipdirectoryheader &hdr)
+{
+    if(fseek(f, 0, SEEK_END) < 0) return false;
+
+    uchar buf[1024], *src = NULL;
+    int len = 0, offset = ftell(f), end = max(offset - 0xFFFF - ZIP_DIRECTORY_SIZE, 0);
+    const uint signature = lilswap<uint>(ZIP_DIRECTORY_SIGNATURE);
+
+    while(offset > end)
+    {
+        int carry = min(len, ZIP_DIRECTORY_SIZE-1), next = min((int)sizeof(buf) - carry, offset - end);
+        offset -= next;
+        memmove(&buf[next], buf, carry);
+        if(next + carry < ZIP_DIRECTORY_SIZE || fseek(f, offset, SEEK_SET) < 0 || (int)fread(buf, 1, next, f) != next) return false;
+        len = next + carry;
+        uchar *search = &buf[next-1];
+        for(; search >= buf; search--) if(*(uint *)search == signature) break; 
+        if(search >= buf) { src = search; break; }
+    }        
+
+    if(&buf[len] - src < ZIP_DIRECTORY_SIZE) return false;
+
+    hdr.signature = lilswap(*(uint *)src); src += 4;
+    hdr.disknumber = lilswap(*(ushort *)src); src += 2;
+    hdr.directorydisk = lilswap(*(ushort *)src); src += 2;
+    hdr.diskentries = lilswap(*(ushort *)src); src += 2;
+    hdr.entries = lilswap(*(ushort *)src); src += 2;
+    hdr.size = lilswap(*(uint *)src); src += 4;
+    hdr.offset = lilswap(*(uint *)src); src += 4;
+    hdr.commentlength = lilswap(*(ushort *)src); src += 2;
+
+    if(hdr.signature != ZIP_DIRECTORY_SIGNATURE || hdr.disknumber != hdr.directorydisk || hdr.diskentries != hdr.entries) return false;
+
+    return true;
+}
+
+#ifndef STANDALONE
+VAR(dbgzip, 0, 0, 1);
+#endif
+
+static bool readzipdirectory(ziparchive &arch, FILE *f, int entries, int offset, int size)
+{
+    uchar *buf = new uchar[size], *src = buf;
+    if(fseek(f, offset, SEEK_SET) < 0 || (int)fread(buf, 1, size, f) != size) { delete[] buf; return false; }
+    loopi(entries)
+    {
+        if(src + ZIP_FILE_SIZE > &buf[size]) break;
+
+        zipfileheader hdr;
+        hdr.signature = lilswap(*(uint *)src); src += 4;
+        hdr.version = lilswap(*(ushort *)src); src += 2;
+        hdr.needversion = lilswap(*(ushort *)src); src += 2;
+        hdr.flags = lilswap(*(ushort *)src); src += 2;
+        hdr.compression = lilswap(*(ushort *)src); src += 2;
+        hdr.modtime = lilswap(*(ushort *)src); src += 2;
+        hdr.moddate = lilswap(*(ushort *)src); src += 2;
+        hdr.crc32 = lilswap(*(uint *)src); src += 4;
+        hdr.compressedsize = lilswap(*(uint *)src); src += 4;
+        hdr.uncompressedsize = lilswap(*(uint *)src); src += 4;
+        hdr.namelength = lilswap(*(ushort *)src); src += 2;
+        hdr.extralength = lilswap(*(ushort *)src); src += 2;
+        hdr.commentlength = lilswap(*(ushort *)src); src += 2;
+        hdr.disknumber = lilswap(*(ushort *)src); src += 2;
+        hdr.internalattribs = lilswap(*(ushort *)src); src += 2;
+        hdr.externalattribs = lilswap(*(uint *)src); src += 4;
+        hdr.offset = lilswap(*(uint *)src); src += 4;
+        if(hdr.signature != ZIP_FILE_SIGNATURE) break;
+        if(!hdr.namelength || !hdr.uncompressedsize || (hdr.compression && (hdr.compression != Z_DEFLATED || !hdr.compressedsize)))
+        {
+            src += hdr.namelength + hdr.extralength + hdr.commentlength;
+            continue;
+        }
+        if(src + hdr.namelength > &buf[size]) break;
+
+        string pname;
+        int namelen = min((int)hdr.namelength, (int)sizeof(pname)-1);
+        memcpy(pname, src, namelen);
+        pname[namelen] = '\0';
+        path(pname);
+        char *name = newstring(pname);
+    
+        zipfile &f = arch.files[name];
+        if(f.name)
+        {
+            delete[] name;
+            src += hdr.namelength + hdr.extralength + hdr.commentlength;
+            continue;
+        } 
+    
+        f.name = name;
+        f.header = hdr.offset;
+        f.size = hdr.uncompressedsize;
+        f.compressedsize = hdr.compression ? hdr.compressedsize : 0;
+#ifndef STANDALONE
+        if(dbgzip) conoutf(CON_DEBUG, "%s: file %s, size %d, compress %d, flags %x", arch.name, name, hdr.uncompressedsize, hdr.compression, hdr.flags);
+#endif
+
+        src += hdr.namelength + hdr.extralength + hdr.commentlength;
+    }
+    delete[] buf;
+
+    return true;
+}
+
+static bool readlocalfileheader(FILE *f, ziplocalfileheader &h, uint offset)
+{
+    fseek(f, offset, SEEK_SET);
+    uchar buf[ZIP_LOCAL_FILE_SIZE];
+    if(fread(buf, 1, ZIP_LOCAL_FILE_SIZE, f) != ZIP_LOCAL_FILE_SIZE)
+        return false;
+    uchar *src = buf;
+    h.signature = lilswap(*(uint *)src); src += 4;
+    h.version = lilswap(*(ushort *)src); src += 2;
+    h.flags = lilswap(*(ushort *)src); src += 2;
+    h.compression = lilswap(*(ushort *)src); src += 2;
+    h.modtime = lilswap(*(ushort *)src); src += 2;
+    h.moddate = lilswap(*(ushort *)src); src += 2;
+    h.crc32 = lilswap(*(uint *)src); src += 4;
+    h.compressedsize = lilswap(*(uint *)src); src += 4;
+    h.uncompressedsize = lilswap(*(uint *)src); src += 4;
+    h.namelength = lilswap(*(ushort *)src); src += 2;
+    h.extralength = lilswap(*(ushort *)src); src += 2;
+    if(h.signature != ZIP_LOCAL_FILE_SIGNATURE || !h.uncompressedsize) return false;
+    if(h.compression && !h.compressedsize) return false;
+    return true;
+}
+
+static vector<ziparchive *> archives;
+
+ziparchive *findzip(const char *name)
+{
+    loopv(archives) if(!strcmp(name, archives[i]->name)) return archives[i];
+    return NULL;
+}
+
+bool addzip(const char *name)
+{
+    const char *pname = path(name, true);
+    ziparchive *exists = findzip(pname);
+    if(exists)
+    {
+        exists->refcount++;
+        return true;
+    }
+ 
+    FILE *f = fopen(findfile(pname, "rb"), "rb");
+    if(!f) return false;
+    zipdirectoryheader h;
+    if(!findzipdirectory(f, h))
+    {
+        fclose(f);
+        return false;
+    }
+    
+    ziparchive *arch = new ziparchive;
+    arch->name = newstring(pname);
+    arch->data = f;
+    arch->refcount = 1;
+    readzipdirectory(*arch, f, h.entries, h.offset, h.size);
+    archives.add(arch);
+    return true;
+} 
+     
+bool removezip(const char *name)
+{
+    ziparchive *exists = findzip(path(name, true));
+    if(!exists) return false;
+    exists->refcount--;
+    if(exists->refcount <= 0) { archives.removeobj(exists); delete exists; }
+    return true;
+}
+
+struct zipstream : stream
+{
+    enum
+    {
+        BUFSIZE  = 3000
+    };
+
+    ziparchive *arch;
+    zipfile *info;
+    z_stream zfile;
+    uchar *buf;
+    int reading;
+
+    zipstream() : arch(NULL), info(NULL), buf(NULL), reading(-1)
+    {
+        zfile.zalloc = NULL;
+        zfile.zfree = NULL;
+        zfile.opaque = NULL;
+        zfile.next_in = zfile.next_out = NULL;
+        zfile.avail_in = zfile.avail_out = 0;
+    }
+
+    ~zipstream()
+    {
+        close();
+    }
+
+    void readbuf(uint size = BUFSIZE)
+    {
+        if(!zfile.avail_in) zfile.next_in = (Bytef *)buf;
+        size = min(size, uint(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
+        if(arch->owner != this)
+        {
+            arch->owner = NULL;
+            if(fseek(arch->data, reading, SEEK_SET) >= 0) arch->owner = this;
+            else return;
+        }
+        else if(ftell(arch->data) != reading) *(int *)0 = 0;
+        uint remaining = info->offset + info->compressedsize - reading;
+        int n = arch->owner == this ? fread(zfile.next_in + zfile.avail_in, 1, min(size, remaining), arch->data) : 0;
+        zfile.avail_in += n;
+        reading += n;
+    }
+
+    bool open(ziparchive *a, zipfile *f)
+    {
+        if(f->offset == ~0U)
+        {
+            ziplocalfileheader h;
+            a->owner = NULL;
+            if(!readlocalfileheader(a->data, h, f->header)) { f->size = 0; return false; }
+            f->offset = f->header + ZIP_LOCAL_FILE_SIZE + h.namelength + h.extralength;
+        }
+
+        if(f->compressedsize && inflateInit2(&zfile, -MAX_WBITS) != Z_OK) { reading = -1; return false; }
+
+        a->openfiles++;
+        arch = a;
+        info = f;
+        reading = f->offset;
+        if(f->compressedsize) buf = new uchar[BUFSIZE];
+        return true;
+    }
+
+    void stopreading()
+    {
+        if(reading < 0) return;
+#ifndef STANDALONE
+        if(dbgzip) conoutf(CON_DEBUG, info->compressedsize ? "%s: zfile.total_out %d, info->size %d" : "%s: reading %d, info->size %d", info->name, info->compressedsize ? zfile.total_out : reading - info->offset, info->size);
+#endif
+        if(info->compressedsize) inflateEnd(&zfile);
+        reading = -1;
+    }
+
+    void close()
+    {
+        stopreading();
+        DELETEA(buf);
+        if(arch) { arch->owner = NULL; arch->openfiles--; arch = NULL; }
+    }
+
+    int size() { return info->size; }
+    bool end() { return reading < 0; }
+    int tell() { return reading >= 0 ? (info->compressedsize ? zfile.total_out : reading - info->offset) : -1; }
+
+    bool seek(int pos, int whence)
+    {
+        if(reading < 0) return false;
+        if(!info->compressedsize)
+        {
+            switch(whence)
+            {
+                case SEEK_END: pos += info->offset + info->size; break; 
+                case SEEK_CUR: pos += reading; break;
+                case SEEK_SET: pos += info->offset; break;
+                default: return false;
+            } 
+            pos = clamp(pos, int(info->offset), int(info->offset + info->size));
+            arch->owner = NULL;
+            if(fseek(arch->data, pos, SEEK_SET) < 0) return false;
+            arch->owner = this;
+            reading = pos;
+            return true;
+        }
+ 
+        switch(whence)
+        {
+            case SEEK_END: pos += info->size; break; 
+            case SEEK_CUR: pos += zfile.total_out; break;
+            case SEEK_SET: break;
+            default: return false;
+        }
+
+        if(pos >= (int)info->size)
+        {
+            reading = info->offset + info->compressedsize;
+            zfile.next_in += zfile.avail_in;
+            zfile.avail_in = 0;
+            zfile.total_in = info->compressedsize; 
+            arch->owner = NULL;
+            return true;
+        }
+
+        if(pos < 0) return false;
+        if(pos >= (int)zfile.total_out) pos -= zfile.total_out;
+        else 
+        {
+            if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf))
+            {
+                zfile.avail_in += zfile.total_in;
+                zfile.next_in -= zfile.total_in;
+            }
+            else
+            {
+                arch->owner = NULL;
+                zfile.avail_in = 0;
+                zfile.next_in = NULL;
+                reading = info->offset;
+            }
+            inflateReset(&zfile);
+        }
+
+        uchar skip[512];
+        while(pos > 0)
+        {
+            int skipped = min(pos, (int)sizeof(skip));
+            if(read(skip, skipped) != skipped) { stopreading(); return false; }
+            pos -= skipped;
+        }
+
+        return true;
+    }
+
+    int read(void *buf, int len)
+    {
+        if(reading < 0 || !buf || !len) return 0;
+        if(!info->compressedsize)
+        {
+            if(arch->owner != this)
+            {
+                arch->owner = NULL;
+                if(fseek(arch->data, reading, SEEK_SET) < 0) { stopreading(); return 0; }
+                arch->owner = this;
+            }
+              
+            int n = fread(buf, 1, min(len, int(info->size + info->offset - reading)), arch->data);
+            reading += n;
+            if(n < len) stopreading();
+            return n;
+        }
+
+        zfile.next_out = (Bytef *)buf;
+        zfile.avail_out = len;
+        while(zfile.avail_out > 0)
+        {
+            if(!zfile.avail_in) readbuf(BUFSIZE);
+            int err = inflate(&zfile, Z_NO_FLUSH);
+            if(err != Z_OK) 
+            {
+#ifndef STANDALONE
+                if(err != Z_STREAM_END && dbgzip) conoutf(CON_DEBUG, "inflate error: %s", err);
+#endif
+                stopreading(); 
+                break; 
+            }
+        }
+        return len - zfile.avail_out;
+    }
+};
+
+stream *openzipfile(const char *name, const char *mode)
+{
+    for(; *mode; mode++) if(*mode=='w' || *mode=='a') return NULL;
+    loopvrev(archives)
+    {
+        ziparchive *arch = archives[i];
+        zipfile *f = arch->files.access(name);
+        if(!f) continue;
+        zipstream *s = new zipstream;
+        if(s->open(arch, f)) return s;
+        delete s;
+    }
+    return NULL;
+}
+
+int listzipfiles(const char *dir, const char *ext, vector<char *> &files)
+{
+    int extsize = ext ? (int)strlen(ext)+1 : 0, dirsize = strlen(dir), dirs = 0;
+    loopvrev(archives)
+    {
+        ziparchive *arch = archives[i];
+        int oldsize = files.length();
+        enumerate(arch->files, zipfile, f,
+        {
+            if(strncmp(f.name, dir, dirsize)) continue;
+            const char *name = f.name + dirsize;
+            if(name[0] == PATHDIV) name++;
+            if(strchr(name, PATHDIV)) continue;
+            if(!ext) files.add(newstring(name));
+            else
+            {
+                int namelength = (int)strlen(name) - extsize;
+                if(namelength > 0 && name[namelength] == '.' && strncmp(name+namelength+1, ext, extsize-1)==0)
+                    files.add(newstring(name, namelength));
+            }
+        });
+        if(files.length() > oldsize) dirs++;
+    }
+    return dirs;
+}
+
+#ifndef STANDALONE
+ICOMMAND(addzip, "s", (const char *name),
+{
+    if(addzip(name)) conoutf("added zip %s", name);
+    else conoutf(CON_ERROR, "failed loading zip %s", name);
+});
+
+ICOMMAND(removezip, "s", (const char *name),
+{
+    if(removezip(name)) conoutf("removed zip %s", name);
+    else conoutf(CON_ERROR, "failed unloading zip %s", name);
+});
+#endif
+
Index: shared/iengine.h
===================================================================
--- shared/iengine.h	(revision 918)
+++ shared/iengine.h	(working copy)
@@ -289,7 +289,7 @@
 
 extern void *getinfo(int i);
 extern void sendf(int cn, int chan, const char *format, ...);
-extern void sendfile(int cn, int chan, FILE *file, const char *format = "", ...);
+extern void sendfile(int cn, int chan, stream *file, const char *format = "", ...);
 extern void sendpacket(int cn, int chan, ENetPacket *packet, int exclude = -1);
 extern int getnumclients();
 extern uint getclientip(int n);
Index: shared/command.h
===================================================================
--- shared/command.h	(revision 918)
+++ shared/command.h	(working copy)
@@ -185,5 +185,4 @@
 #define IVARR(n, m, c, x)  _IVAR(n, m, c, x, , IDF_OVERRIDE)
 #define IVARFP(n, m, c, x, b) _IVAR(n, m, c, x, void changed() { b; }, IDF_PERSIST)
 #define IVARFR(n, m, c, x, b) _IVAR(n, m, c, x, void changed() { b; }, IDF_OVERRIDE)
-//#define ICALL(n, a) { char *args[] = a; icom_##n.run(args); }
-//
+
Index: shared/tools.h
===================================================================
--- shared/tools.h	(revision 918)
+++ shared/tools.h	(working copy)
@@ -635,6 +635,69 @@
 #endif 
 #endif
 
+#ifdef SDL_BYTEORDER
+#define endianswap16 SDL_Swap16
+#define endianswap32 SDL_Swap32
+#else
+inline ushort endianswap16(ushort n) { return (n<<8) | (n>>8); }
+inline uint endianswap32(uint n) { return (n<<24) | (n>>24) | ((n>>8)&0xFF00) | ((n<<8)&0xFF0000); }
+#endif
+template<class T> inline T endianswap(T n) { union { T t; uint i; } conv; conv.t = n; conv.i = endianswap32(conv.i); return conv.t; }
+template<> inline ushort endianswap<ushort>(ushort n) { return endianswap16(n); }
+template<> inline short endianswap<short>(short n) { return endianswap16(n); }
+template<> inline uint endianswap<uint>(uint n) { return endianswap32(n); }
+template<> inline int endianswap<int>(int n) { return endianswap32(n); }
+template<class T> inline void endianswap(T *buf, int len) { for(T *end = &buf[len]; buf < end; buf++) *buf = endianswap(*buf); }
+template<> inline void endianswap(float *buf, int len) { uint *src = (uint *)buf; for(uint *end = &src[len]; src < end; src++) *src = endianswap(*src); }
+template<class T> inline T endiansame(T n) { return n; }
+template<class T> inline void endiansame(T *buf, int len) {}
+#ifdef SDL_BYTEORDER
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+#define lilswap endiansame
+#define bigswap endianswap
+#else
+#define lilswap endianswap
+#define bigswap endiansame
+#endif
+#else
+const int islittleendian = 1;
+template<class T> inline T lilswap(T n) { return *(const uchar *)&islittleendian ? n : endianswap(n); }
+template<class T> inline void lilswap(T *buf, int len) { if(!*(const uchar *)&islittleendian) endianswap(buf, len); }
+template<class T> inline T bigswap(T n) { return *(const uchar *)&islittleendian ? endianswap(n) : n; }
+template<class T> inline void bigswap(T *buf, int len) { if(*(const uchar *)&islittleendian) endianswap(buf, len); }
+#endif
+
+struct stream
+{
+    virtual ~stream() {}
+    virtual void close() = 0;
+    virtual bool end() = 0;
+    virtual int tell() { return -1; }
+    virtual bool seek(int offset, int whence = SEEK_SET) { return -1; }
+    virtual int size();
+    virtual int read(void *buf, int len) { return 0; }
+    virtual int write(const void *buf, int len) { return 0; }
+    virtual int getchar() { uchar c; return read(&c, 1) == 1 ? c : -1; }
+    virtual bool putchar(int n) { uchar c = n; return write(&c, 1) == 1; }
+    virtual bool getline(char *str, int len);
+    virtual bool putstring(const char *str) { int len = strlen(str); return write(str, len) == len; }
+    virtual bool putline(const char *str) { return putstring(str) && putchar('\n'); }
+    virtual int scanf(const char *fmt, ...) { return -1; }
+    virtual int printf(const char *fmt, ...) { return -1; }
+
+    template<class T> bool put(T n) { return write(&n, sizeof(n)) == sizeof(n); }
+    template<class T> bool putlil(T n) { return put<T>(lilswap(n)); }
+    template<class T> bool putbig(T n) { return put<T>(bigswap(n)); }
+
+    template<class T> T get() { T n; return read(&n, sizeof(n)) == sizeof(n) ? n : 0; }
+    template<class T> T getlil() { return lilswap(get<T>()); }
+    template<class T> T getbig() { return bigswap(get<T>()); }
+
+#ifndef STANDALONE
+    SDL_RWops *rwops();
+#endif
+};
+
 extern char *makerelpath(const char *dir, const char *file, const char *prefix = NULL, const char *cmd = NULL);
 extern char *path(char *s);
 extern char *path(const char *s, bool copy);
@@ -644,12 +707,15 @@
 extern void sethomedir(const char *dir);
 extern void addpackagedir(const char *dir);
 extern const char *findfile(const char *filename, const char *mode);
-extern FILE *openfile(const char *filename, const char *mode);
-extern gzFile opengzfile(const char *filename, const char *mode);
+extern stream *openrawfile(const char *filename, const char *mode);
+extern stream *openzipfile(const char *filename, const char *mode);
+extern stream *openfile(const char *filename, const char *mode);
+extern stream *opentempfile(const char *mode);
+extern stream *opengzfile(const char *filename, const char *mode, stream *file = NULL, int level = Z_BEST_COMPRESSION);
 extern char *loadfile(const char *fn, int *size);
 extern bool listdir(const char *dir, const char *ext, vector<char *> &files);
 extern int listfiles(const char *dir, const char *ext, vector<char *> &files);
-extern void endianswap(void *, int, int);
+extern int listzipfiles(const char *dir, const char *ext, vector<char *> &files);
 extern void seedMT(uint seed);
 extern uint randomMT(void);
 
Index: shared/crypto.h
===================================================================
--- shared/crypto.h	(revision 918)
+++ shared/crypto.h	(working copy)
@@ -42,12 +42,11 @@
 
     void zero() { len = 0; }
 
-    void print(FILE *out) const 
+    void print(stream *out) const 
     { 
         vector<char> buf;
         printdigits(buf);
-        buf.add('\0');
-        fputs(buf.getbuf(), out);
+        out->write(buf.getbuf(), buf.length());
     }
 
     void printdigits(vector<char> &buf) const
Index: shared/igame.h
===================================================================
--- shared/igame.h	(revision 918)
+++ shared/igame.h	(working copy)
@@ -35,7 +35,7 @@
     extern void gameconnect(bool _remote);
     extern bool allowedittoggle();
     extern void edittoggled(bool on);
-    extern void writeclientinfo(FILE *f);
+    extern void writeclientinfo(stream *f);
     extern void toserver(char *text);
     extern void changemap(const char *name);
     extern void forceedit(const char *name);
Index: shared/stream.cpp
===================================================================
--- shared/stream.cpp	(revision 0)
+++ shared/stream.cpp	(revision 0)
@@ -0,0 +1,675 @@
+#include "cube.h"
+
+///////////////////////// file system ///////////////////////
+
+#ifndef WIN32
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#endif
+
+string homedir = "";
+vector<char *> packagedirs;
+
+char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd)
+{
+    static string tmp;
+    if(prefix) s_strcpy(tmp, prefix);
+    else tmp[0] = '\0';
+    if(file[0]=='<')
+    {
+        const char *end = strrchr(file, '>');
+        if(end)
+        {
+            size_t len = strlen(tmp);
+            s_strncpy(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file)));
+            file = end+1;
+        }
+    }
+    if(cmd) s_strcat(tmp, cmd);
+    s_sprintfd(pname)("%s/%s", dir, file);
+    s_strcat(tmp, pname);
+    return tmp;
+}
+
+char *path(char *s)
+{
+    for(char *curpart = s;;)
+    {
+        char *endpart = strchr(curpart, '&');
+        if(endpart) *endpart = '\0';
+        if(curpart[0]=='<')
+        {
+            char *file = strrchr(curpart, '>');
+            if(!file) return s;
+            curpart = file+1;
+        }
+        for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV);
+        for(char *prevdir = NULL, *curdir = s;;)
+        {
+            prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir;
+            curdir = strchr(prevdir, PATHDIV);
+            if(!curdir) break;
+            if(prevdir+1==curdir && prevdir[0]=='.')
+            {
+                memmove(prevdir, curdir+1, strlen(curdir+1)+1);
+                curdir = prevdir;
+            }
+            else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV)
+            {
+                if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue;
+                memmove(prevdir, curdir+4, strlen(curdir+4)+1);
+                curdir = prevdir;
+            }
+        }
+        if(endpart)
+        {
+            *endpart = '&';
+            curpart = endpart+1;
+        }
+        else break;
+    }
+    return s;
+}
+
+char *path(const char *s, bool copy)
+{
+    static string tmp;
+    s_strcpy(tmp, s);
+    path(tmp);
+    return tmp;
+}
+
+const char *parentdir(const char *directory)
+{
+    const char *p = directory + strlen(directory);
+    while(p > directory && *p != '/' && *p != '\\') p--;
+    static string parent;
+    size_t len = p-directory+1;
+    s_strncpy(parent, directory, len);
+    return parent;
+}
+
+bool fileexists(const char *path, const char *mode)
+{
+    bool exists = true;
+    if(mode[0]=='w' || mode[0]=='a') path = parentdir(path);
+#ifdef WIN32
+    if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) exists = false;
+#else
+    if(access(path, R_OK | (mode[0]=='w' || mode[0]=='a' ? W_OK : 0)) == -1) exists = false;
+#endif
+    return exists;
+}
+
+bool createdir(const char *path)
+{
+    size_t len = strlen(path);
+    if(path[len-1]==PATHDIV)
+    {
+        static string strip;
+        path = s_strncpy(strip, path, len);
+    }
+#ifdef WIN32
+    return CreateDirectory(path, NULL)!=0;
+#else
+    return mkdir(path, 0777)==0;
+#endif
+}
+
+static void fixdir(char *dir)
+{
+    path(dir);
+    size_t len = strlen(dir);
+    if(dir[len-1]!=PATHDIV)
+    {
+        dir[len] = PATHDIV;
+        dir[len+1] = '\0';
+    }
+}
+
+void sethomedir(const char *dir)
+{
+    fixdir(s_strcpy(homedir, dir));
+}
+
+void addpackagedir(const char *dir)
+{
+    fixdir(packagedirs.add(newstringbuf(dir)));
+}
+
+const char *findfile(const char *filename, const char *mode)
+{
+    static string s;
+    if(homedir[0])
+    {
+        s_sprintf(s)("%s%s", homedir, filename);
+        if(fileexists(s, mode)) return s;
+        if(mode[0]=='w' || mode[0]=='a')
+        {
+            string dirs;
+            s_strcpy(dirs, s);
+            char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV);
+            while(dir)
+            {
+                *dir = '\0';
+                if(!fileexists(dirs, "r") && !createdir(dirs)) return s;
+                *dir = PATHDIV;
+                dir = strchr(dir+1, PATHDIV);
+            }
+            return s;
+        }
+    }
+    if(mode[0]=='w' || mode[0]=='a') return filename;
+    loopv(packagedirs)
+    {
+        s_sprintf(s)("%s%s", packagedirs[i], filename);
+        if(fileexists(s, mode)) return s;
+    }
+    return filename;
+}
+
+bool listdir(const char *dir, const char *ext, vector<char *> &files)
+{
+    int extsize = ext ? (int)strlen(ext)+1 : 0;
+    #if defined(WIN32)
+    s_sprintfd(pathname)("%s\\*.%s", dir, ext ? ext : "*");
+    WIN32_FIND_DATA FindFileData;
+    HANDLE Find = FindFirstFile(path(pathname), &FindFileData);
+    if(Find != INVALID_HANDLE_VALUE)
+    {
+        do {
+            files.add(newstring(FindFileData.cFileName, (int)strlen(FindFileData.cFileName) - extsize));
+        } while(FindNextFile(Find, &FindFileData));
+        return true;
+    }
+    #else
+    string pathname;
+    s_strcpy(pathname, dir);
+    DIR *d = opendir(path(pathname));
+    if(d)
+    {
+        struct dirent *de;
+        while((de = readdir(d)) != NULL)
+        {
+            if(!ext) files.add(newstring(de->d_name));
+            else
+            {
+                int namelength = (int)strlen(de->d_name) - extsize;
+                if(namelength > 0 && de->d_name[namelength] == '.' && strncmp(de->d_name+namelength+1, ext, extsize-1)==0)
+                    files.add(newstring(de->d_name, namelength));
+            }
+        }
+        closedir(d);
+        return true;
+    }
+    #endif
+    else return false;
+}
+
+int listfiles(const char *dir, const char *ext, vector<char *> &files)
+{
+    int dirs = 0;
+    if(listdir(dir, ext, files)) dirs++;
+    string s;
+    if(homedir[0])
+    {
+        s_sprintf(s)("%s%s", homedir, dir);
+        if(listdir(s, ext, files)) dirs++;
+    }
+    loopv(packagedirs)
+    {
+        s_sprintf(s)("%s%s", packagedirs[i], dir);
+        if(listdir(s, ext, files)) dirs++;
+    }
+#ifndef STANDALONE
+    dirs += listzipfiles(dir, ext, files);
+#endif
+    return dirs;
+}
+
+#ifndef STANDALONE
+static int rwopsseek(SDL_RWops *rw, int offset, int whence)
+{
+    stream *f = (stream *)rw->hidden.unknown.data1;
+    if((!offset && whence==SEEK_CUR) || f->seek(offset, whence)) return f->tell();
+    return -1;
+}
+
+static int rwopsread(SDL_RWops *rw, void *buf, int size, int nmemb)
+{
+    stream *f = (stream *)rw->hidden.unknown.data1;
+    return f->read(buf, size*nmemb)/size;
+}
+
+static int rwopswrite(SDL_RWops *rw, const void *buf, int size, int nmemb)
+{
+    stream *f = (stream *)rw->hidden.unknown.data1;
+    return f->write(buf, size*nmemb)/size;
+}
+
+static int rwopsclose(SDL_RWops *rw)
+{
+    if(!rw) return 0;
+    stream *f = (stream *)rw->hidden.unknown.data1;
+    if(f) { delete f; rw->hidden.unknown.data1 = NULL; }
+    SDL_FreeRW(rw);
+    return 0;
+}
+
+SDL_RWops *stream::rwops()
+{
+    SDL_RWops *rw = SDL_AllocRW();
+    if(!rw) return NULL;
+    rw->hidden.unknown.data1 = this;
+    rw->seek = rwopsseek;
+    rw->read = rwopsread;
+    rw->write = rwopswrite;
+    rw->close = rwopsclose;
+    return rw;
+}
+#endif
+
+int stream::size()
+{
+    int pos = tell(), endpos;
+    if(pos < 0 || !seek(0, SEEK_END)) return -1;
+    endpos = tell();
+    return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1;
+}
+
+bool stream::getline(char *str, int len)
+{
+    loopi(len-1)
+    {
+        if(read(&str[i], 1) != 1) { str[i] = '\0'; return i > 0; }
+        else if(str[i] == '\n') { str[i+1] = '\0'; return true; }
+    }
+    if(len > 0) str[len-1] = '\0';
+    return true;
+}
+
+struct filestream : stream
+{
+    FILE *file;
+
+    filestream() : file(NULL) {}
+    ~filestream() { close(); }
+
+    bool open(const char *name, const char *mode)
+    {
+        if(file) return false;
+        file = fopen(name, mode);
+        return file!=NULL;
+    }
+
+    bool opentemp(const char *mode)
+    {
+        if(file) return false;
+        file = tmpfile();
+        return file!=NULL;
+    }
+
+    void close()
+    {
+        if(file) { fclose(file); file = NULL; }
+    }
+
+    bool end() { return feof(file); }
+    int tell() { return ftell(file); }
+    bool seek(int offset, int whence) { return fseek(file, offset, whence) >= 0; }
+    int read(void *buf, int len) { return fread(buf, 1, len, file); }
+    int write(const void *buf, int len) { return fwrite(buf, 1, len, file); }
+    int getchar() { return fgetc(file); }
+    bool putchar(int c) { return fputc(c, file)!=EOF; }
+    bool getline(char *str, int len) { return fgets(str, len, file)!=NULL; }
+    bool putstring(const char *str) { return fputs(str, file)!=EOF; }
+
+    int scanf(const char *fmt, ...)
+    {
+        va_list v;
+        va_start(v, fmt);
+        int result = vfscanf(file, fmt, v);
+        va_end(v);
+        return result;
+    }
+
+    int printf(const char *fmt, ...)
+    {
+        va_list v;
+        va_start(v, fmt);
+        int result = vfprintf(file, fmt, v);
+        va_end(v);
+        return result;
+    }
+};
+
+#ifndef STANDALONE
+VAR(dbggz, 0, 0, 1);
+#endif
+
+struct gzstream : stream
+{
+    enum
+    {
+        MAGIC1   = 0x1F,
+        MAGIC2   = 0x8B,
+        BUFSIZE  = 16384,
+        OS_UNIX  = 0x03
+    };
+
+    enum
+    {
+        F_ASCII    = 0x01,
+        F_CRC      = 0x02,
+        F_EXTRA    = 0x04,
+        F_NAME     = 0x08,
+        F_COMMENT  = 0x10,
+        F_RESERVED = 0xE0
+    };
+
+    stream *file;
+    z_stream zfile;
+    uchar *buf;
+    bool reading, writing, autoclose;
+    uint crc;
+    int headersize;
+
+    gzstream() : file(NULL), buf(NULL), reading(false), writing(false), autoclose(false), crc(0), headersize(0)
+    {
+        zfile.zalloc = NULL;
+        zfile.zfree = NULL;
+        zfile.opaque = NULL;
+        zfile.next_in = zfile.next_out = NULL;
+        zfile.avail_in = zfile.avail_out = 0;
+    }
+
+    ~gzstream()
+    {
+        close();
+    }
+
+    void writeheader()
+    {
+        uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX };
+        file->write(header, sizeof(header));
+    }
+
+    void readbuf(int size = BUFSIZE)
+    {
+        if(!zfile.avail_in) zfile.next_in = (Bytef *)buf;
+        size = min(size, int(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
+        int n = file->read(zfile.next_in + zfile.avail_in, size);
+        if(n > 0) zfile.avail_in += n;
+    }
+
+    int readbyte(int size = BUFSIZE)
+    {
+        if(!zfile.avail_in) readbuf(size);
+        if(!zfile.avail_in) return 0;
+        zfile.avail_in--;
+        return *(uchar *)zfile.next_in++;
+    }
+
+    void skipbytes(int n)
+    {
+        while(n > 0 && zfile.avail_in > 0)
+        {
+            int skipped = min(n, (int)zfile.avail_in);
+            zfile.avail_in -= skipped;
+            zfile.next_in += skipped;
+            n -= skipped;
+        }
+        if(n <= 0) return;
+        file->seek(n, SEEK_CUR);
+    }
+
+    bool checkheader()
+    {
+        readbuf(10);
+        if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED) return false;
+        int flags = readbyte();
+        if(flags & F_RESERVED) return false;
+        skipbytes(6);
+        if(flags & F_EXTRA)
+        {
+            int len = readbyte(512);
+            len |= readbyte(512)<<8;
+            skipbytes(len);
+        }
+        if(flags & F_NAME) while(readbyte(512));
+        if(flags & F_COMMENT) while(readbyte(512));
+        if(flags & F_CRC) skipbytes(2);
+        headersize = file->tell() - zfile.avail_in;
+        return zfile.avail_in > 0 || !file->end();
+    }
+
+    bool open(stream *f, const char *mode, bool needclose, int level)
+    {
+        if(file) return false;
+        for(; *mode; *mode++)
+        {
+            if(*mode=='r') { reading = true; break; }
+            else if(*mode=='w') { writing = true; break; }
+        }
+        if(reading)
+        {
+            if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK) reading = false;
+        }
+        else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, min(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK) writing = false;
+        if(!reading && !writing) return false;
+
+        autoclose = needclose;
+        file = f;
+        crc = crc32(0, NULL, 0);
+        buf = new uchar[BUFSIZE];
+
+        if(reading)
+        {
+            if(!checkheader()) { stopreading(); return false; }
+        }
+        else if(writing) writeheader();
+        return true;
+    }
+
+    void finishreading()
+    {
+        if(!reading) return;
+#ifndef STANDALONE
+        uint checkcrc = 0, checksize = 0;
+        loopi(4) checkcrc |= uint(readbyte()) << (i*8);
+        loopi(4) checksize |= uint(readbyte()) << (i*8);
+        if(dbggz)
+        {
+            if(checkcrc != crc)
+                conoutf(CON_DEBUG, "gzip crc check failed: read %X, calculated %X", checkcrc, crc);
+            if(checksize != zfile.total_out)
+                conoutf(CON_DEBUG, "gzip size check failed: read %d, calculated %d", checksize, zfile.total_out);
+        }
+#endif
+    }
+
+    void stopreading()
+    {
+        if(!reading) return;
+        inflateEnd(&zfile);
+        reading = false;
+    }
+
+    void finishwriting()
+    {
+        if(!writing) return;
+        for(;;)
+        {
+            int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK;
+            if(err != Z_OK && err != Z_STREAM_END) break;
+            flush();
+            if(err == Z_STREAM_END) break;
+        }
+        uchar trailer[8] =
+        {
+            crc&0xFF, (crc>>8)&0xFF, (crc>>16)&0xFF, (crc>>24)&0xFF,
+            zfile.total_in&0xFF, (zfile.total_in>>8)&0xFF, (zfile.total_in>>16)&0xFF, (zfile.total_in>>24)&0xFF
+        };
+        file->write(trailer, sizeof(trailer));
+    }
+
+    void stopwriting()
+    {
+        if(!writing) return;
+        deflateEnd(&zfile);
+        writing = false;
+    }
+
+    void close()
+    {
+        if(reading) finishreading();
+        stopreading();
+        if(writing) finishwriting();
+        stopwriting();
+        DELETEA(buf);
+        if(autoclose) DELETEP(file);
+    }
+
+    bool end() { return !reading && !writing; }
+    int tell() { return reading ? zfile.total_out : (writing ? zfile.total_in : -1); }
+
+    bool seek(int offset, int whence)
+    {
+        if(writing || !reading || whence == SEEK_END) return false;
+
+        if(whence == SEEK_CUR) offset += zfile.total_out;
+
+        if(offset >= (int)zfile.total_out) offset -= zfile.total_out;
+        else if(offset < 0 || !file->seek(headersize, SEEK_SET)) return false;
+        else
+        {
+            if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf))
+            {
+                zfile.avail_in += zfile.total_in;
+                zfile.next_in -= zfile.total_in;
+            }
+            else
+            {
+                zfile.avail_in = 0;
+                zfile.next_in = NULL;
+            }
+            inflateReset(&zfile);
+            crc = crc32(0, NULL, 0);
+        }
+
+        uchar skip[512];
+        while(offset > 0)
+        {
+            int skipped = min(offset, (int)sizeof(skip));
+            if(read(skip, skipped) != skipped) { stopreading(); return false; }
+            offset -= skipped;
+        }
+
+        return true;
+    }
+
+    int read(void *buf, int len)
+    {
+        if(!reading || !buf || !len) return 0;
+        zfile.next_out = (Bytef *)buf;
+        zfile.avail_out = len;
+        while(zfile.avail_out > 0)
+        {
+            if(!zfile.avail_in)
+            {
+                readbuf(BUFSIZE);
+                if(!zfile.avail_in) { stopreading(); break; }
+            }
+            int err = inflate(&zfile, Z_NO_FLUSH);
+            if(err == Z_STREAM_END) { crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); finishreading(); stopreading(); return len - zfile.avail_out; }
+            else if(err != Z_OK) { stopreading(); break; }
+        }
+        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out);
+        return len - zfile.avail_out;
+    }
+
+    bool flush()
+    {
+        if(zfile.next_out && zfile.avail_out < BUFSIZE)
+        {
+            if(file->write(buf, BUFSIZE - zfile.avail_out) != int(BUFSIZE - zfile.avail_out))
+                return false;
+        }
+        zfile.next_out = buf;
+        zfile.avail_out = BUFSIZE;
+        return true;
+    }
+
+    int write(const void *buf, int len)
+    {
+        if(!writing || !buf || !len) return 0;
+        zfile.next_in = (Bytef *)buf;
+        zfile.avail_in = len;
+        while(zfile.avail_in > 0)
+        {
+            if(!zfile.avail_out && !flush()) { stopwriting(); break; }
+            int err = deflate(&zfile, Z_NO_FLUSH);
+            if(err != Z_OK) { stopwriting(); break; }
+        }
+        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_in);
+        return len - zfile.avail_in;
+    }
+};
+
+
+stream *openrawfile(const char *filename, const char *mode)
+{
+    const char *found = findfile(filename, mode);
+    if(!found) return NULL;
+    filestream *file = new filestream;
+    if(!file->open(filename, mode)) { delete file; return NULL; }
+    return file;
+}
+
+stream *openfile(const char *filename, const char *mode)
+{
+#ifndef STANDALONE
+    stream *s = openzipfile(filename, mode);
+    if(s) return s;
+#endif
+    return openrawfile(filename, mode);
+}
+
+stream *opentempfile(const char *mode)
+{
+    filestream *file = new filestream;
+    if(!file->opentemp(mode)) { delete file; return NULL; }
+    return file;
+}
+
+stream *opengzfile(const char *filename, const char *mode, stream *file, int level)
+{
+    stream *source = file ? file : openfile(filename, mode);
+    if(!source) return NULL;
+    gzstream *gz = new gzstream;
+    if(!gz->open(source, mode, !file, level)) { if(!file) delete source; return NULL; }
+    return gz;
+}
+
+char *loadfile(const char *fn, int *size)
+{
+    stream *f = openfile(fn, "rb");
+    if(!f) return NULL;
+    int len = f->size();
+    if(len<=0) { delete f; return NULL; }
+    char *buf = new char[len+1];
+    if(!buf) { delete f; return NULL; }
+    buf[len] = 0;
+    int rlen = f->read(buf, len);
+    delete f;
+    if(len!=rlen)
+    {
+        delete[] buf;
+        return NULL;
+    }
+    if(size!=NULL) *size = len;
+    return buf;
+}
+
Index: vcpp/cube.vcproj
===================================================================
--- vcpp/cube.vcproj	(revision 918)
+++ vcpp/cube.vcproj	(working copy)
@@ -680,6 +680,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\shared\stream.cpp"
+				>
+			</File>
+			<File
 				RelativePath="..\shared\tools.cpp"
 				>
 			</File>
@@ -687,6 +691,10 @@
 				RelativePath="..\shared\tools.h"
 				>
 			</File>
+			<File
+				RelativePath="..\shared\zip.cpp"
+				>
+			</File>
 			<Filter
 				Name="text"
 				Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
Index: vcpp/sauerbraten.cbp
===================================================================
--- vcpp/sauerbraten.cbp	(revision 918)
+++ vcpp/sauerbraten.cbp	(working copy)
@@ -174,8 +174,10 @@
 			<Option compile="0" />
 			<Option link="0" />
 		</Unit>
+		<Unit filename="..\shared\stream.cpp" />
 		<Unit filename="..\shared\tools.cpp" />
 		<Unit filename="..\shared\tools.h" />
+		<Unit filename="..\shared\zip.cpp" />
 		<Unit filename="..\vcpp\SDL_win32_main.c">
 			<Option compilerVar="CC" />
 		</Unit>
Index: Makefile
===================================================================
--- Makefile	(revision 918)
+++ Makefile	(working copy)
@@ -18,8 +18,10 @@
 CLIENT_LIBS+= -lrt
 endif
 CLIENT_OBJS= \
+	shared/geom.o \
+	shared/stream.o \
 	shared/tools.o \
-	shared/geom.o \
+	shared/zip.o \
 	engine/3dgui.o \
 	engine/bih.o \
 	engine/blend.o \
@@ -79,6 +81,7 @@
 SERVER_LIBS= -Lenet -lenet -lz
 endif
 SERVER_OBJS= \
+	shared/stream-standalone.o \
 	shared/tools-standalone.o \
 	engine/server-standalone.o \
 	fpsgame/server-standalone.o

