diff --git a/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml b/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml index 82b7f1b5b2..b626a410e2 100644 --- a/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml +++ b/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml @@ -17,4 +17,6 @@ + + diff --git a/disks/pcx86/compiled/library.xml b/disks/pcx86/compiled/library.xml index 5f3b37f16d..7411c127bb 100644 --- a/disks/pcx86/compiled/library.xml +++ b/disks/pcx86/compiled/library.xml @@ -423,6 +423,8 @@ WordStar for PCjr (Disk 1) WordStar for PCjr (Disk 2) WordStar for PCjr (Disk 3) + Sidekick 1.11C + Sidekick 1.56 Borland Turbo Pascal 3.00B Borland Turbo Pascal 3.01A IBM BASIC Compiler 1.00 (Disk 1) diff --git a/disks/pcx86/library.xml b/disks/pcx86/library.xml index 4851052318..02cfb6b346 100644 --- a/disks/pcx86/library.xml +++ b/disks/pcx86/library.xml @@ -83,6 +83,8 @@ + + diff --git a/disks/pcx86/minix/1.1/README.md b/disks/pcx86/minix/1.1/README.md index 80dce6bc7a..0460610af6 100644 --- a/disks/pcx86/minix/1.1/README.md +++ b/disks/pcx86/minix/1.1/README.md @@ -5,7 +5,7 @@ permalink: /disks/pcx86/minix/1.1/ --- MINIX 1.1 ---- +--------- The MINIX 1.1 Operating System was distributed on five 1.2M disk images: diff --git a/disks/pcx86/minix/README.md b/disks/pcx86/minix/README.md index 663c0bc9f0..24b7feae8b 100644 --- a/disks/pcx86/minix/README.md +++ b/disks/pcx86/minix/README.md @@ -5,8 +5,8 @@ permalink: /disks/pcx86/minix/ --- MINIX Operating Systems ---- +----------------------- -Information is available for the following versions of MINIX: +Information is available for the following versions of MINIX. * [MINIX 1.1](1.1/) diff --git a/disks/pcx86/tools/README.md b/disks/pcx86/tools/README.md index c6a7179ca7..fb75240b2e 100644 --- a/disks/pcx86/tools/README.md +++ b/disks/pcx86/tools/README.md @@ -7,7 +7,7 @@ permalink: /disks/pcx86/tools/ PC Tools -------- -Information is available for tools from these vendors: +Information is available for tools from these vendors. * [Borland](borland/) * [IBM](ibm/) diff --git a/disks/pcx86/tools/borland/README.md b/disks/pcx86/tools/borland/README.md index 5a7ad8c9be..3c41735b23 100644 --- a/disks/pcx86/tools/borland/README.md +++ b/disks/pcx86/tools/borland/README.md @@ -7,4 +7,5 @@ permalink: /disks/pcx86/tools/borland/ Borland Languages and Tools --------------------------- +* [Borland Sidekick](sidekick/) * [Borland Turbo Pascal](tpascal/) diff --git a/disks/pcx86/tools/borland/sidekick/1.11c/README.md b/disks/pcx86/tools/borland/sidekick/1.11c/README.md new file mode 100644 index 0000000000..6e46ff246a --- /dev/null +++ b/disks/pcx86/tools/borland/sidekick/1.11c/README.md @@ -0,0 +1,26 @@ +--- +layout: page +title: Borland Sidekick 1.11 +permalink: /disks/pcx86/tools/borland/sidekick/1.11c/ +--- + +Borland Sidekick 1.11 +--------------------- + +### Directory of Borland Sidekick 1.11C + + Volume in drive A has no label + Directory of A:\ + + SK COM 36318 1-26-85 12:19a + SKN COM 30968 1-26-85 12:19a + SKC COM 26286 1-26-85 12:19a + SKM COM 16401 1-26-85 12:19a + SKINST COM 44971 7-23-84 4:53p + SKINST MSG 3456 7-12-84 2:03p + READ ME! 2275 10-30-84 2:48p + DIAL DOC 980 10-17-84 11:25a + PHONE DIR 1156 9-02-84 4:59a + CHIP APP 589 1-04-85 10:43a + SK HLP 43520 10-09-84 4:21p + 11 File(s) 149504 bytes free diff --git a/disks/pcx86/tools/borland/sidekick/1.11c/manifest.xml b/disks/pcx86/tools/borland/sidekick/1.11c/manifest.xml new file mode 100644 index 0000000000..c9f3d4709f --- /dev/null +++ b/disks/pcx86/tools/borland/sidekick/1.11c/manifest.xml @@ -0,0 +1,25 @@ + + + + Sidekick + 1.11C + Utility + TSR + Borland + 1984 + 258608 + + WinWorld + CHIP.APP + DIAL.DOC + PHONE.DIR + READ.ME! + SK.COM + SK.HLP + SKC.COM + SKINST.COM + SKINST.MSG + SKM.COM + SKN.COM + + diff --git a/disks/pcx86/tools/borland/sidekick/1.56/README.md b/disks/pcx86/tools/borland/sidekick/1.56/README.md new file mode 100644 index 0000000000..298242338b --- /dev/null +++ b/disks/pcx86/tools/borland/sidekick/1.56/README.md @@ -0,0 +1,26 @@ +--- +layout: page +title: Borland Sidekick 1.56 +permalink: /disks/pcx86/tools/borland/sidekick/1.56/ +--- + +Borland Sidekick 1.56 +--------------------- + +### Directory of Borland Sidekick 1.56 + + Volume in drive A is SIDEKICK156 + Directory of A:\ + + README COM 16074 8-02-85 10:27a + SK COM 39515 8-06-85 1:57p + SKN COM 34009 8-06-85 1:57p + SKC COM 28049 8-06-85 1:57p + SKM COM 17642 8-06-85 1:57p + SK HLP 53632 8-02-85 12:40p + SKINST COM 54574 6-25-85 7:49p + SKINST MSG 4224 6-14-85 4:19p + NOTES 1647 9-02-84 4:58a + PHONE DIR 1358 5-03-85 2:01p + READ-ME SK 12928 8-14-85 11:10a + 11 File(s) 92160 bytes free diff --git a/disks/pcx86/tools/borland/sidekick/1.56/manifest.xml b/disks/pcx86/tools/borland/sidekick/1.56/manifest.xml new file mode 100644 index 0000000000..94e46dcbac --- /dev/null +++ b/disks/pcx86/tools/borland/sidekick/1.56/manifest.xml @@ -0,0 +1,25 @@ + + + + Sidekick + 1.56 + Utility + TSR + Borland + 1985 + + + WinWorld + NOTES + PHONE.DIR + READ-ME.SK + README.COM + SK.COM + SK.HLP + SKC.COM + SKINST.COM + SKINST.MSG + SKM.COM + SKN.COM + + diff --git a/disks/pcx86/tools/borland/sidekick/README.md b/disks/pcx86/tools/borland/sidekick/README.md new file mode 100644 index 0000000000..f8b124fe2e --- /dev/null +++ b/disks/pcx86/tools/borland/sidekick/README.md @@ -0,0 +1,11 @@ +--- +layout: page +title: Borland Sidekick +permalink: /disks/pcx86/tools/borland/sidekick/ +--- + +Borland Sidekick +---------------- + +* [Borland Sidekick 1.11C](1.11c/) +* [Borland Sidekick 1.56](1.56/) diff --git a/disks/pcx86/tools/other/README.md b/disks/pcx86/tools/other/README.md index fbefea3180..7f71960d42 100644 --- a/disks/pcx86/tools/other/README.md +++ b/disks/pcx86/tools/other/README.md @@ -7,7 +7,7 @@ permalink: /disks/pcx86/tools/other/ Other PC Tools -------------- -Information is available for these additional tools: +Information is available for these additional PC tools. * [DoubleDOS](doubledos/) * [Enhanced DEBUG](enhdebug/) diff --git a/disks/pcx86/tools/other/doubledos/README.md b/disks/pcx86/tools/other/doubledos/README.md index b788745d33..5e25b812ec 100644 --- a/disks/pcx86/tools/other/doubledos/README.md +++ b/disks/pcx86/tools/other/doubledos/README.md @@ -7,6 +7,6 @@ permalink: /disks/pcx86/tools/other/doubledos/ DoubleDOS --------- -PCjs has archived the following versions of DoubleDOS: +PCjs has archived the following versions of DoubleDOS. * [DoubleDOS 2.0V](2.0v/) diff --git a/disks/pcx86/tools/other/qemm386/README.md b/disks/pcx86/tools/other/qemm386/README.md index c0b52c8385..aa90474b84 100644 --- a/disks/pcx86/tools/other/qemm386/README.md +++ b/disks/pcx86/tools/other/qemm386/README.md @@ -7,7 +7,7 @@ permalink: /disks/pcx86/tools/other/qemm386/ QEMM-386 -------- -PCjs has archived the following versions of QEMM-386: +PCjs has archived the following versions of QEMM-386. * [QEMM-386 v4.10](4.10/) * [QEMM-386 v4.23](4.23/) diff --git a/modules/pcx86/lib/keyboard.js b/modules/pcx86/lib/keyboard.js index 6ee20c65a1..6b4f2ab892 100644 --- a/modules/pcx86/lib/keyboard.js +++ b/modules/pcx86/lib/keyboard.js @@ -75,8 +75,16 @@ class Keyboard extends Component { this.setModel(parmsKbd['model']); this.fMobile = Web.isMobile(); - this.fMSIE = Web.isUserAgent("MSIE"); this.printMessage("mobile keyboard support: " + (this.fMobile? "true" : "false")); + + /* + * This flag (formerly fMSIE, for all versions of Microsoft Internet Explorer, up to and including v11) + * has been expanded to include the Microsoft Edge browser, because while Microsoft did eliminate lots of + * incompatibilities with other browsers in Edge, the one that this component actually cares about (ie, + * whether the "lock" keys generate UP and DOWN events on every press, like IE historically did, or only + * DOWN on "lock" and UP on "unlock", like other browsers do) has been preserved by Edge. + */ + this.fMSIEorEdge = Web.isUserAgent("MSIE") || Web.isUserAgent("Edge"); /* * This is count of the number of "soft keyboard" keys present. At the moment, its only @@ -1219,7 +1227,7 @@ class Keyboard extends Component { else if (!fDown) { /* * In current webkit browsers, pressing and then releasing both left and right shift keys together - * (or both alt keys, or both cmd/windows keys, or presumably both ctrl keys) results in 4 events, as + * (or both ALT keys, or both CMD/Windows keys, or presumably both CTRL keys) results in 4 events, as * you would expect, but 3 of the 4 are "down" events; only the last of the 4 is an "up" event. * * Perhaps this is a browser accessibility feature (ie, deliberately suppressing the "up" event @@ -1255,6 +1263,7 @@ class Keyboard extends Component { * @this {Keyboard} * @param {number} simCode * @param {boolean} [fPress] + * @return {boolean} true if added, false if not (eg, not recognized, already added, etc) */ addActiveKey(simCode, fPress) { @@ -1264,13 +1273,13 @@ class Keyboard extends Component { if (!COMPILED && this.messageEnabled(Messages.KBD | Messages.KEY)) { this.printMessage("addActiveKey(" + simCode + "," + (fPress? "press" : "down") + "): unrecognized", true); } - return; + return false; } /* * Ignore all active keys if the CPU is not running. */ - if (!this.cpu || !this.cpu.isRunning()) return; + if (!this.cpu || !this.cpu.isRunning()) return false; /* * If this simCode is in the KEYSTATE table, then stop all repeating. @@ -1303,7 +1312,7 @@ class Keyboard extends Component { this.printMessage("addActiveKey(" + simCode + "," + (fPress? "press" : "down") + "): " + (i < 0? "already active" : (i == this.aKeysActive.length? "adding" : "updating")), true); } - if (i < 0) return; + if (i < 0) return false; if (i == this.aKeysActive.length) { key = {simCode}; // create a new Key object @@ -1320,6 +1329,7 @@ class Keyboard extends Component { key.nRepeat = (fPress? -1: (Keyboard.KEYSTATES[simCode]? 0 : 1)); this.updateActiveKey(key); + return true; } /** @@ -1585,16 +1595,16 @@ class Keyboard extends Component { if (keyCode == Keys.KEYCODE.CAPS_LOCK || keyCode == Keys.KEYCODE.NUM_LOCK || keyCode == Keys.KEYCODE.SCROLL_LOCK) { /* - * FYI, "lock" keys generate a "down" event ONLY when getting locked and an "up" event ONLY + * FYI, "lock" keys generate a DOWN event ONLY when getting locked and an UP event ONLY * when getting unlocked--which is a little odd, since the key did go UP and DOWN each time. * * We must treat each event like a "down", and also as a "press", so that addActiveKey() will * automatically generate both the "make" and "break". * - * Of course, there have to be exceptions, most notably MSIE, which sends both "up" and down" - * on every press, so there's no need for trickery. + * Of course, there have to be exceptions, most notably both Microsoft Internet Explorer and Edge, + * which send both UP and DOWN events on every press, so there's no need for trickery. */ - if (!this.fMSIE) { + if (!this.fMSIEorEdge) { fDown = fPress = true; } } @@ -1606,15 +1616,19 @@ class Keyboard extends Component { * for the ALT key, but not an UP event, leaving our machine with the impression that the ALT key * is still down, which the user user has no easy way to detect OR correct. * - * We still record the ALT state in bitsState as best we can, and clear it whenever we lose focus + * So we still record the ALT state in bitsState as best we can, and clear it whenever we lose focus * in onFocusChange(), but we no longer pass through DOWN events to our machine. Instead, we now * check bitsState prior to simulating any other key, and if the ALT bit is set, we simulate an * active ALT key first; you'll find that check at the end of both onKeyChange() and onKeyPress(). * + * However, one exception to this hack is the "Sidekick" exception: if the CTRL key is also down, + * we'll still simulate ALT immediately, for those users who press CTRL and then ALT to pop up Sidekick + * (as opposed to pressing ALT and then CTRL, which should also work, regardless). + * * NOTE: Even though this is a hack specifically for Windows, I'm doing it across the board, for all * platforms and browsers, for consistency. */ - if (keyCode == Keys.KEYCODE.ALT) { + if (keyCode == Keys.KEYCODE.ALT && !(this.bitsState & Keyboard.STATE.CTRL)) { fIgnore = fDown; // if an ALT key went down, then set fIgnore as well } @@ -1640,14 +1654,14 @@ class Keyboard extends Component { * leaving the current page. * * Regarding TAB: If I don't consume TAB on the "down" event, then that's all I'll see, because the browser - * act on it by giving focus to the next control. + * acts on it by giving focus to the next control. * * Regarding ESC: This key generates "down" and "up" events (LOTS of "down" events for that matter), but no * "press" event. */ /* - * HACK for simulating CTRL_BREAK using CTRL_DEL (Mac) or CTRL_BS (Windows) + * HACK for simulating CTRL-BREAK using CTRL-DEL (Mac) or CTRL-BS (Windows) */ if (keyCode == Keys.KEYCODE.BS && (this.bitsState & (Keyboard.STATE.CTRL|Keyboard.STATE.ALT)) == Keyboard.STATE.CTRL) { simCode = Keyboard.SIMCODE.CTRL_BREAK; @@ -1864,7 +1878,7 @@ class Keyboard extends Component { fSimulated = true; } - if (!COMPILED && this.messageEnabled(Messages.KEY)) { + if (!COMPILED && this.messageEnabled(Messages.KBD | Messages.KEY)) { this.printMessage("keySimulate(" + simCode + "," + (fDown? "down" : "up") + "): " + (fSimulated? "true" : "false"), true); } diff --git a/versions/pcx86/1.50.3/pcx86-uncompiled.js b/versions/pcx86/1.50.3/pcx86-uncompiled.js index 9ea1673c76..39b47cdf38 100644 --- a/versions/pcx86/1.50.3/pcx86-uncompiled.js +++ b/versions/pcx86/1.50.3/pcx86-uncompiled.js @@ -45040,8 +45040,16 @@ class Keyboard extends Component { this.setModel(parmsKbd['model']); this.fMobile = Web.isMobile(); - this.fMSIE = Web.isUserAgent("MSIE"); this.printMessage("mobile keyboard support: " + (this.fMobile? "true" : "false")); + + /* + * This flag (formerly fMSIE, for all versions of Microsoft Internet Explorer, up to and including v11) + * has been expanded to include the Microsoft Edge browser, because while Microsoft did eliminate lots of + * incompatibilities with other browsers in Edge, the one that this component actually cares about (ie, + * whether the "lock" keys generate UP and DOWN events on every press, like IE historically did, or only + * DOWN on "lock" and UP on "unlock", like other browsers do) has been preserved by Edge. + */ + this.fMSIEorEdge = Web.isUserAgent("MSIE") || Web.isUserAgent("Edge"); /* * This is count of the number of "soft keyboard" keys present. At the moment, its only @@ -46184,7 +46192,7 @@ class Keyboard extends Component { else if (!fDown) { /* * In current webkit browsers, pressing and then releasing both left and right shift keys together - * (or both alt keys, or both cmd/windows keys, or presumably both ctrl keys) results in 4 events, as + * (or both ALT keys, or both CMD/Windows keys, or presumably both CTRL keys) results in 4 events, as * you would expect, but 3 of the 4 are "down" events; only the last of the 4 is an "up" event. * * Perhaps this is a browser accessibility feature (ie, deliberately suppressing the "up" event @@ -46220,6 +46228,7 @@ class Keyboard extends Component { * @this {Keyboard} * @param {number} simCode * @param {boolean} [fPress] + * @return {boolean} true if added, false if not (eg, not recognized, already added, etc) */ addActiveKey(simCode, fPress) { @@ -46229,13 +46238,13 @@ class Keyboard extends Component { if (!COMPILED && this.messageEnabled(Messages.KBD | Messages.KEY)) { this.printMessage("addActiveKey(" + simCode + "," + (fPress? "press" : "down") + "): unrecognized", true); } - return; + return false; } /* * Ignore all active keys if the CPU is not running. */ - if (!this.cpu || !this.cpu.isRunning()) return; + if (!this.cpu || !this.cpu.isRunning()) return false; /* * If this simCode is in the KEYSTATE table, then stop all repeating. @@ -46268,7 +46277,7 @@ class Keyboard extends Component { this.printMessage("addActiveKey(" + simCode + "," + (fPress? "press" : "down") + "): " + (i < 0? "already active" : (i == this.aKeysActive.length? "adding" : "updating")), true); } - if (i < 0) return; + if (i < 0) return false; if (i == this.aKeysActive.length) { key = {simCode}; // create a new Key object @@ -46285,6 +46294,7 @@ class Keyboard extends Component { key.nRepeat = (fPress? -1: (Keyboard.KEYSTATES[simCode]? 0 : 1)); this.updateActiveKey(key); + return true; } /** @@ -46550,16 +46560,16 @@ class Keyboard extends Component { if (keyCode == Keys.KEYCODE.CAPS_LOCK || keyCode == Keys.KEYCODE.NUM_LOCK || keyCode == Keys.KEYCODE.SCROLL_LOCK) { /* - * FYI, "lock" keys generate a "down" event ONLY when getting locked and an "up" event ONLY + * FYI, "lock" keys generate a DOWN event ONLY when getting locked and an UP event ONLY * when getting unlocked--which is a little odd, since the key did go UP and DOWN each time. * * We must treat each event like a "down", and also as a "press", so that addActiveKey() will * automatically generate both the "make" and "break". * - * Of course, there have to be exceptions, most notably MSIE, which sends both "up" and down" - * on every press, so there's no need for trickery. + * Of course, there have to be exceptions, most notably both Microsoft Internet Explorer and Edge, + * which send both UP and DOWN events on every press, so there's no need for trickery. */ - if (!this.fMSIE) { + if (!this.fMSIEorEdge) { fDown = fPress = true; } } @@ -46571,15 +46581,19 @@ class Keyboard extends Component { * for the ALT key, but not an UP event, leaving our machine with the impression that the ALT key * is still down, which the user user has no easy way to detect OR correct. * - * We still record the ALT state in bitsState as best we can, and clear it whenever we lose focus + * So we still record the ALT state in bitsState as best we can, and clear it whenever we lose focus * in onFocusChange(), but we no longer pass through DOWN events to our machine. Instead, we now * check bitsState prior to simulating any other key, and if the ALT bit is set, we simulate an * active ALT key first; you'll find that check at the end of both onKeyChange() and onKeyPress(). * + * However, one exception to this hack is the "Sidekick" exception: if the CTRL key is also down, + * we'll still simulate ALT immediately, for those users who press CTRL and then ALT to pop up Sidekick + * (as opposed to pressing ALT and then CTRL, which should also work, regardless). + * * NOTE: Even though this is a hack specifically for Windows, I'm doing it across the board, for all * platforms and browsers, for consistency. */ - if (keyCode == Keys.KEYCODE.ALT) { + if (keyCode == Keys.KEYCODE.ALT && !(this.bitsState & Keyboard.STATE.CTRL)) { fIgnore = fDown; // if an ALT key went down, then set fIgnore as well } @@ -46605,14 +46619,14 @@ class Keyboard extends Component { * leaving the current page. * * Regarding TAB: If I don't consume TAB on the "down" event, then that's all I'll see, because the browser - * act on it by giving focus to the next control. + * acts on it by giving focus to the next control. * * Regarding ESC: This key generates "down" and "up" events (LOTS of "down" events for that matter), but no * "press" event. */ /* - * HACK for simulating CTRL_BREAK using CTRL_DEL (Mac) or CTRL_BS (Windows) + * HACK for simulating CTRL-BREAK using CTRL-DEL (Mac) or CTRL-BS (Windows) */ if (keyCode == Keys.KEYCODE.BS && (this.bitsState & (Keyboard.STATE.CTRL|Keyboard.STATE.ALT)) == Keyboard.STATE.CTRL) { simCode = Keyboard.SIMCODE.CTRL_BREAK; @@ -46829,7 +46843,7 @@ class Keyboard extends Component { fSimulated = true; } - if (!COMPILED && this.messageEnabled(Messages.KEY)) { + if (!COMPILED && this.messageEnabled(Messages.KBD | Messages.KEY)) { this.printMessage("keySimulate(" + simCode + "," + (fDown? "down" : "up") + "): " + (fSimulated? "true" : "false"), true); } diff --git a/versions/pcx86/1.50.3/pcx86.js b/versions/pcx86/1.50.3/pcx86.js index 2a3fd41cd1..4674e24529 100644 --- a/versions/pcx86/1.50.3/pcx86.js +++ b/versions/pcx86/1.50.3/pcx86.js @@ -115,14 +115,14 @@ function Bc(a,b,c){yb.call(this,"Bus",a);this.G=b;this.ca=c;this.I=a.busWidth||2 function Fc(a,b,c,d,e){for(var f=b,g=c,h=f>>>a.A;0g&&(q=g);if(k&&k.size){if(k.type==d&&k.controller==e){if(f+g<=k.xa)return k.ve+=k.xa-f,k.xa=f,!0;if(f>=k.xa+k.ve){q=k.size-(f-m);q>g&&(q=g);k.ve=f-k.xa+q;f=m+a.B;g-=q;h++;continue}}return Gc(a,Hc,f,g)}f=new w(f,q,a.B,d,e);Cc(f,a.ca,k);a.Z[h++]=f;f=m+a.B;g-=q}return 0>=g?(Ic(a.G),a.G.flags.Wa||(e=c/1024|0,a.status((e?e+"Kb ":c+" bytes ")+Jc[d]+" at "+p(b))),!0):Gc(a,Kc,b,c)} function Dc(a,b){32==a.I?b?a.J&&(Lc(a,1048576,1048576,a.J),a.J=null):a.J||(a.J=Mc(a,1048576,1048576),Lc(a,1048576,1048576,Mc(a,0,1048576))):20>>this.A;0>>a.A;0>>=a.A;0>>a.A;0>>a.A].zc(b&a.D,b)}function $c(a,b){var c=b&a.D,d=(b&a.C)>>>a.A;return c!=a.D?a.Z[d].Hf(c,b):a.Z[d++].Yd(c,b)|a.Z[d&a.P].Yd(0,b+1)<<8} +function Lc(a,b,c,d,e){for(var f=0,g=b>>>a.A;0>>a.A].zc(b&a.D,b)}function $c(a,b){var c=b&a.D,d=(b&a.C)>>>a.A;return c!=a.D?a.Z[d].Hf(c,b):a.Z[d++].Yd(c,b)|a.Z[d&a.P].Yd(0,b+1)<<8} function ad(a,b,c){a.Z[(b&a.C)>>>a.A].Bc(b&a.D,c&255,b)}function bd(a,b,c){var d=b&a.D,e=(b&a.C)>>>a.A;d!=a.D?a.Z[e].si(d,c&65535,b):(a.Z[e++].Ye(d,c&255,b),a.Z[e&a.P].Ye(0,c>>8&255,b+1))} function cd(a,b){var c=0,d=[],e=!a.J&&a.N==a.C;e||Dc(a,!0);for(var f=0;f>>=f;if(void 0!==g){if(g[0])g[0](b,k,e);a.ca&&a.L!=g[1]&&kd(a.ca,b,k)}else a.ca&&(ec(a.ca,a,b,k,e),a.L&&kd(a.ca,b,k));f+=h<<3;b+=h;c-=h}}function Gc(a,b,c,d,e){b="Memory block error ("+b+": "+p(c)+","+p(d)+")";e?a.ca?a.ca.message(b):a.log(b):tb(b);return!1}var sc,ld={On:20,count:8,mq:1,type:3},md=0,nd;for(nd in ld){var od=ld[nd];ld[nd]={Gn:(1<>1),this.W=new Int32Array(this.I,0,c>>2),Pc(this,Tc?Uc:Xc);else{this.W=Array(c>>2);for(e=0;e>1),this.W=new Int32Array(this.I,0,c>>2),Pc(this,Tc?Uc:Vc);else{this.W=Array(c>>2);for(e=0;e>2),a=0;a>8):this.Qd[a>>1]=b;this.nb.W[this.A]|=32;this.ob.W[this.B]|=96;this.Mf=this.jl;this.sd.Ma=!0};l.Gp=function(a,b){this.H.setInt32(a,b,!0);this.Ma=!0};l.il=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.W[a>>2]=b;this.Ma=!0}; l.Jp=function(a,b){a&3?(this.Pa[a]=b,this.Pa[a+1]=b>>8,this.Pa[a+2]=b>>16,this.Pa[a+3]=b>>24):this.W[a>>2]=b;this.nb.W[this.A]|=32;this.ob.W[this.B]|=96;this.Lf=this.il;this.sd.Ma=!0};function Kd(a){gc&&!Tc&&(a=a<<24|a<<8&16711680|a>>8&65280|a>>>24);return a} var sd=0,Sc=2,td=5,zd=6,Jc="NONE RAM ROM VIDEO H/W UNPAGED PAGED".split(" "),rd=0,Bd=[],Yc=[w.prototype.Zo,w.prototype.Cp,w.prototype.lp,w.prototype.Op,w.prototype.fp,w.prototype.Ip],Ed=[w.prototype.Yo,w.prototype.Bp,w.prototype.kp,w.prototype.Np,w.prototype.ep,w.prototype.Hp],Ad=[w.prototype.ap,w.prototype.Ep,w.prototype.np,w.prototype.Qp,w.prototype.hp,w.prototype.Kp],ud=[w.prototype.bp,w.prototype.Fp,w.prototype.op,w.prototype.Rp,w.prototype.ip,w.prototype.Lp]; -if(gc)var Xc=[w.prototype.Xo,w.prototype.Ap,w.prototype.jp,w.prototype.Mp,w.prototype.cp,w.prototype.Gp],Uc=[w.prototype.Vk,w.prototype.hl,w.prototype.$k,w.prototype.jl,w.prototype.Xk,w.prototype.il],Ld=[w.prototype.$o,w.prototype.Dp,w.prototype.mp,w.prototype.Pp,w.prototype.gp,w.prototype.Jp]; +if(gc)var Vc=[w.prototype.Xo,w.prototype.Ap,w.prototype.jp,w.prototype.Mp,w.prototype.cp,w.prototype.Gp],Uc=[w.prototype.Vk,w.prototype.hl,w.prototype.$k,w.prototype.jl,w.prototype.Xk,w.prototype.il],Ld=[w.prototype.$o,w.prototype.Dp,w.prototype.mp,w.prototype.Pp,w.prototype.gp,w.prototype.Jp]; function Md(a,b){yb.call(this,"CPU",a,268435457);b=a.cycles||b;var c=a.multiplier||1;this.Y={};this.Y.Vd=b;this.Y.oh=Math.round(1E3/Nd);this.Y.Jj=this.Y.sh=this.Y.xd=c;this.Y.ng=Math.round(this.Y.Vd/1E4)/100;this.Y.Hd=this.Y.me=this.Y.ng*this.Y.xd;this.flags.oi=this.flags.Wa=this.flags.Nf=!1;this.flags.ee=a.autoStart;this.flags.lj=!1;this.flags.Ge=!1;this.Y.rf=this.Y.Pe=0;this.Y.tf=a.csStart;this.Y.Oe=a.csInterval;this.Y.Qe=a.csStop;this.pa=[];this.Od=0;this.Dj=this.pp.bind(this)}ja(Md,yb);l=Md.prototype; l.qc=function(a,b,c,d){this.ma=a;this.na=b;this.ca=d;for(b=0;b>>0)+1,a.mb=a.A.mb,a.type=a.A.type,a.ext=a.A.ext,a.Lb=a.A.Lb,a.A.T=-1,pe(a,!0,!0,!1),a.ta;a.A.T=-1;var f=e.ia(b+0),g=e.ia(b+4),h=g&7936,k=e.ia(b+2)|(g&255)<<16,m=e.ia(b+6),q=c&65528;if(80386<=e.ba){var u=f;k|=(m&65280)<<16;f|=(m&15)<<16;m&128&&(f=f<<12|4095)}switch(a.id){case ye:var v=a.C;a.Ke=!1;if(v&&c==re&&a.B.length){var A=a.B[a.Ef-1];if(A&&!A())return-1}var I=c&3,G=(g&24576)>>13;A=-1;q||b>=e.Hb&& b=a.zb&&(I>a.zb&&(A=ze(e),Ae(e,ze(e),!0),Be(e,A),a.Ke=!0),A=0);else{if(256==h||2304==h)return Ce(a,c,v)?a.ta:-1;if(1024==h){A=2;var H=0;Ih||2048==(h&2560))return x.call(e,13,c&65532),-1;if(!(g&32768))return x.call(e,11,c&65532),-1}break;case ue:if(!q||4096>h||512!=(h&2560))return x.call(e,13,c&65532),-1;if(!(g&32768))return x.call(e, +Ge(e,e.Ka.T,4,2),e.Ka.load(0),Ge(e,e.Ia.T,4,2),e.Ia.load(0),Ge(e,e.Da.T,4,2),He(e,0),Ge(e,e.oa.T,4,2),Ie(e,0));Ge(e,v,e.X,2);for(Je(e,g);f;)Je(e,a.G[--f]);a.Ke=!0}return a.ta}}if(0!=A)return x.call(e,13,c&65532|(Q?2:0)),-1;if(!(g&32768))return x.call(e,11,c&65532|(Q?2:0)),-1;break;case Oe:if(q){if(4096>h||2048==(h&2560))return x.call(e,13,c&65532),-1;if(!(g&32768))return x.call(e,11,c&65532),-1}break;case ue:if(!q||4096>h||512!=(h&2560))return x.call(e,13,c&65532),-1;if(!(g&32768))return x.call(e, 12,c&65532),-1;break;case we:v=h&-513;if(!q||256!=v&&2304!=v)return x.call(e,13,c&65532),-1;2304==v&&(a.Ui=k+e.ia(k+102)|0,a.Rl=k+a.Qa|0);break;case te:if(!(h&4096)&&768>>0)+1,a.mb=g,a.type=h,a.ext=m,a.Lb=b,pe(a,!0,!0,!1));return k} function Ce(a,b,c){var d=a.Ib,e=a.zb,f=d.ja.T,g=d.ja.ta;if(!c){if(!(d.ja.type&512))return x.call(d,13,b&65532),!1;d.gb(d.ja.Lb+4,d.ja.mb&=-513)}if(-1===d.ja.load(b))return!1;var h=d.ja.ta;if(!1!==c){if(d.ja.type&512)return x.call(d,13,b&65532),!1;d.gb(d.ja.Lb+4,d.ja.mb|=512)}d.ja.type=d.ja.type&-513|d.ja.mb&512;256==d.ja.type||768==d.ja.type?(d.gb(g+14,z(d)),d.gb(g+16,Pe(d)),d.gb(g+18,d.D),d.gb(g+20,d.I),d.gb(g+22,d.L),d.gb(g+24,d.H),d.gb(g+26,y(d)),d.gb(g+28,d.N),d.gb(g+30,d.K),d.gb(g+32,d.J),d.gb(g+ 34,d.oa.T),d.gb(g+36,d.aa.T),d.gb(g+38,d.U.T),d.gb(g+40,d.Da.T),d.Db.load(d.ia(h+42)),Qe(d,d.ia(h+16)|(c?16384:0)),d.D=d.ia(h+18),d.I=d.ia(h+20),d.L=d.ia(h+22),d.H=d.ia(h+24),d.N=d.ia(h+28),d.K=d.ia(h+30),d.J=d.ia(h+32),d.oa.load(d.ia(h+34)),d.Da.load(d.ia(h+40)),Re(d,d.ia(h+14),d.ia(h+36)),b=38,g=26,a.zb>8),a.M&=-9):a.gb(c,b)}} function ah(a,b){a.M&2||a.bb(a.Ga.Rb(a.sb,4),b)}function Fe(a,b,c){c=b.yb(c,a.X);a.M&8?(b=Se(a,c)|Se(a,b.yb(0,1))<<8,a.M&=-9):b=a.lb(c);return b}function bh(a,b,c,d){c=b.Rb(c,a.X);a.M&8?(Te(a,c,d),Te(a,b.Rb(0,1),d>>8),a.M&=-9):a.bc(c,d)}l.ra=function(){var a=vg(this,1),b=Se(this,this.da);this.da=a;return b};function ch(a){var b=vg(a,2);if(a.M&8){var c=Se(a,a.da)|Se(a,b-1)<<8;a.M&=-9}else c=a.ia(a.da);a.da=b;return c} function J(a){var b=vg(a,a.Kc);if(a.M&8){var c=Se(a,a.da)|Se(a,b-1)<<8;a.M&=-9}else c=a.Sb(a.da);a.da=b;return c}l.Fa=function(){var a=vg(this,this.X);if(this.M&8){var b=Se(this,this.da)|Se(this,a-1)<<8;this.M&=-9}else b=this.lb(this.da);this.da=a;return b};l.S=function(){var a=vg(this,1),b=Se(this,this.da)<<24>>24;this.da=a;return b}; -function ze(a){var b=a.lb(a.Aa);a.Aa=a.Aa+a.X|0;var c=(a.Sg>>>0)-(a.Aa>>>0);0>c&&(8088>=a.ba?(Be(a,a.Aa-a.U.ta&a.U.wa),-1>c&&(b=b&255|Se(a,a.Aa-1)<<8)):-1>c?x.call(a,12,0):(!a.U.cd&&a.U.Qa==a.U.wa||a.U.cd&&!a.U.Qa)&&Be(a,a.Aa-a.U.ta&a.U.wa));return b}function Ne(a,b){Ge(a,b,a.X)} +function ze(a){var b=a.lb(a.Aa);a.Aa=a.Aa+a.X|0;var c=(a.Sg>>>0)-(a.Aa>>>0);0>c&&(8088>=a.ba?(Be(a,a.Aa-a.U.ta&a.U.wa),-1>c&&(b=b&255|Se(a,a.Aa-1)<<8)):-1>c?x.call(a,12,0):(!a.U.cd&&a.U.Qa==a.U.wa||a.U.cd&&!a.U.Qa)&&Be(a,a.Aa-a.U.ta&a.U.wa));return b}function Je(a,b){Ge(a,b,a.X)} function Ge(a,b,c,d){d=void 0===d?c:d;var e=a.Aa-c|0,f=(e>>>0)-(a.Ej>>>0);if(0>f){if(8088>=a.ba&&-1==f){Te(a,e+1,b>>8);Be(a,e-a.U.ta&a.U.wa);Te(a,a.Aa,b);return}if(!a.U.cd&&a.U.Qa==a.U.wa||a.U.cd&&!a.U.Qa){if(f>-c){x.call(a,12,0);return}Be(a,e-a.U.ta&a.U.wa);e=a.Aa}else{x.call(a,12,0);return}}switch(d){case 1:Te(a,e,b);break;case 2:a.gb(e,b);break;case 4:a.bb(e,b)}a.Aa=e} function dh(a,b,c){var d=4;1==b.length&&(d=1,c=c?1:0);if(80386>a.ba)2this.ba?0:1;for(var e=0;2>e;e++){switch(d){case 0:if(this.Ta&1&&this.P&512){var f=fh(this.V);if(-1<=f&&(this.Ta&=-2,0<= @@ -259,7 +259,7 @@ a)}this.A-=this.B.ci}return b}function qk(a,b){a^=b;Eg(this,a,128);this.A-=-1=== function uk(a,b,c){c>>>=0;if(!c||c<=b>>>0)return!1;var d=0,e=1,f=this.An;f[0]=c>>>0;f[1]=0;c=this.Hn;c[0]=a>>>0;for(c[1]=b>>>0;0>>=0,a[1]++),e+=e;do 0<=sk(c,f)&&(a=c,b=f,a[0]-=b[0],a[1]-=b[1],0>a[0]&&(a[0]>>>=0,a[1]--),d+=e),a=f,a[0]>>>=1,a[1]&1&&(a[0]=(a[0]|2147483648)>>>0),a[1]>>>=1,e/=2;while(1<=e);this.Ca=d;this.ab=c[0];return!0}function vk(a){var b=a+1|0;xg(this,a,1,b,this.Eb|62);this.A-=2;return a&~this.R|b&this.R} function wk(a){this.qa=a;De(this);this.qa&-2147483648?Mf(this):Pf(this)}function xk(a){this.M|=1;this.rc.call(this,a);this.A-=-1===this.C?4:5}function Xj(a,b,c){if(c){16>>16-c)&65535;Eg(this,a,32768,d&32768)}return a}function Zj(a,b,c){if(c){var d=a<>>32-c;Eg(this,a,-2147483648,d&-2147483648)}return a}function dk(a,b,c){if(c){16>>c-1;a=(d>>>1|b<<16-c)&65535;Eg(this,a,32768,d&1)}return a} function fk(a,b,c){if(c){var d=a>>>c-1;a=d>>>1|b<<32-c;Eg(this,a,-2147483648,d&1)}return a}function yk(){this.A-=-1===this.C?2:this.B.Jk;return 1}function zk(){var a=this.I&255;this.A-=(-1===this.C?this.B.Vh:this.B.Uh)+(a<this.Ic?(-1!=this.md&&(this.md!==this.aa.T&&(this.aa.zb=this.md&3,tg(this,this.md)),this.md=-1),this.nd!==this.da&&ug(this,this.nd),-1!=this.Jc&&(this.Jc!==this.U.T&&Ae(this,this.Jc),this.Jc=-1),-1!==this.sa&&(this.sa!==this.Aa&&Be(this,this.xc&~this.U.wa|this.sa-this.U.ta),this.sa=-1)):8!=this.Ic?(b=0,a=8):(b=0,a=-1,Kf(this),e=d=!1)):ug(this,this.nd);var f=a,g=b,h=268435504,k=Tg(this,this.da);204!=k||this.Yc||(d=!1);this.P& 131072&&(6==f&&99==k||13==f&&205==k)&&(d=!1);!1===d&&(h|=268435457);983040<=this.da&&1048575>=this.da&&(d=!1);r(this,h|-2130706432)&&(d=!0);if(r(this,h)||d){var m=this.flags.Wa;f="Fault "+Da(f)+(null!=g?" ("+Ea(g)+")":"")+" on opcode "+Da(k);d&&m&&(f+=" (blocked)");this.ca?(dc(this,f,d||h,!0),d&&(d=m,Ik(this.ca))):(this.Ba(f),Ud(this))}if(d&&e)throw-1;if(e)throw this.Ic=a,Ek.call(this,a,b,c),this.nd=this.da,this.M=1==a?this.M|8192:this.M|4096,a;} @@ -350,9 +350,9 @@ this.S());this.F=this.C;break;case 66:d=F(this,this.L+this.S());this.F=this.C;br this.F=this.C;break;case 131:d=F(this,this.H+J(this));this.F=this.C;break;case 132:d=F(this,Jk.call(this,2)+J(this));this.F=this.C;break;case 133:d=Yg(this,this.N+J(this));this.F=this.C;break;case 134:d=F(this,this.K+J(this));this.F=this.C;break;case 135:d=F(this,this.J+J(this));this.F=this.C;break;case 192:d=this.D;break;case 193:d=this.I;break;case 194:d=this.L;break;case 195:d=this.H;break;case 196:d=y(this);break;case 197:d=this.N;break;case 198:d=this.K;break;case 199:d=this.J;break;default:d= 0}a=a[this.ea>>3&7].call(this,d,b.call(this));switch(c){case 192:this.D=a;break;case 193:this.I=a;break;case 194:this.L=a;break;case 195:this.H=a;break;case 196:Be(this,a);break;case 197:this.N=a;break;case 198:this.K=a;break;case 199:this.J=a;break;default:ah(this,a)}} function Jk(a){var b=this.ra(),c=b>>6;switch(b>>3&7){case 0:var d=this.D;break;case 1:d=this.I;break;case 2:d=this.L;break;case 3:d=this.H;break;case 4:d=0;break;case 5:d=this.N;break;case 6:d=this.K;break;case 7:d=this.J}switch(b&7){case 0:var e=this.D;break;case 1:e=this.I;break;case 2:e=this.L;break;case 3:e=this.H;break;case 4:e=y(this);this.Ea=this.Pb;break;case 5:a?(e=this.N,this.Ea=this.Pb):e=J(this);break;case 6:e=this.K;break;case 7:e=this.J}return(d<>8&255;if(9<(b&15)||Ag(this)){b+=6;80286<=this.ba&&255>8&255;if(9<(b&15)||Ag(this)){b=b-6&15;c=c-1&255;var d=a=1}else d=a=0;this.D=this.D&-65536|c<<8|b;d?Fg(this):Gg(this);a?Pg(this):Lg(this);this.A-=this.B.pe},function(){this.D=vk.call(this,this.D)},function(){this.I=vk.call(this,this.I)},function(){this.L=vk.call(this,this.L)},function(){this.H=vk.call(this, -this.H)},function(){Be(this,vk.call(this,y(this)))},function(){this.N=vk.call(this,this.N)},function(){this.K=vk.call(this,this.K)},function(){this.J=vk.call(this,this.J)},function(){this.D=tk.call(this,this.D)},function(){this.I=tk.call(this,this.I)},function(){this.L=tk.call(this,this.L)},function(){this.H=tk.call(this,this.H)},function(){Be(this,tk.call(this,y(this)))},function(){this.N=tk.call(this,this.N)},function(){this.K=tk.call(this,this.K)},function(){this.J=tk.call(this,this.J)},function(){Ne(this, -this.D&this.R);this.A-=this.B.Fc},function(){Ne(this,this.I&this.R);this.A-=this.B.Fc},function(){Ne(this,this.L&this.R);this.A-=this.B.Fc},function(){Ne(this,this.H&this.R);this.A-=this.B.Fc},function(){Ne(this,y(this)-2&65535);this.A-=this.B.Fc},function(){Ne(this,this.N&this.R);this.A-=this.B.Fc},function(){Ne(this,this.K&this.R);this.A-=this.B.Fc},function(){Ne(this,this.J&this.R);this.A-=this.B.Fc},function(){this.D=this.D&~this.R|ze(this);this.A-=this.B.kc},function(){this.I=this.I&~this.R| +this.H)},function(){Be(this,vk.call(this,y(this)))},function(){this.N=vk.call(this,this.N)},function(){this.K=vk.call(this,this.K)},function(){this.J=vk.call(this,this.J)},function(){this.D=tk.call(this,this.D)},function(){this.I=tk.call(this,this.I)},function(){this.L=tk.call(this,this.L)},function(){this.H=tk.call(this,this.H)},function(){Be(this,tk.call(this,y(this)))},function(){this.N=tk.call(this,this.N)},function(){this.K=tk.call(this,this.K)},function(){this.J=tk.call(this,this.J)},function(){Je(this, +this.D&this.R);this.A-=this.B.Fc},function(){Je(this,this.I&this.R);this.A-=this.B.Fc},function(){Je(this,this.L&this.R);this.A-=this.B.Fc},function(){Je(this,this.H&this.R);this.A-=this.B.Fc},function(){Je(this,y(this)-2&65535);this.A-=this.B.Fc},function(){Je(this,this.N&this.R);this.A-=this.B.Fc},function(){Je(this,this.K&this.R);this.A-=this.B.Fc},function(){Je(this,this.J&this.R);this.A-=this.B.Fc},function(){this.D=this.D&~this.R|ze(this);this.A-=this.B.kc},function(){this.I=this.I&~this.R| ze(this);this.A-=this.B.kc},function(){this.L=this.L&~this.R|ze(this);this.A-=this.B.kc},function(){this.H=this.H&~this.R|ze(this);this.A-=this.B.kc},function(){Be(this,y(this)&~this.R|ze(this));this.A-=this.B.kc},function(){this.N=this.N&~this.R|ze(this);this.A-=this.B.kc},function(){this.K=this.K&~this.R|ze(this);this.A-=this.B.kc},function(){this.J=this.J&~this.R|ze(this);this.A-=this.B.kc},Kk,Lk,Mk,Nk,Ok,Pk,Qk,Rk,Sk,Tk,Uk,Vk,Wk,Xk,Yk,Zk,Kk,Lk,Mk,Nk,Ok,Pk,Qk,Rk,Sk,Tk,Uk,Vk,Wk,Xk,Yk,Zk,$k,function(){this.kb.call(this, ol,this.Fa);this.A-=-1===this.F?1:this.B.ug},$k,function(){this.kb.call(this,ol,this.S);this.A-=-1===this.F?1:this.B.ug},function(){this.rc.call(this,kk)},function(){this.za.call(this,lk)},function(){this.hc.call(this,ok)},function(){this.ka.call(this,pk)},bl,cl,dl,el,function(){this.M|=1;this.za.call(this,tj)},function(){this.M|=1;this.Ea=this.Pb=this.Ig;this.ka.call(this,jj)},function(){this.ka.call(this,sj);switch(this.ea>>3&7){case 0:var a=this.D;this.D=this.ya;Ie(this,a);break;case 1:a=this.I; this.I=this.ya;tg(this,a);break;case 2:a=this.L;this.L=this.ya;Ae(this,a);break;case 3:a=this.H;this.H=this.ya;He(this,a);break;case 4:a=y(this);Be(this,this.ya);80386<=this.ba?this.Ia.load(a):Ie(this,a);break;case 5:a=this.N;this.N=this.ya;80386<=this.ba?this.Ka.load(a):tg(this,a);break;case 6:a=this.K;this.K=this.ya;Ae(this,a);break;case 7:a=this.J,this.J=this.ya,He(this,a)}},function(){this.M|=1;this.sa=this.Aa;this.ya=ze(this);this.kb.call(this,pl,Ck);this.sa=-1},function(){this.A-=3},function(){var a= this.D;this.D=this.D&~this.R|this.I&this.R;this.I=this.I&~this.R|a&this.R;this.A-=3},function(){var a=this.D;this.D=this.D&~this.R|this.L&this.R;this.L=this.L&~this.R|a&this.R;this.A-=3},function(){var a=this.D;this.D=this.D&~this.R|this.H&this.R;this.H=this.H&~this.R|a&this.R;this.A-=3},function(){var a=this.D,b=y(this);this.D=this.D&~this.R|b&this.R;Be(this,b&~this.R|a&this.R);this.A-=3},function(){var a=this.D;this.D=this.D&~this.R|this.N&this.R;this.N=this.N&~this.R|a&this.R;this.A-=3},function(){var a= this.D;this.D=this.D&~this.R|this.K&this.R;this.K=this.K&~this.R|a&this.R;this.A-=3},function(){var a=this.D;this.D=this.D&~this.R|this.J&this.R;this.J=this.J&~this.R|a&this.R;this.A-=3},function(){this.D=2==this.X?this.D&-65536|this.D<<24>>24&65535:this.D<<16>>16;this.A-=2},function(){this.L=2==this.X?this.L&-65536|(this.D&32768?65535:0):this.D&-2147483648?-1:0;this.A-=this.B.Oj},function(){Dk.call(this,this.Fa(),ch(this));this.A-=this.B.Rj},function(){this.A-=3},function(){var a=Pe(this);a&131072&& -3>this.ic?x.call(this,13,0):(Ne(this,a&-196609),this.A-=this.B.Fc)},function(){if(this.P&131072&&3>this.ic)x.call(this,13,0);else{var a=ze(this);a=a&65535|this.P&-65536;Qe(this,a);this.A-=this.B.kc}},function(){var a=this.D>>8&255;a&1?Fg(this):Gg(this);a&4?(this.resultType&=-3,this.P|=4):(this.resultType&=-3,this.P&=-5);a&16?Pg(this):Lg(this);a&64?Qg(this):Mg(this);a&128?(this.resultType&=-17,this.P|=128):(this.resultType&=-17,this.P&=-129);this.A-=this.B.Vb},function(){this.D=this.D&-65281|(Pe(this)& +3>this.ic?x.call(this,13,0):(Je(this,a&-196609),this.A-=this.B.Fc)},function(){if(this.P&131072&&3>this.ic)x.call(this,13,0);else{var a=ze(this);a=a&65535|this.P&-65536;Qe(this,a);this.A-=this.B.kc}},function(){var a=this.D>>8&255;a&1?Fg(this):Gg(this);a&4?(this.resultType&=-3,this.P|=4):(this.resultType&=-3,this.P&=-5);a&16?Pg(this):Lg(this);a&64?Qg(this):Mg(this);a&128?(this.resultType&=-17,this.P|=128):(this.resultType&=-17,this.P&=-129);this.A-=this.B.Vb},function(){this.D=this.D&-65281|(Pe(this)& 213)<<8;this.A-=this.B.Vb},function(){var a=this.D&-256;var b=J(this);b=Se(this,this.Ea.yb(b,1));this.D=a|b;this.A-=this.B.Kh},function(){this.D=this.D&~this.R|Fe(this,this.Ea,J(this));this.A-=this.B.Kh},function(){var a=J(this),b=this.D;Te(this,this.Ea.Rb(a,1),b);this.A-=this.B.Lh},function(){bh(this,this.Ea,J(this),this.D);this.A-=this.B.Lh},function(){var a=1,b=0,c=this.wa,d=this.B.Mh;this.ga&192&&(a=this.I&c,b=1,d=this.B.Oh,this.ga&256||(this.A-=this.B.Nh));if(a--){var e=Se(this,this.Ea.yb(this.K& c,1));Te(this,this.oa.Rb(this.J&c,1),e);e=this.P&1024?-1:1;this.K=this.K&~c|this.K+e&c;this.J=this.J&~c|this.J+e&c;this.A-=d;this.I=this.I&~c|this.I-b&c;a&&(wg(this),this.M|=256)}},function(){var a=1,b=0,c=this.wa,d=this.B.Mh;this.ga&192&&(a=this.I&c,b=1,d=this.B.Oh,this.ga&256||(this.A-=this.B.Nh));if(a--){bh(this,this.oa,this.J&c,Fe(this,this.Ea,this.K&c));var e=this.P&1024?-this.X:this.X;this.K=this.K&~c|this.K+e&c;this.J=this.J&~c|this.J+e&c;this.A-=d;this.I=this.I&~c|this.I-b&c;a&&(wg(this), this.M|=256)}},function(){var a=1,b=0,c=this.wa,d=this.B.xh;this.ga&192&&(a=this.I&c,b=1,d=this.B.zh,this.ga&256||(this.A-=this.B.yh));if(a--){var e=Ug(this,this.Ea,this.K),f=Ug(this,this.oa,this.J);this.F=this.C;Xi.call(this,e,f);e=this.P&1024?-1:1;this.K=this.K&~c|this.K+e&c;this.J=this.J&~c|this.J+e&c;this.I=this.I&~c|this.I-b&c;this.A-=d-this.B.Jb;a&&Bg(this)==(this.ga&64)&&(wg(this),this.M|=256)}},function(){var a=1,b=0,c=this.wa,d=this.B.xh;this.ga&192&&(a=this.I&c,b=1,d=this.B.zh,this.ga&256|| @@ -395,7 +395,7 @@ this.L&-256|this.ra();this.A-=this.B.Vb},function(){this.H=this.H&-256|this.ra() He(this,h);this.Ia.load(k);this.Ka.load(m)}null!=Re(this,b,c,!1)&&(Qe(this,d,a),this.Uf&&og(this,this.da))}this.Jc=this.sa=-1}},function(){this.jd.call(this,fl,yk)},function(){this.kb.call(this,2==this.X?gl:hl,yk)},function(){this.jd.call(this,fl,zk)},function(){this.kb.call(this,2==this.X?gl:hl,zk)},function(){var a=this.ra();if(a){var b=this.D&255;this.D=this.D&-65536|b/a<<8|b%a;Eg(this,this.D,128);this.A-=this.B.Lj}else Hk.call(this)},function(){var a=this.D&255,b=(this.D>>8&255)*this.ra()|0,c= a+b|0;this.D=this.D&-65536|c&255;xg(this,a,b,c,191);this.A-=this.B.Kj},function(){this.D=this.D&-256|(yg(this)?255:0);this.A-=2},function(){this.D=this.D&-256|Ug(this,this.Ea,this.H+(this.D&255));this.A-=this.B.Kk},function(){ml.call(this,216)},function(){ml.call(this,217)},function(){ml.call(this,218)},function(){ml.call(this,219)},function(){ml.call(this,220)},function(){ml.call(this,221)},function(){ml.call(this,222)},function(){ml.call(this,223)},function(){var a=this.S(),b=this.I-1&this.wa;this.I= this.I&~this.wa|b;b&&!Bg(this)?(C(this,z(this)+a),this.A-=this.B.rk):this.A-=this.B.Hh},function(){var a=this.S(),b=this.I-1&this.wa;this.I=this.I&~this.wa|b;b&&Bg(this)?(C(this,z(this)+a),this.A-=this.B.Ih):this.A-=this.B.Jh},function(){var a=this.S(),b=this.I-1&this.wa;this.I=this.I&~this.wa|b;b?(C(this,z(this)+a),this.A-=this.B.qk):this.A-=this.B.Hh},function(){var a=this.S();this.I&this.wa?this.A-=this.B.Jh:(C(this,z(this)+a),this.A-=this.B.Ih)},function(){var a=this.ra();Sg(this,a,1,!0)&&(this.D= -this.D&-256|fd(this.na,a,1,this.da-2)&255,this.A-=this.B.Ch)},function(){var a=this.ra();Sg(this,a,this.X,!0)&&(this.D=this.D&~this.R|fd(this.na,a,this.X,this.da-2)&this.R,this.A-=this.B.Ch)},function(){var a=this.ra();Sg(this,a,1,!1)&&(jd(this.na,a,1,this.D&255,this.da-2),this.A-=this.B.Qh)},function(){var a=this.ra();Sg(this,a,this.X,!1)&&(jd(this.na,a,this.X,this.D&this.R,this.da-2),this.A-=this.B.Qh)},function(){var a=this.Fa(),b=z(this);a=b+a;Ne(this,b);C(this,a);this.A-=this.B.Pj},function(){var a= +this.D&-256|fd(this.na,a,1,this.da-2)&255,this.A-=this.B.Ch)},function(){var a=this.ra();Sg(this,a,this.X,!0)&&(this.D=this.D&~this.R|fd(this.na,a,this.X,this.da-2)&this.R,this.A-=this.B.Ch)},function(){var a=this.ra();Sg(this,a,1,!1)&&(jd(this.na,a,1,this.D&255,this.da-2),this.A-=this.B.Qh)},function(){var a=this.ra();Sg(this,a,this.X,!1)&&(jd(this.na,a,this.X,this.D&this.R,this.da-2),this.A-=this.B.Qh)},function(){var a=this.Fa(),b=z(this);a=b+a;Je(this,b);C(this,a);this.A-=this.B.Pj},function(){var a= this.Fa();C(this,z(this)+a);this.A-=this.B.Dh},function(){Re(this,this.Fa(),ch(this));this.A-=this.B.lk},function(){var a=this.S();C(this,z(this)+a);this.A-=this.B.Dh},function(){var a=this.L&65535;Sg(this,a,1,!0)&&(this.D=this.D&-256|fd(this.na,a,1,this.da-1)&255,this.A-=this.B.Bh)},function(){var a=this.L&65535;Sg(this,a,this.X,!0)&&(this.D=this.D&~this.R|fd(this.na,a,this.X,this.da-1)&this.R,this.A-=this.B.Bh)},function(){var a=this.L&65535;Sg(this,a,1,!1)&&(jd(this.na,a,1,this.D&255,this.da-1), this.A-=this.B.Ph)},function(){var a=this.L&65535;Sg(this,a,2,!1)&&(jd(this.na,a,this.X,this.D&this.R,this.da-1),this.A-=this.B.Ph)},nl,nl,function(){this.M|=132;this.A-=this.B.Xc},function(){this.M|=68;this.A-=this.B.Xc},function(){this.P&131072?x.call(this,13,0):(this.Ta|=4,this.A-=2,this.ca&&r(this,-2130706432)?(wg(this),Ik(this.ca)):this.P&512||(this.ca&&wg(this),Ud(this)))},function(){yg(this)?Gg(this):Fg(this);this.A-=2},function(){this.sc=!1;this.jd.call(this,sl,Bk);this.sc&&(this.D=this.D& ~this.R|this.Ca&this.R)},function(){this.sc=!1;this.kb.call(this,tl,Bk);this.sc&&(this.D=this.D&~this.R|this.Ca&this.R,this.L=this.L&~this.R|this.ab&this.R)},function(){Gg(this);this.A-=2},function(){Fg(this);this.A-=2},function(){this.Na>this.ic?x.call(this,13,0):(this.P&=-513,this.A-=this.B.Nj)},function(){this.Na>this.ic?x.call(this,13,0):(this.P|=512,this.M|=4,this.A-=2)},function(){this.P&=-1025;this.A-=2},function(){this.P|=1024;this.A-=2},function(){this.jd.call(this,We,Bk)},function(){this.kb.call(this, @@ -410,8 +410,8 @@ if(d!=d<<24>>24||8086==this.ba&&-128==d)return Hk.call(this),a;this.Ca=d&255|(b% this.Ca=b&65535,this.ab=b>>16&65535):(gj.call(this,a,this.D),80563==this.Nd&&68657152==this.D&&129==a&&(this.ab=0));this.ab?(Fg(this),Hg(this)):(Gg(this),Ig(this));this.sc=!0;this.A-=-1===this.C?this.B.Ak:this.B.zk;this.M|=2;return a},function(a,b){2==this.X?(b=this.D&65535,b=(b<<16>>16)*(a<<16>>16)|0,this.Ca=b&65535,this.ab=b>>16&65535,b=32767b):(fj.call(this,a,this.D),b=this.ab!=this.Ca>>31);b?(Fg(this),Hg(this)):(Gg(this),Ig(this));this.sc=!0;this.A-=-1===this.C?this.B.ek:this.B.dk; this.M|=2;return a},function(a,b){if(2==this.X){if(!a)return Hk.call(this),a;b=65536*(this.L&65535)+(this.D&65535);var c=b/a;if(65536<=c)return Hk.call(this),a;this.Ca=c&65535;this.ab=b%a&65535}else{if(!uk.call(this,this.D,this.L,a))return Hk.call(this),a;this.Ca|=0;this.ab|=0}this.sc=!0;this.A-=-1===this.C?this.B.Xj:this.B.Wj;this.M|=2;return a},function(a,b){if(2==this.X){if(!a)return Hk.call(this),a;var c=a<<16>>16,d=(b=this.L<<16|this.D&65535)/c|0;if(d!=d<<16>>16||8086==this.ba&&-32768==d)return Hk.call(this), a;this.Ca=d&65535;this.ab=b%c&65535}else{b=this.D;c=this.L;d=a;var e=0,f=0;0>d&&(d=-d|0,e=1-e);0>c&&(b=-b|0,c=~c+(b?0:1)|0,f=1,e=1-e);!uk.call(this,b,c,d)||this.Ca>2147483647+e||this.ab>2147483647+f?b=!1:(e&&(this.Ca=-this.Ca),f&&(this.ab=-this.ab),b=!0);if(!b)return Hk.call(this),a;this.Ca|=0;this.ab|=0}this.sc=!0;this.A-=-1===this.C?this.B.ak:this.B.$j;this.M|=2;return a}],We=[function(a){var b=a+1|0;xg(this,a,1,b,190);this.A-=-1===this.C?this.B.xf:this.B.wf;return b&255},function(a){var b=a-1| -0;xg(this,a,1,b,190,!0);this.A-=-1===this.C?this.B.xf:this.B.wf;return b&255},aj,aj,aj,aj,aj,aj],Xe=[function(a){var b=a+1|0;xg(this,a,1,b,this.Eb|62);this.A-=-1===this.C?this.B.xf:this.B.wf;return b&this.R},function(a){var b=a-1|0;xg(this,a,1,b,this.Eb|62,!0);this.A-=-1===this.C?this.B.xf:this.B.wf;return b&this.R},function(a){Ne(this,z(this));C(this,a);this.A-=-1===this.C?this.B.Tj:this.B.Sj;this.M|=2;return a},function(a,b){if(-1===this.C)return aj.call(this,a,b);this.sa=this.Aa;Dk.call(this,a, -this.ia(this.C+this.X));this.A-=this.B.Qj;this.M|=2;this.sa=-1;return a},function(a){C(this,a);this.A-=-1===this.C?this.B.nk:this.B.mk;this.M|=2;return a},function(a,b){if(-1===this.C)return aj.call(this,a,b);Re(this,a,this.ia(this.C+this.X));this.A-=this.B.kk;this.M|=2;return a},function(a){var b=a;this.M&512&&(a=a-2&65535,80286>this.ba&&(b=a));Ne(this,b);this.A-=-1===this.C?this.B.Fc:this.B.Ek;this.M|=2;return a},aj];function Df(){this.ka.call(this,nk);this.A-=-1===this.C?6:13} +0;xg(this,a,1,b,190,!0);this.A-=-1===this.C?this.B.xf:this.B.wf;return b&255},aj,aj,aj,aj,aj,aj],Xe=[function(a){var b=a+1|0;xg(this,a,1,b,this.Eb|62);this.A-=-1===this.C?this.B.xf:this.B.wf;return b&this.R},function(a){var b=a-1|0;xg(this,a,1,b,this.Eb|62,!0);this.A-=-1===this.C?this.B.xf:this.B.wf;return b&this.R},function(a){Je(this,z(this));C(this,a);this.A-=-1===this.C?this.B.Tj:this.B.Sj;this.M|=2;return a},function(a,b){if(-1===this.C)return aj.call(this,a,b);this.sa=this.Aa;Dk.call(this,a, +this.ia(this.C+this.X));this.A-=this.B.Qj;this.M|=2;this.sa=-1;return a},function(a){C(this,a);this.A-=-1===this.C?this.B.nk:this.B.mk;this.M|=2;return a},function(a,b){if(-1===this.C)return aj.call(this,a,b);Re(this,a,this.ia(this.C+this.X));this.A-=this.B.kk;this.M|=2;return a},function(a){var b=a;this.M&512&&(a=a-2&65535,80286>this.ba&&(b=a));Je(this,b);this.A-=-1===this.C?this.B.Fc:this.B.Ek;this.M|=2;return a},aj];function Df(){this.ka.call(this,nk);this.A-=-1===this.C?6:13} function Jf(){this.za.call(this,mk);this.A-=-1===this.C?12:19}var vf=Array(256);vf[0]=function(){16>(Se(this,this.da)&56)&&(this.M|=1);this.kb.call(this,this.sj,Bk)};vf[1]=function(){Se(this,this.da)&16||(this.M|=1);this.kb.call(this,ul,Bk)};vf[2]=function(){!(this.qa&1)||this.P&131072?x.call(this,6):this.ka.call(this,hj)};vf[3]=function(){!(this.qa&1)||this.P&131072?x.call(this,6):this.ka.call(this,nj)}; vf[5]=function(){this.Na?x.call(this,13,0,0,!0):(Rg(this,this.ia(2054)),this.J=this.ia(2086),this.K=this.ia(2088),this.N=this.ia(2090),this.H=this.ia(2094),this.L=this.ia(2096),this.I=this.ia(2098),this.D=this.ia(2100),xe(this.oa,2102,this.ia(2084)),xe(this.aa,2108,this.ia(2082)),xe(this.U,2114,this.ia(2080)),xe(this.Da,2120,this.ia(2078)),Qe(this,this.ia(2072)),C(this,this.ia(2074)),Be(this,this.ia(2092)),this.Hb=this.ia(2126)|Se(this,2128)<<16,this.Gc=this.Hb+this.ia(2130),this.Mb=this.ia(2138)| Se(this,2140)<<16,this.Yc=this.Mb+this.ia(2142),xe(this.Db,2132,this.ia(2076)),xe(this.ja,2144,this.ia(2070)),this.A-=195)};vf[6]=function(){this.Na?x.call(this,13,0):(this.qa&=-9,this.A-=2)};vf[11]=Ze;vf[166]=Ze;vf[255]=Ze;var B=[];B[5]=Ze; @@ -516,7 +516,7 @@ l.reset=function(){if(!this.B&&!this.D&&this.V){var a=1024*sm(this.V);this.A&&a! l.save=function(){var a=new rg(this);this.controller&&a.set(0,this.controller.save());return a.data()};l.restore=function(a){return this.controller?this.controller.restore(a[0]):!0};function Wq(a){this.H=a;this.G=Yq;this.F=Zq;this.C=$q;this.B=null}ja(Wq,Ac);Wq.prototype.save=function(){return[this.G,this.C]};Wq.prototype.restore=function(a){ar(this,0,a[0]&255);ar(this,2,a[1]&255);return!0}; function ar(a,b,c){if(b)2==b&&(a.C=a.C&-256|c);else if(c!=(a.G&255)){b=a.H.na;if(c&br)a.B&&(Lc(b,cr,dr,a.B),a.B=null);else{a.B||(a.B=Mc(b,cr,dr));var d=Mc(b,er,dr);Lc(b,cr,dr,d,c&fr?1:Sc)}a.G=a.G&-256|c}}Wq.prototype.kg=function(){return gr};Wq.prototype.D=function(){return hr}; var Xq=-2134900736,er=16646144,cr=917504,dr=131072,br=1,fr=2,Yq=65535,Zq=2575,$q=2,hr=[null,0],gr=[function(a){var b=this.controller,c=255;2>a?c=a&1?b.F>>8:b.F&255:4>a&&(c=a&1?b.C>>8:b.C&255);return c},null,null,function(a,b){ar(this.controller,a,b)},null,null];pb(function(){for(var a=Jb(document,"pcx86","ram"),b=0;bb.gd&&(b.jf=!1),Jr(a,b.te,b.jf)&&b.gd){if(0>b.gd){if(!b.jf){wr(a,b.te);return}var d=a.ka}else d=1==b.gd++?500:100;b.Kf&&clearTimeout(b.Kf);b.Kf=setTimeout(function(a){return function(){Hr(a,b,d)}}(a),d)}}else wr(a,b.te,!0)}function Kr(a,b,c){var d=b;if(b>=n.be&&b<=n.Xf)!(a.A&(Lr|Mr|Nr))==c&&(d=b+(n.Ed-n.be));else if(b>=n.Ed&&b<=n.z)!!(a.A&(Lr|Mr|Nr))==c&&(d=b-(n.Ed-n.be));else if(!!(a.A&(Lr|Mr))==c){if(a=xa[b])d=a}else if(a=wa[b])d=a;return d} l.xg=function(a){a||(this.A&=~Fr,Ir(this))}; -function kr(a,b,c){var d=!0,e=!1,f=!1,g=b.keyCode;if(!lr(a.ma))return!1;a.C="";Qb(a.ae);var h=Kr(a,g,!0);a.eh&&h==n["`"]&&(g=h=27);if(N[g+1E3])if(h+=1E3,2==b.location&&(h+=2E3),ur(a,h,!1,c)){if(20==g||144==g||145==g)a.ja||(c=e=!0);18==g&&(f=c);c||91!=g&&93!=g||Ir(a)}else 8==g&&(a.A&(Or|Pr))==Or&&(h=Qr),d=!1;else if(N[h]&&a.A&(Rr|nr)&&(d=!1),d&&c||a.A&Sr)f=!0;d||b.preventDefault();f||a.da&&d||(c?(a.A&nr&&(dc(a,"onKeyChange("+or+"): simulating ALT down",1073741828),pr(a,or)),pr(a,h,e)):wr(a,h)||(b= -Kr(a,g,!1),b!=h&&wr(a,b)));return d} +function kr(a,b,c){var d=!0,e=!1,f=!1,g=b.keyCode;if(!lr(a.ma))return!1;a.C="";Qb(a.ae);var h=Kr(a,g,!0);a.eh&&h==n["`"]&&(g=h=27);if(N[g+1E3])if(h+=1E3,2==b.location&&(h+=2E3),ur(a,h,!1,c)){if(20==g||144==g||145==g)a.ja||(c=e=!0);18!=g||a.A&Or||(f=c);c||91!=g&&93!=g||Ir(a)}else 8==g&&(a.A&(Or|Pr))==Or&&(h=Qr),d=!1;else if(N[h]&&a.A&(Rr|nr)&&(d=!1),d&&c||a.A&Sr)f=!0;d||b.preventDefault();f||a.da&&d||(c?(a.A&nr&&(dc(a,"onKeyChange("+or+"): simulating ALT down",1073741828),pr(a,or)),pr(a,h,e)):wr(a, +h)||(b=Kr(a,g,!1),b!=h&&wr(a,b)));return d} function Jr(a,b,c){var d=!1;ur(a,b,!0,c);var e=N[b]||N[b+1E3];if(void 0!==e){e!=Tr&&e!=Ur||(a.A&(Or|Pr))!=(Or|Pr)||(e=Vr);d=[];var f=e&255;if(83=n.be&&b<=n.Xf||b>=n.Ed&&b<=n.z;e>>>=8;){var g=0,h=e&255;f==Xr||f==Yr?d.push(f|(c?0:Wr)):(h==O?a.D&(Lr|Mr)||a.D&Nr&&b||(g=h):h==Zr?a.D&(Or|$r)||(g=h):h==as?a.D&(Pr|bs)||(g=h):d.push(f|(c?0:Wr)),g&&(c?d.unshift(g):d.push(g|Wr)))}for(c=0;c>>20;Ua.Vi=a.G.tc+Ua.Lk;Ua.nb=Ff.Z[(Ua.Vi&Ff.C)>>>Ff.A];Ua.Fj=Ua.nb.Kd(Ua.Lk);Ua.Mk=(hh&4190208)>>>10;Ua.Wi=(Ua.Fj&-4096)+Ua.Mk;Ua.ob=Ff.Z[(Ua.Wi&Ff.C)>>>Ff.A];Ua.Gj=Ua.ob.Kd(Ua.Mk);Ua.Sl=(Ua.Gj&-4096)+(hh&4095)}if(Ef=Ua){a.O("linear PDE addr PDE PTE addr PTE physical");a.O("--------- ---------- -------- ---------- -------- ----------");var wj="%"+p(hh);wj+=" %%"+OA(Ef.Vi,Ef.Fj);wj+=" %%"+ -OA(Ef.Wi,Ef.Gj,!0);wj+=" %%"+p(Ef.Sl);a.O(wj)}else a.O("unsupported operation")}}else a.O("missing address")}else{if("d"==sa){if("disk"==ib){ba[0]="l";ba[1]="json";XC(a,ba);break a}for(Bb in kc)if(ba[1]==Bb){var lu=a.Da[Bb];lu?(ba.shift(),ba.shift(),lu(ba)):a.O("no dump registered for "+ib);break a}ib||(sa=a.kb||"db")}if("dh"==sa){var mu=ib,nu=Vb,ih=Mb;ih=void 0===ih?"history":ih;var ou="",pu=0,Vc=a.pa,vd=a.U;if(vd.length){var Ec=+mu||a.Oa,jh=+nu||10;isNaN(Ec)?Ec=jh:ou="more ";Ec>vd.length&&(a.O("note: only "+ -vd.length+" available"),Ec=vd.length);Vc-=Ec;0>Vc&&(null==vd[vd.length-1].T?(Ec=Vc+Ec,Vc=0):Vc+=vd.length);var Um=[];"call"==nu&&(jh=1E5,Um=["CALL"]);for(void 0!==mu&&a.O(Ec+" instructions earlier:");0=vd.length&&(Vc=0);a.Oa=Ec;pu++;jh--}}pu||(a.O("no "+ +OA(Ef.Wi,Ef.Gj,!0);wj+=" %%"+p(Ef.Sl);a.O(wj)}else a.O("unsupported operation")}}else a.O("missing address")}else{if("d"==sa){if("disk"==ib){ba[0]="l";ba[1]="json";XC(a,ba);break a}for(Bb in kc)if(ba[1]==Bb){var lu=a.Da[Bb];lu?(ba.shift(),ba.shift(),lu(ba)):a.O("no dump registered for "+ib);break a}ib||(sa=a.kb||"db")}if("dh"==sa){var mu=ib,nu=Vb,ih=Mb;ih=void 0===ih?"history":ih;var ou="",pu=0,Wc=a.pa,vd=a.U;if(vd.length){var Ec=+mu||a.Oa,jh=+nu||10;isNaN(Ec)?Ec=jh:ou="more ";Ec>vd.length&&(a.O("note: only "+ +vd.length+" available"),Ec=vd.length);Wc-=Ec;0>Wc&&(null==vd[vd.length-1].T?(Ec=Wc+Ec,Wc=0):Wc+=vd.length);var Um=[];"call"==nu&&(jh=1E5,Um=["CALL"]);for(void 0!==mu&&a.O(Ec+" instructions earlier:");0=vd.length&&(Wc=0);a.Oa=Ec;pu++;jh--}}pu||(a.O("no "+ ou+"history available"),a.Oa=void 0)}else if("di"==sa){ba.shift();var Vm=ba[0];if(Vm){var lh=Rz(a,Vm);if(void 0===lh||0>lh||255"abwd".indexOf(sa[1]))a.O("unrecognized dump command"); -else{a.kb=sa;var Je=JA(a,ib);if(Je&&(null!=Je.T||null!=Je.xa)){var Gf=0;if(Vb){if("l"==Vb.charAt(0))Vb=Vb.substr(1)||Mb,Gf=Rz(a,Vb);else{var wu=JA(a,Vb);if(!wu)break a;Gf=wu.Ha-Je.Ha+1}if(0>Gf||65536>4||1,Ym=4==Hf?16:a.D;"a"==sa[1]&&(Wm=!0,Xm=25,Ym=160,mh=Xm*Ym);for(;Xm--&&0Aj?String.fromCharCode(Aj):Wm?"":".";mh--}Ke&&(Ke+="\n");Ke=Wm?Ke+zj:Ke+(ib+" "+$m+Ka(zj,zj.length+3*nh+1,!0))}Ke&&a.O(Ke.replace(/\s*$/,""));a.Aa=Je}}}}}break;case "e":if("else"==g[0])break;var Bj=1,xu=255,yu=a.ec,zu=a.Ng;"ew"==g[0]&&(Bj=2,xu=65535,yu=a.Sc,zu=a.Gl);var Au=Bj<<1,Bu=g[1];if(null==Bu)a.O("edit memory commands:"),a.O("\teb [a] [...] edit bytes at address a"),a.O("\tew [a] [...] edit words at address a");else{var Cj=JA(a,Bu);if(Cj)for(var Dj= +else{a.kb=sa;var Ke=JA(a,ib);if(Ke&&(null!=Ke.T||null!=Ke.xa)){var Gf=0;if(Vb){if("l"==Vb.charAt(0))Vb=Vb.substr(1)||Mb,Gf=Rz(a,Vb);else{var wu=JA(a,Vb);if(!wu)break a;Gf=wu.Ha-Ke.Ha+1}if(0>Gf||65536>4||1,Ym=4==Hf?16:a.D;"a"==sa[1]&&(Wm=!0,Xm=25,Ym=160,mh=Xm*Ym);for(;Xm--&&0Aj?String.fromCharCode(Aj):Wm?"":".";mh--}Le&&(Le+="\n");Le=Wm?Le+zj:Le+(ib+" "+$m+Ka(zj,zj.length+3*nh+1,!0))}Le&&a.O(Le.replace(/\s*$/,""));a.Aa=Ke}}}}}break;case "e":if("else"==g[0])break;var Bj=1,xu=255,yu=a.ec,zu=a.Ng;"ew"==g[0]&&(Bj=2,xu=65535,yu=a.Sc,zu=a.Gl);var Au=Bj<<1,Bu=g[1];if(null==Bu)a.O("edit memory commands:"),a.O("\teb [a] [...] edit bytes at address a"),a.O("\tew [a] [...] edit words at address a");else{var Cj=JA(a,Bu);if(Cj)for(var Dj= 2;Djen;){for(var xd=null,VD=256;de.Ha>>>0>>0;){qh.Ha=a.lb(de,!0);if(null==de.xa||!VD--)break;qh.T=Eu;if(xd=aD(a,qh))break;qh.T=a.lb(de);if(xd=aD(a,qh,!0)){Eu=a.lb(de,!0);0\";\n\nvar LICENSE = \"License: GPL version 3 or later \";\n\nvar CSSCLASS = \"pcjs\";\n\n/**\n * @define {string}\n */\nvar SITEHOST = \"localhost:8088\";// this @define is overridden by the Closure Compiler with \"www.pcjs.org\"\n\n/**\n * @define {boolean}\n */\nvar COMPILED = false; // this @define is overridden by the Closure Compiler (to true)\n\n/**\n * @define {boolean}\n */\nvar DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code\n\n/**\n * @define {boolean}\n */\nvar MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code\n\n/**\n * @define {boolean}\n */\nvar PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code\n\n/*\n * RS-232 DB-25 Pin Definitions, mapped to bits 1-25 in a 32-bit status value.\n *\n * SerialPorts in PCjs machines are considered DTE (Data Terminal Equipment), which means they should be \"virtually\"\n * connected to each other via a null-modem cable, which assumes the following cross-wiring:\n *\n * G 1 <-> 1 G (Ground)\n * TD 2 <-> 3 RD (Received Data)\n * RD 3 <-> 2 TD (Transmitted Data)\n * RTS 4 <-> 5 CTS (Clear To Send)\n * CTS 5 <-> 4 RTS (Request To Send)\n * DSR 6+8 <-> 20 DTR (Data Terminal Ready)\n * SG 7 <-> 7 SG (Signal Ground)\n * DTR 20 <-> 6+8 DSR (Data Set Ready + Carrier Detect)\n * RI 22 <-> 22 RI (Ring Indicator)\n *\n * TODO: Move these definitions to a more appropriate shared file at some point.\n */\nvar RS232 = {\n RTS: {\n PIN: 4,\n MASK: 0x00000010\n },\n CTS: {\n PIN: 5,\n MASK: 0x00000020\n },\n DSR: {\n PIN: 6,\n MASK: 0x00000040\n },\n CD: {\n PIN: 8,\n MASK: 0x00000100\n },\n DTR: {\n PIN: 20,\n MASK: 0x00100000\n },\n RI: {\n PIN: 22,\n MASK: 0x00400000\n }\n};\n\n/*\n * NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser)\n */\nvar NODE = false;\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2018\n */\n\n/*\n * Our \"DiskIO API\" looks like:\n *\n * http://www.pcjs.org/api/v1/disk?action=open&volume=*10mb.img&mode=demandrw&chs=c:h:s&machine=xxx&user=yyy\n */\nvar DiskAPI = {\n ENDPOINT: \"/api/v1/disk\",\n QUERY: {\n ACTION: \"action\", // value is one of DiskAPI.ACTION.*\n VOLUME: \"volume\", // value is path of a disk image\n MODE: \"mode\", // value is one of DiskAPI.MODE.*\n CHS: \"chs\", // value is cylinders:heads:sectors:bytes\n ADDR: \"addr\", // value is cylinder:head:sector:count\n MACHINE: \"machine\", // value is machine token\n USER: \"user\", // value is user ID\n DATA: \"data\" // value is data to be written\n },\n ACTION: {\n OPEN: \"open\",\n READ: \"read\",\n WRITE: \"write\",\n CLOSE: \"close\"\n },\n MODE: {\n LOCAL: \"local\", // this mode implies no API (at best, localStorage backing only)\n PRELOAD: \"preload\", // this mode implies use of the DumpAPI\n DEMANDRW: \"demandrw\",\n DEMANDRO: \"demandro\"\n },\n FAIL: {\n BADACTION: \"invalid action\",\n BADUSER: \"invalid user\",\n BADVOL: \"invalid volume\",\n OPENVOL: \"unable to open volume\",\n CREATEVOL: \"unable to create volume\",\n WRITEVOL: \"unable to write volume\",\n REVOKED: \"access revoked\"\n }\n};\n\n/*\n * TODO: Eventually, our tools will need to support looking up disk formats by \"model\" rather than by raw disk size,\n * because obviously multiple disk geometries can yield the same raw disk size. For each conflict that arises, I'll\n * probably create a fake (approximate) disk size entry above, and then create a mapping to that approximate size below.\n */\nDiskAPI.MODELS = {\n \"RL01\": 5242880,\n \"RL02\": 10485760\n};\n\nDiskAPI.MBR = {\n PARTITIONS: {\n OFFSET: 0x1BE,\n ENTRY: {\n STATUS: 0x00, // 0x80 if active\n CHS_FIRST: 0x01, // 3-byte CHS specifier\n TYPE: 0x04, // see TYPE.*\n CHS_LAST: 0x05, // 3-byte CHS specifier\n LBA_FIRST: 0x08,\n LBA_TOTAL: 0x0C,\n LENGTH: 0x10\n },\n STATUS: {\n ACTIVE: 0x80\n },\n TYPE: {\n EMPTY: 0x00,\n FAT12_PRIMARY: 0x01, // DOS 2.0 and up (12-bit FAT)\n FAT16_PRIMARY: 0x04 // DOS 3.0 and up (16-bit FAT)\n }\n },\n SIG_OFFSET: 0x1FE,\n SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA\n};\n\n/*\n * Boot sector offsets (and assorted constants) in DOS-compatible boot sectors (DOS 2.0 and up)\n *\n * WARNING: I've heard apocryphal stories about SIGNATURE being improperly reversed on some systems\n * (ie, 0x55AA instead 0xAA55) -- perhaps by a dyslexic programmer -- so be careful out there.\n */\nDiskAPI.BOOT = {\n JMP_OPCODE: 0x000, // 1 byte for a JMP opcode, followed by a 1 or 2-byte offset\n OEM_STRING: 0x003, // 8 bytes\n SIG_OFFSET: 0x1FE,\n SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA\n};\n\n/*\n * BIOS Parameter Block (BPB) offsets in DOS-compatible boot sectors (DOS 2.x and up)\n *\n * NOTE: DOS 2.x OEM documentation says that the words starting at offset 0x018 (TRACK_SECS, TOTAL_HEADS, and HIDDEN_SECS)\n * are optional, but even the DOS 2.0 FORMAT utility initializes all three of those words. There may be some OEM media out\n * there with BPBs that are only valid up to offset 0x018, but I've not run across any media like that.\n *\n * DOS 3.20 added LARGE_SECS, but unfortunately, it was added as a 2-byte value at offset 0x01E. DOS 3.31 decided\n * to make both HIDDEN_SECS and LARGE_SECS 4-byte values, which meant that LARGE_SECS had to move from 0x01E to 0x020.\n */\nDiskAPI.BPB = {\n SECTOR_BYTES: 0x00B, // 2 bytes: bytes per sector (eg, 0x200 or 512)\n CLUSTER_SECS: 0x00D, // 1 byte: sectors per cluster (eg, 1)\n RESERVED_SECS: 0x00E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1)\n TOTAL_FATS: 0x010, // 1 byte: FAT copies (eg, 2)\n ROOT_DIRENTS: 0x011, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)\n TOTAL_SECS: 0x013, // 2 bytes: number of sectors (eg, 0x140 or 320); if zero, refer to LARGE_SECS\n MEDIA_ID: 0x015, // 1 byte: media ID (see DiskAPI.FAT.MEDIA_*); should also match the first byte of the FAT (aka FAT ID)\n FAT_SECS: 0x016, // 2 bytes: sectors per FAT (eg, 1)\n TRACK_SECS: 0x018, // 2 bytes: sectors per track (eg, 8)\n TOTAL_HEADS: 0x01A, // 2 bytes: number of heads (eg, 1)\n HIDDEN_SECS: 0x01C, // 2 bytes (DOS 2.x) or 4 bytes (DOS 3.31 and up): number of hidden sectors (always 0 for non-partitioned media)\n LARGE_SECS: 0x020 // 4 bytes (DOS 3.31 and up): number of sectors if TOTAL_SECS is zero\n};\n\n/*\n * Common (supported) diskette geometries.\n *\n * Each entry in GEOMETRIES is an array of values in \"CHS\" order:\n *\n * [# cylinders, # heads, # sectors/track, # bytes/sector, media ID]\n *\n * If the 4th value is omitted, the sector size is assumed to be 512. The order of these \"geometric\" values mirrors\n * the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which is an array of\n * heads, each of which is an array of sector objects.\n */\nDiskAPI.GEOMETRIES = {\n 163840: [40,1,8,,0xFE], // media ID 0xFE: 40 cylinders, 1 head (single-sided), 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)\n 184320: [40,1,9,,0xFC], // media ID 0xFC: 40 cylinders, 1 head (single-sided), 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)\n 327680: [40,2,8,,0xFF], // media ID 0xFF: 40 cylinders, 2 heads (double-sided), 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)\n 368640: [40,2,9,,0xFD], // media ID 0xFD: 40 cylinders, 2 heads (double-sided), 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)\n 737280: [80,2,9,,0xF9], // media ID 0xF9: 80 cylinders, 2 heads (double-sided), 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)\n 1228800: [80,2,15,,0xF9], // media ID 0xF9: 80 cylinders, 2 heads (double-sided), 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)\n 1474560: [80,2,18,,0xF0], // media ID 0xF0: 80 cylinders, 2 heads (double-sided), 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)\n 2949120: [80,2,36,,0xF0], // media ID 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)\n /*\n * The following are some common disk sizes and their CHS values, since missing or bogus MBR and/or BPB values\n * might mislead us when attempting to determine the exact disk geometry.\n */\n 10653696:[306,4,17], // PC XT 10Mb hard drive (type 3)\n 21411840:[615,4,17], // PC AT 20Mb hard drive (type 2)\n /*\n * Assorted DEC disk formats.\n */\n 256256: [77, 1,26,128], // RX01 single-platter diskette: 77 tracks, 1 head, 26 sectors/track, 128 bytes/sector, for a total of 256256 bytes\n 2494464: [203,2,12,512], // RK03 single-platter disk cartridge: 203 tracks, 2 heads, 12 sectors/track, 512 bytes/sector, for a total of 2494464 bytes\n 5242880: [256,2,40,256], // RL01K single-platter disk cartridge: 256 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 5242880 bytes\n 10485760:[512,2,40,256] // RL02K single-platter disk cartridge: 512 tracks, 2 heads, 40 sectors/track, 256 bytes/sector, for a total of 10485760 bytes\n};\n\n/*\n * Media ID (descriptor) bytes for DOS-compatible FAT-formatted disks (stored in the first byte of the FAT)\n */\nDiskAPI.FAT = {\n MEDIA_160KB: 0xFE, // 5.25-inch, 1-sided, 8-sector, 40-track\n MEDIA_180KB: 0xFC, // 5.25-inch, 1-sided, 9-sector, 40-track\n MEDIA_320KB: 0xFF, // 5.25-inch, 2-sided, 8-sector, 40-track\n MEDIA_360KB: 0xFD, // 5.25-inch, 2-sided, 9-sector, 40-track\n MEDIA_720KB: 0xF9, // 3.5-inch, 2-sided, 9-sector, 80-track\n MEDIA_1200KB: 0xF9, // 3.5-inch, 2-sided, 15-sector, 80-track\n MEDIA_FIXED: 0xF8, // fixed disk (aka hard drive)\n MEDIA_1440KB: 0xF0, // 3.5-inch, 2-sided, 18-sector, 80-track\n MEDIA_2880KB: 0xF0 // 3.5-inch, 2-sided, 36-sector, 80-track\n};\n\n/*\n * Cluster constants for 12-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs)\n */\nDiskAPI.FAT12 = {\n MAX_CLUSTERS: 4084,\n CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain)\n CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain\n CLUSNUM_MIN: 2, // smallest valid cluster number\n CLUSNUM_MAX: 0xFF6, // largest valid cluster number\n CLUSNUM_BAD: 0xFF7, // bad cluster; this should NEVER appear in cluster chain\n CLUSNUM_EOC: 0xFF8 // end of chain (actually, anything from 0xFF8-0xFFF indicates EOC)\n};\n\n/*\n * Cluster constants for 16-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs)\n */\nDiskAPI.FAT16 = {\n MAX_CLUSTERS: 65524,\n CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain)\n CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain\n CLUSNUM_MIN: 2, // smallest valid cluster number\n CLUSNUM_MAX: 0xFFF6, // largest valid cluster number\n CLUSNUM_BAD: 0xFFF7, // bad cluster; this should NEVER appear in cluster chain\n CLUSNUM_EOC: 0xFFF8 // end of chain (actually, anything from 0xFFF8-0xFFFF indicates EOC)\n};\n\n/*\n * Directory Entry offsets (and assorted constants) in FAT disk images\n *\n * NOTE: Versions of DOS prior to 2.0 use INVALID exclusively to mark available directory entries; any entry marked\n * UNUSED will actually be considered USED. In DOS 2.0 and up, UNUSED was added to indicate that all remaining entries\n * are unused, relieving it from having to initialize the rest of the sectors in the directory cluster(s). And in fact,\n * you WILL encounter garbage in subsequent directory sectors if you attempt to read past an UNUSED entry.\n */\nDiskAPI.DIRENT = {\n NAME: 0x000, // 8 bytes\n EXT: 0x008, // 3 bytes\n ATTR: 0x00B, // 1 byte\n MODTIME: 0x016, // 2 bytes\n MODDATE: 0x018, // 2 bytes\n CLUSTER: 0x01A, // 2 bytes\n SIZE: 0x01C, // 4 bytes (typically zero for subdirectories)\n LENGTH: 0x20, // 32 bytes total\n UNUSED: 0x00, // indicates this and all subsequent directory entries are unused\n INVALID: 0xE5 // indicates this directory entry is unused\n};\n\n/*\n * Possible values for DIRENT.ATTR\n */\nDiskAPI.ATTR = {\n READONLY: 0x01, // PC-DOS 2.0 and up\n HIDDEN: 0x02,\n SYSTEM: 0x04,\n LABEL: 0x08, // PC-DOS 2.0 and up\n SUBDIR: 0x10, // PC-DOS 2.0 and up\n ARCHIVE: 0x20 // PC-DOS 2.0 and up\n};\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2018\n */\n\n/*\n * Our \"DiskDump API\", such as it was, used to look like:\n *\n * http://jsmachines.net/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img\n *\n * To make it (a bit) more \"REST-like\", the above request now looks like:\n *\n * http://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img\n *\n * Similarly, our \"FileDump API\" used to look like:\n *\n * http://jsmachines.net/bin/convrom.php?rom=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json\n *\n * and that request now looks like:\n *\n * http://www.pcjs.org/api/v1/dump?file=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json\n *\n * I don't think it makes sense to avoid \"query\" parameters, because blending the path of a disk image with the\n * the rest of the URL would be (a) confusing, and (b) more work to parse.\n */\nvar DumpAPI = {\n ENDPOINT: \"/api/v1/dump\",\n QUERY: {\n DIR: \"dir\", // value is path of a directory (DiskDump only)\n DISK: \"disk\", // value is path of a disk image (DiskDump only)\n FILE: \"file\", // value is path of a ROM image file (FileDump only)\n IMG: \"img\", // alias for DISK\n PATH: \"path\", // value is path of a one or more files (DiskDump only)\n FORMAT: \"format\", // value is one of FORMAT values below\n COMMENTS: \"comments\", // value is either \"true\" or \"false\"\n DECIMAL: \"decimal\", // value is either \"true\" to force all numbers to decimal, \"false\" or undefined otherwise\n MBHD: \"mbhd\", // value is hard drive size in Mb (formerly \"mbsize\") (DiskDump only) (DEPRECATED)\n SIZE: \"size\" // value is target disk size in Kb (supersedes \"mbhd\") (DiskDump only)\n },\n FORMAT: {\n JSON: \"json\", // default\n JSON_GZ: \"gz\", // gzip is currently used ONLY for compressed JSON\n DATA: \"data\", // same as \"json\", but built without JSON.stringify() (DiskDump only)\n HEX: \"hex\", // deprecated\n OCTAL: \"octal\", // displays data as octal words\n BYTES: \"bytes\", // displays data as hex bytes; normally used only when comments are enabled\n WORDS: \"words\", // displays data as hex words; normally used only when comments are enabled\n LONGS: \"longs\", // displays data as dwords\n IMG: \"img\", // returns the raw disk data (ie, using a Buffer object) (DiskDump only)\n ROM: \"rom\" // returns the raw file data (ie, using a Buffer object) (FileDump only)\n }\n};\n\n/*\n * Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must\n * also provide a list of commands which, when combined with the endpoint, define a unique request.\n */\nDumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH];\nDumpAPI.asFileCommands = [DumpAPI.QUERY.FILE];\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2018\n */\n\nvar ReportAPI = {\n ENDPOINT: \"/api/v1/report\",\n QUERY: {\n APP: \"app\",\n VER: \"ver\",\n URL: \"url\",\n USER: \"user\",\n TYPE: \"type\",\n DATA: \"data\"\n },\n TYPE: {\n BUG: \"bug\"\n },\n RES: {\n OK: \"Thank you\"\n }\n};\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2018\n */\n\n/*\n * Examples of User API requests:\n *\n * web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser;\n */\nvar UserAPI = {\n ENDPOINT: \"/api/v1/user\",\n QUERY: {\n REQ: \"req\", // specifies a request\n USER: \"user\", // specifies a user ID\n STATE: \"state\", // specifies a state ID\n DATA: \"data\" // specifies state data\n },\n REQ: {\n CREATE: \"create\", // creates a user ID\n VERIFY: \"verify\", // requests verification of a user ID\n STORE: \"store\", // stores a machine state on the server\n LOAD: \"load\" // loads a machine state from the server\n },\n RES: {\n CODE: \"code\",\n DATA: \"data\"\n },\n CODE: {\n OK: \"ok\",\n FAIL: \"error\"\n },\n FAIL: {\n DUPLICATE: \"user already exists\",\n VERIFY: \"unable to verify user\",\n BADSTATE: \"invalid state parameter\",\n NOSTATE: \"no machine state\",\n BADLOAD: \"unable to load machine state\",\n BADSTORE: \"unable to save machine state\"\n }\n};\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2018\n */\n\nvar Keys = {\n /*\n * Keys and/or key combinations that generate common ASCII codes.\n *\n * NOTE: If you're looking for a general-purpose ASCII code table, see Str.ASCII in strlib.js;\n * if something's missing, that's probably the more appropriate table to add it to.\n *\n * TODO: The Closure Compiler doesn't inline all references to these values, at least those with\n * quoted property names, which is why I've 'unquoted' as many of them as possible. One solution\n * would be to add mnemonics for all of them, not just the non-printable ones (eg, SPACE instead\n * of ' ', AMP instead of '&', etc.)\n */\n ASCII: {\n BREAK: 0, CTRL_A: 1, CTRL_B: 2, CTRL_C: 3, CTRL_D: 4, CTRL_E: 5, CTRL_F: 6, CTRL_G: 7,\n CTRL_H: 8, CTRL_I: 9, CTRL_J: 10, CTRL_K: 11, CTRL_L: 12, CTRL_M: 13, CTRL_N: 14, CTRL_O: 15,\n CTRL_P: 16, CTRL_Q: 17, CTRL_R: 18, CTRL_S: 19, CTRL_T: 20, CTRL_U: 21, CTRL_V: 22, CTRL_W: 23,\n CTRL_X: 24, CTRL_Y: 25, CTRL_Z: 26, ESC: 27,\n ' ': 32, '!': 33, '\"': 34, '#': 35, '$': 36, '%': 37, '&': 38, \"'\": 39,\n '(': 40, ')': 41, '*': 42, '+': 43, ',': 44, '-': 45, '.': 46, '/': 47,\n '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55,\n '8': 56, '9': 57, ':': 58, ';': 59, '<': 60, '=': 61, '>': 62, '?': 63,\n '@': 64, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71,\n H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79,\n P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87,\n X: 88, Y: 89, Z: 90, '[': 91, '\\\\':92, ']': 93, '^': 94, '_': 95,\n '`': 96, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103,\n h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111,\n p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119,\n x: 120, y: 121, z: 122, '{':123, '|':124, '}':125, '~':126, DEL: 127\n },\n /*\n * Browser keyCodes we must pay particular attention to. For the most part, these are non-alphanumeric\n * or function keys, some which may require special treatment (eg, preventDefault() if returning false on\n * the initial keyDown event is insufficient).\n *\n * keyCodes for most common ASCII keys can simply use the appropriate ASCII code above.\n *\n * Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, browsers defined\n * them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37).\n */\n KEYCODE: {\n /* 0x08 */ BS: 8, // BACKSPACE (ASCII.CTRL_H)\n /* 0x09 */ TAB: 9, // TAB (ASCII.CTRL_I)\n /* 0x0A */ LF: 10, // LINE-FEED (ASCII.CTRL_J) (Some Windows-based browsers used to generate this via CTRL-ENTER)\n /* 0x0D */ CR: 13, // CARRIAGE RETURN (ASCII.CTRL_M)\n /* 0x10 */ SHIFT: 16,\n /* 0x11 */ CTRL: 17,\n /* 0x12 */ ALT: 18,\n /* 0x13 */ PAUSE: 19, // PAUSE/BREAK\n /* 0x14 */ CAPS_LOCK: 20,\n /* 0x1B */ ESC: 27,\n /* 0x20 */ SPACE: 32,\n /* 0x21 */ PGUP: 33,\n /* 0x22 */ PGDN: 34,\n /* 0x23 */ END: 35,\n /* 0x24 */ HOME: 36,\n /* 0x25 */ LEFT: 37,\n /* 0x26 */ UP: 38,\n /* 0x27 */ RIGHT: 39,\n /* 0x27 */ FF_QUOTE: 39,\n /* 0x28 */ DOWN: 40,\n /* 0x2C */ FF_COMMA: 44,\n /* 0x2C */ PRTSC: 44,\n /* 0x2D */ INS: 45,\n /* 0x2E */ DEL: 46,\n /* 0x2E */ FF_PERIOD: 46,\n /* 0x2F */ FF_SLASH: 47,\n /* 0x30 */ ZERO: 48,\n /* 0x31 */ ONE: 49,\n /* 0x32 */ TWO: 50,\n /* 0x33 */ THREE: 51,\n /* 0x34 */ FOUR: 52,\n /* 0x35 */ FIVE: 53,\n /* 0x36 */ SIX: 54,\n /* 0x37 */ SEVEN: 55,\n /* 0x38 */ EIGHT: 56,\n /* 0x39 */ NINE: 57,\n /* 0x3B */ FF_SEMI: 59,\n /* 0x3D */ FF_EQUALS: 61,\n /* 0x5B */ CMD: 91, // aka WIN\n /* 0x5B */ FF_LBRACK: 91,\n /* 0x5C */ FF_BSLASH: 92,\n /* 0x5D */ RCMD: 93, // aka MENU\n /* 0x5D */ FF_RBRACK: 93,\n /* 0x60 */ NUM_0: 96,\n /* 0x60 */ NUM_INS: 96,\n /* 0x60 */ FF_BQUOTE: 96,\n /* 0x61 */ NUM_1: 97,\n /* 0x61 */ NUM_END: 97,\n /* 0x62 */ NUM_2: 98,\n /* 0x62 */ NUM_DOWN: 98,\n /* 0x63 */ NUM_3: 99,\n /* 0x63 */ NUM_PGDN: 99,\n /* 0x64 */ NUM_4: 100,\n /* 0x64 */ NUM_LEFT: 100,\n /* 0x65 */ NUM_5: 101,\n /* 0x65 */ NUM_CENTER: 101,\n /* 0x66 */ NUM_6: 102,\n /* 0x66 */ NUM_RIGHT: 102,\n /* 0x67 */ NUM_7: 103,\n /* 0x67 */ NUM_HOME: 103,\n /* 0x68 */ NUM_8: 104,\n /* 0x68 */ NUM_UP: 104,\n /* 0x69 */ NUM_9: 105,\n /* 0x69 */ NUM_PGUP: 105,\n /* 0x6A */ NUM_MUL: 106,\n /* 0x6B */ NUM_ADD: 107,\n /* 0x6D */ NUM_SUB: 109,\n /* 0x6E */ NUM_DEL: 110, // aka PERIOD\n /* 0x6F */ NUM_DIV: 111,\n /* 0x70 */ F1: 112,\n /* 0x71 */ F2: 113,\n /* 0x72 */ F3: 114,\n /* 0x73 */ F4: 115,\n /* 0x74 */ F5: 116,\n /* 0x75 */ F6: 117,\n /* 0x76 */ F7: 118,\n /* 0x77 */ F8: 119,\n /* 0x78 */ F9: 120,\n /* 0x79 */ F10: 121,\n /* 0x7A */ F11: 122,\n /* 0x7B */ F12: 123,\n /* 0x90 */ NUM_LOCK: 144,\n /* 0x91 */ SCROLL_LOCK: 145,\n /* 0xAD */ FF_DASH: 173,\n /* 0xBA */ SEMI: 186, // Firefox: 59 (FF_SEMI)\n /* 0xBB */ EQUALS: 187, // Firefox: 61 (FF_EQUALS)\n /* 0xBC */ COMMA: 188,\n /* 0xBD */ DASH: 189, // Firefox: 173 (FF_DASH)\n /* 0xBE */ PERIOD: 190,\n /* 0xBF */ SLASH: 191,\n /* 0xC0 */ BQUOTE: 192,\n /* 0xDB */ LBRACK: 219,\n /* 0xDC */ BSLASH: 220,\n /* 0xDD */ RBRACK: 221,\n /* 0xDE */ QUOTE: 222,\n /* 0xE0 */ FF_CMD: 224, // Firefox only (used for both CMD and RCMD)\n //\n // The following biases use what I'll call Decimal Coded Binary or DCB (the opposite of BCD),\n // where the thousands digit is used to store the sum of \"binary\" digits 1 and/or 2 and/or 4.\n //\n // Technically, that makes it DCO (Decimal Coded Octal), but then again, BCD should have really\n // been called HCD (Hexadecimal Coded Decimal), so if \"they\" can take liberties, so can I.\n //\n // ONDOWN is a bias we add to browser keyCodes that we want to handle on \"down\" rather than on \"press\".\n //\n ONDOWN: 1000,\n //\n // ONRIGHT is a bias we add to browser keyCodes that need to check for a \"right\" location (default is \"left\")\n //\n ONRIGHT: 2000,\n //\n // FAKE is a bias we add to signal these are fake keyCodes corresponding to internal keystroke combinations.\n // The actual values are for internal use only and merely need to be unique and used consistently.\n //\n FAKE: 4000\n },\n /*\n * The set of values that a browser may store in the 'location' property of a keyboard event object\n * which we also support.\n */\n LOCATION: {\n LEFT: 1,\n RIGHT: 2,\n NUMPAD: 3\n }\n};\n\n/*\n * Check the event object's 'location' property for a non-zero value for the following ONRIGHT keys.\n */\nKeys.KEYCODE.NUM_CR = Keys.KEYCODE.CR + Keys.KEYCODE.ONRIGHT;\n\n\n/*\n * Maps Firefox keyCodes to their more common keyCode counterparts; a number of entries in this table\n * are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I\n * simply extended this table to resolve additional differences in other browsers (ie, Opera), but without\n * browser-specific checks, it's not safe to perform all the mappings shown below.\n */\nKeys.FF_KEYCODES = {};\nKeys.FF_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.KEYCODE.SEMI; // 59 -> 186\nKeys.FF_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.KEYCODE.EQUALS; // 61 -> 187\nKeys.FF_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.KEYCODE.DASH; // 173 -> 189\nKeys.FF_KEYCODES[Keys.KEYCODE.FF_CMD] = Keys.KEYCODE.CMD; // 224 -> 91\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_COMMA] = Keys.KEYCODE.COMMA; // 44 -> 188\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_PERIOD] = Keys.KEYCODE.PERIOD; // 46 -> 190\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_SLASH] = Keys.KEYCODE.SLASH; // 47 -> 191\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BQUOTE] = Keys.KEYCODE.BQUOTE; // 96 -> 192\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_LBRACK = Keys.KEYCODE.LBRACK; // 91 -> 219\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BSLASH] = Keys.KEYCODE.BSLASH; // 92 -> 220\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_RBRACK] = Keys.KEYCODE.RBRACK; // 93 -> 221\n// Keys.FF_KEYCODES[Keys.KEYCODE.FF_QUOTE] = Keys.KEYCODE.QUOTE; // 39 -> 222\n\n/*\n * Maps non-ASCII keyCodes to their ASCII counterparts\n */\nKeys.NONASCII_KEYCODES = {};\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['-']; // 173 -> 45\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[';']; // 186 -> 59\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['=']; // 187 -> 61\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['-']; // 189 -> 45\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII[',']; // 188 -> 44\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['.']; // 190 -> 46\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['/']; // 191 -> 47\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['`']; // 192 -> 96\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['[']; // 219 -> 91\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['\\\\']; // 220 -> 92\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII[']']; // 221 -> 93\nKeys.NONASCII_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII[\"'\"]; // 222 -> 39\n\n/*\n * Maps unshifted keyCodes to their shifted counterparts; to be used when a shift-key is down.\n * Alphabetic characters are handled in code, since they must also take CAPS_LOCK into consideration.\n */\nKeys.SHIFTED_KEYCODES = {};\nKeys.SHIFTED_KEYCODES[Keys.ASCII['1']] = Keys.ASCII['!'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['2']] = Keys.ASCII['@'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['3']] = Keys.ASCII['#'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['4']] = Keys.ASCII['$'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['5']] = Keys.ASCII['%'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['6']] = Keys.ASCII['^'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['7']] = Keys.ASCII['&'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['8']] = Keys.ASCII['*'];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['9']] = Keys.ASCII['('];\nKeys.SHIFTED_KEYCODES[Keys.ASCII['0']] = Keys.ASCII[')'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[':'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['+'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII['<'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['_'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['>'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['?'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['~'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['{'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['|'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII['}'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII['\"'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['_'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.ASCII['+'];\nKeys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.ASCII[':'];\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2018\n */\n\nclass Str {\n /**\n * isValidInt(s, base)\n *\n * The built-in parseInt() function has the annoying feature of returning a partial value (ie,\n * up to the point where it encounters an invalid character); eg, parseInt(\"foo\", 16) returns 0xf.\n *\n * So it's best to use our own Str.parseInt() function, which will in turn use this function to\n * validate the entire string.\n *\n * @param {string} s is the string representation of some number\n * @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported\n * @return {boolean} true if valid, false if invalid (or the specified base isn't supported)\n */\n static isValidInt(s, base)\n {\n if (!base || base == 10) return s.match(/^-?[0-9]+$/) !== null;\n if (base == 16) return s.match(/^-?[0-9a-f]+$/i) !== null;\n if (base == 8) return s.match(/^-?[0-7]+$/) !== null;\n if (base == 2) return s.match(/^-?[01]+$/) !== null;\n return false;\n }\n\n /**\n * parseInt(s, base)\n *\n * This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes\n * ('$' or \"0x\" for hex, '#' or \"0o\" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for\n * binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values;\n * see isValidInt() for details.\n *\n * The use of multiple prefix/suffix combinations is undefined (although for the record, we process\n * prefixes first). We do NOT support the \"0b\" prefix to indicate binary UNLESS one or more commas are\n * also present (because \"0b\" is also a valid hex sequence), and we do NOT support a single leading zero\n * to indicate octal (because such a number could also be decimal or hex). Any number of commas are\n * allowed; we remove them all before calling the built-in parseInt().\n *\n * More recently, we've added support for \"^D\", \"^O\", and \"^B\" prefixes to accommodate the base overrides\n * that the PDP-10's MACRO-10 assembly language supports (decimal, octal, and binary, respectively).\n * If this support turns out to adversely affect other debuggers, then it will have to be \"conditionalized\".\n * Similarly, we've added support for \"K\", \"M\", and \"G\" MACRO-10-style suffixes that add 3, 6, or 9 zeros\n * to the value to be parsed, respectively.\n *\n * @param {string} s is the string representation of some number\n * @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes\n * @return {number|undefined} corresponding value, or undefined if invalid\n */\n static parseInt(s, base)\n {\n var value;\n\n if (s) {\n if (!base) base = 10;\n\n var ch, chPrefix, chSuffix;\n var fCommas = (s.indexOf(',') > 0);\n if (fCommas) s = s.replace(/,/g, '');\n\n ch = chPrefix = s.charAt(0);\n if (chPrefix == '#') {\n base = 8;\n chPrefix = '';\n }\n else if (chPrefix == '$') {\n base = 16;\n chPrefix = '';\n }\n if (ch != chPrefix) {\n s = s.substr(1);\n }\n else {\n ch = chPrefix = s.substr(0, 2);\n if (chPrefix == '0b' && fCommas || chPrefix == '^B') {\n base = 2;\n chPrefix = '';\n }\n else if (chPrefix == '0o' || chPrefix == '^O') {\n base = 8;\n chPrefix = '';\n }\n else if (chPrefix == '^D') {\n base = 10;\n chPrefix = '';\n }\n else if (chPrefix == '0x') {\n base = 16;\n chPrefix = '';\n }\n if (ch != chPrefix) s = s.substr(2);\n }\n ch = chSuffix = s.slice(-1);\n if (chSuffix == 'Y' || chSuffix == 'y') {\n base = 2;\n chSuffix = '';\n }\n else if (chSuffix == '.') {\n base = 10;\n chSuffix = '';\n }\n else if (chSuffix == 'H' || chSuffix == 'h') {\n base = 16;\n chSuffix = '';\n }\n else if (chSuffix == 'K') {\n chSuffix = '000';\n }\n else if (chSuffix == 'M') {\n chSuffix = '000000';\n }\n else if (chSuffix == 'G') {\n chSuffix = '000000000';\n }\n if (ch != chSuffix) s = s.slice(0, -1) + chSuffix;\n /*\n * This adds support for the MACRO-10 binary shifting (Bn) suffix, which must be stripped from the\n * number before parsing, and then applied to the value after parsing. If n is omitted, 35 is assumed,\n * which is a net shift of zero. If n < 35, then a left shift of (35 - n) is required; if n > 35, then\n * a right shift of -(35 - n) is required.\n */\n var v, shift = 0;\n if (base <= 10) {\n var match = s.match(/(-?[0-9]+)B([0-9]*)/);\n if (match) {\n s = match[1];\n shift = 35 - ((match[2] || 35) & 0xff);\n }\n }\n if (Str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {\n /*\n * With the need to support larger (eg, 36-bit) integers, truncating to 32 bits is no longer helpful.\n *\n * value = v|0;\n */\n if (shift) {\n /*\n * Since binary shifting is a logical operation, and since shifting by division only works properly\n * with positive numbers, we must convert a negative value to a positive value, by computing the two's\n * complement.\n */\n if (v < 0) v += Math.pow(2, 36);\n if (shift > 0) {\n v *= Math.pow(2, shift);\n } else {\n v = Math.trunc(v / Math.pow(2, -shift));\n }\n }\n value = v;\n }\n }\n return value;\n }\n\n /**\n * toBase(n, radix, cch, sPrefix, nGrouping)\n *\n * Displays the given number as an unsigned integer using the specified radix and number of digits.\n *\n * @param {number|null|undefined} n\n * @param {number} radix (ie, the base)\n * @param {number} cch (the desired number of digits)\n * @param {string} [sPrefix] (default is none)\n * @param {number} [nGrouping]\n * @return {string}\n */\n static toBase(n, radix, cch, sPrefix = \"\", nGrouping = 0)\n {\n /*\n * An initial \"falsey\" check for null takes care of both null and undefined;\n * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.\n *\n * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,\n * since JavaScript coerces such operands to zero, but I think there's \"value\" in seeing those\n * values displayed differently.\n */\n var s = \"\";\n if (isNaN(n)) {\n n = null;\n } else if (n != null) {\n /*\n * Callers that produced an input by dividing by a power of two rather than shifting (in order\n * to access more than 32 bits) may produce a fractional result, which ordinarily we would simply\n * ignore, but if the integer portion is zero and the sign is negative, we should probably treat\n * this value as a sign-extension.\n */\n if (n < 0 && n > -1) n = -1;\n /*\n * Negative values should be two's complemented according to the number of digits; for example,\n * 12 octal digits implies an upper limit 8^12.\n */\n if (n < 0) {\n n += Math.pow(radix, cch);\n }\n if (n >= Math.pow(radix, cch)) {\n cch = Math.ceil(Math.log(n) / Math.log(radix));\n }\n }\n var g = nGrouping || -1;\n while (cch-- > 0) {\n if (!g) {\n s = ',' + s;\n g = nGrouping;\n }\n if (n == null) {\n s = '?' + s;\n } else {\n var d = n % radix;\n d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10);\n s = String.fromCharCode(d) + s;\n n = Math.trunc(n / radix);\n }\n g--;\n }\n return sPrefix + s;\n }\n\n /**\n * toBin(n, cch, nGrouping)\n *\n * Converts an integer to binary, with the specified number of digits (up to a maximum of 36).\n *\n * @param {number|null|undefined} n (supports integers up to 36 bits now)\n * @param {number} [cch] is the desired number of binary digits (0 or undefined for default of either 8, 18, or 36)\n * @param {number} [nGrouping]\n * @return {string} the binary representation of n\n */\n static toBin(n, cch, nGrouping)\n {\n if (!cch) {\n // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN2) || 1;\n var v = Math.abs(n);\n if (v <= 0b11111111) {\n cch = 8;\n } else if (v <= 0b111111111111111111) {\n cch = 18;\n } else {\n cch = 36;\n }\n } else if (cch > 36) cch = 36;\n return Str.toBase(n, 2, cch, \"\", nGrouping);\n }\n\n /**\n * toBinBytes(n, cb, fPrefix)\n *\n * Converts an integer to binary, with the specified number of bytes (up to the default of 4).\n *\n * @param {number|null|undefined} n (interpreted as a 32-bit value)\n * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum)\n * @param {boolean} [fPrefix]\n * @return {string} the binary representation of n\n */\n static toBinBytes(n, cb, fPrefix)\n {\n var s = \"\";\n if (!cb || cb > 4) cb = 4;\n for (var i = 0; i < cb; i++) {\n if (s) s = ',' + s;\n s = Str.toBin(n & 0xff, 8) + s;\n n >>= 8;\n }\n return (fPrefix? \"0b\" : \"\") + s;\n }\n\n /**\n * toOct(n, cch, fPrefix)\n *\n * Converts an integer to octal, with the specified number of digits (default of 6; max of 12)\n *\n * You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it\n * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw\n * an exception, whereas this function will return '?' characters.\n *\n * @param {number|null|undefined} n (supports integers up to 36 bits now)\n * @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6, 8, or 12)\n * @param {boolean} [fPrefix]\n * @return {string} the octal representation of n\n */\n static toOct(n, cch, fPrefix)\n {\n if (!cch) {\n // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(8)) || 1;\n var v = Math.abs(n);\n if (v <= 0o777777) {\n cch = 6;\n } else if (v <= 0o77777777) {\n cch = 8;\n } else {\n cch = 12;\n }\n } else if (cch > 12) cch = 12;\n return Str.toBase(n, 8, cch, fPrefix? \"0o\" : \"\");\n }\n\n /**\n * toDec(n, cch)\n *\n * Converts an integer to decimal, with the specified number of digits (default of 5; max of 11)\n *\n * You might be tempted to use the built-in n.toString(10) instead, but it doesn't zero-pad and it\n * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw\n * an exception, whereas this function will return '?' characters.\n *\n * @param {number|null|undefined} n (supports integers up to 36 bits now)\n * @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 11)\n * @return {string} the decimal representation of n\n */\n static toDec(n, cch)\n {\n if (!cch) {\n // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN10) || 1;\n var v = Math.abs(n);\n if (v <= 99999) {\n cch = 5;\n } else {\n cch = 11;\n }\n } else if (cch > 11) cch = 11;\n return Str.toBase(n, 10, cch);\n }\n\n /**\n * toHex(n, cch, fPrefix)\n *\n * Converts an integer to hex, with the specified number of digits (default of 4 or 8, max of 9).\n *\n * You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it\n * doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16)\n * will return \"-7fffffff\" instead of \"80000001\". Moreover, if n is undefined, n.toString() will\n * throw an exception, whereas this function will return '?' characters.\n *\n * NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution,\n * taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values:\n *\n * s = (n < 0? n + 0x100000000 : n).toString(16);\n * s = \"00000000\".substr(0, 8 - s.length) + s;\n * s = s.substr(0, cch).toUpperCase();\n *\n * @param {number|null|undefined} n (supports integers up to 36 bits now)\n * @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4, 8, or 9)\n * @param {boolean} [fPrefix]\n * @return {string} the hex representation of n\n */\n static toHex(n, cch, fPrefix)\n {\n if (!cch) {\n // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(16)) || 1;\n var v = Math.abs(n);\n if (v <= 0xffff) {\n cch = 4;\n } else if (v <= 0xffffffff) {\n cch = 8;\n } else {\n cch = 9;\n }\n } else if (cch > 9) cch = 9;\n return Str.toBase(n, 16, cch, fPrefix? \"0x\" : \"\");\n }\n\n /**\n * toHexByte(b)\n *\n * Alias for Str.toHex(b, 2, true)\n *\n * @param {number|null|undefined} b is a byte value\n * @return {string} the hex representation of b\n */\n static toHexByte(b)\n {\n return Str.toHex(b, 2, true);\n }\n\n /**\n * toHexWord(w)\n *\n * Alias for Str.toHex(w, 4, true)\n *\n * @param {number|null|undefined} w is a word (16-bit) value\n * @return {string} the hex representation of w\n */\n static toHexWord(w)\n {\n return Str.toHex(w, 4, true);\n }\n\n /**\n * toHexLong(l)\n *\n * Alias for Str.toHex(l, 8, true)\n *\n * @param {number|null|undefined} l is a dword (32-bit) value\n * @return {string} the hex representation of w\n */\n static toHexLong(l)\n {\n return Str.toHex(l, 8, true);\n }\n\n /**\n * getBaseName(sFileName, fStripExt)\n *\n * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead.\n *\n * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips the extension only\n * if it matches the second parameter (eg, path.basename(\"/foo/bar/baz/asdf/quux.html\", \".html\") returns \"quux\").\n *\n * @param {string} sFileName\n * @param {boolean} [fStripExt]\n * @return {string}\n */\n static getBaseName(sFileName, fStripExt)\n {\n var sBaseName = sFileName;\n\n var i = sFileName.lastIndexOf('/');\n if (i >= 0) sBaseName = sFileName.substr(i + 1);\n\n /*\n * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters.\n */\n i = sBaseName.indexOf('&');\n if (i > 0) sBaseName = sBaseName.substr(0, i);\n\n if (fStripExt) {\n i = sBaseName.lastIndexOf(\".\");\n if (i > 0) {\n sBaseName = sBaseName.substring(0, i);\n }\n }\n return sBaseName;\n }\n\n /**\n * getExtension(sFileName)\n *\n * This is a poor-man's version of Node's path.extname(), which Node-only components should use instead.\n *\n * Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it.\n *\n * @param {string} sFileName\n * @return {string} the filename's extension (in lower-case and EXCLUDING the \".\"), or an empty string\n */\n static getExtension(sFileName)\n {\n var sExtension = \"\";\n var i = sFileName.lastIndexOf(\".\");\n if (i >= 0) {\n sExtension = sFileName.substr(i + 1).toLowerCase();\n }\n return sExtension;\n }\n\n /**\n * endsWith(s, sSuffix)\n *\n * @param {string} s\n * @param {string} sSuffix\n * @return {boolean} true if s ends with sSuffix, false if not\n */\n static endsWith(s, sSuffix)\n {\n return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1;\n }\n\n /**\n * escapeHTML(sHTML)\n *\n * @param {string} sHTML\n * @return {string} with HTML entities \"escaped\", similar to PHP's htmlspecialchars()\n */\n static escapeHTML(sHTML)\n {\n return sHTML.replace(/[&<>\"']/g, function(m)\n {\n return Str.HTMLEscapeMap[m];\n });\n }\n\n /**\n * replace(sSearch, sReplace, s)\n *\n * The JavaScript replace() function ALWAYS interprets \"$\" specially in replacement strings, even when\n * the search string is NOT a RegExp; specifically:\n *\n * $$ Inserts a \"$\"\n * $& Inserts the matched substring\n * $` Inserts the portion of the string that precedes the matched substring\n * $' Inserts the portion of the string that follows the matched substring\n * $n Where n is a positive integer less than 100, inserts the nth parenthesized sub-match string,\n * provided the first argument was a RegExp object\n *\n * So, if a replacement string containing dollar signs passes through a series of replace() calls, untold\n * problems could result. Hence, this function, which simply uses the replacement string as-is.\n *\n * Similar to the JavaScript replace() method (when sSearch is a string), this replaces only ONE occurrence\n * (ie, the FIRST occurrence); it might be nice to add options to replace the LAST occurrence and/or ALL\n * occurrences, but we'll revisit that later.\n *\n * @param {string} sSearch\n * @param {string} sReplace\n * @param {string} s\n * @return {string}\n */\n static replace(sSearch, sReplace, s)\n {\n var i = s.indexOf(sSearch);\n if (i >= 0) {\n s = s.substr(0, i) + sReplace + s.substr(i + sSearch.length);\n }\n return s;\n }\n\n /**\n * replaceAll(sSearch, sReplace, s)\n *\n * @param {string} sSearch\n * @param {string} sReplace\n * @param {string} s\n * @return {string}\n */\n static replaceAll(sSearch, sReplace, s)\n {\n var a = {};\n a[sSearch] = sReplace;\n return Str.replaceArray(a, s);\n }\n\n /**\n * replaceArray(a, s)\n *\n * @param {Object} a\n * @param {string} s\n * @return {string}\n */\n static replaceArray(a, s)\n {\n var sMatch = \"\";\n for (var k in a) {\n /*\n * As noted in:\n *\n * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/\n *\n * inside character classes, only backslash, caret, hyphen and the closing bracket need to be\n * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first,\n * and the hyphen is last, you can avoid escaping those as well.\n */\n k = k.replace(/([\\\\[\\]*{}().+?|$])/g, \"\\\\$1\");\n sMatch += (sMatch? '|' : '') + k;\n }\n return s.replace(new RegExp('(' + sMatch + ')', \"g\"), function(m)\n {\n return a[m];\n });\n }\n\n /**\n * pad(s, cch, fPadLeft)\n *\n * NOTE: the maximum amount of padding currently supported is 40 spaces.\n *\n * @param {string} s is a string\n * @param {number} cch is desired length\n * @param {boolean} [fPadLeft] (default is padding on the right)\n * @return {string} the original string (s) with spaces padding it to the specified length\n */\n static pad(s, cch, fPadLeft)\n {\n var sPadding = \" \";\n return fPadLeft? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch);\n }\n\n /**\n * sprintf(format, ...args)\n *\n * Copied from the CCjs project (/ccjs/lib/stdio.js) and extended. Far from complete let alone sprintf-compatible,\n * but it's a start.\n *\n * @param {string} format\n * @param {...} args\n * @return {string}\n */\n static sprintf(format, ...args)\n {\n var parts = format.split(/%([-+ 0#]?)([0-9]*)(\\.?)([0-9]*)([hlL]?)([A-Za-z%])/);\n var buffer = \"\";\n var partIndex = 0;\n for (var i = 0; i < args.length; i++) {\n\n var arg = args[i], d, s;\n buffer += parts[partIndex++];\n var flags = parts[partIndex];\n var minimum = +parts[partIndex+1] || 0;\n var precision = +parts[partIndex+3] || 0;\n var conversion = parts[partIndex+5];\n\n switch(conversion) {\n case 'd':\n case 'f':\n d = Math.trunc(arg);\n s = d + \"\";\n if (precision) {\n minimum -= (precision + 1);\n }\n if (s.length < minimum) {\n if (flags == '0') {\n if (d < 0) minimum--;\n s = (\"0000000000\" + Math.abs(d)).slice(-minimum);\n if (d < 0) s = '-' + s;\n } else {\n s = (\" \" + s).slice(-minimum);\n }\n }\n if (precision) {\n d = Math.trunc((arg - Math.trunc(arg)) * Math.pow(10, precision));\n s += '.' + (\"0000000000\" + Math.abs(d)).slice(-precision);\n }\n buffer += s;\n break;\n case 's':\n buffer += arg;\n break;\n default:\n /*\n * The supported ANSI C set of conversions: \"dioxXucsfeEgGpn%\"\n */\n buffer += \"(unrecognized printf conversion %\" + conversion + \")\";\n break;\n }\n\n partIndex += 6;\n }\n buffer += parts[partIndex];\n return buffer;\n }\n\n /**\n * stripLeadingZeros(s, fPad)\n *\n * @param {string} s\n * @param {boolean} [fPad]\n * @return {string}\n */\n static stripLeadingZeros(s, fPad)\n {\n var cch = s.length;\n s = s.replace(/^0+([0-9A-F]+)$/i, \"$1\");\n if (fPad) s = Str.pad(s, cch, true);\n return s;\n }\n\n /**\n * trim(s)\n *\n * @param {string} s\n * @return {string}\n */\n static trim(s)\n {\n if (String.prototype.trim) {\n return s.trim();\n }\n return s.replace(/^\\s+|\\s+$/g, \"\");\n }\n\n /**\n * toASCIICode(b)\n *\n * @param {number} b\n * @return {string}\n */\n static toASCIICode(b)\n {\n var s;\n if (b != Str.ASCII.CR && b != Str.ASCII.LF) {\n s = Str.ASCIICodeMap[b];\n }\n if (s) {\n s = '<' + s + '>';\n } else {\n s = String.fromCharCode(b);\n }\n return s;\n }\n}\n\n/*\n * Map special characters to their HTML escape sequences.\n */\nStr.HTMLEscapeMap = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n};\n\n/*\n * Map \"unprintable\" ASCII codes to mnemonics, to more clearly see what's being printed.\n */\nStr.ASCIICodeMap = {\n 0x00: \"NUL\",\n 0x01: \"SOH\", // (CTRL_A) Start of Heading\n 0x02: \"STX\", // (CTRL_B) Start of Text\n 0x03: \"ETX\", // (CTRL_C) End of Text\n 0x04: \"EOT\", // (CTRL_D) End of Transmission\n 0x05: \"ENQ\", // (CTRL_E) Enquiry\n 0x06: \"ACK\", // (CTRL_F) Acknowledge\n 0x07: \"BEL\", // (CTRL_G) Bell\n 0x08: \"BS\", // (CTRL_H) Backspace\n 0x09: \"TAB\", // (CTRL_I) Horizontal Tab (aka HT)\n 0x0A: \"LF\", // (CTRL_J) Line Feed (New Line)\n 0x0B: \"VT\", // (CTRL_K) Vertical Tab\n 0x0C: \"FF\", // (CTRL_L) Form Feed (New Page)\n 0x0D: \"CR\", // (CTRL_M) Carriage Return\n 0x0E: \"SO\", // (CTRL_N) Shift Out\n 0x0F: \"SI\", // (CTRL_O) Shift In\n 0x10: \"DLE\", // (CTRL_P) Data Link Escape\n 0x11: \"XON\", // (CTRL_Q) Device Control 1 (aka DC1)\n 0x12: \"DC2\", // (CTRL_R) Device Control 2\n 0x13: \"XOFF\", // (CTRL_S) Device Control 3 (aka DC3)\n 0x14: \"DC4\", // (CTRL_T) Device Control 4\n 0x15: \"NAK\", // (CTRL_U) Negative Acknowledge\n 0x16: \"SYN\", // (CTRL_V) Synchronous Idle\n 0x17: \"ETB\", // (CTRL_W) End of Transmission Block\n 0x18: \"CAN\", // (CTRL_X) Cancel\n 0x19: \"EM\", // (CTRL_Y) End of Medium\n 0x1A: \"SUB\", // (CTRL_Z) Substitute\n 0x1B: \"ESC\", // Escape\n 0x1C: \"FS\", // File Separator\n 0x1D: \"GS\", // Group Separator\n 0x1E: \"RS\", // Record Separator\n 0x1F: \"US\", // Unit Separator\n 0x7F: \"DEL\"\n};\n\n/*\n * Refer to: https://en.wikipedia.org/wiki/Code_page_437\n */\nStr.CP437ToUnicode = [\n '\\u0000', '\\u263A', '\\u263B', '\\u2665', '\\u2666', '\\u2663', '\\u2660', '\\u2022',\n '\\u25D8', '\\u25CB', '\\u25D9', '\\u2642', '\\u2640', '\\u266A', '\\u266B', '\\u263C',\n '\\u25BA', '\\u25C4', '\\u2195', '\\u203C', '\\u00B6', '\\u00A7', '\\u25AC', '\\u21A8',\n '\\u2191', '\\u2193', '\\u2192', '\\u2190', '\\u221F', '\\u2194', '\\u25B2', '\\u25BC',\n '\\u0020', '\\u0021', '\\u0022', '\\u0023', '\\u0024', '\\u0025', '\\u0026', '\\u0027',\n '\\u0028', '\\u0029', '\\u002A', '\\u002B', '\\u002C', '\\u002D', '\\u002E', '\\u002F',\n '\\u0030', '\\u0031', '\\u0032', '\\u0033', '\\u0034', '\\u0035', '\\u0036', '\\u0037',\n '\\u0038', '\\u0039', '\\u003A', '\\u003B', '\\u003C', '\\u003D', '\\u003E', '\\u003F',\n '\\u0040', '\\u0041', '\\u0042', '\\u0043', '\\u0044', '\\u0045', '\\u0046', '\\u0047',\n '\\u0048', '\\u0049', '\\u004A', '\\u004B', '\\u004C', '\\u004D', '\\u004E', '\\u004F',\n '\\u0050', '\\u0051', '\\u0052', '\\u0053', '\\u0054', '\\u0055', '\\u0056', '\\u0057',\n '\\u0058', '\\u0059', '\\u005A', '\\u005B', '\\u005C', '\\u005D', '\\u005E', '\\u005F',\n '\\u0060', '\\u0061', '\\u0062', '\\u0063', '\\u0064', '\\u0065', '\\u0066', '\\u0067',\n '\\u0068', '\\u0069', '\\u006A', '\\u006B', '\\u006C', '\\u006D', '\\u006E', '\\u006F',\n '\\u0070', '\\u0071', '\\u0072', '\\u0073', '\\u0074', '\\u0075', '\\u0076', '\\u0077',\n '\\u0078', '\\u0079', '\\u007A', '\\u007B', '\\u007C', '\\u007D', '\\u007E', '\\u2302',\n '\\u00C7', '\\u00FC', '\\u00E9', '\\u00E2', '\\u00E4', '\\u00E0', '\\u00E5', '\\u00E7',\n '\\u00EA', '\\u00EB', '\\u00E8', '\\u00EF', '\\u00EE', '\\u00EC', '\\u00C4', '\\u00C5',\n '\\u00C9', '\\u00E6', '\\u00C6', '\\u00F4', '\\u00F6', '\\u00F2', '\\u00FB', '\\u00F9',\n '\\u00FF', '\\u00D6', '\\u00DC', '\\u00A2', '\\u00A3', '\\u00A5', '\\u20A7', '\\u0192',\n '\\u00E1', '\\u00ED', '\\u00F3', '\\u00FA', '\\u00F1', '\\u00D1', '\\u00AA', '\\u00BA',\n '\\u00BF', '\\u2310', '\\u00AC', '\\u00BD', '\\u00BC', '\\u00A1', '\\u00AB', '\\u00BB',\n '\\u2591', '\\u2592', '\\u2593', '\\u2502', '\\u2524', '\\u2561', '\\u2562', '\\u2556',\n '\\u2555', '\\u2563', '\\u2551', '\\u2557', '\\u255D', '\\u255C', '\\u255B', '\\u2510',\n '\\u2514', '\\u2534', '\\u252C', '\\u251C', '\\u2500', '\\u253C', '\\u255E', '\\u255F',\n '\\u255A', '\\u2554', '\\u2569', '\\u2566', '\\u2560', '\\u2550', '\\u256C', '\\u2567',\n '\\u2568', '\\u2564', '\\u2565', '\\u2559', '\\u2558', '\\u2552', '\\u2553', '\\u256B',\n '\\u256A', '\\u2518', '\\u250C', '\\u2588', '\\u2584', '\\u258C', '\\u2590', '\\u2580',\n '\\u03B1', '\\u00DF', '\\u0393', '\\u03C0', '\\u03A3', '\\u03C3', '\\u00B5', '\\u03C4',\n '\\u03A6', '\\u0398', '\\u03A9', '\\u03B4', '\\u221E', '\\u03C6', '\\u03B5', '\\u2229',\n '\\u2261', '\\u00B1', '\\u2265', '\\u2264', '\\u2320', '\\u2321', '\\u00F7', '\\u2248',\n '\\u00B0', '\\u2219', '\\u00B7', '\\u221A', '\\u207F', '\\u00B2', '\\u25A0', '\\u00A0'\n];\n\n/*\n * TODO: Future home of a complete ASCII table.\n */\nStr.ASCII = {\n LF: 0x0A,\n CR: 0x0D\n};\n\nStr.TYPES = {\n NULL: 0,\n BYTE: 1,\n WORD: 2,\n DWORD: 3,\n NUMBER: 4,\n STRING: 5,\n BOOLEAN: 6,\n OBJECT: 7,\n ARRAY: 8\n};\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2018\n */\n\n/**\n * @typedef {{\n * mask: number,\n * shift: number\n * }}\n */\nvar BitField;\n\n/**\n * @typedef {Object.}\n */\nvar BitFields;\n\nclass Usr {\n /**\n * binarySearch(a, v, fnCompare)\n *\n * @param {Array} a is an array\n * @param {number|string|Array|Object} v\n * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]\n * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point\n */\n static binarySearch(a, v, fnCompare)\n {\n var left = 0;\n var right = a.length;\n var found = 0;\n if (fnCompare === undefined) {\n fnCompare = function(a, b)\n {\n return a > b ? 1 : a < b ? -1 : 0;\n };\n }\n while (left < right) {\n var middle = (left + right) >> 1;\n var compareResult;\n compareResult = fnCompare(v, a[middle]);\n if (compareResult > 0) {\n left = middle + 1;\n } else {\n right = middle;\n found = !compareResult;\n }\n }\n return found ? left : ~left;\n }\n\n /**\n * binaryInsert(a, v, fnCompare)\n *\n * If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the\n * element is inserted into the array at the appropriate index.\n *\n * @param {Array} a is an array\n * @param {number|string|Array|Object} v is the value to insert\n * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]\n */\n static binaryInsert(a, v, fnCompare)\n {\n var index = Usr.binarySearch(a, v, fnCompare);\n if (index < 0) {\n a.splice(-(index + 1), 0, v);\n }\n }\n\n /**\n * getTimestamp()\n *\n * @return {string} timestamp containing the current date and time (\"yyyy-mm-dd hh:mm:ss\")\n */\n static getTimestamp()\n {\n return Usr.formatDate(\"Y-m-d H:i:s\");\n }\n\n /**\n * getMonthDays(nMonth, nYear)\n *\n * Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100),\n * so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails\n * the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap\n * year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year.\n *\n * TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD,\n * so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and\n * we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what\n * a real RTC actually does.\n *\n * @param {number} nMonth (1-12)\n * @param {number} nYear (normally a 4-digit year, but it may also be mod 100)\n * @return {number} the maximum (1-based) day allowed for the specified month and year\n */\n static getMonthDays(nMonth, nYear)\n {\n var nDays = Usr.aMonthDays[nMonth - 1];\n if (nDays == 28) {\n if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) {\n nDays++;\n }\n }\n return nDays;\n }\n\n /**\n * formatDate(sFormat, date)\n *\n * @param {string} sFormat (eg, \"F j, Y\", \"Y-m-d H:i:s\")\n * @param {Date} [date] (default is the current time)\n * @return {string}\n *\n * Supported identifiers in sFormat include:\n *\n * a: lowercase ante meridiem and post meridiem (am or pm)\n * d: day of the month, 2 digits with leading zeros (01,02,...,31)\n * D: 3-letter day of the week (\"Sun\",\"Mon\",...,\"Sat\")\n * F: month (\"January\",\"February\",...,\"December\")\n * g: hour in 12-hour format, without leading zeros (1,2,...,12)\n * h: hour in 24-hour format, without leading zeros (0,1,...,23)\n * H: hour in 24-hour format, with leading zeros (00,01,...,23)\n * i: minutes, with leading zeros (00,01,...,59)\n * j: day of the month, without leading zeros (1,2,...,31)\n * l: day of the week (\"Sunday\",\"Monday\",...,\"Saturday\")\n * m: month, with leading zeros (01,02,...,12)\n * M: 3-letter month (\"Jan\",\"Feb\",...,\"Dec\")\n * n: month, without leading zeros (1,2,...,12)\n * s: seconds, with leading zeros (00,01,...,59)\n * y: 2-digit year (eg, 14)\n * Y: 4-digit year (eg, 2014)\n *\n * For more inspiration, see: http://php.net/manual/en/function.date.php (of which we support ONLY a subset).\n */\n static formatDate(sFormat, date)\n {\n var sDate = \"\";\n if (!date) date = new Date();\n var iHour = date.getHours();\n var iDay = date.getDate();\n var iMonth = date.getMonth() + 1;\n for (var i = 0; i < sFormat.length; i++) {\n var ch;\n switch ((ch = sFormat.charAt(i))) {\n case 'a':\n sDate += (iHour < 12 ? \"am\" : \"pm\");\n break;\n case 'd':\n sDate += ('0' + iDay).slice(-2);\n break;\n case 'D':\n sDate += Usr.asDays[date.getDay()].substr(0, 3);\n break;\n case 'F':\n sDate += Usr.asMonths[iMonth - 1];\n break;\n case 'g':\n sDate += (!iHour ? 12 : (iHour > 12 ? iHour - 12 : iHour));\n break;\n case 'h':\n sDate += iHour;\n break;\n case 'H':\n sDate += ('0' + iHour).slice(-2);\n break;\n case 'i':\n sDate += ('0' + date.getMinutes()).slice(-2);\n break;\n case 'j':\n sDate += iDay;\n break;\n case 'l':\n sDate += Usr.asDays[date.getDay()];\n break;\n case 'm':\n sDate += ('0' + iMonth).slice(-2);\n break;\n case 'M':\n sDate += Usr.asMonths[iMonth - 1].substr(0, 3);\n break;\n case 'n':\n sDate += iMonth;\n break;\n case 's':\n sDate += ('0' + date.getSeconds()).slice(-2);\n break;\n case 'y':\n sDate += (\"\" + date.getFullYear()).slice(-2);\n break;\n case 'Y':\n sDate += date.getFullYear();\n break;\n default:\n sDate += ch;\n break;\n }\n }\n return sDate;\n }\n\n /**\n * defineBitFields(bfs)\n *\n * Prepares a bit field definition for use with getBitField() and setBitField(); eg:\n *\n * var bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3});\n *\n * The above defines a set of bit fields containing four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31).\n *\n * Usr.setBitField(bfs.num, n, 1);\n *\n * The above set bit field \"bfs.num\" in numeric variable \"n\" to the value 1.\n *\n * @param {Object} bfs\n * @return {BitFields}\n */\n static defineBitFields(bfs)\n {\n var bit = 0;\n for (var f in bfs) {\n var width = bfs[f];\n var mask = ((1 << width) - 1) << bit;\n bfs[f] = {mask: mask, shift: bit};\n bit += width;\n }\n return bfs;\n }\n\n /**\n * initBitFields(bfs, ...)\n *\n * @param {BitFields} bfs\n * @param {...number} var_args\n * @return {number} a value containing all supplied bit fields\n */\n static initBitFields(bfs, var_args)\n {\n var v = 0, i = 1;\n for (var f in bfs) {\n if (i >= arguments.length) break;\n v = Usr.setBitField(bfs[f], v, arguments[i++]);\n }\n return v;\n }\n\n /**\n * getBitField(bf, v)\n *\n * @param {BitField} bf\n * @param {number} v is a value containing bit fields\n * @return {number} the value of the bit field in v defined by bf\n */\n static getBitField(bf, v)\n {\n return (v & bf.mask) >> bf.shift;\n }\n\n /**\n * setBitField(bf, v, n)\n *\n * @param {BitField} bf\n * @param {number} v is a value containing bit fields\n * @param {number} n is a value to store in v in the bit field defined by bf\n * @return {number} updated v\n */\n static setBitField(bf, v, n)\n {\n return (v & ~bf.mask) | ((n << bf.shift) & bf.mask);\n }\n\n /**\n * indexOf(a, t, i)\n *\n * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it.\n *\n * @param {Array} a\n * @param {*} t\n * @param {number} [i]\n * @returns {number}\n */\n static indexOf(a, t, i)\n {\n if (Array.prototype.indexOf) {\n return a.indexOf(t, i);\n }\n i = i || 0;\n if (i < 0) i += a.length;\n if (i < 0) i = 0;\n for (var n = a.length; i < n; i++) {\n if (i in a && a[i] === t) return i;\n }\n return -1;\n }\n}\n\nUsr.asDays = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\nUsr.asMonths = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\nUsr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n\n/**\n * getTime()\n *\n * @return {number} the current time, in milliseconds\n */\nUsr.getTime = Date.now || function() { return +new Date(); };\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2018\n */\n\n\n/*\n * According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties\n * and functions of JavaScript-in-the-Browser:\n *\n * Property Description\n * ---\n * Infinity A numeric value that represents positive/negative infinity\n * NaN \"Not-a-Number\" value\n * undefined Indicates that a variable has not been assigned a value\n *\n * Function Description\n * ---\n * decodeURI() Decodes a URI\n * decodeURIComponent() Decodes a URI component\n * encodeURI() Encodes a URI\n * encodeURIComponent() Encodes a URI component\n * escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead\n * eval() Evaluates a string and executes it as if it was script code\n * isFinite() Determines whether a value is a finite, legal number\n * isNaN() Determines whether a value is an illegal number\n * Number() Converts an object's value to a number\n * parseFloat() Parses a string and returns a floating point number\n * parseInt() Parses a string and returns an integer\n * String() Converts an object's value to a string\n * unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead\n *\n * And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions\n * of the *window* object.\n *\n * Property Description\n * ---\n * closed Returns a Boolean value indicating whether a window has been closed or not\n * defaultStatus Sets or returns the default text in the statusbar of a window\n * document Returns the Document object for the window (See Document object)\n * frames Returns an array of all the frames (including iframes) in the current window\n * history Returns the History object for the window (See History object)\n * innerHeight Returns the inner height of a window's content area\n * innerWidth Returns the inner width of a window's content area\n * length Returns the number of frames (including iframes) in a window\n * location Returns the Location object for the window (See Location object)\n * name Sets or returns the name of a window\n * navigator Returns the Navigator object for the window (See Navigator object)\n * opener Returns a reference to the window that created the window\n * outerHeight Returns the outer height of a window, including toolbars/scrollbars\n * outerWidth Returns the outer width of a window, including toolbars/scrollbars\n * pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window\n * pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window\n * parent Returns the parent window of the current window\n * screen Returns the Screen object for the window (See Screen object)\n * screenLeft Returns the x coordinate of the window relative to the screen\n * screenTop Returns the y coordinate of the window relative to the screen\n * screenX Returns the x coordinate of the window relative to the screen\n * screenY Returns the y coordinate of the window relative to the screen\n * self Returns the current window\n * status Sets or returns the text in the statusbar of a window\n * top Returns the topmost browser window\n *\n * Method Description\n * ---\n * alert() Displays an alert box with a message and an OK button\n * atob() Decodes a base-64 encoded string\n * blur() Removes focus from the current window\n * btoa() Encodes a string in base-64\n * clearInterval() Clears a timer set with setInterval()\n * clearTimeout() Clears a timer set with setTimeout()\n * close() Closes the current window\n * confirm() Displays a dialog box with a message and an OK and a Cancel button\n * createPopup() Creates a pop-up window\n * focus() Sets focus to the current window\n * moveBy() Moves a window relative to its current position\n * moveTo() Moves a window to the specified position\n * open() Opens a new browser window\n * print() Prints the content of the current window\n * prompt() Displays a dialog box that prompts the visitor for input\n * resizeBy() Resizes the window by the specified pixels\n * resizeTo() Resizes the window to the specified width and height\n * scroll() This method has been replaced by the scrollTo() method.\n * scrollBy() Scrolls the content by the specified number of pixels\n * scrollTo() Scrolls the content to the specified coordinates\n * setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds)\n * setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds\n * stop() Stops the window from loading\n */\n\nclass Web {\n /**\n * log(s, type)\n *\n * For diagnostic output only. DEBUG must be true (or \"--debug\" specified via the command-line)\n * for Component.log() to display anything.\n *\n * @param {string} [s] is the message text\n * @param {string} [type] is the message type\n */\n static log(s, type)\n {\n Component.log(s, type);\n }\n\n /**\n * notice(s, fPrintOnly, id)\n *\n * @param {string} s is the message text\n * @param {boolean} [fPrintOnly]\n * @param {string} [id] is the caller's ID, if any\n */\n static notice(s, fPrintOnly, id)\n {\n Component.notice(s, fPrintOnly, id);\n }\n\n /**\n * alertUser(sMessage)\n * \n * NOTE: Legacy function for older modules (eg, DiskDump); see Component.alertUser().\n *\n * @param {string} sMessage\n */\n static alertUser(sMessage)\n {\n if (window) {\n window.alert(sMessage);\n } else {\n Web.log(sMessage);\n }\n }\n\n /**\n * getResource(sURL, type, fAsync, done, progress)\n *\n * Request the specified resource (sURL), and once the request is complete, notify done().\n *\n * If fAsync is true, a done() callback should ALWAYS be supplied; otherwise, you'll have no\n * idea when the request is complete or what the response was. done() is passed three parameters:\n *\n * done(sURL, resource, nErrorCode)\n *\n * If nErrorCode is zero, resource should contain the requested data; otherwise, an error occurred.\n *\n * If type is set to a string, that string can be used to control the response format;\n * by default, the response format is plain text, but you can specify \"arraybuffer\" to request arbitrary\n * binary data, in which case the returned resource will be a ArrayBuffer rather than a string.\n *\n * @param {string} sURL\n * @param {string|Object|null} [type] (object for POST request, otherwise type of GET request)\n * @param {boolean} [fAsync] is true for an asynchronous request; false otherwise (MUST be set for IE)\n * @param {function(string,string,number)} [done]\n * @param {function(number)} [progress]\n * @return {Array|null} Array containing [resource, nErrorCode], or null if no response available (yet)\n */\n static getResource(sURL, type = \"text\", fAsync = false, done, progress)\n {\n var nErrorCode = 0, resource = null, response = null;\n\n if (typeof resources == 'object' && (resource = resources[sURL])) {\n if (done) done(sURL, resource, nErrorCode);\n return [resource, nErrorCode];\n }\n else if (fAsync && typeof resources == 'function') {\n resources(sURL, function(resource, nErrorCode)\n {\n if (done) done(sURL, resource, nErrorCode);\n });\n return response;\n }\n\n if (DEBUG) {\n /*\n * The larger resources we put on archive.pcjs.org should also be available locally.\n *\n * NOTE: \"http://archive.pcjs.org\" is now \"https://s3-us-west-2.amazonaws.com/archive.pcjs.org\"\n */\n sURL = sURL.replace(/^(http:\\/\\/archive\\.pcjs\\.org|https:\\/\\/s3-us-west-2\\.amazonaws\\.com\\/archive\\.pcjs\\.org)(\\/.*)\\/([^\\/]*)$/, \"$2/archive/$3\");\n }\n\n\n var request = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject(\"Microsoft.XMLHTTP\"));\n var fArrayBuffer = false, fXHR2 = (typeof request.responseType === 'string');\n \n var callback = function() {\n if (request.readyState !== 4) {\n if (progress) progress(1);\n return null;\n }\n /*\n * The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple\n * times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when\n * debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests\n * pending, any debugging activity means most of them simply get dropped on floor, so what may actually be\n * happening are mis-notifications rather than redundant notifications.\n *\n * request.onreadystatechange = undefined;\n */\n /*\n * If the request failed due to, say, a CORS policy denial; eg:\n * \n * Failed to load http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img:\n * Redirect from 'http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img' to\n * 'http://www.allbootdisks.com/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.\n * Origin 'http://pcjs:8088' is therefore not allowed access.\n * \n * and our request type was \"arraybuffer\", attempting to access responseText may trigger an exception; eg:\n * \n * Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's\n * 'responseType' is '' or 'text' (was 'arraybuffer').\n * \n * We could tiptoe around these potential landmines, but the safest thing to do is wrap this code with try/catch.\n */\n try {\n resource = fArrayBuffer? request.response : request.responseText;\n } catch(err) {\n if (MAXDEBUG) Web.log(\"xmlHTTPRequest(\" + sURL + \") exception: \" + err.message);\n }\n /*\n * The normal \"success\" case is a non-null resource and an HTTP status code of 200, but when loading files from the\n * local file system (ie, when using the \"file:\" protocol), we have to be a bit more flexible.\n */\n if (resource != null && (request.status == 200 || !request.status && resource.length && Web.getHostProtocol() == \"file:\")) {\n if (MAXDEBUG) Web.log(\"xmlHTTPRequest(\" + sURL + \"): returned \" + resource.length + \" bytes\");\n }\n else {\n nErrorCode = request.status || -1;\n Web.log(\"xmlHTTPRequest(\" + sURL + \"): error code \" + nErrorCode);\n }\n if (progress) progress(2);\n if (done) done(sURL, resource, nErrorCode);\n return [resource, nErrorCode];\n };\n \n if (fAsync) {\n request.onreadystatechange = callback;\n }\n\n if (progress) progress(0);\n\n if (type && typeof type == \"object\") {\n var sPost = \"\";\n for (var p in type) {\n if (!type.hasOwnProperty(p)) continue;\n if (sPost) sPost += \"&\";\n sPost += p + '=' + encodeURIComponent(type[p]);\n }\n sPost = sPost.replace(/%20/g, '+');\n if (MAXDEBUG) Web.log(\"Web.getResource(POST \" + sURL + \"): \" + sPost.length + \" bytes\");\n request.open(\"POST\", sURL, fAsync);\n request.setRequestHeader(\"Content-type\", \"application/x-www-form-urlencoded\");\n request.send(sPost);\n } else {\n if (MAXDEBUG) Web.log(\"Web.getResource(GET \" + sURL + \")\");\n request.open(\"GET\", sURL, fAsync);\n if (type == \"arraybuffer\") {\n if (fXHR2) {\n fArrayBuffer = true;\n request.responseType = type;\n } else {\n request.overrideMimeType(\"text/plain; charset=x-user-defined\");\n }\n }\n request.send();\n }\n\n if (!fAsync) {\n request.readyState = 4; // this may already be set for synchronous requests, but I don't want to take any chances \n response = callback();\n }\n return response;\n }\n\n /**\n * parseMemoryResource(sURL, sData)\n *\n * This converts a variety of JSON-style data streams into an Object with the following properties:\n *\n * aBytes\n * aSymbols\n * addrLoad\n * addrExec\n *\n * If the source data contains a 'bytes' array, it's passed through to 'aBytes'; alternatively, if\n * it contains a 'words' array, the values are converted from 16-bit to 8-bit and stored in 'aBytes',\n * and if it contains a 'longs' array, the values are converted from 32-bit longs into bytes and\n * stored in 'aBytes'.\n *\n * Alternatively, if the source data contains a 'data' array, we simply pass that through to the output\n * object as:\n *\n * aData\n *\n * @param {string} sURL\n * @param {string} sData\n * @return {Object|null} (resource)\n */\n static parseMemoryResource(sURL, sData)\n {\n var i;\n var resource = {\n aBytes: null,\n aSymbols: null,\n addrLoad: null,\n addrExec: null\n };\n\n if (sData.charAt(0) == \"[\" || sData.charAt(0) == \"{\") {\n try {\n var a, ib, data;\n\n if (sData.substr(0, 1) == \"<\") { // if the \"data\" begins with a \"<\"...\n /*\n * Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid,\n * but more recent server configs now display a somewhat friendlier HTML error page. The downside,\n * however, is that the original error has been buried, and we've received \"data\" that isn't actually\n * tape data. So if the data we've received appears to be \"HTML-like\", we treat it as an error message.\n */\n throw new Error(sData);\n }\n\n /*\n * TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to\n * eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in\n * IE9 with an \"Out of memory\" exception. One work-around would be to chop the data into chunks\n * (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.\n *\n * However, it turns out that using JSON.parse(sDiskData) instead of eval(\"(\" + sDiskData + \")\")\n * is a much easier fix. The only drawback is that we must first quote any unquoted property names\n * and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;\n * the following RegExp replacements take care of those requirements.\n *\n * The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and\n * while I've stopped using hex values in DumpAPI responses (at least when \"format=json\" is specified),\n * I can't guarantee they won't show up in \"legacy\" images, and there's no simple RegExp replacement\n * for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect\n * any hex prefixes (\"0x\") in the sequence. Ditto for error messages, which appear like so:\n *\n * [\"unrecognized disk path: test.img\"]\n */\n if (sData.indexOf(\"0x\") < 0 && sData.indexOf(\"0o\") < 0 && sData.substr(0, 2) != '[\"') {\n data = JSON.parse(sData.replace(/([a-z]+):/gm, '\"$1\":').replace(/\\/\\/[^\\n]*/gm, \"\"));\n } else {\n data = eval(\"(\" + sData + \")\");\n }\n\n resource.addrLoad = data['load'];\n resource.addrExec = data['exec'];\n\n if (a = data['bytes']) {\n resource.aBytes = a;\n }\n else if (a = data['words']) {\n /*\n * Convert all words into bytes\n */\n resource.aBytes = new Array(a.length * 2);\n for (i = 0, ib = 0; i < a.length; i++) {\n resource.aBytes[ib++] = a[i] & 0xff;\n resource.aBytes[ib++] = (a[i] >> 8) & 0xff;\n\n }\n }\n else if (a = data['longs']) {\n /*\n * Convert all dwords (longs) into bytes\n */\n resource.aBytes = new Array(a.length * 4);\n for (i = 0, ib = 0; i < a.length; i++) {\n resource.aBytes[ib++] = a[i] & 0xff;\n resource.aBytes[ib++] = (a[i] >> 8) & 0xff;\n resource.aBytes[ib++] = (a[i] >> 16) & 0xff;\n resource.aBytes[ib++] = (a[i] >> 24) & 0xff;\n }\n }\n else if (a = data['data']) {\n resource.aData = a;\n }\n else {\n resource.aBytes = data;\n }\n\n if (resource.aBytes) {\n if (!resource.aBytes.length) {\n Component.error(\"Empty resource: \" + sURL);\n resource = null;\n }\n else if (resource.aBytes.length == 1) {\n Component.error(resource.aBytes[0]);\n resource = null;\n }\n }\n resource.aSymbols = data['symbols'];\n\n } catch (e) {\n Component.error(\"Resource data error (\" + sURL + \"): \" + e.message);\n resource = null;\n }\n }\n else {\n /*\n * Parse the data manually; we assume it's a series of hex byte-values separated by whitespace.\n */\n var ab = [];\n var sHexData = sData.replace(/\\n/gm, \" \").replace(/ +$/, \"\");\n var asHexData = sHexData.split(\" \");\n for (i = 0; i < asHexData.length; i++) {\n var n = parseInt(asHexData[i], 16);\n if (isNaN(n)) {\n Component.error(\"Resource data error (\" + sURL + \"): invalid hex byte (\" + asHexData[i] + \")\");\n break;\n }\n ab.push(n & 0xff);\n }\n if (i == asHexData.length) resource.aBytes = ab;\n }\n return resource;\n }\n\n /**\n * sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)\n *\n * Send a report (eg, bug report) to the server.\n *\n * @param {string} sApp (eg, \"PCjs\")\n * @param {string} sVer (eg, \"1.02\")\n * @param {string} sURL (eg, \"/devices/pc/machine/5150/mda/64kb/machine.xml\")\n * @param {string} sUser (ie, the user key, if any)\n * @param {string} sType (eg, \"bug\"); one of ReportAPI.TYPE.*\n * @param {string} sReport (eg, unparsed state data)\n * @param {string} [sHostName] (default is http://SITEHOST)\n */\n static sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)\n {\n var dataPost = {};\n dataPost[ReportAPI.QUERY.APP] = sApp;\n dataPost[ReportAPI.QUERY.VER] = sVer;\n dataPost[ReportAPI.QUERY.URL] = sURL;\n dataPost[ReportAPI.QUERY.USER] = sUser;\n dataPost[ReportAPI.QUERY.TYPE] = sType;\n dataPost[ReportAPI.QUERY.DATA] = sReport;\n var sReportURL = (sHostName? sHostName : \"http://\" + SITEHOST) + ReportAPI.ENDPOINT;\n Web.getResource(sReportURL, dataPost, true);\n }\n\n /**\n * getHost()\n *\n * @return {string}\n */\n static getHost()\n {\n return (\"http://\" + (window? window.location.host : SITEHOST));\n }\n\n /**\n * getHostURL()\n *\n * @return {string|null}\n */\n static getHostURL()\n {\n return (window? window.location.href : null);\n }\n\n /**\n * getHostProtocol()\n *\n * @return {string}\n */\n static getHostProtocol()\n {\n return (window? window.location.protocol : \"file:\");\n }\n\n /**\n * getUserAgent()\n *\n * @return {string}\n */\n static getUserAgent()\n {\n return (window? window.navigator.userAgent : \"\");\n }\n\n /**\n * hasLocalStorage\n *\n * true if localStorage support exists, is enabled, and works; false otherwise\n *\n * @return {boolean}\n */\n static hasLocalStorage()\n {\n if (Web.fLocalStorage == null) {\n var f = false;\n if (window) {\n try {\n window.localStorage.setItem(Web.sLocalStorageTest, Web.sLocalStorageTest);\n f = (window.localStorage.getItem(Web.sLocalStorageTest) == Web.sLocalStorageTest);\n window.localStorage.removeItem(Web.sLocalStorageTest);\n } catch (e) {\n Web.logLocalStorageError(e);\n f = false;\n }\n }\n Web.fLocalStorage = f;\n }\n return Web.fLocalStorage;\n }\n\n /**\n * logLocalStorageError(e)\n *\n * @param {Error} e is an exception\n */\n static logLocalStorageError(e)\n {\n Web.log(e.message, \"localStorage error\");\n }\n\n /**\n * getLocalStorageItem(sKey)\n *\n * Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available\n *\n * @param {string} sKey\n * @return {string|null|undefined} sValue\n */\n static getLocalStorageItem(sKey)\n {\n var sValue;\n if (window) {\n try {\n sValue = window.localStorage.getItem(sKey);\n } catch (e) {\n Web.logLocalStorageError(e);\n }\n }\n return sValue;\n }\n\n /**\n * setLocalStorageItem(sKey, sValue)\n *\n * @param {string} sKey\n * @param {string} sValue\n * @return {boolean} true if localStorage is available, false if not\n */\n static setLocalStorageItem(sKey, sValue)\n {\n try {\n window.localStorage.setItem(sKey, sValue);\n return true;\n } catch (e) {\n Web.logLocalStorageError(e);\n }\n return false;\n }\n\n /**\n * removeLocalStorageItem(sKey)\n *\n * @param {string} sKey\n */\n static removeLocalStorageItem(sKey)\n {\n try {\n window.localStorage.removeItem(sKey);\n } catch (e) {\n Web.logLocalStorageError(e);\n }\n }\n\n /**\n * getLocalStorageKeys()\n *\n * @return {Array}\n */\n static getLocalStorageKeys()\n {\n var a = [];\n try {\n for (var i = 0, c = window.localStorage.length; i < c; i++) {\n a.push(window.localStorage.key(i));\n }\n } catch (e) {\n Web.logLocalStorageError(e);\n }\n return a;\n }\n\n /**\n * reloadPage()\n */\n static reloadPage()\n {\n if (window) window.location.reload();\n }\n\n /**\n * isUserAgent(s)\n *\n * Check the browser's user-agent string for the given substring; \"iOS\" and \"MSIE\" are special values you can\n * use that will match any iOS or MSIE browser, respectively (even IE11, in the case of \"MSIE\").\n *\n * 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating\n * the \"MSIE\" string (which MSDN calls a \"version token\"; see http://msdn.microsoft.com/library/ms537503.aspx);\n * they say \"public websites should rely on feature detection, rather than browser detection, in order to design\n * their sites for browsers that don't support the features used by the website.\" So, in IE11, we get a user-agent\n * that tries to fool apps into thinking the browser is more like WebKit or Gecko:\n *\n * Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko\n *\n * That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained\n * some very critical browser-specific code; turning on IE's \"Compatibility Mode\" didn't help either, because\n * that's a sledgehammer solution which restores the old user-agent string but also disables other features like\n * HTML5 canvas support. As an interim solution, I'm treating any \"MSIE\" check as a check for either \"MSIE\" or\n * \"Trident\".\n *\n * UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't\n * any code that cares about \"MSIE\", but I've left the change in place, because I wouldn't be surprised if I'll\n * need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture.\n *\n * @param {string} s is a substring to search for in the user-agent; as noted above, \"iOS\" and \"MSIE\" are special values\n * @return {boolean} is true if the string was found, false if not\n */\n static isUserAgent(s)\n {\n if (window) {\n var userAgent = Web.getUserAgent();\n /*\n * Here's one case where we have to be careful with Component, because when isUserAgent() is called by\n * the init code below, component.js hasn't been loaded yet. The simple solution for now is to remove the call.\n *\n * Web.log(\"agent: \" + userAgent);\n *\n * And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure\n * Compiler (v20130823) failing to detect the entire expression as a boolean.\n */\n return s == \"iOS\" && !!userAgent.match(/(iPod|iPhone|iPad)/) && !!userAgent.match(/AppleWebKit/) || s == \"MSIE\" && !!userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0);\n }\n return false;\n }\n\n /**\n * isMobile()\n *\n * Check the browser's user-agent string for the substring \"Mobi\", as per Mozilla recommendation:\n *\n * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent\n *\n * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not\n */\n static isMobile()\n {\n return Web.isUserAgent(\"Mobi\");\n }\n\n /**\n * findProperty(obj, sProp, sSuffix)\n *\n * If both sProp and sSuffix are set, then any browser-specific prefixes are inserted between sProp and sSuffix,\n * and if a match is found, it is returned without sProp.\n *\n * For example, if findProperty(document, 'on', 'fullscreenchange') discovers that 'onwebkitfullscreenchange' exists,\n * it will return 'webkitfullscreenchange', in preparation for an addEventListener() call.\n *\n * More commonly, sSuffix is not used, so whatever property is found is returned as-is.\n *\n * @param {Object|null|undefined} obj\n * @param {string} sProp\n * @param {string} [sSuffix]\n * @return {string|null}\n */\n static findProperty(obj, sProp, sSuffix)\n {\n if (obj) {\n for (var i = 0; i < Web.asBrowserPrefixes.length; i++) {\n var sName = Web.asBrowserPrefixes[i];\n if (sSuffix) {\n sName += sSuffix;\n var sEvent = sProp + sName;\n if (sEvent in obj) return sName;\n } else {\n if (!sName) {\n sName = sProp[0];\n } else {\n sName += sProp[0].toUpperCase();\n }\n sName += sProp.substr(1);\n if (sName in obj) return sName;\n }\n }\n }\n return null;\n }\n\n /**\n * getURLParm(sParm)\n *\n * First looks for sParm exactly as specified, then looks for the lower-case version.\n *\n * @param {string} sParm\n * @return {string|undefined}\n */\n static getURLParm(sParm)\n {\n if (!Web.parmsURL) {\n Web.parmsURL = Web.parseURLParms();\n }\n return Web.parmsURL[sParm] || Web.parmsURL[sParm.toLowerCase()];\n }\n\n /**\n * parseURLParms(sParms)\n *\n * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?')\n * @return {Object} containing properties for each parameter found\n */\n static parseURLParms(sParms)\n {\n var aParms = {};\n if (window) { // an alternative to \"if (typeof module === 'undefined')\" if require(\"defines\") was used\n if (!sParms) {\n /*\n * Note that window.location.href returns the entire URL, whereas window.location.search\n * returns only the parameters, if any (starting with the '?', which we skip over with a substr() call).\n */\n sParms = window.location.search.substr(1);\n }\n var match;\n var pl = /\\+/g; // RegExp for replacing addition symbol with a space\n var search = /([^&=]+)=?([^&]*)/g;\n var decode = function(s)\n {\n return decodeURIComponent(s.replace(pl, \" \"));\n };\n\n while ((match = search.exec(sParms))) {\n aParms[decode(match[1])] = decode(match[2]);\n }\n }\n return aParms;\n }\n\n /**\n * downloadFile(sData, sType, fBase64, sFileName)\n *\n * @param {string} sData\n * @param {string} sType\n * @param {boolean} [fBase64]\n * @param {string} [sFileName]\n */\n static downloadFile(sData, sType, fBase64, sFileName)\n {\n var link = null, sAlert;\n var sURI = \"data:application/\" + sType + (fBase64? \";base64\" : \"\") + \",\";\n\n if (!Web.isUserAgent(\"Firefox\")) {\n sURI += (fBase64? sData : encodeURI(sData));\n } else {\n sURI += (fBase64? sData : encodeURIComponent(sData));\n }\n if (sFileName) {\n link = document.createElement('a');\n if (typeof link.download != 'string') link = null;\n }\n if (link) {\n link.href = sURI;\n link.download = sFileName;\n document.body.appendChild(link); // Firefox allegedly requires the link to be in the body\n link.click();\n document.body.removeChild(link);\n sAlert = 'Check your Downloads folder for ' + sFileName + '.';\n } else {\n window.open(sURI);\n sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.';\n }\n return sAlert;\n }\n\n /**\n * onCountRepeat(n, fnRepeat, fnComplete, msDelay)\n *\n * Call fnRepeat() n times with an msDelay millisecond delay between calls,\n * then call fnComplete() when n has been exhausted OR fnRepeat() returns false.\n *\n * @param {number} n\n * @param {function()} fnRepeat\n * @param {function()} fnComplete\n * @param {number} [msDelay]\n */\n static onCountRepeat(n, fnRepeat, fnComplete, msDelay)\n {\n var fnTimeout = function doCountRepeat()\n {\n n -= 1;\n if (n >= 0) {\n if (!fnRepeat()) n = 0;\n }\n if (n > 0) {\n setTimeout(fnTimeout, msDelay || 0);\n return;\n }\n fnComplete();\n };\n fnTimeout();\n }\n\n /**\n * onClickRepeat(e, msDelay, msRepeat, fn)\n *\n * Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter,\n * as long as HTML control Object e has an active \"down\" event and fn() returns true.\n *\n * @param {Object} e\n * @param {number} msDelay\n * @param {number} msRepeat\n * @param {function(boolean)} fn is passed false on the first call, true on all repeated calls\n */\n static onClickRepeat(e, msDelay, msRepeat, fn)\n {\n var ms = 0, timer = null, fIgnoreMouseEvents = false;\n\n var fnRepeat = function doClickRepeat()\n {\n if (fn(ms === msRepeat)) {\n timer = setTimeout(fnRepeat, ms);\n ms = msRepeat;\n }\n };\n e.onmousedown = function()\n {\n // Web.log(\"onMouseDown()\");\n if (!fIgnoreMouseEvents) {\n if (!timer) {\n ms = msDelay;\n fnRepeat();\n }\n }\n };\n e.ontouchstart = function()\n {\n // Web.log(\"onTouchStart()\");\n if (!timer) {\n ms = msDelay;\n fnRepeat();\n }\n };\n e.onmouseup = e.onmouseout = function()\n {\n // Web.log(\"onMouseUp()/onMouseOut()\");\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n e.ontouchend = e.ontouchcancel = function()\n {\n // Web.log(\"onTouchEnd()/onTouchCancel()\");\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n /*\n * Devices that generate ontouch* events ALSO generate onmouse* events,\n * and generally do so immediately after all the touch events are complete,\n * so unless we want double the action, we need to ignore mouse events.\n */\n fIgnoreMouseEvents = true;\n };\n }\n\n /**\n * onPageEvent(sName, fn)\n *\n * For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but\n * instead use Web.onInit(), Web.onShow(), and Web.onExit(), respectively.\n *\n * The only components that should still use onPageEvent() are THIS component (see the bottom of this file)\n * and components that need to capture other events (eg, the 'onresize' event in the Video component).\n *\n * This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers\n * for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'],\n * etc, themselves. However, that's less of a concern now, because assuming everyone else is now using\n * onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one.\n *\n * NOTE: It's risky to refer to obscure event handlers with \"dot\" names, because the Closure Compiler may\n * erroneously replace them (eg, window.onpageshow is a good example).\n *\n * @param {string} sFunc\n * @param {function()} fn\n */\n static onPageEvent(sFunc, fn)\n {\n if (window) {\n var fnPrev = window[sFunc];\n if (typeof fnPrev !== 'function') {\n window[sFunc] = fn;\n } else {\n /*\n * TODO: Determine whether there's any value in receiving/sending the Event object that the\n * browser provides when it generates the original event.\n */\n window[sFunc] = function onWindowEvent()\n {\n if (fnPrev) fnPrev();\n fn();\n };\n }\n }\n };\n\n /**\n * onInit(fn)\n *\n * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler.\n *\n * @param {function()} fn\n */\n static onInit(fn)\n {\n Web.aPageEventHandlers['init'].push(fn);\n };\n\n /**\n * onShow(fn)\n *\n * @param {function()} fn\n *\n * Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler.\n */\n static onShow(fn)\n {\n Web.aPageEventHandlers['show'].push(fn);\n };\n\n /**\n * onExit(fn)\n *\n * @param {function()} fn\n *\n * Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler.\n */\n static onExit(fn)\n {\n Web.aPageEventHandlers['exit'].push(fn);\n };\n\n /**\n * doPageEvent(afn)\n *\n * @param {Array.} afn\n */\n static doPageEvent(afn)\n {\n if (Web.fPageEventsEnabled) {\n try {\n for (var i = 0; i < afn.length; i++) {\n afn[i]();\n }\n } catch (e) {\n Web.notice(\"An unexpected error occurred: \" + e.message + \"\\n\\nIf it happens again, please send this information to support@pcjs.org. Thanks.\");\n }\n }\n };\n\n /**\n * enablePageEvents(fEnable)\n *\n * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default)\n */\n static enablePageEvents(fEnable)\n {\n if (!Web.fPageEventsEnabled && fEnable) {\n Web.fPageEventsEnabled = true;\n if (Web.fPageLoaded) Web.sendPageEvent('init');\n if (Web.fPageShowed) Web.sendPageEvent('show');\n return;\n }\n Web.fPageEventsEnabled = fEnable;\n }\n\n /**\n * sendPageEvent(sEvent)\n *\n * This allows us to manually trigger page events.\n *\n * @param {string} sEvent (one of 'init', 'show' or 'exit')\n */\n static sendPageEvent(sEvent)\n {\n if (Web.aPageEventHandlers[sEvent]) {\n Web.doPageEvent(Web.aPageEventHandlers[sEvent]);\n }\n }\n}\n\nWeb.parmsURL = null; // initialized on first call to parseURLParms()\n\nWeb.aPageEventHandlers = {\n 'init': [], // list of window 'onload' handlers\n 'show': [], // list of window 'onpageshow' handlers\n 'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible)\n};\n\nWeb.asBrowserPrefixes = ['', 'moz', 'ms', 'webkit'];\n\nWeb.fPageLoaded = false; // set once the page's first 'onload' event has occurred\nWeb.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred\nWeb.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents()\n\n/**\n * fLocalStorage\n *\n * true if localStorage support exists, is enabled, and works; \"falsey\" otherwise\n *\n * @type {boolean|null}\n */\nWeb.fLocalStorage = null;\n\n/**\n * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This isn't cutting it.\n *\n * @const {string}\n */\nWeb.sLocalStorageTest = \"PCjs.localStorage\";\n\nWeb.onPageEvent('onload', function onPageLoad() {\n Web.fPageLoaded = true;\n Web.doPageEvent(Web.aPageEventHandlers['init']);\n});\n\nWeb.onPageEvent('onpageshow', function onPageShow() {\n Web.fPageShowed = true;\n Web.doPageEvent(Web.aPageEventHandlers['show']);\n});\n\nWeb.onPageEvent(Web.isUserAgent(\"iOS\")? 'onpagehide' : (Web.isUserAgent(\"Opera\")? 'onunload' : 'onbeforeunload'), function onPageUnload() {\n Web.doPageEvent(Web.aPageEventHandlers['exit']);\n});\n\n\n\n/**\n * @copyright https://www.pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2018\n */\n\n/*\n * All PCjs components now use JSDoc types, primarily so that Google's Closure Compiler will compile\n * everything with zero warnings when ADVANCED_OPTIMIZATIONS are enabled. For more information about\n * the JSDoc types supported by the Closure Compiler:\n *\n * https://developers.google.com/closure/compiler/docs/js-for-compiler#types\n *\n * I also attempted to validate this code with JSLint, but it complained too much; eg, it didn't like\n * \"while (true)\", a tried and \"true\" programming convention for decades, and it wanted me to replace\n * all \"++\" and \"--\" operators with \"+= 1\" and \"-= 1\", use \"(s || '')\" instead of \"(s? s : '')\", etc.\n *\n * I prefer sticking with traditional C-style idioms, in part because they are more portable. That\n * does NOT mean I'm trying to write \"portable JavaScript,\" but some of this code was ported from C code\n * I'd written long ago, so portability is good, and I'm not going to throw that away if there's no need.\n *\n * UPDATE: I've since switched from JSLint to JSHint, which seems to have more reasonable defaults.\n * And for new code, I have adopted some popular JavaScript idioms, like \"(s || '')\", although the need\n * for those kinds of expressions will be reduced as I also start adopting some ES6 features, like\n * default parameters.\n */\n\n\n/**\n * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default,\n * it deters us from defining named properties on our components; eg:\n *\n * this['exports'] = {...}\n *\n * results in an error:\n *\n * Cannot do '[]' access on a struct\n *\n * So, in order to define 'exports', we must override the @struct assumption by annotating\n * the class as @unrestricted (or @dict). Note that this must be done both here and in the\n * subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference*\n * the named property either.\n *\n * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to\n * define every single property the class uses in its constructor, which results in a fair\n * bit of redundant initialization, since many properties aren't (and don't need to be) fully\n * initialized until the appropriate init(), reset(), restore(), etc. function is called.\n *\n * The upside, however, may be that since the structure of the class is completely defined by\n * the constructor, JavaScript engines may be able to optimize and run more efficiently.\n *\n * @unrestricted\n */\nclass Component {\n /**\n * Component(type, parms, bitsMessage)\n *\n * A Component object requires:\n *\n * type: a user-defined type name (eg, \"CPU\")\n *\n * and accepts any or all of the following (parms) properties:\n *\n * id: component ID (default is \"\")\n * name: component name (default is \"\"; if blank, toString() will use the type name only)\n * comment: component comment string (default is undefined)\n *\n * Component subclasses will usually have additional (parms) properties.\n *\n * @param {string} type\n * @param {Object} [parms]\n * @param {number} [bitsMessage] selects message(s) that the component wants to enable (default is 0)\n */\n constructor(type, parms, bitsMessage)\n {\n this.type = type;\n\n if (!parms) parms = {'id': \"\", 'name': \"\"};\n\n this.id = parms['id'] || \"\";\n this.name = parms['name'];\n this.comment = parms['comment'];\n this.parms = parms;\n\n /*\n * The following Component properties need to be accessible by other machines and/or command scripts;\n * well, OK, or we could have exported some new functions to walk the contents of these properties, as we\n * did with findMachineComponent(), but this works just as well.\n *\n * Also, while the double-assignment looks silly (ie, using both dot and bracket property notation), it\n * resolves a complaint from the Closure Compiler, because if we use ONLY bracket notation here, then the\n * Compiler wants us to change all the other references to bracket notation as well.\n */\n this.exports = this['exports'] = {};\n this.bindings = this['bindings'] = {};\n\n var i = this.id.indexOf('.');\n if (i < 0) {\n this.idComponent = this.id;\n } else {\n this.idMachine = this.id.substr(0, i);\n this.idComponent = this.id.substr(i + 1);\n }\n\n /*\n * Gather all the various component flags (booleans) into a single \"flags\" object, and encourage\n * subclasses to do the same, to reduce the property clutter we have to wade through while debugging.\n */\n this.flags = {\n ready: false,\n busy: false,\n busyCancel: false,\n initDone: false,\n powered: false,\n unloading: false,\n error: false\n };\n\n this.fnReady = null;\n this.clearError();\n this.bitsMessage = bitsMessage || 0;\n\n this.cmp = null;\n this.bus = null;\n this.cpu = null;\n this.dbg = null;\n\n /*\n * TODO: Consider adding another parameter to the Component() constructor that allows components to tell\n * us if they support single or multiple instances per machine. For example, there can be multiple SerialPort\n * components per machine, but only one CPU component (some machines also support an FPU, but that component\n * is considered separate from the CPU).\n *\n * It's not critical, but it would help catch machine configuration errors; for example, a machine that mistakenly\n * includes two CPU components may, aside from wasting memory, end up with odd side-effects, like unresponsive\n * CPU controls.\n */\n Component.add(this);\n }\n\n /**\n * Component.add(component)\n *\n * @param {Component} component\n */\n static add(component)\n {\n /*\n * This just generates a lot of useless noise, handy in the early days, not so much these days....\n *\n * if (DEBUG) Component.log(\"Component.add(\" + component.type + \",\" + component.id + \")\");\n */\n Component.components.push(component);\n }\n\n /**\n * Component.addMachine(idMachine)\n *\n * @param {string} idMachine\n */\n static addMachine(idMachine)\n {\n Component.machines[idMachine] = {};\n }\n\n /**\n * Component.addMachineResource(idMachine, sName, data)\n *\n * @param {string} idMachine\n * @param {string|null} sName (name of the resource)\n * @param {*} data\n */\n static addMachineResource(idMachine, sName, data)\n {\n /*\n * I used to assert(Component.machines[idMachine]), but when we're running as a Node app, embed.js is not used,\n * so addMachine() is never called, so resources do not need to be recorded.\n */\n if (Component.machines[idMachine] && sName) {\n Component.machines[idMachine][sName] = data;\n }\n }\n\n /**\n * Component.getMachineResources(idMachine)\n *\n * @param {string} idMachine\n * @return {Object|undefined}\n */\n static getMachineResources(idMachine)\n {\n return Component.machines[idMachine];\n }\n\n /**\n * Component.getTime()\n *\n * @return {number} the current time, in milliseconds\n */\n static getTime()\n {\n return Date.now() || +new Date();\n }\n\n /**\n * Component.log(s, type)\n *\n * For diagnostic output only.\n *\n * @param {string} [s] is the message text\n * @param {string} [type] is the message type\n */\n static log(s, type)\n {\n if (!COMPILED) {\n if (s) {\n var sElapsed = \"\", sMsg = (type? (type + \": \") : \"\") + s;\n if (typeof Usr != \"undefined\") {\n if (Component.msStart === undefined) {\n Component.msStart = Component.getTime();\n }\n sElapsed = (Component.getTime() - Component.msStart) + \"ms: \";\n }\n sMsg = sMsg.replace(/\\r/g, '\\\\r').replace(/\\n/g, ' ');\n if (window && window.console) console.log(sElapsed + sMsg);\n }\n }\n }\n\n /**\n * Component.assert(f, s)\n *\n * Verifies conditions that must be true (for DEBUG builds only).\n *\n * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds.\n * TODO: Add a task to the build process that \"asserts\" there are no instances of \"assertion failure\" in RELEASE builds.\n *\n * @param {boolean} f is the expression we are asserting to be true\n * @param {string} [s] is description of the assertion on failure\n */\n static assert(f, s)\n {\n if (DEBUG) {\n if (!f) {\n if (!s) s = \"assertion failure\";\n Component.log(s);\n throw new Error(s);\n }\n }\n }\n\n /**\n * Component.print(s)\n *\n * Components that inherit from this class should use this.print(), rather than Component.print(), because\n * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the\n * class method would improperly affect any other machines loaded on the same page).\n *\n * @this {Component}\n * @param {string} s\n */\n static print(s)\n {\n if (!COMPILED) {\n var i = s.lastIndexOf('\\n');\n if (i >= 0) {\n Component.println(s.substr(0, i));\n s = s.substr(i + 1);\n }\n Component.printBuffer += s;\n }\n }\n\n /**\n * Component.println(s, type, id)\n *\n * Components that inherit from this class should use this.println(), rather than Component.println(), because\n * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the\n * class method would improperly affect any other machines loaded on the same page).\n *\n * @param {string} [s] is the message text\n * @param {string} [type] is the message type\n * @param {string} [id] is the caller's ID, if any\n */\n static println(s, type, id)\n {\n if (!COMPILED) {\n s = Component.printBuffer + (s || \"\");\n Component.log((id? (id + \": \") : \"\") + (s? (\"\\\"\" + s + \"\\\"\") : \"\"), type);\n Component.printBuffer = \"\";\n }\n }\n\n /**\n * Component.notice(s, fPrintOnly, id)\n *\n * notice() is like println() but implies a need for user notification, so we alert() as well.\n *\n * @param {string} s is the message text\n * @param {boolean} [fPrintOnly]\n * @param {string} [id] is the caller's ID, if any\n * @return {boolean}\n */\n static notice(s, fPrintOnly, id)\n {\n if (!COMPILED) {\n Component.println(s, Component.PRINT.NOTICE, id);\n }\n if (!fPrintOnly) Component.alertUser((id? (id + \": \") : \"\") + s);\n return true;\n }\n\n /**\n * Component.warning(s)\n *\n * @param {string} s describes the warning\n */\n static warning(s)\n {\n if (!COMPILED) {\n Component.println(s, Component.PRINT.WARNING);\n }\n Component.alertUser(s);\n }\n\n /**\n * Component.error(s)\n *\n * @param {string} s describes the error; an alert() is displayed as well\n */\n static error(s)\n {\n if (!COMPILED) {\n Component.println(s, Component.PRINT.ERROR);\n }\n Component.alertUser(s);\n }\n\n /**\n * Component.alertUser(sMessage)\n *\n * @param {string} sMessage\n */\n static alertUser(sMessage)\n {\n if (window) {\n window.alert(sMessage);\n } else {\n Component.log(sMessage);\n }\n }\n\n /**\n * Component.confirmUser(sPrompt)\n *\n * @param {string} sPrompt\n * @returns {boolean} true if the user clicked OK, false if Cancel/Close\n */\n static confirmUser(sPrompt)\n {\n var fResponse = false;\n if (window) {\n fResponse = window.confirm(sPrompt);\n }\n return fResponse;\n }\n\n /**\n * Component.promptUser()\n *\n * @param {string} sPrompt\n * @param {string} [sDefault]\n * @returns {string|null}\n */\n static promptUser(sPrompt, sDefault)\n {\n var sResponse = null;\n if (window) {\n sResponse = window.prompt(sPrompt, sDefault === undefined? \"\" : sDefault);\n }\n return sResponse;\n }\n\n /**\n * Component.appendControl(control, sText)\n *\n * @param {Object} control\n * @param {string} sText\n */\n static appendControl(control, sText)\n {\n control.value += sText;\n /*\n * Prevent the