diff --git a/xtrn/doorscan/doorscan.js b/xtrn/doorscan/doorscan.js
index c2242c806d614d4672f44fbad8800da5af981624..f5962f13adb747dd840fd98c095e7a2f3fc4484b 100644
--- a/xtrn/doorscan/doorscan.js
+++ b/xtrn/doorscan/doorscan.js
@@ -1,4 +1,5 @@
 load("sbbsdefs.js");
+load("text.js");
 load("lockfile.js");
 
 var doorscan_dir='.';
@@ -20,7 +21,7 @@ function LockedOpen(filename, fmode)
 		wr=true;
 	}
 
-	/* TODO: Possible race here in mode */
+	/* TODO: Possible race condition here in mode */
 	var m=fmode.match(/[rwa]/);
 	if(m==null)
 		throw("Unknown file mode "+fmode);
@@ -53,6 +54,7 @@ function Display()
 	this.ANSI=Display_ANSI;
 	this.ASCII=Display_ASCII;
 	this.ASC=Display_ASC;
+	this.LORD=Display_LORD;
 }
 
 function Display_ANSI(filename)
@@ -83,6 +85,129 @@ function Display_ASC(filename)
 	return(true);
 }
 
+function Display_LORD(filename)
+{
+	var f=LockedOpen(filename, "rb");
+	var txt;
+	var out='';
+
+	while((txt=f.read())!=undefined) {
+		txt=f.read();
+		if(txt==undefined || txt=='')
+			break;
+		for(var i=0; i<txt.length; i++) {
+			var ch=charAt(i);
+			if(ch=='`') {
+				if(out.length)
+					console.write(out);
+				out='';
+				ch=txt.charAt(++i);
+				switch(ch) {
+					case '*':
+						console.attributes=BLINK|BLACK|BG_RED;
+						break;
+					case '1':
+						console.attributes=BLUE;
+						break;
+					case '2':
+						console.attributes=GREEN;
+						break;
+					case '3':
+						console.attributes=CYAN;
+						break;
+					case '4':
+						console.attributes=RED;
+						break;
+					case '5':
+						console.attributes=MAGENTA;
+						break;
+					case '6':
+						console.attributes=BROWN;
+						break;
+					case '7':
+						console.attributes=LIGHTGRAY;
+						break;
+					case '8':
+						console.attributes=DARKGRAY;
+						break;
+					case '9':
+						console.attributes=LIGHTBLUE;
+						break;
+					case '0':
+						console.attributes=LIGHTGREEN;
+						break;
+					case '!':
+						console.attributes=LIGHTCYAN;
+						break;
+					case '@':
+						console.attributes=LIGHTRED;
+						break;
+					case '#':
+						console.attributes=LIGHTMAGENTA;
+						break;
+					case '$':
+						console.attributes=YELLOW;
+						break;
+					case '%':
+						console.attributes=WHITE;
+						break;
+					case 'r':
+						ch=txt.charAt(++i);
+						switch(ch) {
+						case '0':
+							console.attributes=BG_BLACK;
+							break;
+						case '1':
+							console.attributes=BG_BLUE;
+							break;
+						case '2':
+							console.attributes=BG_GREEN;
+							break;
+						case '3':
+							console.attributes=BG_CYAN;
+							break;
+						case '4':
+							console.attributes=BG_RED;
+							break;
+						case '5':
+							console.attributes=BG_MAGENTA;
+							break;
+						case '6':
+							console.attributes=BG_BROWN;
+							break;
+						case '7':
+							console.attributes=BG_LIGHTGRAY;
+							break;
+						}
+						break;
+					case 'c':
+						console.clear();
+						console.crlf();
+						console.crlf();
+						break;
+					case 'l':
+						console.attributes=GREEN;
+						console.write('  -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-');
+						break;
+					case '`':
+						ch=console.getkey(K_NOCRLF);
+						break;
+					case 'b':
+					case ')':
+						console.attributes=BLINK|RED;
+						break;
+				}
+			}
+			else {
+				out += ch;
+			}
+		}
+	}
+	if(out.length)
+		console.write(out);
+	return(true);
+}
+
 /*
  * This is like Display, but for news files...
  * the idea is that this *could* display only news since the last time you
@@ -114,7 +239,7 @@ function DoorConfig(leaveopen)
 {
 	if(leaveopen==undefined)
 		leaveopen=false;
-	this.file=LockedOpen(doorscan_dir+"doors.ini", "r+");
+	this.file=LockedOpen(doorscan_dir+"doors.ini", "rb+");
 	this.save=DoorConfig_save;
 	this.door=new Object();
 	this.skipSection=new Object();
@@ -165,7 +290,7 @@ function DoorConfig_save(leaveopen)
 	if(leaveopen==undefined)
 		leaveopen=false;
 	if(!this.file.is_open)
-		this.file=LockedOpen(this.file.name, "r+");
+		this.file=LockedOpen(this.file.name, "rb+");
 
 	var sections=new Array();
 	for(var section in this.skipSection) {
@@ -220,12 +345,14 @@ function UserConfig(unum, leaveopen)
 	this.save=UserConfig_save;
 	this.addxtrn=UserConfig_addxtrn;
 	this.configure=UserConfig_configure;
+	this.configureSec=UserConfig_configure_sec;
+	this.configureDefaults=UserConfig_configure_defaults;
 
 	if(unum==undefined) {
-		this.file=LockedOpen(doorscan_dir+"defaults.ini", "r+");
+		this.file=LockedOpen(doorscan_dir+"defaults.ini", "rb+");
 	}
 	else {
-		this.file=LockedOpen(format("%suser/%04u.doorscan",system.data_dir,unum), "r+");
+		this.file=LockedOpen(format("%suser/%04u.doorscan",system.data_dir,unum), "rb+");
 		if(this.file.length==0) {
 			var defaults=new UserConfig();
 
@@ -266,7 +393,7 @@ function UserConfig(unum, leaveopen)
 function UserConfig_save()
 {
 	if(!this.file.is_open)
-		this.file=LockedOpen(this.file.name, "r+");
+		this.file=LockedOpen(this.file.name, "rb+");
 	var sections=new Array();
 
 	for(var door in this.door) {
@@ -312,10 +439,160 @@ function UserConfig_addxtrn(xtrn)
 	}
 }
 
-function UserConfig_configure()
+function UserConfig_configure(dcfg, sec)
 {
+	var door;
+	var index=new Array();
+	var n,s,r;
+	var xsec;
 	var dcfg=new DoorConfig();
 
+	while(1) {
+		for(sec in xtrn_area.sec) {
+			if(!user.compare_ars(xtrn_area.sec[sec]))
+				continue;
+			if(dcfg.skipSection[sec]!=undefined && dcfg.skipSection[sec])
+				continue;
+			index.push(sec);
+			console.uselect(index.length, "External Program Section", xtrn_area.sec[sec].name);
+		}
+		console.uselect(index.length, "External Program Section", "Global Settings");
+		if(index.length==0)
+			return;
+		xsec=console.uselect();
+		if(xsec < 1 || xsec > index.length) {
+			this.save();
+			return;
+		}
+		if(xsec < index.length)
+			this.configureSec(dcfg, index[xsec-1]);
+		else
+			this.configureDefaults();
+	}
+}
+
+function UserConfig_configure_defaults()
+{
+	while(1) {
+		console.uselect(1, "Global Setting", "Add all new externals to your scan           "+(this.global.addNew?"Yes":"No"));
+		console.uselect(2, "Global Setting", "Add externals to your scan when you run them "+(this.global.noAutoScan?"No":"Yes"));
+		console.uselect(3, "Global Setting", "Default to showing updated news entries      "+(this.global.defaultSkipNews?"No":"Yes"));
+		console.uselect(4, "Global Setting", "Default to showing updated scores entries    "+(this.global.defaultSkipScores?"No":"Yes"));
+		console.uselect(5, "Global Setting", "Default to showing run counts                "+(this.global.skipRunCount?"No":"Yes"));
+		switch(console.uselect()) {
+			case 1:
+				this.global.addNew=!this.global.addNew;
+				break;
+			case 2:
+				this.global.noAutoScan=!this.global.noAutoScan;
+				break;
+			case 3:
+				this.global.defaultSkipNews=!this.global.defaultSkipNews;
+				break;
+			case 4:
+				this.global.defaultSkipScores=!this.global.defaultSkipScores;
+				break;
+			case 5:
+				this.global.skipRunCount=!this.global.skipRunCount;
+				break;
+			default:
+				return;
+		}
+	}
+}
+
+function UserConfig_configure_sec(dcfg, sec)
+{
+	var door;
+	var index=new Array();
+	var n,s,r;
+	var xprog;
+
+	while(1) {
+		for(door in xtrn_area.prog) {
+			if(xtrn_area.prog[door].sec_code != sec)
+				continue;
+			if(dcfg.skipSection[xtrn_area.prog[door].sec_code]!=undefined && dcfg.skipSection[xtrn_area.prog[door].sec_code])
+				continue;
+			if(dcfg.door[door].skip != undefined && dcfg.door[door].skip)
+				continue;
+			index.push(door);
+			if(this.door[door]==undefined) {
+				n=ascii('-');
+				s=ascii('-');
+				r=ascii('-');
+			}
+			else {
+				n=s=r=ascii('Y');
+				if(dcfg.door[door].news==undefined)
+					n=ascii('-');
+				else {
+					if(this.door[door].skipNews)
+						n=ascii('N');
+				}
+				if(dcfg.door[door].score==undefined)
+					s=ascii('-');
+				else {
+					if(this.door[door].skipScores)
+						s=ascii('N');
+				}
+				if(this.door[door].skipRunCount)
+					r=ascii('N');
+			}
+			console.uselect(index.length,"External                                News  Scores  Run Count"
+					,format("%-40s   %c      %c         %c",xtrn_area.prog[door].name,n,s,r));
+		}
+		if(!index.length)
+			return;
+		xprog=console.uselect();
+		if(xprog < 1 || xprog > index.length)
+			return;
+		console.crlf();
+		if(this.door[index[xprog-1]]==undefined)
+			this.addxtrn(index[xprog-1]);
+		if(dcfg.door[index[xprog-1]].news!=undefined) {
+			if(this.door[index[xprog-1]].skipNews)
+				this.door[index[xprog-1]].skipNews=console.noyes("Show updated news files");
+			else
+				this.door[index[xprog-1]].skipNews=!console.yesno("Show updated news files");
+		}
+		if(dcfg.door[index[xprog-1]].score!=undefined) {
+			if(this.door[index[xprog-1]].skipScore)
+				this.door[index[xprog-1]].skipScore=console.noyes("Show updated score files");
+			else
+				this.door[index[xprog-1]].skipScore=!console.yesno("Show updated score files");
+
+		}
+		if(this.door[index[xprog-1]].skipRunCount)
+			this.door[index[xprog-1]].skipRunCount=console.noyes("Show run counts");
+		else
+			this.door[index[xprog-1]].skipRunCount=!console.yesno("Show run counts");
+		if(this.door[index[xprog-1]].skipRunCount
+				&& this.door[index[xprog-1]].skipScore
+				&& this.door[index[xprog-1]].skipNews)
+			delete this.door[index[xprog-1]];
+	}
+
+	// External:                        News | Scores | Run Count
+	// 1) Kannons and Katapults          Y   |   Y    |  Y
+	// 2) TradeWars v2                   Y   |   Y    |  Y
+	//
+	// Select External: 1
+	//
+	// Enter the first letter of the items to SKIP or 'D' to delete from the scan: N
+	//
+	// External:                        News | Scores | Run Count
+	// 1) Kannons and Katapults          N   |   Y    |  Y
+	// 2) TradeWars v2                   Y   |   Y    |  Y
+	//
+	// Select External: 2
+	//
+	// Enter the first letter of the items to SKIP or 'D' to delete from the scan: D
+	//
+	// External:                        News | Scores | Run Count
+	// 1) Kannons and Katapults          N   |   Y    |  Y
+	// 2) TradeWars v2                   -   |   -    |  -
+	//
 	/*
 	 * User settings
 	 * Per Door: 
@@ -332,12 +609,136 @@ function UserConfig_configure()
 	 */
 
 	/*
-	 * Default scan config - new UserConfig(null)
+	 * Default scan config - new UserConfig(null).configure()
 	 */
 
 	// TODO: User configuration
 }
 
+function LogParser()
+{
+	var door;
+	this.door=new Object();
+	this.parsed=new Object();
+	this.parseLog=LogParser_parselog;
+	this.parseSince=LogParser_parsesince;
+	this.usersOfSince=LogParser_usersOfSince;
+
+	for(door in xtrn_area.prog) {
+		this.door[door]=new Object();
+		this.door[door].log=new Array();
+	}
+}
+
+function LogParser_parselog(filename)
+{
+	var f=new File(filename);
+	var uname=undefined;
+	var line;
+	var m;
+
+	if(!f.open("rb", true)) {
+		if(f.exists)
+			throw("Unable to open file "+filename);
+	}
+	if(this.parsed[filename]!=undefined)
+		return
+	while((line=f.readln())!=null) {
+		if(line=='') {
+			uname=undefined;
+		}
+		else if((m=line.match(/^\+\+ \([0-9]+\)  (.{25})  Logon [0-9]+ - [0-9]+$/))!=null) {
+			uname=m[1].replace(/^(.*?)\s*$/,"$1");
+		}
+		else if((m=line.match(/^   DOORSCAN - (.+?) starting @ (.*)$/))!=null) {
+			if(uname!=undefined) {
+				if(this.door[m[1]]!=undefined) {
+					this.door[m[1]].log.push({user:uname, date:(new Date(m[2]))});
+				}
+				else {
+					log("Door "+m[1]+" logged but not configured");
+				}
+			}
+			else {
+				log("User name not found for line "+line);
+			}
+		}
+	}
+	this.parsed[f.name]=true;
+}
+
+/*
+ * Inclusive
+ */
+function LogParser_parsesince(since)
+{
+	var fname;
+	var ldate=eval(since.toSource());
+	var now=new Date();
+	var m,d,y;
+	var nm,nd,ny;
+	var i;
+
+	nm=now.getMonth();
+	nd=now.getDate();
+	ny=now.getFullYear();
+
+	while(ldate <= now) {
+		m=ldate.getMonth();
+		d=ldate.getDate();
+		y=ldate.getFullYear();
+
+		/* Silly paranoia */
+		if(y < ny - 100) {
+			ldate=eval(now.toSource());
+			ldate.setFullYear(ldate.getFullYear()-99);
+			continue;
+		}
+		if(y == ny-100) {
+			if(m <= nm) {
+				m++;
+				if(m==12) {
+					ldate.setFullYear(y+1);
+					ldate.setMonth(0);
+				}
+				else {
+					ldate.setMonth(m);
+				}
+				continue;
+			}
+		}
+		fname=format("%slogs/%2.2d%2.2d%2.2d.log",system.data_dir,m+1,d,y%100);
+		this.parseLog(fname);
+		ldate.setDate(ldate.getDate()+1);
+	}
+
+	for(i in system.node_list) {
+		this.parseLog(system.node_list[i].dir);
+	}
+}
+
+function LogParser_usersOfSince(xtrn, since)
+{
+	this.parseSince(since);
+
+	var ret=new Object();
+	ret.user=new Object();
+	ret.total=0;
+	ret.users=0;
+
+	for(var i in this.door[xtrn].log) {
+		if(this.door[xtrn].log[i].date >= since) {
+			ret.total++;
+			if(ret.user[this.door[xtrn].log[i].user]==undefined) {
+				ret.user[this.door[xtrn].log[i].user]=0;
+				ret.users++;
+			}
+			ret.user[this.door[xtrn].log[i].user]++;
+		}
+	}
+	return(ret);
+}
+
 function sysop_config()
 {
 	/*
@@ -386,7 +787,7 @@ function runXtrn(xtrn)
 	ucfg.save();
 
 	if(!(dcfg.door[xtrn]!=undefined && dcfg.door[xtrn].skip != undefined && dcfg.door[xtrn].skip)
-			|| !(dcfg.skipSection[xtrn]!=undefined && dcfg.skipSection[xtrn])) {
+			|| !(dcfg.skipSection[xtrn_area.prog[xtrn].sec_code]!=undefined && dcfg.skipSection[xtrn_area.prog[xtrn].sec_code])) {
 		bbs.log_str("DOORSCAN - "+xtrn+" starting @ "+now.toString()+"\r\n");
 	}
 
@@ -396,7 +797,7 @@ function runXtrn(xtrn)
 	dcfg=new DoorConfig(true);
 
 	if(!(dcfg.door[xtrn]!=undefined && dcfg.door[xtrn].skip != undefined && dcfg.door[xtrn].skip)
-			&& !(dcfg.skipSection[xtrn]!=undefined && dcfg.skipSection[xtrn])) {
+			&& !(dcfg.skipSection[xtrn_area.prog[xtrn].sec_code]!=undefined && dcfg.skipSection[xtrn_area.prog[xtrn].sec_code])) {
 		bbs.log_str("DOORSCAN - "+xtrn+" ending @ "+now.toString()+"\r\n");
 	}
 
@@ -418,11 +819,13 @@ function doScan()
 	var ucfg=new UserConfig(user.number);
 	var dsp=new Display();
 	var door;
+	var scantime;
 	var tmp;
+	var logdetails=new LogParser();
 
 	/* First, look for new doors */
 	for(door in dcfg.door) {
-		if(dcfg.skipSection[door]!=undefined && dcfg.skipSection[door])
+		if(dcfg.skipSection[xtrn_area.prog[door].sec_code]!=undefined && dcfg.skipSection[xtrn_area.prog[door].sec_code])
 			continue;
 		if(dcfg.door[door].skip != undefined && dcfg.door[door].skip)
 			continue;
@@ -462,17 +865,17 @@ function doScan()
 	 * newer
 	 */
 	for(door in ucfg.door) {
-		if(dcfg.skipSection[door]!=undefined && dcfg.skipSection[door])
+		if(dcfg.skipSection[xtrn_area.prog[door].sec_code]!=undefined && dcfg.skipSection[xtrn_area.prog[door].sec_code])
 			continue;
 		if(dcfg.door[door].skip != undefined && dcfg.door[door].skip)
 			continue;
 		var lastPlayed=false;
-		tmp=ucfg.global.lastScan;
-		if(ucfg.door[door].lastExit != undefined && ucfg.door[door].lastExit > tmp) {
+		scantime=ucfg.global.lastScan;
+		if(ucfg.door[door].lastExit != undefined && ucfg.door[door].lastExit > scantime) {
 			lastPlayed=true;
-			tmp=ucfg.door[door].lastExit;
+			scantime=ucfg.door[door].lastExit;
 		}
-		if(dcfg.door[door].lastRan != undefined && dcfg.door[door].lastRan > tmp) {
+		if(dcfg.door[door].lastRan != undefined && dcfg.door[door].lastRan > scantime) {
 			/* Yes, this has been played... */
 
 			/* News File */
@@ -483,7 +886,7 @@ function doScan()
 					 * Some doors only update the news during maintenance
 					 */
 					
-					if(new Date(file_date(dcfg.door[door].news)*1000) >= tmp) {
+					if(new Date(file_date(dcfg.door[door].news)*1000) >= scantime) {
 						/* Assume ANSI */
 						if(dcfg.door[door].newsType==undefined)
 							dsp.ANSI(dcfg.door[door].news);
@@ -504,7 +907,7 @@ function doScan()
 					 * Some doors only update the Scores during maintenance
 					 */
 					
-					if(new Date(file_date(dcfg.door[door].scores)*1000) >= tmp) {
+					if(new Date(file_date(dcfg.door[door].scores)*1000) >= scantime) {
 						/* Assume ANSI */
 						if(dcfg.door[door].scoresType==undefined)
 							dsp.ANSI(dcfg.door[door].scores);
@@ -520,11 +923,33 @@ function doScan()
 
 			if(!ucfg.door[door].skipRunCount) {
 				if(ucfg.door[door].lastRunCount != undefined) {
+					var rc=logdetails.usersOfSince(door, scantime);
 					console.attributes=LIGHTCYAN;
-					console.writeln(xtrn_area.prog[door].name+" in the "+xtrn_area.sec[xtrn_area.prog[door].sec_code].name+" section has been ran "+(dcfg.door[door].runCount-ucfg.door[door].lastRunCount)+" times since you last "+(lastPlayed?"played":"scanned"));
+					console.writeln(xtrn_area.prog[door].name+" in the "+xtrn_area.sec[xtrn_area.prog[door].sec_code].name+" section has been ran "+rc.total+" times by "+rc.users+" users since you last "+(lastPlayed?"played":"scanned")+"\r\n");
+					tmp=false;
+					var str;
+					var str1;
+					for(var i in rc.user) {
+						var col=0;
+						str1=format("%s (%u)", i, rc.user[i]);
+						if(!tmp) {
+							str='';
+							tmp=true;
+						}
+						else {
+							str=', ';
+						}
+						if(col + str.length+str1.length > 77) {
+							str += '\r\n';
+							console.writeln(str);
+							str='';
+							col=0;
+						}
+						str += str1;
+						console.write(str);
+					}
 				}
 			}
-			// TODO: List how many users have played and possible who (needs more logging)
 		}
 	}
 	ucfg=new UserConfig(user.number, true);
@@ -548,6 +973,14 @@ for(i in argv) {
 			dcfg.save();
 			var ucfg=new UserConfig(user.number);
 			ucfg.save();
+			var lp=new LogParser();
+			var sd=new Date();
+			sd.setFullYear(sd.getFullYear()-1);
+			var us=lp.usersOfSince('knk',sd);
+			writeln("total="+us.total);
+			for(var i in us.user) {
+				writeln(i+"="+us.user[i]);
+			}
 			break;
 		case 'config':
 			new UserConfig(user.number).configure();