shithub: rd

Download patch

ref: f283a6a6cf3a8283e3896e5556fe8cbdbd6413a8
parent: 05bd68a784ff9ed57439a16f8449444229ed16fc
author: Yaroslav K <yarikos@gmail.com>
date: Sat Aug 23 15:31:26 EDT 2025

merge with origin (legacy)

--- a/audio.c
+++ b/audio.c
@@ -5,8 +5,6 @@
 #include "fns.h"
 
 /*
-BUG requires RDPDR support...
-
 [MS-RDPEA] "6 Appendix A: Product Behavior":
 <1> Section 2.1: In Windows, the client advertises the static virtual
 channel named "RDPDR", as defined in [MS-RDPEFS].  If that channel is
@@ -19,48 +17,131 @@
 
 enum	/* Audiomsg.type */
 {
-	Awav=	2,	/* WaveInfo PDU */
-	Awavack=	5,	/* Wave Confirm PDU */
-	Aprobe=	6,	/* Training PDU or Training Confirm PDU */
+	Awinf=	2,	/* WaveInfo PDU */
+	Awav=	0,	/* Wave PDU - always follows Awinf */
+	Awack=	5,	/* Wave Confirm PDU */
+	Aping=	6,	/* Training PDU or Training Confirm PDU */
 	Afmt=	7,	/* Client/Server Audio Formats and Version PDU */
-		/* 2.2.1 RDPSND PDU Header (SNDPROLOG) */
+	Afin=	1,	/* 2.2.3.9 Close PDU */
 };
+
+enum /* RFC2361 Appendix A */
+{
+	FmtPCM=	0x01,	/* WAVE_FORMAT_PCM */		
+};
+
 typedef	struct	Audiomsg Audiomsg;
 struct Audiomsg
 {
 	uint	type;
 	uint	nfmt;	/* Afmt */
-	uint	ack;	/* Afmt */
+	uint	seq;	/* Afmt, Awinf, Awack */
+	uint	time;	/* Awinf, Awack, Aping */
+	uint	ping;	/* Aping */
 	uint	ver;	/* Afmt */
-	uchar	*data;	/* Afmt */
-	uint	ndata;	/* Afmt */
+	uint	ndata;	/* Afmt, Awinf */
+	uint	nxdata;	/* Awinf */
+	uchar	*data;	/* Afmt, Awinf, Awav */
 };
-static	int	getaudiomsg(Audiomsg*,uchar*,int);
-static	int	putaudiomsg(uchar*,int, Audiomsg*);
+static	int	audiogetmsg(Audiomsg*,uchar*,int);
+static	int	audioputmsg(uchar*,int, Audiomsg*);
+static	int	audiosendmsg(Rdp*, Audiomsg*);
+static	void	sfmtrcvd(Rdp*,Audiomsg*);
+static	void	winfrcvd(Rdp*,Audiomsg*);
+static	void	wavrcvd(Rdp*,Audiomsg*);
+static	void	pingrcvd(Rdp*,Audiomsg*);
+static	void	finrcvd(Rdp*,Audiomsg*);
+static	void	sendcfmt(Rdp*);
+static	void	sendwack(Rdp*);
+static	void	sendping(Rdp*, uint, uint);
 
-enum
-{
-	FmtPCM=	0x01,	/* WAVE_FORMAT_PCM */
-	FmtMP3=	0x55,	/* WAVE_FORMAT_MPEGLAYER3 */
-		/* RFC2361 Appendix A */
-};
-
-
 void
 audiovcfn(Rdp* c, uchar* a, uint nb)
 {
 	Audiomsg r;
 
-	USED(c);
-fprint(2, " A ");
-	if(getaudiomsg(&r, a, nb) < 0){
+	if(audiogetmsg(&r, a, nb) < 0){
 		fprint(2, "audio: %r\n");
 		return;
 	}
-fprint(2, " a%ud ", r.type);
+	switch(r.type){
+	case Afmt:	sfmtrcvd(c, &r); break;
+	case Awinf:	winfrcvd(c, &r); break;
+	case Awav:	wavrcvd(c, &r); break;
+	case Aping:	pingrcvd(c, &r); break;
+	case Afin:	finrcvd(c, &r); break;
+	}
 }
 
+static void
+sfmtrcvd(Rdp* c, Audiomsg* r)
+{
+	c->audio.seq = r->seq;
+	sendcfmt(c);
+}
 
+static void
+winfrcvd(Rdp* c, Audiomsg* r)
+{
+	c->audio.seq = r->seq;
+	c->audio.time = r->time;
+	playsound(c, r->data, r->ndata);
+}
+
+static void
+wavrcvd(Rdp* c, Audiomsg* r)
+{
+	playsound(c, r->data, r->ndata);
+	sendwack(c);
+}
+
+static void
+pingrcvd(Rdp* c, Audiomsg* r)
+{
+	sendping(c, r->time, r->ping);
+}
+
+static void
+finrcvd(Rdp* c, Audiomsg* r)
+{
+	USED(c);
+	USED(r);
+}
+
+static void
+sendcfmt(Rdp* c)
+{
+	Audiomsg t;
+
+	t.type = Afmt;
+	if(audiosendmsg(c, &t) < 0)
+		fprint(2, "audio: sendcfmt: %r\n");
+}
+
+static void
+sendwack(Rdp* c)
+{
+	Audiomsg t;
+
+	t.type = Awack;
+	t.seq = c->audio.seq;
+	t.time = c->audio.time;
+	if(audiosendmsg(c, &t) < 0)
+		fprint(2, "audio: sendwack: %r\n");
+}
+
+static void
+sendping(Rdp* c, uint time, uint v)
+{
+	Audiomsg t;
+
+	t.type = Aping;
+	t.time = time;
+	t.ping = v;
+	if(audiosendmsg(c, &t) < 0)
+		fprint(2, "audio: sendping: %r\n");
+}
+
 /*
  * 2.2.1 RDPSND PDU Header (SNDPROLOG)
  *	msgtype[1] pad[1] bodysize[2]
@@ -73,7 +154,7 @@
  */
 
 static int
-getaudiomsg(Audiomsg* m, uchar* a, int nb)
+audiogetmsg(Audiomsg* m, uchar* a, int nb)
 {
 	ulong len;
 
@@ -83,7 +164,7 @@
 	}
 	
 	m->type = *a;
-	len = igets(a+2);
+	len = GSHORT(a+2);
 	switch(m->type){
 	case Afmt:
 		if(len < 20 || nb < len+4){
@@ -90,14 +171,115 @@
 			werrstr(Eshort);
 			return -1;
 		}
-		m->nfmt = igets(a+18);
-		m->ack = a[20];
-		m->ver = igets(a+21);
+		m->nfmt = GSHORT(a+18);
+		m->seq = a[20];
+		m->ver = GSHORT(a+21);
 		m->data = a+24;
 		m->ndata = len-20;
 		return len+4;
+	case Awinf:
+		if(len < 16 || nb < 16){
+			werrstr(Eshort);
+			return -1;
+		}
+		m->time = GSHORT(a+4);
+		m->seq = a[8];
+		m->data = a+12;
+		m->ndata = 4;
+		m->nxdata = len-16;
+		return 16;
+	case Awav:
+		m->data = a+4;
+		m->ndata = nb-4;
+		return nb;
+	case Aping:
+		if(nb < 8){
+			werrstr(Eshort);
+			return -1;
+		}
+		m->time = GSHORT(a+4);
+		m->ping = GSHORT(a+6);
+		return 8;	
+	case Afin:
+		return 4;
 	}
-	werrstr("unrecognized audio msg type");
+	werrstr("unrecognized audio msg type(%ud)", m->type);
 	return -1;
 }
 
+static int
+audioputmsg(uchar* a, int n, Audiomsg* m)
+{
+	int len;
+
+	switch(m->type){
+	case Afmt:
+		len = 42-4;
+		if(n < 42){
+			werrstr(Esmall);
+			return -1;
+		}
+		a[0] = m->type;
+		a[1] = 0; // pad
+		PSHORT(a+2, len);
+		PLONG(a+4, 1);	// flags: 1=alive
+		PLONG(a+8, 0xffffffff);	// volume: l=100, r=100
+		PLONG(a+12, 1<<16);	// pitch
+		PSHORT(a+16, 0);	// dgport
+		PSHORT(a+18, 1);	// nfmt
+		a[20] = 0;	// seq
+		PSHORT(a+21, 5);	// ver
+		a[23] = 0;	// pad
+		// afmt[0]
+		PSHORT(a+24, FmtPCM);	// ftag
+		PSHORT(a+26, 2);	// nchan
+		PLONG(a+28, 44100);	// samphz
+		PLONG(a+32, 44100*2*2);	// avgbytehz
+		PSHORT(a+36, 4);	// blocksz (nchan*sampbitsz/8)
+		PSHORT(a+38, 16);	// sampbitsz
+		PSHORT(a+40, 0);	// ndata
+		/* NOTE: we shouldn't send this unless the server
+		 * also declared support for the exact same format */
+		return 42;
+	case Awack:
+		len = 8-4;
+		if(n < 8){
+			werrstr(Esmall);
+			return -1;
+		}
+		a[0] = m->type;
+		a[1] = 0; // pad
+		PSHORT(a+2, len);
+		PSHORT(a+4, m->time);
+		a[6] = m->seq;
+		a[7] = 0;	// pad
+		return 8;
+	case Aping:
+		len = 8-4;
+		if(n < 8){
+			werrstr(Esmall);
+			return -1;
+		}
+		a[0] = m->type;
+		a[1] = 0; // pad
+		PSHORT(a+2, len);
+		PSHORT(a+4, m->time);
+		PSHORT(a+6, m->ping);
+		return 8;
+	}
+	werrstr("unrecognized audio msg type(%ud)", m->type);
+	return -1;
+}
+
+
+static int
+audiosendmsg(Rdp* c, Audiomsg* m)
+{
+	int n;
+	uchar buf[64];
+
+	n = audioputmsg(buf, sizeof buf, m);
+	if(n < 0)
+		return n;
+	return sendvc(c, rdpsnd, buf, n);
+}
--- a/byte.c
+++ /dev/null
@@ -1,58 +1,0 @@
-#include <u.h>
-#include "fns.h"
-
-short
-igets(uchar* p)
-{
-	return p[0] | (p[1]<<8);
-}
-	
-long
-igetl(uchar* p)
-{
- 	return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
-}
-
-void
-iputs(uchar* p, short v)
-{
-	p[0]=(uchar)v;
-	p[1]=(uchar)(v>>8);
-}
-
-void
-iputl(uchar* p, long v)
-{
-	p[0]=(uchar)v;
-	p[1]=(uchar)(v>>8);
-	p[2]=(uchar)(v>>16);
-	p[3]=(uchar)(v>>24);
-}
-
-short
-nhgets(uchar* p)
-{
-	return (p[0]<<8) | p[1];
-}
-
-long
-nhgetl(uchar* p)
-{
-	return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
-}
-
-void
-hnputs(uchar* p, short v)
-{
-	p[0]=(uchar)(v>>8);
-	p[1]=(uchar)v;
-}
-
-void
-hnputl(uchar* p, long v)
-{
-	p[0]=(uchar)(v>>24);
-	p[1]=(uchar)(v>>16);
-	p[2]=(uchar)(v>>8);
-	p[3]=(uchar)v;
-}
--- a/cap.c
+++ b/cap.c
@@ -58,11 +58,11 @@
 	ep = p+nb;
 	memset(caps, sizeof(*caps), 0);
 
-	ncap = igets(p);
+	ncap = GSHORT(p);
 	p += 4;
 	for(; ncap>0 && p+4<ep; ncap--){
-		type = igets(p+0);
-		len = igets(p+2);
+		type = GSHORT(p+0);
+		len = GSHORT(p+2);
 		if(p+len > ep){
 			werrstr("bad length in server's capability set");
 			return -1;
@@ -75,7 +75,7 @@
 				return -1;
 			}
 			caps->general = 1;
-			extraFlags  = igets(p+14);
+			extraFlags  = GSHORT(p+14);
 			caps->canrefresh = p[22];
 			caps->cansupress = p[23];
 			USED(extraFlags);
@@ -87,9 +87,9 @@
 				return -1;
 			}
 			caps->bitmap = 1;
-			caps->depth = igets(p+4);
-			caps->xsz = igets(p+12);
-			caps->ysz = igets(p+14);
+			caps->depth = GSHORT(p+4);
+			caps->xsz = GSHORT(p+12);
+			caps->ysz = GSHORT(p+14);
 			break;
 		}
 		p += len;
@@ -139,8 +139,8 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p, ncap);
-	iputs(p+2, 0);
+	PSHORT(p, ncap);
+	PSHORT(p+2, 0);
 	return 4;
 }
 
@@ -160,16 +160,16 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapGeneral);
-	iputs(p+2, 24);	// size
-	iputs(p+4, 0);	// OSMAJORTYPE_UNSPECIFIED
-	iputs(p+6, 0);	// OSMINORTYPE_UNSPECIFIED
-	iputs(p+8, 0x200);	// TS_CAPS_PROTOCOLVERSION
-	iputs(p+12, 0);	// generalCompressionTypes
-	iputs(p+14, extraFlags);
-	iputs(p+16, 0);	// updateCapabilityFlag
-	iputs(p+18, 0);	// remoteUnshareFlag 
-	iputs(p+20, 0);	// generalCompressionLevel
+	PSHORT(p+0, CapGeneral);
+	PSHORT(p+2, 24);	// size
+	PSHORT(p+4, 0);	// OSMAJORTYPE_UNSPECIFIED
+	PSHORT(p+6, 0);	// OSMINORTYPE_UNSPECIFIED
+	PSHORT(p+8, 0x200);	// TS_CAPS_PROTOCOLVERSION
+	PSHORT(p+12, 0);	// generalCompressionTypes
+	PSHORT(p+14, extraFlags);
+	PSHORT(p+16, 0);	// updateCapabilityFlag
+	PSHORT(p+18, 0);	// remoteUnshareFlag 
+	PSHORT(p+20, 0);	// generalCompressionLevel
 	p[22] = 0;  	// refreshRectSupport - server only
 	p[23] = 0;  	// suppressOutputSupport - server only
 	return 24;
@@ -184,21 +184,21 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapBitmap);
-	iputs(p+2, 30);	// size
-	iputs(p+4, caps->depth);	// preferredBitsPerPixel
-	iputs(p+6, 1);	// receive1BitPerPixel
-	iputs(p+8, 1);	// receive4BitsPerPixel
-	iputs(p+10, 1);	// receive8BitsPerPixel
-	iputs(p+12, caps->xsz);	// desktopWidth
-	iputs(p+14, caps->ysz);	// desktopHeight
-	iputs(p+16, 0);	// pad2octets 
-	iputs(p+18, 1);	// desktopResizeFlag 
-	iputs(p+20, 1);	// bitmapCompressionFlag 
-	iputs(p+22, 0);	// highColorFlags 
-	iputs(p+24, 1);	// drawingFlags 
-	iputs(p+26, 1);	// multipleRectangleSupport
-	iputs(p+26, 0);	// pad2octetsB
+	PSHORT(p+0, CapBitmap);
+	PSHORT(p+2, 30);	// size
+	PSHORT(p+4, caps->depth);	// preferredBitsPerPixel
+	PSHORT(p+6, 1);	// receive1BitPerPixel
+	PSHORT(p+8, 1);	// receive4BitsPerPixel
+	PSHORT(p+10, 1);	// receive8BitsPerPixel
+	PSHORT(p+12, caps->xsz);	// desktopWidth
+	PSHORT(p+14, caps->ysz);	// desktopHeight
+	PSHORT(p+16, 0);	// pad2octets 
+	PSHORT(p+18, 1);	// desktopResizeFlag 
+	PSHORT(p+20, 1);	// bitmapCompressionFlag 
+	PSHORT(p+22, 0);	// highColorFlags 
+	PSHORT(p+24, 1);	// drawingFlags 
+	PSHORT(p+26, 1);	// multipleRectangleSupport
+	PSHORT(p+26, 0);	// pad2octetsB
 	return 30;
 }
 
@@ -226,25 +226,25 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapOrder);
-	iputs(p+2, 88);	// size
+	PSHORT(p+0, CapOrder);
+	PSHORT(p+2, 88);	// size
 	memset(p+4, 16, 0);	// terminalDescriptor
-	iputl(p+20, 0);	// pad4octetsA 
-	iputs(p+24, 1);	// desktopSaveXGranularity 
-	iputs(p+26, 20);	// desktopSaveYGranularity 
-	iputs(p+28, 0);	// pad2octetsA
-	iputs(p+30, 1);	// maximumOrderLevel 
-	iputs(p+32, 0);	// numberFonts 
-	iputs(p+34, orderFlags);
+	PLONG(p+20, 0);	// pad4octetsA 
+	PSHORT(p+24, 1);	// desktopSaveXGranularity 
+	PSHORT(p+26, 20);	// desktopSaveYGranularity 
+	PSHORT(p+28, 0);	// pad2octetsA
+	PSHORT(p+30, 1);	// maximumOrderLevel 
+	PSHORT(p+32, 0);	// numberFonts 
+	PSHORT(p+34, orderFlags);
 	memcpy(p+36, orderSupport, 32);
-	iputs(p+68, 0x6a1);	// textFlags
-	iputs(p+70, 0);	// orderSupportExFlags
-	iputl(p+72, 0);	// pad4octetsB
-	iputl(p+76, 480*480);	// desktopSaveSize
-	iputs(p+80, 0);	// pad2octetsC
-	iputs(p+82, 0);	// pad2octetsD
-	iputs(p+84, 0xe4);	// textANSICodePage
-	iputs(p+86, 0x04);	// pad2octetsE
+	PSHORT(p+68, 0x6a1);	// textFlags
+	PSHORT(p+70, 0);	// orderSupportExFlags
+	PLONG(p+72, 0);	// pad4octetsB
+	PLONG(p+76, 480*480);	// desktopSaveSize
+	PSHORT(p+80, 0);	// pad2octetsC
+	PSHORT(p+82, 0);	// pad2octetsD
+	PSHORT(p+84, 0xe4);	// textANSICodePage
+	PSHORT(p+86, 0x04);	// pad2octetsE
 	return 88;
 }
 
@@ -258,16 +258,16 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapBitcache2);
-	iputs(p+2, 40);	// size
-	iputs(p+4, 0);	// CacheFlags (2 bytes):  
+	PSHORT(p+0, CapBitcache2);
+	PSHORT(p+2, 40);	// size
+	PSHORT(p+4, 0);	// CacheFlags (2 bytes):  
 	p[6] = 0;	// pad2
 	p[7] = 3;	// NumCellCaches
-	iputl(p+8, 120);	// BitmapCache0CellInfo
-	iputl(p+12, 120);	// BitmapCache1CellInfo
-	iputl(p+16, 336);	// BitmapCache2CellInfo
-	iputl(p+20, 0);	// BitmapCache3CellInfo
-	iputl(p+24, 0);	// BitmapCache4CellInfo
+	PLONG(p+8, 120);	// BitmapCache0CellInfo
+	PLONG(p+12, 120);	// BitmapCache1CellInfo
+	PLONG(p+16, 336);	// BitmapCache2CellInfo
+	PLONG(p+20, 0);	// BitmapCache3CellInfo
+	PLONG(p+24, 0);	// BitmapCache4CellInfo
 	memset(p+28, 12, 0); // Pad3
 	return 40;
 }
@@ -281,10 +281,10 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapPointer);
-	iputs(p+2, 8);	// size
-	iputs(p+4, 0);	// colorPointerFlag  
-	iputs(p+6, 20);	// colorPointerCacheSize 
+	PSHORT(p+0, CapPointer);
+	PSHORT(p+2, 8);	// size
+	PSHORT(p+4, 0);	// colorPointerFlag  
+	PSHORT(p+6, 20);	// colorPointerCacheSize 
 	return 8;
 }
 
@@ -311,16 +311,16 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapInput);
-	iputs(p+2, 88);	// size
-	iputs(p+4, inputFlags);	// inputFlags
-	iputs(p+6, 0);	// pad2octetsA
+	PSHORT(p+0, CapInput);
+	PSHORT(p+2, 88);	// size
+	PSHORT(p+4, inputFlags);	// inputFlags
+	PSHORT(p+6, 0);	// pad2octetsA
 
 	// the below SHOULD be the same as in the Client Core Data (section 2.2.1.3.2).
-	iputl(p+8, 0x409);	// keyboardLayout
-	iputl(p+12, 4);	// keyboardType: IBM enhanced (101- or 102-key)
-	iputl(p+16, 0);	// keyboardSubType
-	iputl(p+20, 12);	// keyboardFunctionKey
+	PLONG(p+8, 0x409);	// keyboardLayout
+	PLONG(p+12, 4);	// keyboardType: IBM enhanced (101- or 102-key)
+	PLONG(p+16, 0);	// keyboardSubType
+	PLONG(p+20, 12);	// keyboardFunctionKey
 	memset(p+24, 64, 0);	// imeFileName
 	return 88;
 }
@@ -338,21 +338,21 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapGlyph);
-	iputs(p+2, 52);	// size
-	iputl(p+4, 0x0400fe);	// GlyphCache 0
-	iputl(p+8, 0x0400fe);	// GlyphCache 1
-	iputl(p+12, 0x0800fe);	// GlyphCache 2
-	iputl(p+16, 0x0800fe);	// GlyphCache 3
-	iputl(p+20, 0x1000fe);	// GlyphCache 4
-	iputl(p+24, 0x2000fe);	// GlyphCache 5
-	iputl(p+28, 0x4000fe);	// GlyphCache 6
-	iputl(p+32, 0x8000fe);	// GlyphCache 7
-	iputl(p+36, 0x10000fe);	// GlyphCache 8
-	iputl(p+40, 0x8000040);	// GlyphCache 9
-	iputl(p+44, 0x01000100);	// FragCache 
-	iputs(p+48, GLYPH_SUPPORT_NONE);	// GlyphSupportLevel
-	iputs(p+50, 0);	// pad2octets 
+	PSHORT(p+0, CapGlyph);
+	PSHORT(p+2, 52);	// size
+	PLONG(p+4, 0x0400fe);	// GlyphCache 0
+	PLONG(p+8, 0x0400fe);	// GlyphCache 1
+	PLONG(p+12, 0x0800fe);	// GlyphCache 2
+	PLONG(p+16, 0x0800fe);	// GlyphCache 3
+	PLONG(p+20, 0x1000fe);	// GlyphCache 4
+	PLONG(p+24, 0x2000fe);	// GlyphCache 5
+	PLONG(p+28, 0x4000fe);	// GlyphCache 6
+	PLONG(p+32, 0x8000fe);	// GlyphCache 7
+	PLONG(p+36, 0x10000fe);	// GlyphCache 8
+	PLONG(p+40, 0x8000040);	// GlyphCache 9
+	PLONG(p+44, 0x01000100);	// FragCache 
+	PSHORT(p+48, GLYPH_SUPPORT_NONE);	// GlyphSupportLevel
+	PSHORT(p+50, 0);	// pad2octets 
 	return 52;
 }
 
@@ -365,9 +365,9 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	iputs(p+0, CapSound);
-	iputs(p+2, 8);	// size
-	iputs(p+4, 0);	// soundFlags
-	iputs(p+6, 0);	// pad2octetsA
+	PSHORT(p+0, CapSound);
+	PSHORT(p+2, 8);	// size
+	PSHORT(p+4, 0);	// soundFlags
+	PSHORT(p+6, 0);	// pad2octetsA
 	return 8;
 }
--- a/dat.h
+++ b/dat.h
@@ -4,12 +4,41 @@
 typedef	struct	Share Share;
 typedef	struct	Caps Caps;
 typedef	struct	Imgupd Imgupd;
+typedef	struct	Audstate	Audstate;
+typedef	struct	Efsstate Efsstate;
+typedef	struct	Efsmsg Efsmsg;
 
+#define	GSHORT(p)	((p)[0]|((p)[1]<<8))
+#define	GSHORTB(p)	((p)[0]<<8|((p)[1]))
+#define	GLONG(p) 	((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
+#define	GLONGB(p)	(((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])
+#define	PSHORT(p,v)	(p)[0]=(uchar)(v);(p)[1]=(uchar)((v)>>8)
+#define	PSHORTB(p,v)	(p)[0]=(uchar)((v)>>8);(p)[1]=(uchar)(v)
+#define	PLONG(p,v)	\
+	(p)[0]=(uchar)(v);(p)[1]=(uchar)((v)>>8);\
+	(p)[2]=(uchar)((v)>>16);(p)[3]=(uchar)((v)>>24)
+#define	PLONGB(p,v)	\
+	(p)[0]=(uchar)((v)>>24);(p)[1]=(uchar)((v)>>16);\
+	(p)[2]=(uchar)((v)>>8);(p)[3]=(uchar)(v)
+
+#define	MIN(x,y)		(((x) < (y)) ? (x) : (y))
+
 enum
 {
 	MAXTPDU=	16386,	/* max TPDU size */
 };
 
+struct Efsstate
+{
+	uint	cid;	/* client ID */
+};
+
+struct Audstate
+{
+	uint	seq;
+	uint	time;
+};
+
 struct Rdp
 {
 	int		fd;			/* connection I/O descriptor */
@@ -24,7 +53,7 @@
 	int		xsz;			/* rfb dimensions */
 	int		ysz;			/* rfb dimensions */
 	int		depth;		/* rfb color depth */
-	int		hupreason;	/* hangup reason as server explains */
+	int		hupreason;	/* hangup reason code */
 	int		mcsuid;		/* MCS [T.122] userId */
 	int		userchan;		/* MCS user channelId */
 	int		srvchan;		/* MCS server channel ID */
@@ -31,6 +60,8 @@
 	int		shareid;		/* share ID - [T128] section 8.4.2 */
 	int		active;		/* T.128 action state */
 	int		wantconsole;	/* attach to the console sesstion */
+	Efsstate	efs;			/* FS extension state (efs.c) */
+	Audstate	audio;		/* Audio extension state */
 	Vchan	*vc;			/* static virtual channels table */
 	uint		nvc;			/* number of vctab entries */
 	uchar	cmap[256];	/* rfb color map for depths ≤ 8 */
@@ -52,8 +83,11 @@
 void	clipannounce(Rdp*);
 void	clipvcfn(Rdp*, uchar*,uint);
 void	audiovcfn(Rdp*, uchar*,uint);
+void	efsvcfn(Rdp*, uchar*,uint);
 void	pollsnarf(Rdp*);
+void	playsound(Rdp*, uchar*,uint);
 
+
 void	initscreen(Rdp*);
 void	readkbd(Rdp*);
 void	sendkbd(Rdp*, Rune);
@@ -220,7 +254,7 @@
 	Ubitmap,
 	Uscrblt,
 	Umemblt,
-	Ucacheimg,
+	Uicache,
 	Umcache,
 };
 struct Imgupd
@@ -233,12 +267,12 @@
 	int	xsz;
 	int	ysz;
 	int	depth;
-	int	clip;
-	int	compressed;
+	int	iscompr;
 	int	cid;
 	int	coff;
 	int	sx;
 	int	sy;
+	int	clipped;
 	int	cx;
 	int	cy;
 	int	cxsz;
@@ -248,6 +282,31 @@
 };
 int	getimgupd(Imgupd*, uchar*, uint);
 int	getfupd(Imgupd*, uchar*, uint);
+
+enum /* 2.2.1.1 Shared Header (RDPDR_HEADER) */
+{
+	CTcore=	0x4472,	// Device redirector core component
+	CTprn=	0x5052,	// Printing component
+	// CTcore
+	CSann=	0x496e,	// Server Announce Request
+	CCann=	0x4343,	// Client Announce Reply
+	CCnrq=	0x434e,	// Client Name Request
+};
+
+struct Efsmsg {
+	uint ctype;	// Component (core or printing)
+	uint pakid;	// PackedId (identifies packet function)
+	// CSann, CCann
+	uint vermaj;
+	uint vermin;
+	uint cid;	// ClientID
+	// CCnrq
+	char* cname;
+};
+int	getefsmsg(Efsmsg*, uchar*, int);
+int	putefsmsg(uchar*, int, Efsmsg*);
+int	sendefsmsg(Rdp*, Efsmsg*);
+
 
 enum 
 {
--- a/draw.c
+++ b/draw.c
@@ -102,7 +102,7 @@
 		sysfatal("bad image depth");
 
 	loadfn = loadbmp;
-	if(up->compressed)
+	if(up->iscompr)
 		loadfn = loadrle;
 
 	r = rectaddpt(Rect(up->x, up->y, up->x+up->xsz, up->y+up->ysz), screen->r.min);
@@ -143,7 +143,7 @@
 		return;
 	}
 
-	if(up->clip){
+	if(up->clipped){
 		clipr = Rect(up->cx, up->cy, up->cx+up->cxsz, up->cy+up->cysz);
 		replclipr(screen, screen->repl, rectaddpt(clipr, screen->r.min));
 	}
@@ -153,12 +153,12 @@
 	pt = Pt(up->sx, up->sy);
 	draw(screen, r, img, nil, pt);
 
-	if(up->clip)
+	if(up->clipped)
 		replclipr(screen, screen->repl, screen->r);
 }
 
 static void
-cacheimg(Rdp* c, Imgupd* u)
+cacheimage2(Rdp* c, Imgupd* up)
 {
 	int (*loadfn)(Image*,Rectangle,uchar*,int,uchar*);
 	int chan;
@@ -166,25 +166,25 @@
 	Rectangle r;
 
 	loadfn = loadbmp;
-	if(u->compressed)
+	if(up->iscompr)
 		loadfn = loadrle;
 
-	r = Rect(0, 0, u->xsz, u->ysz);
+	r = Rect(0, 0, up->xsz, up->ysz);
 
-	if(u->cid >= nelem(icache) || u->coff >= nelem(*icache))
-		sysfatal("cacheimg: bad cache spec [%d %d]", u->cid, u->coff);
+	if(up->cid >= nelem(icache) || up->coff >= nelem(*icache))
+		sysfatal("cacheimage2: bad cache spec [%d %d]", up->cid, up->coff);
 
-	img = icache[u->cid][u->coff];
+	img = icache[up->cid][up->coff];
 	if(img==nil || eqrect(img->r, r)==0){
 		chan = depth2chan(c->depth);
 		freeimage(img);
 		img = allocimage(display, r, chan, 0, DNofill);
 		if(img == nil)
-			sysfatal("cacheimg: %r");
-		icache[u->cid][u->coff] = img;
+			sysfatal("cacheimage2: %r");
+		icache[up->cid][up->coff] = img;
 	}
 
-	if(loadfn(img, r, u->bytes, u->nbytes, c->cmap) < 0)
+	if(loadfn(img, r, up->bytes, up->nbytes, c->cmap) < 0)
 		sysfatal("loadmemimg: %r");
 }
 
@@ -203,7 +203,7 @@
 	case Ubitmap:	imgupd(c, up); break;
 	case Uscrblt:	scrblt(c, up); break;
 	case Umemblt:	memblt(c, up); break;
-	case Ucacheimg:	cacheimg(c, up); break;
+	case Uicache:	cacheimage2(c, up); break;
 	case Umcache:	cachecmap(c, up); break;
 	}
 }
--- a/eclip.c
+++ b/eclip.c
@@ -182,8 +182,8 @@
 		werrstr(Esmall);
 		return -1;
 	}
-	iputs(a+0, m->type);
-	iputs(a+2, m->flags);
+	PSHORT(a+0, m->type);
+	PSHORT(a+2, m->flags);
 	switch(m->type){
 	case Cannounce:
 		m->data = a+8;
@@ -192,7 +192,7 @@
 			werrstr(Esmall);
 			return -1;
 		}
-		iputl(a+8, m->fmtid);
+		PLONG(a+8, m->fmtid);
 		memset(a+12, 0, 32);	/* fmt name - who cares? */
 		break;
 	case Crq:
@@ -202,7 +202,7 @@
 			werrstr(Esmall);
 			return -1;
 		}
-		iputl(a+8, m->fmtid);
+		PLONG(a+8, m->fmtid);
 		break;
 	case Cnoted:
 		m->ndata = 0;
@@ -213,7 +213,7 @@
 		werrstr(Esmall);
 		return -1;
 	}
-	iputl(a+4, m->ndata);
+	PLONG(a+4, m->ndata);
 	memcpy(a+8, m->data, m->ndata);
 	return 8+m->ndata;
 }
@@ -228,9 +228,9 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	m->type = igets(p);
-	m->flags = igets(p+2);
-	len = igetl(p+4);
+	m->type = GSHORT(p);
+	m->flags = GSHORT(p+2);
+	len = GLONG(p+4);
 	if(8+len > n){
 		werrstr(Eshort);
 		return -1;
@@ -244,7 +244,7 @@
 			werrstr(Eshort);
 			return -1;
 		}
-		m->fmtid = igetl(m->data);
+		m->fmtid = GLONG(m->data);
 		break;
 	case Cannounce:
 		m->fmtid = 0;
@@ -251,7 +251,7 @@
 		p += 8;
 		ep = p+len;
 		while(p < ep){
-			m->fmtid = igetl(p);
+			m->fmtid = GLONG(p);
 			if(m->fmtid == FmtUnicode)
 				break;
 			p += 4+32*1;
--- a/egdi.c
+++ b/egdi.c
@@ -8,6 +8,8 @@
 #include "dat.h"
 #include "fns.h"
 
+#define	DBG	if(0)
+
 enum
 {
 	Bits2=	3,
@@ -23,10 +25,10 @@
 };
 enum /* 2.2.2.2.1.1.2 Primary Drawing Order (PRIMARY_DRAWING_ORDER) */
 {
-	Clip=		1<<2,
+	Clipped=		1<<2,
 	NewOrder= 	1<<3,
 	Diff=			1<<4,
-	Lastclipr=	1<<5,
+	SameClipping=	1<<5,
 	ZeroFieldBit0=	1<<6,
 	ZeroFieldBit1=	1<<7,
 };
@@ -117,8 +119,8 @@
 	Rectangle clipr;
 } gc	= {PatBlt};
 
-static	int	loadclipr(Rectangle*,uchar*,int);
-static	int	loadpt(Point*,uchar*,int,int,int);
+static	int	cfclipr(Rectangle*,uchar*,int);
+static	int	cfpt(Point*,uchar*,int,int,int);
 
 int
 getfupd(Imgupd* up, uchar* a, uint nb)
@@ -137,10 +139,10 @@
 	if(ctl&Secondary){
 		if(p+6>ep)
 			sysfatal("draworders: %s", Eshort);
-		size = ((short)igets(p+1))+13;
+		size = ((short)GSHORT(p+1))+13;
 		if(size < 0 || p+size > ep)
 			sysfatal("draworders: size: %s", Eshort);
-		opt = igets(p+3);
+		opt = GSHORT(p+3);
 		xorder = p[5];
 		if(xorder >= nelem(auxtab) || auxtab[xorder].get == nil){
 			fprint(2, "egdi: unsupported secondary order %d\n", xorder);
@@ -166,7 +168,7 @@
 		fset = p[0]|(p[1]<<8)|(p[2]<<16);
 		break;
 	case 2:
-		fset = igets(p);
+		fset = GSHORT(p);
 		break;
 	case 1:
 		fset = p[0];
@@ -175,8 +177,8 @@
 	}
 	p += fsize;
 
-	if(ctl&Clip && !(ctl&Lastclipr))
-		p += loadclipr(&gc.clipr, p, ep-p);
+	if(ctl&Clipped && !(ctl&SameClipping))
+		p += cfclipr(&gc.clipr, p, ep-p);
 
 	if(ordtab[gc.order].get == nil)
 		goto ErrNotsup;
@@ -195,7 +197,7 @@
 }
 
 static int
-loadpt(Point* p, uchar* a, int nb, int isdelta, int fset)
+cfpt(Point* p, uchar* a, int nb, int isdelta, int fset)
 {
 	int n;
 
@@ -212,7 +214,7 @@
 			n += 2;
 	}
 	if(n>nb)
-		sysfatal("loadpt: %s", Eshort);
+		sysfatal("cfpt: %s", Eshort);
 
 	if(isdelta){
 		if(fset&1<<0)
@@ -221,28 +223,28 @@
 			p->y += (signed char)*a;
 	}else{
 		if(fset&1<<0){
-			p->x = igets(a);
+			p->x = GSHORT(a);
 			a += 2;
 		};
 		if(fset&1<<1)
-			p->y = igets(a);
+			p->y = GSHORT(a);
 	}
 	return n;
 }
 
 static int
-loadrect(Rectangle* pr, uchar* p, int nb, int isdelta, int fset){
+cfrect(Rectangle* pr, uchar* p, int nb, int isdelta, int fset){
 	int n, m;
 
 	pr->max = subpt(pr->max, pr->min);
-	n = loadpt(&pr->min, p, nb, isdelta, fset);
-	m = loadpt(&pr->max, p+n, nb-n, isdelta, fset>>2);
+	n = cfpt(&pr->min, p, nb, isdelta, fset);
+	m = cfpt(&pr->max, p+n, nb-n, isdelta, fset>>2);
 	pr->max = addpt(pr->max, pr->min);
 	return n+m;
 }
 
 static int
-loadclipr(Rectangle* pr, uchar* a, int nb)
+cfclipr(Rectangle* pr, uchar* a, int nb)
 {
 	int bctl;
 	uchar *p, *ep;
@@ -254,29 +256,29 @@
 	if(bctl&1<<4)
 		pr->min.x += (char)*p++;
 	else if(bctl&1<<0){
-		pr->min.x = igets(p);
+		pr->min.x = GSHORT(p);
 		p += 2;
 	}
 	if(bctl&1<<5)
 		pr->min.y += (char)*p++;
 	else if(bctl&1<<1){
-		pr->min.y = igets(p);
+		pr->min.y = GSHORT(p);
 		p += 2;
 	}
 	if(bctl&1<<6)
 		pr->max.x += (char)*p++;
 	else if(bctl&1<<2){
-		pr->max.x = igets(p)+1;
+		pr->max.x = GSHORT(p)+1;
 		p += 2;
 	}
 	if(bctl&1<<7)
 		pr->max.y += (char)*p++;
 	else if(bctl&1<<3){
-		pr->max.y = igets(p)+1;
+		pr->max.y = GSHORT(p)+1;
 		p += 2;
 	}
 	if(p>ep)
-		sysfatal("loadclipr: %s", Eshort);
+		sysfatal("cfclipr: %s", Eshort);
 	return p-a;
 }
 
@@ -291,10 +293,11 @@
 	static	Point wp;
 	static	int rop3;
 
+DBG	fprint(2, "getscrblt...");
 	p = a;
 	ep = a+nb;
 
-	n = loadrect(&wr, p, ep-p, ctl&Diff, fset);
+	n = cfrect(&wr, p, ep-p, ctl&Diff, fset);
 	p += n;
 	if(fset&1<<4){
 		if(ep-p<1)
@@ -301,11 +304,11 @@
 			sysfatal(Eshort);
 		rop3 = *p++;
 	}
-	n = loadpt(&wp, p, ep-p, ctl&Diff, fset>>5);
+	n = cfpt(&wp, p, ep-p, ctl&Diff, fset>>5);
 	p += n;
 
 	r = wr;
-	if(ctl&Clip)
+	if(ctl&Clipped)
 		rectclip(&r, gc.clipr);
 
 	if(rop3 != Scopy)
@@ -332,22 +335,23 @@
 	static Point pt;
 	int n;
 	uchar *p, *ep;
+DBG	fprint(2, "getmemblt...");
 
 	p = a;
 	ep = a+nb;
 
 	if(fset&1<<0){
-		cid = igets(p);
+		cid = GSHORT(p);
 		p += 2;
 	}
-	n = loadrect(&r, p, ep-p, ctl&Diff, fset>>1);
+	n = cfrect(&r, p, ep-p, ctl&Diff, fset>>1);
 	p += n;
 	if(fset&1<<5)
 		rop3 = *p++;
-	n = loadpt(&pt, p, ep-p, ctl&Diff, fset>>6);
+	n = cfpt(&pt, p, ep-p, ctl&Diff, fset>>6);
 	p += n;
 	if(fset&1<<8){
-		coff = igets(p);
+		coff = GSHORT(p);
 		p += 2;
 	}
 	if(p>ep)
@@ -366,9 +370,9 @@
 	up->ysz = Dy(r);
 	up->sx = pt.x;
 	up->sy = pt.y;
-	up->clip = 0;
-	if(ctl&Clip){
-		up->clip = 1;
+	up->clipped = 0;
+	if(ctl&Clipped){
+		up->clipped = 1;
 		up->cx = gc.clipr.min.x;
 		up->cy = gc.clipr.min.y;
 		up->cxsz = Dx(gc.clipr);
@@ -388,10 +392,11 @@
 	int n, g;
 	int size;
 
+DBG	fprint(2, "getimgcache2...");
 	p = a;
 	ep = a+nb;
 
-	up->compressed = (xorder==CacheCompressed2);
+	up->iscompr = (xorder==CacheCompressed2);
 
 	cid = opt&Bits3;
 	opt >>= 7;
@@ -431,7 +436,7 @@
 		g = ((g&Bits7)<<8) | *p++;
 	coff = g;
 
-	if(up->compressed && !(opt&1<<3)){
+	if(up->iscompr && !(opt&1<<3)){
 		p += 8;	// bitmapComprHdr
 		size -= 8;
 	}
@@ -438,7 +443,7 @@
 	if(p+size > ep)
 		sysfatal("cacheimage2: size: %s", Eshort);
 
-	up->type = Ucacheimg;
+	up->type = Uicache;
 	up->cid = cid;
 	up->coff = coff;
 	up->nbytes = size;
@@ -451,11 +456,12 @@
 getcmapcache(Imgupd* up, uchar* a, uint nb, int xorder, int opt)
 {
 	int cid, n;
+DBG	fprint(2, "getcmapcache...");
 	USED(xorder);
 	USED(opt);
 	
 	cid = a[6];
-	n = igets(a+7);
+	n = GSHORT(a+7);
 	if(n != 256)
 		sysfatal("cachecmap: %d != 256", n);
 	if(9+4*256 > nb)
@@ -466,3 +472,4 @@
 	up->nbytes = 4*256;
 	return 9+4*256;
 }
+
--- a/ele.c
+++ b/ele.c
@@ -111,24 +111,24 @@
 		 */
 		p[0] = CNeedLicense;
 		p[1] = PreambleV3;
-		iputs(p+2, ndata);
-		iputl(p+4, KeyExRSA);
-		iputl(p+8, 0);
+		PSHORT(p+2, ndata);
+		PLONG(p+4, KeyExRSA);
+		PLONG(p+8, 0);
 		memset(p+12, RandomSize, 0);
 		p += 12+RandomSize;
 	
-		iputs(p+0, Brandom);
-		iputs(p+2, 48);
+		PSHORT(p+0, Brandom);
+		PSHORT(p+2, 48);
 		memset(p+4, 48, 0);
 		p += 4+48;
 
-		iputs(p+0, Bcuser);
-		iputs(p+2, usersize);
+		PSHORT(p+0, Bcuser);
+		PSHORT(p+2, usersize);
 		memcpy(p+4, m->user, usersize);
 		p+= 4+usersize;
 	
-		iputs(p+0, Bchost);
-		iputs(p+2, hostsize);
+		PSHORT(p+0, Bchost);
+		PSHORT(p+2, hostsize);
 		memcpy(p+4, m->sysname, hostsize);
 		p+= 4+hostsize;
 		break;
@@ -140,11 +140,11 @@
 		/* type[1] flags[1] size[2] errcode[4] newstate[4] blob.type[2] blob.len[2] ... */
 		p[0] = Notify;
 		p[1] = PreambleV3;
-		iputs(p+2, ndata);
-		iputl(p+4, errcode);
-		iputl(p+8, newstate);
-		iputs(p+12, Berror);
-		iputs(p+14, 0);
+		PSHORT(p+2, ndata);
+		PLONG(p+4, errcode);
+		PLONG(p+8, newstate);
+		PSHORT(p+12, Berror);
+		PSHORT(p+14, 0);
 		break;
 	}
 	assert(p == ep);
--- a/fns.h
+++ b/fns.h
@@ -1,14 +1,4 @@
-/* byte.c*/
-short	igets(uchar*);
-long 	igetl(uchar*);
-void 	iputs(uchar*, short);
-void 	iputl(uchar*, long);
 
-short	nhgets(uchar*);
-long 	nhgetl(uchar*);
-void 	hnputs(uchar*, short);
-void 	hnputl(uchar*, long);
-
 /* mcs.c */
 int		mcschan(uchar*,uchar*);
 int		mcstype(uchar*,uchar*);
@@ -60,4 +50,3 @@
 void		pbshort(uchar*,int);
 
 uchar*	putsdh(uchar*,uchar*,int,int,int,int);
-
--- a/load.c
+++ b/load.c
@@ -42,11 +42,11 @@
 			return -1;
 		}
 		a[0] = 'y';
-		iputl(a+1, i->id);
-		iputl(a+5, r.min.x);
-		iputl(a+9, r.max.y-1);
-		iputl(a+13, r.max.x);
-		iputl(a+17, r.max.y);
+		BPLONG(a+1, i->id);
+		BPLONG(a+5, r.min.x);
+		BPLONG(a+9, r.max.y-1);
+		BPLONG(a+13, r.max.x);
+		BPLONG(a+17, r.max.y);
 		memmove(a+21, data, n);
 		ndata += n;
 		data += n;
@@ -95,7 +95,7 @@
 	ep = as->data + as->ndata;
 	cmap = c->cmap;
 
-	n = igets(p+4);
+	n = GSHORT(p+4);
 	p += 8;
 	if(n > sizeof(c->cmap)){
 		fprint(2, "loadcmap: data too big");
--- a/mcs.c
+++ b/mcs.c
@@ -94,9 +94,9 @@
 	default:	werrstr(Ebignum); return nil;
 	case 0:	*plen = 0; break;
 	case 1:	*plen = p[0]; break;
-	case 2:	*plen = nhgets(p); break;
-	case 3:	*plen = (nhgets(p)<<8)|p[2]; break;
-	case 4:	*plen = nhgetl(p); break;
+	case 2:	*plen = GSHORTB(p); break;
+	case 3:	*plen = (GSHORTB(p)<<8)|p[2]; break;
+	case 4:	*plen = GLONGB(p); break;
 	}
 	return p+c;
 }
@@ -106,7 +106,7 @@
 {
 	p[0]=2;
 	p[1]=2;
-	hnputs(p+2,v);
+	PSHORTB(p+2,v);
 }
 
 int
@@ -145,7 +145,7 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	return nhgets(p+3);
+	return GSHORTB(p+3);
 }
 
 uchar*
@@ -184,9 +184,9 @@
 		return -1;
 	}
 
-	hnputs(p, 0x7f65);	/* Connect-Initial tag */
+	PSHORTB(p, 0x7f65);	/* Connect-Initial tag */
 	p[2] = 0x82;		/* length in next 2 bytes  */
-	hnputs(p+3, ndata+MCSCIFIXLEN-2*2-1);
+	PSHORTB(p+3, ndata+MCSCIFIXLEN-2*2-1);
 	p += 5;
 
 	/* BER callingDomainSelector */
@@ -247,7 +247,7 @@
 	/* BER userData */
 	p[0] = TagOctetString;
 	p[1] = 0x82;			/* length in next 2 bytes  */
-	hnputs(p+2, ndata);
+	PSHORTB(p+2, ndata);
 	/* userData should follow */
 
 	return MCSCIFIXLEN+ndata;
@@ -299,73 +299,71 @@
 	earlyCapabilityFlags = CanErrinfo;
 	if(depth == 32)
 		earlyCapabilityFlags |= Want32bpp;
-	if(depth > 24)
-		depth = 24;
 
 	memcpy(p, t124IdentifierKeyOid, 7);
 
 	// connectPDU as a PER octet string
-	hnputs(p+7, (gccsize | 0x8000));	// connectPDU length
-	hnputs(p+9, 8);		// ConferenceCreateRequest
-	hnputs(p+11, 16);
+	PSHORTB(p+7, (gccsize | 0x8000));	// connectPDU length
+	PSHORTB(p+9, 8);		// ConferenceCreateRequest
+	PSHORTB(p+11, 16);
 	p[13] = 0;
-	iputs(p+14, 0xC001);	// userData key: h221NonStandard
+	PSHORT(p+14, 0xC001);	// userData key: h221NonStandard
 	p[16] = 0;
 	memcpy(p+17, "Duca", 4);	// H.221 nonstandard key (3.2.5.3.3)
-	hnputs(p+21, ((gccsize-14) | 0x8000));		// userData length
+	PSHORTB(p+21, ((gccsize-14) | 0x8000));		// userData length
 	p += 23;
 
 	// 2.2.1.3.2 Client Core Data
-	iputs(p+0, ClientCore);
-	iputs(p+2, 216);	// length of the data block
-	iputl(p+4, ver);	// rdpVersion: RDP5=0x00080004
-	iputs(p+8, width);	// desktopWidth ≤ 4096
-	iputs(p+10, height);	// desktopHeight ≤ 2048
-	iputs(p+12, 0xCA01);	// colorDepth=8bpp, ignored
-	iputs(p+14, 0xAA03);	// SASSequence
-	iputl(p+16, 0x409);	// keyboardLayout=us
-	iputl(p+20, 2600); 	// clientBuild
+	PSHORT(p+0, ClientCore);
+	PSHORT(p+2, 216);	// length of the data block
+	PLONG(p+4, ver);	// rdpVersion: RDP5=0x00080004
+	PSHORT(p+8, width);	// desktopWidth ≤ 4096
+	PSHORT(p+10, height);	// desktopHeight ≤ 2048
+	PSHORT(p+12, 0xCA01);	// colorDepth=8bpp, ignored
+	PSHORT(p+14, 0xAA03);	// SASSequence
+	PLONG(p+16, 0x409);	// keyboardLayout=us
+	PLONG(p+20, 2600); 	// clientBuild
 	memset(p+24, 32, 0);	// clientName[32]
 	toutf16(p+24, 32, sysname, strlen(sysname)+1);
-	iputs(p+54, 0);		// zero-terminateclientName
-	iputl(p+56, 4);	// keyboardType: 4="IBM enhanced (101-key or 102-key)"
-	iputl(p+60, 0);	// keyboardSubType
-	iputl(p+64, 12);	// keyboardFunctionKey
+	PSHORT(p+54, 0);		// zero-terminateclientName
+	PLONG(p+56, 4);	// keyboardType: 4="IBM enhanced (101-key or 102-key)"
+	PLONG(p+60, 0);	// keyboardSubType
+	PLONG(p+64, 12);	// keyboardFunctionKey
 	memset(p+68, 64, 0);	// imeFileName[64]
-	iputs(p+132, 0xCA01);	// postBeta2ColorDepth=8bpp, ignored
-	iputs(p+134, 1);	// clientProductId
-	iputl(p+136, 0);	// serialNumber
-	iputs(p+140, depth);	// highColorDepth: 4, 8, 15, 16, 24 bpp.
-	iputs(p+142, 1+2+4+8);	// supportedColorDepths: 1=24, 2=16, 4=15, 8=32 bpp
-	iputs(p+144, earlyCapabilityFlags);	// earlyCapabilityFlags 
+	PSHORT(p+132, 0xCA01);	// postBeta2ColorDepth=8bpp, ignored
+	PSHORT(p+134, 1);	// clientProductId
+	PLONG(p+136, 0);	// serialNumber
+	PSHORT(p+140, MIN(depth, 24));	// highColorDepth: 4, 8, 15, 16, 24 bpp.
+	PSHORT(p+142, 1+2+4+8);	// supportedColorDepths: 1=24, 2=16, 4=15, 8=32 bpp
+	PSHORT(p+144, earlyCapabilityFlags);	// earlyCapabilityFlags 
 	memset(p+146, 64, 0);	// clientDigProductId[64]
 	p[210] = 7;	// connectionType: 7=autodetect
 	p[211] = 0;	// pad1octet
-	iputl(p+212, sproto);	// serverSelectedProtocol
+	PLONG(p+212, sproto);	// serverSelectedProtocol
 	p += 216;
 	
 	// 2.2.1.3.3 Client Security Data
-	iputs(p+0, ClientSec);
-	iputs(p+2, 12);	// length of the data block
-	iputl(p+4, 0); 	// (legacy) encryptionMethods
-	iputl(p+8, 0); 	// extEncryptionMethods
+	PSHORT(p+0, ClientSec);
+	PSHORT(p+2, 12);	// length of the data block
+	PLONG(p+4, 0); 	// (legacy) encryptionMethods
+	PLONG(p+8, 0); 	// extEncryptionMethods
 	p += 12;
 
 	// 2.2.1.3.5 Client Cluster Data		*optional*
-	iputs(p+0, ClientCluster);
-	iputs(p+2, 12);	// length of the data block
-	iputl(p+4, (wantconsole? 11 : 9));	// Flags
-	iputl(p+8, 0);		// RedirectedSessionID
+	PSHORT(p+0, ClientCluster);
+	PSHORT(p+2, 12);	// length of the data block
+	PLONG(p+4, (wantconsole? 11 : 9));	// Flags
+	PLONG(p+8, 0);		// RedirectedSessionID
 	p += 12;
 
 	// 2.2.1.3.4 Client Network Data 	*optional*
 	// type[2] len[2] nchan[4] nchan*(name[8] options[4])
-	iputs(p+0, ClientNet);
-	iputs(p+2, 8+12*nv);
-	iputl(p+4, nv);
+	PSHORT(p+0, ClientNet);
+	PSHORT(p+2, 8+12*nv);
+	PLONG(p+4, nv);
 	for(i=0; i<nv; i++){
 		memcpy(p+8+12*i+0, v[i].name, 8);
-		hnputl(p+8+12*i+8, v[i].flags);
+		PLONGB(p+8+12*i+8, v[i].flags);
 	}
 	p += 8+12*nv;
 
@@ -423,11 +421,11 @@
 
 	while(p<ep){
 		/* 2.2.1.3.1 User Data Header (TS_UD_HEADER) */
-		utype = igets(p+0);
-		ulen = igets(p+2);
+		utype = GSHORT(p+0);
+		ulen = GSHORT(p+2);
 		switch(utype){
 		case SrvCore:		/* 2.2.1.4.2 Server Core Data */
-			m->ver = igetl(p+4);
+			m->ver = GLONG(p+4);
 			break;
 		/* BUG: exract channel IDs from SrvNet */
 		}
@@ -447,9 +445,9 @@
 	}
 	
 	p[0] = (Msdr<<2);
-	hnputs(p+1, mcsuid);
-	hnputs(p+3, chanid);
+	PSHORTB(p+1, mcsuid);
+	PSHORTB(p+3, chanid);
 	p[5] = 0x70;
-	hnputs(p+6, ndata|0x8000);
+	PSHORTB(p+6, ndata|0x8000);
 	return 8;
 }
--- a/mkfile
+++ b/mkfile
@@ -8,10 +8,10 @@
 OFILES=\
 	alloc.$O\
 	audio.$O\
-	byte.$O\
 	cap.$O\
 	draw.$O\
 	eclip.$O\
+	efs.$O\
 	egdi.$O\
 	ele.$O\
 	kbd.$O\
@@ -31,7 +31,11 @@
 	x224.$O\
 
 THREADOFILES=${OFILES:rd.$O=rd-thread.$O}
-CLEANFILES=$O.thread
+CLEANFILES=$O.thread $O.test
+TESTHFILES=audio.c
+TESTOFILES=\
+	efs_test.$O	efs.$O utf16.$O \
+	aud_test.$O	
 
 </sys/src/cmd/mkone
 #<$PLAN9/src/mkone
@@ -40,6 +44,12 @@
 
 default:V: $O.thread
 all:V: $O.thread
+test:V: $O.test
+	$O.test
 
 $O.thread:	$THREADOFILES $LIB
+	$LD $LDFLAGS -o $target $prereq
+
+$TESTOFILES: $TESTHFILES
+$O.test:	$TESTOFILES $LIB
 	$LD $LDFLAGS -o $target $prereq
--- a/mpas.c
+++ b/mpas.c
@@ -83,7 +83,7 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	nb = igets(p);
+	nb = GSHORT(p);
 	p += 2;
 	q = p+nb;
 	if(cflags&Pcompress){
@@ -105,7 +105,7 @@
 			return -1;
 		}
 		as->type = ShUorders;
-		as->nr = igets(p);
+		as->nr = GSHORT(p);
 		as->data = p+2;
 		as->ndata = ep-(p+2);
 		break;
@@ -115,7 +115,7 @@
 			return -1;
 		}
 		as->type = ShUimg;
-		as->nr = igets(p+2);
+		as->nr = GSHORT(p+2);
 		as->data = p+4;
 		as->ndata = ep-(p+4);
 		break;
@@ -130,8 +130,8 @@
 			return -1;
 		}
 		as->type = ShUwarp;
-		as->x = igets(p+0);
-		as->y = igets(p+2);
+		as->x = GSHORT(p+0);
+		as->y = GSHORT(p+2);
 		break;
 	}
 	return q-a;
@@ -157,13 +157,13 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	len = igets(p);
+	len = GSHORT(p);
 	if(len < SCHSIZE || len > nb){
 		werrstr("bad length in Share Control PDU header");
 		return -1;
 	}
-	type = igets(p+2)&Bits4;
-	as->source = igets(p+4);
+	type = GSHORT(p+2)&Bits4;
+	as->source = GSHORT(p+4);
 
 	switch(type){
 	case PDUactivate:
@@ -176,15 +176,15 @@
 			werrstr(Eshort);
 			return -1;
 		}
-		as->shareid = igetl(p+6);
-		nsrc = igets(p+10);
-		as->ndata = igets(p+12);
+		as->shareid = GLONG(p+6);
+		nsrc = GSHORT(p+10);
+		as->ndata = GSHORT(p+12);
 		if(len<14+nsrc+as->ndata){
 			werrstr(Eshort);
 			return -1;
 		}
 		as->data = p+14+nsrc;
-		as->ncap = igets(p+14+nsrc);
+		as->ncap = GSHORT(p+14+nsrc);
 		break;
 	case PDUdeactivate:
 		as->type = ShDeactivate;
@@ -204,10 +204,10 @@
 			return -1;
 		}
 	
-		ulen = igets(p+12);
+		ulen = GSHORT(p+12);
 		pduType2 = p[14];
 		ctype = p[15];
-		clen = igets(p+16) - SCDSIZE;
+		clen = GSHORT(p+16) - SCDSIZE;
 		p += 18;
 	
 		if(ctype&(1<<5)){
@@ -243,7 +243,7 @@
 				return -1;
 			}
 			as->type = ShEinfo;
-			as->err = igetl(p);
+			as->err = GLONG(p);
 			break;
 		case ADdraw:
 			/* 2.2.9.1.1.3.1 Slow-Path Graphics Update (TS_GRAPHICS_UPDATE) */
@@ -252,7 +252,7 @@
 				werrstr("ADdraw: %s", Eshort);
 				return -1;
 			}
-			uptype = igets(p);
+			uptype = GSHORT(p);
 			switch(uptype){
 			case UpdOrders:
 				if(p+8 > ep){
@@ -260,7 +260,7 @@
 					return -1;
 				}
 				as->type = ShUorders;
-				as->nr = igets(p+4);
+				as->nr = GSHORT(p+4);
 				as->data = p+8;
 				as->ndata = ep-(p+8);
 				break;
@@ -270,7 +270,7 @@
 					return -1;
 				}
 				as->type = ShUimg;
-				as->nr = igets(p+2);
+				as->nr = GSHORT(p+2);
 				as->data = p+4;
 				as->ndata = ep-(p+4);
 				break;
@@ -287,7 +287,7 @@
 				werrstr(Eshort);
 				return -1;
 			}
-			mtype = igets(p);
+			mtype = GSHORT(p);
 			if(mtype == PDUcursorwarp){
 				if(p+8 > ep){
 					werrstr(Eshort);
@@ -294,8 +294,8 @@
 					return -1;
 				}
 				as->type = ShUwarp;
-				as->x = igets(p+4);
-				as->y = igets(p+6);
+				as->x = GSHORT(p+4);
+				as->y = GSHORT(p+6);
 				break;
 			}
 		}
@@ -306,7 +306,7 @@
 
 /* 2.2.9.1.1.3.1.2.2 Bitmap Data (TS_BITMAP_DATA) */
 int
-getimgupd(Imgupd* u, uchar* a, uint nb)
+getimgupd(Imgupd* iu, uchar* a, uint nb)
 {
 	uchar *p, *ep, *s;
 	int opt, len;
@@ -317,16 +317,16 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	u->type = Ubitmap;
-	u->x = igets(p+0);
-	u->y = igets(p+2);
-	u->xm = igets(p+4);
-	u->ym = igets(p+6);
-	u->xsz = igets(p+8);
-	u->ysz = igets(p+10);
-	u->depth = igets(p+12);
-	opt = igets(p+14);
-	len = igets(p+16);
+	iu->type = Ubitmap;
+	iu->x = GSHORT(p+0);
+	iu->y = GSHORT(p+2);
+	iu->xm = GSHORT(p+4);
+	iu->ym = GSHORT(p+6);
+	iu->xsz = GSHORT(p+8);
+	iu->ysz = GSHORT(p+10);
+	iu->depth = GSHORT(p+12);
+	opt = GSHORT(p+14);
+	len = GSHORT(p+16);
 	p += 18;
 	s = p+len;
 	if(s > ep){
@@ -333,11 +333,11 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	u->compressed = (opt&Bcompress);
+	iu->iscompr = (opt&Bcompress);
 	if(opt&Bcompress && !(opt&NoBitcomphdr))
 		p += 8;
-	u->bytes = p;
-	u->nbytes = s-p;
+	iu->bytes = p;
+	iu->nbytes = s-p;
 	return s-a;
 }
 
@@ -351,7 +351,7 @@
 		werrstr(Eshort);
 		return -1;
 	}
-	marker = igets(p);
+	marker = GSHORT(p);
 	return marker == 0x8000;
 }
 
@@ -386,15 +386,15 @@
 
 	/* 2.2.8.1.1.1.1 Share Control Header */
 	/* totalLength[2] pduType[2] PDUSource[2] */
-	iputs(p+0, ndata);
-	iputs(p+2, PDUactivated | (1<<4));
-	iputs(p+4, userchan);
+	PSHORT(p+0, ndata);
+	PSHORT(p+2, PDUactivated | (1<<4));
+	PSHORT(p+4, userchan);
 
 	/* shareId[4] originatorId[2] sdlen[2] caplen[2] srcdesc[sdlen] ncap[2] pad[2] */
-	iputl(p+6, shareid);
-	iputs(p+10, originid);
-	iputs(p+12, nsrc);
-	iputs(p+14, capsize);
+	PLONG(p+6, shareid);
+	PSHORT(p+10, originid);
+	PSHORT(p+12, nsrc);
+	PSHORT(p+14, capsize);
 	memcpy(p+16, srcDesc, nsrc);
 	p += nsrc+16;
 	if((n = putcaps(p, ep-p, &caps)) < 0)
@@ -467,13 +467,13 @@
 	usize = p+ndata-b;
 	q = p;
 
-	iputl(q+0, 0);	// codePage; langId when opt&InfUnicode
-	iputl(q+4, opt);
-	iputs(q+8, ndom-2);
-	iputs(q+10, nusr-2);
-	iputs(q+12, npw-2);
-	iputs(q+14, nsh-2);
-	iputs(q+16, nwd-2);
+	PLONG(q+0, 0);	// codePage; langId when opt&InfUnicode
+	PLONG(q+4, opt);
+	PSHORT(q+8, ndom-2);
+	PSHORT(q+10, nusr-2);
+	PSHORT(q+12, npw-2);
+	PSHORT(q+14, nsh-2);
+	PSHORT(q+16, nwd-2);
 	q += 18;
 	memcpy(q, wdom, ndom);
 	q += ndom;
@@ -486,13 +486,13 @@
 	memcpy(q, wwd, nwd);
 	q += nwd;
 
-	iputs(q+0, 2);	// cbClientAddress 
-	iputs(q+2, 0);	// clientAddress
-	iputs(q+4, 2);	// cbClientDir
-	iputs(q+6, 0);	// clientDir
+	PSHORT(q+0, 2);	// cbClientAddress 
+	PSHORT(q+2, 0);	// clientAddress
+	PSHORT(q+4, 2);	// cbClientDir
+	PSHORT(q+6, 0);	// clientDir
 	memset(q+8, 172, 0);	// clientTimeZone
-	iputl(q+180, 0);	// clientSessionId
-	iputl(q+184, perfopt);	// performanceFlags 
+	PLONG(q+180, 0);	// clientSessionId
+	PLONG(q+184, perfopt);	// performanceFlags 
 	q += 188;
 
 	assert(q == p+ndata);
@@ -512,15 +512,15 @@
 {
 	if(p+18>ep)
 		sysfatal(Eshort);
-	iputs(p+0, ndata);
-	iputs(p+2, (PDUdata | (1<<4)));
-	iputs(p+4, originid);
-	iputl(p+6, shareid);
+	PSHORT(p+0, ndata);
+	PSHORT(p+2, (PDUdata | (1<<4)));
+	PSHORT(p+4, originid);
+	PLONG(p+6, shareid);
 	p[10] = 0;	// pad1
 	p[11] = 1;	// streamId: 1=Low, 2=Medium, 4=High
-	iputs(p+12, ndata);
+	PSHORT(p+12, ndata);
 	p[14] = pduType2;
 	p[15] = 0; // compressedType 
-	iputs(p+16, 0); // compressedLength 
+	PSHORT(p+16, 0); // compressedLength 
 	return p+18;
 }
--- a/msg.c
+++ b/msg.c
@@ -29,8 +29,8 @@
 	}
 	b[0] = m->type;
 	b[1] = m->flags;
-	iputs(b+2, len);
-	iputl(b+4, m->proto);
+	PSHORT(b+2, len);
+	PLONG(b+4, m->proto);
 
 	return len;
 }
@@ -46,8 +46,8 @@
 	}
 	m->type = b[0];
 	m->flags = b[1];
-	len = igets(b+2);
-	m->proto = igetl(b+4);
+	len = GSHORT(b+2);
+	m->proto = GLONG(b+4);
 	if(len != 8){
 		werrstr("bad length in RDP Nego Response");
 		return -1;
@@ -94,7 +94,7 @@
 	p += 8;
 
 	if(shdsize > 0)
-		iputl(p, secflags);
+		PLONG(p, secflags);
 	return p + shdsize;
 }
 
@@ -139,8 +139,8 @@
 			return -1;
 		p = b+TPDATAFIXLEN;
 		p[0] = (Mcjr << 2);
-		hnputs(p+1, m->mcsuid);
-		hnputs(p+3, m->chanid);
+		PSHORTB(p+1, m->mcsuid);
+		PSHORTB(p+3, m->chanid);
 		return n;
 
 	case Merectdom:
@@ -149,8 +149,8 @@
 			return -1;
 		p = b+TPDATAFIXLEN;
 		p[0] = (Medr << 2);
-		hnputs(p+1, 1);
-		hnputs(p+3, 1);
+		PSHORTB(p+1, 1);
+		PSHORTB(p+3, 1);
 		return n;
 
 	case Mconnect:
@@ -188,8 +188,8 @@
 		p = txprep(b, nb, nld, m->chanid, m->originid, 0);
 		if(p == nil)
 			return -1;
-		iputl(p+0, m->len);
-		iputl(p+4, m->flags);
+		PLONG(p+0, m->len);
+		PLONG(p+4, m->flags);
 		memcpy(p+8, m->data, m->ndata);
 		len = p+nld-b;
 		return len;
@@ -201,8 +201,8 @@
 			return -1;
 		len = p+nld-b;
 		q = putsdh(p, p+nld, nld, ADsync, m->originid, m->shareid);
-		iputs(q+0, 1);	// sync message type
-		iputs(q+2, m->mcsuid);	// target MCS userId
+		PSHORT(q+0, 1);	// sync message type
+		PSHORT(q+2, m->mcsuid);	// target MCS userId
 		return len;
 
 	case Actl:
@@ -214,9 +214,9 @@
 	
 		q = putsdh(p, p+nld, nld, ADctl, m->originid, m->shareid);
 		/* action[2] grantId[2] controlId[2] */
-		iputs(q+0, m->action);
-		iputs(q+2, 0);
-		iputl(q+4, 0);
+		PSHORT(q+0, m->action);
+		PSHORT(q+2, 0);
+		PLONG(q+4, 0);
 		return len;
 
 	case Afontls:
@@ -227,10 +227,10 @@
 		len = p+nld-b;
 	
 		q = putsdh(p, p+nld, nld, ADfontlist, m->originid, m->shareid);
-		iputs(q+0, 0);	// numberFonts
-		iputs(q+2, 0);	// totalNumFonts
-		iputs(q+4, 2+1);	// listFlags: 1=first, 2=last
-		iputs(q+6, 50);	// entrySize
+		PSHORT(q+0, 0);	// numberFonts
+		PSHORT(q+2, 0);	// totalNumFonts
+		PSHORT(q+4, 2+1);	// listFlags: 1=first, 2=last
+		PSHORT(q+6, 50);	// entrySize
 		return len;
 
 	case Ainput:
@@ -241,13 +241,13 @@
 		len = p+nld-b;
 	
 		q = putsdh(p, p+nld, nld, ADinput, m->originid, m->shareid);
-		iputs(q+0, 1);	/* numEvents */
-		iputs(q+2, 0);
-		iputl(q+4, m->msec);
-		iputs(q+8, m->mtype);
-		iputs(q+10, m->flags);
-		iputs(q+12, m->iarg[0]);
-		iputs(q+14, m->iarg[1]);
+		PSHORT(q+0, 1);	/* numEvents */
+		PSHORT(q+2, 0);
+		PLONG(q+4, m->msec);
+		PSHORT(q+8, m->mtype);
+		PSHORT(q+10, m->flags);
+		PSHORT(q+12, m->iarg[0]);
+		PSHORT(q+14, m->iarg[1]);
 		return len;
 
 	case Lreq:
@@ -274,10 +274,10 @@
 		q[0] = (m->allow?1:0); 
 		memset(q+1, 3, 0);
 		if(m->allow){
-			iputs(q+4, 0);	// left
-			iputs(q+6, 0);	// top
-			iputs(q+8, m->xsz-1);	// right
-			iputs(q+10, m->ysz-1);	// bottom
+			PSHORT(q+4, 0);	// left
+			PSHORT(q+6, 0);	// top
+			PSHORT(q+8, m->xsz-1);	// right
+			PSHORT(q+10, m->ysz-1);	// bottom
 		}
 		return len;
 
@@ -356,7 +356,7 @@
 					werrstr(Eshort);
 					return -1;
 				}
-				m->mcsuid = nhgets(p+2);
+				m->mcsuid = GSHORTB(p+2);
 			}
 			return nb;
 		case Mcjc:
@@ -381,8 +381,8 @@
 				return -1;
 			if(m->chanid != GLOBALCHAN){
 				m->type = Mvchan;
-				m->len = igetl(p+0);
-				m->flags = igetl(p+4);
+				m->len = GLONG(p+0);
+				m->flags = GLONG(p+4);
 				m->data = p+8;
 				m->ndata = ep-p-8;
 				if(m->len > 8*1024*1024){
@@ -395,8 +395,8 @@
 				m->type = Aflow;
 				return nb;
 			}
-			secflg = igets(p);
-			sctlver = igets(p+2)>>4;
+			secflg = GSHORT(p);
+			sctlver = GSHORT(p+2)>>4;
 			if(secflg&Slicensepk && sctlver != 1)
 				return getlicensemsg(m, p+4, ep-(p+4));
 			
--- a/rd-thread.c
+++ b/rd-thread.c
@@ -214,3 +214,8 @@
 		apply(c, &r);
 	}
 }
+
+void
+playsound(Rdp*, uchar*, uint)
+{
+}
--- a/rd.c
+++ b/rd.c
@@ -23,7 +23,7 @@
 static void
 usage(void)
 {
-	fprint(2, "usage: rd [-0A] [-T title] [-a depth] [-c wdir] [-d dom] [-k keyspec] [-n term] [-s shell] [net!]server[!port]\n");
+	fprint(2, "usage: rd [-0A] [-T title] [-a depth] [-c wdir] [-d dom] [-k keyspec] [-s shell] [net!]server[!port]\n");
 	exits("usage");
 }
 
@@ -84,6 +84,45 @@
 	return 0;
 }
 
+static int afd = -1;
+
+static void
+readaudiopipe(Rdp*, int fd)
+{
+	int n, afd;
+	uchar buf[4];
+
+	afd = open("/dev/audio", OWRITE);
+	for(;;){
+		if((n = read(fd, buf, sizeof buf)) < 0)
+			break;
+		write(afd, buf, n);
+	}
+}
+
+static int
+startaudioproc(Rdp* c)
+{
+	int pid, fd[2];
+	
+	if(pipe(fd) < 0)
+		sysfatal("pipe: %r");
+
+	switch(pid = rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		break;
+	default:
+		close(fd[0]);
+		afd = fd[1];
+		return pid;
+	}
+	close(fd[1]);
+	readaudiopipe(c, fd[0]);
+	return 0;
+}
+
 static int killpid[32];
 static int nkillpid;
 
@@ -185,6 +224,7 @@
 	atexitkill(startmouseproc(c));
 	atexitkill(startkbdproc(c));
 	atexitkill(startsnarfproc(c));
+	//atexitkill(startaudioproc(c));
 
 	readnet(c);
 
@@ -269,4 +309,10 @@
 		return;
 
 	fprint(mfd, "m%d %d", x, y);
+}
+
+void
+playsound(Rdp*, uchar* a, uint n)
+{
+	write(afd, a, n);
 }
--- a/rle.c
+++ b/rle.c
@@ -104,8 +104,8 @@
 	we = d+nd;
 	wasbg = 0;
 	wasline1 = 1;
-	iputl(pen, DWhite);
-	iputl(wpen, DWhite);
+	PLONG(pen, DWhite);
+	PLONG(wpen, DWhite);
 
 	while(p < ep){
 		hdr = *p++;
@@ -129,7 +129,7 @@
 		}else{
 			code = hdr&Bits4;
 			if(code < 9){
-				len = igets(p);
+				len = GSHORT(p);
 				p += 2;
 			}else
 				len = 0;
@@ -138,7 +138,7 @@
 		len *= pixelsize;
 		if(wp+len > we){
 		   Overrun:
-			werrstr("unrle: output buffer is %zd bytes short", (intptr)(wp+len-we));
+			werrstr("unrle: output buffer is %uld bytes short", wp+len-we);
 			return nil;
 		}
 
--- a/utf16.c
+++ b/utf16.c
@@ -28,7 +28,7 @@
 		if(*s == '\n'){
 			if(b+2 > eb)
 				break;
-			iputs(b, '\r');
+			PSHORT(b, '\r');
 			b+=2;
 		}
 		s += chartorune(&r, s);
@@ -35,20 +35,20 @@
 		if(b+2 > eb)
 			break;
 		if(r <= Bits16){
-			iputs(b, r);
+			PSHORT(b, r);
 			b+=2;
 			continue;
 		} 
 		r -= Bits16+1;
 		if(r > Bits20){
-			iputs(b, Runeerror);
+			PSHORT(b, Runeerror);
 			b+=2;
 			continue;
 		}
 		if(b+4 > eb)
 			break;
-		iputs(b+0, HHalfZoneS | (r >> 10));
-		iputs(b+2, LHalfZoneS | (r & Bits10));
+		PSHORT(b+0, HHalfZoneS | (r >> 10));
+		PSHORT(b+2, LHalfZoneS | (r & Bits10));
 		b+=4;
 	}
 	return b-buf;
@@ -69,7 +69,7 @@
 	eq = ws + nw;
 
 	while(q+2 <= eq){
-		w1 = igets(q);
+		w1 = GSHORT(q);
 		q += 2;
 		if(w1<HHalfZoneS || w1>LHalfZoneE){
 			r = w1;
@@ -83,7 +83,7 @@
 			r = Runeerror;
 			goto Convert;
 		}
-		w2 = igets(q);
+		w2 = GSHORT(q);
 		q += 2;
 		if(w2<LHalfZoneS || w2>LHalfZoneE){
 			r = Runeerror;
--- a/vchan.c
+++ b/vchan.c
@@ -31,6 +31,12 @@
 	},
 	{
 		.mcsid = GLOBALCHAN+2,
+		.name = "RDPDR",
+		.fn = efsvcfn,
+		.flags = Inited,
+	},
+	{
+		.mcsid = GLOBALCHAN+3,
 		.name = "RDPSND",
 		.fn = audiovcfn,
 		.flags = Inited,
--- a/x224.c
+++ b/x224.c
@@ -60,7 +60,7 @@
 		break;
 	default:
 		/* TPKT-encapsulated TPDU */
-		len = nhgets(p+2);
+		len = GSHORTB(p+2);
 	}
 	
 	if(len <= n || len > nbuf){
@@ -111,13 +111,13 @@
 	/* TPKT header: version[1] unused[1] len[2] */
 	p[0] = 0x03;
 	p[1] = 0;
-	hnputs(p+2, size);
+	PSHORTB(p+2, size);
 
 	/* ConReq: hdlen[1] type[1] dstref[2] srcref[2] class[1] */
 	p[4+0] = 7-1+ndata;
 	p[4+1] = ConReq;
-	hnputs(p+4+2, 0);
-	hnputs(p+4+4, 0);
+	PSHORTB(p+4+2, 0);
+	PSHORTB(p+4+4, 0);
 	p[4+6] = 0;
 
 	return size;
@@ -140,7 +140,7 @@
 	/* TPKT header: version[1] unused[1] len[2] */
 	p[0] = 0x03;
 	p[1] = 0;
-	hnputs(p+2, size);
+	PSHORTB(p+2, size);
 
 	/* TPDU: hdlen[1] type[1] seqno[1] */
 	p[4] = 2;
@@ -167,7 +167,7 @@
 	/* TPKT header: version[1] unused[1] len[2] */
 	p[0] = 0x03;
 	p[1] = 0;
-	hnputs(p+2, size);
+	PSHORTB(p+2, size);
 
 	/* HupReq: hdlen[1] type[1] seqno[1] */
 	p[4] = 2;
--