diff --git a/xtrn/minesweeper/minesweeper.js b/xtrn/minesweeper/minesweeper.js
index c80cc60bed253646c54c00828c45fbe115660118..15be05c492b5410b579713cc4e89cb6f318c09f1 100644
--- a/xtrn/minesweeper/minesweeper.js
+++ b/xtrn/minesweeper/minesweeper.js
@@ -1,5 +1,3 @@
-// $Id: minesweeper.js,v 2.14 2020/08/04 05:11:26 rswindell Exp $
-
 // Minesweeper, the game
 
 // See readme.txt for instructions on installation, configuration, and use
@@ -8,7 +6,7 @@
 
 const title = "Synchronet Minesweeper";
 const ini_section = "minesweeper";
-const REVISION = "$Revision: 2.14 $".split(' ')[1];
+const REVISION = "$Revision: 2.15 $".split(' ')[1];
 const author = "Digital Man";
 const header_height = 4;
 const winners_list = js.exec_dir + "winners.jsonl";
@@ -32,6 +30,8 @@ const char_mine = '\x01r\x01h\xEB';
 const char_detonated_mine = '\x01r\x01h\x01i\*';
 const attr_count = "\x01c";
 const winner_subject = "Winner";
+const highscores_subject = "High Scores";
+const tear_line = "\r\n--- " + js.exec_file + " " + REVISION + "\r\n";
 const selectors = ["()", "[]", "<>", "{}", "--", "  "];
 
 require("sbbsdefs.js", "K_NONE");
@@ -61,13 +61,22 @@ if(!options.splash_delay)
 	options.splash_delay = 500;
 if(!options.sub)
     options.sub = load({}, "syncdata.js").find();
-
-var userprops = bbs.mods.userprops;
-if(!userprops)
-	userprops = load(bbs.mods.userprops = {}, "userprops.js");
-var json_lines = bbs.mods.json_lines;
-if(!json_lines)
-	json_lines = load(bbs.mods.json_lines = {}, "json_lines.js");
+if(js.global.bbs === undefined)
+	json_lines = load({}, "json_lines.js");
+else {
+	var userprops = bbs.mods.userprops;
+	if(!userprops)
+		userprops = load(bbs.mods.userprops = {}, "userprops.js");
+	var json_lines = bbs.mods.json_lines;
+	if(!json_lines)
+		json_lines = load(bbs.mods.json_lines = {}, "json_lines.js");
+	var selector = userprops.get(ini_section, "selector", options.selector);
+	var highlight = userprops.get(ini_section, "highlight", options.highlight);
+	var difficulty = userprops.get(ini_section, "difficulty", options.difficulty);
+	var ansiterm = bbs.mods.ansiterm_lib;
+	if(!ansiterm)
+		ansiterm = bbs.mods.ansiterm_lib = load({}, "ansiterm_lib.js");
+}
 var game = {};
 var board = [];
 var selected = {x:0, y:0};
@@ -77,17 +86,10 @@ var new_best = false;
 var win_rank = false;
 var view_details = false;
 var cell_width;	// either 3 or 2
-var selector = userprops.get(ini_section, "selector", options.selector);
-var highlight = userprops.get(ini_section, "highlight", options.highlight);
-var difficulty = userprops.get(ini_section, "difficulty", options.difficulty);
 var best = null;
 
 log(LOG_DEBUG, title + " options: " + JSON.stringify(options));
 
-var ansiterm = bbs.mods.ansiterm_lib;
-if(!ansiterm)
-	ansiterm = bbs.mods.ansiterm_lib = load({}, "ansiterm_lib.js");
-
 function mouse_enable(enable)
 {
 	const mouse_passthru = (CON_MOUSE_CLK_PASSTHRU | CON_MOUSE_REL_PASSTHRU);
@@ -183,7 +185,7 @@ function isgamewon()
 			game.md5 = md5_calc(JSON.stringify(game));
 			game.name = undefined;
 			var body = lfexpand(JSON.stringify(game, null, 1));
-			body += "\r\n--- " + js.exec_file + " " + REVISION + "\r\n";
+			body += tear_line;
 			if(!msgbase.save_msg(hdr, body))
 				alert("Error saving message to: " + options.sub);
 			msgbase.close();
@@ -266,6 +268,21 @@ function secondstr(t, frac)
 	return format("%2u:%02u", Math.floor(t/60), Math.floor(t%60));
 }
 
+function list_contains(list, obj)
+{
+	var match = false;
+	for(var i = 0; i < list.length && !match; i++) {
+		match = true;
+		for(var p in obj) {
+			if(list[i][p] != obj[p]) {
+				match = false;
+				break;
+			}
+		}
+	}
+	return match;
+}
+
 function get_winners(level)
 {
 	var list = json_lines.get(winners_list);
@@ -276,17 +293,21 @@ function get_winners(level)
 		var msgbase = new MsgBase(options.sub);
 		if(msgbase.get_index !== undefined && msgbase.open()) {
 			var to_crc = crc16_calc(title.toLowerCase());
-			var subj_crc = crc16_calc(winner_subject.toLowerCase());
+			var winner_crc = crc16_calc(winner_subject.toLowerCase());
+			var highscores_crc = crc16_calc(highscores_subject.toLowerCase());
 			var index = msgbase.get_index();
 			for(var i = 0; index && i < index.length; i++) {
 				var idx = index[i];
-				if((idx.attr&MSG_DELETE)
-					|| idx.to != to_crc || idx.subject != subj_crc)
+				if((idx.attr&MSG_DELETE) || idx.to != to_crc)
+					continue;
+				if(idx.subject != winner_crc && idx.subject != highscores_crc)
 					continue;
 				var hdr = msgbase.get_msg_header(true, idx.offset);
 				if(!hdr)
 					continue;
-				if(!hdr.from_net_type || hdr.to != title || hdr.subject != winner_subject)
+				if(!hdr.from_net_type || hdr.to != title)
+					continue;
+				if(hdr.subject != winner_subject && hdr.subject != highscores_subject)
 					continue;
 				var body = msgbase.get_msg_body(hdr, false, false, false);
 				if(!body)
@@ -302,13 +323,25 @@ function get_winners(level)
 				}
 				if(!obj.md5)	// Ignore old test messages
 					continue;
+				if(idx.subject == highscores_crc && !obj.game)
+					continue;
 				obj.name = hdr.from;
 				var md5 = obj.md5;
 				obj.md5 = undefined;
-				var calced = md5_calc(JSON.stringify(obj));
+				var calced = md5_calc(JSON.stringify(idx.subject == winner_crc ? obj : obj.game));
 				if(calced == md5) {
-					obj.net_addr = hdr.from_net_addr;	// Not included in MD5 sum
-					list.push(obj);
+					if(idx.subject == winner_crc) {
+						obj.net_addr = hdr.from_net_addr;	// Not included in MD5 sum
+						if(!list_contains(obj))
+							list.push(obj);
+					} else {
+						for(var j = 0; j < obj.game.length; j++) {
+							var game = obj.game[j];
+							game.net_addr = hdr.from_net_addr;
+							if(!list_contains(game))
+								list.push(game);
+						}
+					}
 				} else {
 					log(LOG_INFO, title +
 						" MD5 not " + calced +
@@ -1213,12 +1246,13 @@ try {
 		if(!isNaN(numval))
 			break;
 	}
-	
-	if(argv.indexOf("nocls") < 0)
-		js.on_exit("console.clear()");
-	
-	js.on_exit("console.attributes = LIGHTGRAY");
 
+	if(js.global.console) {
+		if(argv.indexOf("nocls") < 0)
+			js.on_exit("console.clear()");
+
+		js.on_exit("console.attributes = LIGHTGRAY");
+	}
 	if(argv.indexOf("winners") >= 0) {
 		if(!isNaN(numval) && numval > 0)
 			options.winners = numval;
@@ -1226,11 +1260,46 @@ try {
 		exit();
 	}
 
-	js.on_exit("console.line_counter = 0");
-	js.on_exit("console.status = " + console.status);
-	js.on_exit("console.ctrlkey_passthru = " + console.ctrlkey_passthru);
-	console.ctrlkey_passthru = "KOPTUZ";
+	if(argv.indexOf("export") >= 0) {
+		if(!options.sub) {
+			alert("Sub-board not defined");
+			exit(1);
+		}
+		var count = 20;
+		if(!isNaN(numval) && numval > 0)
+			count = numval;
+		var list = json_lines.get(winners_list);
+		if(typeof list != 'object') {
+			alert("No winners yet: " + list);
+			exit(0);
+		}
+		list.sort(compare_won_game);
+		var obj = { date: Date(), game: [] };
+		for(var i = 0; i < list.length && i < count; i++)
+			obj.game.push(list[i]);
+		obj.md5 = md5_calc(JSON.stringify(obj.game));
+		var msgbase = new MsgBase(options.sub);
+		var hdr = {
+			to: title,
+			from: system.operator,
+			subject: highscores_subject
+		};
+		var body = lfexpand(JSON.stringify(obj, null, 1));
+		body += tear_line;
+		if(!msgbase.save_msg(hdr, body)) {
+			alert("Error saving message to: " + options.sub);
+			exit(2);
+		}
+		msgbase.close();
+		exit(0);
+	}
 
+	if(js.global.console) {
+		js.on_exit("console.line_counter = 0");
+		js.on_exit("console.status = " + console.status);
+		js.on_exit("console.ctrlkey_passthru = " + console.ctrlkey_passthru);
+		console.ctrlkey_passthru = "KOPTUZ";
+	}
 	if(!isNaN(numval) && numval > 0 && numval < max_difficulty)
 		difficulty = numval;
 
@@ -1247,16 +1316,17 @@ try {
 	var msg = file_getname(e.fileName) + 
 		" line " + e.lineNumber + 
 		": " + e.message;
-	console.crlf();
+	if(js.global.console)
+		console.crlf();
 	alert(msg);
 	if(options.sub && user.alias != author) {
 		var msgbase = new MsgBase(options.sub);
 		var hdr = { 
 			to: author,
-			from: user.alias,
+			from: user.alias || system.operator,
 			subject: title
 		};
-		msg += "\r\n--- " + js.exec_file + " " + REVISION + "\r\n";		
+		msg += tear_line;
 		if(!msgbase.save_msg(hdr, msg))
 			alert("Error saving exception-message to: " + options.sub);
 		msgbase.close();