ref: acd94a2a4561fe213b2706681c40b199a1ecf164
author: qwx <[email protected]>
date: Mon May 27 20:24:08 EDT 2019
import from 9front
--- /dev/null
+++ b/dpic.c
@@ -1,0 +1,132 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <bio.h>
+
+int dx = 64, dy = 64;
+Biobuf *bi, *bo;
+u32int pal[256];
+
+u8int
+get8(void)
+{
+ uchar v;
+
+ if(Bread(bi, &v, 1) != 1)
+ sysfatal("get8: short read");
+ return v;
+}
+
+u16int
+get16(void)
+{
+ u8int v;
+
+ v = get8();
+ return get8() << 8 | v;
+}
+
+u32int
+get32(void)
+{
+ u16int v;
+
+ v = get16();
+ return get16() << 16 | v;
+}
+
+u32int*
+unpic(void)
+{
+ int n, h;
+ u32int *p, *d, *cols, *buf;
+
+ dx = get16();
+ dy = get16();
+ cols = mallocz(dx * sizeof *cols, 1);
+ buf = mallocz(dx * dy * sizeof *buf, 1);
+ if(cols == nil || buf == nil)
+ sysfatal("mallocz: %r");
+ get32();
+ for(p=cols; p<cols+dx; p++)
+ *p = get32();
+ for(p=cols; p<cols+dx; p++){
+ Bseek(bi, *p, 0);
+ for(;;){
+ if((h = get8()) == 0xff)
+ break;
+ n = get8();
+ get8();
+ for(d=buf+(p-cols)+h*dx; n-->0; d+=dx)
+ *d = pal[get8()];
+ get8();
+ }
+ }
+ free(cols);
+ return buf;
+}
+
+u32int*
+unflat(void)
+{
+ u32int *p;
+ static u32int buf[4096];
+
+ for(p=buf; p<buf+nelem(buf); p++)
+ *p = pal[get8()];
+ return buf;
+}
+
+void
+getpal(char *f)
+{
+ uchar u[3];
+ u32int *p;
+ Biobuf *bp;
+
+ if((bp = Bopen(f, OREAD)) == nil)
+ sysfatal("getpal: %r");
+ for(p=pal; p<pal+nelem(pal); p++){
+ if(Bread(bp, u, 3) != 3)
+ sysfatal("getpal: short read: %r");
+ *p = u[2]<<16 | u[1]<<8 | u[0];
+ }
+ Bterm(bp);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-f] [-p palette] pic\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd, flat;
+ char *p, c[9];
+ u32int *buf;
+
+ flat = 0;
+ p = "/mnt/wad/playpal";
+ ARGBEGIN{
+ case 'f': flat = 1; break;
+ case 'p': p = EARGF(usage()); break;
+ default: usage();
+ }ARGEND
+ if(*argv == nil)
+ usage();
+ if((fd = open(*argv, OREAD)) < 0)
+ sysfatal("open: %r");
+ getpal(p);
+ bi = Bfdopen(fd, OREAD);
+ bo = Bfdopen(1, OWRITE);
+ if(bi == nil || bo == nil)
+ sysfatal("Bfdopen: %r");
+ buf = flat ? unflat() : unpic();
+ Bprint(bo, "%11s %11d %11d %11d %11d ",
+ chantostr(c, XBGR32), 0, 0, dx, dy);
+ Bwrite(bo, buf, dx * dy * sizeof *buf);
+ exits(nil);
+}
--- /dev/null
+++ b/man/1/dpic
@@ -1,0 +1,118 @@
+.TH DPIC 1
+.SH NAME
+dpic, todpic \- Doom picture decoder and encoder
+.SH SYNOPSIS
+.B dpic
+[
+.B -f
+] [
+.B -p
+.I palette
+] [
+.I pic
+]
+.PP
+.B todpic
+[
+.B -fw
+] [
+.B -b
+.I bgcol
+] [
+.B -p
+.I palette
+] [
+.I image
+]
+.SH DESCRIPTION
+.I Dpic
+reads a doom picture formatted image (default standard input),
+converts it to a Plan 9
+.IR image (6)
+and writes it to standard out.
+.I Todpic
+does the opposite transformation.
+.PP
+A color palette is needed for the process;
+its location is set to
+.B /mnt/wad/playpal
+by default.
+This may be overridden with the
+.B -p
+command line option.
+Both programs also accept an
+.B -f
+flag to indicate processing a doom 64x64 flat picture.
+.PP
+When encoding a doom picture,
+x and y offsets are set to the input's top left corner coordinates.
+The
+.B -w
+flag sets the offsets so as to center the picture when drawn by the doom engine,
+which is useful for wall patches.
+The
+.B -b
+option sets the RGB24 color to signal transparent pixels,
+.L 0x00FFFF
+by default.
+.SH EXAMPLES
+Create a patch
+.I WAD
+(see
+.IR wadfs (4))
+replacing a sky texture.
+First, create a 256x128 image, mirror it, and convert it for use with
+.IR tweak (1).
+.IP
+.EX
+% png -9t tuttleglenda.png \\
+ | resample -x 128 -y 128 \\
+ | crop -r 0 0 256 128 \\
+ | rotate -l \\
+ | iconv -c m8 > tuttlesky
+.EE
+.PP
+Next, use
+.IR tweak (1)
+to tile the 128x128 picture.
+Then, mount an
+.I IWAD
+containing the base color palette, convert to a doom picture,
+create a patch
+.IR WAD ,
+then launch doom using it.
+.IP
+.EX
+% games/wadfs /sys/games/lib/doom/doom2.wad
+createfile SW18_7: file already exists
+% games/wadfs -m /mnt/new
+% games/todpic tuttlesky > /mnt/new/rsky1
+% cp /mnt/new/WAD tuttle.wad
+% games/doom -file tuttle.wad
+.EE
+.PP
+Create a crude catclock weapon sprite.
+.IP
+.EX
+% games/wadfs /sys/games/lib/doom/doom2.wad
+createfile SW18_7: file already exists
+% mkdir /mnt/new/s
+adding end marker S_END
+% cp /mnt/wad/s/* /mnt/new/s/
+% crop -r 0 0 114 120 -t -120 -60 catclock.bit \\
+ | games/todpic -b 0xffffff > /mnt/new/s/punga0
+% games/doom -file /mnt/new/WAD
+.EE
+.SH SOURCE
+.B /sys/src/games/dpic.c
+.br
+.B /sys/src/games/todpic.c
+.SH "SEE ALSO"
+.IR games (1),
+.IR tweak (1),
+.IR wadfs (4)
+.SH HISTORY
+.I Dpic
+and
+.I todpic
+first appeared in 9front (July, 2018).
--- /dev/null
+++ b/man/1/mus
@@ -1,0 +1,23 @@
+.TH MUS 1
+.SH NAME
+mus \- MUS to MIDI converter
+.SH SYNOPSIS
+.B games/mus
+[
+.I musfile
+]
+.SH DESCRIPTION
+The MUS format is a simplified MIDI music format used in doom
+and several related games.
+.PP
+.I Mus
+decodes MIDI music encoded in MUS format, either from
+.B musfile
+or from standard input, and produces a MIDI format file on standard output.
+.SH "SEE ALSO"
+.IR games (1)
+.SH SOURCE
+.B /sys/src/games/mus.c
+.SH HISTORY
+.I Mus
+first appeared in 9front (September, 2015).
--- /dev/null
+++ b/man/4/wadfs
@@ -1,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
+% games/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
+% games/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.
--- /dev/null
+++ b/mus.c
@@ -1,0 +1,219 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct Trk Trk;
+struct Trk{
+ u32int len;
+ uchar *dat;
+ uchar *p;
+ uchar *end;
+ uchar v[16];
+ int done;
+};
+Trk t;
+uchar *mcmd, *mp, *me;
+int fd;
+
+#define PBIT16(p,v) (p)[0]=(v);(p)[1]=(v)>>8
+#define BBIT32(p,v) (p)[3]=(v);(p)[2]=(v)>>8;(p)[1]=(v)>>16;(p)[0]=(v)>>24
+
+void
+eread(int fd, void *u, long n)
+{
+ if(readn(fd, u, n) != n)
+ sysfatal("readn: %r");
+}
+
+uchar
+r8(void)
+{
+ return *t.p++;
+}
+
+void
+delay(void)
+{
+ uchar v;
+
+ do{
+ v = r8();
+ *mp++ = v;
+ }while(v & 0x80);
+}
+
+void
+putcmd(uchar *cmd, int n)
+{
+ if(mp + n >= me){
+ me += 8192;
+ mcmd = realloc(mcmd, me - mcmd);
+ if(mcmd == nil)
+ sysfatal("realloc: %r");
+ }
+ memcpy(mp, cmd, n);
+ mp += n;
+}
+
+void
+ev(void)
+{
+ uchar e, v, cmd[3], *p;
+
+ e = r8();
+ p = cmd;
+ switch(e >> 4 & 7){
+ case 0:
+ v = r8() & 0x7f;
+ *p++ = e | 0x80;
+ *p++ = v;
+ *p++ = 0x40;
+ break;
+ case 1:
+ v = r8();
+ *p++ = e | 0x80;
+ *p++ = v & 0x7f;
+ if(v & 0x80)
+ t.v[e & 15] = r8() & 0x7f;
+ *p++ = t.v[e & 15];
+ break;
+ case 2:
+ v = r8();
+ *p++ = e | 0xc0;
+ PBIT16(p, v << 7 & 0x7f7f);
+ p += 2;
+ break;
+ case 3:
+ v = r8();
+ *p++ = 0xb | e & 15;
+ switch(v){
+ case 10: *p++ = 0x78; break;
+ case 11: *p++ = 0x7b; break;
+ case 12: *p++ = 0x7e; break;
+ case 13: *p++ = 0x7f; break;
+ case 14: *p++ = 0x79; break;
+ default: sysfatal("unknown system event %ux\n", v);
+ }
+ *p++ = 0;
+ break;
+ case 4:
+ v = r8();
+ if(v > 9)
+ sysfatal("unknown controller %ux\n", v);
+ *p++ = 0xb0 | e & 15;
+ switch(v){
+ case 1: *p++ = 0x00; break;
+ case 2: *p++ = 0x01; break;
+ case 3: *p++ = 0x07; break;
+ case 4: *p++ = 0x0a; break;
+ case 5: *p++ = 0x0b; break;
+ case 6: *p++ = 0x5b; break;
+ case 7: *p++ = 0x5d; break;
+ case 8: *p++ = 0x40; break;
+ case 9: *p++ = 0x43; break;
+ }
+ *p++ = r8() & 0x7f;
+ if(v == 0)
+ cmd[0] += 0x10;
+ break;
+ case 6:
+ *p++ = 0xff;
+ *p++ = 0x2f;
+ e = 0;
+ t.done++;
+ break;
+ default:
+ sysfatal("unknown event %ux\n", e >> 4 & 7);
+ }
+ if((e & 15) == 9)
+ cmd[0] |= 6;
+ if((e & 15) == 15)
+ cmd[0] &= ~6;
+ putcmd(cmd, p-cmd);
+ if(e & 0x80)
+ delay();
+ else
+ *mp++ = 0;
+}
+
+void
+reset(void)
+{
+ memset(t.v, 0x7f, sizeof t.v);
+ mcmd = mallocz(t.len * 2, 1);
+ if(mcmd == nil)
+ sysfatal("mallocz: %r");
+ mp = mcmd;
+ me = mcmd + t.len * 2;
+}
+
+void
+barf(void)
+{
+ static uchar hdr[] = {
+ 'M', 'T', 'h', 'd',
+ 0x00, 0x00, 0x00, 0x06,
+ 0x00, 0x00,
+ 0x00, 0x01,
+ 0x01, 0x01,
+ 'M', 'T', 'r', 'k',
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xb0, 0x07, 0x7f,
+ 0x00, 0xb1, 0x07, 0x7f,
+ 0x00, 0xb2, 0x07, 0x7f,
+ 0x00, 0xb3, 0x07, 0x7f,
+ 0x00, 0xb4, 0x07, 0x7f,
+ 0x00, 0xb5, 0x07, 0x7f,
+ 0x00, 0xb6, 0x07, 0x7f,
+ 0x00, 0xb7, 0x07, 0x7f,
+ 0x00, 0xb8, 0x07, 0x7f,
+ 0x00, 0xb9, 0x07, 0x7f,
+ 0x00, 0xba, 0x07, 0x7f,
+ 0x00, 0xbb, 0x07, 0x7f,
+ 0x00, 0xbc, 0x07, 0x7f,
+ 0x00, 0xbd, 0x07, 0x7f,
+ 0x00, 0xbe, 0x07, 0x7f,
+ 0x00, 0xbf, 0x07, 0x7f,
+ 0x00, 0xff, 0x51, 0x03, 0x1b, 0x8a, 0x06,
+ 0x00
+ };
+ int n;
+
+ n = sizeof(hdr) - 22 + mp - mcmd;
+ BBIT32(hdr + 18, n);
+ write(1, hdr, sizeof hdr);
+ write(1, mcmd, mp - mcmd);
+}
+
+void
+main(int argc, char *argv[])
+{
+ int n, ofs;
+ uchar s[8], b[1024];
+
+ if(argc > 1){
+ fd = open(argv[1], OREAD);
+ if(fd < 0)
+ sysfatal("open: %r");
+ }
+ eread(fd, s, sizeof s);
+ if(memcmp(s, "MUS\x1a", 4) != 0)
+ sysfatal("invalid mus file: %r");
+ t.len = s[5] << 8 | s[4];
+ ofs = (s[7] << 8 | s[6]) - 8;
+ while(ofs > 0){
+ n = ofs > sizeof b ? sizeof b : ofs;
+ eread(fd, b, n);
+ ofs -= n;
+ }
+ t.dat = malloc(t.len);
+ if(t.dat == nil)
+ sysfatal("malloc: %r");
+ t.p = t.dat;
+ t.end = t.dat + t.len;
+ eread(fd, t.dat, t.len);
+ reset();
+ while(!t.done && t.p < t.end)
+ ev();
+ barf();
+ exits(nil);
+}
--- /dev/null
+++ b/todpic.c
@@ -1,0 +1,190 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <bio.h>
+
+int wofs;
+u32int pal[256], bg = 0x00ffff;
+Biobuf *bp;
+
+#define abs(x) ((x) < 0 ? -(x) : (x))
+
+void
+put8(u8int v)
+{
+ if(Bwrite(bp, &v, sizeof v) != sizeof v)
+ sysfatal("put8: short write");
+}
+
+void
+put16(u16int v)
+{
+ put8(v);
+ put8(v >> 8);
+}
+
+void
+put32(u32int v)
+{
+ put16(v);
+ put16(v >> 16);
+}
+
+int
+pali(u32int v)
+{
+ int i, Δ, Δ´;
+ u32int *p;
+
+ i = 0;
+ Δ = abs((char)v - (char)*pal)
+ + abs((char)(v >> 8) - (char)(*pal >> 8))
+ + abs((char)(v >> 16) - (char)(*pal >> 16));
+ for(p=pal; p<pal+nelem(pal); p++){
+ Δ´ = abs((char)v - (char)*p)
+ + abs((char)(v >> 8) - (char)(*p >> 8))
+ + abs((char)(v >> 16) - (char)(*p >> 16));
+ if(Δ´ < Δ){
+ Δ = Δ´;
+ i = p - pal;
+ if(Δ == 0)
+ break;
+ }
+ }
+ return i;
+}
+
+void
+topic(Memimage *i)
+{
+ int w, h, dx, dy;
+ uchar *np, *b, *buf, *p, *pp;
+ u32int v;
+
+ p = i->data->bdata;
+ dx = Dx(i->r);
+ dy = Dy(i->r);
+ if(dy > 254)
+ sysfatal("topic: invalid pic height");
+ put16(dx);
+ put16(dy);
+ put16(wofs ? dx / 2 - 1 : i->r.min.x);
+ put16(wofs ? dy - 5 : i->r.min.y);
+ if(i->r.min.x != 0)
+ dx = i->width;
+ buf = mallocz((5 * dy / 2 + 5) * dx, 1);
+ if(buf == nil)
+ sysfatal("mallocz: %r");
+ for(w=dx, b=buf; w>0; w--, p+=3){
+ put32(b - buf + 8 + dx * 4);
+ for(h=0, np=b+1, pp=p; h<dy; h++, pp+=dx*3){
+ v = pp[2] << 16 | pp[1] << 8 | pp[0];
+ if(v == bg){
+ if(b - np - 2 > 0){
+ *np = b - np - 2;
+ *b++ = 0;
+ np = b + 1;
+ }
+ continue;
+ }
+ if(b - np - 2 < 0){
+ *b++ = h;
+ b++;
+ *b++ = 0;
+ }
+ *b++ = pali(v);
+ }
+ if(b - np - 2 >= 0){
+ *np = b - np - 2;
+ *b++ = 0;
+ }
+ *b++ = 0xff;
+ }
+ Bwrite(bp, buf, b - buf);
+ free(buf);
+}
+
+void
+toflat(Memimage *i)
+{
+ int n;
+ uchar *p;
+
+ if(Dx(i->r) != 64 || Dy(i->r) != 64)
+ sysfatal("toflat: invalid flatpic dimensions");
+ p = i->data->bdata;
+ n = 64*64;
+ while(n-- > 0){
+ put8(pali(p[2] << 16 | p[1] << 8 | p[0]));
+ p += 4;
+ }
+}
+
+static Memimage*
+iconv(Memimage *i)
+{
+ Memimage *ni;
+
+ if(i->chan == RGB24)
+ return i;
+ if((ni = allocmemimage(i->r, RGB24)) == nil)
+ sysfatal("allocmemimage: %r");
+ memimagedraw(ni, ni->r, i, i->r.min, nil, i->r.min, S);
+ freememimage(i);
+ return ni;
+}
+
+void
+getpal(char *f)
+{
+ uchar u[3];
+ u32int *p;
+ Biobuf *bp;
+
+ if((bp = Bopen(f, OREAD)) == nil)
+ sysfatal("getpal: %r");
+ for(p=pal; p<pal+nelem(pal); p++){
+ if(Bread(bp, u, 3) != 3)
+ sysfatal("getpal: short read: %r");
+ *p = u[0]<<16 | u[1]<<8 | u[2];
+ }
+ Bterm(bp);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-fw] [-b bgcol] [-p palette] [image]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd, flat;
+ char *p;
+ Memimage *i;
+
+ fd = 0;
+ flat = 0;
+ p = "/mnt/wad/playpal";
+ ARGBEGIN{
+ case 'b': bg = strtoul(EARGF(usage()), nil, 0); break;
+ case 'f': flat = 1; break;
+ case 'p': p = EARGF(usage()); break;
+ case 'w': wofs = 1; break;
+ default: usage();
+ }ARGEND
+ if(*argv != nil)
+ if((fd = open(*argv, OREAD)) < 0)
+ sysfatal("open: %r");
+ getpal(p);
+ if((bp = Bfdopen(1, OWRITE)) == nil)
+ sysfatal("Bfdopen: %r");
+ memimageinit();
+ if((i = readmemimage(fd)) == nil)
+ sysfatal("readmemimage: %r");
+ (flat ? toflat : topic)(iconv(i));
+ exits(nil);
+}
--- /dev/null
+++ b/wadfs.c
@@ -1,0 +1,864 @@
+#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);
+}