diff --git a/src/sbbs3/js_file.c b/src/sbbs3/js_file.c
index 2cf7962f227634448b6f096746aa55d57f85249e..410a9d50cb03c7dc08380035e81980de4daf528c 100644
--- a/src/sbbs3/js_file.c
+++ b/src/sbbs3/js_file.c
@@ -43,6 +43,7 @@ typedef struct
 {
 	char	name[MAX_PATH+1];
 	char	mode[4];
+	uchar	etx;
 	FILE*	fp;
 	BOOL	external;	/* externally created, don't close */
 	BOOL	debug;
@@ -62,7 +63,7 @@ static void dbprintf(BOOL error, private_t* p, char* fmt, ...)
     vsprintf(sbuf,fmt,argptr);
     va_end(argptr);
 	
-	lprintf("%04u File %s%s",fileno(p->fp),error ? "ERROR: ":"",sbuf);
+	lprintf("%04u File %s%s",p->fp ? fileno(p->fp) : 0,error ? "ERROR: ":"",sbuf);
 }
 
 /* File Constructor (creates file descriptor) */
@@ -108,7 +109,7 @@ static void js_finalize_file(JSContext *cx, JSObject *obj)
 	if(p->external==JS_FALSE && p->fp!=NULL)
 		fclose(p->fp);
 
-	dbprintf(FALSE, p, "closed");
+	dbprintf(FALSE, p, "closed: %s",p->name);
 
 	free(p);
 
@@ -142,8 +143,10 @@ js_open(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 	}
 	sprintf(p->mode,"%.*s",sizeof(p->mode)-1,mode);
 
-	if((p->fp=fopen(p->name,p->mode))!=NULL)
+	if((p->fp=fopen(p->name,p->mode))==NULL)
 		*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+	else
+		dbprintf(FALSE, p, "opened: %s",p->name);
 
 	return(JS_TRUE);
 }
@@ -171,64 +174,88 @@ js_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 	return(JS_TRUE);
 }
 
-
 static JSBool
-js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+js_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
 	char*		cp;
-	size_t		len;
+	char*		buf;
+	int			len=512;
 	JSString*	str;
 	private_t*	p;
 
+	*rval = JSVAL_NULL;
+
 	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
 		return(JS_FALSE);
 
-	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+	if(p->fp==NULL)
+		return(JS_TRUE);
 
-	if(!argc)
-		return(JS_FALSE);
+	if(argc)
+		len = JSVAL_TO_INT(argv[0]);
 
-	str = JS_ValueToString(cx, argv[0]);
-	cp=JS_GetStringBytes(str);
-	len=strlen(cp);
-	if(fwrite(cp,1,len,p->fp)==len) {
-		dbprintf(FALSE, p, "wrote %u bytes",len);
-		*rval = BOOLEAN_TO_JSVAL(JS_TRUE);
-	} else 
-		dbprintf(TRUE, p, "write of %u bytes failed",len);
+	if((buf=malloc(len+1))==NULL)
+		return(JS_TRUE);
+
+	len = fread(buf,1,len,p->fp);
+	if(len<0) 
+		len=0;
+	buf[len]=0;
+
+	if(p->etx) {
+		cp=strchr(buf,p->etx);
+		if(cp) *cp=0; 
+	}
+
+	str = JS_NewStringCopyZ(cx, buf);
+
+	free(buf);
+
+	*rval = STRING_TO_JSVAL(str);
+
+	dbprintf(FALSE, p, "read %u bytes",len);
 		
 	return(JS_TRUE);
 }
 
 static JSBool
-js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+js_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
-	char*		cp="";
-	JSString*	str;
+	char*		cp;
+	char*		buf;
+	ulong		len=512;
 	private_t*	p;
 
-	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+	*rval = JSVAL_NULL;
 
 	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
 		return(JS_FALSE);
 
 	if(p->fp==NULL)
 		return(JS_TRUE);
+	
+	if(argc)
+		len = JSVAL_TO_INT(argv[0]);
 
-	if(argc) {
-		if((str = JS_ValueToString(cx, argv[0]))==NULL)
-			return(JS_FALSE);
-		cp = JS_GetStringBytes(str);
+	if((buf=malloc(len))==NULL)
+		return(JS_TRUE);
+
+	if(fgets(buf,len,p->fp)!=NULL) {
+		truncsp(buf);
+		if(p->etx) {
+			cp=strchr(buf,p->etx);
+			if(cp) *cp=0; 
+		}
+		*rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,buf));
 	}
 
-	if(fprintf(p->fp,"%s\n",cp)!=0)
-		*rval = BOOLEAN_TO_JSVAL(JS_TRUE);
-		
+	free(buf);
+
 	return(JS_TRUE);
 }
 
 static JSBool
-js_writeint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+js_readbin(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
 	BYTE		b;
 	WORD		w;
@@ -236,7 +263,7 @@ js_writeint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 	size_t		size=sizeof(DWORD);
 	private_t*	p;
 
-	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+	*rval = JSVAL_NULL;
 
 	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
 		return(JS_FALSE);
@@ -247,79 +274,126 @@ js_writeint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 	if(argc>1) 
 		size = JSVAL_TO_INT(argv[1]);
 
-	*rval = BOOLEAN_TO_JSVAL(JS_TRUE);
-
 	switch(size) {
 		case sizeof(BYTE):
-			b = (BYTE)JSVAL_TO_INT(argv[0]);
-			fwrite(&b,1,size,p->fp);
+			fread(&b,1,size,p->fp);
+			*rval = INT_TO_JSVAL(b);
 			break;
 		case sizeof(WORD):
-			w = (SHORT)JSVAL_TO_INT(argv[0]);
-			fwrite(&w,1,size,p->fp);
+			fread(&w,1,size,p->fp);
+			*rval = INT_TO_JSVAL(w);
 			break;
 		case sizeof(DWORD):
-			l = JSVAL_TO_INT(argv[0]);
-			fwrite(&l,1,size,p->fp);
-			break;
-		default:	/* unknown size */
-			*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+			fread(&l,1,size,p->fp);
+			*rval = INT_TO_JSVAL(l);
 			break;
 	}
 		
 	return(JS_TRUE);
 }
 
-
 static JSBool
-js_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+js_readall(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
-	char		buf[513];
-	int			len;
-	JSString*	str;
+    jsint       len=0;
+    jsval       line;
+    JSObject*	array;
 	private_t*	p;
 
+	*rval = JSVAL_NULL;
+
 	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
 		return(JS_FALSE);
 
-	if(argc)
-		len = JSVAL_TO_INT(argv[0]);
-	else
-		len = sizeof(buf)-1;
+	if(p->fp==NULL)
+		return(JS_TRUE);
 
-	len = len > sizeof(buf)-1 ? sizeof(buf)-1 : len;
+    array = JS_NewArrayObject(cx, 0, NULL);
 
-	len = fread(buf,1,len,p->fp);
-	if(len<0) {
-		len=0;
+    while(!feof(p->fp)) {
+		js_readln(cx, obj, 0, NULL, &line); 
+		if(line==JSVAL_NULL)
+			break;
+        JS_SetElement(cx, array, len++, &line);
 	}
-	buf[len]=0;
+    *rval = OBJECT_TO_JSVAL(array);
 
-	str = JS_NewStringCopyZ(cx, buf);
+    return(JS_TRUE);
+}
 
-	*rval = STRING_TO_JSVAL(str);
+static JSBool
+js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char*		cp;
+	size_t		len;
+	JSString*	str;
+	private_t*	p;
 
-	dbprintf(FALSE, p, "read %u bytes",len);
+	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+
+	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
+		return(JS_FALSE);
+
+	if(p->fp==NULL)
+		return(JS_TRUE);
+
+	if(!argc)
+		return(JS_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	cp = JS_GetStringBytes(str);
+	len = strlen(cp);
+/*  need etx support
+	if(argc>1)
+		len=JSVAL_TO_INT(argv[1]);
+*/
+	if(fwrite(cp,1,len,p->fp)==len) {
+		dbprintf(FALSE, p, "wrote %u bytes",len);
+		*rval = BOOLEAN_TO_JSVAL(JS_TRUE);
+	} else 
+		dbprintf(TRUE, p, "write of %u bytes failed",len);
 		
 	return(JS_TRUE);
 }
 
 static JSBool
-js_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
+	char*		cp="";
+	JSString*	str;
+	private_t*	p;
+
+	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+
+	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
+		return(JS_FALSE);
+
+	if(p->fp==NULL)
+		return(JS_TRUE);
+
+	if(argc) {
+		if((str = JS_ValueToString(cx, argv[0]))==NULL)
+			return(JS_FALSE);
+		cp = JS_GetStringBytes(str);
+	}
+
+	if(fprintf(p->fp,"%s\n",cp)!=0)
+		*rval = BOOLEAN_TO_JSVAL(JS_TRUE);
+
 	return(JS_TRUE);
 }
-	
+
 static JSBool
-js_readint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+js_writebin(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
 	BYTE		b;
 	WORD		w;
 	DWORD		l;
+	size_t		wr=0;
 	size_t		size=sizeof(DWORD);
 	private_t*	p;
 
-	*rval = JSVAL_VOID;
+	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
 
 	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
 		return(JS_FALSE);
@@ -332,22 +406,65 @@ js_readint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 
 	switch(size) {
 		case sizeof(BYTE):
-			fread(&b,1,size,p->fp);
-			*rval = INT_TO_JSVAL(b);
+			b = (BYTE)JSVAL_TO_INT(argv[0]);
+			wr=fwrite(&b,1,size,p->fp);
 			break;
 		case sizeof(WORD):
-			fread(&w,1,size,p->fp);
-			*rval = INT_TO_JSVAL(w);
+			w = (SHORT)JSVAL_TO_INT(argv[0]);
+			wr=fwrite(&w,1,size,p->fp);
 			break;
 		case sizeof(DWORD):
-			fread(&l,1,size,p->fp);
-			*rval = INT_TO_JSVAL(l);
+			l = JSVAL_TO_INT(argv[0]);
+			wr=fwrite(&l,1,size,p->fp);
+			break;
+		default:	
+			/* unknown size */
+			dbprintf(TRUE, p, "unsupported binary write size: %d",size);
 			break;
 	}
+	if(wr==size)
+		*rval = BOOLEAN_TO_JSVAL(JS_TRUE);
 		
 	return(JS_TRUE);
 }
 
+static JSBool
+js_writeall(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    jsuint      i;
+    jsuint      limit;
+    JSObject*	array;
+    JSObject*	elem;
+    jsval       elemval;
+	private_t*	p;
+
+	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+
+	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
+		return(JS_FALSE);
+
+	if(p->fp==NULL)
+		return(JS_TRUE);
+
+    if(!JS_IsArrayObject(cx, JSVAL_TO_OBJECT(argv[0])))
+		return(JS_TRUE);
+
+    array = JSVAL_TO_OBJECT(argv[0]);
+
+    JS_GetArrayLength(cx, array, &limit);
+
+    for(i=0;i<limit;i++) {
+        if(!JS_GetElement(cx, array, i, &elemval))
+			break;
+        elem = JSVAL_TO_OBJECT(elemval);
+        js_writeln(cx, obj, 1, &elemval, rval);
+    }
+
+    *rval = BOOLEAN_TO_JSVAL(JSVAL_TRUE);
+
+    return(JS_TRUE);
+}
+
 static JSBool
 js_lock(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
@@ -442,7 +559,8 @@ js_clear_error(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rva
 /* File Object Properites */
 enum {
 	 FILE_PROP_NAME		
-	,FILE_PROP_MODE		
+	,FILE_PROP_MODE
+	,FILE_PROP_ETX
 	,FILE_PROP_EXISTS	
 	,FILE_PROP_DATE		
 	,FILE_PROP_IS_OPEN	
@@ -482,6 +600,9 @@ static JSBool js_file_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 		case FILE_PROP_ATTRIBUTES:
 			CHMOD(p->name,JSVAL_TO_INT(*vp));
 			break;
+		case FILE_PROP_ETX:
+			p->etx = (uchar)JSVAL_TO_INT(*vp);
+			break;
 	}
 
 	return(JS_TRUE);
@@ -544,6 +665,9 @@ static JSBool js_file_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 		case FILE_PROP_DESCRIPTOR:
 			*vp = INT_TO_JSVAL(fileno(p->fp));
 			break;
+		case FILE_PROP_ETX:
+			*vp = INT_TO_JSVAL(p->etx);
+			break;
 	}
 
 	return(TRUE);
@@ -588,15 +712,18 @@ static JSFunctionSpec js_file_functions[] = {
 	{"delete",			js_delete,			0},		/* delete the file */
 	{"remove",			js_delete,			0},		/* delete the file */
 	{"clear_error",		js_clear_error,		0},		/* clear error */
+	{"clearError",		js_clear_error,		0},		/* clear error */
 	{"flush",			js_flush,			0},		/* flush buffers */
 	{"lock",			js_lock,			2},		/* lock offset, length */
 	{"unlock",			js_unlock,			2},		/* unlock offset, length */
 	{"read",			js_read,			0},		/* read a string */
 	{"readln",			js_readln,			0},		/* read a \n terminated string	*/
-	{"readint",			js_readint,			0},		/* read an integer (length) */
+	{"readBin",			js_readbin,			0},		/* read an integer (length) */
+	{"readAll",			js_readall,			0},		/* read all lines into an array */
 	{"write",			js_write,			1},		/* write a string */
 	{"writeln",			js_writeln,			0},		/* write a \n terminated string */
-	{"writeint",		js_writeint,		1},		/* read an integer (length) */
+	{"writeBin",		js_writebin,		1},		/* read an integer (length) */
+	{"writeAll",		js_writeall,		0},		/* write array of strings to file */
 	{0}
 };