diff --git a/exec/load/frame.js b/exec/load/frame.js
index bb865f239c289278e83145cc8f848b3e554d4a5a..c834fe1f4f464565bc6562bb59bab52373ed81a5 100644
--- a/exec/load/frame.js
+++ b/exec/load/frame.js
@@ -1,1762 +1,1760 @@
-/* $Id: frame.js,v 1.91 2020/08/01 19:32:23 rswindell Exp $ */
-
-/**
- 	Javascript Frame Library
- 	for Synchronet v3.15a+
- 	by Matt Johnson (mcmlxxix)
-
-DESCRIPTION:
-
- 	this library is meant to be used in conjunction with other libraries that
- 	store display data in a Frame() object or objects
- 	this allows for "windows" that can be hidden, moved, closed, etc...
-	without destroying the data behind them.
-
- 	the object itself takes the following parameters:
-
- 		x: 			the coordinate representing the top left corner of the frame (horiz)
- 		y: 			the coordinate representing the top left corner of the frame (vert)
- 		width: 		the horizontal width of the frame
- 		height: 	the vertical height of the frame
- 		attr:		the default color attributes of the frame
-		parent:		a frame object representing the parent of the new frame
-
-METHODS:
-
-	frame.open()				//populate frame contents in character canvas
-	frame.close()				//remove frame contents from character canvas
-	frame.delete()				//delete this frame (remove from screen buffer, destroy internal references)
-	frame.invalidate()			//clear screen buffer to redraw contents on cycle() or draw()
-	frame.draw()				//open frame (if not open) move to top (if not on top) and cycle()
-	frame.cycle()				//check the display matrix for updated characters and displays them
-	frame.refresh()				//flag all frame sectors for potential update
-	frame.load(filename)		//load a binary graphic (.BIN) or ANSI graphic (.ANS) file
-	frame.bottom()				//push frame to bottom of display stack
-	frame.top()					//pull frame to top of display stack
-	frame.scroll(x,y)			//scroll frame n spaces in any direction
-	frame.scrollTo(x,y)			//scroll frame to absolute offset
-	frame.move(x,y)				//move frame n spaces in any direction
-	frame.moveTo(x,y)			//move frame to absolute position
-	frame.end()					//opposite of frame.home()
-	frame.screenShot(file,append)
-								//capture the contents of a frame to file
-	frame.getData(x,y,use_offset)
-								//return the character and attribute located at x,y in frame.data (optional scroll offset)
-	frame.setData(x,y,ch,attr,use_offset)
-								//modify the character and attribute located at x,y in frame.data (optional scroll offset)
-	frame.clearData(x,y,use_offset)
-								//delete the character and attribute located at x,y in frame.data (optional scroll offset)
-	frame.clearline(attr)		//see http://synchro.net/docs/jsobjs.html#console
-	frame.cleartoeol(attr)
-	frame.putmsg(str,attr)
-	frame.clear(attr)
-	frame.home()
-	frame.center(str,attr)
-	frame.crlf()
-	frame.getxy()
-	frame.gotoxy(x,y)
-	frame.pushxy()
-	frame.popxy()
-
-PROPERTIES:
-
-	frame.x						//x screen position
-	frame.y						//y screen position
-	frame.width					//frame width
-	frame.height				//frame height
-	frame.data					//frame data matrix
-	frame.data_height			//true height of frame contents (READ ONLY)
-	frame.data_width			//true width of frame contents (READ ONLY)
-	frame.attr					//default attributes for frame
-	frame.checkbounds			//toggle true/false to restrict/allow frame movement outside display
-	frame.lf_strict				//toggle true/false to force newline after a crlf-terminated string
-	frame.v_scroll				//toggle true/false to enable/disable vertical scrolling
-	frame.h_scroll				//toggle true/false to enable/disable horizontal scrolling
-	frame.scrollbars			//toggle true/false to show/hide scrollbars
-	frame.transparent			//toggle true/false to enable transparency mode
-								//(do not display frame sectors where char == undefined)
-	frame.offset				//current offset object {x,y}
-	frame.cursor				//current cursor object {x,y}
-	frame.parent				//the parent frame of a frame
-	frame.id					//a unique identifier (e.g. "0.1.1.2.3")
-	frame.is_open				//return true is frame is opened in screen buffer
-
-USAGE:
-
-	//create a new frame object at screen position 1,1. 80 characters wide by 24 tall
- 	load("frame.js");
- 	var frame = new Frame(1,1,80,24,BG_BLUE);
-
-	//add frame to the display canvas
-	frame.open();
-
-	//add a new frame within the frame object that will display on top at position 10,10
-	var subframe = new Frame(10,10,10,10,BG_GREEN,frame);
-
-	//add subframe to the display canvas
-	subframe.open();
-
-	//place cursor at position x:5 y:5 relative to subframe's coordinates
-	subframe.gotoxy(5,5);
-
-	//beware this sample infinite loop
- 	while(!js.terminated) {
-		//print a message into subframe
-		subframe.putmsg("1");
-
-		//on first call this will draw the entire initial frame,
-		//as triggered by the open() method call.
-		//on subsequent calls this will draw only areas that have changed
-		frame.cycle();
-		//NOTE: if frames are linked, only one frame needs to be cycled
-		//		for all frames to update
-	}
-
-	//close out the entire frame tree
-	frame.close();
-
- */
-
-load("sbbsdefs.js");
-function Frame(x,y,width,height,attr,parent) {
-
-	/* private properties */
-	this.__properties__ = {
-		x:undefined,
-		y:undefined,
-		width:undefined,
-		height:undefined,
-		attr:undefined,
-		display:undefined,
-		data:[],
-		open:false,
-		ctrl_a:false,
-		curr_attr:undefined,
-    attr_stack:[],
-		id:0
-	};
-	this.__settings__ = {
-		v_scroll:true,
-		h_scroll:false,
-		scrollbars:false,
-		lf_strict:true,
-		checkbounds:true,
-		transparent:false,
-		word_wrap:false
-	};
-	this.__relations__ = {
-		parent:undefined,
-		child:[]
-	};
-	this.__position__ = {
-		cursor:undefined,
-		offset:undefined,
-		stored:undefined
-	};
-
-	function init(x,y,width,height,attr,parent) {
-		if(parent instanceof Frame) {
-			this.__properties__.id = parent.display.nextID;
-			this.__properties__.display = parent.display;
-			this.__settings__.checkbounds = parent.checkbounds;
-			this.__relations__.parent = parent;
-			parent.child = this;
-		}
-		else {
-			this.__properties__.display = new Display(x,y,width,height);
-		}
-
-		this.x = x;
-		this.y = y;
-		this.width = width;
-		this.height = height;
-		this.attr = attr;
-
-		this.__position__.cursor = new Cursor(0,0,this);
-		this.__position__.offset = new Offset(0,0,this);
-		this.__position__.stored = new Cursor(0,0,this);
-
-		//log(LOG_DEBUG,format("new frame initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y));
-	}
-	init.apply(this,arguments);
-}
-
-/* protected properties */
-Frame.prototype.__defineGetter__("child", function() {
-	return this.__relations__.child;
-});
-Frame.prototype.__defineSetter__("child", function(frame) {
-	if(frame instanceof Frame)
-		this.__relations__.child.push(frame);
-	else
-		throw("child not an instance of Frame()");
-});
-Frame.prototype.__defineGetter__("attr", function() {
-	return this.__properties__.attr;
-});
-Frame.prototype.__defineSetter__("attr", function(attr) {
-	if(attr !== undefined && isNaN(attr))
-		throw("invalid attribute: " + attr);
-	this.__properties__.attr = attr;
-});
-Frame.prototype.__defineGetter__("x", function() {
-	if(this.__properties__.x == undefined)
-		return this.__properties__.display.x;
-	return this.__properties__.x;
-});
-Frame.prototype.__defineSetter__("x", function(x) {
-	if(x == undefined)
-		return;
-	if(!this.__checkX__(x))
-		throw("invalid x coordinate: " + x);
-	this.__properties__.x = Number(x);
-});
-Frame.prototype.__defineGetter__("y", function() {
-	if(this.__properties__.y == undefined)
-		return this.__properties__.display.y;
-	return this.__properties__.y;
-});
-Frame.prototype.__defineSetter__("y", function(y) {
-	if(y == undefined)
-		return;
-	if(!this.__checkY__(y))
-		throw("invalid y coordinate: " + y);
-	this.__properties__.y = Number(y);
-});
-Frame.prototype.__defineGetter__("width", function() {
-	if(this.__properties__.width == undefined)
-		return this.__properties__.display.width;
-	return this.__properties__.width;
-});
-Frame.prototype.__defineSetter__("width", function(width) {
-	if(width == undefined)
-		return;
-	if(!this.__checkWidth__(this.x,Number(width)))
-		throw("invalid width: " + width);
-	this.__properties__.width = Number(width);
-});
-Frame.prototype.__defineGetter__("height", function() {
-	if(this.__properties__.height == undefined)
-		return this.__properties__.display.height;
-	return this.__properties__.height;
-});
-Frame.prototype.__defineSetter__("height", function(height) {
-	if(height == undefined)
-		return;
-	if(!this.__checkHeight__(this.y,Number(height)))
-		throw("invalid height: " + height);
-	this.__properties__.height = Number(height);
-});
-
-/* read-only properties */
-Frame.prototype.__defineGetter__("cursor",function() {
-	return this.__position__.cursor;
-});
-Frame.prototype.__defineGetter__("offset",function() {
-	return this.__position__.offset;
-});
-Frame.prototype.__defineGetter__("id", function() {
-	return this.__properties__.id;
-});
-Frame.prototype.__defineGetter__("parent", function() {
-	return this.__relations__.parent;
-});
-Frame.prototype.__defineGetter__("display", function() {
-	return this.__properties__.display;
-});
-Frame.prototype.__defineGetter__("data_height", function() {
-	return this.__properties__.data.length;
-});
-Frame.prototype.__defineGetter__("data_width", function() {
-	if(typeof this.__properties__.data[0] == "undefined")
-		return 0;
-	var longest = 0;
-	for(var d in this.__properties__.data) {
-		if(this.__properties__.data[d].length > longest)
-			longest = this.__properties__.data[d].length;
-	}
-	return longest + 1;
-});
-Frame.prototype.__defineGetter__("data", function() {
-	return this.__properties__.data;
-});
-
-/* protected settings */
-Frame.prototype.__defineGetter__("checkbounds", function() {
-	return this.__settings__.checkbounds;
-});
-Frame.prototype.__defineSetter__("checkbounds", function(bool) {
-	if(typeof bool == "boolean")
-		this.__settings__.checkbounds=bool;
-	else
-		throw("non-boolean checkbounds: " + bool);
-});
-Frame.prototype.__defineGetter__("transparent", function() {
-	return this.__settings__.transparent;
-});
-Frame.prototype.__defineSetter__("transparent", function(bool) {
-	if(typeof bool == "boolean")
-		this.__settings__.transparent=bool;
-	else
-		throw("non-boolean transparent: " + bool);
-});
-Frame.prototype.__defineGetter__("lf_strict", function() {
-	return this.__settings__.lf_strict;
-});
-Frame.prototype.__defineSetter__("lf_strict", function(bool) {
-	if(typeof bool == "boolean")
-		this.__settings__.lf_strict=bool;
-	else
-		throw("non-boolean lf_strict: " + bool);
-});
-Frame.prototype.__defineGetter__("scrollbars", function() {
-	return this.__settings__.scrollbars;
-});
-Frame.prototype.__defineSetter__("scrollbars", function(bool) {
-	if(typeof bool == "boolean")
-		this.__settings__.scrollbars=bool;
-	else
-		throw("non-boolean scrollbars: " + bool);
-});
-Frame.prototype.__defineGetter__("v_scroll", function() {
-	return this.__settings__.v_scroll;
-});
-Frame.prototype.__defineSetter__("v_scroll", function(bool) {
-	if(typeof bool == "boolean")
-		this.__settings__.v_scroll=bool;
-	else
-		throw("non-boolean v_scroll: " + bool);
-});
-Frame.prototype.__defineGetter__("word_wrap", function() {
-	return this.__settings__.word_wrap;
-});
-Frame.prototype.__defineSetter__("word_wrap", function(bool) {
-	if(typeof bool == "boolean")
-		this.__settings__.word_wrap=bool;
-	else
-		throw("non-boolean word_wrap: " + bool);
-});
-Frame.prototype.__defineGetter__("h_scroll", function() {
-	return this.__settings__.h_scroll;
-});
-Frame.prototype.__defineSetter__("h_scroll", function(bool) {
-	if(typeof bool == "boolean")
-		this.__settings__.h_scroll=bool;
-	else
-		throw("non-boolean h_scroll: " + bool);
-});
-Frame.prototype.__defineGetter__("is_open",function() {
-	return this.__properties__.open;
-});
-
-/* public methods */
-Frame.prototype.getData = function(x,y,use_offset) {
-	var px = x;
-	var py = y;
-	if(use_offset) {
-		px += this.__position__.offset.x;
-		py += this.__position__.offset.y;
-	}
-	// if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
-		// throw("Frame.getData() - invalid coordinates: " + px + "," + py);
-	if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
-		return new Char();
-	return this.__properties__.data[py][px];
-}
-Frame.prototype.setData = function(x,y,ch,attr,use_offset) {
-	var px = x;
-	var py = y;
-	if(use_offset) {
-		px += this.__position__.offset.x;
-		py += this.__position__.offset.y;
-	}
-	//I don't remember why I did this, but it was probably important at the time
-	//if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
-		// throw("Frame.setData() - invalid coordinates: " + px + "," + py);
-	if(!this.__properties__.data[py])
-		this.__properties__.data[py] = [];
-	if(!this.__properties__.data[py][px])
-		this.__properties__.data[py][px] = new Char();
-	if(this.__properties__.data[py][px].ch == ch && this.__properties__.data[py][px].attr == attr)
-		return;
-	if(ch)
-		this.__properties__.data[py][px].ch = ch;
-	if(attr)
-		this.__properties__.data[py][px].attr = attr;
-	if(this.__properties__.open)
-		this.__properties__.display.updateChar(this,x,y);
-}
-Frame.prototype.getWord = function(x,y) {
-	var word = []
-	var nx = x-this.x;
-	var ny = y-this.y;
-	var cell = this.getData(nx,ny,false);
-	while(nx >= 0 && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) {
-		word.unshift(cell.ch);
-		nx--;
-		cell = this.getData(nx,ny,false);
-	}
-	nx = x-this.x+1;
-	cell = this.getData(nx,ny,false);
-	while(nx < this.width && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) {
-		word.push(cell.ch);
-		nx++;
-		cell = this.getData(nx,ny,false);
-	}
-	return word.join("");
-}
-Frame.prototype.clearData = function(x,y,use_offset) {
-	var px = x;
-	var py = y;
-	if(use_offset) {
-		px += this.__position__.offset.x;
-		py += this.__position__.offset.y;
-	}
-	if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
-		return;
-	else if(this.__properties__.data[py][px].ch == undefined && this.__properties__.data[py][px].attr == undefined)
-		return;
-	this.__properties__.data[py][px].ch = undefined;
-	this.__properties__.data[py][px].attr = undefined;
-	if(this.__properties__.open)
-		this.__properties__.display.updateChar(this,x,y);
-}
-Frame.prototype.bottom = function() {
-	if(this.__properties__.open) {
-		for each(var c in this.__relations__.child)
-			c.bottom();
-		this.__properties__.display.bottom(this);
-	}
-}
-Frame.prototype.top = function() {
-	if(this.__properties__.open) {
-		this.__properties__.display.top(this);
-		for each(var c in this.__relations__.child)
-			c.top();
-	}
-}
-Frame.prototype.open = function() {
-	if(!this.__properties__.open) {
-		this.__properties__.display.open(this);
-		this.__properties__.open = true;
-	}
-	for each(var c in this.__relations__.child) {
-		c.open();
-	}
-}
-Frame.prototype.refresh = function() {
-	if(this.__properties__.open) {
-		this.__properties__.display.updateFrame(this);
-		for each(var c in this.__relations__.child)
-			c.refresh();
-	}
-}
-Frame.prototype.close = function() {
-	for each(var c in this.__relations__.child)
-		c.close();
-	if(this.__properties__.open) {
-		this.__properties__.display.close(this);
-		this.__properties__.open = false;
-	}
-}
-Frame.prototype.delete = function(id) {
-	if(id == undefined) {
-		this.close();
-		if(this.__relations__.parent) {
-			this.__relations__.parent.delete(this.id);
-		}
-	}
-	else {
-		for(var c=0;c<this.__relations__.child.length;c++) {
-			if(this.__relations__.child[c].id == id) {
-				this.__relations__.child.splice(c--,1);
-				break;
-			}
-		}
-	}
-}
-Frame.prototype.move = function(x,y) {
-	var nx = undefined;
-	var ny = undefined;
-	if(this.__checkX__(this.x+x) && this.__checkWidth__(this.x+x,this.width))
-		nx = this.x+x;
-	if(this.__checkY__(this.y+y) && this.__checkHeight__(this.y+y,this.height))
-		ny = this.y+y;
-	if(nx == undefined && ny == undefined)
-		return;
-	this.__properties__.display.updateFrame(this);
-	if(nx !== undefined)
-		this.x=nx;
-	if(ny !== undefined)
-		this.y=ny;
-	this.__properties__.display.updateFrame(this);
-	for each(var c in this.__relations__.child)
-		c.move(x,y);
-}
-Frame.prototype.moveTo = function(x,y) {
-	for each(var c in this.__relations__.child) {
-		var cx = (x + (c.x-this.x));
-		var cy = (y + (c.y-this.y));
-		c.moveTo(cx,cy);
-	}
-	var nx = undefined;
-	var ny = undefined;
-	if(this.__checkX__(x))
-		nx = x;
-	if(this.__checkY__(y))
-		ny = y;
-	if(nx == undefined && ny == undefined)
-		return;
-	this.__properties__.display.updateFrame(this);
-	if(nx !== undefined)
-		this.x=nx;
-	if(ny !== undefined)
-		this.y=ny;
-	this.__properties__.display.updateFrame(this);
-}
-Frame.prototype.draw = function() {
-	if(this.__properties__.open)
-		this.refresh();
-	else
-		this.open();
-	this.cycle();
-}
-Frame.prototype.cycle = function() {
-	return this.__properties__.display.cycle();
-}
-Frame.prototype.load = function(filename,width,height) {
-	var f=new File(filename);
-	if (!f.open("rb", true))
-		return false;
-	var contents=f.read();
-	f.close();
-	var valid_sauce = false;
-	var ext = file_getext(filename).substr(1).toUpperCase();
-
-	if (contents.substr(-128, 7) == "SAUCE00") {
-		var sauceless_size = ascii(contents.substr(-35,1));
-		sauceless_size <<= 8;
-		sauceless_size |= ascii(contents.substr(-36,1));
-		sauceless_size <<= 8;
-		sauceless_size |= ascii(contents.substr(-37,1));
-		sauceless_size <<= 8;
-		sauceless_size |= ascii(contents.substr(-38,1));
-
-		var data_type = ascii(contents.substr(-34,1));
-		var file_type = ascii(contents.substr(-33,1));
-		var tinfo1 = ascii(contents.substr(-31,1));
-		tinfo1 <<= 8;
-		tinfo1 |= ascii(contents.substr(-32,1));
-		var tinfo2 = ascii(contents.substr(-29,1));
-		tinfo2 <<= 8;
-		tinfo2 |= ascii(contents.substr(-30,1));
-		switch(data_type) {
-			case 1:
-				switch(file_type) {
-					case 0:	// PLain ASCII
-						ext = "TXT";
-						if (tinfo1)
-							width = tinfo1;
-						if (tinfo2)
-							height = tinfo2;
-						break;
-					case 1: // ANSi
-						ext = "ANS";
-						if (tinfo1)
-							width = tinfo1;
-						if (tinfo2)
-							height = tinfo2;
-						break;
-					case 7: // Source
-						ext = "TXT";
-						break;
-				}
-				valid_sauce = true;
-				break;
-			case 5:
-				ext = 'BIN';
-				width = file_type * 2;
-				height = (sauceless_size / 2) / width;
-				valid_sauce = true;
-				break;
-		}
-		if (valid_sauce)
-			contents = contents.substr(0, sauceless_size);
-	}
-
-	switch(ext) {
-	case "ANS":
-		/*
-		 * TODO: This doesn't do exactly what reading a text file does
-		 * one Windows (nor on Linux), but it should be close to what
-		 * was meant, and should work given that it resets x every line.
-		 */
-		var lines=contents.split(/\r\n/);
-
-		var attr = this.attr;
-		var bg = BG_BLACK;
-		var fg = LIGHTGRAY;
-
-		var i = 0;
-		var y = 0;
-		var saved = {};
-
-		while(lines.length > 0) {
-			var x = 0;
-			var line = lines.shift();
-			/* parse 'ATCODES'??
-			line = line.replace(/@(.*)@/g,
-				function (str, code, offset, s) {
-					return bbs.atcode(code);
-				}
-			);
-			*/
-			while(line.length > 0) {
-				/* parse an attribute sequence*/
-				var m = line.match(/^\x1b\[(\d*);?(\d*);?(\d*)m/);
-				if(m !== null) {
-					line = line.substr(m.shift().length);
-					if(m[0] == 0) {
-						bg = BG_BLACK;
-						fg = LIGHTGRAY;
-						i = 0;
-						m.shift();
-					}
-					if(m[0] == 1) {
-						i = HIGH;
-						m.shift();
-					}
-					if(m[0] >= 40) {
-						switch(Number(m.shift())) {
-						case 40:
-							bg = BG_BLACK;
-							break;
-						case 41:
-							bg = BG_RED;
-							break;
-						case 42:
-							bg = BG_GREEN;
-							break;
-						case 43:
-							bg = BG_BROWN;
-							break;
-						case 44:
-							bg = BG_BLUE;
-							break;
-						case 45:
-							bg = BG_MAGENTA;
-							break;
-						case 46:
-							bg = BG_CYAN;
-							break;
-						case 47:
-							bg = BG_LIGHTGRAY;
-							break;
-						}
-					}
-					if(m[0] >= 30) {
-						switch(Number(m.shift())) {
-						case 30:
-							fg = BLACK;
-							break;
-						case 31:
-							fg = RED;
-							break;
-						case 32:
-							fg = GREEN;
-							break;
-						case 33:
-							fg = BROWN;
-							break;
-						case 34:
-							fg = BLUE;
-							break;
-						case 35:
-							fg = MAGENTA;
-							break;
-						case 36:
-							fg = CYAN;
-							break;
-						case 37:
-							fg = LIGHTGRAY;
-							break;
-						}
-					}
-					attr = bg + fg + i;
-					continue;
-				}
-
-				/* parse absolute character position */
-				var m = line.match(/^\x1b\[(\d*);?(\d*)[Hf]/);
-				if(m !== null) {
-					line = line.substr(m.shift().length);
-
-					if(m.length==0) {
-						x=0;
-						y=0;
-					}
-					else {
-						if(m[0])
-							y = Number(m.shift())-1;
-						if(m[0])
-							x = Number(m.shift())-1;
-					}
-					continue;
-				}
-
-				/* ignore a bullshit sequence */
-				var n = line.match(/^\x1b\[\?7h/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					continue;
-				}
-
-				/* parse an up positional sequence */
-				var n = line.match(/^\x1b\[(\d*)A/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					var chars = n.shift();
-					if(chars < 1)
-						y-=1;
-					else
-						y-=Number(chars);
-					continue;
-				}
-
-				/* parse a down positional sequence */
-				var n = line.match(/^\x1b\[(\d*)B/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					var chars = n.shift();
-					if(chars < 1)
-						y+=1;
-					else
-						y+=Number(chars);
-					continue;
-				}
-
-				/* parse a forward positional sequence */
-				var n = line.match(/^\x1b\[(\d*)C/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					var chars = n.shift();
-					if(chars < 1)
-						x+=1;
-					else
-						x+=Number(chars);
-					continue;
-				}
-
-				/* parse a backward positional sequence */
-				var n = line.match(/^\x1b\[(\d*)D/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					var chars = n.shift()
-					if(chars < 1)
-						x-=1;
-					else
-						x-=Number(chars);
-					continue;
-				}
-
-				/* parse a clear screen sequence */
-				var n = line.match(/^\x1b\[2J/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					continue;
-				}
-
-				/* parse save cursor sequence */
-				var n = line.match(/^\x1b\[s/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					saved.x = x;
-					saved.y = y;
-					continue;
-				}
-
-				/* parse restore cursor sequence */
-				var n = line.match(/^\x1b\[u/);
-				if(n !== null) {
-					line = line.substr(n.shift().length);
-					x = saved.x;
-					y = saved.y;
-					continue;
-				}
-
-				/* set character and attribute */
-				var ch = line[0];
-				line = line.substr(1);
-
-				/* validate position */
-				if(y<0)
-					y=0;
-				if(x<0)
-					x=0;
-				if(x>=this.width) {
-					x=0;
-					y++;
-				}
-				/* set character and attribute */
-				if(!this.__properties__.data[y])
-					this.__properties__.data[y]=[];
-				this.__properties__.data[y][x]=new Char(ch,attr);
-				x++;
-			}
-			y++;
-		}
-		break;
-    case "BIN":
-        if (!this.load_bin(contents, width, height, 0)) return false;
-		break;
-	case "TXT":
-		var lines=contents.split(/\r\n/);
-		while(lines.length > 0)
-			this.putmsg(lines.shift() + "\r\n");
-		break;
-	default:
-		throw("unsupported filetype");
-		break;
-	}
-}
-Frame.prototype.load_bin = function(contents, width, height, offset) {
-    if(width == undefined || height == undefined)
-        throw("unknown graphic dimensions");
-    if(offset == undefined) offset = 0;
-    for(var y=0; y<height; y++) {
-        for(var x=0; x<width; x++) {
-            var c = new Char();
-            if(offset >= contents.length)
-                return(false);
-            c.ch = contents.substr(offset++, 1);
-            if(offset == contents.length)
-                return(false);
-            c.attr = ascii(contents.substr(offset++, 1));
-            c.id = this.id;
-            if(!this.__properties__.data[y])
-                this.__properties__.data[y]=[];
-            this.__properties__.data[y][x] = c;
-        }
-    }
-    return true;
-}
-Frame.prototype.scroll = function(x,y) {
-	var update = false;
-	/* default: add a new line to the data matrix */
-	if(x == undefined && y == undefined) {
-		if(this.__settings__.v_scroll) {
-			var newrow = [];
-			for(var x = 0;x<this.width;x++) {
-				for(var y = 0;y<this.height;y++)
-					this.__properties__.display.updateChar(this,x,y);
-				newrow.push(new Char());
-			}
-			this.__properties__.data.push(newrow);
-			this.__position__.offset.y++;
-			update = true;
-		}
-	}
-	/* otherwise, adjust the x/y offset */
-	else {
-		if(typeof x == "number" && x !== 0 && this.__settings__.h_scroll) {
-			this.__position__.offset.x += x;
-			update = true;
-		}
-		if(typeof y == "number" && y !== 0 && this.__settings__.v_scroll) {
-			this.__position__.offset.y += y;
-			update = true;
-		}
-		if(update)
-			this.refresh();
-	}
-	return update;
-}
-Frame.prototype.scrollTo = function(x,y) {
-	var update = false;
-	if(typeof x == "number") {
-		if(this.__settings__.h_scroll) {
-			this.__position__.offset.x = x;
-			update = true;
-		}
-	}
-	if(typeof y == "number") {
-		if(this.__settings__.v_scroll) {
-			this.__position__.offset.y = y;
-			update = true;
-		}
-	}
-	if(update)
-		this.refresh();
-}
-Frame.prototype.insertLine = function(y) {
-	if(this.__properties__.data[y])
-		this.__properties__.data.splice(y,0,[]);
-	else
-		this.__properties__.data[y] = [];
-	this.refresh();
-	return this.__properties__.data[y];
-}
-Frame.prototype.deleteLine = function(y) {
-	var l = undefined;
-	if(this.__properties__.data[y]) {
-		l = this.__properties__.data.splice(y,1);
-		this.refresh();
-	}
-	return l;
-}
-Frame.prototype.screenShot = function(file,append) {
-	return this.__properties__.display.screenShot(file,append);
-}
-Frame.prototype.invalidate = function() {
-	this.__properties__.display.invalidate();
-	this.refresh();
-}
-Frame.prototype.dump = function() {
-	return this.__properties__.display.dump();
-}
-
-/* console method emulation */
-Frame.prototype.home = function() {
-	this.__position__.cursor.x = 0;
-	this.__position__.cursor.y = 0;
-	return true;
-}
-Frame.prototype.end = function() {
-	this.__position__.cursor.x = this.width-1;
-	this.__position__.cursor.y = this.height-1;
-	return true;
-}
-Frame.prototype.pagedown = function() {
-	this.__position__.offset.y += this.height-1;
-	if(this.__position__.offset.y >= this.data_height)
-		this.__position__.offset.y = this.data_height - this.height;
-	this.refresh();
-}
-Frame.prototype.pageup = function() {
-	this.__position__.offset.y -= this.height-1;
-	if(this.__position__.offset.y < 0)
-		this.__position__.offset.y = 0;
-	this.refresh();
-}
-Frame.prototype.clear = function (attr) {
-	if (attr) this.attr = attr;
-	this.__properties__.data = [];
-	this.__position__.offset.x = 0;
-	this.__position__.offset.y = 0;
-	this.home();
-	this.invalidate();
-}
-Frame.prototype.erase = function(ch, attr) {
-	if(attr == undefined)
-		attr = this.attr;
-	var px = this.__position__.offset.x;
-	var py = this.__position__.offset.y;
-	for(var y = 0; y< this.height; y++) {
-		if(!this.__properties__.data[py + y]) {
-			continue;
-		}
-		for(var x = 0; x<this.width; x++) {
-			if(!this.__properties__.data[py + y][px + x]) {
-				continue;
-			}
-			if((this.__properties__.data[py + y][px + x].ch === undefined || 
-				this.__properties__.data[py + y][px + x].ch === ch) && 
-				this.__properties__.data[py + y][px + x].attr == attr) {
-				continue;
-			}
-			this.__properties__.data[py + y][px + x].ch = undefined;
-			this.__properties__.data[py + y][px + x].attr = attr;
-			this.__properties__.display.updateChar(this, x, y);
-		}
-	}
-	this.home();
-}
-Frame.prototype.clearline = function(attr) {
-	if(attr == undefined)
-		attr = this.attr;
-	var py = this.__position__.offset.y + this.__position__.cursor.y;
-	if(!this.__properties__.data[py])
-		return false;
-	for(var x=0;x<this.__properties__.data[py].length;x++) {
-		if(this.__properties__.data[py][x]) {
-			this.__properties__.data[py][x].ch = undefined;
-			this.__properties__.data[py][x].attr = attr;
-		}
-	}
-	for(var x=0;x<this.width;x++) {
-		this.__properties__.display.updateChar(this,x,this.__position__.cursor.y);
-	}
-}
-Frame.prototype.cleartoeol = function(attr) {
-	if(attr == undefined)
-		attr = this.attr;
-	var px = this.__position__.offset.x + this.__position__.cursor.x;
-	var py = this.__position__.offset.y + this.__position__.cursor.y;
-	if(!this.__properties__.data[this.__position__.cursor.y])
-		return false;
-	for(var x=px;x<this.__properties__.data[py].length;x++) {
-		if(this.__properties__.data[py][x]) {
-			if(this.__properties__.data[py][x].ch !== undefined) {
-				this.__properties__.data[py][x].ch = undefined;
-				this.__properties__.display.updateChar(this,x - this.__position__.offset.x, this.__position__.cursor.y);
-			}
-			this.__properties__.data[py][x].attr = attr;
-		}
-	}
-}
-Frame.prototype.crlf = function() {
-	this.__position__.cursor.x = 0;
-	if(this.__position__.cursor.y < this.height-1)
-		this.__position__.cursor.y += 1;
-	else {}
-}
-Frame.prototype.write = function(str,attr) {
-	if(str == undefined)
-		return;
-	if(this.__settings__.word_wrap)
-		str = word_wrap(str, this.width,str.length, false);
-	str = str.toString().split('');
-
-	if(attr)
-		this.__properties__.curr_attr = attr;
-	else
-		this.__properties__.curr_attr = this.attr;
-	var pos = this.__position__.cursor;
-	while(str.length > 0) {
-		var ch = str.shift();
-		this.__putChar__.call(this,ch,this.__properties__.curr_attr);
-		pos.x++;
-	}
-}
-Frame.prototype.putmsg = function(str,attr) {
-	if(str == undefined)
-		return;
-	if(this.__settings__.word_wrap) {
-		var remainingWidth = this.width - this.__position__.cursor.x;
-		if(str.length > remainingWidth) {
-			str = word_wrap(str,remainingWidth,str.length,false).split('\n');
-			var trailing_newline = str[str.length - 1].length == 0;
-			str = str.shift() + '\r\n' + word_wrap(str.join('\r\n'),this.width,remainingWidth,false);
-			if(!trailing_newline) str = str.trimRight();
-		}
-	}
-	str = str.toString().split('');
-
-	if(attr)
-		this.__properties__.curr_attr = attr;
-	else
-		this.__properties__.curr_attr = this.attr;
-	var pos = this.__position__.cursor;
-
-	while(str.length > 0) {
-		var ch = str.shift();
-		if(this.__properties__.ctrl_a) {
-			var k = ch;
-			if(k)
-				k = k.toUpperCase();
-			switch(k) {
-			case '\1':	/* A "real" ^A code */
-				this.__putChar__.call(this,ch,this.__properties__.curr_attr);
-				pos.x++;
-				break;
-			case 'K':	/* Black */
-				this.__properties__.curr_attr=(this.__properties__.curr_attr)&0xf8;
-				break;
-			case 'R':	/* Red */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|RED;
-				break;
-			case 'G':	/* Green */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|GREEN;
-				break;
-			case 'Y':	/* Yellow */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BROWN;
-				break;
-			case 'B':	/* Blue */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BLUE;
-				break;
-			case 'M':	/* Magenta */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|MAGENTA;
-				break;
-			case 'C':	/* Cyan */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|CYAN;
-				break;
-			case 'W':	/* White */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|LIGHTGRAY;
-				break;
-			case '0':	/* Black */
-				this.__properties__.curr_attr=(this.__properties__.curr_attr)&0x8f;
-				break;
-			case '1':	/* Red */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(RED<<4);
-				break;
-			case '2':	/* Green */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(GREEN<<4);
-				break;
-			case '3':	/* Yellow */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BROWN<<4);
-				break;
-			case '4':	/* Blue */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BLUE<<4);
-				break;
-			case '5':	/* Magenta */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(MAGENTA<<4);
-				break;
-			case '6':	/* Cyan */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(CYAN<<4);
-				break;
-			case '7':	/* White */
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(LIGHTGRAY<<4);
-				break;
-			case 'H':	/* High Intensity */
-				this.__properties__.curr_attr|=HIGH;
-				break;
-			case 'I':	/* Blink */
-				this.__properties__.curr_attr|=BLINK;
-				break;
-			case 'N': 	/* Normal */
-				this.__properties__.curr_attr&=~HIGH;
-				this.__properties__.curr_attr&=~BLINK;
-				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|LIGHTGRAY;
-				break;
-      case '+':
-        this.__properties__.attr_stack.push(this.__properties__.curr_attr);
-        break;
-			case '-':	/* Normal if High, Blink, or BG */
-        if (this.__properties__.attr_stack.length) {
-          this.__properties__.curr_attr = this.__properties__.attr_stack.pop();
-        } else if(this.__properties__.curr_attr & 0xf8) {
-					this.__properties__.curr_attr=this.attr;
-        }
-				break;
-			case '_':	/* Normal if blink/background */
-				if(this.__properties__.curr_attr & 0xf0)
-					this.__properties__.curr_attr=this.attr;
-				break;
-			case '[':	/* CR */
-				pos.x=0;
-				break;
-			case ']':	/* LF */
-				pos.y++;
-				if(this.__settings__.lf_strict && pos.y >= this.height) {
-					this.scroll();
-					pos.y--;
-				}
-				break;
-			default:	/* Other stuff... specifically, check for right movement */
-				if(ch.charCodeAt(0)>127)
-					pos.x+=ch.charCodeAt(0)-127;
-				break;
-			}
-			this.__properties__.ctrl_a = false;
-		}
-		else {
-			switch(ch) {
-			case '\1':		/* CTRL-A code */
-			case ctrl('A'):
-				this.__properties__.ctrl_a = true;
-				break;
-			case '\7':		/* Beep */
-				break;
-			case '\x7f':	/* DELETE */
-				break;
-			case '\b':
-				if(pos.x > 0) {
-					pos.x--;
-					this.__putChar__.call(this," ",this.__properties__.curr_attr);
-				}
-				break;
-			case '\r':
-				pos.x=0;
-				break;
-			case '\t':
-				pos.x += 4;
-				break;
-			case '\n':
-				pos.y++;
-				if(this.__settings__.lf_strict && pos.y >= this.height) {
-					this.scroll();
-					pos.y--;
-				}
-				break;
-			default:
-				this.__putChar__.call(this,ch,this.__properties__.curr_attr);
-				pos.x++;
-				break;
+/* $Id: frame.js,v 1.91 2020/08/01 19:32:23 rswindell Exp $ */
+
+/**
+ 	Javascript Frame Library
+ 	for Synchronet v3.15a+
+ 	by Matt Johnson (mcmlxxix)
+
+DESCRIPTION:
+
+ 	this library is meant to be used in conjunction with other libraries that
+ 	store display data in a Frame() object or objects
+ 	this allows for "windows" that can be hidden, moved, closed, etc...
+	without destroying the data behind them.
+
+ 	the object itself takes the following parameters:
+
+ 		x: 			the coordinate representing the top left corner of the frame (horiz)
+ 		y: 			the coordinate representing the top left corner of the frame (vert)
+ 		width: 		the horizontal width of the frame
+ 		height: 	the vertical height of the frame
+ 		attr:		the default color attributes of the frame
+		parent:		a frame object representing the parent of the new frame
+
+METHODS:
+
+	frame.open()				//populate frame contents in character canvas
+	frame.close()				//remove frame contents from character canvas
+	frame.delete()				//delete this frame (remove from screen buffer, destroy internal references)
+	frame.invalidate()			//clear screen buffer to redraw contents on cycle() or draw()
+	frame.draw()				//open frame (if not open) move to top (if not on top) and cycle()
+	frame.cycle()				//check the display matrix for updated characters and displays them
+	frame.refresh()				//flag all frame sectors for potential update
+	frame.load(filename)		//load a binary graphic (.BIN) or ANSI graphic (.ANS) file
+	frame.bottom()				//push frame to bottom of display stack
+	frame.top()					//pull frame to top of display stack
+	frame.scroll(x,y)			//scroll frame n spaces in any direction
+	frame.scrollTo(x,y)			//scroll frame to absolute offset
+	frame.move(x,y)				//move frame n spaces in any direction
+	frame.moveTo(x,y)			//move frame to absolute position
+	frame.end()					//opposite of frame.home()
+	frame.screenShot(file,append)
+								//capture the contents of a frame to file
+	frame.getData(x,y,use_offset)
+								//return the character and attribute located at x,y in frame.data (optional scroll offset)
+	frame.setData(x,y,ch,attr,use_offset)
+								//modify the character and attribute located at x,y in frame.data (optional scroll offset)
+	frame.clearData(x,y,use_offset)
+								//delete the character and attribute located at x,y in frame.data (optional scroll offset)
+	frame.clearline(attr)		//see http://synchro.net/docs/jsobjs.html#console
+	frame.cleartoeol(attr)
+	frame.putmsg(str,attr)
+	frame.clear(attr)
+	frame.home()
+	frame.center(str,attr)
+	frame.crlf()
+	frame.getxy()
+	frame.gotoxy(x,y)
+	frame.pushxy()
+	frame.popxy()
+
+PROPERTIES:
+
+	frame.x						//x screen position
+	frame.y						//y screen position
+	frame.width					//frame width
+	frame.height				//frame height
+	frame.data					//frame data matrix
+	frame.data_height			//true height of frame contents (READ ONLY)
+	frame.data_width			//true width of frame contents (READ ONLY)
+	frame.attr					//default attributes for frame
+	frame.checkbounds			//toggle true/false to restrict/allow frame movement outside display
+	frame.lf_strict				//toggle true/false to force newline after a crlf-terminated string
+	frame.v_scroll				//toggle true/false to enable/disable vertical scrolling
+	frame.h_scroll				//toggle true/false to enable/disable horizontal scrolling
+	frame.scrollbars			//toggle true/false to show/hide scrollbars
+	frame.transparent			//toggle true/false to enable transparency mode
+								//(do not display frame sectors where char == undefined)
+	frame.offset				//current offset object {x,y}
+	frame.cursor				//current cursor object {x,y}
+	frame.parent				//the parent frame of a frame
+	frame.id					//a unique identifier (e.g. "0.1.1.2.3")
+	frame.is_open				//return true is frame is opened in screen buffer
+
+USAGE:
+
+	//create a new frame object at screen position 1,1. 80 characters wide by 24 tall
+ 	load("frame.js");
+ 	var frame = new Frame(1,1,80,24,BG_BLUE);
+
+	//add frame to the display canvas
+	frame.open();
+
+	//add a new frame within the frame object that will display on top at position 10,10
+	var subframe = new Frame(10,10,10,10,BG_GREEN,frame);
+
+	//add subframe to the display canvas
+	subframe.open();
+
+	//place cursor at position x:5 y:5 relative to subframe's coordinates
+	subframe.gotoxy(5,5);
+
+	//beware this sample infinite loop
+ 	while(!js.terminated) {
+		//print a message into subframe
+		subframe.putmsg("1");
+
+		//on first call this will draw the entire initial frame,
+		//as triggered by the open() method call.
+		//on subsequent calls this will draw only areas that have changed
+		frame.cycle();
+		//NOTE: if frames are linked, only one frame needs to be cycled
+		//		for all frames to update
+	}
+
+	//close out the entire frame tree
+	frame.close();
+
+ */
+
+load("sbbsdefs.js");
+function Frame(x,y,width,height,attr,parent) {
+
+	/* private properties */
+	this.__properties__ = {
+		x:undefined,
+		y:undefined,
+		width:undefined,
+		height:undefined,
+		attr:undefined,
+		display:undefined,
+		data:[],
+		open:false,
+		ctrl_a:false,
+		curr_attr:undefined,
+    attr_stack:[],
+		id:0
+	};
+	this.__settings__ = {
+		v_scroll:true,
+		h_scroll:false,
+		scrollbars:false,
+		lf_strict:true,
+		checkbounds:true,
+		transparent:false,
+		word_wrap:false
+	};
+	this.__relations__ = {
+		parent:undefined,
+		child:[]
+	};
+	this.__position__ = {
+		cursor:undefined,
+		offset:undefined,
+		stored:undefined
+	};
+
+	function init(x,y,width,height,attr,parent) {
+		if(parent instanceof Frame) {
+			this.__properties__.id = parent.display.nextID;
+			this.__properties__.display = parent.display;
+			this.__settings__.checkbounds = parent.checkbounds;
+			this.__relations__.parent = parent;
+			parent.child = this;
+		}
+		else {
+			this.__properties__.display = new Display(x,y,width,height);
+		}
+
+		this.x = x;
+		this.y = y;
+		this.width = width;
+		this.height = height;
+		this.attr = attr;
+
+		this.__position__.cursor = new Cursor(0,0,this);
+		this.__position__.offset = new Offset(0,0,this);
+		this.__position__.stored = new Cursor(0,0,this);
+
+		//log(LOG_DEBUG,format("new frame initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y));
+	}
+	init.apply(this,arguments);
+}
+
+/* protected properties */
+Frame.prototype.__defineGetter__("child", function() {
+	return this.__relations__.child;
+});
+Frame.prototype.__defineSetter__("child", function(frame) {
+	if(frame instanceof Frame)
+		this.__relations__.child.push(frame);
+	else
+		throw("child not an instance of Frame()");
+});
+Frame.prototype.__defineGetter__("attr", function() {
+	return this.__properties__.attr;
+});
+Frame.prototype.__defineSetter__("attr", function(attr) {
+	if(attr !== undefined && isNaN(attr))
+		throw("invalid attribute: " + attr);
+	this.__properties__.attr = attr;
+});
+Frame.prototype.__defineGetter__("x", function() {
+	if(this.__properties__.x == undefined)
+		return this.__properties__.display.x;
+	return this.__properties__.x;
+});
+Frame.prototype.__defineSetter__("x", function(x) {
+	if(x == undefined)
+		return;
+	if(!this.__checkX__(x))
+		throw("invalid x coordinate: " + x);
+	this.__properties__.x = Number(x);
+});
+Frame.prototype.__defineGetter__("y", function() {
+	if(this.__properties__.y == undefined)
+		return this.__properties__.display.y;
+	return this.__properties__.y;
+});
+Frame.prototype.__defineSetter__("y", function(y) {
+	if(y == undefined)
+		return;
+	if(!this.__checkY__(y))
+		throw("invalid y coordinate: " + y);
+	this.__properties__.y = Number(y);
+});
+Frame.prototype.__defineGetter__("width", function() {
+	if(this.__properties__.width == undefined)
+		return this.__properties__.display.width;
+	return this.__properties__.width;
+});
+Frame.prototype.__defineSetter__("width", function(width) {
+	if(width == undefined)
+		return;
+	if(!this.__checkWidth__(this.x,Number(width)))
+		throw("invalid width: " + width);
+	this.__properties__.width = Number(width);
+});
+Frame.prototype.__defineGetter__("height", function() {
+	if(this.__properties__.height == undefined)
+		return this.__properties__.display.height;
+	return this.__properties__.height;
+});
+Frame.prototype.__defineSetter__("height", function(height) {
+	if(height == undefined)
+		return;
+	if(!this.__checkHeight__(this.y,Number(height)))
+		throw("invalid height: " + height);
+	this.__properties__.height = Number(height);
+});
+
+/* read-only properties */
+Frame.prototype.__defineGetter__("cursor",function() {
+	return this.__position__.cursor;
+});
+Frame.prototype.__defineGetter__("offset",function() {
+	return this.__position__.offset;
+});
+Frame.prototype.__defineGetter__("id", function() {
+	return this.__properties__.id;
+});
+Frame.prototype.__defineGetter__("parent", function() {
+	return this.__relations__.parent;
+});
+Frame.prototype.__defineGetter__("display", function() {
+	return this.__properties__.display;
+});
+Frame.prototype.__defineGetter__("data_height", function() {
+	return this.__properties__.data.length;
+});
+Frame.prototype.__defineGetter__("data_width", function() {
+	if(typeof this.__properties__.data[0] == "undefined")
+		return 0;
+	var longest = 0;
+	for(var d in this.__properties__.data) {
+		if(this.__properties__.data[d].length > longest)
+			longest = this.__properties__.data[d].length;
+	}
+	return longest + 1;
+});
+Frame.prototype.__defineGetter__("data", function() {
+	return this.__properties__.data;
+});
+
+/* protected settings */
+Frame.prototype.__defineGetter__("checkbounds", function() {
+	return this.__settings__.checkbounds;
+});
+Frame.prototype.__defineSetter__("checkbounds", function(bool) {
+	if(typeof bool == "boolean")
+		this.__settings__.checkbounds=bool;
+	else
+		throw("non-boolean checkbounds: " + bool);
+});
+Frame.prototype.__defineGetter__("transparent", function() {
+	return this.__settings__.transparent;
+});
+Frame.prototype.__defineSetter__("transparent", function(bool) {
+	if(typeof bool == "boolean")
+		this.__settings__.transparent=bool;
+	else
+		throw("non-boolean transparent: " + bool);
+});
+Frame.prototype.__defineGetter__("lf_strict", function() {
+	return this.__settings__.lf_strict;
+});
+Frame.prototype.__defineSetter__("lf_strict", function(bool) {
+	if(typeof bool == "boolean")
+		this.__settings__.lf_strict=bool;
+	else
+		throw("non-boolean lf_strict: " + bool);
+});
+Frame.prototype.__defineGetter__("scrollbars", function() {
+	return this.__settings__.scrollbars;
+});
+Frame.prototype.__defineSetter__("scrollbars", function(bool) {
+	if(typeof bool == "boolean")
+		this.__settings__.scrollbars=bool;
+	else
+		throw("non-boolean scrollbars: " + bool);
+});
+Frame.prototype.__defineGetter__("v_scroll", function() {
+	return this.__settings__.v_scroll;
+});
+Frame.prototype.__defineSetter__("v_scroll", function(bool) {
+	if(typeof bool == "boolean")
+		this.__settings__.v_scroll=bool;
+	else
+		throw("non-boolean v_scroll: " + bool);
+});
+Frame.prototype.__defineGetter__("word_wrap", function() {
+	return this.__settings__.word_wrap;
+});
+Frame.prototype.__defineSetter__("word_wrap", function(bool) {
+	if(typeof bool == "boolean")
+		this.__settings__.word_wrap=bool;
+	else
+		throw("non-boolean word_wrap: " + bool);
+});
+Frame.prototype.__defineGetter__("h_scroll", function() {
+	return this.__settings__.h_scroll;
+});
+Frame.prototype.__defineSetter__("h_scroll", function(bool) {
+	if(typeof bool == "boolean")
+		this.__settings__.h_scroll=bool;
+	else
+		throw("non-boolean h_scroll: " + bool);
+});
+Frame.prototype.__defineGetter__("is_open",function() {
+	return this.__properties__.open;
+});
+
+/* public methods */
+Frame.prototype.getData = function(x,y,use_offset) {
+	var px = x;
+	var py = y;
+	if(use_offset) {
+		px += this.__position__.offset.x;
+		py += this.__position__.offset.y;
+	}
+	// if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
+		// throw("Frame.getData() - invalid coordinates: " + px + "," + py);
+	if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
+		return new Char();
+	return this.__properties__.data[py][px];
+}
+Frame.prototype.setData = function(x,y,ch,attr,use_offset) {
+	var px = x;
+	var py = y;
+	if(use_offset) {
+		px += this.__position__.offset.x;
+		py += this.__position__.offset.y;
+	}
+	//I don't remember why I did this, but it was probably important at the time
+	//if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
+		// throw("Frame.setData() - invalid coordinates: " + px + "," + py);
+	if(!this.__properties__.data[py])
+		this.__properties__.data[py] = [];
+	if(!this.__properties__.data[py][px])
+		this.__properties__.data[py][px] = new Char();
+	if(this.__properties__.data[py][px].ch == ch && this.__properties__.data[py][px].attr == attr)
+		return;
+	if(ch)
+		this.__properties__.data[py][px].ch = ch;
+	if(attr)
+		this.__properties__.data[py][px].attr = attr;
+	if(this.__properties__.open)
+		this.__properties__.display.updateChar(this,x,y);
+}
+Frame.prototype.getWord = function(x,y) {
+	var word = []
+	var nx = x-this.x;
+	var ny = y-this.y;
+	var cell = this.getData(nx,ny,false);
+	while(nx >= 0 && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) {
+		word.unshift(cell.ch);
+		nx--;
+		cell = this.getData(nx,ny,false);
+	}
+	nx = x-this.x+1;
+	cell = this.getData(nx,ny,false);
+	while(nx < this.width && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) {
+		word.push(cell.ch);
+		nx++;
+		cell = this.getData(nx,ny,false);
+	}
+	return word.join("");
+}
+Frame.prototype.clearData = function(x,y,use_offset) {
+	var px = x;
+	var py = y;
+	if(use_offset) {
+		px += this.__position__.offset.x;
+		py += this.__position__.offset.y;
+	}
+	if(!this.__properties__.data[py] || !this.__properties__.data[py][px])
+		return;
+	else if(this.__properties__.data[py][px].ch == undefined && this.__properties__.data[py][px].attr == undefined)
+		return;
+	this.__properties__.data[py][px].ch = undefined;
+	this.__properties__.data[py][px].attr = undefined;
+	if(this.__properties__.open)
+		this.__properties__.display.updateChar(this,x,y);
+}
+Frame.prototype.bottom = function() {
+	if(this.__properties__.open) {
+		for each(var c in this.__relations__.child)
+			c.bottom();
+		this.__properties__.display.bottom(this);
+	}
+}
+Frame.prototype.top = function() {
+	if(this.__properties__.open) {
+		this.__properties__.display.top(this);
+		for each(var c in this.__relations__.child)
+			c.top();
+	}
+}
+Frame.prototype.open = function() {
+	if(!this.__properties__.open) {
+		this.__properties__.display.open(this);
+		this.__properties__.open = true;
+	}
+	for each(var c in this.__relations__.child) {
+		c.open();
+	}
+}
+Frame.prototype.refresh = function() {
+	if(this.__properties__.open) {
+		this.__properties__.display.updateFrame(this);
+		for each(var c in this.__relations__.child)
+			c.refresh();
+	}
+}
+Frame.prototype.close = function() {
+	for each(var c in this.__relations__.child)
+		c.close();
+	if(this.__properties__.open) {
+		this.__properties__.display.close(this);
+		this.__properties__.open = false;
+	}
+}
+Frame.prototype.delete = function(id) {
+	if(id == undefined) {
+		this.close();
+		if(this.__relations__.parent) {
+			this.__relations__.parent.delete(this.id);
+		}
+	}
+	else {
+		for(var c=0;c<this.__relations__.child.length;c++) {
+			if(this.__relations__.child[c].id == id) {
+				this.__relations__.child.splice(c--,1);
+				break;
 			}
-		}
-	}
-}
-Frame.prototype.center = function(str,attr) {
-	this.__position__.cursor.x = Math.ceil(this.width/2) - Math.ceil(console.strlen(strip_ctrl(str))/2);
-	if(this.__position__.cursor.x < 0)
-		this.__position__.cursor.x = 0;
-	this.putmsg(str,attr);
-}
-Frame.prototype.gotoxy = function(x,y) {
-	if(typeof x == "object" && x.x && x.y) {
-		if(this.__validateCursor__.call(this,x.x-1,x.y-1)) {
-			this.__position__.cursor.x = x.x-1;
-			this.__position__.cursor.y = x.y-1;
-			return true;
-		}
-		return false;
-	}
-	if(this.__validateCursor__.call(this,x-1,y-1)) {
-		this.__position__.cursor.x = x-1;
-		this.__position__.cursor.y = y-1;
-		return true;
-	}
-	return false;
-}
-Frame.prototype.getxy = function() {
-	return {
-		x:this.__position__.cursor.x+1,
-		y:this.__position__.cursor.y+1
-	};
-}
-Frame.prototype.pushxy = function() {
-	this.__position__.stored.x = this.__position__.cursor.x;
-	this.__position__.stored.y = this.__position__.cursor.y;
-}
-Frame.prototype.popxy = function() {
-	this.__position__.cursor.x = this.__position__.stored.x;
-	this.__position__.cursor.y = this.__position__.stored.y;
-}
-Frame.prototype.up = function(n) {
-	if(this.__position__.cursor.y == 0 && this.__position__.offset.y == 0)
-		return false;
-	if(isNaN(n))
-		n = 1;
-	while(n > 0) {
-		if(this.__position__.cursor.y > 0) {
-			this.__position__.cursor.y--;
-			n--;
-		}
-		else break;
-	}
-	if(n > 0)
-		this.scroll(0,-(n));
-	return true;
-}
-Frame.prototype.down = function(n) {
-	if(this.__position__.cursor.y == this.height-1 && this.__position__.offset.y == this.data_height - this.height)
-		return false;
-	if(isNaN(n))
-		n = 1;
-	while(n > 0) {
-		if(this.__position__.cursor.y < this.height - 1) {
-			this.__position__.cursor.y++;
-			n--;
-		}
-		else break;
-	}
-	if(n > 0)
-		this.scroll(0,n);
-	return true;
-}
-Frame.prototype.left = function(n) {
-	if(this.__position__.cursor.x == 0 && this.__position__.offset.x == 0)
-		return false;
-	if(isNaN(n))
-		n = 1;
-	while(n > 0) {
-		if(this.__position__.cursor.x > 0) {
-			this.__position__.cursor.x--;
-			n--;
-		}
-		else break;
-	}
-	if(n > 0)
-		this.scroll(-(n),0);
-	return true;
-}
-Frame.prototype.right = function(n) {
-	if(this.__position__.cursor.x == this.width-1 && this.__position__.offset.x == this.data_width - this.width)
-		return false;
-	if(isNaN(n))
-		n = 1;
-	while(n > 0) {
-		if(this.__position__.cursor.x < this.width - 1) {
-			this.__position__.cursor.x++;
-			n--;
-		}
-		else break;
-	}
-	if(n > 0)
-		this.scroll(n,0);
-	return true;
-}
-
-/* internal frame methods */
-Frame.prototype.__checkX__ = function(x) {
-	if(	isNaN(x) || (this.__settings__.checkbounds &&
-		(x > this.__properties__.display.x + this.__properties__.display.width ||
-		x < this.__properties__.display.x)))
-		return false;
-	return true;
-}
-Frame.prototype.__checkY__ = function(y) {
-	if( isNaN(y) || (this.__settings__.checkbounds &&
-		(y > this.__properties__.display.y + this.__properties__.display.height ||
-		y < this.__properties__.display.y)))
-		return false;
-	return true;
-}
-Frame.prototype.__checkWidth__ = function(x,width) {
-	if(	width < 1 || isNaN(width) || (this.__settings__.checkbounds &&
-		x + width > this.__properties__.display.x + this.__properties__.display.width))
-		return false;
-	return true;
-}
-Frame.prototype.__checkHeight__ = function(y,height) {
-	if( height < 1 || isNaN(height) || (this.__settings__.checkbounds &&
-		y + height > this.__properties__.display.y + this.__properties__.display.height))
-		return false;
-	return true;
-}
-Frame.prototype.__validateCursor__ = function(x,y) {
-	if(x >= this.width)
-		return false;
-	if(y >= this.height)
-		return false;
-	return true;
-}
-Frame.prototype.__putChar__ = function(ch,attr) {
-	if(this.__position__.cursor.x >= this.width) {
-		this.__position__.cursor.x=0;
-		this.__position__.cursor.y++;
-	}
-	if(this.__position__.cursor.y >= this.height) {
-		this.scroll();
-		this.__position__.cursor.y--;
-	}
-	this.setData(this.__position__.cursor.x,this.__position__.cursor.y,ch,attr,true);
-}
-
-Frame.prototype.__flip__ = function (swaps, flipx) {
-    for (var y = 0; y < this.data_height; y++) {
-        if (!Array.isArray(this.data[y])) continue;
-        if (flipx) this.data[y] = this.data[y].reverse();
-        for (var x = 0; x < this.data[y].length; x++) {
-            for (var s = 0; s < swaps.length; s++) {
-                var i = swaps[s].indexOf(ascii(this.data[y][x].ch));
-                if (i >= 0) {
-                    this.data[y][x].ch = ascii(swaps[s][(i + 1) % 2]);
-                    break;
-                }
-            }
-        }
-    }
-    if (!flipx) this.data.reverse();
-}
-
-Frame.prototype.flipX = function () {
-    const swaps = [
-        [ 40, 41 ],
-        [ 47, 92 ],
-        [ 60, 62 ],
-        [ 91, 93 ],
-        [ 123, 125 ],
-        [ 169, 170 ],
-        [ 174, 175 ],
-        [ 180, 195 ],
-        [ 181, 198 ],
-        [ 182, 199 ],
-        [ 183, 214 ],
-        [ 184, 213 ],
-        [ 185, 204 ],
-        [ 187, 201 ],
-        [ 188, 200 ],
-        [ 189, 211 ],
-        [ 190, 212 ],
-        [ 191, 218 ],
-        [ 192, 217 ],
-        [ 221, 222 ]
-    ];
-    this.__flip__(swaps, true);
-}
-
-Frame.prototype.flipY = function () {
-    const swaps = [
-        [ 47, 92 ],
-        [ 183, 189 ],
-        [ 184, 190 ],
-        [ 187, 188 ],
-        [ 191, 217 ],
-        [ 192, 218 ],
-        [ 193, 194 ],
-        [ 200, 201 ],
-        [ 202, 203 ],
-        [ 207, 209 ],
-        [ 208, 210 ],
-        [ 211, 214 ],
-        [ 212, 213 ],
-        [ 220, 223 ]
-    ];
-    this.__flip__(swaps, false);
-}
-
-/* frame reference object */
-function Canvas(frame,display) {
-	this.frame = frame;
-	this.display = display;
-	this.__defineGetter__("xoff",function() {
-		return this.frame.x - this.display.x;
-	});
-	this.__defineGetter__("yoff",function() {
-		return this.frame.y - this.display.y;
-	});
-}
-
-Canvas.prototype.hasData = function(x,y) {
-	var xpos = x-this.xoff;
-	var ypos = y-this.yoff;
-
-	if(xpos < 0 || ypos < 0)
-		return undefined;
-	if(xpos >= this.frame.width || ypos >= this.frame.height)
-		return undefined;
-	if(this.frame.transparent && this.frame.getData(xpos,ypos,true).ch == undefined)
-		return undefined;
-	return true;
-}
-
-/* object representing screen positional and dimensional limits and canvas stack */
-function Display(x,y,width,height) {
-	/* private properties */
-	this.__properties__ = {
-		x:undefined,
-		y:undefined,
-		width:undefined,
-		height:undefined,
-		canvas:{},
-		update:{},
-		buffer:{},
-		count:0
-	}
-
-	/* initialize display properties */
-	this.x = x;
-	this.y = y;
-	this.width = width;
-	this.height = height;
-	log(LOG_DEBUG,format("new display initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y));
-}
-
-/* protected display properties */
-Display.prototype.__defineGetter__("x", function() {
-	return this.__properties__.x;
-});
-Display.prototype.__defineSetter__("x", function(x) {
-	if(x == undefined)
-		this.__properties__.x = 1;
-	else if(isNaN(x))
-		throw("invalid x coordinate: " + x);
-	else
-		this.__properties__.x = Number(x);
-});
-Display.prototype.__defineGetter__("y", function() {
-	return this.__properties__.y;
-});
-Display.prototype.__defineSetter__("y", function(y) {
-	if(y == undefined)
-		this.__properties__.y = 1;
-	else if(isNaN(y) || y < 1 || y > console.screen_rows)
-		throw("invalid y coordinate: " + y);
-	else
-		this.__properties__.y = Number(y);
-});
-Display.prototype.__defineGetter__("width", function() {
-	return this.__properties__.width;
-});
-Display.prototype.__defineSetter__("width", function(width) {
-	if(width == undefined)
-		this.__properties__.width = console.screen_columns;
-	else if(isNaN(width) || (this.x + Number(width) - 1) > (console.screen_columns))
-		throw("invalid width: " + width);
-	else
-		this.__properties__.width = Number(width);
-});
-Display.prototype.__defineGetter__("height", function() {
-	return this.__properties__.height;
-});
-Display.prototype.__defineSetter__("height", function(height) {
-	if(height == undefined)
-		this.__properties__.height = console.screen_rows;
-	else if(isNaN(height) || (this.y + Number(height) - 1) > (console.screen_rows))
-		throw("invalid height: " + height);
-	else
-		this.__properties__.height = Number(height);
-});
-Display.prototype.__defineGetter__("nextID",function() {
-	return ++this.__properties__.count;
-});
-
-/* public display methods */
-Display.prototype.cycle = function() {
-	var updates = this.__getUpdateList__();
-	if(updates.length > 0) {
-		var lasty = undefined;
-		var lastx = undefined;
-		var lastf = undefined;
-		for each(var u in updates) {
-			if(lasty !== u.y || lastx == undefined || (u.x - lastx) !== 1)
-				console.gotoxy(u.px,u.py);
-			if(lastf !== u.id)
-				console.attributes = undefined;
-			this.__drawChar__(u.ch,u.attr,u.px,u.py);
-			lastx = u.x;
-			lasty = u.y;
-			lastf = u.id;
-		}
-		//console.attributes=undefined;
-		return true;
-	}
-	return false;
-}
-Display.prototype.draw = function() {
-	for(var y = 0;y<this.height;y++) {
-		for(var x = 0;x<this.width;x++) {
-			this.__updateChar__(x,y);
-		}
-	}
-	this.cycle();
-}
-Display.prototype.open = function(frame) {
-	var canvas = new Canvas(frame,this);
-	this.__properties__.canvas[frame.id] = canvas;
-	this.updateFrame(frame);
-}
-Display.prototype.close = function(frame) {
-	this.updateFrame(frame);
-	delete this.__properties__.canvas[frame.id];
-}
-Display.prototype.top = function(frame) {
-	var canvas = this.__properties__.canvas[frame.id];
-	delete this.__properties__.canvas[frame.id];
-	this.__properties__.canvas[frame.id] = canvas;
-	this.updateFrame(frame);
-}
-Display.prototype.bottom = function(frame) {
-	for(var c in this.__properties__.canvas) {
-		if(c == frame.id)
-			continue;
-		var canvas = this.__properties__.canvas[c];
-		delete this.__properties__.canvas[c];
-		this.__properties__.canvas[c] = canvas;
-	}
-	this.updateFrame(frame);
-}
-Display.prototype.updateFrame = function(frame) {
-	for(var y = 0;y<frame.height;y++) {
-		for(var x = 0;x<frame.width;x++) {
-			this.updateChar(frame,x,y/*,data*/);
-		}
-	}
-}
-Display.prototype.updateChar = function(frame,x,y) {
-	var xoff = frame.x - this.x;
-	var yoff = frame.y - this.y;
-	this.__updateChar__(xoff + x,yoff + y);
-}
-Display.prototype.screenShot = function(file,append) {
-	var f = new File(file);
-	if(append)
-		f.open('ab',true,4096);
-	else
-		f.open('wb',true,4096) ;
-	if(!f.is_open)
-		return false;
-
-	for(var y = 0;y<this.height;y++) {
-		for(var x = 0;x<this.width;x++) {
-			var c = this.__getTopCanvas__(x,y);
-			var d = this.__getData__(c,x,y);
-			if(d.ch)
-				f.write(d.ch);
-			else
-				f.write(" ");
-			if(d.attr)
-				f.writeBin(d.attr,1);
-			else
-				f.writeBin(0,1);
-		}
-	}
-
-	f.close();
-	return true;
-}
-Display.prototype.invalidate = function() {
-	this.__properties__.buffer = {};
-}
-Display.prototype.dump = function() {
-	var arr = [];
-	for(var y = 0; y < this.height; y++) {
-		arr[y] = [];
-		for(var x = 0; x < this.width; x++) {
-			var c = this.__getTopCanvas__(x, y);
-			if(typeof c == "undefined")
-				continue;
-			var d = this.__getData__(c, x, y);
-			if(typeof d.ch != "undefined")
-				arr[y][x] = d;
-		}
-	}
-	return arr;
-}
-
-/* private functions */
-Display.prototype.__updateChar__ = function(x,y/*,data*/) {
-	//ToDo: maybe store char update so I dont have to retrieve it later?
-	if(!this.__properties__.update[y])
-		this.__properties__.update[y] = {};
-	this.__properties__.update[y][x] = 1; /*data; */
-}
-
-Display.prototype.__getUpdateList__ = function() {
-	var list = [];
-	for(var y in this.__properties__.update) {
-		for(var x in this.__properties__.update[y]) {
-			var c = this.__getTopCanvas__(x,y);
-			var d = this.__getData__(c,x,y);
-
-			if(d.px < 1 ||  d.py < 1 || d.px > console.screen_columns || d.py > console.screen_rows)
-				continue;
-			if(!this.__properties__.buffer[x]) {
-				this.__properties__.buffer[x] = {};
-				this.__properties__.buffer[x][y] = d;
-				list.push(d);
-			}
-			else if(this.__properties__.buffer[x][y] == undefined ||
-				this.__properties__.buffer[x][y].ch != d.ch ||
-				this.__properties__.buffer[x][y].attr != d.attr) {
-				this.__properties__.buffer[x][y] = d;
-				list.push(d);
-			}
-		}
-	}
-	this.__properties__.update={};
-	return list.sort(function(a,b) {
-		if(a.y == b.y)
-			return a.x-b.x;
-		return a.y-b.y;
-	});
-}
-Display.prototype.__getData__ = function(c,x,y) {
-	var cd = {
-		x:Number(x),
-		y:Number(y),
-		px:Number(x) + this.__properties__.x,
-		py:Number(y) + this.__properties__.y
-	};
-	if(c) {
-		var d = c.frame.getData(x-c.xoff,y-c.yoff,true);
-		cd.id = c.frame.id;
-		cd.ch = d.ch;
-		if(d.attr)
-			cd.attr = d.attr;
-		else
-			cd.attr = c.frame.attr;
-	}
-	return cd;
-}
-Display.prototype.__drawChar__ = function(ch,attr,xpos,ypos) {
-	if(attr)
-		console.attributes = attr;
-	if(xpos == console.screen_columns && ypos == console.screen_rows)
-		console.cleartoeol();
-	else if(ch == undefined) {
-		console.write(" ");
-	}
-	else {
-		console.write(ch);
-	}
-}
-Display.prototype.__getTopCanvas__ = function(x,y) {
-	var top = undefined;
-	for each(var c in this.__properties__.canvas) {
-		if(c.frame.parent == undefined || c.hasData(x,y))
-			top = c;
-	}
-	return top;
-}
-
-/* character/attribute pair representing a screen position and its contents */
-function Char(ch,attr) {
-	this.ch = ch;
-	this.attr = attr;
-}
-
-/* self-validating cursor position object */
-function Cursor(x,y,frame) {
-	this.__properties__ = {
-		x:undefined,
-		y:undefined,
-		frame:undefined
-	}
-
-	if(frame instanceof Frame)
-		this.__properties__.frame = frame;
-	else
-		throw("the frame is not a frame");
-
-	this.x = x;
-	this.y = y;
-}
-
-Cursor.prototype.__defineGetter__("x", function() {
-	return this.__properties__.x;
-});
-Cursor.prototype.__defineSetter__("x", function(x) {
-	if(isNaN(x))
-		throw("invalid x coordinate: " + x);
-	this.__properties__.x = x;
-});
-Cursor.prototype.__defineGetter__("y", function() {
-	return this.__properties__.y;
-});
-Cursor.prototype.__defineSetter__("y", function(y) {
-	if(isNaN(y))
-		throw("invalid y coordinate: " + y);
-	this.__properties__.y = y;
-});
-
-
-
-/* self-validating scroll offset object */
-function Offset(x,y,frame) {
-	this.__properties__ = {
-		x:undefined,
-		y:undefined,
-		frame:undefined
-	}
-
-	if(frame instanceof Frame)
-		this.__properties__.frame = frame;
-	else
-		throw("the frame is not a frame");
-
-	this.x = x;
-	this.y = y;
-}
-
-Offset.prototype.__defineGetter__("x", function() {
-	return this.__properties__.x;
-});
-Offset.prototype.__defineSetter__("x", function(x) {
-	if(x == undefined)
-		throw("invalid x offset: " + x);
-	else if(x < 0)
-		x = 0;
-	this.__properties__.x = x;
-});
-Offset.prototype.__defineGetter__("y", function() {
-	return this.__properties__.y;
-});
-Offset.prototype.__defineSetter__("y", function(y) {
-	if(y == undefined)
-		throw("invalid y offset: " + y);
-	else if(y < 0)
-		y = 0;
-	this.__properties__.y = y;
-});
+		}
+	}
+}
+Frame.prototype.move = function(x,y) {
+	var nx = undefined;
+	var ny = undefined;
+	if(this.__checkX__(this.x+x) && this.__checkWidth__(this.x+x,this.width))
+		nx = this.x+x;
+	if(this.__checkY__(this.y+y) && this.__checkHeight__(this.y+y,this.height))
+		ny = this.y+y;
+	if(nx == undefined && ny == undefined)
+		return;
+	this.__properties__.display.updateFrame(this);
+	if(nx !== undefined)
+		this.x=nx;
+	if(ny !== undefined)
+		this.y=ny;
+	this.__properties__.display.updateFrame(this);
+	for each(var c in this.__relations__.child)
+		c.move(x,y);
+}
+Frame.prototype.moveTo = function(x,y) {
+	for each(var c in this.__relations__.child) {
+		var cx = (x + (c.x-this.x));
+		var cy = (y + (c.y-this.y));
+		c.moveTo(cx,cy);
+	}
+	var nx = undefined;
+	var ny = undefined;
+	if(this.__checkX__(x))
+		nx = x;
+	if(this.__checkY__(y))
+		ny = y;
+	if(nx == undefined && ny == undefined)
+		return;
+	this.__properties__.display.updateFrame(this);
+	if(nx !== undefined)
+		this.x=nx;
+	if(ny !== undefined)
+		this.y=ny;
+	this.__properties__.display.updateFrame(this);
+}
+Frame.prototype.draw = function() {
+	if(this.__properties__.open)
+		this.refresh();
+	else
+		this.open();
+	this.cycle();
+}
+Frame.prototype.cycle = function() {
+	return this.__properties__.display.cycle();
+}
+Frame.prototype.load = function(filename,width,height) {
+	var f=new File(filename);
+	if (!f.open("rb", true))
+		return false;
+	var contents=f.read();
+	f.close();
+	var valid_sauce = false;
+	var ext = file_getext(filename).substr(1).toUpperCase();
+
+	if (contents.substr(-128, 7) == "SAUCE00") {
+		var sauceless_size = ascii(contents.substr(-35,1));
+		sauceless_size <<= 8;
+		sauceless_size |= ascii(contents.substr(-36,1));
+		sauceless_size <<= 8;
+		sauceless_size |= ascii(contents.substr(-37,1));
+		sauceless_size <<= 8;
+		sauceless_size |= ascii(contents.substr(-38,1));
+
+		var data_type = ascii(contents.substr(-34,1));
+		var file_type = ascii(contents.substr(-33,1));
+		var tinfo1 = ascii(contents.substr(-31,1));
+		tinfo1 <<= 8;
+		tinfo1 |= ascii(contents.substr(-32,1));
+		var tinfo2 = ascii(contents.substr(-29,1));
+		tinfo2 <<= 8;
+		tinfo2 |= ascii(contents.substr(-30,1));
+		switch(data_type) {
+			case 1:
+				switch(file_type) {
+					case 0:	// PLain ASCII
+						ext = "TXT";
+						if (tinfo1)
+							width = tinfo1;
+						if (tinfo2)
+							height = tinfo2;
+						break;
+					case 1: // ANSi
+						ext = "ANS";
+						if (tinfo1)
+							width = tinfo1;
+						if (tinfo2)
+							height = tinfo2;
+						break;
+					case 7: // Source
+						ext = "TXT";
+						break;
+				}
+				valid_sauce = true;
+				break;
+			case 5:
+				ext = 'BIN';
+				width = file_type * 2;
+				height = (sauceless_size / 2) / width;
+				valid_sauce = true;
+				break;
+		}
+		if (valid_sauce)
+			contents = contents.substr(0, sauceless_size);
+	}
+
+	switch(ext) {
+	case "ANS":
+		/*
+		 * TODO: This doesn't do exactly what reading a text file does
+		 * one Windows (nor on Linux), but it should be close to what
+		 * was meant, and should work given that it resets x every line.
+		 */
+		var lines=contents.split(/\r\n/);
+
+		var attr = this.attr;
+		var bg = BG_BLACK;
+		var fg = LIGHTGRAY;
+
+		var i = 0;
+		var y = 0;
+		var saved = {};
+
+		while(lines.length > 0) {
+			var x = 0;
+			var line = lines.shift();
+			/* parse 'ATCODES'??
+			line = line.replace(/@(.*)@/g,
+				function (str, code, offset, s) {
+					return bbs.atcode(code);
+				}
+			);
+			*/
+			while(line.length > 0) {
+				/* parse an attribute sequence*/
+				var m = line.match(/^\x1b\[(\d*);?(\d*);?(\d*)m/);
+				if(m !== null) {
+					line = line.substr(m.shift().length);
+					if(m[0] == 0) {
+						bg = BG_BLACK;
+						fg = LIGHTGRAY;
+						i = 0;
+						m.shift();
+					}
+					if(m[0] == 1) {
+						i = HIGH;
+						m.shift();
+					}
+					if(m[0] >= 40) {
+						switch(Number(m.shift())) {
+						case 40:
+							bg = BG_BLACK;
+							break;
+						case 41:
+							bg = BG_RED;
+							break;
+						case 42:
+							bg = BG_GREEN;
+							break;
+						case 43:
+							bg = BG_BROWN;
+							break;
+						case 44:
+							bg = BG_BLUE;
+							break;
+						case 45:
+							bg = BG_MAGENTA;
+							break;
+						case 46:
+							bg = BG_CYAN;
+							break;
+						case 47:
+							bg = BG_LIGHTGRAY;
+							break;
+						}
+					}
+					if(m[0] >= 30) {
+						switch(Number(m.shift())) {
+						case 30:
+							fg = BLACK;
+							break;
+						case 31:
+							fg = RED;
+							break;
+						case 32:
+							fg = GREEN;
+							break;
+						case 33:
+							fg = BROWN;
+							break;
+						case 34:
+							fg = BLUE;
+							break;
+						case 35:
+							fg = MAGENTA;
+							break;
+						case 36:
+							fg = CYAN;
+							break;
+						case 37:
+							fg = LIGHTGRAY;
+							break;
+						}
+					}
+					attr = bg + fg + i;
+					continue;
+				}
+
+				/* parse absolute character position */
+				var m = line.match(/^\x1b\[(\d*);?(\d*)[Hf]/);
+				if(m !== null) {
+					line = line.substr(m.shift().length);
+
+					if(m.length==0) {
+						x=0;
+						y=0;
+					}
+					else {
+						if(m[0])
+							y = Number(m.shift())-1;
+						if(m[0])
+							x = Number(m.shift())-1;
+					}
+					continue;
+				}
+
+				/* ignore a bullshit sequence */
+				var n = line.match(/^\x1b\[\?7h/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					continue;
+				}
+
+				/* parse an up positional sequence */
+				var n = line.match(/^\x1b\[(\d*)A/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					var chars = n.shift();
+					if(chars < 1)
+						y-=1;
+					else
+						y-=Number(chars);
+					continue;
+				}
+
+				/* parse a down positional sequence */
+				var n = line.match(/^\x1b\[(\d*)B/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					var chars = n.shift();
+					if(chars < 1)
+						y+=1;
+					else
+						y+=Number(chars);
+					continue;
+				}
+
+				/* parse a forward positional sequence */
+				var n = line.match(/^\x1b\[(\d*)C/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					var chars = n.shift();
+					if(chars < 1)
+						x+=1;
+					else
+						x+=Number(chars);
+					continue;
+				}
+
+				/* parse a backward positional sequence */
+				var n = line.match(/^\x1b\[(\d*)D/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					var chars = n.shift()
+					if(chars < 1)
+						x-=1;
+					else
+						x-=Number(chars);
+					continue;
+				}
+
+				/* parse a clear screen sequence */
+				var n = line.match(/^\x1b\[2J/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					continue;
+				}
+
+				/* parse save cursor sequence */
+				var n = line.match(/^\x1b\[s/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					saved.x = x;
+					saved.y = y;
+					continue;
+				}
+
+				/* parse restore cursor sequence */
+				var n = line.match(/^\x1b\[u/);
+				if(n !== null) {
+					line = line.substr(n.shift().length);
+					x = saved.x;
+					y = saved.y;
+					continue;
+				}
+
+				/* set character and attribute */
+				var ch = line[0];
+				line = line.substr(1);
+
+				/* validate position */
+				if(y<0)
+					y=0;
+				if(x<0)
+					x=0;
+				if(x>=this.width) {
+					x=0;
+					y++;
+				}
+				/* set character and attribute */
+				if(!this.__properties__.data[y])
+					this.__properties__.data[y]=[];
+				this.__properties__.data[y][x]=new Char(ch,attr);
+				x++;
+			}
+			y++;
+		}
+		break;
+    case "BIN":
+        if (!this.load_bin(contents, width, height, 0)) return false;
+		break;
+	case "TXT":
+		var lines=contents.split(/\r\n/);
+		while(lines.length > 0)
+			this.putmsg(lines.shift() + "\r\n");
+		break;
+	default:
+		throw("unsupported filetype");
+		break;
+	}
+}
+Frame.prototype.load_bin = function(contents, width, height, offset) {
+    if(width == undefined || height == undefined)
+        throw("unknown graphic dimensions");
+    if(offset == undefined) offset = 0;
+    for(var y=0; y<height; y++) {
+        for(var x=0; x<width; x++) {
+            var c = new Char();
+            if(offset >= contents.length)
+                return(false);
+            c.ch = contents.substr(offset++, 1);
+            if(offset == contents.length)
+                return(false);
+            c.attr = ascii(contents.substr(offset++, 1));
+            c.id = this.id;
+            if(!this.__properties__.data[y])
+                this.__properties__.data[y]=[];
+            this.__properties__.data[y][x] = c;
+        }
+    }
+    return true;
+}
+Frame.prototype.scroll = function(x,y) {
+	var update = false;
+	/* default: add a new line to the data matrix */
+	if(x == undefined && y == undefined) {
+		if(this.__settings__.v_scroll) {
+			var newrow = [];
+			for(var x = 0;x<this.width;x++) {
+				for(var y = 0;y<this.height;y++)
+					this.__properties__.display.updateChar(this,x,y);
+				newrow.push(new Char());
+			}
+			this.__properties__.data.push(newrow);
+			this.__position__.offset.y++;
+			update = true;
+		}
+	}
+	/* otherwise, adjust the x/y offset */
+	else {
+		if(typeof x == "number" && x !== 0 && this.__settings__.h_scroll) {
+			this.__position__.offset.x += x;
+			update = true;
+		}
+		if(typeof y == "number" && y !== 0 && this.__settings__.v_scroll) {
+			this.__position__.offset.y += y;
+			update = true;
+		}
+		if(update)
+			this.refresh();
+	}
+	return update;
+}
+Frame.prototype.scrollTo = function(x,y) {
+	var update = false;
+	if(typeof x == "number") {
+		if(this.__settings__.h_scroll) {
+			this.__position__.offset.x = x;
+			update = true;
+		}
+	}
+	if(typeof y == "number") {
+		if(this.__settings__.v_scroll) {
+			this.__position__.offset.y = y;
+			update = true;
+		}
+	}
+	if(update)
+		this.refresh();
+}
+Frame.prototype.insertLine = function(y) {
+	if(this.__properties__.data[y])
+		this.__properties__.data.splice(y,0,[]);
+	else
+		this.__properties__.data[y] = [];
+	this.refresh();
+	return this.__properties__.data[y];
+}
+Frame.prototype.deleteLine = function(y) {
+	var l = undefined;
+	if(this.__properties__.data[y]) {
+		l = this.__properties__.data.splice(y,1);
+		this.refresh();
+	}
+	return l;
+}
+Frame.prototype.screenShot = function(file,append) {
+	return this.__properties__.display.screenShot(file,append);
+}
+Frame.prototype.invalidate = function() {
+	this.__properties__.display.invalidate();
+	this.refresh();
+}
+Frame.prototype.dump = function() {
+	return this.__properties__.display.dump();
+}
+
+/* console method emulation */
+Frame.prototype.home = function() {
+	this.__position__.cursor.x = 0;
+	this.__position__.cursor.y = 0;
+	return true;
+}
+Frame.prototype.end = function() {
+	this.__position__.cursor.x = this.width-1;
+	this.__position__.cursor.y = this.height-1;
+	return true;
+}
+Frame.prototype.pagedown = function() {
+	this.__position__.offset.y += this.height-1;
+	if(this.__position__.offset.y >= this.data_height)
+		this.__position__.offset.y = this.data_height - this.height;
+	this.refresh();
+}
+Frame.prototype.pageup = function() {
+	this.__position__.offset.y -= this.height-1;
+	if(this.__position__.offset.y < 0)
+		this.__position__.offset.y = 0;
+	this.refresh();
+}
+Frame.prototype.clear = function (attr) {
+	if (attr) this.attr = attr;
+	this.__properties__.data = [];
+	this.__position__.offset.x = 0;
+	this.__position__.offset.y = 0;
+	this.home();
+	this.invalidate();
+}
+Frame.prototype.erase = function(ch, attr) {
+	if(attr == undefined)
+		attr = this.attr;
+	var px = this.__position__.offset.x;
+	var py = this.__position__.offset.y;
+	for(var y = 0; y< this.height; y++) {
+		if(!this.__properties__.data[py + y]) {
+			continue;
+		}
+		for(var x = 0; x<this.width; x++) {
+			if(!this.__properties__.data[py + y][px + x]) {
+				continue;
+			}
+			if((this.__properties__.data[py + y][px + x].ch === undefined || 
+				this.__properties__.data[py + y][px + x].ch === ch) && 
+				this.__properties__.data[py + y][px + x].attr == attr) {
+				continue;
+			}
+			this.__properties__.data[py + y][px + x].ch = undefined;
+			this.__properties__.data[py + y][px + x].attr = attr;
+			this.__properties__.display.updateChar(this, x, y);
+		}
+	}
+	this.home();
+}
+Frame.prototype.clearline = function(attr) {
+	if(attr == undefined)
+		attr = this.attr;
+	var py = this.__position__.offset.y + this.__position__.cursor.y;
+	if(!this.__properties__.data[py])
+		return false;
+	for(var x=0;x<this.__properties__.data[py].length;x++) {
+		if(this.__properties__.data[py][x]) {
+			this.__properties__.data[py][x].ch = undefined;
+			this.__properties__.data[py][x].attr = attr;
+		}
+	}
+	for(var x=0;x<this.width;x++) {
+		this.__properties__.display.updateChar(this,x,this.__position__.cursor.y);
+	}
+}
+Frame.prototype.cleartoeol = function(attr) {
+	if(attr == undefined)
+		attr = this.attr;
+	var px = this.__position__.offset.x + this.__position__.cursor.x;
+	var py = this.__position__.offset.y + this.__position__.cursor.y;
+	if(!this.__properties__.data[this.__position__.cursor.y])
+		return false;
+	for(var x=px;x<this.__properties__.data[py].length;x++) {
+		if(this.__properties__.data[py][x]) {
+			if(this.__properties__.data[py][x].ch !== undefined) {
+				this.__properties__.data[py][x].ch = undefined;
+				this.__properties__.display.updateChar(this,x - this.__position__.offset.x, this.__position__.cursor.y);
+			}
+			this.__properties__.data[py][x].attr = attr;
+		}
+	}
+}
+Frame.prototype.crlf = function() {
+	this.__position__.cursor.x = 0;
+	if(this.__position__.cursor.y < this.height-1)
+		this.__position__.cursor.y += 1;
+	else {}
+}
+Frame.prototype.write = function(str,attr) {
+	if(str == undefined)
+		return;
+	if(this.__settings__.word_wrap)
+		str = word_wrap(str, this.width,str.length, false);
+	str = str.toString().split('');
+
+	if(attr)
+		this.__properties__.curr_attr = attr;
+	else
+		this.__properties__.curr_attr = this.attr;
+	var pos = this.__position__.cursor;
+	while(str.length > 0) {
+		var ch = str.shift();
+		this.__putChar__.call(this,ch,this.__properties__.curr_attr);
+		pos.x++;
+	}
+}
+Frame.prototype.putmsg = function(str,attr) {
+	if(str == undefined)
+		return;
+	if(this.__settings__.word_wrap) {
+		var remainingWidth = this.width - this.__position__.cursor.x;
+		if(str.length > remainingWidth) {
+			str = word_wrap(str,remainingWidth,str.length,false).split('\n');
+			var trailing_newline = str[str.length - 1].length == 0;
+			str = str.shift() + '\r\n' + word_wrap(str.join('\r\n'),this.width,remainingWidth,false);
+			if(!trailing_newline) str = str.trimRight();
+		}
+	}
+	str = str.toString().split('');
+
+	if(attr)
+		this.__properties__.curr_attr = attr;
+	else
+		this.__properties__.curr_attr = this.attr;
+	var pos = this.__position__.cursor;
+
+	while(str.length > 0) {
+		var ch = str.shift();
+		if(this.__properties__.ctrl_a) {
+			var k = ch;
+			if(k)
+				k = k.toUpperCase();
+			switch(k) {
+			case '\1':	/* A "real" ^A code */
+				this.__putChar__.call(this,ch,this.__properties__.curr_attr);
+				pos.x++;
+				break;
+			case 'K':	/* Black */
+				this.__properties__.curr_attr=(this.__properties__.curr_attr)&0xf8;
+				break;
+			case 'R':	/* Red */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|RED;
+				break;
+			case 'G':	/* Green */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|GREEN;
+				break;
+			case 'Y':	/* Yellow */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BROWN;
+				break;
+			case 'B':	/* Blue */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BLUE;
+				break;
+			case 'M':	/* Magenta */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|MAGENTA;
+				break;
+			case 'C':	/* Cyan */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|CYAN;
+				break;
+			case 'W':	/* White */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|LIGHTGRAY;
+				break;
+			case '0':	/* Black */
+				this.__properties__.curr_attr=(this.__properties__.curr_attr)&0x8f;
+				break;
+			case '1':	/* Red */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(RED<<4);
+				break;
+			case '2':	/* Green */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(GREEN<<4);
+				break;
+			case '3':	/* Yellow */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BROWN<<4);
+				break;
+			case '4':	/* Blue */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BLUE<<4);
+				break;
+			case '5':	/* Magenta */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(MAGENTA<<4);
+				break;
+			case '6':	/* Cyan */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(CYAN<<4);
+				break;
+			case '7':	/* White */
+				this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(LIGHTGRAY<<4);
+				break;
+			case 'H':	/* High Intensity */
+				this.__properties__.curr_attr|=HIGH;
+				break;
+			case 'I':	/* Blink */
+				this.__properties__.curr_attr|=BLINK;
+				break;
+			case 'N': 	/* Normal */
+				this.__properties__.curr_attr=(this.attr);
+				break;
+			case '+':
+				this.__properties__.attr_stack.push(this.__properties__.curr_attr);
+				break;
+			case '-':	/* Normal if High, Blink, or BG */
+				if (this.__properties__.attr_stack.length) {
+				  this.__properties__.curr_attr = this.__properties__.attr_stack.pop();
+				} else if(this.__properties__.curr_attr & 0xf8) {
+							this.__properties__.curr_attr=this.attr;
+				}
+				break;
+			case '_':	/* Normal if blink/background */
+				if(this.__properties__.curr_attr & 0xf0)
+					this.__properties__.curr_attr=this.attr;
+				break;
+			case '[':	/* CR */
+				pos.x=0;
+				break;
+			case ']':	/* LF */
+				pos.y++;
+				if(this.__settings__.lf_strict && pos.y >= this.height) {
+					this.scroll();
+					pos.y--;
+				}
+				break;
+			default:	/* Other stuff... specifically, check for right movement */
+				if(ch.charCodeAt(0)>127)
+					pos.x+=ch.charCodeAt(0)-127;
+				break;
+			}
+			this.__properties__.ctrl_a = false;
+		}
+		else {
+			switch(ch) {
+			case '\1':		/* CTRL-A code */
+			case ctrl('A'):
+				this.__properties__.ctrl_a = true;
+				break;
+			case '\7':		/* Beep */
+				break;
+			case '\x7f':	/* DELETE */
+				break;
+			case '\b':
+				if(pos.x > 0) {
+					pos.x--;
+					this.__putChar__.call(this," ",this.__properties__.curr_attr);
+				}
+				break;
+			case '\r':
+				pos.x=0;
+				break;
+			case '\t':
+				pos.x += 4;
+				break;
+			case '\n':
+				pos.y++;
+				if(this.__settings__.lf_strict && pos.y >= this.height) {
+					this.scroll();
+					pos.y--;
+				}
+				break;
+			default:
+				this.__putChar__.call(this,ch,this.__properties__.curr_attr);
+				pos.x++;
+				break;
+			}

+		}
+	}
+}
+Frame.prototype.center = function(str,attr) {
+	this.__position__.cursor.x = Math.ceil(this.width/2) - Math.ceil(console.strlen(strip_ctrl(str))/2);
+	if(this.__position__.cursor.x < 0)
+		this.__position__.cursor.x = 0;
+	this.putmsg(str,attr);
+}
+Frame.prototype.gotoxy = function(x,y) {
+	if(typeof x == "object" && x.x && x.y) {
+		if(this.__validateCursor__.call(this,x.x-1,x.y-1)) {
+			this.__position__.cursor.x = x.x-1;
+			this.__position__.cursor.y = x.y-1;
+			return true;
+		}
+		return false;
+	}
+	if(this.__validateCursor__.call(this,x-1,y-1)) {
+		this.__position__.cursor.x = x-1;
+		this.__position__.cursor.y = y-1;
+		return true;
+	}
+	return false;
+}
+Frame.prototype.getxy = function() {
+	return {
+		x:this.__position__.cursor.x+1,
+		y:this.__position__.cursor.y+1
+	};
+}
+Frame.prototype.pushxy = function() {
+	this.__position__.stored.x = this.__position__.cursor.x;
+	this.__position__.stored.y = this.__position__.cursor.y;
+}
+Frame.prototype.popxy = function() {
+	this.__position__.cursor.x = this.__position__.stored.x;
+	this.__position__.cursor.y = this.__position__.stored.y;
+}
+Frame.prototype.up = function(n) {
+	if(this.__position__.cursor.y == 0 && this.__position__.offset.y == 0)
+		return false;
+	if(isNaN(n))
+		n = 1;
+	while(n > 0) {
+		if(this.__position__.cursor.y > 0) {
+			this.__position__.cursor.y--;
+			n--;
+		}
+		else break;
+	}
+	if(n > 0)
+		this.scroll(0,-(n));
+	return true;
+}
+Frame.prototype.down = function(n) {
+	if(this.__position__.cursor.y == this.height-1 && this.__position__.offset.y == this.data_height - this.height)
+		return false;
+	if(isNaN(n))
+		n = 1;
+	while(n > 0) {
+		if(this.__position__.cursor.y < this.height - 1) {
+			this.__position__.cursor.y++;
+			n--;
+		}
+		else break;
+	}
+	if(n > 0)
+		this.scroll(0,n);
+	return true;
+}
+Frame.prototype.left = function(n) {
+	if(this.__position__.cursor.x == 0 && this.__position__.offset.x == 0)
+		return false;
+	if(isNaN(n))
+		n = 1;
+	while(n > 0) {
+		if(this.__position__.cursor.x > 0) {
+			this.__position__.cursor.x--;
+			n--;
+		}
+		else break;
+	}
+	if(n > 0)
+		this.scroll(-(n),0);
+	return true;
+}
+Frame.prototype.right = function(n) {
+	if(this.__position__.cursor.x == this.width-1 && this.__position__.offset.x == this.data_width - this.width)
+		return false;
+	if(isNaN(n))
+		n = 1;
+	while(n > 0) {
+		if(this.__position__.cursor.x < this.width - 1) {
+			this.__position__.cursor.x++;
+			n--;
+		}
+		else break;
+	}
+	if(n > 0)
+		this.scroll(n,0);
+	return true;
+}
+
+/* internal frame methods */
+Frame.prototype.__checkX__ = function(x) {
+	if(	isNaN(x) || (this.__settings__.checkbounds &&
+		(x > this.__properties__.display.x + this.__properties__.display.width ||
+		x < this.__properties__.display.x)))
+		return false;
+	return true;
+}
+Frame.prototype.__checkY__ = function(y) {
+	if( isNaN(y) || (this.__settings__.checkbounds &&
+		(y > this.__properties__.display.y + this.__properties__.display.height ||
+		y < this.__properties__.display.y)))
+		return false;
+	return true;
+}
+Frame.prototype.__checkWidth__ = function(x,width) {
+	if(	width < 1 || isNaN(width) || (this.__settings__.checkbounds &&
+		x + width > this.__properties__.display.x + this.__properties__.display.width))
+		return false;
+	return true;
+}
+Frame.prototype.__checkHeight__ = function(y,height) {
+	if( height < 1 || isNaN(height) || (this.__settings__.checkbounds &&
+		y + height > this.__properties__.display.y + this.__properties__.display.height))
+		return false;
+	return true;
+}
+Frame.prototype.__validateCursor__ = function(x,y) {
+	if(x >= this.width)
+		return false;
+	if(y >= this.height)
+		return false;
+	return true;
+}
+Frame.prototype.__putChar__ = function(ch,attr) {
+	if(this.__position__.cursor.x >= this.width) {
+		this.__position__.cursor.x=0;
+		this.__position__.cursor.y++;
+	}
+	if(this.__position__.cursor.y >= this.height) {
+		this.scroll();
+		this.__position__.cursor.y--;
+	}
+	this.setData(this.__position__.cursor.x,this.__position__.cursor.y,ch,attr,true);
+}
+
+Frame.prototype.__flip__ = function (swaps, flipx) {
+    for (var y = 0; y < this.data_height; y++) {
+        if (!Array.isArray(this.data[y])) continue;
+        if (flipx) this.data[y] = this.data[y].reverse();
+        for (var x = 0; x < this.data[y].length; x++) {
+            for (var s = 0; s < swaps.length; s++) {
+                var i = swaps[s].indexOf(ascii(this.data[y][x].ch));
+                if (i >= 0) {
+                    this.data[y][x].ch = ascii(swaps[s][(i + 1) % 2]);
+                    break;
+                }
+            }
+        }
+    }
+    if (!flipx) this.data.reverse();
+}
+
+Frame.prototype.flipX = function () {
+    const swaps = [
+        [ 40, 41 ],
+        [ 47, 92 ],
+        [ 60, 62 ],
+        [ 91, 93 ],
+        [ 123, 125 ],
+        [ 169, 170 ],
+        [ 174, 175 ],
+        [ 180, 195 ],
+        [ 181, 198 ],
+        [ 182, 199 ],
+        [ 183, 214 ],
+        [ 184, 213 ],
+        [ 185, 204 ],
+        [ 187, 201 ],
+        [ 188, 200 ],
+        [ 189, 211 ],
+        [ 190, 212 ],
+        [ 191, 218 ],
+        [ 192, 217 ],
+        [ 221, 222 ]
+    ];
+    this.__flip__(swaps, true);
+}
+
+Frame.prototype.flipY = function () {
+    const swaps = [
+        [ 47, 92 ],
+        [ 183, 189 ],
+        [ 184, 190 ],
+        [ 187, 188 ],
+        [ 191, 217 ],
+        [ 192, 218 ],
+        [ 193, 194 ],
+        [ 200, 201 ],
+        [ 202, 203 ],
+        [ 207, 209 ],
+        [ 208, 210 ],
+        [ 211, 214 ],
+        [ 212, 213 ],
+        [ 220, 223 ]
+    ];
+    this.__flip__(swaps, false);
+}
+
+/* frame reference object */
+function Canvas(frame,display) {
+	this.frame = frame;
+	this.display = display;
+	this.__defineGetter__("xoff",function() {
+		return this.frame.x - this.display.x;
+	});
+	this.__defineGetter__("yoff",function() {
+		return this.frame.y - this.display.y;
+	});
+}
+
+Canvas.prototype.hasData = function(x,y) {
+	var xpos = x-this.xoff;
+	var ypos = y-this.yoff;
+
+	if(xpos < 0 || ypos < 0)
+		return undefined;
+	if(xpos >= this.frame.width || ypos >= this.frame.height)
+		return undefined;
+	if(this.frame.transparent && this.frame.getData(xpos,ypos,true).ch == undefined)
+		return undefined;
+	return true;
+}
+
+/* object representing screen positional and dimensional limits and canvas stack */
+function Display(x,y,width,height) {
+	/* private properties */
+	this.__properties__ = {
+		x:undefined,
+		y:undefined,
+		width:undefined,
+		height:undefined,
+		canvas:{},
+		update:{},
+		buffer:{},
+		count:0
+	}
+
+	/* initialize display properties */
+	this.x = x;
+	this.y = y;
+	this.width = width;
+	this.height = height;
+	log(LOG_DEBUG,format("new display initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y));
+}
+
+/* protected display properties */
+Display.prototype.__defineGetter__("x", function() {
+	return this.__properties__.x;
+});
+Display.prototype.__defineSetter__("x", function(x) {
+	if(x == undefined)
+		this.__properties__.x = 1;
+	else if(isNaN(x))
+		throw("invalid x coordinate: " + x);
+	else
+		this.__properties__.x = Number(x);
+});
+Display.prototype.__defineGetter__("y", function() {
+	return this.__properties__.y;
+});
+Display.prototype.__defineSetter__("y", function(y) {
+	if(y == undefined)
+		this.__properties__.y = 1;
+	else if(isNaN(y) || y < 1 || y > console.screen_rows)
+		throw("invalid y coordinate: " + y);
+	else
+		this.__properties__.y = Number(y);
+});
+Display.prototype.__defineGetter__("width", function() {
+	return this.__properties__.width;
+});
+Display.prototype.__defineSetter__("width", function(width) {
+	if(width == undefined)
+		this.__properties__.width = console.screen_columns;
+	else if(isNaN(width) || (this.x + Number(width) - 1) > (console.screen_columns))
+		throw("invalid width: " + width);
+	else
+		this.__properties__.width = Number(width);
+});
+Display.prototype.__defineGetter__("height", function() {
+	return this.__properties__.height;
+});
+Display.prototype.__defineSetter__("height", function(height) {
+	if(height == undefined)
+		this.__properties__.height = console.screen_rows;
+	else if(isNaN(height) || (this.y + Number(height) - 1) > (console.screen_rows))
+		throw("invalid height: " + height);
+	else
+		this.__properties__.height = Number(height);
+});
+Display.prototype.__defineGetter__("nextID",function() {
+	return ++this.__properties__.count;
+});
+
+/* public display methods */
+Display.prototype.cycle = function() {
+	var updates = this.__getUpdateList__();
+	if(updates.length > 0) {
+		var lasty = undefined;
+		var lastx = undefined;
+		var lastf = undefined;
+		for each(var u in updates) {
+			if(lasty !== u.y || lastx == undefined || (u.x - lastx) !== 1)
+				console.gotoxy(u.px,u.py);
+			if(lastf !== u.id)
+				console.attributes = undefined;
+			this.__drawChar__(u.ch,u.attr,u.px,u.py);
+			lastx = u.x;
+			lasty = u.y;
+			lastf = u.id;
+		}
+		//console.attributes=undefined;
+		return true;
+	}
+	return false;
+}
+Display.prototype.draw = function() {
+	for(var y = 0;y<this.height;y++) {
+		for(var x = 0;x<this.width;x++) {
+			this.__updateChar__(x,y);
+		}
+	}
+	this.cycle();
+}
+Display.prototype.open = function(frame) {
+	var canvas = new Canvas(frame,this);
+	this.__properties__.canvas[frame.id] = canvas;
+	this.updateFrame(frame);
+}
+Display.prototype.close = function(frame) {
+	this.updateFrame(frame);
+	delete this.__properties__.canvas[frame.id];
+}
+Display.prototype.top = function(frame) {
+	var canvas = this.__properties__.canvas[frame.id];
+	delete this.__properties__.canvas[frame.id];
+	this.__properties__.canvas[frame.id] = canvas;
+	this.updateFrame(frame);
+}
+Display.prototype.bottom = function(frame) {
+	for(var c in this.__properties__.canvas) {
+		if(c == frame.id)
+			continue;
+		var canvas = this.__properties__.canvas[c];
+		delete this.__properties__.canvas[c];
+		this.__properties__.canvas[c] = canvas;
+	}
+	this.updateFrame(frame);
+}
+Display.prototype.updateFrame = function(frame) {
+	for(var y = 0;y<frame.height;y++) {
+		for(var x = 0;x<frame.width;x++) {
+			this.updateChar(frame,x,y/*,data*/);
+		}
+	}
+}
+Display.prototype.updateChar = function(frame,x,y) {
+	var xoff = frame.x - this.x;
+	var yoff = frame.y - this.y;
+	this.__updateChar__(xoff + x,yoff + y);
+}
+Display.prototype.screenShot = function(file,append) {
+	var f = new File(file);
+	if(append)
+		f.open('ab',true,4096);
+	else
+		f.open('wb',true,4096) ;
+	if(!f.is_open)
+		return false;
+
+	for(var y = 0;y<this.height;y++) {
+		for(var x = 0;x<this.width;x++) {
+			var c = this.__getTopCanvas__(x,y);
+			var d = this.__getData__(c,x,y);
+			if(d.ch)
+				f.write(d.ch);
+			else
+				f.write(" ");
+			if(d.attr)
+				f.writeBin(d.attr,1);
+			else
+				f.writeBin(0,1);
+		}
+	}
+
+	f.close();
+	return true;
+}
+Display.prototype.invalidate = function() {
+	this.__properties__.buffer = {};
+}
+Display.prototype.dump = function() {
+	var arr = [];
+	for(var y = 0; y < this.height; y++) {
+		arr[y] = [];
+		for(var x = 0; x < this.width; x++) {
+			var c = this.__getTopCanvas__(x, y);
+			if(typeof c == "undefined")
+				continue;
+			var d = this.__getData__(c, x, y);
+			if(typeof d.ch != "undefined")
+				arr[y][x] = d;
+		}
+	}
+	return arr;
+}
+
+/* private functions */
+Display.prototype.__updateChar__ = function(x,y/*,data*/) {
+	//ToDo: maybe store char update so I dont have to retrieve it later?
+	if(!this.__properties__.update[y])
+		this.__properties__.update[y] = {};
+	this.__properties__.update[y][x] = 1; /*data; */
+}
+
+Display.prototype.__getUpdateList__ = function() {
+	var list = [];
+	for(var y in this.__properties__.update) {
+		for(var x in this.__properties__.update[y]) {
+			var c = this.__getTopCanvas__(x,y);
+			var d = this.__getData__(c,x,y);
+
+			if(d.px < 1 ||  d.py < 1 || d.px > console.screen_columns || d.py > console.screen_rows)
+				continue;
+			if(!this.__properties__.buffer[x]) {
+				this.__properties__.buffer[x] = {};
+				this.__properties__.buffer[x][y] = d;
+				list.push(d);
+			}
+			else if(this.__properties__.buffer[x][y] == undefined ||
+				this.__properties__.buffer[x][y].ch != d.ch ||
+				this.__properties__.buffer[x][y].attr != d.attr) {
+				this.__properties__.buffer[x][y] = d;
+				list.push(d);
+			}
+		}
+	}
+	this.__properties__.update={};
+	return list.sort(function(a,b) {
+		if(a.y == b.y)
+			return a.x-b.x;
+		return a.y-b.y;
+	});
+}
+Display.prototype.__getData__ = function(c,x,y) {
+	var cd = {
+		x:Number(x),
+		y:Number(y),
+		px:Number(x) + this.__properties__.x,
+		py:Number(y) + this.__properties__.y
+	};
+	if(c) {
+		var d = c.frame.getData(x-c.xoff,y-c.yoff,true);
+		cd.id = c.frame.id;
+		cd.ch = d.ch;
+		if(d.attr)
+			cd.attr = d.attr;
+		else
+			cd.attr = c.frame.attr;
+	}
+	return cd;
+}
+Display.prototype.__drawChar__ = function(ch,attr,xpos,ypos) {
+	if(attr)
+		console.attributes = attr;
+	if(xpos == console.screen_columns && ypos == console.screen_rows)
+		console.cleartoeol();
+	else if(ch == undefined) {
+		console.write(" ");
+	}
+	else {
+		console.write(ch);
+	}
+}
+Display.prototype.__getTopCanvas__ = function(x,y) {
+	var top = undefined;
+	for each(var c in this.__properties__.canvas) {
+		if(c.frame.parent == undefined || c.hasData(x,y))
+			top = c;
+	}
+	return top;
+}
+
+/* character/attribute pair representing a screen position and its contents */
+function Char(ch,attr) {
+	this.ch = ch;
+	this.attr = attr;
+}
+
+/* self-validating cursor position object */
+function Cursor(x,y,frame) {
+	this.__properties__ = {
+		x:undefined,
+		y:undefined,
+		frame:undefined
+	}
+
+	if(frame instanceof Frame)
+		this.__properties__.frame = frame;
+	else
+		throw("the frame is not a frame");
+
+	this.x = x;
+	this.y = y;
+}
+
+Cursor.prototype.__defineGetter__("x", function() {
+	return this.__properties__.x;
+});
+Cursor.prototype.__defineSetter__("x", function(x) {
+	if(isNaN(x))
+		throw("invalid x coordinate: " + x);
+	this.__properties__.x = x;
+});
+Cursor.prototype.__defineGetter__("y", function() {
+	return this.__properties__.y;
+});
+Cursor.prototype.__defineSetter__("y", function(y) {
+	if(isNaN(y))
+		throw("invalid y coordinate: " + y);
+	this.__properties__.y = y;
+});
+
+
+
+/* self-validating scroll offset object */
+function Offset(x,y,frame) {
+	this.__properties__ = {
+		x:undefined,
+		y:undefined,
+		frame:undefined
+	}
+
+	if(frame instanceof Frame)
+		this.__properties__.frame = frame;
+	else
+		throw("the frame is not a frame");
+
+	this.x = x;
+	this.y = y;
+}
+
+Offset.prototype.__defineGetter__("x", function() {
+	return this.__properties__.x;
+});
+Offset.prototype.__defineSetter__("x", function(x) {
+	if(x == undefined)
+		throw("invalid x offset: " + x);
+	else if(x < 0)
+		x = 0;
+	this.__properties__.x = x;
+});
+Offset.prototype.__defineGetter__("y", function() {
+	return this.__properties__.y;
+});
+Offset.prototype.__defineSetter__("y", function(y) {
+	if(y == undefined)
+		throw("invalid y offset: " + y);
+	else if(y < 0)
+		y = 0;
+	this.__properties__.y = y;
+});