diff --git a/exec/avatars.js b/exec/avatars.js
index eb7bd29d7d943af6d01bef5708b747c29562a7f3..a09f591ca543f0927cdb024b30f6758c29bb8680 100644
--- a/exec/avatars.js
+++ b/exec/avatars.js
@@ -4,10 +4,12 @@ var REVISION = "$Revision$".split(' ')[1];
 load('sbbsdefs.js');
 load("lz-string.js");
 var lib = load({}, 'avatar_lib.js');
+var SAUCE = load({}, 'sauce_lib.js');
 
-const to_name = 'SBBS Avatars';
+const user_avatars = 'SBBS User Avatars';
+const shared_avatars = 'SBBS Shared Avatars';
 
-function parse_msg(text)
+function parse_user_msg(text)
 {
 	var i;
     var json_begin;
@@ -41,13 +43,115 @@ function parse_msg(text)
 	return false;
 }
 
+function parse_file_msg(text)
+{
+	var i;
+    var bin_begin;
+    var bin_end;
+
+	// Terminate at tear-line
+    text=text.split("\r\n");
+    for(i=0; i<text.length; i++) {
+        if(text[i]=="---" || text[i].substring(0,4)=="--- ")
+            break;
+    }
+    text.length=i;
+
+	// Parse JSON block
+    for(i=0; i<text.length; i++) {
+        if(text[i].toLowerCase()=="bin-lz-begin")
+            bin_begin=i+1;
+        else if(text[i].toLowerCase()=="bin-lz-end")
+            bin_end=i;
+    }
+    if(bin_begin && bin_end > bin_begin) {
+        text = text.splice(bin_begin, bin_end-bin_begin);
+		return LZString.decompressFromBase64(text.join('').replace(/\s+/g, ''));
+    }
+	return false;
+}
+
+
+function find_name(objs, name)
+{
+	for(var i=0; i < objs.length; i++)
+		if(objs[i].name.toLowerCase() == name.toLowerCase())
+			return i;
+	return -1;
+}
+
+function import_netuser_list(hdr, list)
+{
+	var objs = [];
+	var file = new File(this.netuser_fname(hdr.from_netaddr));
+	if(file.open("r")) {
+		objs = file.iniGetAllObjects();
+		file.close();
+	}
+	for(var i in list) {
+		for(var u in list[i]) {
+			var index = find_name(objs, list[i][u]);
+			if(index >= 0) {
+				if(objs[index].data != i) {
+					objs[index].data = i;
+					objs[index].updated = new Date();
+				}
+			} else
+				objs.push({ name: list[i][u], data: i, created: new Date() });
+		}
+	}
+	if(!file.open("w"))
+		return false;
+	file.writeln(format(";from %s at %s (%s) on %s"
+		, hdr.from, hdr.subj, hdr.from_net_addr, new Date().toISOString()));
+	var result = file.iniSetAllObjects(objs);
+	file.close();
+	return result;
+}
+
+function import_shared_file(hdr, body)
+{
+	var data = parse_file_msg(body);
+	if(!data)
+		return false;
+
+	var filename = format("%s.%s", hdr.msg_net_addr, file_getname(hdr.subject));
+	if(file_getext(filename).toLowerCase() != '.bin')
+		filename += '.bin';
+
+	var file = new File(format("%sqnet/%s", system.data_dir, filename));
+	if(!file.open("wb")) {
+		alert("ERROR " + file.error + " opening " + file.name);
+		return false;
+	}
+	file.write(data);
+	file.close();
+	print(file.name + " created successfully");
+	var sauce = SAUCE.read(file.name);
+	if(!sauce) {
+		alert(file.name + " has no SAUCE!");
+		return false;
+	}
+	if(sauce.datatype != SAUCE.defs.datatype.bin
+		|| sauce.cols != lib.defs.width) {
+		alert(format("%s has invalid SAUCE! (%u %u)"
+			,file.name, sauce.datatype, sauce.cols));
+		return false;
+	}
+	var new_path = format("%savatars/%s", system.text_dir, filename);
+	var result = file_copy(file.name, new_path);
+	if(!result)
+		alert("ERROR copying " + file.name + " to " + new_path);
+	return result;
+}
 
 function import_from_msgbase(msgbase, import_ptr, limit, all)
 {
     var i;
     var count=0;
     var highest;
-    var avatars_crc=crc16_calc(to_name.toLowerCase());
+    var users_crc=crc16_calc(user_avatars.toLowerCase());
+	var shared_crc=crc16_calc(shared_avatars.toLowerCase());
 
     var ini = new File(msgbase.file + ".ini");
 	if(import_ptr == undefined) {
@@ -71,26 +175,29 @@ function import_from_msgbase(msgbase, import_ptr, limit, all)
         }
         if(idx.number <= import_ptr)
             continue;
-        if(idx.to != avatars_crc) {
+        if(idx.to != users_crc && idx.to != shared_crc) {
             continue;
         }
         if(idx.number > highest)
             highest = idx.number;
         var hdr = msgbase.get_msg_header(/* by_offset: */true, i);
-		if(hdr.to.toLowerCase() != to_name.toLowerCase())
-			continue;
-        var l;
-        var msg_from = hdr.from;
 		if(all != true && !hdr.from_net_type)	// Skip locally posted messages
 			continue;
-
-        var body = msgbase.get_msg_body(/* by_offset: */true, i
-            ,/* strip Ctrl-A */true, /* rfc822-encoded: */false, /* include tails: */false);
-		var avatars = parse_msg(body);
-		if(!avatars)
-			continue;
-		list = decompress_list(avatars);
-		import_list(hdr.from_net_addr, avatars);
+		printf("Importing msg #%u from %s: ", hdr.number, hdr.from);
+		var success = false;
+		var body = msgbase.get_msg_body(/* by_offset: */true, i
+			,/* strip Ctrl-A */true, /* rfc822-encoded: */false, /* include tails: */false);
+		if(hdr.to.toLowerCase() == user_avatars.toLowerCase()) {
+			var l;
+			var avatars = parse_user_msg(body);
+			if(!avatars)
+				continue;
+			success = import_netuser_list(hdr, decompress_list(avatars));
+		} else {
+			// Shared avatars
+			success = import_shared_file(hdr, body);
+		}
+		printf("%s\r\n", success ? "success" : "FAILED");
         count++;
 		if(limit && count >= limit)
 			break;
@@ -113,6 +220,55 @@ function decompress_list(list)
 	return new_list;
 }
 
+function export_users(msgbase, realnames)
+{
+	var last_user = system.lastuser;
+	var list = {};
+	
+	for(var n = 1; n <= last_user && !js.terminated; n++) {
+		if(!file_exists(lib.localuser_fname(n)))
+			continue;
+		if(!system.username(n))
+			continue;
+		var u = new User(n);
+		if(u.settings&USER_DELETED)
+			continue;
+		var avatar = lib.read_localuser(n);
+		if(!avatar)
+			continue;
+		var data = LZString.compressToBase64(base64_decode(avatar.data));
+		if(!list[data])
+			list[data] = [];
+		list[data].push(u.alias);
+		if(realnames)
+			list[data].push(u.name);
+	}
+	for(var i in list)
+		list[i].sort();
+	var body = "json-begin\r\n";
+	body += JSON.stringify(list, null, 1) + "\r\n";
+	body += "json-end\r\n";
+	body += "--- " + js.exec_file + " " + REVISION + "\r\n";
+	return msgbase.save_msg({ to:user_avatars, from:system.operator, subject:system.name }, body);
+}
+
+function export_file(msgbase, filename)
+{
+	var file = new File(filename);
+	if(!file.open("rb")) {
+		alert("Error " + file.error + " opening " + filename);
+		return false;
+	}
+	var data = file.read();
+	file.close();
+	data = LZString.compressToBase64(data);
+	var body = "bin-lz-begin\r\n";
+	body += data.match(/([\x00-\xff]{1,72})/g).join("\r\n");
+	body += "\r\nbin-lz-end\r\n";
+	body += "--- " + js.exec_file + " " + REVISION + "\r\n";
+	return msgbase.save_msg({ to:shared_avatars, from:system.operator, subject:file_getname(filename) }, body);
+}
+
 function main()
 {
 	var optval={};
@@ -123,6 +279,8 @@ function main()
 	var realnames = false;
 	var ptr;
 	var limit;
+	var all;
+	var users = false;
 
     for(i in argv) {
 		var arg = argv[i];
@@ -142,12 +300,18 @@ function main()
 			case '-offset':
 				offset = val;
 				break;
+			case '-users':
+				users = true;
+				break;
 			case '-realnames':
 				realnames = true;
 				break;
 			case "-ptr":
 				ptr = val;
 				break;
+			case "-all":
+				all = true;
+				break;
 			default:
 				if(parseInt(arg) < 0)
 					limit = -parseInt(arg);
@@ -173,23 +337,9 @@ function main()
 					alert("Error " + msgbase.error + " opening msgbase: " + msgbase.file);
 					exit(-1);
 				}
-				import_from_msgbase(msgbase, ptr, limit);
+				import_from_msgbase(msgbase, ptr, limit, all);
 				msgbase.close();
 				break;
-			case "import_msg":
-				var file = new File(filename);
-				if(!file.open("r")) {
-					alert("failure opening " + file.name);
-					return 1;
-				}
-				var msg = file.read();
-//				print(msg);
-				var list = parse_msg(lfexpand(msg));
-				print(JSON.stringify(list, null, 1));
-				list = decompress_list(list);
-				var success = lib.import_list(optval[cmd], list);
-				printf("%s\r\n", success ? "Successful" : "FAILED!");
-				break;
 			case "export":
 				var msgbase = new MsgBase(optval[cmd]);
 				print("Opening msgbase " + msgbase.file);
@@ -197,43 +347,37 @@ function main()
 					alert("Error " + msgbase.error + " opening msgbase: " + msgbase.file);
 					exit(-1);
 				}
-				var last_user = system.lastuser;
-				var list = {};
-				var count = 0;
-				for(var n = 1; n <= last_user && !js.terminated; n++) {
-					if(!file_exists(lib.local_fname(n)))
-						continue;
-					if(!system.username(n))
-						continue;
-					var u = new User(n);
-					if(u.settings&USER_DELETED)
-						continue;
-					var avatar = lib.read_local(n);
-					if(!avatar)
-						continue;
-					var data = LZString.compressToBase64(base64_decode(avatar.data));
-					if(!list[data])
-						list[data] = [];
-					list[data].push(u.alias);
-					if(realnames)
-						list[data].push(u.name);
-					count++;
-					if(limit && count > limit)
-						break;
+				var success = true;
+				if(users) {
+					printf("Exporting user avatars\n");
+					success = export_users(msgbase, realnames);
+				}
+				if(success && filename) {
+					printf("Exporting avatar file: %s\n", filename);
+					success = export_file(msgbase, filename);
 				}
-				for(var i in list)
-					list[i].sort();
-				var body = "json-begin\r\n";
-				body += JSON.stringify(list, null, 1) + "\r\n";
-				body += "json-end\r\n";
-				body += "--- " + js.exec_file + " " + REVISION + "\r\n";
-			    success = msgbase.save_msg({ to:to_name, from:system.operator, subject:system.name + ' Avatars' }, body);
 				printf("%s\r\n", success ? "Successful" : "FAILED: " + msgbase.last_error);
 				break;
 			case "dump":
-				var obj = lib.read_local(optval[cmd]);
+				var usernum = optval[cmd];
+				if(!usernum)
+					usernum = user.number;
+				var obj = lib.read_localuser(usernum);
 				print(JSON.stringify(obj));
 				break;
+			case "draw":	// Uses Graphic.draw()
+				var usernum = optval[cmd];
+				if(!usernum)
+					usernum = user.number;
+				console.clear();
+				var obj = lib.draw(usernum);
+				break;
+			case "show":	// Uses console.write()
+				var usernum = optval[cmd];
+				if(!usernum)
+					usernum = user.number;
+				var obj = lib.show(usernum);
+				break;
 		}
 	}
 }
diff --git a/exec/load/avatar_lib.js b/exec/load/avatar_lib.js
index ce777cfefd358b57b4f991597ceb22bfb2373981..24f94448c8d17ea0d8682fae08aeb0ca25db4b2a 100644
--- a/exec/load/avatar_lib.js
+++ b/exec/load/avatar_lib.js
@@ -7,19 +7,24 @@ const defs = {
 	height: 6,
 };
 
-function local_fname(usernum)
+function local_library()
+{
+	return format("%savatars/", system.text_dir);
+}
+
+function localuser_fname(usernum)
 {
 	return format("%suser/%04u.ini", system.data_dir, usernum);
 }
 
-function qnet_fname(netaddr)
+function netuser_fname(netaddr)
 {
 	return format("%sqnet/%s.avatars.ini", system.data_dir, file_getname(netaddr));
 }
 
-function write_local(usernum, obj)
+function write_localuser(usernum, obj)
 {
-	var file = new File(this.local_fname(usernum));
+	var file = new File(this.localuser_fname(usernum));
 	if(!file.open(file.exists ? 'r+':'w+')) {
 		return false;
 	}
@@ -28,21 +33,20 @@ function write_local(usernum, obj)
 	return result;
 }
 
-function write_qnet(username, netaddr, obj)
+function write_netuser(username, netaddr, obj)
 {
-	var file = new File(this.qnet_fname(netaddr));
+	var file = new File(this.netuser_fname(netaddr));
 	if(!file.open(file.exists ? 'r+':'w+')) {
 		return false;
 	}
 	var result = file.iniSetObject(username, obj);
 	file.close();
 	return result;
-
 }
 
-function read_local(usernum)
+function read_localuser(usernum)
 {
-	var file = new File(this.local_fname(usernum));
+	var file = new File(this.localuser_fname(usernum));
 	if(!file.open("r")) {
 		return false;
 	}
@@ -51,9 +55,9 @@ function read_local(usernum)
 	return obj;
 }
 
-function read_qnet(username, netaddr)
+function read_netuser(username, netaddr)
 {
-	var file = new File(this.qnet_fname(netaddr));
+	var file = new File(this.netuser_fname(netaddr));
 	if(!file.open("r")) {
 		return false;
 	}
@@ -65,19 +69,19 @@ function read_qnet(username, netaddr)
 function read(usernum, username, netaddr)
 {
 	if(parseInt(usernum) >= 1)
-		return read_local(usernum);
+		return read_localuser(usernum);
 	else
-		return read_qnet(username, netaddr);
+		return read_netuser(username, netaddr);
 }
 
-function update_local(usernum, data)
+function update_localuser(usernum, data)
 {
-	var obj = read_local(usernum);
+	var obj = read_localuser(usernum);
 	if(obj == false)
 		obj = { created: new Date() };
 	obj.data = data;
 	obj.updated = new Date();
-	return write_local(usernum, obj);
+	return write_localuser(usernum, obj);
 }
 
 function import_file(usernum, filename, offset)
@@ -86,67 +90,54 @@ function import_file(usernum, filename, offset)
 	var graphic = new Graphic(this.defs.width, this.defs.height);
 	if(!graphic.load(filename, offset))
 		return false;
-	return update_local(usernum, base64_encode(graphic.BIN));
-}
-
-function find_name(objs, name)
-{
-	for(var i=0; i < objs.length; i++)
-		if(objs[i].name.toLowerCase() == name.toLowerCase())
-			return i;
-	return -1;
+	return update_localuser(usernum, base64_encode(graphic.BIN));
 }
 
-function import_list(netaddr, list)
+// Uses Graphic.draw() at an absolute screen coordinate
+function draw(usernum, username, netaddr, above, right)
 {
-	var objs = [];
-	var file = new File(this.qnet_fname(netaddr));
-	if(file.open("r")) {
-		objs = file.iniGetAllObjects();
-		file.close();
-	}
-	for(var i in list) {
-		for(var u in list[i]) {
-			var index = find_name(objs, list[i][u]);
-			if(index >= 0) {
-				if(objs[index].data != i) {
-					objs[index].data = i;
-					objs[index].updated = new Date();
-				}
-			} else
-				objs.push({ name: list[i][u], data: i, created: new Date() });
-		}
-	}
-	if(!file.open("w"))
+	var avatar = this.read(usernum, username, netaddr);
+	if(!avatar)
 		return false;
-	var result = file.iniSetAllObjects(objs);
-	file.close();
-	return result;
+	load('graphic.js');
+	var graphic = new Graphic(this.defs.width, this.defs.height);
+	try {
+		graphic.BIN = base64_decode([avatar.data]);
+		var lncntr = console.line_counter;
+		var pos = console.getxy();
+		var x = pos.x;
+		var y = pos.y;
+		if(above)
+			y -= this.defs.height;
+		if(right)
+			x = console.screen_columns - (this.defs.width + 1);
+		graphic.attr_mask = ~graphic.defs.BLINK;	// Disable blink attribute (consider iCE colors?)
+		graphic.draw(x, y, this.defs.width, this.defs.height);
+		console.gotoxy(pos);
+		console.line_counter = lncntr;
+	} catch(e) {
+		return false;
+	};
+	return true;
 }
 
-function draw(usernum, username, netaddr, above, right)
+// Uses console.write() where-ever the cursor happens to be
+function show(usernum, username, netaddr)
 {
 	var avatar = this.read(usernum, username, netaddr);
-	if(avatar) {
-		load('graphic.js');
-		var graphic = new Graphic(this.defs.width, this.defs.height);
-		try {
-			graphic.BIN = base64_decode([avatar.data]);
-			var lncntr = console.line_counter;
-			var pos = console.getxy();
-			var x = pos.x;
-			var y = pos.y;
-			if(above)
-				y -= this.defs.height;
-			if(right)
-				x = console.screen_columns - (this.defs.width + 1);
-			graphic.attr_mask = ~graphic.defs.BLINK;	// Disable blink attribute (consider iCE colors?)
-			graphic.draw(x, y, this.defs.width, this.defs.height);
-			console.gotoxy(pos);
-			console.line_counter = lncntr;
-		} catch(e) {
-		};
-	}
+	if(!avatar)
+		return false;
+	load('graphic.js');
+	var graphic = new Graphic(this.defs.width, this.defs.height);
+	graphic.attr_mask = ~graphic.defs.BLINK;	// Disable blink attribute (consider iCE colors?)
+	try {
+		graphic.BIN = base64_decode([avatar.data]);
+		console.write(graphic.ANSI);
+	} catch(e) {
+		return false;
+	};
+	return true;
 }
 
+
 this;