]> git.lizzy.rs Git - plan9front.git/commitdiff
add games/wadfs
authorqwx <devnull@localhost>
Thu, 10 Aug 2017 09:39:18 +0000 (11:39 +0200)
committerqwx <devnull@localhost>
Thu, 10 Aug 2017 09:39:18 +0000 (11:39 +0200)
sys/man/4/wadfs [new file with mode: 0644]
sys/src/games/mkfile
sys/src/games/wadfs.c [new file with mode: 0644]

diff --git a/sys/man/4/wadfs b/sys/man/4/wadfs
new file mode 100644 (file)
index 0000000..dbde8ae
--- /dev/null
@@ -0,0 +1,192 @@
+.TH WADFS 4
+.SH NAME
+wadfs \- WAD file system
+.SH SYNOPSIS
+.B wadfs
+[
+.B -Dr
+] [
+.B -m
+.I mtpt
+] [
+.B -S
+.I srvname
+] [
+.I WAD
+]
+.SH DESCRIPTION
+.I Wadfs
+serves a file tree mounted at
+.I mtpt
+(default
+.BR /mnt/wad )
+that provides access to a
+.I WAD
+file's contents.
+.PP
+The command line options are:
+.TF "-S srvname"
+.TP
+.B -D
+Enable 9P debugging messages.
+.TP
+.B -r
+Set read-only file tree.
+.TP
+.BI -S \ srvname
+Post channel on
+.RI /srv/ srvname .
+.TP
+.BI -m \ mtpt
+Set mountpoint.
+.PD
+.PP
+A
+.I WAD
+is a concatenation of uncompressed files, referred to as lumps.
+A lump may contain either data,
+or be used as a marker to indicate the beginning or end of a section,
+segregating lumps of the same format.
+.PP
+.I Wadfs
+represents section start markers as directories,
+and regular lumps and end markers as files.
+For convenience, lump file names are in lower case,
+and are translated to the upper case internally.
+.PP
+At startup, if the path to a
+.I WAD
+file is provided as argument,
+.I wadfs
+will attempt to parse it and construct a file tree.
+Otherwise,
+.I wadfs
+starts with a blank tree instead.
+.PP
+Two additional files are provided in the file system's root directory:
+.L SIG
+and
+.LR WAD .
+Reading from and writing to
+.L SIG
+allows accessing and changing the
+.IR WAD 's
+type.
+The only possible values are
+.L PWAD
+(the default) and
+.LR IWAD .
+.PP
+.L WAD
+returns the new
+.I WAD
+file resulting from the recompilation of the lump tree.
+.SS "WAD file structure"
+There are few restrictions on the structure of
+.I WAD
+files.
+Excepting maps, sections can nest and may have no end marker,
+or one named differently than the section itself.
+Regular sections typically have one-letter names,
+and nested sections use the same name appended by a digit.
+By convention,
+lump names may only contain visible printing
+.SM ASCII
+characters,
+excepting lower-case letters.
+Map sections do not end at a marker but at the next non map lump,
+and use hardcoded names, depending on game version.
+.PP
+.I Wadfs
+imposes a number of additional restrictions on structure and naming:
+.IP • 3
+Lump names may not contain upper-case letters and the
+.L /
+character.
+.IP •
+A map section may only contain map lumps, which use hardcoded names.
+Ordering is significant, but is handled automatically.
+Map sections may not nest.
+.IP •
+Regular sections may not nest beyond one level,
+and may not contain more than one end marker.
+End markers may not exist outside of a section.
+Directory names omit the start marker's
+.L "_START"
+suffix.
+.IP •
+Excepting map lumps, no two lumps, including markers,
+may have the same name.
+.IP •
+Once created, a lump may not be renamed so as to change its type.
+.SS "Error recovery"
+Upon parsing the initial
+.I WAD
+file, if one of the restrictions for
+.I WAD
+file structure outlined in the sections above is not respected,
+a warning is issued, and the offending lump is potentially skipped.
+Some recovery is attempted,
+but one must systematically recheck the tree.
+When duplicate non marker lumps are encountered,
+each will overwrite the previous entry.
+.SH EXAMPLES
+Open
+.B doom2.wad
+and play a MUS file:
+.IP
+.EX
+% wadfs /sys/games/lib/doom/doom2.wad
+createfile SW18_7: file already exists
+% games/mus /mnt/wad/d_romero | games/midi
+.EE
+.PP
+Now create a blank
+.IR WAD ,
+then one section
+.LR FF ;
+copy a flat from
+.B doom2.wad
+to the directory,
+then rename the end marker to
+.L F_END
+to have the
+.B doom
+engine find the flat;
+finally, compile and save the new
+.I WAD
+file.
+.IP
+.EX
+% wadfs -m /mnt/wad2
+% cd /mnt/wad2
+% mkdir ff
+adding end marker FF_END
+% cp ../wad/f/f1/f_sky1 ff/
+% mv ff/ff_end ff/f_end
+% cp WAD /sys/games/lib/doom/sky.wad
+.EE
+.SH SOURCE
+.B /sys/src/games/wadfs.c
+.SH "SEE ALSO"
+.IR games (1),
+.IR mus (1)
+.SH HISTORY
+.I Wadfs
+first appeared in 9front (August, 2017).
+.SH BUGS
+Many
+.I WAD
+files in the wild do not conform to all the rules exposed above,
+in particular ones using
+.SM DeHackEd
+engine modifications.
+.IR WAD 's
+using end markers outside of a section,
+typically 
+.LR F_END ,
+will lose them.
+.PP
+Repairing broken
+.I WAD
+files can be a pain.
index c2c7c867d52ee70d361b1ee2c4ce40cc6514b3f4..0c33447aaf9d9a33f999b0d136ba0865dd3c60ab 100644 (file)
@@ -15,6 +15,7 @@ TARG=4s\
        packet\
        mandel\
        midi\
+       wadfs\
 
 OFILES=
 HFILES=
diff --git a/sys/src/games/wadfs.c b/sys/src/games/wadfs.c
new file mode 100644 (file)
index 0000000..56a6184
--- /dev/null
@@ -0,0 +1,861 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <bio.h>
+
+enum{
+       Nsig = 4,
+       Nhdr = Nsig+4+4,
+       Ndict = 4+4+8,
+       Nname = 8,
+       Nbuf = 8192,
+       Maxsz = 0x7fffffff - Nhdr
+};
+
+enum{
+       LTnil,
+       LTreg,
+       LTmap,
+       LTmrk,
+       LTend
+};
+typedef struct Lump Lump;
+struct Lump{
+       char name[Nname+1];
+       u32int ofs;
+       uchar *buf;
+       ulong nbuf;
+       int type;
+       File *f;
+       Lump *l;
+       Lump *lp;
+};
+Lump l1 = {.l = &l1, .lp = &l1}, *lumps = &l1;
+
+Biobuf *wad;
+u32int nlmp;
+File *ldir, *fsig, *fwad;
+int rdonly, dirty;
+
+Srv fs;
+
+char *mapn[] = {
+       "things", "linedefs", "sidedefs", "vertexes", "segs",
+       "ssectors", "nodes", "sectors", "reject", "blockmap"
+};
+
+void
+strupr(char *s, char *p)
+{
+       char c;
+
+       do{
+               c = *p++;
+               *s++ = toupper(c);
+       }while(c != 0);
+}
+
+void
+strlwr(char *s, char *p)
+{
+       char c;
+
+       do{
+               c = *p++;
+               *s++ = tolower(c);
+       }while(c != 0);
+}
+
+void
+link(Lump *l, Lump *lp, int len)
+{
+       l->lp = lp;
+       l->l = lp->l;
+       lp->l->lp = l;
+       lp->l = l;
+       nlmp++;
+       fwad->length += Ndict + len;
+}
+
+void
+unlink(Lump *l)
+{
+       if(l->l == nil)
+               return;
+       l->lp->l = l->l;
+       l->l->lp = l->lp;
+       l->l = nil;
+       nlmp--;
+       fwad->length -= Ndict + (l->f != nil ? l->f->length : 0);
+}
+
+void
+freelump(Lump *l)
+{
+       unlink(l);
+       free(l->buf);
+       free(l);
+}
+
+void
+readlump(Lump *l, uchar *p, long n)
+{
+       if(n <= 0)
+               return;
+       Bseek(wad, l->ofs, 0);
+       if(Bread(wad, p, n) != n)
+               fprint(2, "readlump: short read: %r\n");
+}
+
+void
+loadlump(File *f, ulong n)
+{
+       Lump *l;
+
+       l = f->aux;
+       if(f->length > n)
+               n = f->length;
+       l->buf = emalloc9p(n);
+       l->nbuf = n;
+       l->ofs = 0;
+       readlump(l, l->buf, f->length);
+}
+
+Lump *
+lastlump(Lump *lp)
+{
+       File *f, *dir;
+
+       for(dir=lp->f, f=lp->l->f; lp->l!=lumps; lp=lp->l, f=lp->l->f)
+               if(f->parent != dir && f->parent->parent != dir)
+                       break;
+       if(lp->type == LTend && lp->f->parent == dir)
+               lp = lp->lp;
+       return lp;
+}
+
+int
+nextmaplump(char *s)
+{
+       char **p;
+
+       for(p=mapn; p<mapn+nelem(mapn); p++)
+               if(strcmp(s, *p) == 0)
+                       return p-mapn;
+       return -1;
+}
+
+Lump *
+sortmap(Lump *lp, Lump *l)
+{
+       int ip, i;
+
+       i = nextmaplump(l->f->name);
+       for(; lp->l != lumps; lp=lp->l){
+               ip = nextmaplump(lp->l->f->name);
+               if(ip < 0 || ip > i)
+                       break;
+       }
+       return lp;
+}
+
+int
+ismaplump(char *s)
+{
+       return nextmaplump(s) >= 0;
+}
+
+int
+ismapname(char *s)
+{
+       if(strncmp(s, "map", 3) == 0)
+               return isdigit(s[3]) && isdigit(s[4]);
+       return s[0] == 'e' && isdigit(s[1])
+               && s[2] == 'm' && isdigit(s[3]);
+}
+
+int
+ismarkname(char *s, char *m)
+{
+       char *p;
+
+       p = strstr(s, m);
+       if(p == nil || p[strlen(m)] != 0)
+               return 0;
+       if(p - s > 2)
+               return 0;
+       return 1;
+}
+
+int
+validname(char *s, File *dir, int *type, int isnew, int isdir)
+{
+       int n;
+       char c, *p;
+       Lump *lp;
+
+       *type = LTnil;
+       n = strlen(s);
+       if(n < 1 || n > sizeof(lp->name)-1){
+               werrstr("invalid lump name");
+               return 0;
+       }
+       for(p=s+n-1; c=*p, p-->=s;)
+               if(!isprint(c) || isupper(c) || c == '/'){
+                       werrstr("invalid char %c in filename", c);
+                       return 0;
+               }
+       if(isnew && !ismaplump(s))
+               for(lp=lumps->l; lp!=lumps; lp=lp->l)
+                       if(cistrcmp(s, lp->name) == 0){
+                               werrstr("duplicate non map lump");
+                               return 0;
+                       }
+       *type = LTreg;
+       lp = dir->aux;
+       if(ismapname(s)){
+               *type = LTmap;
+               if(isnew && !isdir){
+                       werrstr("map marker not a directory");
+                       return 0;
+               }else if(dir != fs.tree->root){
+                       werrstr("nested map directory");
+                       return 0;
+               }
+               return 1;
+       }else if(ismarkname(s, "_end")){
+               *type = LTend;
+               if(dir == fs.tree->root || lp == nil || lp->type == LTmap){
+                       werrstr("orphaned end marker");
+                       return 0;
+               }
+               return 1;
+       }else if(ismarkname(s, "_start")){
+               *type = LTmrk;
+               if(isnew){
+                       werrstr("not allowed");
+                       return 0;
+               }
+               goto mrkchk;
+       }else if(isnew && isdir){
+               *type = LTmrk;
+               if(n > 2){
+                       werrstr("marker name too long");
+                       return 0;
+               }
+mrkchk:
+               if(dir->parent != fs.tree->root){
+                       werrstr("start marker nested too deep");
+                       return 0;
+               }else if(lp != nil && lp->type == LTmap){
+                       werrstr("start marker within map directory");
+                       return 0;
+               }
+               return 1;
+       }else if(ismaplump(s) ^ (lp != nil && lp->type == LTmap)){
+               werrstr("map lump outside of map directory");
+               return 0;
+       }
+       return 1;
+}
+
+int
+endldir(Lump *lp, Lump *le)
+{
+       char *s, name[sizeof lp->name];
+       Lump *l;
+       File *f;
+
+       l = emalloc9p(sizeof *l);
+       strcpy(l->name, lp->name);
+       s = strrchr(l->name, '_');
+       strcpy(s, "_END");
+       strlwr(name, l->name);
+       fprint(2, "adding end marker %s\n", l->name);
+       if(!validname(name, lp->f, &l->type, 1, 0) || l->type != LTend)
+               goto err;
+       f = createfile(lp->f, name, nil, lp->f->mode & 0666, l);
+       if(f == nil)
+               goto err;
+       closefile(f);
+       l->f = f;
+       link(l, le, 0);
+       return 0;
+err:
+       free(l);
+       return -1;
+}
+
+void
+accessfile(File *f, int mode)
+{
+       f->atime = time(nil);
+       if(mode & AWRITE){
+               f->mtime = f->atime;
+               f->qid.vers++;
+               dirty = 1;
+       }
+}
+
+void
+fswstat(Req *r)
+{
+       int type;
+       char *e;
+       File *f, *fp;
+       Lump *lp;
+
+       e = "permission denied";
+       if(rdonly)
+               goto err;
+       if(r->d.mode != ~0 || r->d.gid[0] != 0)
+               goto err;
+       f = r->fid->file;
+       lp = f->aux;
+       if(r->d.length != ~0 && r->d.length != f->length){
+               if(f == fsig || f->mode & DMDIR)
+                       goto err;
+               if(!hasperm(f, r->fid->uid, AWRITE))
+                       goto err;
+               if(r->d.length < 0){
+                       e = "invalid file length";
+                       goto err;
+               }
+               if(fwad->length - f->length + r->d.length >= Maxsz){
+                       e = "lump size exceeds wad limit";
+                       goto err;
+               }
+       }
+       if(r->d.name[0] != 0 && strcmp(r->d.name, f->name) != 0){
+               fp = f->parent;
+               if(fp == nil){
+                       e = "orphaned file";
+                       goto err;
+               }
+               if(!hasperm(fp, r->fid->uid, AWRITE))
+                       goto err;
+               if(!validname(r->d.name, fp, &type, 1, f->mode & DMDIR)){
+                       responderror(r);
+                       return;
+               }
+               if(lp->type != type){
+                       e = "incompatible lump type";
+                       goto err;
+               }
+               incref(fp);
+               fp = walkfile(fp, r->d.name);
+               if(fp != nil){
+                       e = "file already exists";
+                       goto err;
+               }
+       }
+
+       if(r->d.length != ~0 && r->d.length != f->length){
+               if(lp->buf == nil)
+                       loadlump(f, r->d.length);
+               fwad->length += r->d.length - f->length;
+               f->length = r->d.length;
+       }
+       if(r->d.name[0] != 0 && strcmp(r->d.name, f->name) != 0){
+               free(f->name);
+               f->name = estrdup9p(r->d.name);
+               strupr(lp->name, f->name);
+               if(lp->type == LTmrk)
+                       strcat(lp->name, "_START");
+       }
+       accessfile(f, AWRITE);
+       if(r->d.mtime != ~0)
+               f->mtime = r->d.mtime;
+       respond(r, nil);
+       return;
+err:
+       respond(r, e);
+}
+
+void
+fsremove(Req *r)
+{
+       File *f;
+       Lump *lp;
+
+       f = r->fid->file;
+       lp = f->aux;
+       if(f == fsig || f == fwad){
+               respond(r, "not allowed");
+               return;
+       }else if(lp->l->f != nil && lp->l->f->parent == f){
+               respond(r, "has children");
+               return;
+       }
+       unlink(f->aux);
+       dirty = 1;
+       respond(r, nil);
+}
+
+char *
+writesig(uchar *buf, char *s, vlong n)
+{
+       if(n > Nsig+1 || strncmp(s, "IWAD", Nsig) != 0 && strncmp(s, "PWAD", Nsig) != 0)
+               return "invalid wad signature";
+       memcpy(buf, s, Nsig);
+       dirty = 1;
+       return nil;
+}
+
+void
+fswrite(Req *r)
+{
+       vlong n, m, ofs, end;
+       File *f;
+       Lump *l;
+
+       f = r->fid->file;
+       n = r->ifcall.count;
+       ofs = r->ifcall.offset;
+       if(f->mode & DMAPPEND)
+               ofs = f->length;
+       end = ofs + n;
+       l = f->aux;
+       if(f == fsig){
+               respond(r, writesig(l->buf, r->ifcall.data, n));
+               return;
+       }
+       if(l->buf == nil)
+               loadlump(f, end + Nbuf);
+       if(end > l->nbuf){
+               m = l->nbuf + Nbuf > end ? l->nbuf + Nbuf : end;
+               if(fwad->length - l->nbuf + m >= Maxsz){
+                       respond(r, "lump size exceeds wad limit");
+                       return;
+               }
+               l->buf = erealloc9p(l->buf, m);
+               l->nbuf = m;
+       }
+       memcpy(l->buf + ofs, r->ifcall.data, n);
+       m = end - f->length;
+       if(m > 0){
+               f->length += m;
+               fwad->length += m;
+       }
+       accessfile(f, AWRITE);
+       r->ofcall.count = n;
+       respond(r, nil);
+}
+
+void
+makewad(void)
+{
+       vlong n;
+       uchar *p;
+       u32int ofs;
+       Lump *l, *lp;
+
+       l = fwad->aux;
+       free(l->buf);
+       l->buf = emalloc9p(fwad->length);
+       p = l->buf;
+       lp = fsig->aux;
+       memcpy(p, lp->buf, 4), p += 4;
+       PBIT32(p, nlmp), p += 8;
+       for(lp=lumps->l; lp!=lumps; p+=n, lp=lp->l){
+               n = lp->f->length;
+               if(lp->buf != nil)
+                       memcpy(p, lp->buf, n);
+               else
+                       readlump(lp, p, n);
+       }
+       PBIT32(l->buf + 8, p - l->buf);
+       ofs = Nhdr;
+       for(lp=lumps->l; lp!=lumps; ofs+=n, lp=lp->l){
+               n = lp->f->length;
+               PBIT32(p, ofs), p += 4;
+               PBIT32(p, n), p += 4;
+               memcpy(p, lp->name, 8), p += 8;
+       }
+       dirty = 0;
+}
+
+void
+fsread(Req *r)
+{
+       vlong n, ofs, end;
+       File *f;
+       Lump *l;
+
+       f = r->fid->file;
+       l = f->aux;
+       ofs = r->ifcall.offset + l->ofs;
+       end = l->ofs + f->length;
+       n = r->ifcall.count;
+       if(ofs + n >= end)
+               n = end - ofs;
+       if(n <= 0){
+               r->ofcall.count = 0;
+               respond(r, nil);
+               return;
+       }
+       if(f == fwad && dirty)
+               makewad();
+       if(l->buf != nil)
+               memcpy(r->ofcall.data, l->buf+ofs, n);
+       else{
+               Bseek(wad, ofs, 0);
+               n = Bread(wad, r->ofcall.data, n);
+               if(n < 0){
+                       responderror(r);
+                       return;
+               }
+       }
+       accessfile(f, AREAD);
+       r->ofcall.count = n;
+       respond(r, nil);
+}
+
+int
+addlump(Lump *l, File *dir)
+{
+       Lump *lp;
+
+       lp = lumps->lp;
+       if(dir != fs.tree->root){
+               lp = dir->aux;
+               lp = lp->type == LTmap ? sortmap(lp, l) : lastlump(lp);
+       }
+       if(l->type == LTend && lp->l->type == LTend && lp->l->f->parent == dir){
+               werrstr("an end marker already exists");
+               return -1;
+       }
+       link(l, lp, 0);
+       if(l->type == LTmrk){
+               strcat(l->name, "_START");
+               if(endldir(l, l) < 0)
+                       return -1;
+       }else if(l->type == LTreg){
+               l->buf = emalloc9p(Nbuf);
+               l->nbuf = Nbuf;
+       }
+       dirty = 1;
+       return 0;
+}
+
+Lump *
+createlump(char *s, File *dir, int ismark)
+{
+       int type;
+       Lump *l;
+
+       if(!validname(s, dir, &type, 1, ismark))
+               return nil;
+       l = emalloc9p(sizeof *l);
+       l->type = type;
+       strupr(l->name, s);
+       return l;
+}
+
+void
+fscreate(Req *r)
+{
+       int p;
+       File *f;
+       Lump *l;
+
+       f = r->fid->file;
+       p = r->ifcall.perm;
+       if(p & DMDIR)
+               p = p & ~0777 | p & f->mode & 0777;
+       else
+               p = p & ~0666 | p & f->mode & 0666;
+       l = createlump(r->ifcall.name, f, p & DMDIR);
+       if(l == nil)
+               goto err;
+       f = createfile(f, r->ifcall.name, r->fid->uid, p, l);
+       if(f == nil){
+               free(l);
+               goto err;
+       }
+       l->f = f;
+       if(addlump(l, r->fid->file) < 0){
+               removefile(f);
+               goto err;
+       }
+       r->fid->file = f;
+       r->ofcall.qid = f->qid;
+       respond(r, nil);
+       return;
+err:
+       responderror(r);
+}
+
+void
+fsopen(Req *r)
+{
+       File *f;
+
+       f = r->fid->file;
+       if((f->mode & DMAPPEND) == 0 && (r->ifcall.mode & OTRUNC) != 0
+       && f != fsig){
+               fwad->length -= f->length;
+               f->length = 0;
+               dirty = 1;
+       }
+       respond(r, nil);
+}
+
+void
+fsdestroyfile(File *f)
+{
+       freelump(f->aux);
+}
+
+Srv fs = {
+       .open = fsopen,
+       .create = fscreate,
+       .read = fsread,
+       .write = fswrite,
+       .remove = fsremove,
+       .wstat = fswstat
+};
+
+int
+get32(Biobuf *bf, u32int *v)
+{
+       int n;
+       uchar u[4];
+
+       n = Bread(bf, u, sizeof u);
+       if(n != sizeof u)
+               return -1;
+       *v = GBIT32(u);
+       return 0;
+}
+
+File *
+replacefile(File *dir, char *fname, int mode, Lump *l)
+{
+       File *f;
+
+       incref(dir);
+       f = walkfile(dir, fname);
+       if(f == nil)
+               return nil;
+       if(removefile(f) < 0)
+               return nil;
+       f = createfile(dir, fname, nil, mode, l);
+       return f;
+}
+
+void
+addsigfile(char *sig)
+{
+       int n;
+       Lump *l;
+       File *f;
+
+       n = strlen(sig) + 1;
+       l = emalloc9p(sizeof *l);
+       l->buf = (uchar *)estrdup9p(sig);
+       l->buf[n-1] = '\n';
+       f = createfile(fs.tree->root, "SIG", nil, rdonly ? 0444 : 0666, l);
+       if(f == nil)
+               sysfatal("addsigfile: %r");
+       else{
+               fsig = f;
+               f->length = n;
+       }
+}
+
+void
+addwadfile(void)
+{
+       Lump *l;
+       File *f;
+
+       l = emalloc9p(sizeof *l);
+       f = createfile(fs.tree->root, "WAD", nil, 0444, l);
+       if(f == nil)
+               sysfatal("addwadfile: %r");
+       else{
+               fwad = f;
+               f->length = Nhdr;
+       }
+       dirty++;
+
+}
+
+void
+checkends(void)
+{
+       Lump *lp;
+
+       if(ldir == fs.tree->root)
+               return;
+       lp = ldir->aux;
+       if(lp->type != LTmap && endldir(lp, lastlump(lp)) < 0)
+               fprint(2, "checkends: %r\n");
+       ldir = ldir->parent;
+       checkends();
+}
+
+int
+addfile(Lump *l, u32int *len, int mode)
+{
+       int err;
+       char fname[sizeof l->name], *s;
+       Lump *lp;
+       File *f;
+
+       *len = 0;
+       if(get32(wad, &l->ofs) < 0 || get32(wad, len) < 0)
+               return -1;
+       if(Bread(wad, l->name, sizeof(l->name)-1) != sizeof(l->name)-1)
+               return -1;
+       strlwr(fname, l->name);
+
+       lp = ldir->aux;
+       err = !validname(fname, ldir, &l->type, 0, 0);
+       switch(l->type){
+       case LTmap:
+               closefile(ldir);
+               ldir = fs.tree->root;
+               if(err && lp != nil && lp->type != LTmap){
+                       fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len);
+                       if(endldir(lp, lastlump(lp)) < 0)
+                               fprint(2, "endldir: %r\n");
+               }
+               mode |= DMDIR|0111;
+               *len = 0;
+               break;
+       case LTmrk:
+               if(err){
+                       if(lp != nil && lp->type == LTmap){
+                               closefile(ldir);
+                               ldir = fs.tree->root;
+                       }else{
+                               fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len);
+                               if(endldir(lp, lastlump(lp)) < 0)
+                                       return -1;
+                               ldir = ldir->parent;
+                       }
+               }
+               s = strrchr(fname, '_');
+               *s = 0;
+               mode |= DMDIR|0111;
+               *len = 0;
+               break;
+       case LTend:
+               if(err){
+                       ldir = ldir->parent;
+                       return -1;
+               }
+               *len = 0;
+               break;
+       case LTreg:
+               if(err){
+                       if(ismaplump(fname))
+                               fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len);
+                       else
+                               ldir = fs.tree->root;
+               }
+               break;
+       default:
+               return -1;
+       }
+
+       f = createfile(ldir, fname, nil, mode, l);
+       if(f == nil){
+               fprint(2, "createfile %s: %r\n", l->name);
+               if(mode & DMDIR)
+                       return -1;
+               f = replacefile(ldir, fname, mode, l);
+               if(f == nil)
+                       return -1;
+       }
+       if(mode & DMDIR)
+               ldir = f;
+       else if(l->type == LTend)
+               ldir = ldir->parent;
+       else
+               closefile(f);
+       f->length = *len;
+       l->f = f;
+       return 0;
+}
+
+void
+parsewad(void)
+{
+       int n, ne, mode;
+       u32int len;
+       Lump *l;
+
+       mode = rdonly ? 0444 : 0666;
+       ldir = fs.tree->root;
+       for(n=0, ne=nlmp, nlmp=0; n<ne; n++){
+               l = emalloc9p(sizeof *l);
+               if(addfile(l, &len, mode) < 0){
+                       fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, len);
+                       free(l);
+                       continue;
+               }
+               link(l, lumps->lp, len);
+       }
+       checkends();
+}
+
+void
+wadinfo(char *sig)
+{
+       int n;
+       u32int dictofs;
+
+       n = Bread(wad, sig, Nsig);
+       if(n != Nsig)
+               sysfatal("readwad: short read: %r");
+       sig[4] = 0;
+       if(strcmp(sig, "IWAD") != 0 && strcmp(sig, "PWAD") != 0)
+               sysfatal("invalid wad signature");
+       if(get32(wad, &nlmp) < 0 || get32(wad, &dictofs) < 0)
+               sysfatal("wadinfo: %r");
+       Bseek(wad, dictofs, 0);
+}
+
+void
+usage(void)
+{
+       fprint(2, "usage: %s [-Dr] [-m mtpt] [-S srvname] [wad]\n", argv0);
+       exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+       int fl, p;
+       char *mtpt, *srvname, sig[Nsig+1] = "PWAD";
+
+       mtpt = "/mnt/wad";
+       srvname = nil;
+       fl = MREPL|MCREATE;
+       p = DMDIR|0777;
+       ARGBEGIN{
+       case 'D': chatty9p++; break;
+       case 'S': srvname = EARGF(usage()); break;
+       case 'm': mtpt = EARGF(usage()); break;
+       case 'r': rdonly++; p &= ~0222; fl &= ~MCREATE; break;
+       default: usage();
+       }ARGEND
+       if(*argv != nil){
+               wad = Bopen(*argv, OREAD);
+               if(wad == nil)
+                       sysfatal("Bopen: %r");
+               wadinfo(sig);
+       }
+       fs.tree = alloctree(nil, nil, p, fsdestroyfile);
+       addsigfile(sig);
+       addwadfile();
+       parsewad();
+       postmountsrv(&fs, srvname, mtpt, fl);
+       exits(nil);
+}