From e286da43be0e710cf195e7db852782c629afc20c Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Mon, 28 May 2018 18:08:32 -0700 Subject: [PATCH] Fixed issue #66 (386 should force reserved CR0 bits set) and cleaned up the PCx86 Video component's vertical and horizontal retrace calculations, which vary according to video card and monitor combinations; the code is now more sensible and no longer sensitive to CPU speed variations (in theory) --- _data/machines.json | 2 +- apps/pcx86/1981/visicalc/manifest.xml | 2 +- apps/pcx86/1982/esuite/manifest.xml | 2 +- apps/pcx86/1982/ratbas/manifest.xml | 2 +- apps/pcx86/1983/adventmath/manifest.xml | 2 +- apps/pcx86/1985/castle_adventure/manifest.xml | 2 +- apps/pcx86/1985/rogue/manifest.xml | 2 +- apps/pcx86/1987/thinktank/manifest.xml | 2 +- apps/pcx86/1988/moria/manifest.xml | 2 +- apps/pcx86/1992/moria/manifest.xml | 2 +- devices/c1p/machine/32kb/machine.xml | 2 +- .../c1p/machine/8kb/all/debugger/machine.xml | 2 +- devices/c1p/machine/8kb/all/machine.xml | 2 +- devices/c1p/machine/8kb/array/machine.xml | 2 +- .../machine/8kb/large/debugger/machine.xml | 2 +- devices/c1p/machine/8kb/large/machine.xml | 2 +- devices/c1p/machine/8kb/small/machine.xml | 2 +- .../machine/exerciser/machine-8080ex1.xml | 2 +- .../machine/exerciser/machine-8080pre.xml | 2 +- .../machine/exerciser/machine-cputest.xml | 2 +- .../pc8080/machine/exerciser/machine-test.xml | 2 +- devices/pc8080/machine/exerciser/machine.xml | 2 +- .../machine/invaders/debugger/machine.xml | 2 +- devices/pc8080/machine/invaders/machine.xml | 2 +- .../pc8080/machine/vt100/debugger/machine.xml | 2 +- devices/pc8080/machine/vt100/machine.xml | 2 +- .../4860/cga/64kb/debugger/machine.xml | 2 +- .../5150/cga/256kb/debugger/machine.xml | 2 +- .../pcx86/machine/5150/cga/256kb/machine.xml | 2 +- .../5150/cga/384kb/debugger/machine.xml | 2 +- .../5150/cga/64kb/debugger/machine.xml | 2 +- .../pcx86/machine/5150/cga/64kb/machine.xml | 2 +- .../pcx86/machine/5150/dual/64kb/machine.xml | 2 +- .../5150/mda/256kb/debugger/machine.xml | 2 +- .../pcx86/machine/5150/mda/256kb/machine.xml | 2 +- .../5150/mda/64kb/debugger/machine.xml | 2 +- .../pcx86/machine/5150/mda/64kb/machine.xml | 2 +- .../machine/5160/cga/256kb/array/machine.xml | 2 +- .../5160/cga/256kb/debugger/machine.xml | 2 +- .../pcx86/machine/5160/cga/256kb/machine.xml | 2 +- .../5160/cga/512kb/debugger/machine.xml | 2 +- .../5160/cga/640kb/debugger/machine.xml | 2 +- .../pcx86/machine/5160/cga/640kb/machine.xml | 2 +- .../5160/cga/640kb/mouse/debugger/machine.xml | 2 +- .../machine/5160/cga/640kb/mouse/machine.xml | 2 +- .../5160/ega/256kb/color/debugger/machine.xml | 2 +- .../machine/5160/ega/256kb/color/machine.xml | 2 +- .../5160/ega/256kb/debugger/machine.xml | 2 +- .../pcx86/machine/5160/ega/256kb/machine.xml | 2 +- .../5160/ega/256kb/mono/debugger/machine.xml | 2 +- .../machine/5160/ega/256kb/mono/machine.xml | 2 +- .../machine/5160/ega/640kb/array/machine.xml | 2 +- .../5160/ega/640kb/debugger/machine.xml | 2 +- .../pcx86/machine/5160/ega/640kb/machine.xml | 2 +- .../5160/mda/256kb/debugger/machine.xml | 2 +- .../mda/256kb/fake188/debugger/machine.xml | 2 +- .../5160/mda/256kb/fake188/machine.xml | 2 +- .../pcx86/machine/5160/mda/256kb/machine.xml | 2 +- .../5160/mda/64kb/debugger/machine.xml | 2 +- .../5170/cga/640kb/rev3/debugger/machine.xml | 2 +- .../machine/5170/cga/640kb/rev3/machine.xml | 2 +- .../5170/ega/1152kb/rev1/debugger/machine.xml | 2 +- .../machine/5170/ega/1152kb/rev1/machine.xml | 2 +- .../5170/ega/1152kb/rev3/debugger/machine.xml | 2 +- .../1152kb/rev3/debugger/visual/machine.xml | 2 +- .../machine/5170/ega/1152kb/rev3/machine.xml | 2 +- .../5170/ega/2048kb/rev3/debugger/machine.xml | 2 +- .../2048kb/rev3/debugger/visual/machine.xml | 2 +- .../2048kb/rev3/debugger/vt100/machine.xml | 2 +- .../machine/5170/ega/2048kb/rev3/machine.xml | 2 +- .../ega/640kb/rev1/color/debugger/machine.xml | 2 +- .../5170/ega/640kb/rev1/color/machine.xml | 2 +- .../5170/ega/640kb/rev1/debugger/machine.xml | 2 +- .../machine/5170/ega/640kb/rev1/machine.xml | 2 +- .../ega/640kb/rev1/mono/debugger/machine.xml | 2 +- .../5170/ega/640kb/rev1/mono/machine.xml | 2 +- .../5170/ega/640kb/rev1/testmon/machine.xml | 2 +- .../5170/ega/640kb/rev3/debugger/machine.xml | 2 +- .../machine/5170/ega/640kb/rev3/machine.xml | 2 +- .../5170/mda/640kb/rev3/debugger/machine.xml | 2 +- .../machine/5170/mda/640kb/rev3/machine.xml | 2 +- .../5170/vga/2048kb/debugger/machine.xml | 2 +- .../pcx86/machine/5170/vga/2048kb/machine.xml | 2 +- .../5170/vga/4096kb/debugger/machine.xml | 2 +- .../pcx86/machine/5170/vga/4096kb/machine.xml | 2 +- .../att/6300/cga/640kb/debugger/machine.xml | 2 +- .../machine/att/6300/cga/640kb/machine.xml | 2 +- .../mpc1600/cga/640kb/debugger/machine.xml | 2 +- .../machine/cdp/mpc1600/cga/640kb/machine.xml | 2 +- .../ega/2048kb/debugger/machine.xml | 2 +- .../compaq/deskpro386/ega/2048kb/machine.xml | 2 +- .../ega/4096kb/debugger/machine.xml | 2 +- .../compaq/deskpro386/ega/4096kb/machine.xml | 2 +- .../other/2048kb/debugger/machine.xml | 2 +- .../other/2048kb/debugger/visual/machine.xml | 2 +- .../vga/2048kb/debugger/machine.xml | 2 +- .../vga/2048kb/debugger/visual/machine.xml | 2 +- .../compaq/deskpro386/vga/2048kb/machine.xml | 2 +- .../vga/4096kb/debugger/machine.xml | 2 +- .../vga/4096kb/debugger/visual/machine.xml | 2 +- .../compaq/deskpro386/vga/4096kb/machine.xml | 2 +- devices/pcx86/machine/custom/machine.xml | 2 +- .../z150/cga/640kb/debugger/machine.xml | 2 +- .../machine/zenith/z150/cga/640kb/machine.xml | 2 +- .../machine/ka10/test/debugger/machine.xml | 2 +- devices/pdp10/machine/ka10/test/machine.xml | 2 +- .../machine/1120/basic/debugger/machine.xml | 2 +- devices/pdp11/machine/1120/basic/machine.xml | 2 +- .../1120/bootstrap/debugger/machine.xml | 2 +- .../pdp11/machine/1120/bootstrap/machine.xml | 2 +- .../machine/1120/monitor/debugger/machine.xml | 2 +- .../pdp11/machine/1120/monitor/machine.xml | 2 +- .../machine/1120/panel/debugger/machine.xml | 2 +- .../1120/panel/debugger/test14/machine.xml | 2 +- devices/pdp11/machine/1120/panel/machine.xml | 2 +- .../machine/1145/panel/debugger/machine.xml | 2 +- devices/pdp11/machine/1145/panel/machine.xml | 2 +- .../machine/1145/vt100/debugger/machine.xml | 2 +- devices/pdp11/machine/1145/vt100/machine.xml | 2 +- .../machine/1170/4mb/debugger/machine.xml | 2 +- devices/pdp11/machine/1170/4mb/machine.xml | 2 +- .../machine/1170/monitor/debugger/machine.xml | 2 +- .../pdp11/machine/1170/monitor/machine.xml | 2 +- .../1170/panel/debugger/cpuexer/machine.xml | 2 +- .../1170/panel/debugger/machine-slim.xml | 2 +- .../machine/1170/panel/debugger/machine.xml | 2 +- devices/pdp11/machine/1170/panel/machine.xml | 2 +- .../machine/1170/vt100/debugger/machine.xml | 2 +- devices/pdp11/machine/1170/vt100/machine.xml | 2 +- disks/pcx86/cpm/1.00/debugger/machine.xml | 2 +- disks/pcx86/cpm/1.00/machine.xml | 2 +- disks/pcx86/games/infocom/machine.xml | 2 +- .../games/infocom/zork1/debugger/machine.xml | 2 +- .../games/microsoft/adventure/machine.xml | 2 +- .../shareware/pcsig08/debugger/machine.xml | 2 +- disks/pcx86/shareware/pcsig08/machine.xml | 2 +- modules/pcx86/lib/cpux86.js | 10 +- modules/pcx86/lib/debugger.js | 15 +- modules/pcx86/lib/video.js | 106 +- modules/pcx86/lib/x86.js | 3 +- modules/pcx86/lib/x86help.js | 4 +- package.json | 2 +- pcjs-disks | 2 +- pcjs-games | 2 +- private-disks | 2 +- .../demos/machine-debugger.xml | 2 +- .../Graphics_for_the_IBM_PC/demos/machine.xml | 2 +- pubs/pc/programming/manifest.xml | 2 +- .../reference/ibm/5150/techref/manifest.xml | 2 +- .../reference/ibm/5160/techref/manifest.xml | 2 +- pubs/pc/reference/ibm/5170/setup/manifest.xml | 2 +- .../reference/ibm/5170/techref/manifest.xml | 2 +- pubs/pc/reference/ibm/ps2/manifest.xml | 2 +- pubs/pc/reference/ibm/video/ega/manifest.xml | 2 +- pubs/pc/reference/intel/80286/manifest.xml | 2 +- .../software/os2/microsoft/sdk10/manifest.xml | 2 +- pubs/pc/software/windows/sdk200/manifest.xml | 2 +- versions/c1pjs/1.71.5/c1p-uncompiled.js | 14933 +++ versions/c1pjs/1.71.5/c1p.js | 206 + versions/c1pjs/1.71.5/c1p.js.map | 1 + versions/c1pjs/1.71.5/common.css | 279 + versions/c1pjs/1.71.5/common.xsl | 58 + versions/c1pjs/1.71.5/components.css | 108 + versions/c1pjs/1.71.5/components.xsl | 604 + versions/c1pjs/1.71.5/document.css | 162 + versions/c1pjs/1.71.5/document.xsl | 452 + versions/c1pjs/1.71.5/machine.xsl | 61 + versions/c1pjs/1.71.5/manifest.xsl | 247 + versions/c1pjs/1.71.5/outline.xsl | 47 + versions/pc8080/1.71.5/common.css | 279 + versions/pc8080/1.71.5/common.xsl | 58 + versions/pc8080/1.71.5/components.css | 263 + versions/pc8080/1.71.5/components.xsl | 1475 + versions/pc8080/1.71.5/document.css | 162 + versions/pc8080/1.71.5/document.xsl | 452 + versions/pc8080/1.71.5/machine.xsl | 61 + versions/pc8080/1.71.5/manifest.xsl | 247 + versions/pc8080/1.71.5/outline.xsl | 47 + versions/pc8080/1.71.5/pc8080-uncompiled.js | 26022 +++++ versions/pc8080/1.71.5/pc8080.js | 317 + versions/pc8080/1.71.5/pc8080.js.map | 1 + versions/pcx86/1.71.5/common.css | 279 + versions/pcx86/1.71.5/common.xsl | 58 + versions/pcx86/1.71.5/components.css | 263 + versions/pcx86/1.71.5/components.xsl | 1475 + versions/pcx86/1.71.5/document.css | 162 + versions/pcx86/1.71.5/document.xsl | 452 + versions/pcx86/1.71.5/machine.xsl | 61 + versions/pcx86/1.71.5/manifest.xsl | 247 + versions/pcx86/1.71.5/outline.xsl | 47 + versions/pcx86/1.71.5/pcx86-uncompiled.js | 81741 ++++++++++++++++ versions/pcx86/1.71.5/pcx86.js | 987 + versions/pcx86/1.71.5/pcx86.js.map | 1 + versions/pdpjs/1.71.5/common.css | 279 + versions/pdpjs/1.71.5/common.xsl | 58 + versions/pdpjs/1.71.5/components.css | 263 + versions/pdpjs/1.71.5/components.xsl | 1475 + versions/pdpjs/1.71.5/document.css | 162 + versions/pdpjs/1.71.5/document.xsl | 452 + versions/pdpjs/1.71.5/machine.xsl | 61 + versions/pdpjs/1.71.5/manifest.xsl | 247 + versions/pdpjs/1.71.5/outline.xsl | 47 + versions/pdpjs/1.71.5/pdp10-uncompiled.js | 28689 ++++++ versions/pdpjs/1.71.5/pdp10.js | 312 + versions/pdpjs/1.71.5/pdp10.js.map | 1 + versions/pdpjs/1.71.5/pdp11-uncompiled.js | 32577 ++++++ versions/pdpjs/1.71.5/pdp11.js | 409 + versions/pdpjs/1.71.5/pdp11.js.map | 1 + 208 files changed, 197545 insertions(+), 245 deletions(-) create mode 100644 versions/c1pjs/1.71.5/c1p-uncompiled.js create mode 100644 versions/c1pjs/1.71.5/c1p.js create mode 100644 versions/c1pjs/1.71.5/c1p.js.map create mode 100644 versions/c1pjs/1.71.5/common.css create mode 100644 versions/c1pjs/1.71.5/common.xsl create mode 100644 versions/c1pjs/1.71.5/components.css create mode 100644 versions/c1pjs/1.71.5/components.xsl create mode 100644 versions/c1pjs/1.71.5/document.css create mode 100644 versions/c1pjs/1.71.5/document.xsl create mode 100644 versions/c1pjs/1.71.5/machine.xsl create mode 100644 versions/c1pjs/1.71.5/manifest.xsl create mode 100644 versions/c1pjs/1.71.5/outline.xsl create mode 100644 versions/pc8080/1.71.5/common.css create mode 100644 versions/pc8080/1.71.5/common.xsl create mode 100644 versions/pc8080/1.71.5/components.css create mode 100644 versions/pc8080/1.71.5/components.xsl create mode 100644 versions/pc8080/1.71.5/document.css create mode 100644 versions/pc8080/1.71.5/document.xsl create mode 100644 versions/pc8080/1.71.5/machine.xsl create mode 100644 versions/pc8080/1.71.5/manifest.xsl create mode 100644 versions/pc8080/1.71.5/outline.xsl create mode 100644 versions/pc8080/1.71.5/pc8080-uncompiled.js create mode 100644 versions/pc8080/1.71.5/pc8080.js create mode 100644 versions/pc8080/1.71.5/pc8080.js.map create mode 100644 versions/pcx86/1.71.5/common.css create mode 100644 versions/pcx86/1.71.5/common.xsl create mode 100644 versions/pcx86/1.71.5/components.css create mode 100644 versions/pcx86/1.71.5/components.xsl create mode 100644 versions/pcx86/1.71.5/document.css create mode 100644 versions/pcx86/1.71.5/document.xsl create mode 100644 versions/pcx86/1.71.5/machine.xsl create mode 100644 versions/pcx86/1.71.5/manifest.xsl create mode 100644 versions/pcx86/1.71.5/outline.xsl create mode 100644 versions/pcx86/1.71.5/pcx86-uncompiled.js create mode 100644 versions/pcx86/1.71.5/pcx86.js create mode 100644 versions/pcx86/1.71.5/pcx86.js.map create mode 100644 versions/pdpjs/1.71.5/common.css create mode 100644 versions/pdpjs/1.71.5/common.xsl create mode 100644 versions/pdpjs/1.71.5/components.css create mode 100644 versions/pdpjs/1.71.5/components.xsl create mode 100644 versions/pdpjs/1.71.5/document.css create mode 100644 versions/pdpjs/1.71.5/document.xsl create mode 100644 versions/pdpjs/1.71.5/machine.xsl create mode 100644 versions/pdpjs/1.71.5/manifest.xsl create mode 100644 versions/pdpjs/1.71.5/outline.xsl create mode 100644 versions/pdpjs/1.71.5/pdp10-uncompiled.js create mode 100644 versions/pdpjs/1.71.5/pdp10.js create mode 100644 versions/pdpjs/1.71.5/pdp10.js.map create mode 100644 versions/pdpjs/1.71.5/pdp11-uncompiled.js create mode 100644 versions/pdpjs/1.71.5/pdp11.js create mode 100644 versions/pdpjs/1.71.5/pdp11.js.map diff --git a/_data/machines.json b/_data/machines.json index 9f590318f7..83da949e72 100644 --- a/_data/machines.json +++ b/_data/machines.json @@ -1,6 +1,6 @@ { "shared": { - "version": "1.71.4", + "version": "1.71.5", "externs": [ "./modules/shared/lib/externs.js" ], diff --git a/apps/pcx86/1981/visicalc/manifest.xml b/apps/pcx86/1981/visicalc/manifest.xml index 9873202828..d36213e075 100644 --- a/apps/pcx86/1981/visicalc/manifest.xml +++ b/apps/pcx86/1981/visicalc/manifest.xml @@ -1,5 +1,5 @@ - + VisiCalc diff --git a/apps/pcx86/1982/esuite/manifest.xml b/apps/pcx86/1982/esuite/manifest.xml index 1f8c9f29bb..7c5e79263d 100644 --- a/apps/pcx86/1982/esuite/manifest.xml +++ b/apps/pcx86/1982/esuite/manifest.xml @@ -1,5 +1,5 @@ - + Executive Suite diff --git a/apps/pcx86/1982/ratbas/manifest.xml b/apps/pcx86/1982/ratbas/manifest.xml index 1fad727cdf..c153798e97 100644 --- a/apps/pcx86/1982/ratbas/manifest.xml +++ b/apps/pcx86/1982/ratbas/manifest.xml @@ -1,5 +1,5 @@ - + RatBas 2.13 diff --git a/apps/pcx86/1983/adventmath/manifest.xml b/apps/pcx86/1983/adventmath/manifest.xml index a96e054194..5d515e1eaa 100644 --- a/apps/pcx86/1983/adventmath/manifest.xml +++ b/apps/pcx86/1983/adventmath/manifest.xml @@ -1,5 +1,5 @@ - + Adventures in Math 1.00 diff --git a/apps/pcx86/1985/castle_adventure/manifest.xml b/apps/pcx86/1985/castle_adventure/manifest.xml index 24d0d8db57..51b16ae993 100644 --- a/apps/pcx86/1985/castle_adventure/manifest.xml +++ b/apps/pcx86/1985/castle_adventure/manifest.xml @@ -1,5 +1,5 @@ - + Castle Adventure diff --git a/apps/pcx86/1985/rogue/manifest.xml b/apps/pcx86/1985/rogue/manifest.xml index a99f239a94..c04cc20df0 100644 --- a/apps/pcx86/1985/rogue/manifest.xml +++ b/apps/pcx86/1985/rogue/manifest.xml @@ -1,5 +1,5 @@ - + Rogue 1.49 diff --git a/apps/pcx86/1987/thinktank/manifest.xml b/apps/pcx86/1987/thinktank/manifest.xml index 2179e663ce..c43c3fc518 100644 --- a/apps/pcx86/1987/thinktank/manifest.xml +++ b/apps/pcx86/1987/thinktank/manifest.xml @@ -1,5 +1,5 @@ - + ThinkTank 2.41NP diff --git a/apps/pcx86/1988/moria/manifest.xml b/apps/pcx86/1988/moria/manifest.xml index c99835b095..4f26e4473c 100644 --- a/apps/pcx86/1988/moria/manifest.xml +++ b/apps/pcx86/1988/moria/manifest.xml @@ -1,5 +1,5 @@ - + The Dungeons of Moria 4.872 diff --git a/apps/pcx86/1992/moria/manifest.xml b/apps/pcx86/1992/moria/manifest.xml index 30c2f8b756..fbbd0aab33 100644 --- a/apps/pcx86/1992/moria/manifest.xml +++ b/apps/pcx86/1992/moria/manifest.xml @@ -1,5 +1,5 @@ - + The Dungeons of Moria 5.5 diff --git a/devices/c1p/machine/32kb/machine.xml b/devices/c1p/machine/32kb/machine.xml index 652111c107..3b97725dc1 100644 --- a/devices/c1p/machine/32kb/machine.xml +++ b/devices/c1p/machine/32kb/machine.xml @@ -1,5 +1,5 @@ - + OSI Challenger 1P (32Kb) with Disk Support diff --git a/devices/c1p/machine/8kb/all/debugger/machine.xml b/devices/c1p/machine/8kb/all/debugger/machine.xml index 2be1dc7052..f5288345a6 100644 --- a/devices/c1p/machine/8kb/all/debugger/machine.xml +++ b/devices/c1p/machine/8kb/all/debugger/machine.xml @@ -1,5 +1,5 @@ - + OSI Challenger 1P (8Kb, Additional Software) diff --git a/devices/c1p/machine/8kb/all/machine.xml b/devices/c1p/machine/8kb/all/machine.xml index 3fc2536002..38836c1c40 100644 --- a/devices/c1p/machine/8kb/all/machine.xml +++ b/devices/c1p/machine/8kb/all/machine.xml @@ -1,5 +1,5 @@ - + OSI Challenger 1P (8Kb, Additional Software) diff --git a/devices/c1p/machine/8kb/array/machine.xml b/devices/c1p/machine/8kb/array/machine.xml index a933b7d8b3..5b3ecbb3f4 100644 --- a/devices/c1p/machine/8kb/array/machine.xml +++ b/devices/c1p/machine/8kb/array/machine.xml @@ -1,5 +1,5 @@ - + Challenger 1P (8Kb) "Server Array" diff --git a/devices/c1p/machine/8kb/large/debugger/machine.xml b/devices/c1p/machine/8kb/large/debugger/machine.xml index 979d7377b5..9bfb6654af 100644 --- a/devices/c1p/machine/8kb/large/debugger/machine.xml +++ b/devices/c1p/machine/8kb/large/debugger/machine.xml @@ -1,5 +1,5 @@ - + OSI Challenger 1P (8Kb) with Debugger diff --git a/devices/c1p/machine/8kb/large/machine.xml b/devices/c1p/machine/8kb/large/machine.xml index 3ad46458ee..7b0b8452b6 100644 --- a/devices/c1p/machine/8kb/large/machine.xml +++ b/devices/c1p/machine/8kb/large/machine.xml @@ -1,5 +1,5 @@ - + OSI Challenger 1P (circa 1978) diff --git a/devices/c1p/machine/8kb/small/machine.xml b/devices/c1p/machine/8kb/small/machine.xml index 55107b27b6..72e4d55fcc 100644 --- a/devices/c1p/machine/8kb/small/machine.xml +++ b/devices/c1p/machine/8kb/small/machine.xml @@ -1,5 +1,5 @@ - + diff --git a/devices/pc8080/machine/exerciser/machine-8080ex1.xml b/devices/pc8080/machine/exerciser/machine-8080ex1.xml index 009985ed09..d51b529a6a 100644 --- a/devices/pc8080/machine/exerciser/machine-8080ex1.xml +++ b/devices/pc8080/machine/exerciser/machine-8080ex1.xml @@ -1,5 +1,5 @@ - + 8080 Exerciser Test Machine diff --git a/devices/pc8080/machine/exerciser/machine-8080pre.xml b/devices/pc8080/machine/exerciser/machine-8080pre.xml index ebebf08d7d..3130ec7200 100644 --- a/devices/pc8080/machine/exerciser/machine-8080pre.xml +++ b/devices/pc8080/machine/exerciser/machine-8080pre.xml @@ -1,5 +1,5 @@ - + 8080 Exerciser Preliminary Test Machine diff --git a/devices/pc8080/machine/exerciser/machine-cputest.xml b/devices/pc8080/machine/exerciser/machine-cputest.xml index d2d9466ec0..566362f6f6 100644 --- a/devices/pc8080/machine/exerciser/machine-cputest.xml +++ b/devices/pc8080/machine/exerciser/machine-cputest.xml @@ -1,5 +1,5 @@ - + 8080 CPUTEST Machine diff --git a/devices/pc8080/machine/exerciser/machine-test.xml b/devices/pc8080/machine/exerciser/machine-test.xml index fdf7e08e05..2e69a30a78 100644 --- a/devices/pc8080/machine/exerciser/machine-test.xml +++ b/devices/pc8080/machine/exerciser/machine-test.xml @@ -1,5 +1,5 @@ - + 8080 "Kelly Smith" Test Machine diff --git a/devices/pc8080/machine/exerciser/machine.xml b/devices/pc8080/machine/exerciser/machine.xml index aea3c89081..e66dc95978 100644 --- a/devices/pc8080/machine/exerciser/machine.xml +++ b/devices/pc8080/machine/exerciser/machine.xml @@ -1,5 +1,5 @@ - + 8080 Exerciser Test Machine diff --git a/devices/pc8080/machine/invaders/debugger/machine.xml b/devices/pc8080/machine/invaders/debugger/machine.xml index 91effb9de5..7e9cf2d256 100644 --- a/devices/pc8080/machine/invaders/debugger/machine.xml +++ b/devices/pc8080/machine/invaders/debugger/machine.xml @@ -1,5 +1,5 @@ - + Space Invaders diff --git a/devices/pc8080/machine/invaders/machine.xml b/devices/pc8080/machine/invaders/machine.xml index 7f47f1fa74..226e857995 100644 --- a/devices/pc8080/machine/invaders/machine.xml +++ b/devices/pc8080/machine/invaders/machine.xml @@ -1,5 +1,5 @@ - + Space Invaders diff --git a/devices/pc8080/machine/vt100/debugger/machine.xml b/devices/pc8080/machine/vt100/debugger/machine.xml index ba064580d8..e9bca18dc6 100644 --- a/devices/pc8080/machine/vt100/debugger/machine.xml +++ b/devices/pc8080/machine/vt100/debugger/machine.xml @@ -1,5 +1,5 @@ - + VT100 Terminal diff --git a/devices/pc8080/machine/vt100/machine.xml b/devices/pc8080/machine/vt100/machine.xml index 080073ef4e..8e4dc4f6e7 100644 --- a/devices/pc8080/machine/vt100/machine.xml +++ b/devices/pc8080/machine/vt100/machine.xml @@ -1,5 +1,5 @@ - + VT100 Terminal diff --git a/devices/pcx86/machine/4860/cga/64kb/debugger/machine.xml b/devices/pcx86/machine/4860/cga/64kb/debugger/machine.xml index 48eba561fa..1fe5a35bcf 100644 --- a/devices/pcx86/machine/4860/cga/64kb/debugger/machine.xml +++ b/devices/pcx86/machine/4860/cga/64kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PCjr (Model 4860) with 64K diff --git a/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml b/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml index 8bb3f43208..859cd6be33 100644 --- a/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml +++ b/devices/pcx86/machine/5150/cga/256kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), CGA, 256K, Debugger diff --git a/devices/pcx86/machine/5150/cga/256kb/machine.xml b/devices/pcx86/machine/5150/cga/256kb/machine.xml index 2e3eba95bd..05aa057572 100644 --- a/devices/pcx86/machine/5150/cga/256kb/machine.xml +++ b/devices/pcx86/machine/5150/cga/256kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), CGA, 256K diff --git a/devices/pcx86/machine/5150/cga/384kb/debugger/machine.xml b/devices/pcx86/machine/5150/cga/384kb/debugger/machine.xml index b072bba33b..bbadb8a8cd 100644 --- a/devices/pcx86/machine/5150/cga/384kb/debugger/machine.xml +++ b/devices/pcx86/machine/5150/cga/384kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), CGA, 384K, Debugger diff --git a/devices/pcx86/machine/5150/cga/64kb/debugger/machine.xml b/devices/pcx86/machine/5150/cga/64kb/debugger/machine.xml index 236733afb4..d394e3d4ec 100644 --- a/devices/pcx86/machine/5150/cga/64kb/debugger/machine.xml +++ b/devices/pcx86/machine/5150/cga/64kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), CGA, 64K diff --git a/devices/pcx86/machine/5150/cga/64kb/machine.xml b/devices/pcx86/machine/5150/cga/64kb/machine.xml index 4f46547ffd..dba3499353 100644 --- a/devices/pcx86/machine/5150/cga/64kb/machine.xml +++ b/devices/pcx86/machine/5150/cga/64kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), CGA, 64K diff --git a/devices/pcx86/machine/5150/dual/64kb/machine.xml b/devices/pcx86/machine/5150/dual/64kb/machine.xml index 9bf4c7cfbf..9d5abfbc79 100644 --- a/devices/pcx86/machine/5150/dual/64kb/machine.xml +++ b/devices/pcx86/machine/5150/dual/64kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150) with Dual Displays diff --git a/devices/pcx86/machine/5150/mda/256kb/debugger/machine.xml b/devices/pcx86/machine/5150/mda/256kb/debugger/machine.xml index 906c7e30e5..05621180e6 100644 --- a/devices/pcx86/machine/5150/mda/256kb/debugger/machine.xml +++ b/devices/pcx86/machine/5150/mda/256kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), MDA, 256K, Debugger diff --git a/devices/pcx86/machine/5150/mda/256kb/machine.xml b/devices/pcx86/machine/5150/mda/256kb/machine.xml index 16548fe9c8..2a0b5b1e09 100644 --- a/devices/pcx86/machine/5150/mda/256kb/machine.xml +++ b/devices/pcx86/machine/5150/mda/256kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), MDA, 256K diff --git a/devices/pcx86/machine/5150/mda/64kb/debugger/machine.xml b/devices/pcx86/machine/5150/mda/64kb/debugger/machine.xml index c513f01877..3f448dbe79 100644 --- a/devices/pcx86/machine/5150/mda/64kb/debugger/machine.xml +++ b/devices/pcx86/machine/5150/mda/64kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), MDA, 64K, Debugger diff --git a/devices/pcx86/machine/5150/mda/64kb/machine.xml b/devices/pcx86/machine/5150/mda/64kb/machine.xml index 307ddd5e14..62a5e1b338 100644 --- a/devices/pcx86/machine/5150/mda/64kb/machine.xml +++ b/devices/pcx86/machine/5150/mda/64kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), MDA, 64K diff --git a/devices/pcx86/machine/5160/cga/256kb/array/machine.xml b/devices/pcx86/machine/5160/cga/256kb/array/machine.xml index 1b678c73ac..4bf6237a50 100644 --- a/devices/pcx86/machine/5160/cga/256kb/array/machine.xml +++ b/devices/pcx86/machine/5160/cga/256kb/array/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 256K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5160/cga/256kb/debugger/machine.xml b/devices/pcx86/machine/5160/cga/256kb/debugger/machine.xml index 482bb404b5..e2151225f3 100644 --- a/devices/pcx86/machine/5160/cga/256kb/debugger/machine.xml +++ b/devices/pcx86/machine/5160/cga/256kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 256K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5160/cga/256kb/machine.xml b/devices/pcx86/machine/5160/cga/256kb/machine.xml index 9e7033ad0e..4dea77b1cc 100644 --- a/devices/pcx86/machine/5160/cga/256kb/machine.xml +++ b/devices/pcx86/machine/5160/cga/256kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 256K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5160/cga/512kb/debugger/machine.xml b/devices/pcx86/machine/5160/cga/512kb/debugger/machine.xml index de899d6ebe..e9b3756aa2 100644 --- a/devices/pcx86/machine/5160/cga/512kb/debugger/machine.xml +++ b/devices/pcx86/machine/5160/cga/512kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 512K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5160/cga/640kb/debugger/machine.xml b/devices/pcx86/machine/5160/cga/640kb/debugger/machine.xml index a0645d88ef..6755c62c92 100644 --- a/devices/pcx86/machine/5160/cga/640kb/debugger/machine.xml +++ b/devices/pcx86/machine/5160/cga/640kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 640K RAM, 10Mb Drive (Formatted) diff --git a/devices/pcx86/machine/5160/cga/640kb/machine.xml b/devices/pcx86/machine/5160/cga/640kb/machine.xml index 27dd202ca7..7252c2478b 100644 --- a/devices/pcx86/machine/5160/cga/640kb/machine.xml +++ b/devices/pcx86/machine/5160/cga/640kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 640K RAM, 10Mb Drive (Formatted) diff --git a/devices/pcx86/machine/5160/cga/640kb/mouse/debugger/machine.xml b/devices/pcx86/machine/5160/cga/640kb/mouse/debugger/machine.xml index ba187d19c3..bcee765e1d 100644 --- a/devices/pcx86/machine/5160/cga/640kb/mouse/debugger/machine.xml +++ b/devices/pcx86/machine/5160/cga/640kb/mouse/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 640K RAM, 10Mb Drive (Formatted) diff --git a/devices/pcx86/machine/5160/cga/640kb/mouse/machine.xml b/devices/pcx86/machine/5160/cga/640kb/mouse/machine.xml index 3d597f5da8..34a12046a8 100644 --- a/devices/pcx86/machine/5160/cga/640kb/mouse/machine.xml +++ b/devices/pcx86/machine/5160/cga/640kb/mouse/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Color Display, 640K RAM, 10Mb Drive (Formatted) diff --git a/devices/pcx86/machine/5160/ega/256kb/color/debugger/machine.xml b/devices/pcx86/machine/5160/ega/256kb/color/debugger/machine.xml index 816b7e634f..d0dbe4b2ab 100644 --- a/devices/pcx86/machine/5160/ega/256kb/color/debugger/machine.xml +++ b/devices/pcx86/machine/5160/ega/256kb/color/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 256K EGA, 256K RAM, 10Mb Hard Disk, Color Display diff --git a/devices/pcx86/machine/5160/ega/256kb/color/machine.xml b/devices/pcx86/machine/5160/ega/256kb/color/machine.xml index 77850d4be1..b8ffa1a742 100644 --- a/devices/pcx86/machine/5160/ega/256kb/color/machine.xml +++ b/devices/pcx86/machine/5160/ega/256kb/color/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 256K EGA, 256K RAM, 10Mb Hard Disk, Color Display diff --git a/devices/pcx86/machine/5160/ega/256kb/debugger/machine.xml b/devices/pcx86/machine/5160/ega/256kb/debugger/machine.xml index d6b7314180..9ff6ac6d93 100644 --- a/devices/pcx86/machine/5160/ega/256kb/debugger/machine.xml +++ b/devices/pcx86/machine/5160/ega/256kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 256K EGA, 256K RAM, 10Mb Hard Disk, Enhanced Color Display diff --git a/devices/pcx86/machine/5160/ega/256kb/machine.xml b/devices/pcx86/machine/5160/ega/256kb/machine.xml index 7ebab18e1b..f034152397 100644 --- a/devices/pcx86/machine/5160/ega/256kb/machine.xml +++ b/devices/pcx86/machine/5160/ega/256kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 256K EGA, 256K RAM, 10Mb Hard Disk, Enhanced Color Display diff --git a/devices/pcx86/machine/5160/ega/256kb/mono/debugger/machine.xml b/devices/pcx86/machine/5160/ega/256kb/mono/debugger/machine.xml index a75deee9fb..d17555057e 100644 --- a/devices/pcx86/machine/5160/ega/256kb/mono/debugger/machine.xml +++ b/devices/pcx86/machine/5160/ega/256kb/mono/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 256K EGA, 256K RAM, 10Mb Hard Disk, Monochrome Display diff --git a/devices/pcx86/machine/5160/ega/256kb/mono/machine.xml b/devices/pcx86/machine/5160/ega/256kb/mono/machine.xml index c8a18f999c..7c292df8e1 100644 --- a/devices/pcx86/machine/5160/ega/256kb/mono/machine.xml +++ b/devices/pcx86/machine/5160/ega/256kb/mono/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 256K EGA, 256K RAM, 10Mb Hard Disk, Monochrome Display diff --git a/devices/pcx86/machine/5160/ega/640kb/array/machine.xml b/devices/pcx86/machine/5160/ega/640kb/array/machine.xml index 405d4a9320..0123af9ca9 100644 --- a/devices/pcx86/machine/5160/ega/640kb/array/machine.xml +++ b/devices/pcx86/machine/5160/ega/640kb/array/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 128K EGA, 640K RAM, 10Mb Hard Disk diff --git a/devices/pcx86/machine/5160/ega/640kb/debugger/machine.xml b/devices/pcx86/machine/5160/ega/640kb/debugger/machine.xml index 033deda9cd..55c5c331b8 100644 --- a/devices/pcx86/machine/5160/ega/640kb/debugger/machine.xml +++ b/devices/pcx86/machine/5160/ega/640kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 128K EGA, 640K RAM, 10Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5160/ega/640kb/machine.xml b/devices/pcx86/machine/5160/ega/640kb/machine.xml index e709274c25..0ecc32bea8 100644 --- a/devices/pcx86/machine/5160/ega/640kb/machine.xml +++ b/devices/pcx86/machine/5160/ega/640kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 128K EGA, 640K RAM, 10Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5160/mda/256kb/debugger/machine.xml b/devices/pcx86/machine/5160/mda/256kb/debugger/machine.xml index 914a42bf31..7583275bde 100644 --- a/devices/pcx86/machine/5160/mda/256kb/debugger/machine.xml +++ b/devices/pcx86/machine/5160/mda/256kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Monochrome Display, 256K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5160/mda/256kb/fake188/debugger/machine.xml b/devices/pcx86/machine/5160/mda/256kb/fake188/debugger/machine.xml index 2a91865b0d..c044caf80e 100644 --- a/devices/pcx86/machine/5160/mda/256kb/fake188/debugger/machine.xml +++ b/devices/pcx86/machine/5160/mda/256kb/fake188/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT (80188 CPU), Monochrome Display, 256K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5160/mda/256kb/fake188/machine.xml b/devices/pcx86/machine/5160/mda/256kb/fake188/machine.xml index cfea7f674c..583f60bf22 100644 --- a/devices/pcx86/machine/5160/mda/256kb/fake188/machine.xml +++ b/devices/pcx86/machine/5160/mda/256kb/fake188/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT (80188 CPU), Monochrome Display, 256K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5160/mda/256kb/machine.xml b/devices/pcx86/machine/5160/mda/256kb/machine.xml index 05b8dc0a65..b0ae26f87c 100644 --- a/devices/pcx86/machine/5160/mda/256kb/machine.xml +++ b/devices/pcx86/machine/5160/mda/256kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT (Model 5160), MDA, 256Kb, 10Mb Drive diff --git a/devices/pcx86/machine/5160/mda/64kb/debugger/machine.xml b/devices/pcx86/machine/5160/mda/64kb/debugger/machine.xml index 81c265a918..82a00ef85f 100644 --- a/devices/pcx86/machine/5160/mda/64kb/debugger/machine.xml +++ b/devices/pcx86/machine/5160/mda/64kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, Monochrome Display, 64K RAM, 10Mb Drive diff --git a/devices/pcx86/machine/5170/cga/640kb/rev3/debugger/machine.xml b/devices/pcx86/machine/5170/cga/640kb/rev3/debugger/machine.xml index df809837a0..0d9cecc7a4 100644 --- a/devices/pcx86/machine/5170/cga/640kb/rev3/debugger/machine.xml +++ b/devices/pcx86/machine/5170/cga/640kb/rev3/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz, 640Kb, Dual Floppy) with Color Display diff --git a/devices/pcx86/machine/5170/cga/640kb/rev3/machine.xml b/devices/pcx86/machine/5170/cga/640kb/rev3/machine.xml index 2feb62e696..4c21ee4d6a 100644 --- a/devices/pcx86/machine/5170/cga/640kb/rev3/machine.xml +++ b/devices/pcx86/machine/5170/cga/640kb/rev3/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz, 640Kb, Dual Floppy) with Color Display diff --git a/devices/pcx86/machine/5170/ega/1152kb/rev1/debugger/machine.xml b/devices/pcx86/machine/5170/ega/1152kb/rev1/debugger/machine.xml index 40370040f9..e0c1f54a1f 100644 --- a/devices/pcx86/machine/5170/ega/1152kb/rev1/debugger/machine.xml +++ b/devices/pcx86/machine/5170/ega/1152kb/rev1/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (6Mhz), 128Kb EGA, 1152Kb RAM, 20Mb Hard Disk diff --git a/devices/pcx86/machine/5170/ega/1152kb/rev1/machine.xml b/devices/pcx86/machine/5170/ega/1152kb/rev1/machine.xml index 3e6fe60c0b..565c1e917c 100644 --- a/devices/pcx86/machine/5170/ega/1152kb/rev1/machine.xml +++ b/devices/pcx86/machine/5170/ega/1152kb/rev1/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (6Mhz), 128Kb EGA, 1152Kb RAM, 20Mb Hard Disk diff --git a/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/machine.xml b/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/machine.xml index 7dddd72dee..6b125459b1 100644 --- a/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/machine.xml +++ b/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 128Kb EGA, 1152Kb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/visual/machine.xml b/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/visual/machine.xml index 71f03c203a..e93e1bef6c 100644 --- a/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/visual/machine.xml +++ b/devices/pcx86/machine/5170/ega/1152kb/rev3/debugger/visual/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 128Kb EGA, 1152Kb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/ega/1152kb/rev3/machine.xml b/devices/pcx86/machine/5170/ega/1152kb/rev3/machine.xml index 8df3b88be0..9008e9388b 100644 --- a/devices/pcx86/machine/5170/ega/1152kb/rev3/machine.xml +++ b/devices/pcx86/machine/5170/ega/1152kb/rev3/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 128Kb EGA, 1152Kb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/machine.xml b/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/machine.xml index 4ffbb55174..dac5afcba5 100644 --- a/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/machine.xml +++ b/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 128Kb EGA, 2Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/visual/machine.xml b/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/visual/machine.xml index d9e3c654d1..6ce38f5a3f 100644 --- a/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/visual/machine.xml +++ b/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/visual/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 128Kb EGA, 2Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/vt100/machine.xml b/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/vt100/machine.xml index 890d69df5d..4b95f49242 100644 --- a/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/vt100/machine.xml +++ b/devices/pcx86/machine/5170/ega/2048kb/rev3/debugger/vt100/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 128Kb EGA, 2Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/ega/2048kb/rev3/machine.xml b/devices/pcx86/machine/5170/ega/2048kb/rev3/machine.xml index ea8f30d7ba..3a1670ed22 100644 --- a/devices/pcx86/machine/5170/ega/2048kb/rev3/machine.xml +++ b/devices/pcx86/machine/5170/ega/2048kb/rev3/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 128Kb EGA, 2Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/ega/640kb/rev1/color/debugger/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev1/color/debugger/machine.xml index 149d466bde..c381dc5fc3 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev1/color/debugger/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev1/color/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT, 640Kb RAM, 128K EGA, 47Mb Hard Disk, Color Display diff --git a/devices/pcx86/machine/5170/ega/640kb/rev1/color/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev1/color/machine.xml index a54fa0ed0c..6892730aba 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev1/color/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev1/color/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT, 640Kb RAM, 128K EGA, 47Mb Drive, Color Display diff --git a/devices/pcx86/machine/5170/ega/640kb/rev1/debugger/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev1/debugger/machine.xml index 8e5e142ca1..d28c0b816a 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev1/debugger/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev1/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT, 640Kb RAM, 128K EGA, 47Mb Drive, Enhanced Color Display diff --git a/devices/pcx86/machine/5170/ega/640kb/rev1/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev1/machine.xml index 23bc3b7b47..f112433a3b 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev1/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev1/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT, 640Kb RAM, 128K EGA, 47Mb Drive, Enhanced Color Display diff --git a/devices/pcx86/machine/5170/ega/640kb/rev1/mono/debugger/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev1/mono/debugger/machine.xml index 49f2e87875..1a9c326edd 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev1/mono/debugger/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev1/mono/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT, 640Kb RAM, 128K EGA, 47Mb Drive, Monochrome Display diff --git a/devices/pcx86/machine/5170/ega/640kb/rev1/mono/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev1/mono/machine.xml index 666465cc7c..a8d39d9da9 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev1/mono/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev1/mono/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT, 640Kb RAM, 128K EGA, 47Mb Drive, Monochrome Display diff --git a/devices/pcx86/machine/5170/ega/640kb/rev1/testmon/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev1/testmon/machine.xml index a165b7c0bc..99943554d8 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev1/testmon/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev1/testmon/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT, 640Kb RAM, 128K EGA, 47Mb Drive, Enhanced Color Display diff --git a/devices/pcx86/machine/5170/ega/640kb/rev3/debugger/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev3/debugger/machine.xml index 2869c45885..5d58160417 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev3/debugger/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev3/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 640Kb RAM, 128K EGA diff --git a/devices/pcx86/machine/5170/ega/640kb/rev3/machine.xml b/devices/pcx86/machine/5170/ega/640kb/rev3/machine.xml index 158be09a81..a574e2ba05 100644 --- a/devices/pcx86/machine/5170/ega/640kb/rev3/machine.xml +++ b/devices/pcx86/machine/5170/ega/640kb/rev3/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), 640Kb RAM, 128K EGA diff --git a/devices/pcx86/machine/5170/mda/640kb/rev3/debugger/machine.xml b/devices/pcx86/machine/5170/mda/640kb/rev3/debugger/machine.xml index 9b96f55663..9cc0a47f8b 100644 --- a/devices/pcx86/machine/5170/mda/640kb/rev3/debugger/machine.xml +++ b/devices/pcx86/machine/5170/mda/640kb/rev3/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz, 640Kb, Dual Floppy) with Monochrome Display diff --git a/devices/pcx86/machine/5170/mda/640kb/rev3/machine.xml b/devices/pcx86/machine/5170/mda/640kb/rev3/machine.xml index 7bd4f555a0..e81ae33572 100644 --- a/devices/pcx86/machine/5170/mda/640kb/rev3/machine.xml +++ b/devices/pcx86/machine/5170/mda/640kb/rev3/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz, 640Kb, Dual Floppy) with Monochrome Display diff --git a/devices/pcx86/machine/5170/vga/2048kb/debugger/machine.xml b/devices/pcx86/machine/5170/vga/2048kb/debugger/machine.xml index 7861aa4131..bc4029aba5 100644 --- a/devices/pcx86/machine/5170/vga/2048kb/debugger/machine.xml +++ b/devices/pcx86/machine/5170/vga/2048kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), VGA, 2Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/vga/2048kb/machine.xml b/devices/pcx86/machine/5170/vga/2048kb/machine.xml index 8d1517a7f6..6bf462b35b 100644 --- a/devices/pcx86/machine/5170/vga/2048kb/machine.xml +++ b/devices/pcx86/machine/5170/vga/2048kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), VGA, 2Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/vga/4096kb/debugger/machine.xml b/devices/pcx86/machine/5170/vga/4096kb/debugger/machine.xml index 089c8d361c..c240b96947 100644 --- a/devices/pcx86/machine/5170/vga/4096kb/debugger/machine.xml +++ b/devices/pcx86/machine/5170/vga/4096kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), VGA, 4Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/5170/vga/4096kb/machine.xml b/devices/pcx86/machine/5170/vga/4096kb/machine.xml index 23ecdd9c37..4ea5ac75d6 100644 --- a/devices/pcx86/machine/5170/vga/4096kb/machine.xml +++ b/devices/pcx86/machine/5170/vga/4096kb/machine.xml @@ -1,5 +1,5 @@ - + IBM PC AT (8Mhz), VGA, 4Mb RAM, 20Mb Hard Disk (Formatted) diff --git a/devices/pcx86/machine/att/6300/cga/640kb/debugger/machine.xml b/devices/pcx86/machine/att/6300/cga/640kb/debugger/machine.xml index db7f92f203..c010d2b1d9 100644 --- a/devices/pcx86/machine/att/6300/cga/640kb/debugger/machine.xml +++ b/devices/pcx86/machine/att/6300/cga/640kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + AT&T Personal Computer 6300 with Color Display diff --git a/devices/pcx86/machine/att/6300/cga/640kb/machine.xml b/devices/pcx86/machine/att/6300/cga/640kb/machine.xml index 005721b150..4dca83e1de 100644 --- a/devices/pcx86/machine/att/6300/cga/640kb/machine.xml +++ b/devices/pcx86/machine/att/6300/cga/640kb/machine.xml @@ -1,5 +1,5 @@ - + AT&T Personal Computer 6300 with Color Display diff --git a/devices/pcx86/machine/cdp/mpc1600/cga/640kb/debugger/machine.xml b/devices/pcx86/machine/cdp/mpc1600/cga/640kb/debugger/machine.xml index fadd5fa1b3..98b0805ee2 100644 --- a/devices/pcx86/machine/cdp/mpc1600/cga/640kb/debugger/machine.xml +++ b/devices/pcx86/machine/cdp/mpc1600/cga/640kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + Columbia Data Products MPC 1600 with Color Display diff --git a/devices/pcx86/machine/cdp/mpc1600/cga/640kb/machine.xml b/devices/pcx86/machine/cdp/mpc1600/cga/640kb/machine.xml index 8ef5144134..294df728a3 100644 --- a/devices/pcx86/machine/cdp/mpc1600/cga/640kb/machine.xml +++ b/devices/pcx86/machine/cdp/mpc1600/cga/640kb/machine.xml @@ -1,5 +1,5 @@ - + Columbia Data Products MPC 1600 with Color Display diff --git a/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/debugger/machine.xml b/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/debugger/machine.xml index 204060dc56..1c5ec611ef 100644 --- a/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/debugger/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 2Mb RAM, 128Kb IBM EGA diff --git a/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/machine.xml b/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/machine.xml index 8d700bfab0..c954c14c83 100644 --- a/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/ega/2048kb/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 2Mb RAM, 128Kb IBM EGA diff --git a/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/debugger/machine.xml b/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/debugger/machine.xml index 97376a5c50..c8b7cef296 100644 --- a/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/debugger/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 4Mb RAM, 128Kb IBM EGA diff --git a/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/machine.xml b/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/machine.xml index 544c37636c..1c5e3864c8 100644 --- a/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/ega/4096kb/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 4Mb RAM, 128Kb IBM EGA diff --git a/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/machine.xml b/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/machine.xml index 12af9b18f0..911da82836 100644 --- a/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 2Mb RAM, COMPAQ VGA, 47Mb Hard Disk (Unformatted) diff --git a/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/visual/machine.xml b/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/visual/machine.xml index 71b54db966..7ddb3ca309 100644 --- a/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/visual/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/other/2048kb/debugger/visual/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 2Mb RAM, COMPAQ VGA, 47Mb Hard Disk (Unformatted) diff --git a/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/machine.xml b/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/machine.xml index 59061935bd..afbe52bba3 100644 --- a/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 2Mb RAM, IBM VGA diff --git a/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/visual/machine.xml b/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/visual/machine.xml index 625b13647e..0d948e40ea 100644 --- a/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/visual/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/debugger/visual/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 2Mb RAM, IBM VGA diff --git a/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/machine.xml b/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/machine.xml index 2b1419f162..e51ec94acf 100644 --- a/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/vga/2048kb/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 2Mb RAM, IBM VGA diff --git a/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/machine.xml b/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/machine.xml index 38fc8465a5..e30f67f622 100644 --- a/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 4Mb RAM, IBM VGA diff --git a/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/visual/machine.xml b/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/visual/machine.xml index 7c928a4433..081de9b168 100644 --- a/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/visual/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/debugger/visual/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 4Mb RAM, IBM VGA diff --git a/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/machine.xml b/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/machine.xml index 6db952103c..ac6876d1c8 100644 --- a/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/machine.xml +++ b/devices/pcx86/machine/compaq/deskpro386/vga/4096kb/machine.xml @@ -1,5 +1,5 @@ - + COMPAQ DeskPro 386, 4Mb RAM, IBM VGA diff --git a/devices/pcx86/machine/custom/machine.xml b/devices/pcx86/machine/custom/machine.xml index 82e805322a..a23e7ca45b 100644 --- a/devices/pcx86/machine/custom/machine.xml +++ b/devices/pcx86/machine/custom/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150) with Monochrome Display diff --git a/devices/pcx86/machine/zenith/z150/cga/640kb/debugger/machine.xml b/devices/pcx86/machine/zenith/z150/cga/640kb/debugger/machine.xml index 73b7bbb35e..a5d18c374d 100644 --- a/devices/pcx86/machine/zenith/z150/cga/640kb/debugger/machine.xml +++ b/devices/pcx86/machine/zenith/z150/cga/640kb/debugger/machine.xml @@ -1,5 +1,5 @@ - + Zenith Z-150 with Color Display diff --git a/devices/pcx86/machine/zenith/z150/cga/640kb/machine.xml b/devices/pcx86/machine/zenith/z150/cga/640kb/machine.xml index 80bff12ad3..c7810cc8e0 100644 --- a/devices/pcx86/machine/zenith/z150/cga/640kb/machine.xml +++ b/devices/pcx86/machine/zenith/z150/cga/640kb/machine.xml @@ -1,5 +1,5 @@ - + Zenith Z-150 with Color Display diff --git a/devices/pdp10/machine/ka10/test/debugger/machine.xml b/devices/pdp10/machine/ka10/test/debugger/machine.xml index 16063e4950..65f39c6eb3 100644 --- a/devices/pdp10/machine/ka10/test/debugger/machine.xml +++ b/devices/pdp10/machine/ka10/test/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-10 (Model KA10) with Debugger diff --git a/devices/pdp10/machine/ka10/test/machine.xml b/devices/pdp10/machine/ka10/test/machine.xml index 4d7af870ae..84ffb0c4e8 100644 --- a/devices/pdp10/machine/ka10/test/machine.xml +++ b/devices/pdp10/machine/ka10/test/machine.xml @@ -1,5 +1,5 @@ - + PDP-10 Test Machine diff --git a/devices/pdp11/machine/1120/basic/debugger/machine.xml b/devices/pdp11/machine/1120/basic/debugger/machine.xml index 005ea8d181..6488d7a892 100644 --- a/devices/pdp11/machine/1120/basic/debugger/machine.xml +++ b/devices/pdp11/machine/1120/basic/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20: 16Kb, PDP-11 BASIC, Debugger diff --git a/devices/pdp11/machine/1120/basic/machine.xml b/devices/pdp11/machine/1120/basic/machine.xml index 5282f3281b..c71d322894 100644 --- a/devices/pdp11/machine/1120/basic/machine.xml +++ b/devices/pdp11/machine/1120/basic/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20: 16Kb, PDP-11 BASIC diff --git a/devices/pdp11/machine/1120/bootstrap/debugger/machine.xml b/devices/pdp11/machine/1120/bootstrap/debugger/machine.xml index 56aebc8173..45c4ad6f86 100644 --- a/devices/pdp11/machine/1120/bootstrap/debugger/machine.xml +++ b/devices/pdp11/machine/1120/bootstrap/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20: 16Kb, Bootstrap Loader, Debugger diff --git a/devices/pdp11/machine/1120/bootstrap/machine.xml b/devices/pdp11/machine/1120/bootstrap/machine.xml index 47a4109414..9e9ec52293 100644 --- a/devices/pdp11/machine/1120/bootstrap/machine.xml +++ b/devices/pdp11/machine/1120/bootstrap/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20: 16Kb, Bootstrap Loader diff --git a/devices/pdp11/machine/1120/monitor/debugger/machine.xml b/devices/pdp11/machine/1120/monitor/debugger/machine.xml index 80503d84e0..ebb0de7c08 100644 --- a/devices/pdp11/machine/1120/monitor/debugger/machine.xml +++ b/devices/pdp11/machine/1120/monitor/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20 Boot Monitor with 56Kb and Debugger diff --git a/devices/pdp11/machine/1120/monitor/machine.xml b/devices/pdp11/machine/1120/monitor/machine.xml index 0859264445..482a20138a 100644 --- a/devices/pdp11/machine/1120/monitor/machine.xml +++ b/devices/pdp11/machine/1120/monitor/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20 Boot Monitor with 56Kb diff --git a/devices/pdp11/machine/1120/panel/debugger/machine.xml b/devices/pdp11/machine/1120/panel/debugger/machine.xml index 0b6034cb29..50f7210f70 100644 --- a/devices/pdp11/machine/1120/panel/debugger/machine.xml +++ b/devices/pdp11/machine/1120/panel/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20 with Front Panel and Debugger diff --git a/devices/pdp11/machine/1120/panel/debugger/test14/machine.xml b/devices/pdp11/machine/1120/panel/debugger/test14/machine.xml index 09154a1e8f..edecb7490d 100644 --- a/devices/pdp11/machine/1120/panel/debugger/test14/machine.xml +++ b/devices/pdp11/machine/1120/panel/debugger/test14/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20 with Front Panel and TEST 14 diff --git a/devices/pdp11/machine/1120/panel/machine.xml b/devices/pdp11/machine/1120/panel/machine.xml index 40726a34de..61ded5f698 100644 --- a/devices/pdp11/machine/1120/panel/machine.xml +++ b/devices/pdp11/machine/1120/panel/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/20 with Front Panel diff --git a/devices/pdp11/machine/1145/panel/debugger/machine.xml b/devices/pdp11/machine/1145/panel/debugger/machine.xml index 811054c61a..98deea93cf 100644 --- a/devices/pdp11/machine/1145/panel/debugger/machine.xml +++ b/devices/pdp11/machine/1145/panel/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/45 with 256Kb, Front Panel and Debugger diff --git a/devices/pdp11/machine/1145/panel/machine.xml b/devices/pdp11/machine/1145/panel/machine.xml index fbc4bae1fb..583c62a5cf 100644 --- a/devices/pdp11/machine/1145/panel/machine.xml +++ b/devices/pdp11/machine/1145/panel/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/45 with 256Kb and Front Panel diff --git a/devices/pdp11/machine/1145/vt100/debugger/machine.xml b/devices/pdp11/machine/1145/vt100/debugger/machine.xml index 5668f2b46c..6d984274d0 100644 --- a/devices/pdp11/machine/1145/vt100/debugger/machine.xml +++ b/devices/pdp11/machine/1145/vt100/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/45 with 256Kb, Front Panel, and Debugger diff --git a/devices/pdp11/machine/1145/vt100/machine.xml b/devices/pdp11/machine/1145/vt100/machine.xml index bb0242bcbe..711a9fe625 100644 --- a/devices/pdp11/machine/1145/vt100/machine.xml +++ b/devices/pdp11/machine/1145/vt100/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/45 with 256Kb and Front Panel diff --git a/devices/pdp11/machine/1170/4mb/debugger/machine.xml b/devices/pdp11/machine/1170/4mb/debugger/machine.xml index 61ca5e6c6c..8a28aca742 100644 --- a/devices/pdp11/machine/1170/4mb/debugger/machine.xml +++ b/devices/pdp11/machine/1170/4mb/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 4Mb, Front Panel and Debugger diff --git a/devices/pdp11/machine/1170/4mb/machine.xml b/devices/pdp11/machine/1170/4mb/machine.xml index 86041862bf..7758e9a2ca 100644 --- a/devices/pdp11/machine/1170/4mb/machine.xml +++ b/devices/pdp11/machine/1170/4mb/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 4Mb and Front Panel diff --git a/devices/pdp11/machine/1170/monitor/debugger/machine.xml b/devices/pdp11/machine/1170/monitor/debugger/machine.xml index eb63a3ff7f..e3123e3f26 100644 --- a/devices/pdp11/machine/1170/monitor/debugger/machine.xml +++ b/devices/pdp11/machine/1170/monitor/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb, Boot Monitor, and Debugger diff --git a/devices/pdp11/machine/1170/monitor/machine.xml b/devices/pdp11/machine/1170/monitor/machine.xml index f2b6ca2c36..9742321dc7 100644 --- a/devices/pdp11/machine/1170/monitor/machine.xml +++ b/devices/pdp11/machine/1170/monitor/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb and Boot Monitor diff --git a/devices/pdp11/machine/1170/panel/debugger/cpuexer/machine.xml b/devices/pdp11/machine/1170/panel/debugger/cpuexer/machine.xml index f9a0bb5b34..532f3cc01c 100644 --- a/devices/pdp11/machine/1170/panel/debugger/cpuexer/machine.xml +++ b/devices/pdp11/machine/1170/panel/debugger/cpuexer/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb running CPU Exerciser diff --git a/devices/pdp11/machine/1170/panel/debugger/machine-slim.xml b/devices/pdp11/machine/1170/panel/debugger/machine-slim.xml index daeba81f0b..4c8aa9f067 100644 --- a/devices/pdp11/machine/1170/panel/debugger/machine-slim.xml +++ b/devices/pdp11/machine/1170/panel/debugger/machine-slim.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb, Front Panel and Debugger diff --git a/devices/pdp11/machine/1170/panel/debugger/machine.xml b/devices/pdp11/machine/1170/panel/debugger/machine.xml index c43c1f3282..97fe899a3a 100644 --- a/devices/pdp11/machine/1170/panel/debugger/machine.xml +++ b/devices/pdp11/machine/1170/panel/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb, Front Panel and Debugger diff --git a/devices/pdp11/machine/1170/panel/machine.xml b/devices/pdp11/machine/1170/panel/machine.xml index 927b855d38..0dc75b608f 100644 --- a/devices/pdp11/machine/1170/panel/machine.xml +++ b/devices/pdp11/machine/1170/panel/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb and Front Panel diff --git a/devices/pdp11/machine/1170/vt100/debugger/machine.xml b/devices/pdp11/machine/1170/vt100/debugger/machine.xml index d26966fcc9..157a8125a1 100644 --- a/devices/pdp11/machine/1170/vt100/debugger/machine.xml +++ b/devices/pdp11/machine/1170/vt100/debugger/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb, Front Panel, and Debugger diff --git a/devices/pdp11/machine/1170/vt100/machine.xml b/devices/pdp11/machine/1170/vt100/machine.xml index 9cfe1b6911..cc4f767140 100644 --- a/devices/pdp11/machine/1170/vt100/machine.xml +++ b/devices/pdp11/machine/1170/vt100/machine.xml @@ -1,5 +1,5 @@ - + PDP-11/70 with 256Kb and Front Panel diff --git a/disks/pcx86/cpm/1.00/debugger/machine.xml b/disks/pcx86/cpm/1.00/debugger/machine.xml index 6d01b4757a..a9be41207e 100644 --- a/disks/pcx86/cpm/1.00/debugger/machine.xml +++ b/disks/pcx86/cpm/1.00/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), MDA, 256K, Debugger diff --git a/disks/pcx86/cpm/1.00/machine.xml b/disks/pcx86/cpm/1.00/machine.xml index a066571e8b..00b47e71c4 100644 --- a/disks/pcx86/cpm/1.00/machine.xml +++ b/disks/pcx86/cpm/1.00/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150), MDA, 256K diff --git a/disks/pcx86/games/infocom/machine.xml b/disks/pcx86/games/infocom/machine.xml index 7f10e1b9d2..69a40581d9 100644 --- a/disks/pcx86/games/infocom/machine.xml +++ b/disks/pcx86/games/infocom/machine.xml @@ -1,5 +1,5 @@ - + IBM PC Model 5150 (CGA, 256K) diff --git a/disks/pcx86/games/infocom/zork1/debugger/machine.xml b/disks/pcx86/games/infocom/zork1/debugger/machine.xml index 59fb7ca4d5..36ca0776ff 100644 --- a/disks/pcx86/games/infocom/zork1/debugger/machine.xml +++ b/disks/pcx86/games/infocom/zork1/debugger/machine.xml @@ -1,5 +1,5 @@ - + Zork I (IBM PC Model 5150) diff --git a/disks/pcx86/games/microsoft/adventure/machine.xml b/disks/pcx86/games/microsoft/adventure/machine.xml index 0c0c18b859..80f3eb047f 100644 --- a/disks/pcx86/games/microsoft/adventure/machine.xml +++ b/disks/pcx86/games/microsoft/adventure/machine.xml @@ -1,5 +1,5 @@ - + IBM PC (Model 5150) running Microsoft Adventure diff --git a/disks/pcx86/shareware/pcsig08/debugger/machine.xml b/disks/pcx86/shareware/pcsig08/debugger/machine.xml index 0059cdb501..b206d9317a 100644 --- a/disks/pcx86/shareware/pcsig08/debugger/machine.xml +++ b/disks/pcx86/shareware/pcsig08/debugger/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 128K EGA, 640K RAM, 10Mb Hard Disk (Formatted) diff --git a/disks/pcx86/shareware/pcsig08/machine.xml b/disks/pcx86/shareware/pcsig08/machine.xml index f727501f77..27aa47ce0a 100644 --- a/disks/pcx86/shareware/pcsig08/machine.xml +++ b/disks/pcx86/shareware/pcsig08/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT, 128K EGA, 640K RAM, 10Mb Hard Disk (Formatted) diff --git a/modules/pcx86/lib/cpux86.js b/modules/pcx86/lib/cpux86.js index c44df51e56..9272767f80 100644 --- a/modules/pcx86/lib/cpux86.js +++ b/modules/pcx86/lib/cpux86.js @@ -1160,7 +1160,7 @@ class CPUX86 extends CPU { this.regEDX = 0x0300; // in the absence of a specific stepping, set revision (DL) to zero break; } - this.regCR0 = X86.CR0.ET; // formerly MSW + this.regCR0 = X86.CR0.ON | X86.CR0.ET; this.regCR1 = 0; // reserved this.regCR2 = 0; // page fault linear address (PFLA) this.regCR3 = 0; // page directory base register (PDBR) @@ -3791,17 +3791,11 @@ class CPUX86 extends CPU { let newLIP = this.checkIP(1); let b = (PREFETCH? this.getBytePrefetch() : this.getByte(this.regLIP)); if (BACKTRACK) this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMem0); - /* * With the following cycle penalty (which really only affects 8086/8088 CPUs), PC Tools 4.30 * correctly reports an IBM PC-relative speed of 100% (assuming you're using a 4.77Mhz configuration). - * - * However, this creates monitor timing issues in the Video component; there's a work-around for that - * (see monitorSpecsXT) but this also slows the machine down much more than I would have expected, so - * for now, it's disabled. */ - // this.nStepCycles -= this.cycleCounts.nWordCyclePenalty; - + this.nStepCycles -= this.cycleCounts.nWordCyclePenalty; this.regLIP = newLIP; return b; } diff --git a/modules/pcx86/lib/debugger.js b/modules/pcx86/lib/debugger.js index 20771a7040..e73deed025 100644 --- a/modules/pcx86/lib/debugger.js +++ b/modules/pcx86/lib/debugger.js @@ -246,8 +246,6 @@ class DebuggerX86 extends Debugger { this.fpu = cmp.getMachineComponent("FPU"); this.mouse = cmp.getMachineComponent("Mouse"); - // this.video = cmp.getMachineComponent("Video"); - // this.chipset = cmp.getMachineComponent("ChipSet"); /* * Re-initialize Debugger message and command support as needed @@ -256,6 +254,17 @@ class DebuggerX86 extends Debugger { if (sMessages) this.messageInit(sMessages); this.sCommandsInit = cmp.getMachineParm('commands') || this.sCommandsInit; + /* + * If CHIPSET or VIDEO messages are enabled at startup, we enable ChipSet or Video diagnostic info in the + * instruction history buffer as appropriate. + */ + if (this.messageEnabled(Messages.CHIPSET)) { + this.chipset = cmp.getMachineComponent("ChipSet"); + } + else if (this.messageEnabled(Messages.VIDEO)) { + this.video = cmp.getMachineComponent("Video"); + } + this.cchAddr = bus.getWidth() >> 2; this.maskAddr = bus.nBusLimit; @@ -5576,7 +5585,7 @@ class DebuggerX86 extends Debugger { sign = -1; sDelta = sDelta.substr(1); } - let n = this.parseValue(sDelta, sAction); + n = this.parseValue(sDelta, sAction); if (n === undefined) return; n = (n * sign)|0; } diff --git a/modules/pcx86/lib/video.js b/modules/pcx86/lib/video.js index 9afc768076..c7c0cbc47d 100644 --- a/modules/pcx86/lib/video.js +++ b/modules/pcx86/lib/video.js @@ -452,47 +452,20 @@ class Card extends Controller { this.initEGA(data[6], nMonitorType); } - let monitorSpecs; - /* - * This is only necessary for machines that apply this.cycleCounts.nWordCyclePenalty - * to getIPByte(); currently, we're not doing that, so this work-around is not required. - * - * if (video.cpu.model <= X86.MODEL_8088) monitorSpecs = Video.monitorSpecsXT[nMonitorType]; - */ - if (!monitorSpecs) { - monitorSpecs = Video.monitorSpecs[nMonitorType] || Video.monitorSpecs[ChipSet.MONITOR.MONO]; - } + let monitorSpecs = Video.monitorSpecs[nMonitorType] || Video.monitorSpecs[ChipSet.MONITOR.MONO]; /* - * Let's look at an example of the calculations below for the COLOR monitor on an IBM PC: - * - * nCyclesDefault: 4772727 - * nCyclesHorzPeriod: (4772727 / 15700 nHorzPeriodsPerSec) = 303 - * nCyclesHorzActive: (303 * 85%) = 257 - * nCyclesVertPeriod: (303 * 208 nHorzPeriodsPerFrame) = 63024 - * nCyclesVertActive: (63024 * 96%) = 60503 - * - * getRetraceBits() calculated elapsedCycles % 63024 (nCyclesVertPeriod), and whenever that - * remainder was > 60503 (nCyclesVertActive), we were deemed in the "inactive" retrace period. - * - * That logic has been superseded by a startVerticalRetrace() timer that fires every nCyclesVertPeriod, - * which then snaps the current cycle count in nCyclesVertRetrace. Whenever getRetraceBits() is called, - * it too examines the current cycle count, and when the cycle count delta exceeds nCyclesVertPeriod - - * nCyclesVertActive, vertical retrace has ended. - * - * Here's another example of the calculations below for the EGACOLOR monitor on an IBM PC: - * - * nCyclesDefault: 4772727 - * nCyclesHorzPeriod: (4772727 / 21850 nHorzPeriodsPerSec) = 218 - * nCyclesHorzActive: (218 * 85%) = 185 - * nCyclesVertPeriod: (218 * 364 nHorzPeriodsPerFrame) = 79352 - * nCyclesVertActive: (79352 * 96%) = 76177 + * nCyclesVertPeriod determines how frequently startVerticalRetrace() is called. That function + * snaps the current cycle count in nCyclesVertRetrace. Then whenever getRetraceBits() is called, + * it subtracts nCyclesVertRetrace from the current cycle count, and whenever the delta exceeds + * nCyclesVertPeriod - nCyclesVertActive, vertical retrace has ended. Similarly, horizontal retrace + * ends whenever that delta MOD nCyclesHorzPeriod exceeds nCyclesHorzActive. */ let nCyclesDefault = video.cpu.getBaseCyclesPerSecond(); this.nCyclesHorzPeriod = (nCyclesDefault / monitorSpecs.nHorzPeriodsPerSec)|0; this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100)|0; - this.nCyclesVertPeriod = (this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame)|0; - this.nCyclesVertActive = (this.nCyclesVertPeriod * monitorSpecs.percentVertActive / 100)|0; + this.nCyclesVertActive = (this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame)|0; + this.nCyclesVertPeriod = (this.nCyclesVertActive / (monitorSpecs.percentVertActive / 100))|0; this.nCyclesVertRetrace = (data[7] || 0); } } @@ -3473,7 +3446,9 @@ class Video extends Component { this.timerRetrace = this.cpu.addTimer(this.id, function startVerticalRetrace() { let card = video.cardActive; card.nCyclesVertRetrace = video.cpu.getCycles(); - video.printf("vertical retrace timer fired (%d cycles)\n", card.nCyclesVertRetrace); + if (video.messageEnabled(Messages.VIDEO | Messages.INT)) { + video.printf("vertical retrace timer fired (%d cycles)\n", card.nCyclesVertRetrace); + } if (video.nIRQ) { if (!(card.regCRTData[Card.CRTC.EGA.VREND.INDX] & Card.CRTC.EGA.VREND.DISABLE_VRINT)) { if (video.chipset) video.chipset.setIRR(video.nIRQ); @@ -3518,7 +3493,8 @@ class Video extends Component { } } video.msUpdatePrev = msUpdate - (msDelta >= video.msUpdateInterval? 0 : msDelta); - } else { + } + else if (video.messageEnabled(Messages.VIDEO | Messages.INT)) { video.printf("skipping update (%dms too soon)\n", -msDelta); } video.latchStartAddress(); @@ -6451,12 +6427,13 @@ class Video extends Component { card.nCyclesVertRetrace = nCycles; nCyclesElapsed = 0; } - if (nCyclesElapsed < card.nCyclesVertPeriod - card.nCyclesVertActive) { + nCyclesElapsed -= card.nCyclesVertPeriod - card.nCyclesVertActive; + if (nCyclesElapsed < 0) { b |= Card.CGA.STATUS.VRETRACE | Card.CGA.STATUS.RETRACE; // this.printf("vertical retrace (%d cycles)\n", nCyclesElapsed); } else { let nCyclesHorzRemain = nCyclesElapsed % card.nCyclesHorzPeriod; - if (nCyclesHorzRemain < card.nCyclesHorzPeriod - card.nCyclesHorzActive) { + if (nCyclesHorzRemain > card.nCyclesHorzActive) { b |= Card.CGA.STATUS.RETRACE; // this.printf("horizontal retrace (%d cycles)\n", nCyclesElapsed); } else { @@ -7985,84 +7962,57 @@ Video.MODEL = { * @property {number} nHorzPeriodsPerFrame * @property {number} percentHorzActive * @property {number} percentVertActive - * - * From these monitor specs, we calculate the following values for a given Card: - * - * nCyclesDefault = cpu.getBaseCyclesPerSecond(); // eg, 4772727 - * nCyclesHorzPeriod = (nCyclesDefault / monitorSpecs.nHorzPeriodsPerSec) | 0; - * nCyclesHorzActive = (nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100) | 0; - * nCyclesVertPeriod = nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame; - * nCyclesVertActive = (nCyclesVertPeriod * monitorSpecs.percentVertActive / 100) | 0; */ /** * @type {Object} */ Video.monitorSpecs = {}; -Video.monitorSpecsXT = {}; /** - * NOTE: Based on trial-and-error, 208 is the magic number of horizontal syncs per vertical sync that - * yielded the necessary number of "horizontal enables" (200 or 0xC8) in the EGA ROM BIOS at C000:03D0. + * NOTE: The number of horizontal periods per frame (200) is dictated by the EGA ROM BIOS at C000:03D0. * * @type {MonitorSpecs} */ Video.monitorSpecs[ChipSet.MONITOR.COLOR] = { nHorzPeriodsPerSec: 15700, - nHorzPeriodsPerFrame: 208, - percentHorzActive: 85, + nHorzPeriodsPerFrame: 200, + percentHorzActive: 75, percentVertActive: 96 }; /** - * NOTE: Based on trial-and-error, 364 is the magic number of horizontal syncs per vertical sync that - * yielded the necessary number of "horizontal enables" (350 or 0x15E) in the EGA ROM BIOS at C000:03D0. + * NOTE: The number of horizontal periods per frame (350) is dictated by the EGA ROM BIOS at C000:03D0. * * @type {MonitorSpecs} */ Video.monitorSpecs[ChipSet.MONITOR.MONO] = { nHorzPeriodsPerSec: 18432, - nHorzPeriodsPerFrame: 364, - percentHorzActive: 85, - percentVertActive: 96 -}; - -Video.monitorSpecsXT[ChipSet.MONITOR.MONO] = { - nHorzPeriodsPerSec: 18432, - nHorzPeriodsPerFrame: 365, - percentHorzActive: 77, + nHorzPeriodsPerFrame: 350, + percentHorzActive: 75, percentVertActive: 96 }; /** + * NOTE: The number of horizontal periods per frame (350) is dictated by the EGA ROM BIOS at C000:03D0. + * * @type {MonitorSpecs} */ Video.monitorSpecs[ChipSet.MONITOR.EGACOLOR] = { nHorzPeriodsPerSec: 21850, - nHorzPeriodsPerFrame: 364, - percentHorzActive: 80, - percentVertActive: 96 -}; - -/** - * @type {MonitorSpecs} - */ -Video.monitorSpecsXT[ChipSet.MONITOR.EGACOLOR] = { - nHorzPeriodsPerSec: 21850, - nHorzPeriodsPerFrame: 365, - percentHorzActive: 77, + nHorzPeriodsPerFrame: 350, + percentHorzActive: 75, percentVertActive: 96 }; /** - * NOTE: As above, the following values are based purely on trial-and-error, to yield results that fall - * squarely within the bounds of the IBM VGA ROM timing requirements; see the IBM VGA ROM code at C000:024A. + * NOTE: The number of horizontal periods per frame (410) is dictated by the IBM VGA ROM code at C000:024A. * * @type {MonitorSpecs} */ Video.monitorSpecs[ChipSet.MONITOR.VGACOLOR] = { nHorzPeriodsPerSec: 16700, - nHorzPeriodsPerFrame: 480, + nHorzPeriodsPerFrame: 410, percentHorzActive: 85, percentVertActive: 83 }; diff --git a/modules/pcx86/lib/x86.js b/modules/pcx86/lib/x86.js index 70a922bcfe..6e6cebeedc 100644 --- a/modules/pcx86/lib/x86.js +++ b/modules/pcx86/lib/x86.js @@ -154,7 +154,8 @@ var X86 = { MASK: 0xFFFF // these are the only (MSW) bits that the 80286 can access (within CR0) }, ET: 0x00000010, // coprocessor type (80287 or 80387); always 1 on post-80386 CPUs - PG: 0x80000000|0 // 0: paging disabled + ON: 0x7FFFFFE0, // CR0 bits that are always on + PG: 0x80000000|0, // 0: paging disabled }, DR7: { // Debug Control Register L0: 0x00000001, diff --git a/modules/pcx86/lib/x86help.js b/modules/pcx86/lib/x86help.js index 878fd07a72..80c32443ec 100644 --- a/modules/pcx86/lib/x86help.js +++ b/modules/pcx86/lib/x86help.js @@ -241,14 +241,12 @@ X86.helpINCreg = function(w) * * This is called by an 80386 control instruction (ie, MOV CR0,reg). * - * TODO: Determine which CR0 bits, if any, cannot be modified by MOV CR0,reg. - * * @this {CPUX86} * @param {number} l */ X86.helpLoadCR0 = function(l) { - this.regCR0 = l; + this.regCR0 = l | X86.CR0.ON; this.setProtMode(); if (this.regCR0 & X86.CR0.PG) { /* diff --git a/package.json b/package.json index 8144171cbd..5dfebece9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pcjs", - "version": "1.71.4", + "version": "1.71.5", "year": 2018, "description": "Node-enabled version of PCjs", "license": "GPL-3.0", diff --git a/pcjs-disks b/pcjs-disks index 5b527ef34c..e14de7d43d 160000 --- a/pcjs-disks +++ b/pcjs-disks @@ -1 +1 @@ -Subproject commit 5b527ef34ce6ea7ebea1632958d6971950559ef7 +Subproject commit e14de7d43d0cf3ddb27b9a142437fb8b255efa6b diff --git a/pcjs-games b/pcjs-games index 94febe6f37..f4fe45c396 160000 --- a/pcjs-games +++ b/pcjs-games @@ -1 +1 @@ -Subproject commit 94febe6f37ce0375e62f22f8f1d5783d12dd3372 +Subproject commit f4fe45c396420105f7fcb88f64c164e3a3861dfe diff --git a/private-disks b/private-disks index 58361569ac..271bbedce0 160000 --- a/private-disks +++ b/private-disks @@ -1 +1 @@ -Subproject commit 58361569acd2465354a56c83266ae53c0f8563bc +Subproject commit 271bbedce0fb1caa414df820cbbc209bfd82f5f2 diff --git a/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine-debugger.xml b/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine-debugger.xml index c595d5666d..ad4bcfd43f 100644 --- a/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine-debugger.xml +++ b/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine-debugger.xml @@ -1,5 +1,5 @@ - + IBM PC XT (Model 5160), CGA, 256Kb, 10Mb Drive diff --git a/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine.xml b/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine.xml index 728fff06db..db82d4d0c6 100644 --- a/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine.xml +++ b/pubs/pc/programming/Graphics_for_the_IBM_PC/demos/machine.xml @@ -1,5 +1,5 @@ - + IBM PC XT (Model 5160), CGA, 256Kb, 10Mb Drive diff --git a/pubs/pc/programming/manifest.xml b/pubs/pc/programming/manifest.xml index da62cf4fa0..d2a0b96015 100644 --- a/pubs/pc/programming/manifest.xml +++ b/pubs/pc/programming/manifest.xml @@ -1,5 +1,5 @@ - + PC Programming Guides diff --git a/pubs/pc/reference/ibm/5150/techref/manifest.xml b/pubs/pc/reference/ibm/5150/techref/manifest.xml index c8f2eecf73..5cf89846da 100644 --- a/pubs/pc/reference/ibm/5150/techref/manifest.xml +++ b/pubs/pc/reference/ibm/5150/techref/manifest.xml @@ -1,5 +1,5 @@ - + IBM 5150 Technical Reference minuszerodegrees.net diff --git a/pubs/pc/reference/ibm/5160/techref/manifest.xml b/pubs/pc/reference/ibm/5160/techref/manifest.xml index 550567d424..0ad4b84d75 100644 --- a/pubs/pc/reference/ibm/5160/techref/manifest.xml +++ b/pubs/pc/reference/ibm/5160/techref/manifest.xml @@ -1,5 +1,5 @@ - + IBM 5160 Technical Reference retroarchive.org diff --git a/pubs/pc/reference/ibm/5170/setup/manifest.xml b/pubs/pc/reference/ibm/5170/setup/manifest.xml index e9e57d1b9f..23ab693f42 100644 --- a/pubs/pc/reference/ibm/5170/setup/manifest.xml +++ b/pubs/pc/reference/ibm/5170/setup/manifest.xml @@ -1,5 +1,5 @@ - + IBM 5170 Installation and Setup minuszerodegrees.net diff --git a/pubs/pc/reference/ibm/5170/techref/manifest.xml b/pubs/pc/reference/ibm/5170/techref/manifest.xml index 28efefd13e..198ade02cf 100644 --- a/pubs/pc/reference/ibm/5170/techref/manifest.xml +++ b/pubs/pc/reference/ibm/5170/techref/manifest.xml @@ -1,5 +1,5 @@ - + IBM 5170 Technical Reference minuszerodegrees.net diff --git a/pubs/pc/reference/ibm/ps2/manifest.xml b/pubs/pc/reference/ibm/ps2/manifest.xml index ed9e5d605d..232c815d19 100644 --- a/pubs/pc/reference/ibm/ps2/manifest.xml +++ b/pubs/pc/reference/ibm/ps2/manifest.xml @@ -1,5 +1,5 @@ - + PS/2 Technical Reference diff --git a/pubs/pc/reference/ibm/video/ega/manifest.xml b/pubs/pc/reference/ibm/video/ega/manifest.xml index e6b7e37e0b..0d3d33314b 100644 --- a/pubs/pc/reference/ibm/video/ega/manifest.xml +++ b/pubs/pc/reference/ibm/video/ega/manifest.xml @@ -1,5 +1,5 @@ - + IBM Enhanced Graphics Adapter minuszerodegrees.net diff --git a/pubs/pc/reference/intel/80286/manifest.xml b/pubs/pc/reference/intel/80286/manifest.xml index e30244c6d2..1a0a3e9a8f 100644 --- a/pubs/pc/reference/intel/80286/manifest.xml +++ b/pubs/pc/reference/intel/80286/manifest.xml @@ -1,5 +1,5 @@ - + Intel 80286 References diff --git a/pubs/pc/software/os2/microsoft/sdk10/manifest.xml b/pubs/pc/software/os2/microsoft/sdk10/manifest.xml index 31db61006c..d7ab3c56c0 100644 --- a/pubs/pc/software/os2/microsoft/sdk10/manifest.xml +++ b/pubs/pc/software/os2/microsoft/sdk10/manifest.xml @@ -1,5 +1,5 @@ - + OS/2 1.0 Programmer's Toolkit 1.0 diff --git a/pubs/pc/software/windows/sdk200/manifest.xml b/pubs/pc/software/windows/sdk200/manifest.xml index b595d401d7..e38a58e9d3 100644 --- a/pubs/pc/software/windows/sdk200/manifest.xml +++ b/pubs/pc/software/windows/sdk200/manifest.xml @@ -1,5 +1,5 @@ - + Microsoft Windows Software Development Kit 2.00 diff --git a/versions/c1pjs/1.71.5/c1p-uncompiled.js b/versions/c1pjs/1.71.5/c1p-uncompiled.js new file mode 100644 index 0000000000..af39aa1513 --- /dev/null +++ b/versions/c1pjs/1.71.5/c1p-uncompiled.js @@ -0,0 +1,14933 @@ +"use strict"; + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @define {string} + */ +var APPVERSION = ""; // this @define is overridden by the Closure Compiler with the version in machines.json + +var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML + +var COPYRIGHT = "Copyright © 2012-2018 Jeff Parsons "; + +var LICENSE = "License: GPL version 3 or later "; + +var CSSCLASS = "pcjs"; + +/** + * @define {string} + */ +var SITEURL = "http://localhost:8088"; // this @define is overridden by the Closure Compiler with "https://www.pcjs.org" + +/** + * @define {boolean} + */ +var COMPILED = false; // this @define is overridden by the Closure Compiler (to true) + +/** + * @define {boolean} + */ +var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code + +/** + * @define {boolean} + */ +var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code + +/** + * @define {boolean} + */ +var PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code + +/* + * RS-232 DB-25 Pin Definitions, mapped to bits 1-25 in a 32-bit status value. + * + * SerialPorts in PCjs machines are considered DTE (Data Terminal Equipment), which means they should be "virtually" + * connected to each other via a null-modem cable, which assumes the following cross-wiring: + * + * G 1 <-> 1 G (Ground) + * TD 2 <-> 3 RD (Received Data) + * RD 3 <-> 2 TD (Transmitted Data) + * RTS 4 <-> 5 CTS (Clear To Send) + * CTS 5 <-> 4 RTS (Request To Send) + * DSR 6+8 <-> 20 DTR (Data Terminal Ready) + * SG 7 <-> 7 SG (Signal Ground) + * DTR 20 <-> 6+8 DSR (Data Set Ready + Carrier Detect) + * RI 22 <-> 22 RI (Ring Indicator) + * + * TODO: Move these definitions to a more appropriate shared file at some point. + */ +var RS232 = { + RTS: { + PIN: 4, + MASK: 0x00000010 + }, + CTS: { + PIN: 5, + MASK: 0x00000020 + }, + DSR: { + PIN: 6, + MASK: 0x00000040 + }, + CD: { + PIN: 8, + MASK: 0x00000100 + }, + DTR: { + PIN: 20, + MASK: 0x00100000 + }, + RI: { + PIN: 22, + MASK: 0x00400000 + } +}; + +/* + * NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser) + */ +var NODE = false; + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Our "DiskDump API", such as it was, used to look like: + * + * http://pcjs.org/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * To make it (a bit) more "REST-like", the above request now looks like: + * + * https://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * Similarly, our "FileDump API" used to look like: + * + * http://pcjs.org/bin/convrom.php?rom=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * and that request now looks like: + * + * https://www.pcjs.org/api/v1/dump?file=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the + * the rest of the URL would be (a) confusing, and (b) more work to parse. + */ +var DumpAPI = { + ENDPOINT: "/api/v1/dump", + QUERY: { + DIR: "dir", // value is path of a directory (DiskDump only) + DISK: "disk", // value is path of a disk image (DiskDump only) + FILE: "file", // value is path of a ROM image file (FileDump only) + IMG: "img", // alias for DISK + PATH: "path", // value is path of a one or more files (DiskDump only) + FORMAT: "format", // value is one of FORMAT values below + COMMENTS: "comments", // value is either "true" or "false" + DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise + MBHD: "mbhd", // value is hard drive size in Mb (formerly "mbsize") (DiskDump only) (DEPRECATED) + SIZE: "size" // value is target disk size in Kb (supersedes "mbhd") (DiskDump only) + }, + FORMAT: { + JSON: "json", // default + JSON_GZ: "gz", // gzip is currently used ONLY for compressed JSON + DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only) + HEX: "hex", // deprecated + OCTAL: "octal", // displays data as octal words + BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled + WORDS: "words", // displays data as hex words; normally used only when comments are enabled + LONGS: "longs", // displays data as dwords + IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only) + ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only) + } +}; + +/* + * Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must + * also provide a list of commands which, when combined with the endpoint, define a unique request. + */ +DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH]; +DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE]; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2018 + */ + +var ReportAPI = { + ENDPOINT: "/api/v1/report", + QUERY: { + APP: "app", + VER: "ver", + URL: "url", + USER: "user", + TYPE: "type", + DATA: "data" + }, + TYPE: { + BUG: "bug" + }, + RES: { + OK: "Thank you" + } +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2018 + */ + +class Str { + /** + * isValidInt(s, base) + * + * The built-in parseInt() function has the annoying feature of returning a partial value (ie, + * up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf. + * + * So it's best to use our own Str.parseInt() function, which will in turn use this function to + * validate the entire string. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported + * @return {boolean} true if valid, false if invalid (or the specified base isn't supported) + */ + static isValidInt(s, base) + { + if (!base || base == 10) return s.match(/^-?[0-9]+$/) !== null; + if (base == 16) return s.match(/^-?[0-9a-f]+$/i) !== null; + if (base == 8) return s.match(/^-?[0-7]+$/) !== null; + if (base == 2) return s.match(/^-?[01]+$/) !== null; + return false; + } + + /** + * parseInt(s, base) + * + * This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes + * ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for + * binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values; + * see isValidInt() for details. + * + * The use of multiple prefix/suffix combinations is undefined (although for the record, we process + * prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are + * also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero + * to indicate octal (because such a number could also be decimal or hex). Any number of commas are + * allowed; we remove them all before calling the built-in parseInt(). + * + * More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides + * that the PDP-10's MACRO-10 assembly language supports (decimal, octal, and binary, respectively). + * If this support turns out to adversely affect other debuggers, then it will have to be "conditionalized". + * Similarly, we've added support for "K", "M", and "G" MACRO-10-style suffixes that add 3, 6, or 9 zeros + * to the value to be parsed, respectively. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes + * @return {number|undefined} corresponding value, or undefined if invalid + */ + static parseInt(s, base) + { + let value; + + if (s) { + if (!base) base = 10; + + let ch, chPrefix, chSuffix; + let fCommas = (s.indexOf(',') > 0); + if (fCommas) s = s.replace(/,/g, ''); + + ch = chPrefix = s.charAt(0); + if (chPrefix == '#') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '$') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) { + s = s.substr(1); + } + else { + ch = chPrefix = s.substr(0, 2); + if (chPrefix == '0b' && fCommas || chPrefix == '^B') { + base = 2; + chPrefix = ''; + } + else if (chPrefix == '0o' || chPrefix == '^O') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '^D') { + base = 10; + chPrefix = ''; + } + else if (chPrefix == '0x') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) s = s.substr(2); + } + ch = chSuffix = s.slice(-1); + if (chSuffix == 'Y' || chSuffix == 'y') { + base = 2; + chSuffix = ''; + } + else if (chSuffix == '.') { + base = 10; + chSuffix = ''; + } + else if (chSuffix == 'H' || chSuffix == 'h') { + base = 16; + chSuffix = ''; + } + else if (chSuffix == 'K') { + chSuffix = '000'; + } + else if (chSuffix == 'M') { + chSuffix = '000000'; + } + else if (chSuffix == 'G') { + chSuffix = '000000000'; + } + if (ch != chSuffix) s = s.slice(0, -1) + chSuffix; + /* + * This adds support for the MACRO-10 binary shifting (Bn) suffix, which must be stripped from the + * number before parsing, and then applied to the value after parsing. If n is omitted, 35 is assumed, + * which is a net shift of zero. If n < 35, then a left shift of (35 - n) is required; if n > 35, then + * a right shift of -(35 - n) is required. + */ + let v, shift = 0; + if (base <= 10) { + let match = s.match(/(-?[0-9]+)B([0-9]*)/); + if (match) { + s = match[1]; + shift = 35 - ((match[2] || 35) & 0xff); + } + } + if (Str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) { + /* + * With the need to support larger (eg, 36-bit) integers, truncating to 32 bits is no longer helpful. + * + * value = v|0; + */ + if (shift) { + /* + * Since binary shifting is a logical operation, and since shifting by division only works properly + * with positive numbers, we must convert a negative value to a positive value, by computing the two's + * complement. + */ + if (v < 0) v += Math.pow(2, 36); + if (shift > 0) { + v *= Math.pow(2, shift); + } else { + v = Math.trunc(v / Math.pow(2, -shift)); + } + } + value = v; + } + } + return value; + } + + /** + * toBase(n, radix, cch, sPrefix, nGrouping) + * + * Displays the given number as an unsigned integer using the specified radix and number of digits. + * + * @param {number|*} n + * @param {number} radix (ie, the base) + * @param {number} cch (the desired number of digits) + * @param {string} [sPrefix] (default is none) + * @param {number} [nGrouping] + * @return {string} + */ + static toBase(n, radix, cch, sPrefix = "", nGrouping = 0) + { + /* + * We can't rely entirely on isNaN(), because isNaN(null) returns false, and we can't rely + * entirely on typeof either, because typeof Nan returns "number". Sigh. + * + * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN, + * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those + * values displayed differently. + */ + let s = ""; + if (isNaN(n) || typeof n != "number") { + n = null; + } else { + /* + * Callers that produced an input by dividing by a power of two rather than shifting (in order + * to access more than 32 bits) may produce a fractional result, which ordinarily we would simply + * ignore, but if the integer portion is zero and the sign is negative, we should probably treat + * this value as a sign-extension. + */ + if (n < 0 && n > -1) n = -1; + /* + * Negative values should be two's complemented according to the number of digits; for example, + * 12 octal digits implies an upper limit 8^12. + */ + if (n < 0) { + n += Math.pow(radix, cch); + } + if (n >= Math.pow(radix, cch)) { + cch = Math.ceil(Math.log(n) / Math.log(radix)); + } + } + let g = nGrouping || -1; + while (cch-- > 0) { + if (!g) { + s = ',' + s; + g = nGrouping; + } + if (n == null) { + s = '?' + s; + } else { + let d = n % radix; + d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10); + s = String.fromCharCode(d) + s; + n = Math.trunc(n / radix); + } + g--; + } + return sPrefix + s; + } + + /** + * toBin(n, cch, nGrouping) + * + * Converts an integer to binary, with the specified number of digits (up to a maximum of 36). + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of binary digits (0 or undefined for default of either 8, 18, or 36) + * @param {number} [nGrouping] + * @return {string} the binary representation of n + */ + static toBin(n, cch, nGrouping) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN2) || 1; + let v = Math.abs(n); + if (v <= 0b11111111) { + cch = 8; + } else if (v <= 0b111111111111111111) { + cch = 18; + } else { + cch = 36; + } + } else if (cch > 36) cch = 36; + return Str.toBase(n, 2, cch, "", nGrouping); + } + + /** + * toBinBytes(n, cb, fPrefix) + * + * Converts an integer to binary, with the specified number of bytes (up to the default of 4). + * + * @param {number|null|undefined} n (interpreted as a 32-bit value) + * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum) + * @param {boolean} [fPrefix] + * @return {string} the binary representation of n + */ + static toBinBytes(n, cb, fPrefix) + { + let s = ""; + if (!cb || cb > 4) cb = 4; + for (let i = 0; i < cb; i++) { + if (s) s = ',' + s; + s = Str.toBin(n & 0xff, 8) + s; + n >>= 8; + } + return (fPrefix? "0b" : "") + s; + } + + /** + * toOct(n, cch, fPrefix) + * + * Converts an integer to octal, with the specified number of digits (default of 6; max of 12) + * + * You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6, 8, or 12) + * @param {boolean} [fPrefix] + * @return {string} the octal representation of n + */ + static toOct(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(8)) || 1; + let v = Math.abs(n); + if (v <= 0o777777) { + cch = 6; + } else if (v <= 0o77777777) { + cch = 8; + } else { + cch = 12; + } + } else if (cch > 12) cch = 12; + return Str.toBase(n, 8, cch, fPrefix? "0o" : ""); + } + + /** + * toDec(n, cch) + * + * Converts an integer to decimal, with the specified number of digits (default of 5; max of 11) + * + * You might be tempted to use the built-in n.toString(10) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 11) + * @return {string} the decimal representation of n + */ + static toDec(n, cch) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN10) || 1; + let v = Math.abs(n); + if (v <= 99999) { + cch = 5; + } else { + cch = 11; + } + } else if (cch > 11) cch = 11; + return Str.toBase(n, 10, cch); + } + + /** + * toHex(n, cch, fPrefix) + * + * Converts an integer to hex, with the specified number of digits (default of 4 or 8, max of 9). + * + * You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16) + * will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will + * throw an exception, whereas this function will return '?' characters. + * + * NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution, + * taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values: + * + * s = (n < 0? n + 0x100000000 : n).toString(16); + * s = "00000000".substr(0, 8 - s.length) + s; + * s = s.substr(0, cch).toUpperCase(); + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4, 8, or 9) + * @param {boolean} [fPrefix] + * @return {string} the hex representation of n + */ + static toHex(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(16)) || 1; + let v = Math.abs(n); + if (v <= 0xffff) { + cch = 4; + } else if (v <= 0xffffffff) { + cch = 8; + } else { + cch = 9; + } + } else if (cch > 9) cch = 9; + return Str.toBase(n, 16, cch, fPrefix? "0x" : ""); + } + + /** + * toHexByte(b) + * + * Alias for Str.toHex(b, 2, true) + * + * @param {number|null|undefined} b is a byte value + * @return {string} the hex representation of b + */ + static toHexByte(b) + { + return Str.toHex(b, 2, true); + } + + /** + * toHexWord(w) + * + * Alias for Str.toHex(w, 4, true) + * + * @param {number|null|undefined} w is a word (16-bit) value + * @return {string} the hex representation of w + */ + static toHexWord(w) + { + return Str.toHex(w, 4, true); + } + + /** + * toHexLong(l) + * + * Alias for Str.toHex(l, 8, true) + * + * @param {number|null|undefined} l is a dword (32-bit) value + * @return {string} the hex representation of w + */ + static toHexLong(l) + { + return Str.toHex(l, 8, true); + } + + /** + * getBaseName(sFileName, fStripExt) + * + * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead. + * + * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips the extension only + * if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux"). + * + * @param {string} sFileName + * @param {boolean} [fStripExt] + * @return {string} + */ + static getBaseName(sFileName, fStripExt) + { + let sBaseName = sFileName; + + let i = sFileName.lastIndexOf('/'); + if (i >= 0) sBaseName = sFileName.substr(i + 1); + + /* + * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters. + */ + i = sBaseName.indexOf('&'); + if (i > 0) sBaseName = sBaseName.substr(0, i); + + if (fStripExt) { + i = sBaseName.lastIndexOf("."); + if (i > 0) { + sBaseName = sBaseName.substring(0, i); + } + } + return sBaseName; + } + + /** + * getExtension(sFileName) + * + * This is a poor-man's version of Node's path.extname(), which Node-only components should use instead. + * + * Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it. + * + * @param {string} sFileName + * @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string + */ + static getExtension(sFileName) + { + let sExtension = ""; + let i = sFileName.lastIndexOf("."); + if (i >= 0) { + sExtension = sFileName.substr(i + 1).toLowerCase(); + } + return sExtension; + } + + /** + * endsWith(s, sSuffix) + * + * @param {string} s + * @param {string} sSuffix + * @return {boolean} true if s ends with sSuffix, false if not + */ + static endsWith(s, sSuffix) + { + return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1; + } + + /** + * escapeHTML(sHTML) + * + * @param {string} sHTML + * @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars() + */ + static escapeHTML(sHTML) + { + return sHTML.replace(/[&<>"']/g, function(m) + { + return Str.HTMLEscapeMap[m]; + }); + } + + /** + * replace(sSearch, sReplace, s) + * + * The JavaScript replace() function ALWAYS interprets "$" specially in replacement strings, even when + * the search string is NOT a RegExp; specifically: + * + * $$ Inserts a "$" + * $& Inserts the matched substring + * $` Inserts the portion of the string that precedes the matched substring + * $' Inserts the portion of the string that follows the matched substring + * $n Where n is a positive integer less than 100, inserts the nth parenthesized sub-match string, + * provided the first argument was a RegExp object + * + * So, if a replacement string containing dollar signs passes through a series of replace() calls, untold + * problems could result. Hence, this function, which simply uses the replacement string as-is. + * + * Similar to the JavaScript replace() method (when sSearch is a string), this replaces only ONE occurrence + * (ie, the FIRST occurrence); it might be nice to add options to replace the LAST occurrence and/or ALL + * occurrences, but we'll revisit that later. + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replace(sSearch, sReplace, s) + { + let i = s.indexOf(sSearch); + if (i >= 0) { + s = s.substr(0, i) + sReplace + s.substr(i + sSearch.length); + } + return s; + } + + /** + * replaceAll(sSearch, sReplace, s) + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replaceAll(sSearch, sReplace, s) + { + let a = {}; + a[sSearch] = sReplace; + return Str.replaceArray(a, s); + } + + /** + * replaceArray(a, s) + * + * @param {Object} a + * @param {string} s + * @return {string} + */ + static replaceArray(a, s) + { + let sMatch = ""; + for (let k in a) { + /* + * As noted in: + * + * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/ + * + * inside character classes, only backslash, caret, hyphen and the closing bracket need to be + * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first, + * and the hyphen is last, you can avoid escaping those as well. + */ + k = k.replace(/([\\[\]*{}().+?|$])/g, "\\$1"); + sMatch += (sMatch? '|' : '') + k; + } + return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) + { + return a[m]; + }); + } + + /** + * pad(s, cch, fPadLeft) + * + * NOTE: the maximum amount of padding currently supported is 40 spaces. + * + * @param {string} s is a string + * @param {number} cch is desired length + * @param {boolean} [fPadLeft] (default is padding on the right) + * @return {string} the original string (s) with spaces padding it to the specified length + */ + static pad(s, cch, fPadLeft) + { + let sPadding = " "; + return fPadLeft? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch); + } + + /** + * sprintf(format, ...args) + * + * Copied from the CCjs project (https://github.com/jeffpar/ccjs/blob/master/lib/stdio.js) and extended. + * + * Far from complete, let alone sprintf-compatible, but it's adequate for the handful of sprintf-style format + * specifiers that I use. + * + * TODO: The %c and %s specifiers support a negative width (for left-justified output), but the numeric specifiers + * (eg, %d and %x) do not; they support only positive widths and right-justified output. That's one of the more + * glaring omissions at the moment. + * + * @param {string} format + * @param {...} args + * @return {string} + */ + static sprintf(format, ...args) + { + let buffer = ""; + let aParts = format.split(/%([-+ 0#]*)([0-9]*|\*)(\.[0-9]+|)([hlL]?)([A-Za-z%])/); + + let iArg = 0, iPart; + for (iPart = 0; iPart < aParts.length - 6; iPart += 6) { + + buffer += aParts[iPart]; + + let arg = args[iArg++]; + let flags = aParts[iPart+1]; + let width = aParts[iPart+2]; + if (width == '*') { + width = arg; + arg = args[iArg++]; + } else { + width = +width || 0; + } + let precision = aParts[iPart+3]; + precision = precision? +precision.substr(1) : -1; + let prefix = aParts[iPart+4]; + let type = aParts[iPart+5]; + let ach = null, s; + + switch(type) { + case 'd': + /* + * We could use "arg |= 0", but there may be some value to supporting integers > 32 bits. + * + * Also, unlike the 'X' and 'x' hexadecimal cases, there's no need to explicitly check for a string + * arguments, because the call to trunc() automatically coerces any string value to a (decimal) number. + */ + arg = Math.trunc(arg); + /* falls through */ + + case 'f': + s = Math.trunc(arg) + ""; + if (precision > 0) { + width -= (precision + 1); + } + if (s.length < width) { + if (flags.indexOf('0') >= 0) { + if (arg < 0) width--; + s = ("0000000000" + Math.abs(arg)).slice(-width); + if (arg < 0) s = '-' + s; + } else { + s = (" " + s).slice(-width); + } + } + if (precision > 0) { + arg = Math.round((arg - Math.trunc(arg)) * Math.pow(10, precision)); + s += '.' + ("0000000000" + Math.abs(arg)).slice(-precision); + } + buffer += s; + break; + + case 'c': + arg = String.fromCharCode(arg); + /* falls through */ + + case 's': + if (typeof arg == "string") { + while (arg.length < width) { + if (flags.indexOf('-') >= 0) { + arg += ' '; + } else { + arg = ' ' + arg; + } + } + } + buffer += arg.toString(); + break; + + case 'X': + ach = Str.HexUpperCase; + /* falls through */ + + case 'x': + if (!ach) ach = Str.HexLowerCase; + s = ""; + if (typeof arg == "string") { + /* + * Since we're advised to ALWAYS pass a radix to parseInt(), we must detect explicitly + * hex values ourselves, because using a radix of 10 with any "0x..." value always returns 0. + * + * And if the value CAN be interpreted as decimal, then we MUST interpret it as decimal, because + * we have sprintf() calls in /modules/pcx86/lib/testmon.js that depend on this code to perform + * decimal to hex conversion. We're going to make our own rules here, since passing numbers in + * string form isn't part of the sprintf "spec". + */ + arg = Number.parseInt(arg, arg.match(/(^0x|[a-f])/i)? 16 : 10); + } + do { + let d = arg & 0xf; + arg >>>= 4; + if (flags.indexOf('0') >= 0 || s == "" || d || arg) { + s = ach[d] + s; + } else if (width) { + s = ' ' + s; + } + } while (--width > 0 || arg); + buffer += s; + break; + + default: + /* + * The supported ANSI C set of types: "dioxXucsfeEgGpn%" + */ + buffer += "(unrecognized printf type %" + type + ")"; + break; + } + } + + buffer += aParts[iPart]; + return buffer; + } + + /** + * stripLeadingZeros(s, fPad) + * + * @param {string} s + * @param {boolean} [fPad] + * @return {string} + */ + static stripLeadingZeros(s, fPad) + { + let cch = s.length; + s = s.replace(/^0+([0-9A-F]+)$/i, "$1"); + if (fPad) s = Str.pad(s, cch, true); + return s; + } + + /** + * trim(s) + * + * @param {string} s + * @return {string} + */ + static trim(s) + { + if (String.prototype.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ""); + } + + /** + * toASCIICode(b) + * + * @param {number} b + * @return {string} + */ + static toASCIICode(b) + { + let s; + if (b != Str.ASCII.CR && b != Str.ASCII.LF) { + s = Str.ASCIICodeMap[b]; + } + if (s) { + s = '<' + s + '>'; + } else { + s = String.fromCharCode(b); + } + return s; + } +} + +/* + * Map special characters to their HTML escape sequences. + */ +Str.HTMLEscapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' +}; + +/* + * Map "unprintable" ASCII codes to mnemonics, to more clearly see what's being printed. + */ +Str.ASCIICodeMap = { + 0x00: "NUL", + 0x01: "SOH", // (CTRL_A) Start of Heading + 0x02: "STX", // (CTRL_B) Start of Text + 0x03: "ETX", // (CTRL_C) End of Text + 0x04: "EOT", // (CTRL_D) End of Transmission + 0x05: "ENQ", // (CTRL_E) Enquiry + 0x06: "ACK", // (CTRL_F) Acknowledge + 0x07: "BEL", // (CTRL_G) Bell + 0x08: "BS", // (CTRL_H) Backspace + 0x09: "TAB", // (CTRL_I) Horizontal Tab (aka HT) + 0x0A: "LF", // (CTRL_J) Line Feed (New Line) + 0x0B: "VT", // (CTRL_K) Vertical Tab + 0x0C: "FF", // (CTRL_L) Form Feed (New Page) + 0x0D: "CR", // (CTRL_M) Carriage Return + 0x0E: "SO", // (CTRL_N) Shift Out + 0x0F: "SI", // (CTRL_O) Shift In + 0x10: "DLE", // (CTRL_P) Data Link Escape + 0x11: "XON", // (CTRL_Q) Device Control 1 (aka DC1) + 0x12: "DC2", // (CTRL_R) Device Control 2 + 0x13: "XOFF", // (CTRL_S) Device Control 3 (aka DC3) + 0x14: "DC4", // (CTRL_T) Device Control 4 + 0x15: "NAK", // (CTRL_U) Negative Acknowledge + 0x16: "SYN", // (CTRL_V) Synchronous Idle + 0x17: "ETB", // (CTRL_W) End of Transmission Block + 0x18: "CAN", // (CTRL_X) Cancel + 0x19: "EM", // (CTRL_Y) End of Medium + 0x1A: "SUB", // (CTRL_Z) Substitute + 0x1B: "ESC", // Escape + 0x1C: "FS", // File Separator + 0x1D: "GS", // Group Separator + 0x1E: "RS", // Record Separator + 0x1F: "US", // Unit Separator + 0x7F: "DEL" +}; + +/* + * Refer to: https://en.wikipedia.org/wiki/Code_page_437 + */ +Str.CP437ToUnicode = [ + '\u0000', '\u263A', '\u263B', '\u2665', '\u2666', '\u2663', '\u2660', '\u2022', + '\u25D8', '\u25CB', '\u25D9', '\u2642', '\u2640', '\u266A', '\u266B', '\u263C', + '\u25BA', '\u25C4', '\u2195', '\u203C', '\u00B6', '\u00A7', '\u25AC', '\u21A8', + '\u2191', '\u2193', '\u2192', '\u2190', '\u221F', '\u2194', '\u25B2', '\u25BC', + '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', + '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', + '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', + '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', + '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', + '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', + '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', + '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', + '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', + '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', + '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', + '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u2302', + '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', + '\u00EA', '\u00EB', '\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', + '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', + '\u00FF', '\u00D6', '\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', + '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA', + '\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', + '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561', '\u2562', '\u2556', + '\u2555', '\u2563', '\u2551', '\u2557', '\u255D', '\u255C', '\u255B', '\u2510', + '\u2514', '\u2534', '\u252C', '\u251C', '\u2500', '\u253C', '\u255E', '\u255F', + '\u255A', '\u2554', '\u2569', '\u2566', '\u2560', '\u2550', '\u256C', '\u2567', + '\u2568', '\u2564', '\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', + '\u256A', '\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580', + '\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5', '\u03C4', + '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6', '\u03B5', '\u2229', + '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320', '\u2321', '\u00F7', '\u2248', + '\u00B0', '\u2219', '\u00B7', '\u221A', '\u207F', '\u00B2', '\u25A0', '\u00A0' +]; + +/* + * TODO: Future home of a complete ASCII table. + */ +Str.ASCII = { + LF: 0x0A, + CR: 0x0D +}; + +Str.TYPES = { + NULL: 0, + BYTE: 1, + WORD: 2, + DWORD: 3, + NUMBER: 4, + STRING: 5, + BOOLEAN: 6, + OBJECT: 7, + ARRAY: 8 +}; + +Str.HexLowerCase = "0123456789abcdef"; +Str.HexUpperCase = "0123456789ABCDEF"; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @typedef {{ + * mask: number, + * shift: number + * }} + */ +var BitField; + +/** + * @typedef {Object.} + */ +var BitFields; + +class Usr { + /** + * binarySearch(a, v, fnCompare) + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point + */ + static binarySearch(a, v, fnCompare) + { + let left = 0; + let right = a.length; + let found = 0; + if (fnCompare === undefined) { + fnCompare = function(a, b) + { + return a > b ? 1 : a < b ? -1 : 0; + }; + } + while (left < right) { + let middle = (left + right) >> 1; + let compareResult; + compareResult = fnCompare(v, a[middle]); + if (compareResult > 0) { + left = middle + 1; + } else { + right = middle; + found = !compareResult; + } + } + return found ? left : ~left; + } + + /** + * binaryInsert(a, v, fnCompare) + * + * If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the + * element is inserted into the array at the appropriate index. + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v is the value to insert + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + */ + static binaryInsert(a, v, fnCompare) + { + let index = Usr.binarySearch(a, v, fnCompare); + if (index < 0) { + a.splice(-(index + 1), 0, v); + } + } + + /** + * getTimestamp() + * + * @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss") + */ + static getTimestamp() + { + return Usr.formatDate("Y-m-d H:i:s"); + } + + /** + * getMonthDays(nMonth, nYear) + * + * Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100), + * so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails + * the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap + * year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year. + * + * TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD, + * so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and + * we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what + * a real RTC actually does. + * + * @param {number} nMonth (1-12) + * @param {number} nYear (normally a 4-digit year, but it may also be mod 100) + * @return {number} the maximum (1-based) day allowed for the specified month and year + */ + static getMonthDays(nMonth, nYear) + { + let nDays = Usr.aMonthDays[nMonth - 1]; + if (nDays == 28) { + if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) { + nDays++; + } + } + return nDays; + } + + /** + * formatDate(sFormat, date) + * + * @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s") + * @param {Date} [date] (default is the current time) + * @return {string} + * + * Supported identifiers in sFormat include: + * + * a: lowercase ante meridiem and post meridiem (am or pm) + * d: day of the month, 2 digits with leading zeros (01,02,...,31) + * D: 3-letter day of the week ("Sun","Mon",...,"Sat") + * F: month ("January","February",...,"December") + * g: hour in 12-hour format, without leading zeros (1,2,...,12) + * h: hour in 24-hour format, without leading zeros (0,1,...,23) + * H: hour in 24-hour format, with leading zeros (00,01,...,23) + * i: minutes, with leading zeros (00,01,...,59) + * j: day of the month, without leading zeros (1,2,...,31) + * l: day of the week ("Sunday","Monday",...,"Saturday") + * m: month, with leading zeros (01,02,...,12) + * M: 3-letter month ("Jan","Feb",...,"Dec") + * n: month, without leading zeros (1,2,...,12) + * s: seconds, with leading zeros (00,01,...,59) + * y: 2-digit year (eg, 14) + * Y: 4-digit year (eg, 2014) + * + * For more inspiration, see: http://php.net/manual/en/function.date.php (of which we support ONLY a subset). + */ + static formatDate(sFormat, date) + { + let sDate = ""; + if (!date) date = new Date(); + let iHour = date.getHours(); + let iDay = date.getDate(); + let iMonth = date.getMonth() + 1; + for (let i = 0; i < sFormat.length; i++) { + let ch; + switch ((ch = sFormat.charAt(i))) { + case 'a': + sDate += (iHour < 12 ? "am" : "pm"); + break; + case 'd': + sDate += ('0' + iDay).slice(-2); + break; + case 'D': + sDate += Usr.asDays[date.getDay()].substr(0, 3); + break; + case 'F': + sDate += Usr.asMonths[iMonth - 1]; + break; + case 'g': + sDate += (!iHour ? 12 : (iHour > 12 ? iHour - 12 : iHour)); + break; + case 'h': + sDate += iHour; + break; + case 'H': + sDate += ('0' + iHour).slice(-2); + break; + case 'i': + sDate += ('0' + date.getMinutes()).slice(-2); + break; + case 'j': + sDate += iDay; + break; + case 'l': + sDate += Usr.asDays[date.getDay()]; + break; + case 'm': + sDate += ('0' + iMonth).slice(-2); + break; + case 'M': + sDate += Usr.asMonths[iMonth - 1].substr(0, 3); + break; + case 'n': + sDate += iMonth; + break; + case 's': + sDate += ('0' + date.getSeconds()).slice(-2); + break; + case 'y': + sDate += ("" + date.getFullYear()).slice(-2); + break; + case 'Y': + sDate += date.getFullYear(); + break; + default: + sDate += ch; + break; + } + } + return sDate; + } + + /** + * defineBitFields(bfs) + * + * Prepares a bit field definition for use with getBitField() and setBitField(); eg: + * + * let bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3}); + * + * 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). + * + * Usr.setBitField(bfs.num, n, 1); + * + * The above set bit field "bfs.num" in numeric variable "n" to the value 1. + * + * @param {Object} bfs + * @return {BitFields} + */ + static defineBitFields(bfs) + { + let bit = 0; + for (let f in bfs) { + let width = bfs[f]; + let mask = ((1 << width) - 1) << bit; + bfs[f] = {mask: mask, shift: bit}; + bit += width; + } + return bfs; + } + + /** + * initBitFields(bfs, ...) + * + * @param {BitFields} bfs + * @param {...number} var_args + * @return {number} a value containing all supplied bit fields + */ + static initBitFields(bfs, var_args) + { + let v = 0, i = 1; + for (let f in bfs) { + if (i >= arguments.length) break; + v = Usr.setBitField(bfs[f], v, arguments[i++]); + } + return v; + } + + /** + * getBitField(bf, v) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @return {number} the value of the bit field in v defined by bf + */ + static getBitField(bf, v) + { + return (v & bf.mask) >> bf.shift; + } + + /** + * setBitField(bf, v, n) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @param {number} n is a value to store in v in the bit field defined by bf + * @return {number} updated v + */ + static setBitField(bf, v, n) + { + return (v & ~bf.mask) | ((n << bf.shift) & bf.mask); + } + + /** + * indexOf(a, t, i) + * + * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it. + * + * @param {Array} a + * @param {*} t + * @param {number} [i] + * @returns {number} + */ + static indexOf(a, t, i) + { + if (Array.prototype.indexOf) { + return a.indexOf(t, i); + } + i = i || 0; + if (i < 0) i += a.length; + if (i < 0) i = 0; + for (let n = a.length; i < n; i++) { + if (i in a && a[i] === t) return i; + } + return -1; + } +} + +Usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; +Usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; +Usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +/** + * getTime() + * + * @return {number} the current time, in milliseconds + */ +Usr.getTime = Date.now || function() { return +new Date(); }; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2018 + */ + + +/* + * According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties + * and functions of JavaScript-in-the-Browser: + * + * Property Description + * --- + * Infinity A numeric value that represents positive/negative infinity + * NaN "Not-a-Number" value + * undefined Indicates that a variable has not been assigned a value + * + * Function Description + * --- + * decodeURI() Decodes a URI + * decodeURIComponent() Decodes a URI component + * encodeURI() Encodes a URI + * encodeURIComponent() Encodes a URI component + * escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead + * eval() Evaluates a string and executes it as if it was script code + * isFinite() Determines whether a value is a finite, legal number + * isNaN() Determines whether a value is an illegal number + * Number() Converts an object's value to a number + * parseFloat() Parses a string and returns a floating point number + * parseInt() Parses a string and returns an integer + * String() Converts an object's value to a string + * unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead + * + * And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions + * of the *window* object. + * + * Property Description + * --- + * closed Returns a Boolean value indicating whether a window has been closed or not + * defaultStatus Sets or returns the default text in the statusbar of a window + * document Returns the Document object for the window (See Document object) + * frames Returns an array of all the frames (including iframes) in the current window + * history Returns the History object for the window (See History object) + * innerHeight Returns the inner height of a window's content area + * innerWidth Returns the inner width of a window's content area + * length Returns the number of frames (including iframes) in a window + * location Returns the Location object for the window (See Location object) + * name Sets or returns the name of a window + * navigator Returns the Navigator object for the window (See Navigator object) + * opener Returns a reference to the window that created the window + * outerHeight Returns the outer height of a window, including toolbars/scrollbars + * outerWidth Returns the outer width of a window, including toolbars/scrollbars + * pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window + * pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window + * parent Returns the parent window of the current window + * screen Returns the Screen object for the window (See Screen object) + * screenLeft Returns the x coordinate of the window relative to the screen + * screenTop Returns the y coordinate of the window relative to the screen + * screenX Returns the x coordinate of the window relative to the screen + * screenY Returns the y coordinate of the window relative to the screen + * self Returns the current window + * status Sets or returns the text in the statusbar of a window + * top Returns the topmost browser window + * + * Method Description + * --- + * alert() Displays an alert box with a message and an OK button + * atob() Decodes a base-64 encoded string + * blur() Removes focus from the current window + * btoa() Encodes a string in base-64 + * clearInterval() Clears a timer set with setInterval() + * clearTimeout() Clears a timer set with setTimeout() + * close() Closes the current window + * confirm() Displays a dialog box with a message and an OK and a Cancel button + * createPopup() Creates a pop-up window + * focus() Sets focus to the current window + * moveBy() Moves a window relative to its current position + * moveTo() Moves a window to the specified position + * open() Opens a new browser window + * print() Prints the content of the current window + * prompt() Displays a dialog box that prompts the visitor for input + * resizeBy() Resizes the window by the specified pixels + * resizeTo() Resizes the window to the specified width and height + * scroll() This method has been replaced by the scrollTo() method. + * scrollBy() Scrolls the content by the specified number of pixels + * scrollTo() Scrolls the content to the specified coordinates + * setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds) + * setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds + * stop() Stops the window from loading + */ + +class Web { + /** + * log(s, type) + * + * For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line) + * for Component.log() to display anything. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + Component.log(s, type); + } + + /** + * notice(s, fPrintOnly, id) + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + */ + static notice(s, fPrintOnly, id) + { + Component.notice(s, fPrintOnly, id); + } + + /** + * alertUser(sMessage) + * + * NOTE: Legacy function for older modules (eg, DiskDump); see Component.alertUser(). + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Web.log(sMessage); + } + } + + /** + * getResource(sURL, type, fAsync, done, progress) + * + * Request the specified resource (sURL), and once the request is complete, notify done(). + * + * If fAsync is true, a done() callback should ALWAYS be supplied; otherwise, you'll have no + * idea when the request is complete or what the response was. done() is passed three parameters: + * + * done(sURL, resource, nErrorCode) + * + * If nErrorCode is zero, resource should contain the requested data; otherwise, an error occurred. + * + * If type is set to a string, that string can be used to control the response format; + * by default, the response format is plain text, but you can specify "arraybuffer" to request arbitrary + * binary data, in which case the returned resource will be a ArrayBuffer rather than a string. + * + * @param {string} sURL + * @param {string|Object|null} [type] (object for POST request, otherwise type of GET request) + * @param {boolean} [fAsync] is true for an asynchronous request; false otherwise (MUST be set for IE) + * @param {function(string,string,number)} [done] + * @param {function(number)} [progress] + * @return {Array|null} Array containing [resource, nErrorCode], or null if no response available (yet) + */ + static getResource(sURL, type = "text", fAsync = false, done, progress) + { + let nErrorCode = 0, resource = null, response = null; + + if (typeof resources == 'object' && (resource = resources[sURL])) { + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + } + else if (fAsync && typeof resources == 'function') { + resources(sURL, function(resource, nErrorCode) + { + if (done) done(sURL, resource, nErrorCode); + }); + return response; + } + + if (Web.getHost() == "pcjs:8088" || NODE) { + /* + * The larger resources that I've put on archive.pcjs.org are assumed to also be available locally + * whenever the hostname is "pcjs" (or NODE is true); otherwise, use "localhost" when debugging locally. + * + * NOTE: http://archive.pcjs.org is currently redirected to https://s3-us-west-2.amazonaws.com/archive.pcjs.org + */ + sURL = sURL.replace(/^(http:\/\/archive\.pcjs\.org|https:\/\/[a-z0-9-]+\.amazonaws\.com\/archive\.pcjs\.org)(\/.*)\/([^\/]*)$/, "$2/archive/$3"); + sURL = sURL.replace(/^https:\/\/jeffpar\.github\.io\/(pcjs-[a-z]+|private-[a-z]+)\/(.*)$/, "/$1/$2"); + } + else { + /* + * TODO: Perhaps it's time for our code in netlib.js to finally add support for HTTPS; for now + * though, it's just as well that the NODE environment assumes all resources are available locally. + */ + sURL = sURL.replace(/^\/(pcjs-[a-z]+|private-[a-z]+)\//, "https://jeffpar.github.io/$1/"); + } + + + let request = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP")); + let fArrayBuffer = false, fXHR2 = (typeof request.responseType === 'string'); + + let callback = function() { + if (request.readyState !== 4) { + if (progress) progress(1); + return null; + } + /* + * The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple + * times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when + * debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests + * pending, any debugging activity means most of them simply get dropped on floor, so what may actually be + * happening are mis-notifications rather than redundant notifications. + * + * request.onreadystatechange = undefined; + */ + /* + * If the request failed due to, say, a CORS policy denial; eg: + * + * Failed to load http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img: + * Redirect from 'http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img' to + * 'http://www.allbootdisks.com/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. + * Origin 'http://pcjs:8088' is therefore not allowed access. + * + * and our request type was "arraybuffer", attempting to access responseText may trigger an exception; eg: + * + * Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's + * 'responseType' is '' or 'text' (was 'arraybuffer'). + * + * We could tiptoe around these potential landmines, but the safest thing to do is wrap this code with try/catch. + */ + try { + resource = fArrayBuffer? request.response : request.responseText; + } catch(err) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + ") exception: " + err.message); + } + /* + * The normal "success" case is a non-null resource and an HTTP status code of 200, but when loading files from the + * local file system (ie, when using the "file:" protocol), we have to be a bit more flexible. + */ + if (resource != null && (request.status == 200 || !request.status && resource.length && Web.getHostProtocol() == "file:")) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + "): returned " + resource.length + " bytes"); + } + else { + nErrorCode = request.status || -1; + Web.log("xmlHTTPRequest(" + sURL + "): error code " + nErrorCode); + } + if (progress) progress(2); + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + }; + + if (fAsync) { + request.onreadystatechange = callback; + } + + if (progress) progress(0); + + if (type && typeof type == "object") { + let sPost = ""; + for (let p in type) { + if (!type.hasOwnProperty(p)) continue; + if (sPost) sPost += "&"; + sPost += p + '=' + encodeURIComponent(type[p]); + } + sPost = sPost.replace(/%20/g, '+'); + if (MAXDEBUG) Web.log("Web.getResource(POST " + sURL + "): " + sPost.length + " bytes"); + request.open("POST", sURL, fAsync); + request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + request.send(sPost); + } else { + if (MAXDEBUG) Web.log("Web.getResource(GET " + sURL + ")"); + request.open("GET", sURL, fAsync); + if (type == "arraybuffer") { + if (fXHR2) { + fArrayBuffer = true; + request.responseType = type; + } else { + request.overrideMimeType("text/plain; charset=x-user-defined"); + } + } + request.send(); + } + + if (!fAsync) { + request.readyState = 4; // this may already be set for synchronous requests, but I don't want to take any chances + response = callback(); + } + return response; + } + + /** + * parseMemoryResource(sURL, sData) + * + * This converts a variety of JSON-style data streams into an Object with the following properties: + * + * aBytes + * aSymbols + * addrLoad + * addrExec + * + * If the source data contains a 'bytes' array, it's passed through to 'aBytes'; alternatively, if + * it contains a 'words' array, the values are converted from 16-bit to 8-bit and stored in 'aBytes', + * and if it contains a 'longs' array, the values are converted from 32-bit longs into bytes and + * stored in 'aBytes'. + * + * Alternatively, if the source data contains a 'data' array, we simply pass that through to the output + * object as: + * + * aData + * + * @param {string} sURL + * @param {string} sData + * @return {Object|null} (resource) + */ + static parseMemoryResource(sURL, sData) + { + let i; + let resource = { + aBytes: null, + aSymbols: null, + addrLoad: null, + addrExec: null + }; + + if (sData.charAt(0) == "[" || sData.charAt(0) == "{") { + try { + let a, ib, data; + + if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"... + /* + * Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid, + * but more recent server configs now display a somewhat friendlier HTML error page. The downside, + * however, is that the original error has been buried, and we've received "data" that isn't actually + * tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message. + */ + throw new Error(sData); + } + + /* + * TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to + * eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in + * IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks + * (perhaps one track per chunk, using regular expressions) and then manually re-assemble it. + * + * However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")") + * is a much easier fix. The only drawback is that we must first quote any unquoted property names + * and remove any comments, because while eval() was cool with them, JSON.parse() is more particular; + * the following RegExp replacements take care of those requirements. + * + * The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and + * while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified), + * I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement + * for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect + * any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so: + * + * ["unrecognized disk path: test.img"] + */ + if (sData.indexOf("0x") < 0 && sData.indexOf("0o") < 0 && sData.substr(0, 2) != '["') { + data = JSON.parse(sData.replace(/([a-z]+):/gm, '"$1":').replace(/\/\/[^\n]*/gm, "")); + } else { + data = eval("(" + sData + ")"); + } + + resource.addrLoad = data['load']; + resource.addrExec = data['exec']; + + if (a = data['bytes']) { + resource.aBytes = a; + } + else if (a = data['words']) { + /* + * Convert all words into bytes + */ + resource.aBytes = new Array(a.length * 2); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + + } + } + else if (a = data['longs']) { + /* + * Convert all dwords (longs) into bytes + */ + resource.aBytes = new Array(a.length * 4); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + resource.aBytes[ib++] = (a[i] >> 16) & 0xff; + resource.aBytes[ib++] = (a[i] >> 24) & 0xff; + } + } + else if (a = data['data']) { + resource.aData = a; + } + else { + resource.aBytes = data; + } + + if (resource.aBytes) { + if (!resource.aBytes.length) { + Component.error("Empty resource: " + sURL); + resource = null; + } + else if (resource.aBytes.length == 1) { + Component.error(resource.aBytes[0]); + resource = null; + } + } + resource.aSymbols = data['symbols']; + + } catch (e) { + Component.error("Resource data error (" + sURL + "): " + e.message); + resource = null; + } + } + else { + /* + * Parse the data manually; we assume it's a series of hex byte-values separated by whitespace. + */ + let ab = []; + let sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, ""); + let asHexData = sHexData.split(" "); + for (i = 0; i < asHexData.length; i++) { + let n = parseInt(asHexData[i], 16); + if (isNaN(n)) { + Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")"); + break; + } + ab.push(n & 0xff); + } + if (i == asHexData.length) resource.aBytes = ab; + } + return resource; + } + + /** + * sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + * + * Send a report (eg, bug report) to the server. + * + * @param {string} sApp (eg, "PCjs") + * @param {string} sVer (eg, "1.02") + * @param {string} sURL (eg, "/devices/pc/machine/5150/mda/64kb/machine.xml") + * @param {string} sUser (ie, the user key, if any) + * @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.* + * @param {string} sReport (eg, unparsed state data) + * @param {string} [sHostName] (default is SITEURL) + */ + static sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + { + let dataPost = {}; + dataPost[ReportAPI.QUERY.APP] = sApp; + dataPost[ReportAPI.QUERY.VER] = sVer; + dataPost[ReportAPI.QUERY.URL] = sURL; + dataPost[ReportAPI.QUERY.USER] = sUser; + dataPost[ReportAPI.QUERY.TYPE] = sType; + dataPost[ReportAPI.QUERY.DATA] = sReport; + let sReportURL = (sHostName? sHostName : SITEURL) + ReportAPI.ENDPOINT; + Web.getResource(sReportURL, dataPost, true); + } + + /** + * getHost() + * + * This is like getHostName() but with the port number, if any. + * + * @return {string} + */ + static getHost() + { + return (window? window.location.host : "localhost"); + } + + /** + * getHostName() + * + * @return {string} + */ + static getHostName() + { + return (window? window.location.hostname : "localhost"); + } + + /** + * getHostOrigin() + * + * This could also be implemented with window.location.origin, but that wasn't originally available in all browsers. + * + * @return {string} + */ + static getHostOrigin() + { + return (window? window.location.protocol + "//" + window.location.host : SITEURL); + } + + /** + * getHostProtocol() + * + * @return {string} + */ + static getHostProtocol() + { + return (window? window.location.protocol : "file:"); + } + + /** + * getHostURL() + * + * @return {string|null} + */ + static getHostURL() + { + return (window? window.location.href : null); + } + + /** + * getUserAgent() + * + * @return {string} + */ + static getUserAgent() + { + return (window? window.navigator.userAgent : ""); + } + + /** + * hasLocalStorage + * + * true if localStorage support exists, is enabled, and works; false otherwise + * + * @return {boolean} + */ + static hasLocalStorage() + { + if (Web.fLocalStorage == null) { + let f = false; + if (window) { + try { + window.localStorage.setItem(Web.sLocalStorageTest, Web.sLocalStorageTest); + f = (window.localStorage.getItem(Web.sLocalStorageTest) == Web.sLocalStorageTest); + window.localStorage.removeItem(Web.sLocalStorageTest); + } catch (e) { + Web.logLocalStorageError(e); + f = false; + } + } + Web.fLocalStorage = f; + } + return Web.fLocalStorage; + } + + /** + * logLocalStorageError(e) + * + * @param {Error} e is an exception + */ + static logLocalStorageError(e) + { + Web.log(e.message, "localStorage error"); + } + + /** + * getLocalStorageItem(sKey) + * + * Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available + * + * @param {string} sKey + * @return {string|null|undefined} sValue + */ + static getLocalStorageItem(sKey) + { + let sValue; + if (window) { + try { + sValue = window.localStorage.getItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + return sValue; + } + + /** + * setLocalStorageItem(sKey, sValue) + * + * @param {string} sKey + * @param {string} sValue + * @return {boolean} true if localStorage is available, false if not + */ + static setLocalStorageItem(sKey, sValue) + { + try { + window.localStorage.setItem(sKey, sValue); + return true; + } catch (e) { + Web.logLocalStorageError(e); + } + return false; + } + + /** + * removeLocalStorageItem(sKey) + * + * @param {string} sKey + */ + static removeLocalStorageItem(sKey) + { + try { + window.localStorage.removeItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + + /** + * getLocalStorageKeys() + * + * @return {Array} + */ + static getLocalStorageKeys() + { + let a = []; + try { + for (let i = 0, c = window.localStorage.length; i < c; i++) { + a.push(window.localStorage.key(i)); + } + } catch (e) { + Web.logLocalStorageError(e); + } + return a; + } + + /** + * reloadPage() + */ + static reloadPage() + { + if (window) window.location.reload(); + } + + /** + * isUserAgent(s) + * + * Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can + * use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE"). + * + * 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating + * the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx); + * they say "public websites should rely on feature detection, rather than browser detection, in order to design + * their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent + * that tries to fool apps into thinking the browser is more like WebKit or Gecko: + * + * Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko + * + * That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained + * some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because + * that's a sledgehammer solution which restores the old user-agent string but also disables other features like + * HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or + * "Trident". + * + * UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't + * any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll + * need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture. + * + * @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values + * @return {boolean} is true if the string was found, false if not + */ + static isUserAgent(s) + { + if (window) { + let userAgent = Web.getUserAgent(); + /* + * Here's one case where we have to be careful with Component, because when isUserAgent() is called by + * the init code below, component.js hasn't been loaded yet. The simple solution for now is to remove the call. + * + * Web.log("agent: " + userAgent); + * + * And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure + * Compiler (v20130823) failing to detect the entire expression as a boolean. + */ + return s == "iOS" && !!userAgent.match(/(iPod|iPhone|iPad)/) && !!userAgent.match(/AppleWebKit/) || s == "MSIE" && !!userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0); + } + return false; + } + + /** + * isMobile(sDevice) + * + * Checks the URL for a "mobile" parameter, and failing that, checks the browser's user-agent string for the + * substring "Mobi", as per Mozilla recommendation: + * + * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent + * + * @param {string} [sDevice] (eg, "iPad" to check for iPad, or "!iPad" to specifically exclude it) + * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not + */ + static isMobile(sDevice) + { + let sMobile = Web.getURLParm("mobile"); + if (sMobile) return sMobile == "true"; + if (Web.isUserAgent("Mobi")) { + if (!sDevice) return true; + let fInvert = sDevice[0] == '!'; + if (fInvert) sDevice = sDevice.substr(1); + return Web.isUserAgent(sDevice) != fInvert; + } + return false; + } + + /** + * findProperty(obj, sProp, sSuffix) + * + * If both sProp and sSuffix are set, then any browser-specific prefixes are inserted between sProp and sSuffix, + * and if a match is found, it is returned without sProp. + * + * For example, if findProperty(document, 'on', 'fullscreenchange') discovers that 'onwebkitfullscreenchange' exists, + * it will return 'webkitfullscreenchange', in preparation for an addEventListener() call. + * + * More commonly, sSuffix is not used, so whatever property is found is returned as-is. + * + * @param {Object|null|undefined} obj + * @param {string} sProp + * @param {string} [sSuffix] + * @return {string|null} + */ + static findProperty(obj, sProp, sSuffix) + { + if (obj) { + for (let i = 0; i < Web.asBrowserPrefixes.length; i++) { + let sName = Web.asBrowserPrefixes[i]; + if (sSuffix) { + sName += sSuffix; + let sEvent = sProp + sName; + if (sEvent in obj) return sName; + } else { + if (!sName) { + sName = sProp[0]; + } else { + sName += sProp[0].toUpperCase(); + } + sName += sProp.substr(1); + if (sName in obj) return sName; + } + } + } + return null; + } + + /** + * getURLParm(sParm) + * + * First looks for sParm exactly as specified, then looks for the lower-case version. + * + * @param {string} sParm + * @return {string|undefined} + */ + static getURLParm(sParm) + { + if (!Web.parmsURL) { + Web.parmsURL = Web.parseURLParms(); + } + return Web.parmsURL[sParm] || Web.parmsURL[sParm.toLowerCase()]; + } + + /** + * parseURLParms(sParms) + * + * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?') + * @return {Object} containing properties for each parameter found + */ + static parseURLParms(sParms) + { + let aParms = {}; + if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used + if (!sParms) { + /* + * Note that window.location.href returns the entire URL, whereas window.location.search + * returns only the parameters, if any (starting with the '?', which we skip over with a substr() call). + */ + sParms = window.location.search.substr(1); + } + let match; + let pl = /\+/g; // RegExp for replacing addition symbol with a space + let search = /([^&=]+)=?([^&]*)/g; + let decode = function(s) + { + return decodeURIComponent(s.replace(pl, " ")); + }; + + while ((match = search.exec(sParms))) { + aParms[decode(match[1])] = decode(match[2]); + } + } + return aParms; + } + + /** + * downloadFile(sData, sType, fBase64, sFileName) + * + * @param {string} sData + * @param {string} sType + * @param {boolean} [fBase64] + * @param {string} [sFileName] + */ + static downloadFile(sData, sType, fBase64, sFileName) + { + let link = null, sAlert; + let sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ","; + + if (typeof sData != 'string' + && typeof Blob == 'function' && typeof URL != 'undefined' && URL && typeof URL.createObjectURL == 'function') { + let blob = new Blob([sData], { type: 'application/octet-stream' }); + sURI = URL.createObjectURL(blob); + } + else if (!Web.isUserAgent("Firefox")) { + sURI += (fBase64? sData : encodeURI(sData)); + } else { + sURI += (fBase64? sData : encodeURIComponent(sData)); + } + if (sFileName) { + link = document.createElement('a'); + if (typeof link.download != 'string') link = null; + } + if (link) { + link.href = sURI; + link.download = sFileName; + document.body.appendChild(link); // Firefox allegedly requires the link to be in the body + link.click(); + document.body.removeChild(link); + sAlert = 'Check your Downloads folder for ' + sFileName + '.'; + // if (Web.isUserAgent("Chrome")) { + // sAlert += '\n\nIn Chrome, after clicking OK, you may ALSO have to select the "Window" menu, choose "Downloads", and then locate this download and select "Keep".'; + // sAlert += '\n\nThis is part of Chrome\'s "Security By Jumping Through Extra Hoops" technology, which is much easier for Google to implement than actually checking for something malicious.'; + // sAlert += '\n\nAnd for the record, there is nothing malicious on the PCjs website.'; + // } + } else { + window.open(sURI); + sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.'; + } + return sAlert; + } + + /** + * onCountRepeat(n, fnRepeat, fnComplete, msDelay) + * + * Call fnRepeat() n times with an msDelay millisecond delay between calls, + * then call fnComplete() when n has been exhausted OR fnRepeat() returns false. + * + * @param {number} n + * @param {function()} fnRepeat + * @param {function()} fnComplete + * @param {number} [msDelay] + */ + static onCountRepeat(n, fnRepeat, fnComplete, msDelay) + { + let fnTimeout = function doCountRepeat() + { + n -= 1; + if (n >= 0) { + if (!fnRepeat()) n = 0; + } + if (n > 0) { + setTimeout(fnTimeout, msDelay || 0); + return; + } + fnComplete(); + }; + fnTimeout(); + } + + /** + * onClickRepeat(e, msDelay, msRepeat, fn) + * + * Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter, + * as long as HTML control Object e has an active "down" event and fn() returns true. + * + * @param {Object} e + * @param {number} msDelay + * @param {number} msRepeat + * @param {function(boolean)} fn is passed false on the first call, true on all repeated calls + */ + static onClickRepeat(e, msDelay, msRepeat, fn) + { + let ms = 0, timer = null, fIgnoreMouseEvents = false; + + let fnRepeat = function doClickRepeat() + { + if (fn(ms === msRepeat)) { + timer = setTimeout(fnRepeat, ms); + ms = msRepeat; + } + }; + e.onmousedown = function() + { + // Web.log("onMouseDown()"); + if (!fIgnoreMouseEvents) { + if (!timer) { + ms = msDelay; + fnRepeat(); + } + } + }; + e.ontouchstart = function() + { + // Web.log("onTouchStart()"); + if (!timer) { + ms = msDelay; + fnRepeat(); + } + }; + e.onmouseup = e.onmouseout = function() + { + // Web.log("onMouseUp()/onMouseOut()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + e.ontouchend = e.ontouchcancel = function() + { + // Web.log("onTouchEnd()/onTouchCancel()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + /* + * Devices that generate ontouch* events ALSO generate onmouse* events, + * and generally do so immediately after all the touch events are complete, + * so unless we want double the action, we need to ignore mouse events. + */ + fIgnoreMouseEvents = true; + }; + } + + /** + * onPageEvent(sName, fn) + * + * For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but + * instead use Web.onInit(), Web.onShow(), and Web.onExit(), respectively. + * + * The only components that should still use onPageEvent() are THIS component (see the bottom of this file) + * and components that need to capture other events (eg, the 'onresize' event in the Video component). + * + * This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers + * for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'], + * etc, themselves. However, that's less of a concern now, because assuming everyone else is now using + * onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one. + * + * NOTE: It's risky to refer to obscure event handlers with "dot" names, because the Closure Compiler may + * erroneously replace them (eg, window.onpageshow is a good example). + * + * @param {string} sFunc + * @param {function()} fn + */ + static onPageEvent(sFunc, fn) + { + if (window) { + let fnPrev = window[sFunc]; + if (typeof fnPrev !== 'function') { + window[sFunc] = fn; + } else { + /* + * TODO: Determine whether there's any value in receiving/sending the Event object that the + * browser provides when it generates the original event. + */ + window[sFunc] = function onWindowEvent() + { + if (fnPrev) fnPrev(); + fn(); + }; + } + } + }; + + /** + * onInit(fn) + * + * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler. + * + * @param {function()} fn + */ + static onInit(fn) + { + Web.aPageEventHandlers['init'].push(fn); + }; + + /** + * onShow(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler. + */ + static onShow(fn) + { + Web.aPageEventHandlers['show'].push(fn); + }; + + /** + * onError(sMessage) + * + * @param {string} sMessage + */ + static onError(sMessage) + { + Web.notice(sMessage + "\n\nIf it happens again, please send the URL to support@pcjs.org. Thanks."); + } + + /** + * onExit(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler. + */ + static onExit(fn) + { + Web.aPageEventHandlers['exit'].push(fn); + }; + + /** + * doPageEvent(afn) + * + * @param {Array.} afn + */ + static doPageEvent(afn) + { + if (Web.fPageEventsEnabled) { + try { + for (let i = 0; i < afn.length; i++) { + afn[i](); + } + } catch (e) { + Web.onError("An unexpected error occurred: " + e.message); + } + } + }; + + /** + * enablePageEvents(fEnable) + * + * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default) + */ + static enablePageEvents(fEnable) + { + if (!Web.fPageEventsEnabled && fEnable) { + Web.fPageEventsEnabled = true; + if (Web.fPageLoaded) Web.sendPageEvent('init'); + if (Web.fPageShowed) Web.sendPageEvent('show'); + return; + } + Web.fPageEventsEnabled = fEnable; + } + + /** + * sendPageEvent(sEvent) + * + * This allows us to manually trigger page events. + * + * @param {string} sEvent (one of 'init', 'show' or 'exit') + */ + static sendPageEvent(sEvent) + { + if (Web.aPageEventHandlers[sEvent]) { + Web.doPageEvent(Web.aPageEventHandlers[sEvent]); + } + } +} + +Web.parmsURL = null; // initialized on first call to parseURLParms() + +Web.aPageEventHandlers = { + 'init': [], // list of window 'onload' handlers + 'show': [], // list of window 'onpageshow' handlers + 'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible) +}; + +Web.asBrowserPrefixes = ['', 'moz', 'ms', 'webkit']; + +Web.fPageLoaded = false; // set once the page's first 'onload' event has occurred +Web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred +Web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents() + +/** + * fLocalStorage + * + * true if localStorage support exists, is enabled, and works; "falsey" otherwise + * + * @type {boolean|null} + */ +Web.fLocalStorage = null; + +/** + * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This isn't cutting it. + * + * @const {string} + */ +Web.sLocalStorageTest = "PCjs.localStorage"; + +Web.onPageEvent('onload', function onPageLoad() { + Web.fPageLoaded = true; + Web.doPageEvent(Web.aPageEventHandlers['init']); +}); + +Web.onPageEvent('onpageshow', function onPageShow() { + Web.fPageShowed = true; + Web.doPageEvent(Web.aPageEventHandlers['show']); +}); + +Web.onPageEvent(Web.isUserAgent("iOS")? 'onpagehide' : (Web.isUserAgent("Opera")? 'onunload' : 'onbeforeunload'), function onPageUnload() { + Web.doPageEvent(Web.aPageEventHandlers['exit']); +}); + +/* + * If this is DEBUG (eg, un-COMPILED) code, then allow the user to override DEBUG with a "debug=false" embedded in + * the URL; note that the Closure Compiler won't let us alter the DEBUG variable, because it's defined as a @define, which + * implies @const as well, so we must resort to modifying it indirectly, using the global window object. + * + * TODO: Consider yet another embedXXX() parameter that would also allow DEBUG to be turned off on a page-by-page basis; + * it's low priority, because it would only affect machines that explicitly request un-COMPILED code, and there are very + * few such machines (eg, /_posts/2015-01-17-pcjs-uncompiled.md). + * + * Deal with Web.getURLParm("backtrack") in /modules/pcx86/lib/defines.js at the same time. + */ +if (DEBUG && window) { + let sDebug = Web.getURLParm("debug"); + if (sDebug == "false") { + window['DEBUG'] = false; + } +} + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2018 + */ + +/* + * All PCjs components now use JSDoc types, primarily so that Google's Closure Compiler will compile + * everything with zero warnings when ADVANCED_OPTIMIZATIONS are enabled. For more information about + * the JSDoc types supported by the Closure Compiler: + * + * https://developers.google.com/closure/compiler/docs/js-for-compiler#types + * + * I also attempted to validate this code with JSLint, but it complained too much; eg, it didn't like + * "while (true)", a tried and "true" programming convention for decades, and it wanted me to replace + * all "++" and "--" operators with "+= 1" and "-= 1", use "(s || '')" instead of "(s? s : '')", etc. + * + * I prefer sticking with traditional C-style idioms, in part because they are more portable. That + * does NOT mean I'm trying to write "portable JavaScript," but some of this code was ported from C code + * I'd written long ago, so portability is good, and I'm not going to throw that away if there's no need. + * + * UPDATE: I've since switched from JSLint to JSHint, which seems to have more reasonable defaults. + * And for new code, I have adopted some popular JavaScript idioms, like "(s || '')", although the need + * for those kinds of expressions will be reduced as I also start adopting some ES6 features, like + * default parameters. + */ + + + +/** + * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, + * it deters us from defining named properties on our components; eg: + * + * this['exports'] = {...} + * + * results in an error: + * + * Cannot do '[]' access on a struct + * + * So, in order to define 'exports', we must override the @struct assumption by annotating + * the class as @unrestricted (or @dict). Note that this must be done both here and in the + * subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference* + * the named property either. + * + * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to + * define every single property the class uses in its constructor, which results in a fair + * bit of redundant initialization, since many properties aren't (and don't need to be) fully + * initialized until the appropriate init(), reset(), restore(), etc. function is called. + * + * The upside, however, may be that since the structure of the class is completely defined by + * the constructor, JavaScript engines may be able to optimize and run more efficiently. + * + * @unrestricted + */ +class Component { + /** + * Component(type, parms, bitsMessage) + * + * A Component object requires: + * + * type: a user-defined type name (eg, "CPU") + * + * and accepts any or all of the following (parms) properties: + * + * id: component ID (default is "") + * name: component name (default is ""; if blank, toString() will use the type name only) + * comment: component comment string (default is undefined) + * + * Component subclasses will usually have additional (parms) properties. + * + * @param {string} type + * @param {Object} [parms] + * @param {number} [bitsMessage] selects message(s) that the component wants to enable (default is 0) + */ + constructor(type, parms, bitsMessage) + { + this.type = type; + + if (!parms) parms = {'id': "", 'name': ""}; + + this.id = parms['id'] || ""; + this.name = parms['name']; + this.comment = parms['comment']; + this.parms = parms; + + /* + * The following Component properties need to be accessible by other machines and/or command scripts; + * well, OK, or we could have exported some new functions to walk the contents of these properties, as we + * did with findMachineComponent(), but this works just as well. + * + * Also, while the double-assignment looks silly (ie, using both dot and bracket property notation), it + * resolves a complaint from the Closure Compiler, because if we use ONLY bracket notation here, then the + * Compiler wants us to change all the other references to bracket notation as well. + */ + this.exports = this['exports'] = {}; + this.bindings = this['bindings'] = {}; + + let i = this.id.indexOf('.'); + if (i < 0) { + this.idMachine = "PCjs"; + this.idComponent = this.id; + } else { + this.idMachine = this.id.substr(0, i); + this.idComponent = this.id.substr(i + 1); + } + + /* + * Gather all the various component flags (booleans) into a single "flags" object, and encourage + * subclasses to do the same, to reduce the property clutter we have to wade through while debugging. + */ + this.flags = { + ready: false, + busy: false, + busyCancel: false, + initDone: false, + powered: false, + unloading: false, + error: false + }; + + this.fnReady = null; + this.clearError(); + this.bitsMessage = bitsMessage || 0; + + this.cmp = null; + this.bus = null; + this.cpu = null; + this.dbg = null; + + /* + * TODO: Consider adding another parameter to the Component() constructor that allows components to tell + * us if they support single or multiple instances per machine. For example, there can be multiple SerialPort + * components per machine, but only one CPU component (some machines also support an FPU, but that component + * is considered separate from the CPU). + * + * It's not critical, but it would help catch machine configuration errors; for example, a machine that mistakenly + * includes two CPU components may, aside from wasting memory, end up with odd side-effects, like unresponsive + * CPU controls. + */ + Component.add(this); + } + + /** + * Component.add(component) + * + * @param {Component} component + */ + static add(component) + { + /* + * This just generates a lot of useless noise, handy in the early days, not so much these days.... + * + * if (DEBUG) Component.log("Component.add(" + component.type + "," + component.id + ")"); + */ + Component.components.push(component); + } + + /** + * Component.addMachine(idMachine) + * + * @param {string} idMachine + */ + static addMachine(idMachine) + { + Component.machines[idMachine] = {}; + } + + /** + * Component.getMachines() + * + * @return {Array.} + */ + static getMachines() + { + return Object.keys(Component.machines); + } + + /** + * Component.addMachineResource(idMachine, sName, data) + * + * @param {string} idMachine + * @param {string|null} sName (name of the resource) + * @param {*} data + */ + static addMachineResource(idMachine, sName, data) + { + /* + * I used to assert(Component.machines[idMachine]), but when we're running as a Node app, embed.js is not used, + * so addMachine() is never called, so resources do not need to be recorded. + */ + if (Component.machines[idMachine] && sName) { + Component.machines[idMachine][sName] = data; + } + } + + /** + * Component.getMachineResources(idMachine) + * + * @param {string} idMachine + * @return {Object|undefined} + */ + static getMachineResources(idMachine) + { + return Component.machines[idMachine]; + } + + /** + * Component.getTime() + * + * @return {number} the current time, in milliseconds + */ + static getTime() + { + return Date.now() || +new Date(); + } + + /** + * Component.log(s, type) + * + * For diagnostic output only. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + if (!COMPILED) { + if (s) { + let sElapsed = "", sMsg = (type? (type + ": ") : "") + s; + if (typeof Usr != "undefined") { + if (Component.msStart === undefined) { + Component.msStart = Component.getTime(); + } + sElapsed = (Component.getTime() - Component.msStart) + "ms: "; + } + sMsg = sMsg.replace(/\r/g, '\\r').replace(/\n/g, ' '); + if (window && window.console) console.log(sElapsed + sMsg); + } + } + } + + /** + * Component.assert(f, s) + * + * Verifies conditions that must be true (for DEBUG builds only). + * + * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds. + * TODO: Add a task to the build process that "asserts" there are no instances of "assertion failure" in RELEASE builds. + * + * @param {boolean} f is the expression we are asserting to be true + * @param {string} [s] is description of the assertion on failure + */ + static assert(f, s) + { + if (DEBUG) { + if (!f) { + if (!s) s = "assertion failure"; + Component.log(s); + throw new Error(s); + } + } + } + + /** + * Component.print(s) + * + * Components that inherit from this class should use this.print(), rather than Component.print(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @this {Component} + * @param {string} s + */ + static print(s) + { + if (!COMPILED) { + let i = s.lastIndexOf('\n'); + if (i >= 0) { + Component.println(s.substr(0, i)); + s = s.substr(i + 1); + } + Component.printBuffer += s; + } + } + + /** + * Component.println(s, type, id) + * + * Components that inherit from this class should use this.println(), rather than Component.println(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + * @param {string} [id] is the caller's ID, if any + */ + static println(s, type, id) + { + if (!COMPILED) { + s = Component.printBuffer + (s || ""); + Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type); + Component.printBuffer = ""; + } + } + + /** + * Component.notice(s, fPrintOnly, id) + * + * notice() is like println() but implies a need for user notification, so we alert() as well. + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + * @return {boolean} + */ + static notice(s, fPrintOnly, id) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.NOTICE, id); + } + if (!fPrintOnly) Component.alertUser((id? (id + ": ") : "") + s); + return true; + } + + /** + * Component.warning(s) + * + * @param {string} s describes the warning + */ + static warning(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.WARNING); + } + Component.alertUser(s); + } + + /** + * Component.error(s) + * + * @param {string} s describes the error; an alert() is displayed as well + */ + static error(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.ERROR); + } + Component.alertUser(s); + } + + /** + * Component.alertUser(sMessage) + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Component.log(sMessage); + } + } + + /** + * Component.confirmUser(sPrompt) + * + * @param {string} sPrompt + * @returns {boolean} true if the user clicked OK, false if Cancel/Close + */ + static confirmUser(sPrompt) + { + let fResponse = false; + if (window) { + fResponse = window.confirm(sPrompt); + } + return fResponse; + } + + /** + * Component.promptUser() + * + * @param {string} sPrompt + * @param {string} [sDefault] + * @returns {string|null} + */ + static promptUser(sPrompt, sDefault) + { + let sResponse = null; + if (window) { + sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault); + } + return sResponse; + } + + /** + * Component.appendControl(control, sText) + * + * @param {Object} control + * @param {string} sText + */ + static appendControl(control, sText) + { + control.value += sText; + /* + * Prevent the + + +
+
+ +
+
+ + +
+
+
+ +
+
+ +

+
+ +
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + null + + + + + + ,autoStart: + + + + + + + + + + + + + + + 600 + + + + + keyboard + ,model: + + + + + + + + + + + + + + + false + + + + + serial + ,demo: + + + + + + + + + + + + + + disk + + + + + + + + + + + + + + + 0 + + + + + + + + + + + rom + ,size:,image:'' + + + + + + + + + + + + + + + 0 + + + + + ram + ,size: + + + + + + + + + + + + + + + 600 + + + + + + + 256 + + + + + + + 224 + + + + + + + black + + + + + + 32 + + + + + + 32 + + + + + + + 0 + + + + + + + 0 + + + + + + + + + + + + + + null + + + + + video + ,model:,screenWidth:,screenHeight:,charCols:,charRows:,charWidth:,charHeight:,charSet:'',screenColor:'',smoothing: + + + + + + + + + + + + + + debugger + + + + + + + + + + + + + + panel + + + + + + + + + + + + + [ + {} + , + ] + + + + computer + ,modules: + + + + + type:'',refID:'',start:,end: + + + + + + + <></> + + +
<machine></machine>
+
+ + + + diff --git a/versions/c1pjs/1.71.5/document.css b/versions/c1pjs/1.71.5/document.css new file mode 100644 index 0000000000..4281cab24f --- /dev/null +++ b/versions/c1pjs/1.71.5/document.css @@ -0,0 +1,162 @@ +@CHARSET "UTF-8"; + +.page { + margin: 2% 2%; + padding: 2% 2%; + min-width: 30em; + overflow: auto; + font-size: large; + font-family: Helvetica, Arial, sans-serif; + background: #303030; + color: #ccc; + +} +.page-header { +} +.page-header-title { + text-align: center; + +} +.page a { + color: #7fc07f; + text-decoration: none; +} +a.footlink, a.paralink { + text-decoration: none; +} +a.footlink:link, a.paralink:link { + color: blue; +} +a.footlink:visited, a.paralink:visited { + color: blue; +} +.galleryitem { + float: left; + width: 200px; +} +.item { + float: left; + width: 2em; + text-indent: 1em; +} +.list { + margin-left: 3em; + text-indent: 0; + text-align: justify; +} +ul { + list-style: none; +} +div.pnumber { + float: left; + width: 2em; + text-indent: 1em; +} +div.pitem { + margin-left: 10em; +} +p.indent, .justified p { + text-indent: 2em; + text-align: justify; + line-height: 1.5em; +} +p.noindent { + text-indent: 0; + text-align: justify; +} +p.center, .center { + text-align: center; +} +li.para { + margin-top: 1em; + margin-bottom: 1em; +} +.left { + text-align: left; +} +.right { + text-align: right; +} +blockquote.tag { + font-size: small; + font-family: Monaco, Fixed, monospace; + margin-top: 0; + margin-bottom: 0; +} +.blockquote { + padding-left: 1em; + text-indent: 0; + text-align: justify; +} +.italics { + font-style: italic; +} +.medium { + font-size: medium; +} +.small { + font-size: x-small; +} +.smallcaps { + font-variant: small-caps; +} +.strike { + text-decoration: line-through; +} +.summation, .bracelist { + display: inline-block; + position: relative; + vertical-align: middle; + text-align: center; + margin-bottom: 0.5ex; + text-indent: 0; +} +.bracelist-symbol { + font-size: 3em; + vertical-align: -40%; +} +.summation .summation-lower, .summation .summation-upper, .bracelist-item { + display: block; + font-size: 75%; + text-align: center; +} +.summation .summation-upper { + margin-bottom: 0; + margin-left: 0.8ex; + font-style: italic; +} +.summation .summation-lower{ + margin-bottom: -0.6ex; + font-style: italic; +} +.summation .summation-symbol { + font-size: 2em; +} +p sup { + vertical-align: baseline; + position: relative; + bottom: .5em; + font-size: small; +} +p sub { + vertical-align: baseline; + position: relative; + bottom: -.5em; + font-size: small; +} +.footnote { + font-size: medium; + text-indent: 1em; + text-align: justify; + margin-top: .5em; +} +.image-right { + float: right; + margin-left: 1em; + margin-top: 1em; + margin-bottom: 1em; +} +.image-caption { + font-size: small; + text-align: center; +} diff --git a/versions/c1pjs/1.71.5/document.xsl b/versions/c1pjs/1.71.5/document.xsl new file mode 100644 index 0000000000..84e8c55971 --- /dev/null +++ b/versions/c1pjs/1.71.5/document.xsl @@ -0,0 +1,452 @@ + + + + + +]> + + + + + + + + + +

+
+ + + + + + + +

+
+ +

+
+
+
+ + + + + + +
+
+ + +
+ +   + + +
+
+ +
+
+ + + + + + + + + + + + + + + + +

+
+ + +

+
+ + +

+
+ + +
+
+ + +
+
+ + + + + + + + + + + + + + +
+
+ + +
+
+ + +
  • +
    + + +
    image
    +
    + + +
    +
    + + + + +
    {.}
    +
    + +
    {.}
    +
    +
    +
    + + + + + + + + + + < + > + + + + × + + ÷ + σ + + + + + + + + + + + + { + + + + + + + + + + [] + + + + +
    + +
    +
    + + + , and + + + + + MDY + + + + + + + + + + + + + + + + + + + + January + February + March + April + May + June + July + August + September + October + November + December + + + , + + + + + +

    + +
    +
    + + +
    + {.}
    +
    +
    +
    + + + +

    Timeline

    +
    + +

    +
    +
    + +
    +
    + + + + + + + + + +

    +
    + +
    +
    +
    + + + +

    People

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + +

    +
      + +
    +
    + + + + + + + + + + +
  • + +
  • +
    + + + +

    +
    +

    + +

    +
    +
    + + + + false + + + + + + [Original] + + + + + + + + + + [] + + +
    by
    + + +
    + [Source: + + + + + + + ] +
    +
    +
    + + + +

    Resources

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + + +

    +
    +
      + +
    +
    + + +
  • +
    + + + +

    +
    +
    + +
    +
    + + + +

    +
    + +
    + + + +

    +
    +
      + +
    +
    + + + + + +
      + +
    +
    + + + + +
  • +
    + +
  • +
    + +
  • +
    +
    +
    + + +
  • +
    + + + + + + + + + + +
    + < ="" + + ></> + ></> + /> + +
    +
    + +
    diff --git a/versions/c1pjs/1.71.5/machine.xsl b/versions/c1pjs/1.71.5/machine.xsl new file mode 100644 index 0000000000..91f9cc96d3 --- /dev/null +++ b/versions/c1pjs/1.71.5/machine.xsl @@ -0,0 +1,61 @@ + + + + +]> + + + + + + + + + + + + + + + + + js + + + + + + PCjs + + + + +
    + +
    +
    + + + + + , + +
    +
    + + +
    + + + + + + + + + + +
    + +
    diff --git a/versions/c1pjs/1.71.5/manifest.xsl b/versions/c1pjs/1.71.5/manifest.xsl new file mode 100644 index 0000000000..61b4c564f1 --- /dev/null +++ b/versions/c1pjs/1.71.5/manifest.xsl @@ -0,0 +1,247 @@ + + + + +]> + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Document Manifest

    +
    +
      + + + + None + + + + + + + + + + + + + + + + +
    +
    +
    +

    + +
    +
    +
    + + +
    + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Software Manifest

    +
    +
      + + + + None + + + + + Unknown + + + + + None + + + + + None + + + + + + + + + + + + + UpdatedReleased + + Unknown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + + + + +

    No default machine specified for '' in manifest.xml

    +
    + +
    +
    +
    + + + + -dbg + + + + + + +
    + + + + + Unknown + +
  • +
      + + + + + + + + +
    • + + + + + + + + + + + + + + + + + + + + + + + + + +
        + +
      • + + + + + + +
      • +
        +
      +
      +
    • +
      + + + + + + + + +
    +
  • +
    +
    + +
    diff --git a/versions/c1pjs/1.71.5/outline.xsl b/versions/c1pjs/1.71.5/outline.xsl new file mode 100644 index 0000000000..7183e972f8 --- /dev/null +++ b/versions/c1pjs/1.71.5/outline.xsl @@ -0,0 +1,47 @@ + + + + +]> + + + + + + + + + + + + + + + + + + PCjs<xsl:text> | </xsl:text><xsl:value-of select="title"/> + + + + + +
    +
    + +
    +
    + + + + -dbg + + + + + + +
    + +
    diff --git a/versions/pc8080/1.71.5/common.css b/versions/pc8080/1.71.5/common.css new file mode 100644 index 0000000000..4965caa9a6 --- /dev/null +++ b/versions/pc8080/1.71.5/common.css @@ -0,0 +1,279 @@ +@CHARSET "UTF-8"; +/** + @author Jeff Parsons (@jeffpar) + @website https://www.pcjs.org/ + @created 2013-05-05 + @modified 2018-03-13 + @license http://www.gnu.org/licenses/gpl.html + */ +body { + margin: 0; + background: #ffffff; +} +h1, h2 { + margin-top: 0; + +} +h1, h2, h3, h4 { + word-wrap: break-word; +} + +h4 a { + +} +p { + line-height: 1.5em; +} +img { + max-width: 100%; +} +a img { + vertical-align: bottom; +} +pre, code { + + background-color: #cccccc; + font-family: Monaco, Consolas, "Lucida Console", monospace; + font-size: 12px; +} +pre { + margin: 1em 2em; + padding: 1em; + border-radius: 5px; + overflow: auto; +} +code { + padding: 1px; +} +pre a, code a { + +} +.common { + width: 100%; + margin: 0 auto; + +} +.common a { + + + text-decoration: none; +} +.common hr { + border-color: #808080; +} +.common a:hover { + text-decoration: underline; +} +.common, .machine { + font-family: "Titillium Web", "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; + font-size: 16px; +} +.machine { + + overflow: hidden; +} +.c1pjs { + overflow: visible; +} +.machine-placeholder { + text-align: center; + font-weight: bold; +} +.machine-floating { + position: relative; + z-index: auto; + background-color: transparent; + padding-right: 0; +} +.machine-sticky { + position: fixed !important; + z-index: 1 !important; + background-color: #ffffff !important; + padding-right: 16px !important; + top: 0 !important; +} +.common-top { + + padding: 1px 1em 1px 1em; +} +.common-top h2 { + margin: 8px 0px 8px 0px; +} +.common-top-left { + float: left; +} +.common-top-right { + float: right; + line-height: 56px; +} +.common-top-right p { + float: right; + margin: 0; + padding: 1em; +} +.common-top-right ul { + list-style-type: none; + margin: 0; + overflow: hidden; +} +.common-top-right ul li { + display: block; + float: left; + margin-left: 1em; +} +.common-top-left ul li a { + border-right: 1px solid #6f6f6f; + padding: 2px 6px 2px 6px; +} +.common-top-left ul li:last-child a { + border-right: none; +} +.common-middle { + clear: both; + padding: 1px 1em 1px 1em; + +} +.common-sidebar { + float: left; + font-size: small; + width: 140px; + padding-bottom: 20px; + overflow: hidden; + white-space: nowrap; + word-wrap: break-word; +} +.common-list { + list-style-type: none; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; +} +.common-list li { + + +} +.common-list-data { + list-style-type: none; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; +} +.common-list-data li { + line-height: 1.5em; +} +.common-list-data-items, .common-list-data-subitems { + font-size: x-small; + list-style-type: none; + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} +.common-list-data-items li, .common-list-data-subitems li { + padding-bottom: 0; +} +.common-main { + margin-left: 150px; + +} +.common-main blockquote { + text-align: justify; +} +.common-image-gallery { + margin: 0 auto; + text-align: center; +} +.common-image-gallery:after { + content: ''; + display: block; +} +.common-image-frame { + display: inline-block; + margin: 8px; + text-align: center; +} +.common-image-link { + padding: 5px; + border: 1px solid black; + border-radius: 5px; + +} +.common-image-label { + font-size: x-small; +} +.common-bottom { + clear: both; + padding: 1em 1em 1px 1em; +} +.common-bottom:after { + content: ''; + display: block; + clear: both; +} +.common-reference { + float: left; + font-size: x-small; +} +.common-reference a { + text-decoration: none; +} +.common-copyright { + float: right; + font-size: x-small; +} +.common-copyright a { + text-decoration: none; +} +.md-list { +} +.md-list li { + line-height: 1.5em; + margin-bottom: 1em; +} +.md-list li p { + padding-left: 2em; +} +.md-list-compact { +} +.md-list-compact li { + margin-bottom: 0; +} +.md-list-none { + list-style-type: none; + padding-left: 2em; +} +.md-list-none li { + margin-bottom: 0; +} +@media screen and (max-width: 900px) { + + .common-sidebar { + width: 100%; + white-space: normal; + } + .common-list { + padding-left: 0; + } + .common-list-data { + padding-left: 0; + } + .common-sidebar h4, .common-list li, .common-list-data li, .common-list-data-items li { + width: 130px; + float: left; + overflow: hidden; + vertical-align: top; + padding-right: 1em; + margin-top: 0; + } + .common-list-data-subitems { + display: none; + } + .common-main { + clear: both; + margin-left: 0; + padding-left: 0; + padding-right: 0; + } + .md-list-none { + padding-left: 1em; + } +} diff --git a/versions/pc8080/1.71.5/common.xsl b/versions/pc8080/1.71.5/common.xsl new file mode 100644 index 0000000000..9bec6e723f --- /dev/null +++ b/versions/pc8080/1.71.5/common.xsl @@ -0,0 +1,58 @@ + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    +
    +
    + + +
    +

    + +
    +
    + +
    diff --git a/versions/pc8080/1.71.5/components.css b/versions/pc8080/1.71.5/components.css new file mode 100644 index 0000000000..8ddbec7eb0 --- /dev/null +++ b/versions/pc8080/1.71.5/components.css @@ -0,0 +1,263 @@ +@CHARSET "UTF-8"; + + +*:not(input,textarea) { + -webkit-user-select: none; +} +.pcjs-embed { +} +.pcjs-embed:after { + clear:both; +} +.pcjs-machine { +} +.pcjs-name, .pcjs-menu { + clear: both; + font-weight: bold; + padding-bottom: 4px; +} +.pcjs-menu { + float: left; +} +.pcjs-canvas { + width: 100%; + height: auto; +} +.pcjs-container { + color: #000000; + position: relative; +} +.pcjs-label { + font-size: small; + line-height: 20px; + vertical-align: middle; + float: left; + font-family: Monaco, "Lucida Console", monospace; +} +.pcjs-controls textarea { + font-family: Monaco, "Lucida Console", monospace; + font-size: x-small; +} +.pcjs-fieldset { + border: none; + margin: 0; + padding: 0; +} +.pcjs-flag { + font-family: Monaco, "Lucida Console", monospace; + font-size: small; + text-align: center; + line-height: 20px; + vertical-align: middle; +} +.pcjs-progress { + height: 20px; + width: 200px; + margin-top: 8px; + border: 1px solid black; + position: relative; +} +.pcjs-progress-bar { + height: 20px; + width: 0; + background-color: gold; + position: absolute; + top: 0; +} +.pcjs-progress-text { + height: 20px; + width: 200px; + font-size: small; + line-height: 20px; + text-align: center; + position: absolute; + top: 0; + z-index: 1; +} +.pcjs-register { + font-family: Monaco, "Lucida Console", monospace; + font-size: small; + text-align: center; + line-height: 20px; + vertical-align: middle; + border: 1px solid black; +} +.pcjs-switches { + float: left; +} +.pcjs-bitBucket { + float: left; + width: 19px; + height: 38px; +} +.pcjs-bitCell { + float: left; + width: 19px; + height: 19px; + margin-right: -1px; + margin-bottom: -1px; + border: 1px solid black; + text-align: center; + line-height: 19px; +} +.pcjs-bitCellLeft { + border-left: 1px solid black; +} +.pcjs-bitLabel { + font-size: xx-small; + text-align: center; +} +.pcjs-description, .pcjs-status { + font-size: x-small; + line-height: 2.0em; +} +.pcjs-key { + border: 1px solid black; + font-size: x-small; + text-align: center; + position: absolute; + height: 34px; + line-height: 34px; + background-color: #ffffff; + overflow: hidden; +} +.pcjs-panel-group { + color: #ffffff; + background-color: #404040; +} +.pcjs-triplet { + padding: 1px; +} +.pcjs-ledlbl { + text-align: center; + font-size: 40%; + background-color: #000000; +} +.pcjs-ledlbl0 { + text-align: right; + font-size: 50%; + background-color: #8d4076; +} +.pcjs-ledlbl1 { + text-align: right; + font-size: 50%; + background-color: #d83662; +} +.pcjs-ledpad { + text-align: center; + font-size: x-small; + line-height: 32px; + background-color: #000000; + border-bottom-left-radius: 20%; + border-bottom-right-radius: 20%; +} +.pcjs-led { + float: left; + width: 8px; + height: 8px; + margin: 4px; + border: 1px solid black; + text-align: center; + vertical-align: middle; + background-color: #000000; +} +.pcjs-rled { + float: left; + width: 8px; + height: 8px; + margin: 4px; + border: 1px solid black; + border-radius: 50%; + text-align: center; + vertical-align: middle; + background-color: #ff0000; + max-width: 50%; + max-height: 50%; +} +.pcjs-swlbl { + text-align: center; + font-size: 40%; + line-height: 16px; + background-color: #000000; + border-top-left-radius: 20%; + border-top-right-radius: 20%; +} +.pcjs-swpad { + height: 32px; + background-color: #000000; +} +.pcjs-switch { + height: 10px; + width: 28px; + margin-top: 0; + max-width: 90%; + background-color: #00ff00; +} +.pcjs-screen { + clear: both; + height: auto; + position: relative; + line-height: 0; +} +.pcjs-screen textarea { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + border: 0; + padding: 0; + resize: none; + color: white; + font-family: Monaco, "Lucida Console", monospace; + font-size: small; + background: black; + opacity: 0; + line-height: 0; +} +.pcjs-reference { + float: left; + font-size: x-small; +} +.pcjs-reference a { + text-decoration: none; +} +.pcjs-copyright { + float: right; + font-size: x-small; +} +.pcjs-copyright a { + text-decoration: none; +} +@media screen and (max-width: 800px) { + .pcjs-textarea { + width: 96% !important; + } + .pcjs-registers { + width: 100% !important; + max-width: none !important; + padding-left: 0 !important; + padding-right: 0 !important; + } + .pdp11-device { + width: 98% !important; + max-width: none !important; + margin-left: 8px !important; + padding-left: 0 !important; + padding-right: 0 !important; + } +} +@media screen and (min-width: 1024px) { + .machine-left { + max-width: 48%; + float: left; + } + .machine-right { + max-width: 48%; + float: left; + margin-left: 30px; + } + .machine-right + * { + clear: both; + } +} diff --git a/versions/pc8080/1.71.5/components.xsl b/versions/pc8080/1.71.5/components.xsl new file mode 100644 index 0000000000..e66267378e --- /dev/null +++ b/versions/pc8080/1.71.5/components.xsl @@ -0,0 +1,1475 @@ + + + +]> + + + + + + + + pc + pcjs + pc8080 + PC8080 + 1.71.5 + https://www.pcjs.org + #FAEBD7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + float: + + + + + + + js + + + +
    + + + + + + + + + + js + + + "" + + + + + +
    +
    + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + .machine + . + . + + + + + + + + + + + + + + + + + + + + ,comment:'' + + + + + + border:1px solid black;border-radius:15px; + border:; + + + + + + left:; + + + + + + top:; + + + + + + + + width: + width:;max-width:; + width:auto;max-width:; + + + + + + + + height:; + + + + + + padding:; + + padding-top:; + padding-right:; + padding-bottom:; + padding-left:; + + + + + + float:left; + float:right; + margin:0 auto;clear:both; + position:; + position:relative; + + + + + overflow:auto; + + background-color:; + background-color:; + + + + + - -component + +
    + + + + + + +
    + + + + + + + + --object -screen + + +
    + + + +
    +
    + +
    + +
    +
    + + + + +
    + + +
    [XML]
    + +
    + + +
    +
    +
    +
    + + + + + text-align:center; + + + +

    +
    + + +
    +
    + + + + + + +
    +
    + + + + type:'' + + + binding:'' + + + value:'' + + + + border:1px solid black; + border:; + + + + + + width:; + + + + + + height:; + + + + + + left:; + + + + + + top:; + + + + + + padding:; + + padding-top:; + padding-right:; + padding-bottom:; + padding-left:; + + + + + + float:left; + float:right; + clear:both;margin:0 auto; + clear:both; + position:; + position:relative; + text-align:; + float:left; + + + + + + + + + + + + + + + + + + + +
    + + + font-size:; + + + + + -label + + + + width:; + width:; + + + + + + + + text-align:right; + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + ; + + + + + + + + + + + + + + + : + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + ; + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8088 + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + 1 + + + + + + + null + + + + + + 0 + + + + + + + -1 + + + + + + + -1 + + + + + + + -1 + + + + + + ,model:'',stepping:'',fpu:,cycles:,multiplier:,autoStart:,addrReset:,csStart:,csInterval:,csStop: + + + + + + + + + + + + + + + 8087 + + + + + + + + + + + + ,model:'',stepping:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + true + + + + + + + false + + + + + + {} + + + + + + + + + + + + + + + + + + + chipset + ,model:'',scaleTimers:,sw1:'',sw2:'',sound:,floppies:,monitor:'',dateRTC:'' + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + device + ,type:'',baudReceive:,baudTransmit:,autoMount:'' + + + + + + + + + + + + + + + + + + + + keyboard + ,model:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + parallel + ,adapter:,binding:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + + 0 + + + + + + + 0 + + + + + + + false + + + + + serial + ,adapter:,baudReceive:,baudTransmit:,binding:'',tabSize:,charBOL:,upperCase: + + + + + + + + + + + + + + + + + + + + + + + + + + testctl + ,binding:'',tests:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + 0.5 + + + + + + + + + + + mouse + ,adapter:,binding:'',type:'',scaleMouse:,serial:'' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fdc + ,autoMount:'',sortBy:'' + + + + + + + + + + + + + + + + + + + + + XT + + + + + hdc + ,drives:'',type:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + null + + + + + + + + + + + + + + + + + rom + ,addr:,size:,alias:,file:'',notify:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + null + + + + + + null + + + + + + true + + + + + ram + ,addr:,size:,file:'',load:,exec:,test: + + + + + + + + + + + + + + + + + + + + + null + + + + + + + 256 + + + + + + + 224 + + + + + + + black + + + + + + 0 + + + + + + 0 + + + + + + false + + + + + + 1bpp + + + + + + 0 + + + + + + 0 + + + + + + 1 + + + + + + 0 + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + false + + + + + + 1 + + + + + + 1 + + + + + + + 80 + + + + + + + 25 + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + 0 + + + + + + null + + + + + + 0 + + + + + + 60 + + + + + + 0.5 + + + + + video + + + ,model:'',mode:,screenWidth:,screenHeight:,screenColor:'',screenRotate:,bufferAddr:,bufferRAM:,bufferFormat:'',bufferCols:,bufferRows:,bufferBits:,bufferLeft:,bufferRotate:,memory:,switches:'',scale:,cellWidth:,cellHeight:,charCols:,charRows:,fontROM:'',fontColor:'',touchScreen:'',autoLock:,aspectRatio:,smoothing:,interruptRate:,refreshRate:,flicker: + + + + + + + + + + + + + + + 16 + + + + + + + + + + + + + + + + + debugger + ,base:,commands:'',messages:'' + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + true + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + computer + ,autoPower:,busWidth:,resume:'',state:'' + + + + +
    +
    + + + + + + + + + + <></> + + +
    <machine></machine>
    +
    + + + +
    diff --git a/versions/pc8080/1.71.5/document.css b/versions/pc8080/1.71.5/document.css new file mode 100644 index 0000000000..4281cab24f --- /dev/null +++ b/versions/pc8080/1.71.5/document.css @@ -0,0 +1,162 @@ +@CHARSET "UTF-8"; + +.page { + margin: 2% 2%; + padding: 2% 2%; + min-width: 30em; + overflow: auto; + font-size: large; + font-family: Helvetica, Arial, sans-serif; + background: #303030; + color: #ccc; + +} +.page-header { +} +.page-header-title { + text-align: center; + +} +.page a { + color: #7fc07f; + text-decoration: none; +} +a.footlink, a.paralink { + text-decoration: none; +} +a.footlink:link, a.paralink:link { + color: blue; +} +a.footlink:visited, a.paralink:visited { + color: blue; +} +.galleryitem { + float: left; + width: 200px; +} +.item { + float: left; + width: 2em; + text-indent: 1em; +} +.list { + margin-left: 3em; + text-indent: 0; + text-align: justify; +} +ul { + list-style: none; +} +div.pnumber { + float: left; + width: 2em; + text-indent: 1em; +} +div.pitem { + margin-left: 10em; +} +p.indent, .justified p { + text-indent: 2em; + text-align: justify; + line-height: 1.5em; +} +p.noindent { + text-indent: 0; + text-align: justify; +} +p.center, .center { + text-align: center; +} +li.para { + margin-top: 1em; + margin-bottom: 1em; +} +.left { + text-align: left; +} +.right { + text-align: right; +} +blockquote.tag { + font-size: small; + font-family: Monaco, Fixed, monospace; + margin-top: 0; + margin-bottom: 0; +} +.blockquote { + padding-left: 1em; + text-indent: 0; + text-align: justify; +} +.italics { + font-style: italic; +} +.medium { + font-size: medium; +} +.small { + font-size: x-small; +} +.smallcaps { + font-variant: small-caps; +} +.strike { + text-decoration: line-through; +} +.summation, .bracelist { + display: inline-block; + position: relative; + vertical-align: middle; + text-align: center; + margin-bottom: 0.5ex; + text-indent: 0; +} +.bracelist-symbol { + font-size: 3em; + vertical-align: -40%; +} +.summation .summation-lower, .summation .summation-upper, .bracelist-item { + display: block; + font-size: 75%; + text-align: center; +} +.summation .summation-upper { + margin-bottom: 0; + margin-left: 0.8ex; + font-style: italic; +} +.summation .summation-lower{ + margin-bottom: -0.6ex; + font-style: italic; +} +.summation .summation-symbol { + font-size: 2em; +} +p sup { + vertical-align: baseline; + position: relative; + bottom: .5em; + font-size: small; +} +p sub { + vertical-align: baseline; + position: relative; + bottom: -.5em; + font-size: small; +} +.footnote { + font-size: medium; + text-indent: 1em; + text-align: justify; + margin-top: .5em; +} +.image-right { + float: right; + margin-left: 1em; + margin-top: 1em; + margin-bottom: 1em; +} +.image-caption { + font-size: small; + text-align: center; +} diff --git a/versions/pc8080/1.71.5/document.xsl b/versions/pc8080/1.71.5/document.xsl new file mode 100644 index 0000000000..8cbe3e20e9 --- /dev/null +++ b/versions/pc8080/1.71.5/document.xsl @@ -0,0 +1,452 @@ + + + + + +]> + + + + + + + + + +

    +
    + + + + + + + +

    +
    + +

    +
    +
    +
    + + + + + + +
    +
    + + +
    + +   + + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + +

    +
    + + +

    +
    + + +

    +
    + + +
    +
    + + +
    +
    + + + + + + + + + + + + + + +
    +
    + + +
    +
    + + +
  • +
    + + +
    image
    +
    + + +
    +
    + + + + +
    {.}
    +
    + +
    {.}
    +
    +
    +
    + + + + + + + + + + < + > + + + + × + + ÷ + σ + + + + + + + + + + + + { + + + + + + + + + + [] + + + + +
    + +
    +
    + + + , and + + + + + MDY + + + + + + + + + + + + + + + + + + + + January + February + March + April + May + June + July + August + September + October + November + December + + + , + + + + + +

    + +
    +
    + + +
    + {.}
    +
    +
    +
    + + + +

    Timeline

    +
    + +

    +
    +
    + +
    +
    + + + + + + + + + +

    +
    + +
    +
    +
    + + + +

    People

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + +

    +
      + +
    +
    + + + + + + + + + + +
  • + +
  • +
    + + + +

    +
    +

    + +

    +
    +
    + + + + false + + + + + + [Original] + + + + + + + + + + [] + + +
    by
    + + +
    + [Source: + + + + + + + ] +
    +
    +
    + + + +

    Resources

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + + +

    +
    +
      + +
    +
    + + +
  • +
    + + + +

    +
    +
    + +
    +
    + + + +

    +
    + +
    + + + +

    +
    +
      + +
    +
    + + + + + +
      + +
    +
    + + + + +
  • +
    + +
  • +
    + +
  • +
    +
    +
    + + +
  • +
    + + + + + + + + + + +
    + < ="" + + ></> + ></> + /> + +
    +
    + +
    diff --git a/versions/pc8080/1.71.5/machine.xsl b/versions/pc8080/1.71.5/machine.xsl new file mode 100644 index 0000000000..7907ddc0b0 --- /dev/null +++ b/versions/pc8080/1.71.5/machine.xsl @@ -0,0 +1,61 @@ + + + + +]> + + + + + + + + + + + + + + + + + js + + + + + + PCjs + + + + +
    + +
    +
    + + + + + , + +
    +
    + + +
    + + + + + + + + + + +
    + +
    diff --git a/versions/pc8080/1.71.5/manifest.xsl b/versions/pc8080/1.71.5/manifest.xsl new file mode 100644 index 0000000000..97da83e870 --- /dev/null +++ b/versions/pc8080/1.71.5/manifest.xsl @@ -0,0 +1,247 @@ + + + + +]> + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Document Manifest

    +
    +
      + + + + None + + + + + + + + + + + + + + + + +
    +
    +
    +

    + +
    +
    +
    + + +
    + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Software Manifest

    +
    +
      + + + + None + + + + + Unknown + + + + + None + + + + + None + + + + + + + + + + + + + UpdatedReleased + + Unknown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + + + + +

    No default machine specified for '' in manifest.xml

    +
    + +
    +
    +
    + + + + -dbg + + + + + + +
    + + + + + Unknown + +
  • +
      + + + + + + + + +
    • + + + + + + + + + + + + + + + + + + + + + + + + + +
        + +
      • + + + + + + +
      • +
        +
      +
      +
    • +
      + + + + + + + + +
    +
  • +
    +
    + +
    diff --git a/versions/pc8080/1.71.5/outline.xsl b/versions/pc8080/1.71.5/outline.xsl new file mode 100644 index 0000000000..d75cd3b8ec --- /dev/null +++ b/versions/pc8080/1.71.5/outline.xsl @@ -0,0 +1,47 @@ + + + + +]> + + + + + + + + + + + + + + + + + + PCjs<xsl:text> | </xsl:text><xsl:value-of select="title"/> + + + + + +
    +
    + +
    +
    + + + + -dbg + + + + + + +
    + +
    diff --git a/versions/pc8080/1.71.5/pc8080-uncompiled.js b/versions/pc8080/1.71.5/pc8080-uncompiled.js new file mode 100644 index 0000000000..c270ec80f9 --- /dev/null +++ b/versions/pc8080/1.71.5/pc8080-uncompiled.js @@ -0,0 +1,26022 @@ +"use strict"; + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @define {string} + */ +var APPVERSION = ""; // this @define is overridden by the Closure Compiler with the version in machines.json + +var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML + +var COPYRIGHT = "Copyright © 2012-2018 Jeff Parsons "; + +var LICENSE = "License: GPL version 3 or later "; + +var CSSCLASS = "pcjs"; + +/** + * @define {string} + */ +var SITEURL = "http://localhost:8088"; // this @define is overridden by the Closure Compiler with "https://www.pcjs.org" + +/** + * @define {boolean} + */ +var COMPILED = false; // this @define is overridden by the Closure Compiler (to true) + +/** + * @define {boolean} + */ +var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code + +/** + * @define {boolean} + */ +var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code + +/** + * @define {boolean} + */ +var PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code + +/* + * RS-232 DB-25 Pin Definitions, mapped to bits 1-25 in a 32-bit status value. + * + * SerialPorts in PCjs machines are considered DTE (Data Terminal Equipment), which means they should be "virtually" + * connected to each other via a null-modem cable, which assumes the following cross-wiring: + * + * G 1 <-> 1 G (Ground) + * TD 2 <-> 3 RD (Received Data) + * RD 3 <-> 2 TD (Transmitted Data) + * RTS 4 <-> 5 CTS (Clear To Send) + * CTS 5 <-> 4 RTS (Request To Send) + * DSR 6+8 <-> 20 DTR (Data Terminal Ready) + * SG 7 <-> 7 SG (Signal Ground) + * DTR 20 <-> 6+8 DSR (Data Set Ready + Carrier Detect) + * RI 22 <-> 22 RI (Ring Indicator) + * + * TODO: Move these definitions to a more appropriate shared file at some point. + */ +var RS232 = { + RTS: { + PIN: 4, + MASK: 0x00000010 + }, + CTS: { + PIN: 5, + MASK: 0x00000020 + }, + DSR: { + PIN: 6, + MASK: 0x00000040 + }, + CD: { + PIN: 8, + MASK: 0x00000100 + }, + DTR: { + PIN: 20, + MASK: 0x00100000 + }, + RI: { + PIN: 22, + MASK: 0x00400000 + } +}; + +/* + * NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser) + */ +var NODE = false; + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Our "DiskDump API", such as it was, used to look like: + * + * http://pcjs.org/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * To make it (a bit) more "REST-like", the above request now looks like: + * + * https://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * Similarly, our "FileDump API" used to look like: + * + * http://pcjs.org/bin/convrom.php?rom=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * and that request now looks like: + * + * https://www.pcjs.org/api/v1/dump?file=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the + * the rest of the URL would be (a) confusing, and (b) more work to parse. + */ +var DumpAPI = { + ENDPOINT: "/api/v1/dump", + QUERY: { + DIR: "dir", // value is path of a directory (DiskDump only) + DISK: "disk", // value is path of a disk image (DiskDump only) + FILE: "file", // value is path of a ROM image file (FileDump only) + IMG: "img", // alias for DISK + PATH: "path", // value is path of a one or more files (DiskDump only) + FORMAT: "format", // value is one of FORMAT values below + COMMENTS: "comments", // value is either "true" or "false" + DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise + MBHD: "mbhd", // value is hard drive size in Mb (formerly "mbsize") (DiskDump only) (DEPRECATED) + SIZE: "size" // value is target disk size in Kb (supersedes "mbhd") (DiskDump only) + }, + FORMAT: { + JSON: "json", // default + JSON_GZ: "gz", // gzip is currently used ONLY for compressed JSON + DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only) + HEX: "hex", // deprecated + OCTAL: "octal", // displays data as octal words + BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled + WORDS: "words", // displays data as hex words; normally used only when comments are enabled + LONGS: "longs", // displays data as dwords + IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only) + ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only) + } +}; + +/* + * Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must + * also provide a list of commands which, when combined with the endpoint, define a unique request. + */ +DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH]; +DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE]; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2018 + */ + +var ReportAPI = { + ENDPOINT: "/api/v1/report", + QUERY: { + APP: "app", + VER: "ver", + URL: "url", + USER: "user", + TYPE: "type", + DATA: "data" + }, + TYPE: { + BUG: "bug" + }, + RES: { + OK: "Thank you" + } +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Examples of User API requests: + * + * web.getHostOrigin() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser; + */ +var UserAPI = { + ENDPOINT: "/api/v1/user", + QUERY: { + REQ: "req", // specifies a request + USER: "user", // specifies a user ID + STATE: "state", // specifies a state ID + DATA: "data" // specifies state data + }, + REQ: { + CREATE: "create", // creates a user ID + VERIFY: "verify", // requests verification of a user ID + STORE: "store", // stores a machine state on the server + LOAD: "load" // loads a machine state from the server + }, + RES: { + CODE: "code", + DATA: "data" + }, + CODE: { + OK: "ok", + FAIL: "error" + }, + FAIL: { + DUPLICATE: "user already exists", + VERIFY: "unable to verify user", + BADSTATE: "invalid state parameter", + NOSTATE: "no machine state", + BADLOAD: "unable to load machine state", + BADSTORE: "unable to save machine state" + } +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2018 + */ + +var Keys = { + /* + * Keys and/or key combinations that generate common ASCII codes. + * + * NOTE: If you're looking for a general-purpose ASCII code table, see Str.ASCII in strlib.js; + * if something's missing, that's probably the more appropriate table to add it to. + * + * TODO: The Closure Compiler doesn't inline all references to these values, at least those with + * quoted property names, which is why I've 'unquoted' as many of them as possible. One solution + * would be to add mnemonics for all of them, not just the non-printable ones (eg, SPACE instead + * of ' ', AMP instead of '&', etc.) + */ + ASCII: { + BREAK: 0, CTRL_A: 1, CTRL_B: 2, CTRL_C: 3, CTRL_D: 4, CTRL_E: 5, CTRL_F: 6, CTRL_G: 7, + CTRL_H: 8, CTRL_I: 9, CTRL_J: 10, CTRL_K: 11, CTRL_L: 12, CTRL_M: 13, CTRL_N: 14, CTRL_O: 15, + CTRL_P: 16, CTRL_Q: 17, CTRL_R: 18, CTRL_S: 19, CTRL_T: 20, CTRL_U: 21, CTRL_V: 22, CTRL_W: 23, + CTRL_X: 24, CTRL_Y: 25, CTRL_Z: 26, ESC: 27, + ' ': 32, '!': 33, '"': 34, '#': 35, '$': 36, '%': 37, '&': 38, "'": 39, + '(': 40, ')': 41, '*': 42, '+': 43, ',': 44, '-': 45, '.': 46, '/': 47, + '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, + '8': 56, '9': 57, ':': 58, ';': 59, '<': 60, '=': 61, '>': 62, '?': 63, + '@': 64, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, + H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, + P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, + X: 88, Y: 89, Z: 90, '[': 91, '\\':92, ']': 93, '^': 94, '_': 95, + '`': 96, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103, + h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111, + p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119, + x: 120, y: 121, z: 122, '{':123, '|':124, '}':125, '~':126, DEL: 127 + }, + /* + * Browser keyCodes we must pay particular attention to. For the most part, these are non-alphanumeric + * or function keys, some which may require special treatment (eg, preventDefault() if returning false on + * the initial keyDown event is insufficient). + * + * keyCodes for most common ASCII keys can simply use the appropriate ASCII code above. + * + * Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, browsers defined + * them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37). + */ + KEYCODE: { + /* 0x08 */ BS: 8, // BACKSPACE (ASCII.CTRL_H) + /* 0x09 */ TAB: 9, // TAB (ASCII.CTRL_I) + /* 0x0A */ LF: 10, // LINE-FEED (ASCII.CTRL_J) (Some Windows-based browsers used to generate this via CTRL-ENTER) + /* 0x0D */ CR: 13, // CARRIAGE RETURN (ASCII.CTRL_M) + /* 0x10 */ SHIFT: 16, + /* 0x11 */ CTRL: 17, + /* 0x12 */ ALT: 18, + /* 0x13 */ PAUSE: 19, // PAUSE/BREAK + /* 0x14 */ CAPS_LOCK: 20, + /* 0x1B */ ESC: 27, + /* 0x20 */ SPACE: 32, + /* 0x21 */ PGUP: 33, + /* 0x22 */ PGDN: 34, + /* 0x23 */ END: 35, + /* 0x24 */ HOME: 36, + /* 0x25 */ LEFT: 37, + /* 0x26 */ UP: 38, + /* 0x27 */ RIGHT: 39, + /* 0x27 */ FF_QUOTE: 39, + /* 0x28 */ DOWN: 40, + /* 0x2C */ FF_COMMA: 44, + /* 0x2C */ PRTSC: 44, + /* 0x2D */ INS: 45, + /* 0x2E */ DEL: 46, + /* 0x2E */ FF_PERIOD: 46, + /* 0x2F */ FF_SLASH: 47, + /* 0x30 */ ZERO: 48, + /* 0x31 */ ONE: 49, + /* 0x32 */ TWO: 50, + /* 0x33 */ THREE: 51, + /* 0x34 */ FOUR: 52, + /* 0x35 */ FIVE: 53, + /* 0x36 */ SIX: 54, + /* 0x37 */ SEVEN: 55, + /* 0x38 */ EIGHT: 56, + /* 0x39 */ NINE: 57, + /* 0x3B */ FF_SEMI: 59, + /* 0x3D */ FF_EQUALS: 61, + /* 0x5B */ CMD: 91, // aka WIN + /* 0x5B */ FF_LBRACK: 91, + /* 0x5C */ FF_BSLASH: 92, + /* 0x5D */ RCMD: 93, // aka MENU + /* 0x5D */ FF_RBRACK: 93, + /* 0x60 */ NUM_0: 96, + /* 0x60 */ NUM_INS: 96, + /* 0x60 */ FF_BQUOTE: 96, + /* 0x61 */ NUM_1: 97, + /* 0x61 */ NUM_END: 97, + /* 0x62 */ NUM_2: 98, + /* 0x62 */ NUM_DOWN: 98, + /* 0x63 */ NUM_3: 99, + /* 0x63 */ NUM_PGDN: 99, + /* 0x64 */ NUM_4: 100, + /* 0x64 */ NUM_LEFT: 100, + /* 0x65 */ NUM_5: 101, + /* 0x65 */ NUM_CENTER: 101, + /* 0x66 */ NUM_6: 102, + /* 0x66 */ NUM_RIGHT: 102, + /* 0x67 */ NUM_7: 103, + /* 0x67 */ NUM_HOME: 103, + /* 0x68 */ NUM_8: 104, + /* 0x68 */ NUM_UP: 104, + /* 0x69 */ NUM_9: 105, + /* 0x69 */ NUM_PGUP: 105, + /* 0x6A */ NUM_MUL: 106, + /* 0x6B */ NUM_ADD: 107, + /* 0x6D */ NUM_SUB: 109, + /* 0x6E */ NUM_DEL: 110, // aka PERIOD + /* 0x6F */ NUM_DIV: 111, + /* 0x70 */ F1: 112, + /* 0x71 */ F2: 113, + /* 0x72 */ F3: 114, + /* 0x73 */ F4: 115, + /* 0x74 */ F5: 116, + /* 0x75 */ F6: 117, + /* 0x76 */ F7: 118, + /* 0x77 */ F8: 119, + /* 0x78 */ F9: 120, + /* 0x79 */ F10: 121, + /* 0x7A */ F11: 122, + /* 0x7B */ F12: 123, + /* 0x90 */ NUM_LOCK: 144, + /* 0x91 */ SCROLL_LOCK: 145, + /* 0xAD */ FF_DASH: 173, + /* 0xBA */ SEMI: 186, // Firefox: 59 (FF_SEMI) + /* 0xBB */ EQUALS: 187, // Firefox: 61 (FF_EQUALS) + /* 0xBC */ COMMA: 188, + /* 0xBD */ DASH: 189, // Firefox: 173 (FF_DASH) + /* 0xBE */ PERIOD: 190, + /* 0xBF */ SLASH: 191, + /* 0xC0 */ BQUOTE: 192, + /* 0xDB */ LBRACK: 219, + /* 0xDC */ BSLASH: 220, + /* 0xDD */ RBRACK: 221, + /* 0xDE */ QUOTE: 222, + /* 0xE0 */ FF_CMD: 224, // Firefox only (used for both CMD and RCMD) + // + // The following biases use what I'll call Decimal Coded Binary or DCB (the opposite of BCD), + // where the thousands digit is used to store the sum of "binary" digits 1 and/or 2 and/or 4. + // + // Technically, that makes it DCO (Decimal Coded Octal), but then again, BCD should have really + // been called HCD (Hexadecimal Coded Decimal), so if "they" can take liberties, so can I. + // + // ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than on "press". + // + ONDOWN: 1000, + // + // ONRIGHT is a bias we add to browser keyCodes that need to check for a "right" location (default is "left") + // + ONRIGHT: 2000, + // + // FAKE is a bias we add to signal these are fake keyCodes corresponding to internal keystroke combinations. + // The actual values are for internal use only and merely need to be unique and used consistently. + // + FAKE: 4000 + }, + /* + * The set of values that a browser may store in the 'location' property of a keyboard event object + * which we also support. + */ + LOCATION: { + LEFT: 1, + RIGHT: 2, + NUMPAD: 3 + } +}; + +/* + * Check the event object's 'location' property for a non-zero value for the following ONRIGHT keys. + */ +Keys.KEYCODE.NUM_CR = Keys.KEYCODE.CR + Keys.KEYCODE.ONRIGHT; + + +/* + * Maps Firefox keyCodes to their more common keyCode counterparts; a number of entries in this table + * are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I + * simply extended this table to resolve additional differences in other browsers (ie, Opera), but without + * browser-specific checks, it's not safe to perform all the mappings shown below. + */ +Keys.FF_KEYCODES = {}; +Keys.FF_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.KEYCODE.SEMI; // 59 -> 186 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.KEYCODE.EQUALS; // 61 -> 187 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.KEYCODE.DASH; // 173 -> 189 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_CMD] = Keys.KEYCODE.CMD; // 224 -> 91 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_COMMA] = Keys.KEYCODE.COMMA; // 44 -> 188 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_PERIOD] = Keys.KEYCODE.PERIOD; // 46 -> 190 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_SLASH] = Keys.KEYCODE.SLASH; // 47 -> 191 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BQUOTE] = Keys.KEYCODE.BQUOTE; // 96 -> 192 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_LBRACK = Keys.KEYCODE.LBRACK; // 91 -> 219 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BSLASH] = Keys.KEYCODE.BSLASH; // 92 -> 220 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_RBRACK] = Keys.KEYCODE.RBRACK; // 93 -> 221 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_QUOTE] = Keys.KEYCODE.QUOTE; // 39 -> 222 + +/* + * Maps non-ASCII keyCodes to their ASCII counterparts + */ +Keys.NONASCII_KEYCODES = {}; +Keys.NONASCII_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['-']; // 173 -> 45 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[';']; // 186 -> 59 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['=']; // 187 -> 61 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['-']; // 189 -> 45 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII[',']; // 188 -> 44 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['.']; // 190 -> 46 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['/']; // 191 -> 47 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['`']; // 192 -> 96 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['[']; // 219 -> 91 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['\\']; // 220 -> 92 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII[']']; // 221 -> 93 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII["'"]; // 222 -> 39 + +/* + * Maps unshifted keyCodes to their shifted counterparts; to be used when a shift-key is down. + * Alphabetic characters are handled in code, since they must also take CAPS_LOCK into consideration. + */ +Keys.SHIFTED_KEYCODES = {}; +Keys.SHIFTED_KEYCODES[Keys.ASCII['1']] = Keys.ASCII['!']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['2']] = Keys.ASCII['@']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['3']] = Keys.ASCII['#']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['4']] = Keys.ASCII['$']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['5']] = Keys.ASCII['%']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['6']] = Keys.ASCII['^']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['7']] = Keys.ASCII['&']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['8']] = Keys.ASCII['*']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['9']] = Keys.ASCII['(']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['0']] = Keys.ASCII[')']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[':']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['+']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII['<']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['_']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['>']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['?']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['~']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['{']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['|']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII['}']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII['"']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['_']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.ASCII['+']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.ASCII[':']; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2018 + */ + +class Str { + /** + * isValidInt(s, base) + * + * The built-in parseInt() function has the annoying feature of returning a partial value (ie, + * up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf. + * + * So it's best to use our own Str.parseInt() function, which will in turn use this function to + * validate the entire string. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported + * @return {boolean} true if valid, false if invalid (or the specified base isn't supported) + */ + static isValidInt(s, base) + { + if (!base || base == 10) return s.match(/^-?[0-9]+$/) !== null; + if (base == 16) return s.match(/^-?[0-9a-f]+$/i) !== null; + if (base == 8) return s.match(/^-?[0-7]+$/) !== null; + if (base == 2) return s.match(/^-?[01]+$/) !== null; + return false; + } + + /** + * parseInt(s, base) + * + * This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes + * ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for + * binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values; + * see isValidInt() for details. + * + * The use of multiple prefix/suffix combinations is undefined (although for the record, we process + * prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are + * also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero + * to indicate octal (because such a number could also be decimal or hex). Any number of commas are + * allowed; we remove them all before calling the built-in parseInt(). + * + * More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides + * that the PDP-10's MACRO-10 assembly language supports (decimal, octal, and binary, respectively). + * If this support turns out to adversely affect other debuggers, then it will have to be "conditionalized". + * Similarly, we've added support for "K", "M", and "G" MACRO-10-style suffixes that add 3, 6, or 9 zeros + * to the value to be parsed, respectively. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes + * @return {number|undefined} corresponding value, or undefined if invalid + */ + static parseInt(s, base) + { + let value; + + if (s) { + if (!base) base = 10; + + let ch, chPrefix, chSuffix; + let fCommas = (s.indexOf(',') > 0); + if (fCommas) s = s.replace(/,/g, ''); + + ch = chPrefix = s.charAt(0); + if (chPrefix == '#') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '$') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) { + s = s.substr(1); + } + else { + ch = chPrefix = s.substr(0, 2); + if (chPrefix == '0b' && fCommas || chPrefix == '^B') { + base = 2; + chPrefix = ''; + } + else if (chPrefix == '0o' || chPrefix == '^O') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '^D') { + base = 10; + chPrefix = ''; + } + else if (chPrefix == '0x') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) s = s.substr(2); + } + ch = chSuffix = s.slice(-1); + if (chSuffix == 'Y' || chSuffix == 'y') { + base = 2; + chSuffix = ''; + } + else if (chSuffix == '.') { + base = 10; + chSuffix = ''; + } + else if (chSuffix == 'H' || chSuffix == 'h') { + base = 16; + chSuffix = ''; + } + else if (chSuffix == 'K') { + chSuffix = '000'; + } + else if (chSuffix == 'M') { + chSuffix = '000000'; + } + else if (chSuffix == 'G') { + chSuffix = '000000000'; + } + if (ch != chSuffix) s = s.slice(0, -1) + chSuffix; + /* + * This adds support for the MACRO-10 binary shifting (Bn) suffix, which must be stripped from the + * number before parsing, and then applied to the value after parsing. If n is omitted, 35 is assumed, + * which is a net shift of zero. If n < 35, then a left shift of (35 - n) is required; if n > 35, then + * a right shift of -(35 - n) is required. + */ + let v, shift = 0; + if (base <= 10) { + let match = s.match(/(-?[0-9]+)B([0-9]*)/); + if (match) { + s = match[1]; + shift = 35 - ((match[2] || 35) & 0xff); + } + } + if (Str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) { + /* + * With the need to support larger (eg, 36-bit) integers, truncating to 32 bits is no longer helpful. + * + * value = v|0; + */ + if (shift) { + /* + * Since binary shifting is a logical operation, and since shifting by division only works properly + * with positive numbers, we must convert a negative value to a positive value, by computing the two's + * complement. + */ + if (v < 0) v += Math.pow(2, 36); + if (shift > 0) { + v *= Math.pow(2, shift); + } else { + v = Math.trunc(v / Math.pow(2, -shift)); + } + } + value = v; + } + } + return value; + } + + /** + * toBase(n, radix, cch, sPrefix, nGrouping) + * + * Displays the given number as an unsigned integer using the specified radix and number of digits. + * + * @param {number|*} n + * @param {number} radix (ie, the base) + * @param {number} cch (the desired number of digits) + * @param {string} [sPrefix] (default is none) + * @param {number} [nGrouping] + * @return {string} + */ + static toBase(n, radix, cch, sPrefix = "", nGrouping = 0) + { + /* + * We can't rely entirely on isNaN(), because isNaN(null) returns false, and we can't rely + * entirely on typeof either, because typeof Nan returns "number". Sigh. + * + * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN, + * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those + * values displayed differently. + */ + let s = ""; + if (isNaN(n) || typeof n != "number") { + n = null; + } else { + /* + * Callers that produced an input by dividing by a power of two rather than shifting (in order + * to access more than 32 bits) may produce a fractional result, which ordinarily we would simply + * ignore, but if the integer portion is zero and the sign is negative, we should probably treat + * this value as a sign-extension. + */ + if (n < 0 && n > -1) n = -1; + /* + * Negative values should be two's complemented according to the number of digits; for example, + * 12 octal digits implies an upper limit 8^12. + */ + if (n < 0) { + n += Math.pow(radix, cch); + } + if (n >= Math.pow(radix, cch)) { + cch = Math.ceil(Math.log(n) / Math.log(radix)); + } + } + let g = nGrouping || -1; + while (cch-- > 0) { + if (!g) { + s = ',' + s; + g = nGrouping; + } + if (n == null) { + s = '?' + s; + } else { + let d = n % radix; + d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10); + s = String.fromCharCode(d) + s; + n = Math.trunc(n / radix); + } + g--; + } + return sPrefix + s; + } + + /** + * toBin(n, cch, nGrouping) + * + * Converts an integer to binary, with the specified number of digits (up to a maximum of 36). + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of binary digits (0 or undefined for default of either 8, 18, or 36) + * @param {number} [nGrouping] + * @return {string} the binary representation of n + */ + static toBin(n, cch, nGrouping) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN2) || 1; + let v = Math.abs(n); + if (v <= 0b11111111) { + cch = 8; + } else if (v <= 0b111111111111111111) { + cch = 18; + } else { + cch = 36; + } + } else if (cch > 36) cch = 36; + return Str.toBase(n, 2, cch, "", nGrouping); + } + + /** + * toBinBytes(n, cb, fPrefix) + * + * Converts an integer to binary, with the specified number of bytes (up to the default of 4). + * + * @param {number|null|undefined} n (interpreted as a 32-bit value) + * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum) + * @param {boolean} [fPrefix] + * @return {string} the binary representation of n + */ + static toBinBytes(n, cb, fPrefix) + { + let s = ""; + if (!cb || cb > 4) cb = 4; + for (let i = 0; i < cb; i++) { + if (s) s = ',' + s; + s = Str.toBin(n & 0xff, 8) + s; + n >>= 8; + } + return (fPrefix? "0b" : "") + s; + } + + /** + * toOct(n, cch, fPrefix) + * + * Converts an integer to octal, with the specified number of digits (default of 6; max of 12) + * + * You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6, 8, or 12) + * @param {boolean} [fPrefix] + * @return {string} the octal representation of n + */ + static toOct(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(8)) || 1; + let v = Math.abs(n); + if (v <= 0o777777) { + cch = 6; + } else if (v <= 0o77777777) { + cch = 8; + } else { + cch = 12; + } + } else if (cch > 12) cch = 12; + return Str.toBase(n, 8, cch, fPrefix? "0o" : ""); + } + + /** + * toDec(n, cch) + * + * Converts an integer to decimal, with the specified number of digits (default of 5; max of 11) + * + * You might be tempted to use the built-in n.toString(10) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 11) + * @return {string} the decimal representation of n + */ + static toDec(n, cch) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN10) || 1; + let v = Math.abs(n); + if (v <= 99999) { + cch = 5; + } else { + cch = 11; + } + } else if (cch > 11) cch = 11; + return Str.toBase(n, 10, cch); + } + + /** + * toHex(n, cch, fPrefix) + * + * Converts an integer to hex, with the specified number of digits (default of 4 or 8, max of 9). + * + * You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16) + * will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will + * throw an exception, whereas this function will return '?' characters. + * + * NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution, + * taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values: + * + * s = (n < 0? n + 0x100000000 : n).toString(16); + * s = "00000000".substr(0, 8 - s.length) + s; + * s = s.substr(0, cch).toUpperCase(); + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4, 8, or 9) + * @param {boolean} [fPrefix] + * @return {string} the hex representation of n + */ + static toHex(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(16)) || 1; + let v = Math.abs(n); + if (v <= 0xffff) { + cch = 4; + } else if (v <= 0xffffffff) { + cch = 8; + } else { + cch = 9; + } + } else if (cch > 9) cch = 9; + return Str.toBase(n, 16, cch, fPrefix? "0x" : ""); + } + + /** + * toHexByte(b) + * + * Alias for Str.toHex(b, 2, true) + * + * @param {number|null|undefined} b is a byte value + * @return {string} the hex representation of b + */ + static toHexByte(b) + { + return Str.toHex(b, 2, true); + } + + /** + * toHexWord(w) + * + * Alias for Str.toHex(w, 4, true) + * + * @param {number|null|undefined} w is a word (16-bit) value + * @return {string} the hex representation of w + */ + static toHexWord(w) + { + return Str.toHex(w, 4, true); + } + + /** + * toHexLong(l) + * + * Alias for Str.toHex(l, 8, true) + * + * @param {number|null|undefined} l is a dword (32-bit) value + * @return {string} the hex representation of w + */ + static toHexLong(l) + { + return Str.toHex(l, 8, true); + } + + /** + * getBaseName(sFileName, fStripExt) + * + * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead. + * + * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips the extension only + * if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux"). + * + * @param {string} sFileName + * @param {boolean} [fStripExt] + * @return {string} + */ + static getBaseName(sFileName, fStripExt) + { + let sBaseName = sFileName; + + let i = sFileName.lastIndexOf('/'); + if (i >= 0) sBaseName = sFileName.substr(i + 1); + + /* + * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters. + */ + i = sBaseName.indexOf('&'); + if (i > 0) sBaseName = sBaseName.substr(0, i); + + if (fStripExt) { + i = sBaseName.lastIndexOf("."); + if (i > 0) { + sBaseName = sBaseName.substring(0, i); + } + } + return sBaseName; + } + + /** + * getExtension(sFileName) + * + * This is a poor-man's version of Node's path.extname(), which Node-only components should use instead. + * + * Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it. + * + * @param {string} sFileName + * @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string + */ + static getExtension(sFileName) + { + let sExtension = ""; + let i = sFileName.lastIndexOf("."); + if (i >= 0) { + sExtension = sFileName.substr(i + 1).toLowerCase(); + } + return sExtension; + } + + /** + * endsWith(s, sSuffix) + * + * @param {string} s + * @param {string} sSuffix + * @return {boolean} true if s ends with sSuffix, false if not + */ + static endsWith(s, sSuffix) + { + return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1; + } + + /** + * escapeHTML(sHTML) + * + * @param {string} sHTML + * @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars() + */ + static escapeHTML(sHTML) + { + return sHTML.replace(/[&<>"']/g, function(m) + { + return Str.HTMLEscapeMap[m]; + }); + } + + /** + * replace(sSearch, sReplace, s) + * + * The JavaScript replace() function ALWAYS interprets "$" specially in replacement strings, even when + * the search string is NOT a RegExp; specifically: + * + * $$ Inserts a "$" + * $& Inserts the matched substring + * $` Inserts the portion of the string that precedes the matched substring + * $' Inserts the portion of the string that follows the matched substring + * $n Where n is a positive integer less than 100, inserts the nth parenthesized sub-match string, + * provided the first argument was a RegExp object + * + * So, if a replacement string containing dollar signs passes through a series of replace() calls, untold + * problems could result. Hence, this function, which simply uses the replacement string as-is. + * + * Similar to the JavaScript replace() method (when sSearch is a string), this replaces only ONE occurrence + * (ie, the FIRST occurrence); it might be nice to add options to replace the LAST occurrence and/or ALL + * occurrences, but we'll revisit that later. + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replace(sSearch, sReplace, s) + { + let i = s.indexOf(sSearch); + if (i >= 0) { + s = s.substr(0, i) + sReplace + s.substr(i + sSearch.length); + } + return s; + } + + /** + * replaceAll(sSearch, sReplace, s) + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replaceAll(sSearch, sReplace, s) + { + let a = {}; + a[sSearch] = sReplace; + return Str.replaceArray(a, s); + } + + /** + * replaceArray(a, s) + * + * @param {Object} a + * @param {string} s + * @return {string} + */ + static replaceArray(a, s) + { + let sMatch = ""; + for (let k in a) { + /* + * As noted in: + * + * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/ + * + * inside character classes, only backslash, caret, hyphen and the closing bracket need to be + * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first, + * and the hyphen is last, you can avoid escaping those as well. + */ + k = k.replace(/([\\[\]*{}().+?|$])/g, "\\$1"); + sMatch += (sMatch? '|' : '') + k; + } + return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) + { + return a[m]; + }); + } + + /** + * pad(s, cch, fPadLeft) + * + * NOTE: the maximum amount of padding currently supported is 40 spaces. + * + * @param {string} s is a string + * @param {number} cch is desired length + * @param {boolean} [fPadLeft] (default is padding on the right) + * @return {string} the original string (s) with spaces padding it to the specified length + */ + static pad(s, cch, fPadLeft) + { + let sPadding = " "; + return fPadLeft? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch); + } + + /** + * sprintf(format, ...args) + * + * Copied from the CCjs project (https://github.com/jeffpar/ccjs/blob/master/lib/stdio.js) and extended. + * + * Far from complete, let alone sprintf-compatible, but it's adequate for the handful of sprintf-style format + * specifiers that I use. + * + * TODO: The %c and %s specifiers support a negative width (for left-justified output), but the numeric specifiers + * (eg, %d and %x) do not; they support only positive widths and right-justified output. That's one of the more + * glaring omissions at the moment. + * + * @param {string} format + * @param {...} args + * @return {string} + */ + static sprintf(format, ...args) + { + let buffer = ""; + let aParts = format.split(/%([-+ 0#]*)([0-9]*|\*)(\.[0-9]+|)([hlL]?)([A-Za-z%])/); + + let iArg = 0, iPart; + for (iPart = 0; iPart < aParts.length - 6; iPart += 6) { + + buffer += aParts[iPart]; + + let arg = args[iArg++]; + let flags = aParts[iPart+1]; + let width = aParts[iPart+2]; + if (width == '*') { + width = arg; + arg = args[iArg++]; + } else { + width = +width || 0; + } + let precision = aParts[iPart+3]; + precision = precision? +precision.substr(1) : -1; + let prefix = aParts[iPart+4]; + let type = aParts[iPart+5]; + let ach = null, s; + + switch(type) { + case 'd': + /* + * We could use "arg |= 0", but there may be some value to supporting integers > 32 bits. + * + * Also, unlike the 'X' and 'x' hexadecimal cases, there's no need to explicitly check for a string + * arguments, because the call to trunc() automatically coerces any string value to a (decimal) number. + */ + arg = Math.trunc(arg); + /* falls through */ + + case 'f': + s = Math.trunc(arg) + ""; + if (precision > 0) { + width -= (precision + 1); + } + if (s.length < width) { + if (flags.indexOf('0') >= 0) { + if (arg < 0) width--; + s = ("0000000000" + Math.abs(arg)).slice(-width); + if (arg < 0) s = '-' + s; + } else { + s = (" " + s).slice(-width); + } + } + if (precision > 0) { + arg = Math.round((arg - Math.trunc(arg)) * Math.pow(10, precision)); + s += '.' + ("0000000000" + Math.abs(arg)).slice(-precision); + } + buffer += s; + break; + + case 'c': + arg = String.fromCharCode(arg); + /* falls through */ + + case 's': + if (typeof arg == "string") { + while (arg.length < width) { + if (flags.indexOf('-') >= 0) { + arg += ' '; + } else { + arg = ' ' + arg; + } + } + } + buffer += arg.toString(); + break; + + case 'X': + ach = Str.HexUpperCase; + /* falls through */ + + case 'x': + if (!ach) ach = Str.HexLowerCase; + s = ""; + if (typeof arg == "string") { + /* + * Since we're advised to ALWAYS pass a radix to parseInt(), we must detect explicitly + * hex values ourselves, because using a radix of 10 with any "0x..." value always returns 0. + * + * And if the value CAN be interpreted as decimal, then we MUST interpret it as decimal, because + * we have sprintf() calls in /modules/pcx86/lib/testmon.js that depend on this code to perform + * decimal to hex conversion. We're going to make our own rules here, since passing numbers in + * string form isn't part of the sprintf "spec". + */ + arg = Number.parseInt(arg, arg.match(/(^0x|[a-f])/i)? 16 : 10); + } + do { + let d = arg & 0xf; + arg >>>= 4; + if (flags.indexOf('0') >= 0 || s == "" || d || arg) { + s = ach[d] + s; + } else if (width) { + s = ' ' + s; + } + } while (--width > 0 || arg); + buffer += s; + break; + + default: + /* + * The supported ANSI C set of types: "dioxXucsfeEgGpn%" + */ + buffer += "(unrecognized printf type %" + type + ")"; + break; + } + } + + buffer += aParts[iPart]; + return buffer; + } + + /** + * stripLeadingZeros(s, fPad) + * + * @param {string} s + * @param {boolean} [fPad] + * @return {string} + */ + static stripLeadingZeros(s, fPad) + { + let cch = s.length; + s = s.replace(/^0+([0-9A-F]+)$/i, "$1"); + if (fPad) s = Str.pad(s, cch, true); + return s; + } + + /** + * trim(s) + * + * @param {string} s + * @return {string} + */ + static trim(s) + { + if (String.prototype.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ""); + } + + /** + * toASCIICode(b) + * + * @param {number} b + * @return {string} + */ + static toASCIICode(b) + { + let s; + if (b != Str.ASCII.CR && b != Str.ASCII.LF) { + s = Str.ASCIICodeMap[b]; + } + if (s) { + s = '<' + s + '>'; + } else { + s = String.fromCharCode(b); + } + return s; + } +} + +/* + * Map special characters to their HTML escape sequences. + */ +Str.HTMLEscapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' +}; + +/* + * Map "unprintable" ASCII codes to mnemonics, to more clearly see what's being printed. + */ +Str.ASCIICodeMap = { + 0x00: "NUL", + 0x01: "SOH", // (CTRL_A) Start of Heading + 0x02: "STX", // (CTRL_B) Start of Text + 0x03: "ETX", // (CTRL_C) End of Text + 0x04: "EOT", // (CTRL_D) End of Transmission + 0x05: "ENQ", // (CTRL_E) Enquiry + 0x06: "ACK", // (CTRL_F) Acknowledge + 0x07: "BEL", // (CTRL_G) Bell + 0x08: "BS", // (CTRL_H) Backspace + 0x09: "TAB", // (CTRL_I) Horizontal Tab (aka HT) + 0x0A: "LF", // (CTRL_J) Line Feed (New Line) + 0x0B: "VT", // (CTRL_K) Vertical Tab + 0x0C: "FF", // (CTRL_L) Form Feed (New Page) + 0x0D: "CR", // (CTRL_M) Carriage Return + 0x0E: "SO", // (CTRL_N) Shift Out + 0x0F: "SI", // (CTRL_O) Shift In + 0x10: "DLE", // (CTRL_P) Data Link Escape + 0x11: "XON", // (CTRL_Q) Device Control 1 (aka DC1) + 0x12: "DC2", // (CTRL_R) Device Control 2 + 0x13: "XOFF", // (CTRL_S) Device Control 3 (aka DC3) + 0x14: "DC4", // (CTRL_T) Device Control 4 + 0x15: "NAK", // (CTRL_U) Negative Acknowledge + 0x16: "SYN", // (CTRL_V) Synchronous Idle + 0x17: "ETB", // (CTRL_W) End of Transmission Block + 0x18: "CAN", // (CTRL_X) Cancel + 0x19: "EM", // (CTRL_Y) End of Medium + 0x1A: "SUB", // (CTRL_Z) Substitute + 0x1B: "ESC", // Escape + 0x1C: "FS", // File Separator + 0x1D: "GS", // Group Separator + 0x1E: "RS", // Record Separator + 0x1F: "US", // Unit Separator + 0x7F: "DEL" +}; + +/* + * Refer to: https://en.wikipedia.org/wiki/Code_page_437 + */ +Str.CP437ToUnicode = [ + '\u0000', '\u263A', '\u263B', '\u2665', '\u2666', '\u2663', '\u2660', '\u2022', + '\u25D8', '\u25CB', '\u25D9', '\u2642', '\u2640', '\u266A', '\u266B', '\u263C', + '\u25BA', '\u25C4', '\u2195', '\u203C', '\u00B6', '\u00A7', '\u25AC', '\u21A8', + '\u2191', '\u2193', '\u2192', '\u2190', '\u221F', '\u2194', '\u25B2', '\u25BC', + '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', + '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', + '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', + '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', + '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', + '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', + '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', + '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', + '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', + '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', + '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', + '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u2302', + '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', + '\u00EA', '\u00EB', '\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', + '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', + '\u00FF', '\u00D6', '\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', + '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA', + '\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', + '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561', '\u2562', '\u2556', + '\u2555', '\u2563', '\u2551', '\u2557', '\u255D', '\u255C', '\u255B', '\u2510', + '\u2514', '\u2534', '\u252C', '\u251C', '\u2500', '\u253C', '\u255E', '\u255F', + '\u255A', '\u2554', '\u2569', '\u2566', '\u2560', '\u2550', '\u256C', '\u2567', + '\u2568', '\u2564', '\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', + '\u256A', '\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580', + '\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5', '\u03C4', + '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6', '\u03B5', '\u2229', + '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320', '\u2321', '\u00F7', '\u2248', + '\u00B0', '\u2219', '\u00B7', '\u221A', '\u207F', '\u00B2', '\u25A0', '\u00A0' +]; + +/* + * TODO: Future home of a complete ASCII table. + */ +Str.ASCII = { + LF: 0x0A, + CR: 0x0D +}; + +Str.TYPES = { + NULL: 0, + BYTE: 1, + WORD: 2, + DWORD: 3, + NUMBER: 4, + STRING: 5, + BOOLEAN: 6, + OBJECT: 7, + ARRAY: 8 +}; + +Str.HexLowerCase = "0123456789abcdef"; +Str.HexUpperCase = "0123456789ABCDEF"; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @typedef {{ + * mask: number, + * shift: number + * }} + */ +var BitField; + +/** + * @typedef {Object.} + */ +var BitFields; + +class Usr { + /** + * binarySearch(a, v, fnCompare) + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point + */ + static binarySearch(a, v, fnCompare) + { + let left = 0; + let right = a.length; + let found = 0; + if (fnCompare === undefined) { + fnCompare = function(a, b) + { + return a > b ? 1 : a < b ? -1 : 0; + }; + } + while (left < right) { + let middle = (left + right) >> 1; + let compareResult; + compareResult = fnCompare(v, a[middle]); + if (compareResult > 0) { + left = middle + 1; + } else { + right = middle; + found = !compareResult; + } + } + return found ? left : ~left; + } + + /** + * binaryInsert(a, v, fnCompare) + * + * If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the + * element is inserted into the array at the appropriate index. + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v is the value to insert + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + */ + static binaryInsert(a, v, fnCompare) + { + let index = Usr.binarySearch(a, v, fnCompare); + if (index < 0) { + a.splice(-(index + 1), 0, v); + } + } + + /** + * getTimestamp() + * + * @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss") + */ + static getTimestamp() + { + return Usr.formatDate("Y-m-d H:i:s"); + } + + /** + * getMonthDays(nMonth, nYear) + * + * Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100), + * so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails + * the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap + * year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year. + * + * TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD, + * so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and + * we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what + * a real RTC actually does. + * + * @param {number} nMonth (1-12) + * @param {number} nYear (normally a 4-digit year, but it may also be mod 100) + * @return {number} the maximum (1-based) day allowed for the specified month and year + */ + static getMonthDays(nMonth, nYear) + { + let nDays = Usr.aMonthDays[nMonth - 1]; + if (nDays == 28) { + if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) { + nDays++; + } + } + return nDays; + } + + /** + * formatDate(sFormat, date) + * + * @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s") + * @param {Date} [date] (default is the current time) + * @return {string} + * + * Supported identifiers in sFormat include: + * + * a: lowercase ante meridiem and post meridiem (am or pm) + * d: day of the month, 2 digits with leading zeros (01,02,...,31) + * D: 3-letter day of the week ("Sun","Mon",...,"Sat") + * F: month ("January","February",...,"December") + * g: hour in 12-hour format, without leading zeros (1,2,...,12) + * h: hour in 24-hour format, without leading zeros (0,1,...,23) + * H: hour in 24-hour format, with leading zeros (00,01,...,23) + * i: minutes, with leading zeros (00,01,...,59) + * j: day of the month, without leading zeros (1,2,...,31) + * l: day of the week ("Sunday","Monday",...,"Saturday") + * m: month, with leading zeros (01,02,...,12) + * M: 3-letter month ("Jan","Feb",...,"Dec") + * n: month, without leading zeros (1,2,...,12) + * s: seconds, with leading zeros (00,01,...,59) + * y: 2-digit year (eg, 14) + * Y: 4-digit year (eg, 2014) + * + * For more inspiration, see: http://php.net/manual/en/function.date.php (of which we support ONLY a subset). + */ + static formatDate(sFormat, date) + { + let sDate = ""; + if (!date) date = new Date(); + let iHour = date.getHours(); + let iDay = date.getDate(); + let iMonth = date.getMonth() + 1; + for (let i = 0; i < sFormat.length; i++) { + let ch; + switch ((ch = sFormat.charAt(i))) { + case 'a': + sDate += (iHour < 12 ? "am" : "pm"); + break; + case 'd': + sDate += ('0' + iDay).slice(-2); + break; + case 'D': + sDate += Usr.asDays[date.getDay()].substr(0, 3); + break; + case 'F': + sDate += Usr.asMonths[iMonth - 1]; + break; + case 'g': + sDate += (!iHour ? 12 : (iHour > 12 ? iHour - 12 : iHour)); + break; + case 'h': + sDate += iHour; + break; + case 'H': + sDate += ('0' + iHour).slice(-2); + break; + case 'i': + sDate += ('0' + date.getMinutes()).slice(-2); + break; + case 'j': + sDate += iDay; + break; + case 'l': + sDate += Usr.asDays[date.getDay()]; + break; + case 'm': + sDate += ('0' + iMonth).slice(-2); + break; + case 'M': + sDate += Usr.asMonths[iMonth - 1].substr(0, 3); + break; + case 'n': + sDate += iMonth; + break; + case 's': + sDate += ('0' + date.getSeconds()).slice(-2); + break; + case 'y': + sDate += ("" + date.getFullYear()).slice(-2); + break; + case 'Y': + sDate += date.getFullYear(); + break; + default: + sDate += ch; + break; + } + } + return sDate; + } + + /** + * defineBitFields(bfs) + * + * Prepares a bit field definition for use with getBitField() and setBitField(); eg: + * + * let bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3}); + * + * 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). + * + * Usr.setBitField(bfs.num, n, 1); + * + * The above set bit field "bfs.num" in numeric variable "n" to the value 1. + * + * @param {Object} bfs + * @return {BitFields} + */ + static defineBitFields(bfs) + { + let bit = 0; + for (let f in bfs) { + let width = bfs[f]; + let mask = ((1 << width) - 1) << bit; + bfs[f] = {mask: mask, shift: bit}; + bit += width; + } + return bfs; + } + + /** + * initBitFields(bfs, ...) + * + * @param {BitFields} bfs + * @param {...number} var_args + * @return {number} a value containing all supplied bit fields + */ + static initBitFields(bfs, var_args) + { + let v = 0, i = 1; + for (let f in bfs) { + if (i >= arguments.length) break; + v = Usr.setBitField(bfs[f], v, arguments[i++]); + } + return v; + } + + /** + * getBitField(bf, v) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @return {number} the value of the bit field in v defined by bf + */ + static getBitField(bf, v) + { + return (v & bf.mask) >> bf.shift; + } + + /** + * setBitField(bf, v, n) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @param {number} n is a value to store in v in the bit field defined by bf + * @return {number} updated v + */ + static setBitField(bf, v, n) + { + return (v & ~bf.mask) | ((n << bf.shift) & bf.mask); + } + + /** + * indexOf(a, t, i) + * + * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it. + * + * @param {Array} a + * @param {*} t + * @param {number} [i] + * @returns {number} + */ + static indexOf(a, t, i) + { + if (Array.prototype.indexOf) { + return a.indexOf(t, i); + } + i = i || 0; + if (i < 0) i += a.length; + if (i < 0) i = 0; + for (let n = a.length; i < n; i++) { + if (i in a && a[i] === t) return i; + } + return -1; + } +} + +Usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; +Usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; +Usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +/** + * getTime() + * + * @return {number} the current time, in milliseconds + */ +Usr.getTime = Date.now || function() { return +new Date(); }; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2018 + */ + + +/* + * According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties + * and functions of JavaScript-in-the-Browser: + * + * Property Description + * --- + * Infinity A numeric value that represents positive/negative infinity + * NaN "Not-a-Number" value + * undefined Indicates that a variable has not been assigned a value + * + * Function Description + * --- + * decodeURI() Decodes a URI + * decodeURIComponent() Decodes a URI component + * encodeURI() Encodes a URI + * encodeURIComponent() Encodes a URI component + * escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead + * eval() Evaluates a string and executes it as if it was script code + * isFinite() Determines whether a value is a finite, legal number + * isNaN() Determines whether a value is an illegal number + * Number() Converts an object's value to a number + * parseFloat() Parses a string and returns a floating point number + * parseInt() Parses a string and returns an integer + * String() Converts an object's value to a string + * unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead + * + * And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions + * of the *window* object. + * + * Property Description + * --- + * closed Returns a Boolean value indicating whether a window has been closed or not + * defaultStatus Sets or returns the default text in the statusbar of a window + * document Returns the Document object for the window (See Document object) + * frames Returns an array of all the frames (including iframes) in the current window + * history Returns the History object for the window (See History object) + * innerHeight Returns the inner height of a window's content area + * innerWidth Returns the inner width of a window's content area + * length Returns the number of frames (including iframes) in a window + * location Returns the Location object for the window (See Location object) + * name Sets or returns the name of a window + * navigator Returns the Navigator object for the window (See Navigator object) + * opener Returns a reference to the window that created the window + * outerHeight Returns the outer height of a window, including toolbars/scrollbars + * outerWidth Returns the outer width of a window, including toolbars/scrollbars + * pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window + * pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window + * parent Returns the parent window of the current window + * screen Returns the Screen object for the window (See Screen object) + * screenLeft Returns the x coordinate of the window relative to the screen + * screenTop Returns the y coordinate of the window relative to the screen + * screenX Returns the x coordinate of the window relative to the screen + * screenY Returns the y coordinate of the window relative to the screen + * self Returns the current window + * status Sets or returns the text in the statusbar of a window + * top Returns the topmost browser window + * + * Method Description + * --- + * alert() Displays an alert box with a message and an OK button + * atob() Decodes a base-64 encoded string + * blur() Removes focus from the current window + * btoa() Encodes a string in base-64 + * clearInterval() Clears a timer set with setInterval() + * clearTimeout() Clears a timer set with setTimeout() + * close() Closes the current window + * confirm() Displays a dialog box with a message and an OK and a Cancel button + * createPopup() Creates a pop-up window + * focus() Sets focus to the current window + * moveBy() Moves a window relative to its current position + * moveTo() Moves a window to the specified position + * open() Opens a new browser window + * print() Prints the content of the current window + * prompt() Displays a dialog box that prompts the visitor for input + * resizeBy() Resizes the window by the specified pixels + * resizeTo() Resizes the window to the specified width and height + * scroll() This method has been replaced by the scrollTo() method. + * scrollBy() Scrolls the content by the specified number of pixels + * scrollTo() Scrolls the content to the specified coordinates + * setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds) + * setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds + * stop() Stops the window from loading + */ + +class Web { + /** + * log(s, type) + * + * For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line) + * for Component.log() to display anything. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + Component.log(s, type); + } + + /** + * notice(s, fPrintOnly, id) + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + */ + static notice(s, fPrintOnly, id) + { + Component.notice(s, fPrintOnly, id); + } + + /** + * alertUser(sMessage) + * + * NOTE: Legacy function for older modules (eg, DiskDump); see Component.alertUser(). + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Web.log(sMessage); + } + } + + /** + * getResource(sURL, type, fAsync, done, progress) + * + * Request the specified resource (sURL), and once the request is complete, notify done(). + * + * If fAsync is true, a done() callback should ALWAYS be supplied; otherwise, you'll have no + * idea when the request is complete or what the response was. done() is passed three parameters: + * + * done(sURL, resource, nErrorCode) + * + * If nErrorCode is zero, resource should contain the requested data; otherwise, an error occurred. + * + * If type is set to a string, that string can be used to control the response format; + * by default, the response format is plain text, but you can specify "arraybuffer" to request arbitrary + * binary data, in which case the returned resource will be a ArrayBuffer rather than a string. + * + * @param {string} sURL + * @param {string|Object|null} [type] (object for POST request, otherwise type of GET request) + * @param {boolean} [fAsync] is true for an asynchronous request; false otherwise (MUST be set for IE) + * @param {function(string,string,number)} [done] + * @param {function(number)} [progress] + * @return {Array|null} Array containing [resource, nErrorCode], or null if no response available (yet) + */ + static getResource(sURL, type = "text", fAsync = false, done, progress) + { + let nErrorCode = 0, resource = null, response = null; + + if (typeof resources == 'object' && (resource = resources[sURL])) { + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + } + else if (fAsync && typeof resources == 'function') { + resources(sURL, function(resource, nErrorCode) + { + if (done) done(sURL, resource, nErrorCode); + }); + return response; + } + + if (Web.getHost() == "pcjs:8088" || NODE) { + /* + * The larger resources that I've put on archive.pcjs.org are assumed to also be available locally + * whenever the hostname is "pcjs" (or NODE is true); otherwise, use "localhost" when debugging locally. + * + * NOTE: http://archive.pcjs.org is currently redirected to https://s3-us-west-2.amazonaws.com/archive.pcjs.org + */ + sURL = sURL.replace(/^(http:\/\/archive\.pcjs\.org|https:\/\/[a-z0-9-]+\.amazonaws\.com\/archive\.pcjs\.org)(\/.*)\/([^\/]*)$/, "$2/archive/$3"); + sURL = sURL.replace(/^https:\/\/jeffpar\.github\.io\/(pcjs-[a-z]+|private-[a-z]+)\/(.*)$/, "/$1/$2"); + } + else { + /* + * TODO: Perhaps it's time for our code in netlib.js to finally add support for HTTPS; for now + * though, it's just as well that the NODE environment assumes all resources are available locally. + */ + sURL = sURL.replace(/^\/(pcjs-[a-z]+|private-[a-z]+)\//, "https://jeffpar.github.io/$1/"); + } + + + let request = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP")); + let fArrayBuffer = false, fXHR2 = (typeof request.responseType === 'string'); + + let callback = function() { + if (request.readyState !== 4) { + if (progress) progress(1); + return null; + } + /* + * The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple + * times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when + * debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests + * pending, any debugging activity means most of them simply get dropped on floor, so what may actually be + * happening are mis-notifications rather than redundant notifications. + * + * request.onreadystatechange = undefined; + */ + /* + * If the request failed due to, say, a CORS policy denial; eg: + * + * Failed to load http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img: + * Redirect from 'http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img' to + * 'http://www.allbootdisks.com/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. + * Origin 'http://pcjs:8088' is therefore not allowed access. + * + * and our request type was "arraybuffer", attempting to access responseText may trigger an exception; eg: + * + * Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's + * 'responseType' is '' or 'text' (was 'arraybuffer'). + * + * We could tiptoe around these potential landmines, but the safest thing to do is wrap this code with try/catch. + */ + try { + resource = fArrayBuffer? request.response : request.responseText; + } catch(err) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + ") exception: " + err.message); + } + /* + * The normal "success" case is a non-null resource and an HTTP status code of 200, but when loading files from the + * local file system (ie, when using the "file:" protocol), we have to be a bit more flexible. + */ + if (resource != null && (request.status == 200 || !request.status && resource.length && Web.getHostProtocol() == "file:")) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + "): returned " + resource.length + " bytes"); + } + else { + nErrorCode = request.status || -1; + Web.log("xmlHTTPRequest(" + sURL + "): error code " + nErrorCode); + } + if (progress) progress(2); + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + }; + + if (fAsync) { + request.onreadystatechange = callback; + } + + if (progress) progress(0); + + if (type && typeof type == "object") { + let sPost = ""; + for (let p in type) { + if (!type.hasOwnProperty(p)) continue; + if (sPost) sPost += "&"; + sPost += p + '=' + encodeURIComponent(type[p]); + } + sPost = sPost.replace(/%20/g, '+'); + if (MAXDEBUG) Web.log("Web.getResource(POST " + sURL + "): " + sPost.length + " bytes"); + request.open("POST", sURL, fAsync); + request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + request.send(sPost); + } else { + if (MAXDEBUG) Web.log("Web.getResource(GET " + sURL + ")"); + request.open("GET", sURL, fAsync); + if (type == "arraybuffer") { + if (fXHR2) { + fArrayBuffer = true; + request.responseType = type; + } else { + request.overrideMimeType("text/plain; charset=x-user-defined"); + } + } + request.send(); + } + + if (!fAsync) { + request.readyState = 4; // this may already be set for synchronous requests, but I don't want to take any chances + response = callback(); + } + return response; + } + + /** + * parseMemoryResource(sURL, sData) + * + * This converts a variety of JSON-style data streams into an Object with the following properties: + * + * aBytes + * aSymbols + * addrLoad + * addrExec + * + * If the source data contains a 'bytes' array, it's passed through to 'aBytes'; alternatively, if + * it contains a 'words' array, the values are converted from 16-bit to 8-bit and stored in 'aBytes', + * and if it contains a 'longs' array, the values are converted from 32-bit longs into bytes and + * stored in 'aBytes'. + * + * Alternatively, if the source data contains a 'data' array, we simply pass that through to the output + * object as: + * + * aData + * + * @param {string} sURL + * @param {string} sData + * @return {Object|null} (resource) + */ + static parseMemoryResource(sURL, sData) + { + let i; + let resource = { + aBytes: null, + aSymbols: null, + addrLoad: null, + addrExec: null + }; + + if (sData.charAt(0) == "[" || sData.charAt(0) == "{") { + try { + let a, ib, data; + + if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"... + /* + * Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid, + * but more recent server configs now display a somewhat friendlier HTML error page. The downside, + * however, is that the original error has been buried, and we've received "data" that isn't actually + * tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message. + */ + throw new Error(sData); + } + + /* + * TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to + * eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in + * IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks + * (perhaps one track per chunk, using regular expressions) and then manually re-assemble it. + * + * However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")") + * is a much easier fix. The only drawback is that we must first quote any unquoted property names + * and remove any comments, because while eval() was cool with them, JSON.parse() is more particular; + * the following RegExp replacements take care of those requirements. + * + * The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and + * while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified), + * I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement + * for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect + * any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so: + * + * ["unrecognized disk path: test.img"] + */ + if (sData.indexOf("0x") < 0 && sData.indexOf("0o") < 0 && sData.substr(0, 2) != '["') { + data = JSON.parse(sData.replace(/([a-z]+):/gm, '"$1":').replace(/\/\/[^\n]*/gm, "")); + } else { + data = eval("(" + sData + ")"); + } + + resource.addrLoad = data['load']; + resource.addrExec = data['exec']; + + if (a = data['bytes']) { + resource.aBytes = a; + } + else if (a = data['words']) { + /* + * Convert all words into bytes + */ + resource.aBytes = new Array(a.length * 2); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + + } + } + else if (a = data['longs']) { + /* + * Convert all dwords (longs) into bytes + */ + resource.aBytes = new Array(a.length * 4); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + resource.aBytes[ib++] = (a[i] >> 16) & 0xff; + resource.aBytes[ib++] = (a[i] >> 24) & 0xff; + } + } + else if (a = data['data']) { + resource.aData = a; + } + else { + resource.aBytes = data; + } + + if (resource.aBytes) { + if (!resource.aBytes.length) { + Component.error("Empty resource: " + sURL); + resource = null; + } + else if (resource.aBytes.length == 1) { + Component.error(resource.aBytes[0]); + resource = null; + } + } + resource.aSymbols = data['symbols']; + + } catch (e) { + Component.error("Resource data error (" + sURL + "): " + e.message); + resource = null; + } + } + else { + /* + * Parse the data manually; we assume it's a series of hex byte-values separated by whitespace. + */ + let ab = []; + let sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, ""); + let asHexData = sHexData.split(" "); + for (i = 0; i < asHexData.length; i++) { + let n = parseInt(asHexData[i], 16); + if (isNaN(n)) { + Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")"); + break; + } + ab.push(n & 0xff); + } + if (i == asHexData.length) resource.aBytes = ab; + } + return resource; + } + + /** + * sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + * + * Send a report (eg, bug report) to the server. + * + * @param {string} sApp (eg, "PCjs") + * @param {string} sVer (eg, "1.02") + * @param {string} sURL (eg, "/devices/pc/machine/5150/mda/64kb/machine.xml") + * @param {string} sUser (ie, the user key, if any) + * @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.* + * @param {string} sReport (eg, unparsed state data) + * @param {string} [sHostName] (default is SITEURL) + */ + static sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + { + let dataPost = {}; + dataPost[ReportAPI.QUERY.APP] = sApp; + dataPost[ReportAPI.QUERY.VER] = sVer; + dataPost[ReportAPI.QUERY.URL] = sURL; + dataPost[ReportAPI.QUERY.USER] = sUser; + dataPost[ReportAPI.QUERY.TYPE] = sType; + dataPost[ReportAPI.QUERY.DATA] = sReport; + let sReportURL = (sHostName? sHostName : SITEURL) + ReportAPI.ENDPOINT; + Web.getResource(sReportURL, dataPost, true); + } + + /** + * getHost() + * + * This is like getHostName() but with the port number, if any. + * + * @return {string} + */ + static getHost() + { + return (window? window.location.host : "localhost"); + } + + /** + * getHostName() + * + * @return {string} + */ + static getHostName() + { + return (window? window.location.hostname : "localhost"); + } + + /** + * getHostOrigin() + * + * This could also be implemented with window.location.origin, but that wasn't originally available in all browsers. + * + * @return {string} + */ + static getHostOrigin() + { + return (window? window.location.protocol + "//" + window.location.host : SITEURL); + } + + /** + * getHostProtocol() + * + * @return {string} + */ + static getHostProtocol() + { + return (window? window.location.protocol : "file:"); + } + + /** + * getHostURL() + * + * @return {string|null} + */ + static getHostURL() + { + return (window? window.location.href : null); + } + + /** + * getUserAgent() + * + * @return {string} + */ + static getUserAgent() + { + return (window? window.navigator.userAgent : ""); + } + + /** + * hasLocalStorage + * + * true if localStorage support exists, is enabled, and works; false otherwise + * + * @return {boolean} + */ + static hasLocalStorage() + { + if (Web.fLocalStorage == null) { + let f = false; + if (window) { + try { + window.localStorage.setItem(Web.sLocalStorageTest, Web.sLocalStorageTest); + f = (window.localStorage.getItem(Web.sLocalStorageTest) == Web.sLocalStorageTest); + window.localStorage.removeItem(Web.sLocalStorageTest); + } catch (e) { + Web.logLocalStorageError(e); + f = false; + } + } + Web.fLocalStorage = f; + } + return Web.fLocalStorage; + } + + /** + * logLocalStorageError(e) + * + * @param {Error} e is an exception + */ + static logLocalStorageError(e) + { + Web.log(e.message, "localStorage error"); + } + + /** + * getLocalStorageItem(sKey) + * + * Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available + * + * @param {string} sKey + * @return {string|null|undefined} sValue + */ + static getLocalStorageItem(sKey) + { + let sValue; + if (window) { + try { + sValue = window.localStorage.getItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + return sValue; + } + + /** + * setLocalStorageItem(sKey, sValue) + * + * @param {string} sKey + * @param {string} sValue + * @return {boolean} true if localStorage is available, false if not + */ + static setLocalStorageItem(sKey, sValue) + { + try { + window.localStorage.setItem(sKey, sValue); + return true; + } catch (e) { + Web.logLocalStorageError(e); + } + return false; + } + + /** + * removeLocalStorageItem(sKey) + * + * @param {string} sKey + */ + static removeLocalStorageItem(sKey) + { + try { + window.localStorage.removeItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + + /** + * getLocalStorageKeys() + * + * @return {Array} + */ + static getLocalStorageKeys() + { + let a = []; + try { + for (let i = 0, c = window.localStorage.length; i < c; i++) { + a.push(window.localStorage.key(i)); + } + } catch (e) { + Web.logLocalStorageError(e); + } + return a; + } + + /** + * reloadPage() + */ + static reloadPage() + { + if (window) window.location.reload(); + } + + /** + * isUserAgent(s) + * + * Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can + * use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE"). + * + * 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating + * the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx); + * they say "public websites should rely on feature detection, rather than browser detection, in order to design + * their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent + * that tries to fool apps into thinking the browser is more like WebKit or Gecko: + * + * Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko + * + * That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained + * some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because + * that's a sledgehammer solution which restores the old user-agent string but also disables other features like + * HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or + * "Trident". + * + * UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't + * any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll + * need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture. + * + * @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values + * @return {boolean} is true if the string was found, false if not + */ + static isUserAgent(s) + { + if (window) { + let userAgent = Web.getUserAgent(); + /* + * Here's one case where we have to be careful with Component, because when isUserAgent() is called by + * the init code below, component.js hasn't been loaded yet. The simple solution for now is to remove the call. + * + * Web.log("agent: " + userAgent); + * + * And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure + * Compiler (v20130823) failing to detect the entire expression as a boolean. + */ + return s == "iOS" && !!userAgent.match(/(iPod|iPhone|iPad)/) && !!userAgent.match(/AppleWebKit/) || s == "MSIE" && !!userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0); + } + return false; + } + + /** + * isMobile(sDevice) + * + * Checks the URL for a "mobile" parameter, and failing that, checks the browser's user-agent string for the + * substring "Mobi", as per Mozilla recommendation: + * + * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent + * + * @param {string} [sDevice] (eg, "iPad" to check for iPad, or "!iPad" to specifically exclude it) + * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not + */ + static isMobile(sDevice) + { + let sMobile = Web.getURLParm("mobile"); + if (sMobile) return sMobile == "true"; + if (Web.isUserAgent("Mobi")) { + if (!sDevice) return true; + let fInvert = sDevice[0] == '!'; + if (fInvert) sDevice = sDevice.substr(1); + return Web.isUserAgent(sDevice) != fInvert; + } + return false; + } + + /** + * findProperty(obj, sProp, sSuffix) + * + * If both sProp and sSuffix are set, then any browser-specific prefixes are inserted between sProp and sSuffix, + * and if a match is found, it is returned without sProp. + * + * For example, if findProperty(document, 'on', 'fullscreenchange') discovers that 'onwebkitfullscreenchange' exists, + * it will return 'webkitfullscreenchange', in preparation for an addEventListener() call. + * + * More commonly, sSuffix is not used, so whatever property is found is returned as-is. + * + * @param {Object|null|undefined} obj + * @param {string} sProp + * @param {string} [sSuffix] + * @return {string|null} + */ + static findProperty(obj, sProp, sSuffix) + { + if (obj) { + for (let i = 0; i < Web.asBrowserPrefixes.length; i++) { + let sName = Web.asBrowserPrefixes[i]; + if (sSuffix) { + sName += sSuffix; + let sEvent = sProp + sName; + if (sEvent in obj) return sName; + } else { + if (!sName) { + sName = sProp[0]; + } else { + sName += sProp[0].toUpperCase(); + } + sName += sProp.substr(1); + if (sName in obj) return sName; + } + } + } + return null; + } + + /** + * getURLParm(sParm) + * + * First looks for sParm exactly as specified, then looks for the lower-case version. + * + * @param {string} sParm + * @return {string|undefined} + */ + static getURLParm(sParm) + { + if (!Web.parmsURL) { + Web.parmsURL = Web.parseURLParms(); + } + return Web.parmsURL[sParm] || Web.parmsURL[sParm.toLowerCase()]; + } + + /** + * parseURLParms(sParms) + * + * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?') + * @return {Object} containing properties for each parameter found + */ + static parseURLParms(sParms) + { + let aParms = {}; + if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used + if (!sParms) { + /* + * Note that window.location.href returns the entire URL, whereas window.location.search + * returns only the parameters, if any (starting with the '?', which we skip over with a substr() call). + */ + sParms = window.location.search.substr(1); + } + let match; + let pl = /\+/g; // RegExp for replacing addition symbol with a space + let search = /([^&=]+)=?([^&]*)/g; + let decode = function(s) + { + return decodeURIComponent(s.replace(pl, " ")); + }; + + while ((match = search.exec(sParms))) { + aParms[decode(match[1])] = decode(match[2]); + } + } + return aParms; + } + + /** + * downloadFile(sData, sType, fBase64, sFileName) + * + * @param {string} sData + * @param {string} sType + * @param {boolean} [fBase64] + * @param {string} [sFileName] + */ + static downloadFile(sData, sType, fBase64, sFileName) + { + let link = null, sAlert; + let sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ","; + + if (typeof sData != 'string' + && typeof Blob == 'function' && typeof URL != 'undefined' && URL && typeof URL.createObjectURL == 'function') { + let blob = new Blob([sData], { type: 'application/octet-stream' }); + sURI = URL.createObjectURL(blob); + } + else if (!Web.isUserAgent("Firefox")) { + sURI += (fBase64? sData : encodeURI(sData)); + } else { + sURI += (fBase64? sData : encodeURIComponent(sData)); + } + if (sFileName) { + link = document.createElement('a'); + if (typeof link.download != 'string') link = null; + } + if (link) { + link.href = sURI; + link.download = sFileName; + document.body.appendChild(link); // Firefox allegedly requires the link to be in the body + link.click(); + document.body.removeChild(link); + sAlert = 'Check your Downloads folder for ' + sFileName + '.'; + // if (Web.isUserAgent("Chrome")) { + // sAlert += '\n\nIn Chrome, after clicking OK, you may ALSO have to select the "Window" menu, choose "Downloads", and then locate this download and select "Keep".'; + // sAlert += '\n\nThis is part of Chrome\'s "Security By Jumping Through Extra Hoops" technology, which is much easier for Google to implement than actually checking for something malicious.'; + // sAlert += '\n\nAnd for the record, there is nothing malicious on the PCjs website.'; + // } + } else { + window.open(sURI); + sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.'; + } + return sAlert; + } + + /** + * onCountRepeat(n, fnRepeat, fnComplete, msDelay) + * + * Call fnRepeat() n times with an msDelay millisecond delay between calls, + * then call fnComplete() when n has been exhausted OR fnRepeat() returns false. + * + * @param {number} n + * @param {function()} fnRepeat + * @param {function()} fnComplete + * @param {number} [msDelay] + */ + static onCountRepeat(n, fnRepeat, fnComplete, msDelay) + { + let fnTimeout = function doCountRepeat() + { + n -= 1; + if (n >= 0) { + if (!fnRepeat()) n = 0; + } + if (n > 0) { + setTimeout(fnTimeout, msDelay || 0); + return; + } + fnComplete(); + }; + fnTimeout(); + } + + /** + * onClickRepeat(e, msDelay, msRepeat, fn) + * + * Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter, + * as long as HTML control Object e has an active "down" event and fn() returns true. + * + * @param {Object} e + * @param {number} msDelay + * @param {number} msRepeat + * @param {function(boolean)} fn is passed false on the first call, true on all repeated calls + */ + static onClickRepeat(e, msDelay, msRepeat, fn) + { + let ms = 0, timer = null, fIgnoreMouseEvents = false; + + let fnRepeat = function doClickRepeat() + { + if (fn(ms === msRepeat)) { + timer = setTimeout(fnRepeat, ms); + ms = msRepeat; + } + }; + e.onmousedown = function() + { + // Web.log("onMouseDown()"); + if (!fIgnoreMouseEvents) { + if (!timer) { + ms = msDelay; + fnRepeat(); + } + } + }; + e.ontouchstart = function() + { + // Web.log("onTouchStart()"); + if (!timer) { + ms = msDelay; + fnRepeat(); + } + }; + e.onmouseup = e.onmouseout = function() + { + // Web.log("onMouseUp()/onMouseOut()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + e.ontouchend = e.ontouchcancel = function() + { + // Web.log("onTouchEnd()/onTouchCancel()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + /* + * Devices that generate ontouch* events ALSO generate onmouse* events, + * and generally do so immediately after all the touch events are complete, + * so unless we want double the action, we need to ignore mouse events. + */ + fIgnoreMouseEvents = true; + }; + } + + /** + * onPageEvent(sName, fn) + * + * For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but + * instead use Web.onInit(), Web.onShow(), and Web.onExit(), respectively. + * + * The only components that should still use onPageEvent() are THIS component (see the bottom of this file) + * and components that need to capture other events (eg, the 'onresize' event in the Video component). + * + * This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers + * for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'], + * etc, themselves. However, that's less of a concern now, because assuming everyone else is now using + * onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one. + * + * NOTE: It's risky to refer to obscure event handlers with "dot" names, because the Closure Compiler may + * erroneously replace them (eg, window.onpageshow is a good example). + * + * @param {string} sFunc + * @param {function()} fn + */ + static onPageEvent(sFunc, fn) + { + if (window) { + let fnPrev = window[sFunc]; + if (typeof fnPrev !== 'function') { + window[sFunc] = fn; + } else { + /* + * TODO: Determine whether there's any value in receiving/sending the Event object that the + * browser provides when it generates the original event. + */ + window[sFunc] = function onWindowEvent() + { + if (fnPrev) fnPrev(); + fn(); + }; + } + } + }; + + /** + * onInit(fn) + * + * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler. + * + * @param {function()} fn + */ + static onInit(fn) + { + Web.aPageEventHandlers['init'].push(fn); + }; + + /** + * onShow(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler. + */ + static onShow(fn) + { + Web.aPageEventHandlers['show'].push(fn); + }; + + /** + * onError(sMessage) + * + * @param {string} sMessage + */ + static onError(sMessage) + { + Web.notice(sMessage + "\n\nIf it happens again, please send the URL to support@pcjs.org. Thanks."); + } + + /** + * onExit(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler. + */ + static onExit(fn) + { + Web.aPageEventHandlers['exit'].push(fn); + }; + + /** + * doPageEvent(afn) + * + * @param {Array.} afn + */ + static doPageEvent(afn) + { + if (Web.fPageEventsEnabled) { + try { + for (let i = 0; i < afn.length; i++) { + afn[i](); + } + } catch (e) { + Web.onError("An unexpected error occurred: " + e.message); + } + } + }; + + /** + * enablePageEvents(fEnable) + * + * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default) + */ + static enablePageEvents(fEnable) + { + if (!Web.fPageEventsEnabled && fEnable) { + Web.fPageEventsEnabled = true; + if (Web.fPageLoaded) Web.sendPageEvent('init'); + if (Web.fPageShowed) Web.sendPageEvent('show'); + return; + } + Web.fPageEventsEnabled = fEnable; + } + + /** + * sendPageEvent(sEvent) + * + * This allows us to manually trigger page events. + * + * @param {string} sEvent (one of 'init', 'show' or 'exit') + */ + static sendPageEvent(sEvent) + { + if (Web.aPageEventHandlers[sEvent]) { + Web.doPageEvent(Web.aPageEventHandlers[sEvent]); + } + } +} + +Web.parmsURL = null; // initialized on first call to parseURLParms() + +Web.aPageEventHandlers = { + 'init': [], // list of window 'onload' handlers + 'show': [], // list of window 'onpageshow' handlers + 'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible) +}; + +Web.asBrowserPrefixes = ['', 'moz', 'ms', 'webkit']; + +Web.fPageLoaded = false; // set once the page's first 'onload' event has occurred +Web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred +Web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents() + +/** + * fLocalStorage + * + * true if localStorage support exists, is enabled, and works; "falsey" otherwise + * + * @type {boolean|null} + */ +Web.fLocalStorage = null; + +/** + * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This isn't cutting it. + * + * @const {string} + */ +Web.sLocalStorageTest = "PCjs.localStorage"; + +Web.onPageEvent('onload', function onPageLoad() { + Web.fPageLoaded = true; + Web.doPageEvent(Web.aPageEventHandlers['init']); +}); + +Web.onPageEvent('onpageshow', function onPageShow() { + Web.fPageShowed = true; + Web.doPageEvent(Web.aPageEventHandlers['show']); +}); + +Web.onPageEvent(Web.isUserAgent("iOS")? 'onpagehide' : (Web.isUserAgent("Opera")? 'onunload' : 'onbeforeunload'), function onPageUnload() { + Web.doPageEvent(Web.aPageEventHandlers['exit']); +}); + +/* + * If this is DEBUG (eg, un-COMPILED) code, then allow the user to override DEBUG with a "debug=false" embedded in + * the URL; note that the Closure Compiler won't let us alter the DEBUG variable, because it's defined as a @define, which + * implies @const as well, so we must resort to modifying it indirectly, using the global window object. + * + * TODO: Consider yet another embedXXX() parameter that would also allow DEBUG to be turned off on a page-by-page basis; + * it's low priority, because it would only affect machines that explicitly request un-COMPILED code, and there are very + * few such machines (eg, /_posts/2015-01-17-pcjs-uncompiled.md). + * + * Deal with Web.getURLParm("backtrack") in /modules/pcx86/lib/defines.js at the same time. + */ +if (DEBUG && window) { + let sDebug = Web.getURLParm("debug"); + if (sDebug == "false") { + window['DEBUG'] = false; + } +} + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2018 + */ + +/* + * All PCjs components now use JSDoc types, primarily so that Google's Closure Compiler will compile + * everything with zero warnings when ADVANCED_OPTIMIZATIONS are enabled. For more information about + * the JSDoc types supported by the Closure Compiler: + * + * https://developers.google.com/closure/compiler/docs/js-for-compiler#types + * + * I also attempted to validate this code with JSLint, but it complained too much; eg, it didn't like + * "while (true)", a tried and "true" programming convention for decades, and it wanted me to replace + * all "++" and "--" operators with "+= 1" and "-= 1", use "(s || '')" instead of "(s? s : '')", etc. + * + * I prefer sticking with traditional C-style idioms, in part because they are more portable. That + * does NOT mean I'm trying to write "portable JavaScript," but some of this code was ported from C code + * I'd written long ago, so portability is good, and I'm not going to throw that away if there's no need. + * + * UPDATE: I've since switched from JSLint to JSHint, which seems to have more reasonable defaults. + * And for new code, I have adopted some popular JavaScript idioms, like "(s || '')", although the need + * for those kinds of expressions will be reduced as I also start adopting some ES6 features, like + * default parameters. + */ + + + +/** + * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, + * it deters us from defining named properties on our components; eg: + * + * this['exports'] = {...} + * + * results in an error: + * + * Cannot do '[]' access on a struct + * + * So, in order to define 'exports', we must override the @struct assumption by annotating + * the class as @unrestricted (or @dict). Note that this must be done both here and in the + * subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference* + * the named property either. + * + * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to + * define every single property the class uses in its constructor, which results in a fair + * bit of redundant initialization, since many properties aren't (and don't need to be) fully + * initialized until the appropriate init(), reset(), restore(), etc. function is called. + * + * The upside, however, may be that since the structure of the class is completely defined by + * the constructor, JavaScript engines may be able to optimize and run more efficiently. + * + * @unrestricted + */ +class Component { + /** + * Component(type, parms, bitsMessage) + * + * A Component object requires: + * + * type: a user-defined type name (eg, "CPU") + * + * and accepts any or all of the following (parms) properties: + * + * id: component ID (default is "") + * name: component name (default is ""; if blank, toString() will use the type name only) + * comment: component comment string (default is undefined) + * + * Component subclasses will usually have additional (parms) properties. + * + * @param {string} type + * @param {Object} [parms] + * @param {number} [bitsMessage] selects message(s) that the component wants to enable (default is 0) + */ + constructor(type, parms, bitsMessage) + { + this.type = type; + + if (!parms) parms = {'id': "", 'name': ""}; + + this.id = parms['id'] || ""; + this.name = parms['name']; + this.comment = parms['comment']; + this.parms = parms; + + /* + * The following Component properties need to be accessible by other machines and/or command scripts; + * well, OK, or we could have exported some new functions to walk the contents of these properties, as we + * did with findMachineComponent(), but this works just as well. + * + * Also, while the double-assignment looks silly (ie, using both dot and bracket property notation), it + * resolves a complaint from the Closure Compiler, because if we use ONLY bracket notation here, then the + * Compiler wants us to change all the other references to bracket notation as well. + */ + this.exports = this['exports'] = {}; + this.bindings = this['bindings'] = {}; + + let i = this.id.indexOf('.'); + if (i < 0) { + this.idMachine = "PCjs"; + this.idComponent = this.id; + } else { + this.idMachine = this.id.substr(0, i); + this.idComponent = this.id.substr(i + 1); + } + + /* + * Gather all the various component flags (booleans) into a single "flags" object, and encourage + * subclasses to do the same, to reduce the property clutter we have to wade through while debugging. + */ + this.flags = { + ready: false, + busy: false, + busyCancel: false, + initDone: false, + powered: false, + unloading: false, + error: false + }; + + this.fnReady = null; + this.clearError(); + this.bitsMessage = bitsMessage || 0; + + this.cmp = null; + this.bus = null; + this.cpu = null; + this.dbg = null; + + /* + * TODO: Consider adding another parameter to the Component() constructor that allows components to tell + * us if they support single or multiple instances per machine. For example, there can be multiple SerialPort + * components per machine, but only one CPU component (some machines also support an FPU, but that component + * is considered separate from the CPU). + * + * It's not critical, but it would help catch machine configuration errors; for example, a machine that mistakenly + * includes two CPU components may, aside from wasting memory, end up with odd side-effects, like unresponsive + * CPU controls. + */ + Component.add(this); + } + + /** + * Component.add(component) + * + * @param {Component} component + */ + static add(component) + { + /* + * This just generates a lot of useless noise, handy in the early days, not so much these days.... + * + * if (DEBUG) Component.log("Component.add(" + component.type + "," + component.id + ")"); + */ + Component.components.push(component); + } + + /** + * Component.addMachine(idMachine) + * + * @param {string} idMachine + */ + static addMachine(idMachine) + { + Component.machines[idMachine] = {}; + } + + /** + * Component.getMachines() + * + * @return {Array.} + */ + static getMachines() + { + return Object.keys(Component.machines); + } + + /** + * Component.addMachineResource(idMachine, sName, data) + * + * @param {string} idMachine + * @param {string|null} sName (name of the resource) + * @param {*} data + */ + static addMachineResource(idMachine, sName, data) + { + /* + * I used to assert(Component.machines[idMachine]), but when we're running as a Node app, embed.js is not used, + * so addMachine() is never called, so resources do not need to be recorded. + */ + if (Component.machines[idMachine] && sName) { + Component.machines[idMachine][sName] = data; + } + } + + /** + * Component.getMachineResources(idMachine) + * + * @param {string} idMachine + * @return {Object|undefined} + */ + static getMachineResources(idMachine) + { + return Component.machines[idMachine]; + } + + /** + * Component.getTime() + * + * @return {number} the current time, in milliseconds + */ + static getTime() + { + return Date.now() || +new Date(); + } + + /** + * Component.log(s, type) + * + * For diagnostic output only. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + if (!COMPILED) { + if (s) { + let sElapsed = "", sMsg = (type? (type + ": ") : "") + s; + if (typeof Usr != "undefined") { + if (Component.msStart === undefined) { + Component.msStart = Component.getTime(); + } + sElapsed = (Component.getTime() - Component.msStart) + "ms: "; + } + sMsg = sMsg.replace(/\r/g, '\\r').replace(/\n/g, ' '); + if (window && window.console) console.log(sElapsed + sMsg); + } + } + } + + /** + * Component.assert(f, s) + * + * Verifies conditions that must be true (for DEBUG builds only). + * + * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds. + * TODO: Add a task to the build process that "asserts" there are no instances of "assertion failure" in RELEASE builds. + * + * @param {boolean} f is the expression we are asserting to be true + * @param {string} [s] is description of the assertion on failure + */ + static assert(f, s) + { + if (DEBUG) { + if (!f) { + if (!s) s = "assertion failure"; + Component.log(s); + throw new Error(s); + } + } + } + + /** + * Component.print(s) + * + * Components that inherit from this class should use this.print(), rather than Component.print(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @this {Component} + * @param {string} s + */ + static print(s) + { + if (!COMPILED) { + let i = s.lastIndexOf('\n'); + if (i >= 0) { + Component.println(s.substr(0, i)); + s = s.substr(i + 1); + } + Component.printBuffer += s; + } + } + + /** + * Component.println(s, type, id) + * + * Components that inherit from this class should use this.println(), rather than Component.println(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + * @param {string} [id] is the caller's ID, if any + */ + static println(s, type, id) + { + if (!COMPILED) { + s = Component.printBuffer + (s || ""); + Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type); + Component.printBuffer = ""; + } + } + + /** + * Component.notice(s, fPrintOnly, id) + * + * notice() is like println() but implies a need for user notification, so we alert() as well. + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + * @return {boolean} + */ + static notice(s, fPrintOnly, id) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.NOTICE, id); + } + if (!fPrintOnly) Component.alertUser((id? (id + ": ") : "") + s); + return true; + } + + /** + * Component.warning(s) + * + * @param {string} s describes the warning + */ + static warning(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.WARNING); + } + Component.alertUser(s); + } + + /** + * Component.error(s) + * + * @param {string} s describes the error; an alert() is displayed as well + */ + static error(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.ERROR); + } + Component.alertUser(s); + } + + /** + * Component.alertUser(sMessage) + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Component.log(sMessage); + } + } + + /** + * Component.confirmUser(sPrompt) + * + * @param {string} sPrompt + * @returns {boolean} true if the user clicked OK, false if Cancel/Close + */ + static confirmUser(sPrompt) + { + let fResponse = false; + if (window) { + fResponse = window.confirm(sPrompt); + } + return fResponse; + } + + /** + * Component.promptUser() + * + * @param {string} sPrompt + * @param {string} [sDefault] + * @returns {string|null} + */ + static promptUser(sPrompt, sDefault) + { + let sResponse = null; + if (window) { + sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault); + } + return sResponse; + } + + /** + * Component.appendControl(control, sText) + * + * @param {Object} control + * @param {string} sText + */ + static appendControl(control, sText) + { + control.value += sText; + /* + * Prevent the + + +
    +
    + +
    +
    + + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + +
    +
    + +
    +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + ; + + + + + + + + + + + + + + + : + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + ; + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8088 + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + 1 + + + + + + + null + + + + + + 0 + + + + + + + -1 + + + + + + + -1 + + + + + + + -1 + + + + + + ,model:'',stepping:'',fpu:,cycles:,multiplier:,autoStart:,addrReset:,csStart:,csInterval:,csStop: + + + + + + + + + + + + + + + 8087 + + + + + + + + + + + + ,model:'',stepping:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + true + + + + + + + false + + + + + + {} + + + + + + + + + + + + + + + + + + + chipset + ,model:'',scaleTimers:,sw1:'',sw2:'',sound:,floppies:,monitor:'',dateRTC:'' + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + device + ,type:'',baudReceive:,baudTransmit:,autoMount:'' + + + + + + + + + + + + + + + + + + + + keyboard + ,model:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + parallel + ,adapter:,binding:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + + 0 + + + + + + + 0 + + + + + + + false + + + + + serial + ,adapter:,baudReceive:,baudTransmit:,binding:'',tabSize:,charBOL:,upperCase: + + + + + + + + + + + + + + + + + + + + + + + + + + testctl + ,binding:'',tests:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + 0.5 + + + + + + + + + + + mouse + ,adapter:,binding:'',type:'',scaleMouse:,serial:'' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fdc + ,autoMount:'',sortBy:'' + + + + + + + + + + + + + + + + + + + + + XT + + + + + hdc + ,drives:'',type:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + null + + + + + + + + + + + + + + + + + rom + ,addr:,size:,alias:,file:'',notify:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + null + + + + + + null + + + + + + true + + + + + ram + ,addr:,size:,file:'',load:,exec:,test: + + + + + + + + + + + + + + + + + + + + + null + + + + + + + 256 + + + + + + + 224 + + + + + + + black + + + + + + 0 + + + + + + 0 + + + + + + false + + + + + + 1bpp + + + + + + 0 + + + + + + 0 + + + + + + 1 + + + + + + 0 + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + false + + + + + + 1 + + + + + + 1 + + + + + + + 80 + + + + + + + 25 + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + 0 + + + + + + null + + + + + + 0 + + + + + + 60 + + + + + + 0.5 + + + + + video + + + ,model:'',mode:,screenWidth:,screenHeight:,screenColor:'',screenRotate:,bufferAddr:,bufferRAM:,bufferFormat:'',bufferCols:,bufferRows:,bufferBits:,bufferLeft:,bufferRotate:,memory:,switches:'',scale:,cellWidth:,cellHeight:,charCols:,charRows:,fontROM:'',fontColor:'',touchScreen:'',autoLock:,aspectRatio:,smoothing:,interruptRate:,refreshRate:,flicker: + + + + + + + + + + + + + + + 16 + + + + + + + + + + + + + + + + + debugger + ,base:,commands:'',messages:'' + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + true + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + computer + ,autoPower:,busWidth:,resume:'',state:'' + + + + +
    +
    + + + + + + + + + + <></> + + +
    <machine></machine>
    +
    + + + + diff --git a/versions/pcx86/1.71.5/document.css b/versions/pcx86/1.71.5/document.css new file mode 100644 index 0000000000..4281cab24f --- /dev/null +++ b/versions/pcx86/1.71.5/document.css @@ -0,0 +1,162 @@ +@CHARSET "UTF-8"; + +.page { + margin: 2% 2%; + padding: 2% 2%; + min-width: 30em; + overflow: auto; + font-size: large; + font-family: Helvetica, Arial, sans-serif; + background: #303030; + color: #ccc; + +} +.page-header { +} +.page-header-title { + text-align: center; + +} +.page a { + color: #7fc07f; + text-decoration: none; +} +a.footlink, a.paralink { + text-decoration: none; +} +a.footlink:link, a.paralink:link { + color: blue; +} +a.footlink:visited, a.paralink:visited { + color: blue; +} +.galleryitem { + float: left; + width: 200px; +} +.item { + float: left; + width: 2em; + text-indent: 1em; +} +.list { + margin-left: 3em; + text-indent: 0; + text-align: justify; +} +ul { + list-style: none; +} +div.pnumber { + float: left; + width: 2em; + text-indent: 1em; +} +div.pitem { + margin-left: 10em; +} +p.indent, .justified p { + text-indent: 2em; + text-align: justify; + line-height: 1.5em; +} +p.noindent { + text-indent: 0; + text-align: justify; +} +p.center, .center { + text-align: center; +} +li.para { + margin-top: 1em; + margin-bottom: 1em; +} +.left { + text-align: left; +} +.right { + text-align: right; +} +blockquote.tag { + font-size: small; + font-family: Monaco, Fixed, monospace; + margin-top: 0; + margin-bottom: 0; +} +.blockquote { + padding-left: 1em; + text-indent: 0; + text-align: justify; +} +.italics { + font-style: italic; +} +.medium { + font-size: medium; +} +.small { + font-size: x-small; +} +.smallcaps { + font-variant: small-caps; +} +.strike { + text-decoration: line-through; +} +.summation, .bracelist { + display: inline-block; + position: relative; + vertical-align: middle; + text-align: center; + margin-bottom: 0.5ex; + text-indent: 0; +} +.bracelist-symbol { + font-size: 3em; + vertical-align: -40%; +} +.summation .summation-lower, .summation .summation-upper, .bracelist-item { + display: block; + font-size: 75%; + text-align: center; +} +.summation .summation-upper { + margin-bottom: 0; + margin-left: 0.8ex; + font-style: italic; +} +.summation .summation-lower{ + margin-bottom: -0.6ex; + font-style: italic; +} +.summation .summation-symbol { + font-size: 2em; +} +p sup { + vertical-align: baseline; + position: relative; + bottom: .5em; + font-size: small; +} +p sub { + vertical-align: baseline; + position: relative; + bottom: -.5em; + font-size: small; +} +.footnote { + font-size: medium; + text-indent: 1em; + text-align: justify; + margin-top: .5em; +} +.image-right { + float: right; + margin-left: 1em; + margin-top: 1em; + margin-bottom: 1em; +} +.image-caption { + font-size: small; + text-align: center; +} diff --git a/versions/pcx86/1.71.5/document.xsl b/versions/pcx86/1.71.5/document.xsl new file mode 100644 index 0000000000..45d132c1d0 --- /dev/null +++ b/versions/pcx86/1.71.5/document.xsl @@ -0,0 +1,452 @@ + + + + + +]> + + + + + + + + + +

    +
    + + + + + + + +

    +
    + +

    +
    +
    +
    + + + + + + +
    +
    + + +
    + +   + + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + +

    +
    + + +

    +
    + + +

    +
    + + +
    +
    + + +
    +
    + + + + + + + + + + + + + + +
    +
    + + +
    +
    + + +
  • +
    + + +
    image
    +
    + + +
    +
    + + + + +
    {.}
    +
    + +
    {.}
    +
    +
    +
    + + + + + + + + + + < + > + + + + × + + ÷ + σ + + + + + + + + + + + + { + + + + + + + + + + [] + + + + +
    + +
    +
    + + + , and + + + + + MDY + + + + + + + + + + + + + + + + + + + + January + February + March + April + May + June + July + August + September + October + November + December + + + , + + + + + +

    + +
    +
    + + +
    + {.}
    +
    +
    +
    + + + +

    Timeline

    +
    + +

    +
    +
    + +
    +
    + + + + + + + + + +

    +
    + +
    +
    +
    + + + +

    People

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + +

    +
      + +
    +
    + + + + + + + + + + +
  • + +
  • +
    + + + +

    +
    +

    + +

    +
    +
    + + + + false + + + + + + [Original] + + + + + + + + + + [] + + +
    by
    + + +
    + [Source: + + + + + + + ] +
    +
    +
    + + + +

    Resources

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + + +

    +
    +
      + +
    +
    + + +
  • +
    + + + +

    +
    +
    + +
    +
    + + + +

    +
    + +
    + + + +

    +
    +
      + +
    +
    + + + + + +
      + +
    +
    + + + + +
  • +
    + +
  • +
    + +
  • +
    +
    +
    + + +
  • +
    + + + + + + + + + + +
    + < ="" + + ></> + ></> + /> + +
    +
    + +
    diff --git a/versions/pcx86/1.71.5/machine.xsl b/versions/pcx86/1.71.5/machine.xsl new file mode 100644 index 0000000000..3659ea2692 --- /dev/null +++ b/versions/pcx86/1.71.5/machine.xsl @@ -0,0 +1,61 @@ + + + + +]> + + + + + + + + + + + + + + + + + js + + + + + + PCjs + + + + +
    + +
    +
    + + + + + , + +
    +
    + + +
    + + + + + + + + + + +
    + +
    diff --git a/versions/pcx86/1.71.5/manifest.xsl b/versions/pcx86/1.71.5/manifest.xsl new file mode 100644 index 0000000000..3ca622658d --- /dev/null +++ b/versions/pcx86/1.71.5/manifest.xsl @@ -0,0 +1,247 @@ + + + + +]> + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Document Manifest

    +
    +
      + + + + None + + + + + + + + + + + + + + + + +
    +
    +
    +

    + +
    +
    +
    + + +
    + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Software Manifest

    +
    +
      + + + + None + + + + + Unknown + + + + + None + + + + + None + + + + + + + + + + + + + UpdatedReleased + + Unknown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + + + + +

    No default machine specified for '' in manifest.xml

    +
    + +
    +
    +
    + + + + -dbg + + + + + + +
    + + + + + Unknown + +
  • +
      + + + + + + + + +
    • + + + + + + + + + + + + + + + + + + + + + + + + + +
        + +
      • + + + + + + +
      • +
        +
      +
      +
    • +
      + + + + + + + + +
    +
  • +
    +
    + +
    diff --git a/versions/pcx86/1.71.5/outline.xsl b/versions/pcx86/1.71.5/outline.xsl new file mode 100644 index 0000000000..9c531db722 --- /dev/null +++ b/versions/pcx86/1.71.5/outline.xsl @@ -0,0 +1,47 @@ + + + + +]> + + + + + + + + + + + + + + + + + + PCjs<xsl:text> | </xsl:text><xsl:value-of select="title"/> + + + + + +
    +
    + +
    +
    + + + + -dbg + + + + + + +
    + +
    diff --git a/versions/pcx86/1.71.5/pcx86-uncompiled.js b/versions/pcx86/1.71.5/pcx86-uncompiled.js new file mode 100644 index 0000000000..ef0ea501f9 --- /dev/null +++ b/versions/pcx86/1.71.5/pcx86-uncompiled.js @@ -0,0 +1,81741 @@ +"use strict"; + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @define {string} + */ +var APPVERSION = ""; // this @define is overridden by the Closure Compiler with the version in machines.json + +var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML + +var COPYRIGHT = "Copyright © 2012-2018 Jeff Parsons "; + +var LICENSE = "License: GPL version 3 or later "; + +var CSSCLASS = "pcjs"; + +/** + * @define {string} + */ +var SITEURL = "http://localhost:8088"; // this @define is overridden by the Closure Compiler with "https://www.pcjs.org" + +/** + * @define {boolean} + */ +var COMPILED = false; // this @define is overridden by the Closure Compiler (to true) + +/** + * @define {boolean} + */ +var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code + +/** + * @define {boolean} + */ +var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code + +/** + * @define {boolean} + */ +var PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code + +/* + * RS-232 DB-25 Pin Definitions, mapped to bits 1-25 in a 32-bit status value. + * + * SerialPorts in PCjs machines are considered DTE (Data Terminal Equipment), which means they should be "virtually" + * connected to each other via a null-modem cable, which assumes the following cross-wiring: + * + * G 1 <-> 1 G (Ground) + * TD 2 <-> 3 RD (Received Data) + * RD 3 <-> 2 TD (Transmitted Data) + * RTS 4 <-> 5 CTS (Clear To Send) + * CTS 5 <-> 4 RTS (Request To Send) + * DSR 6+8 <-> 20 DTR (Data Terminal Ready) + * SG 7 <-> 7 SG (Signal Ground) + * DTR 20 <-> 6+8 DSR (Data Set Ready + Carrier Detect) + * RI 22 <-> 22 RI (Ring Indicator) + * + * TODO: Move these definitions to a more appropriate shared file at some point. + */ +var RS232 = { + RTS: { + PIN: 4, + MASK: 0x00000010 + }, + CTS: { + PIN: 5, + MASK: 0x00000020 + }, + DSR: { + PIN: 6, + MASK: 0x00000040 + }, + CD: { + PIN: 8, + MASK: 0x00000100 + }, + DTR: { + PIN: 20, + MASK: 0x00100000 + }, + RI: { + PIN: 22, + MASK: 0x00400000 + } +}; + +/* + * NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser) + */ +var NODE = false; + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Our "DiskIO API" looks like: + * + * http://www.pcjs.org/api/v1/disk?action=open&volume=*10mb.img&mode=demandrw&chs=c:h:s&machine=xxx&user=yyy + */ +var DiskAPI = { + ENDPOINT: "/api/v1/disk", + QUERY: { + ACTION: "action", // value is one of DiskAPI.ACTION.* + VOLUME: "volume", // value is path of a disk image + MODE: "mode", // value is one of DiskAPI.MODE.* + CHS: "chs", // value is cylinders:heads:sectors:bytes + ADDR: "addr", // value is cylinder:head:sector:count + MACHINE: "machine", // value is machine token + USER: "user", // value is user ID + DATA: "data" // value is data to be written + }, + ACTION: { + OPEN: "open", + READ: "read", + WRITE: "write", + CLOSE: "close" + }, + MODE: { + LOCAL: "local", // this mode implies no API (at best, localStorage backing only) + PRELOAD: "preload", // this mode implies use of the DumpAPI + DEMANDRW: "demandrw", + DEMANDRO: "demandro" + }, + FAIL: { + BADACTION: "invalid action", + BADUSER: "invalid user", + BADVOL: "invalid volume", + OPENVOL: "unable to open volume", + CREATEVOL: "unable to create volume", + WRITEVOL: "unable to write volume", + REVOKED: "access revoked" + } +}; + +/* + * TODO: Eventually, our tools will need to support looking up disk formats by "model" rather than by raw disk size, + * because obviously multiple disk geometries can yield the same raw disk size. For each conflict that arises, I'll + * probably create a fake (approximate) disk size entry above, and then create a mapping to that approximate size below. + */ +DiskAPI.MODELS = { + "RL01": 5242880, + "RL02": 10485760 +}; + +DiskAPI.MBR = { + PARTITIONS: { + OFFSET: 0x1BE, + ENTRY: { + STATUS: 0x00, // 1-byte (0x80 if active) + CHS_FIRST: 0x01, // 3-byte CHS specifier + TYPE: 0x04, // 1-byte TYPE (see below) + CHS_LAST: 0x05, // 3-byte CHS specifier + LBA_FIRST: 0x08, // 4-byte Logical Block Address + LBA_TOTAL: 0x0C, // 4-byte Logical Block Address + }, + ENTRY_LENGTH: 0x10, + STATUS: { + ACTIVE: 0x80 + }, + TYPE: { + EMPTY: 0x00, + FAT12_PRIMARY: 0x01, // DOS 2.0 and up (12-bit FAT) + FAT16_PRIMARY: 0x04 // DOS 3.0 and up (16-bit FAT) + } + }, + SIG_OFFSET: 0x1FE, + SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA +}; + +/* + * Boot sector offsets (and assorted constants) in DOS-compatible boot sectors (DOS 2.0 and up) + * + * WARNING: I've heard apocryphal stories about SIGNATURE being improperly reversed on some systems + * (ie, 0x55AA instead 0xAA55) -- perhaps by a dyslexic programmer -- so be careful out there. + */ +DiskAPI.BOOT = { + JMP_OPCODE: 0x000, // 1 byte for a JMP opcode, followed by a 1 or 2-byte offset + OEM_STRING: 0x003, // 8 bytes + SIG_OFFSET: 0x1FE, + SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA +}; + +/* + * BIOS Parameter Block (BPB) offsets in DOS-compatible boot sectors (DOS 2.x and up) + * + * NOTE: DOS 2.x OEM documentation says that the words starting at offset 0x018 (TRACK_SECS, TOTAL_HEADS, and HIDDEN_SECS) + * are optional, but even the DOS 2.0 FORMAT utility initializes all three of those words. There may be some OEM media out + * there with BPBs that are only valid up to offset 0x018, but I've not run across any media like that. + * + * DOS 3.20 added LARGE_SECS, but unfortunately, it was added as a 2-byte value at offset 0x01E. DOS 3.31 decided + * to make both HIDDEN_SECS and LARGE_SECS 4-byte values, which meant that LARGE_SECS had to move from 0x01E to 0x020. + */ +DiskAPI.BPB = { + SECTOR_BYTES: 0x00B, // 2 bytes: bytes per sector (eg, 0x200 or 512) + CLUSTER_SECS: 0x00D, // 1 byte: sectors per cluster (eg, 1) + RESERVED_SECS: 0x00E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1) + TOTAL_FATS: 0x010, // 1 byte: FAT copies (eg, 2) + ROOT_DIRENTS: 0x011, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors) + TOTAL_SECS: 0x013, // 2 bytes: number of sectors (eg, 0x140 or 320); if zero, refer to LARGE_SECS + MEDIA_ID: 0x015, // 1 byte: media ID (see DiskAPI.FAT.MEDIA_*); should also match the first byte of the FAT (aka FAT ID) + FAT_SECS: 0x016, // 2 bytes: sectors per FAT (eg, 1) + TRACK_SECS: 0x018, // 2 bytes: sectors per track (eg, 8) + TOTAL_HEADS: 0x01A, // 2 bytes: number of heads (eg, 1) + 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) + LARGE_SECS: 0x020 // 4 bytes (DOS 3.31 and up): number of sectors if TOTAL_SECS is zero +}; + +/* + * Common (supported) diskette geometries. + * + * Each entry in GEOMETRIES is an array of values in "CHS" order: + * + * [# cylinders, # heads, # sectors/track, # bytes/sector, media ID] + * + * If the 4th value is omitted, the sector size is assumed to be 512. The order of these "geometric" values mirrors + * the structure of our JSON-encoded disk images, which consist of an array of cylinders, each of which is an array of + * heads, each of which is an array of sector objects. + */ +DiskAPI.GEOMETRIES = { + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + /* + * The following are some common disk sizes and their CHS values, since missing or bogus MBR and/or BPB values + * might mislead us when attempting to determine the exact disk geometry. + */ + 10653696:[306,4,17], // PC XT 10Mb hard drive (type 3) + 21411840:[615,4,17], // PC AT 20Mb hard drive (type 2) + /* + * Assorted DEC disk formats. + */ + 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 + 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 + 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 + 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 +}; + +/* + * Media ID (descriptor) bytes for DOS-compatible FAT-formatted disks (stored in the first byte of the FAT) + */ +DiskAPI.FAT = { + MEDIA_160KB: 0xFE, // 5.25-inch, 1-sided, 8-sector, 40-track + MEDIA_180KB: 0xFC, // 5.25-inch, 1-sided, 9-sector, 40-track + MEDIA_320KB: 0xFF, // 5.25-inch, 2-sided, 8-sector, 40-track + MEDIA_360KB: 0xFD, // 5.25-inch, 2-sided, 9-sector, 40-track + MEDIA_720KB: 0xF9, // 3.5-inch, 2-sided, 9-sector, 80-track + MEDIA_1200KB: 0xF9, // 3.5-inch, 2-sided, 15-sector, 80-track + MEDIA_FIXED: 0xF8, // fixed disk (aka hard drive) + MEDIA_1440KB: 0xF0, // 3.5-inch, 2-sided, 18-sector, 80-track + MEDIA_2880KB: 0xF0 // 3.5-inch, 2-sided, 36-sector, 80-track +}; + +/* + * Cluster constants for 12-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs) + */ +DiskAPI.FAT12 = { + MAX_CLUSTERS: 4084, + CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain) + CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain + CLUSNUM_MIN: 2, // smallest valid cluster number + CLUSNUM_MAX: 0xFF6, // largest valid cluster number + CLUSNUM_BAD: 0xFF7, // bad cluster; this should NEVER appear in cluster chain + CLUSNUM_EOC: 0xFF8 // end of chain (actually, anything from 0xFF8-0xFFF indicates EOC) +}; + +/* + * Cluster constants for 16-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs) + */ +DiskAPI.FAT16 = { + MAX_CLUSTERS: 65524, + CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain) + CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain + CLUSNUM_MIN: 2, // smallest valid cluster number + CLUSNUM_MAX: 0xFFF6, // largest valid cluster number + CLUSNUM_BAD: 0xFFF7, // bad cluster; this should NEVER appear in cluster chain + CLUSNUM_EOC: 0xFFF8 // end of chain (actually, anything from 0xFFF8-0xFFFF indicates EOC) +}; + +/* + * Directory Entry offsets (and assorted constants) in FAT disk images + * + * NOTE: Versions of DOS prior to 2.0 used INVALID exclusively to mark available directory entries; any entry marked + * UNUSED was actually considered USED. In DOS 2.0 and up, UNUSED was added to indicate that all remaining entries were + * unused, relieving it from having to initialize the rest of the sectors in the directory cluster(s). And in fact, + * you will likely encounter garbage in subsequent directory sectors if you read beyond the first UNUSED entry. + */ +DiskAPI.DIRENT = { + NAME: 0x000, // 8 bytes + EXT: 0x008, // 3 bytes + ATTR: 0x00B, // 1 byte + MODTIME: 0x016, // 2 bytes + MODDATE: 0x018, // 2 bytes + CLUSTER: 0x01A, // 2 bytes + SIZE: 0x01C, // 4 bytes (typically zero for subdirectories) + LENGTH: 0x20, // 32 bytes total + UNUSED: 0x00, // indicates this and all subsequent directory entries are unused + INVALID: 0xE5 // indicates this directory entry is unused +}; + +/* + * Possible values for DIRENT.ATTR + */ +DiskAPI.ATTR = { + READONLY: 0x01, // PC-DOS 2.0 and up + HIDDEN: 0x02, + SYSTEM: 0x04, + LABEL: 0x08, // PC-DOS 2.0 and up + SUBDIR: 0x10, // PC-DOS 2.0 and up + ARCHIVE: 0x20 // PC-DOS 2.0 and up +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Our "DiskDump API", such as it was, used to look like: + * + * http://pcjs.org/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * To make it (a bit) more "REST-like", the above request now looks like: + * + * https://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * Similarly, our "FileDump API" used to look like: + * + * http://pcjs.org/bin/convrom.php?rom=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * and that request now looks like: + * + * https://www.pcjs.org/api/v1/dump?file=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the + * the rest of the URL would be (a) confusing, and (b) more work to parse. + */ +var DumpAPI = { + ENDPOINT: "/api/v1/dump", + QUERY: { + DIR: "dir", // value is path of a directory (DiskDump only) + DISK: "disk", // value is path of a disk image (DiskDump only) + FILE: "file", // value is path of a ROM image file (FileDump only) + IMG: "img", // alias for DISK + PATH: "path", // value is path of a one or more files (DiskDump only) + FORMAT: "format", // value is one of FORMAT values below + COMMENTS: "comments", // value is either "true" or "false" + DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise + MBHD: "mbhd", // value is hard drive size in Mb (formerly "mbsize") (DiskDump only) (DEPRECATED) + SIZE: "size" // value is target disk size in Kb (supersedes "mbhd") (DiskDump only) + }, + FORMAT: { + JSON: "json", // default + JSON_GZ: "gz", // gzip is currently used ONLY for compressed JSON + DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only) + HEX: "hex", // deprecated + OCTAL: "octal", // displays data as octal words + BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled + WORDS: "words", // displays data as hex words; normally used only when comments are enabled + LONGS: "longs", // displays data as dwords + IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only) + ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only) + } +}; + +/* + * Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must + * also provide a list of commands which, when combined with the endpoint, define a unique request. + */ +DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH]; +DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE]; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2018 + */ + +var ReportAPI = { + ENDPOINT: "/api/v1/report", + QUERY: { + APP: "app", + VER: "ver", + URL: "url", + USER: "user", + TYPE: "type", + DATA: "data" + }, + TYPE: { + BUG: "bug" + }, + RES: { + OK: "Thank you" + } +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Examples of User API requests: + * + * web.getHostOrigin() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser; + */ +var UserAPI = { + ENDPOINT: "/api/v1/user", + QUERY: { + REQ: "req", // specifies a request + USER: "user", // specifies a user ID + STATE: "state", // specifies a state ID + DATA: "data" // specifies state data + }, + REQ: { + CREATE: "create", // creates a user ID + VERIFY: "verify", // requests verification of a user ID + STORE: "store", // stores a machine state on the server + LOAD: "load" // loads a machine state from the server + }, + RES: { + CODE: "code", + DATA: "data" + }, + CODE: { + OK: "ok", + FAIL: "error" + }, + FAIL: { + DUPLICATE: "user already exists", + VERIFY: "unable to verify user", + BADSTATE: "invalid state parameter", + NOSTATE: "no machine state", + BADLOAD: "unable to load machine state", + BADSTORE: "unable to save machine state" + } +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2018 + */ + +var Keys = { + /* + * Keys and/or key combinations that generate common ASCII codes. + * + * NOTE: If you're looking for a general-purpose ASCII code table, see Str.ASCII in strlib.js; + * if something's missing, that's probably the more appropriate table to add it to. + * + * TODO: The Closure Compiler doesn't inline all references to these values, at least those with + * quoted property names, which is why I've 'unquoted' as many of them as possible. One solution + * would be to add mnemonics for all of them, not just the non-printable ones (eg, SPACE instead + * of ' ', AMP instead of '&', etc.) + */ + ASCII: { + BREAK: 0, CTRL_A: 1, CTRL_B: 2, CTRL_C: 3, CTRL_D: 4, CTRL_E: 5, CTRL_F: 6, CTRL_G: 7, + CTRL_H: 8, CTRL_I: 9, CTRL_J: 10, CTRL_K: 11, CTRL_L: 12, CTRL_M: 13, CTRL_N: 14, CTRL_O: 15, + CTRL_P: 16, CTRL_Q: 17, CTRL_R: 18, CTRL_S: 19, CTRL_T: 20, CTRL_U: 21, CTRL_V: 22, CTRL_W: 23, + CTRL_X: 24, CTRL_Y: 25, CTRL_Z: 26, ESC: 27, + ' ': 32, '!': 33, '"': 34, '#': 35, '$': 36, '%': 37, '&': 38, "'": 39, + '(': 40, ')': 41, '*': 42, '+': 43, ',': 44, '-': 45, '.': 46, '/': 47, + '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, + '8': 56, '9': 57, ':': 58, ';': 59, '<': 60, '=': 61, '>': 62, '?': 63, + '@': 64, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, + H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, + P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, + X: 88, Y: 89, Z: 90, '[': 91, '\\':92, ']': 93, '^': 94, '_': 95, + '`': 96, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103, + h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111, + p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119, + x: 120, y: 121, z: 122, '{':123, '|':124, '}':125, '~':126, DEL: 127 + }, + /* + * Browser keyCodes we must pay particular attention to. For the most part, these are non-alphanumeric + * or function keys, some which may require special treatment (eg, preventDefault() if returning false on + * the initial keyDown event is insufficient). + * + * keyCodes for most common ASCII keys can simply use the appropriate ASCII code above. + * + * Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, browsers defined + * them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37). + */ + KEYCODE: { + /* 0x08 */ BS: 8, // BACKSPACE (ASCII.CTRL_H) + /* 0x09 */ TAB: 9, // TAB (ASCII.CTRL_I) + /* 0x0A */ LF: 10, // LINE-FEED (ASCII.CTRL_J) (Some Windows-based browsers used to generate this via CTRL-ENTER) + /* 0x0D */ CR: 13, // CARRIAGE RETURN (ASCII.CTRL_M) + /* 0x10 */ SHIFT: 16, + /* 0x11 */ CTRL: 17, + /* 0x12 */ ALT: 18, + /* 0x13 */ PAUSE: 19, // PAUSE/BREAK + /* 0x14 */ CAPS_LOCK: 20, + /* 0x1B */ ESC: 27, + /* 0x20 */ SPACE: 32, + /* 0x21 */ PGUP: 33, + /* 0x22 */ PGDN: 34, + /* 0x23 */ END: 35, + /* 0x24 */ HOME: 36, + /* 0x25 */ LEFT: 37, + /* 0x26 */ UP: 38, + /* 0x27 */ RIGHT: 39, + /* 0x27 */ FF_QUOTE: 39, + /* 0x28 */ DOWN: 40, + /* 0x2C */ FF_COMMA: 44, + /* 0x2C */ PRTSC: 44, + /* 0x2D */ INS: 45, + /* 0x2E */ DEL: 46, + /* 0x2E */ FF_PERIOD: 46, + /* 0x2F */ FF_SLASH: 47, + /* 0x30 */ ZERO: 48, + /* 0x31 */ ONE: 49, + /* 0x32 */ TWO: 50, + /* 0x33 */ THREE: 51, + /* 0x34 */ FOUR: 52, + /* 0x35 */ FIVE: 53, + /* 0x36 */ SIX: 54, + /* 0x37 */ SEVEN: 55, + /* 0x38 */ EIGHT: 56, + /* 0x39 */ NINE: 57, + /* 0x3B */ FF_SEMI: 59, + /* 0x3D */ FF_EQUALS: 61, + /* 0x5B */ CMD: 91, // aka WIN + /* 0x5B */ FF_LBRACK: 91, + /* 0x5C */ FF_BSLASH: 92, + /* 0x5D */ RCMD: 93, // aka MENU + /* 0x5D */ FF_RBRACK: 93, + /* 0x60 */ NUM_0: 96, + /* 0x60 */ NUM_INS: 96, + /* 0x60 */ FF_BQUOTE: 96, + /* 0x61 */ NUM_1: 97, + /* 0x61 */ NUM_END: 97, + /* 0x62 */ NUM_2: 98, + /* 0x62 */ NUM_DOWN: 98, + /* 0x63 */ NUM_3: 99, + /* 0x63 */ NUM_PGDN: 99, + /* 0x64 */ NUM_4: 100, + /* 0x64 */ NUM_LEFT: 100, + /* 0x65 */ NUM_5: 101, + /* 0x65 */ NUM_CENTER: 101, + /* 0x66 */ NUM_6: 102, + /* 0x66 */ NUM_RIGHT: 102, + /* 0x67 */ NUM_7: 103, + /* 0x67 */ NUM_HOME: 103, + /* 0x68 */ NUM_8: 104, + /* 0x68 */ NUM_UP: 104, + /* 0x69 */ NUM_9: 105, + /* 0x69 */ NUM_PGUP: 105, + /* 0x6A */ NUM_MUL: 106, + /* 0x6B */ NUM_ADD: 107, + /* 0x6D */ NUM_SUB: 109, + /* 0x6E */ NUM_DEL: 110, // aka PERIOD + /* 0x6F */ NUM_DIV: 111, + /* 0x70 */ F1: 112, + /* 0x71 */ F2: 113, + /* 0x72 */ F3: 114, + /* 0x73 */ F4: 115, + /* 0x74 */ F5: 116, + /* 0x75 */ F6: 117, + /* 0x76 */ F7: 118, + /* 0x77 */ F8: 119, + /* 0x78 */ F9: 120, + /* 0x79 */ F10: 121, + /* 0x7A */ F11: 122, + /* 0x7B */ F12: 123, + /* 0x90 */ NUM_LOCK: 144, + /* 0x91 */ SCROLL_LOCK: 145, + /* 0xAD */ FF_DASH: 173, + /* 0xBA */ SEMI: 186, // Firefox: 59 (FF_SEMI) + /* 0xBB */ EQUALS: 187, // Firefox: 61 (FF_EQUALS) + /* 0xBC */ COMMA: 188, + /* 0xBD */ DASH: 189, // Firefox: 173 (FF_DASH) + /* 0xBE */ PERIOD: 190, + /* 0xBF */ SLASH: 191, + /* 0xC0 */ BQUOTE: 192, + /* 0xDB */ LBRACK: 219, + /* 0xDC */ BSLASH: 220, + /* 0xDD */ RBRACK: 221, + /* 0xDE */ QUOTE: 222, + /* 0xE0 */ FF_CMD: 224, // Firefox only (used for both CMD and RCMD) + // + // The following biases use what I'll call Decimal Coded Binary or DCB (the opposite of BCD), + // where the thousands digit is used to store the sum of "binary" digits 1 and/or 2 and/or 4. + // + // Technically, that makes it DCO (Decimal Coded Octal), but then again, BCD should have really + // been called HCD (Hexadecimal Coded Decimal), so if "they" can take liberties, so can I. + // + // ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than on "press". + // + ONDOWN: 1000, + // + // ONRIGHT is a bias we add to browser keyCodes that need to check for a "right" location (default is "left") + // + ONRIGHT: 2000, + // + // FAKE is a bias we add to signal these are fake keyCodes corresponding to internal keystroke combinations. + // The actual values are for internal use only and merely need to be unique and used consistently. + // + FAKE: 4000 + }, + /* + * The set of values that a browser may store in the 'location' property of a keyboard event object + * which we also support. + */ + LOCATION: { + LEFT: 1, + RIGHT: 2, + NUMPAD: 3 + } +}; + +/* + * Check the event object's 'location' property for a non-zero value for the following ONRIGHT keys. + */ +Keys.KEYCODE.NUM_CR = Keys.KEYCODE.CR + Keys.KEYCODE.ONRIGHT; + + +/* + * Maps Firefox keyCodes to their more common keyCode counterparts; a number of entries in this table + * are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I + * simply extended this table to resolve additional differences in other browsers (ie, Opera), but without + * browser-specific checks, it's not safe to perform all the mappings shown below. + */ +Keys.FF_KEYCODES = {}; +Keys.FF_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.KEYCODE.SEMI; // 59 -> 186 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.KEYCODE.EQUALS; // 61 -> 187 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.KEYCODE.DASH; // 173 -> 189 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_CMD] = Keys.KEYCODE.CMD; // 224 -> 91 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_COMMA] = Keys.KEYCODE.COMMA; // 44 -> 188 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_PERIOD] = Keys.KEYCODE.PERIOD; // 46 -> 190 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_SLASH] = Keys.KEYCODE.SLASH; // 47 -> 191 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BQUOTE] = Keys.KEYCODE.BQUOTE; // 96 -> 192 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_LBRACK = Keys.KEYCODE.LBRACK; // 91 -> 219 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BSLASH] = Keys.KEYCODE.BSLASH; // 92 -> 220 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_RBRACK] = Keys.KEYCODE.RBRACK; // 93 -> 221 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_QUOTE] = Keys.KEYCODE.QUOTE; // 39 -> 222 + +/* + * Maps non-ASCII keyCodes to their ASCII counterparts + */ +Keys.NONASCII_KEYCODES = {}; +Keys.NONASCII_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['-']; // 173 -> 45 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[';']; // 186 -> 59 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['=']; // 187 -> 61 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['-']; // 189 -> 45 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII[',']; // 188 -> 44 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['.']; // 190 -> 46 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['/']; // 191 -> 47 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['`']; // 192 -> 96 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['[']; // 219 -> 91 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['\\']; // 220 -> 92 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII[']']; // 221 -> 93 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII["'"]; // 222 -> 39 + +/* + * Maps unshifted keyCodes to their shifted counterparts; to be used when a shift-key is down. + * Alphabetic characters are handled in code, since they must also take CAPS_LOCK into consideration. + */ +Keys.SHIFTED_KEYCODES = {}; +Keys.SHIFTED_KEYCODES[Keys.ASCII['1']] = Keys.ASCII['!']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['2']] = Keys.ASCII['@']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['3']] = Keys.ASCII['#']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['4']] = Keys.ASCII['$']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['5']] = Keys.ASCII['%']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['6']] = Keys.ASCII['^']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['7']] = Keys.ASCII['&']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['8']] = Keys.ASCII['*']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['9']] = Keys.ASCII['(']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['0']] = Keys.ASCII[')']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[':']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['+']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII['<']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['_']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['>']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['?']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['~']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['{']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['|']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII['}']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII['"']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['_']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.ASCII['+']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.ASCII[':']; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2018 + */ + +class Str { + /** + * isValidInt(s, base) + * + * The built-in parseInt() function has the annoying feature of returning a partial value (ie, + * up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf. + * + * So it's best to use our own Str.parseInt() function, which will in turn use this function to + * validate the entire string. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported + * @return {boolean} true if valid, false if invalid (or the specified base isn't supported) + */ + static isValidInt(s, base) + { + if (!base || base == 10) return s.match(/^-?[0-9]+$/) !== null; + if (base == 16) return s.match(/^-?[0-9a-f]+$/i) !== null; + if (base == 8) return s.match(/^-?[0-7]+$/) !== null; + if (base == 2) return s.match(/^-?[01]+$/) !== null; + return false; + } + + /** + * parseInt(s, base) + * + * This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes + * ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for + * binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values; + * see isValidInt() for details. + * + * The use of multiple prefix/suffix combinations is undefined (although for the record, we process + * prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are + * also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero + * to indicate octal (because such a number could also be decimal or hex). Any number of commas are + * allowed; we remove them all before calling the built-in parseInt(). + * + * More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides + * that the PDP-10's MACRO-10 assembly language supports (decimal, octal, and binary, respectively). + * If this support turns out to adversely affect other debuggers, then it will have to be "conditionalized". + * Similarly, we've added support for "K", "M", and "G" MACRO-10-style suffixes that add 3, 6, or 9 zeros + * to the value to be parsed, respectively. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes + * @return {number|undefined} corresponding value, or undefined if invalid + */ + static parseInt(s, base) + { + let value; + + if (s) { + if (!base) base = 10; + + let ch, chPrefix, chSuffix; + let fCommas = (s.indexOf(',') > 0); + if (fCommas) s = s.replace(/,/g, ''); + + ch = chPrefix = s.charAt(0); + if (chPrefix == '#') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '$') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) { + s = s.substr(1); + } + else { + ch = chPrefix = s.substr(0, 2); + if (chPrefix == '0b' && fCommas || chPrefix == '^B') { + base = 2; + chPrefix = ''; + } + else if (chPrefix == '0o' || chPrefix == '^O') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '^D') { + base = 10; + chPrefix = ''; + } + else if (chPrefix == '0x') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) s = s.substr(2); + } + ch = chSuffix = s.slice(-1); + if (chSuffix == 'Y' || chSuffix == 'y') { + base = 2; + chSuffix = ''; + } + else if (chSuffix == '.') { + base = 10; + chSuffix = ''; + } + else if (chSuffix == 'H' || chSuffix == 'h') { + base = 16; + chSuffix = ''; + } + else if (chSuffix == 'K') { + chSuffix = '000'; + } + else if (chSuffix == 'M') { + chSuffix = '000000'; + } + else if (chSuffix == 'G') { + chSuffix = '000000000'; + } + if (ch != chSuffix) s = s.slice(0, -1) + chSuffix; + /* + * This adds support for the MACRO-10 binary shifting (Bn) suffix, which must be stripped from the + * number before parsing, and then applied to the value after parsing. If n is omitted, 35 is assumed, + * which is a net shift of zero. If n < 35, then a left shift of (35 - n) is required; if n > 35, then + * a right shift of -(35 - n) is required. + */ + let v, shift = 0; + if (base <= 10) { + let match = s.match(/(-?[0-9]+)B([0-9]*)/); + if (match) { + s = match[1]; + shift = 35 - ((match[2] || 35) & 0xff); + } + } + if (Str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) { + /* + * With the need to support larger (eg, 36-bit) integers, truncating to 32 bits is no longer helpful. + * + * value = v|0; + */ + if (shift) { + /* + * Since binary shifting is a logical operation, and since shifting by division only works properly + * with positive numbers, we must convert a negative value to a positive value, by computing the two's + * complement. + */ + if (v < 0) v += Math.pow(2, 36); + if (shift > 0) { + v *= Math.pow(2, shift); + } else { + v = Math.trunc(v / Math.pow(2, -shift)); + } + } + value = v; + } + } + return value; + } + + /** + * toBase(n, radix, cch, sPrefix, nGrouping) + * + * Displays the given number as an unsigned integer using the specified radix and number of digits. + * + * @param {number|*} n + * @param {number} radix (ie, the base) + * @param {number} cch (the desired number of digits) + * @param {string} [sPrefix] (default is none) + * @param {number} [nGrouping] + * @return {string} + */ + static toBase(n, radix, cch, sPrefix = "", nGrouping = 0) + { + /* + * We can't rely entirely on isNaN(), because isNaN(null) returns false, and we can't rely + * entirely on typeof either, because typeof Nan returns "number". Sigh. + * + * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN, + * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those + * values displayed differently. + */ + let s = ""; + if (isNaN(n) || typeof n != "number") { + n = null; + } else { + /* + * Callers that produced an input by dividing by a power of two rather than shifting (in order + * to access more than 32 bits) may produce a fractional result, which ordinarily we would simply + * ignore, but if the integer portion is zero and the sign is negative, we should probably treat + * this value as a sign-extension. + */ + if (n < 0 && n > -1) n = -1; + /* + * Negative values should be two's complemented according to the number of digits; for example, + * 12 octal digits implies an upper limit 8^12. + */ + if (n < 0) { + n += Math.pow(radix, cch); + } + if (n >= Math.pow(radix, cch)) { + cch = Math.ceil(Math.log(n) / Math.log(radix)); + } + } + let g = nGrouping || -1; + while (cch-- > 0) { + if (!g) { + s = ',' + s; + g = nGrouping; + } + if (n == null) { + s = '?' + s; + } else { + let d = n % radix; + d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10); + s = String.fromCharCode(d) + s; + n = Math.trunc(n / radix); + } + g--; + } + return sPrefix + s; + } + + /** + * toBin(n, cch, nGrouping) + * + * Converts an integer to binary, with the specified number of digits (up to a maximum of 36). + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of binary digits (0 or undefined for default of either 8, 18, or 36) + * @param {number} [nGrouping] + * @return {string} the binary representation of n + */ + static toBin(n, cch, nGrouping) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN2) || 1; + let v = Math.abs(n); + if (v <= 0b11111111) { + cch = 8; + } else if (v <= 0b111111111111111111) { + cch = 18; + } else { + cch = 36; + } + } else if (cch > 36) cch = 36; + return Str.toBase(n, 2, cch, "", nGrouping); + } + + /** + * toBinBytes(n, cb, fPrefix) + * + * Converts an integer to binary, with the specified number of bytes (up to the default of 4). + * + * @param {number|null|undefined} n (interpreted as a 32-bit value) + * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum) + * @param {boolean} [fPrefix] + * @return {string} the binary representation of n + */ + static toBinBytes(n, cb, fPrefix) + { + let s = ""; + if (!cb || cb > 4) cb = 4; + for (let i = 0; i < cb; i++) { + if (s) s = ',' + s; + s = Str.toBin(n & 0xff, 8) + s; + n >>= 8; + } + return (fPrefix? "0b" : "") + s; + } + + /** + * toOct(n, cch, fPrefix) + * + * Converts an integer to octal, with the specified number of digits (default of 6; max of 12) + * + * You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6, 8, or 12) + * @param {boolean} [fPrefix] + * @return {string} the octal representation of n + */ + static toOct(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(8)) || 1; + let v = Math.abs(n); + if (v <= 0o777777) { + cch = 6; + } else if (v <= 0o77777777) { + cch = 8; + } else { + cch = 12; + } + } else if (cch > 12) cch = 12; + return Str.toBase(n, 8, cch, fPrefix? "0o" : ""); + } + + /** + * toDec(n, cch) + * + * Converts an integer to decimal, with the specified number of digits (default of 5; max of 11) + * + * You might be tempted to use the built-in n.toString(10) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 11) + * @return {string} the decimal representation of n + */ + static toDec(n, cch) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN10) || 1; + let v = Math.abs(n); + if (v <= 99999) { + cch = 5; + } else { + cch = 11; + } + } else if (cch > 11) cch = 11; + return Str.toBase(n, 10, cch); + } + + /** + * toHex(n, cch, fPrefix) + * + * Converts an integer to hex, with the specified number of digits (default of 4 or 8, max of 9). + * + * You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16) + * will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will + * throw an exception, whereas this function will return '?' characters. + * + * NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution, + * taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values: + * + * s = (n < 0? n + 0x100000000 : n).toString(16); + * s = "00000000".substr(0, 8 - s.length) + s; + * s = s.substr(0, cch).toUpperCase(); + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4, 8, or 9) + * @param {boolean} [fPrefix] + * @return {string} the hex representation of n + */ + static toHex(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(16)) || 1; + let v = Math.abs(n); + if (v <= 0xffff) { + cch = 4; + } else if (v <= 0xffffffff) { + cch = 8; + } else { + cch = 9; + } + } else if (cch > 9) cch = 9; + return Str.toBase(n, 16, cch, fPrefix? "0x" : ""); + } + + /** + * toHexByte(b) + * + * Alias for Str.toHex(b, 2, true) + * + * @param {number|null|undefined} b is a byte value + * @return {string} the hex representation of b + */ + static toHexByte(b) + { + return Str.toHex(b, 2, true); + } + + /** + * toHexWord(w) + * + * Alias for Str.toHex(w, 4, true) + * + * @param {number|null|undefined} w is a word (16-bit) value + * @return {string} the hex representation of w + */ + static toHexWord(w) + { + return Str.toHex(w, 4, true); + } + + /** + * toHexLong(l) + * + * Alias for Str.toHex(l, 8, true) + * + * @param {number|null|undefined} l is a dword (32-bit) value + * @return {string} the hex representation of w + */ + static toHexLong(l) + { + return Str.toHex(l, 8, true); + } + + /** + * getBaseName(sFileName, fStripExt) + * + * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead. + * + * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips the extension only + * if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux"). + * + * @param {string} sFileName + * @param {boolean} [fStripExt] + * @return {string} + */ + static getBaseName(sFileName, fStripExt) + { + let sBaseName = sFileName; + + let i = sFileName.lastIndexOf('/'); + if (i >= 0) sBaseName = sFileName.substr(i + 1); + + /* + * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters. + */ + i = sBaseName.indexOf('&'); + if (i > 0) sBaseName = sBaseName.substr(0, i); + + if (fStripExt) { + i = sBaseName.lastIndexOf("."); + if (i > 0) { + sBaseName = sBaseName.substring(0, i); + } + } + return sBaseName; + } + + /** + * getExtension(sFileName) + * + * This is a poor-man's version of Node's path.extname(), which Node-only components should use instead. + * + * Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it. + * + * @param {string} sFileName + * @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string + */ + static getExtension(sFileName) + { + let sExtension = ""; + let i = sFileName.lastIndexOf("."); + if (i >= 0) { + sExtension = sFileName.substr(i + 1).toLowerCase(); + } + return sExtension; + } + + /** + * endsWith(s, sSuffix) + * + * @param {string} s + * @param {string} sSuffix + * @return {boolean} true if s ends with sSuffix, false if not + */ + static endsWith(s, sSuffix) + { + return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1; + } + + /** + * escapeHTML(sHTML) + * + * @param {string} sHTML + * @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars() + */ + static escapeHTML(sHTML) + { + return sHTML.replace(/[&<>"']/g, function(m) + { + return Str.HTMLEscapeMap[m]; + }); + } + + /** + * replace(sSearch, sReplace, s) + * + * The JavaScript replace() function ALWAYS interprets "$" specially in replacement strings, even when + * the search string is NOT a RegExp; specifically: + * + * $$ Inserts a "$" + * $& Inserts the matched substring + * $` Inserts the portion of the string that precedes the matched substring + * $' Inserts the portion of the string that follows the matched substring + * $n Where n is a positive integer less than 100, inserts the nth parenthesized sub-match string, + * provided the first argument was a RegExp object + * + * So, if a replacement string containing dollar signs passes through a series of replace() calls, untold + * problems could result. Hence, this function, which simply uses the replacement string as-is. + * + * Similar to the JavaScript replace() method (when sSearch is a string), this replaces only ONE occurrence + * (ie, the FIRST occurrence); it might be nice to add options to replace the LAST occurrence and/or ALL + * occurrences, but we'll revisit that later. + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replace(sSearch, sReplace, s) + { + let i = s.indexOf(sSearch); + if (i >= 0) { + s = s.substr(0, i) + sReplace + s.substr(i + sSearch.length); + } + return s; + } + + /** + * replaceAll(sSearch, sReplace, s) + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replaceAll(sSearch, sReplace, s) + { + let a = {}; + a[sSearch] = sReplace; + return Str.replaceArray(a, s); + } + + /** + * replaceArray(a, s) + * + * @param {Object} a + * @param {string} s + * @return {string} + */ + static replaceArray(a, s) + { + let sMatch = ""; + for (let k in a) { + /* + * As noted in: + * + * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/ + * + * inside character classes, only backslash, caret, hyphen and the closing bracket need to be + * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first, + * and the hyphen is last, you can avoid escaping those as well. + */ + k = k.replace(/([\\[\]*{}().+?|$])/g, "\\$1"); + sMatch += (sMatch? '|' : '') + k; + } + return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) + { + return a[m]; + }); + } + + /** + * pad(s, cch, fPadLeft) + * + * NOTE: the maximum amount of padding currently supported is 40 spaces. + * + * @param {string} s is a string + * @param {number} cch is desired length + * @param {boolean} [fPadLeft] (default is padding on the right) + * @return {string} the original string (s) with spaces padding it to the specified length + */ + static pad(s, cch, fPadLeft) + { + let sPadding = " "; + return fPadLeft? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch); + } + + /** + * sprintf(format, ...args) + * + * Copied from the CCjs project (https://github.com/jeffpar/ccjs/blob/master/lib/stdio.js) and extended. + * + * Far from complete, let alone sprintf-compatible, but it's adequate for the handful of sprintf-style format + * specifiers that I use. + * + * TODO: The %c and %s specifiers support a negative width (for left-justified output), but the numeric specifiers + * (eg, %d and %x) do not; they support only positive widths and right-justified output. That's one of the more + * glaring omissions at the moment. + * + * @param {string} format + * @param {...} args + * @return {string} + */ + static sprintf(format, ...args) + { + let buffer = ""; + let aParts = format.split(/%([-+ 0#]*)([0-9]*|\*)(\.[0-9]+|)([hlL]?)([A-Za-z%])/); + + let iArg = 0, iPart; + for (iPart = 0; iPart < aParts.length - 6; iPart += 6) { + + buffer += aParts[iPart]; + + let arg = args[iArg++]; + let flags = aParts[iPart+1]; + let width = aParts[iPart+2]; + if (width == '*') { + width = arg; + arg = args[iArg++]; + } else { + width = +width || 0; + } + let precision = aParts[iPart+3]; + precision = precision? +precision.substr(1) : -1; + let prefix = aParts[iPart+4]; + let type = aParts[iPart+5]; + let ach = null, s; + + switch(type) { + case 'd': + /* + * We could use "arg |= 0", but there may be some value to supporting integers > 32 bits. + * + * Also, unlike the 'X' and 'x' hexadecimal cases, there's no need to explicitly check for a string + * arguments, because the call to trunc() automatically coerces any string value to a (decimal) number. + */ + arg = Math.trunc(arg); + /* falls through */ + + case 'f': + s = Math.trunc(arg) + ""; + if (precision > 0) { + width -= (precision + 1); + } + if (s.length < width) { + if (flags.indexOf('0') >= 0) { + if (arg < 0) width--; + s = ("0000000000" + Math.abs(arg)).slice(-width); + if (arg < 0) s = '-' + s; + } else { + s = (" " + s).slice(-width); + } + } + if (precision > 0) { + arg = Math.round((arg - Math.trunc(arg)) * Math.pow(10, precision)); + s += '.' + ("0000000000" + Math.abs(arg)).slice(-precision); + } + buffer += s; + break; + + case 'c': + arg = String.fromCharCode(arg); + /* falls through */ + + case 's': + if (typeof arg == "string") { + while (arg.length < width) { + if (flags.indexOf('-') >= 0) { + arg += ' '; + } else { + arg = ' ' + arg; + } + } + } + buffer += arg.toString(); + break; + + case 'X': + ach = Str.HexUpperCase; + /* falls through */ + + case 'x': + if (!ach) ach = Str.HexLowerCase; + s = ""; + if (typeof arg == "string") { + /* + * Since we're advised to ALWAYS pass a radix to parseInt(), we must detect explicitly + * hex values ourselves, because using a radix of 10 with any "0x..." value always returns 0. + * + * And if the value CAN be interpreted as decimal, then we MUST interpret it as decimal, because + * we have sprintf() calls in /modules/pcx86/lib/testmon.js that depend on this code to perform + * decimal to hex conversion. We're going to make our own rules here, since passing numbers in + * string form isn't part of the sprintf "spec". + */ + arg = Number.parseInt(arg, arg.match(/(^0x|[a-f])/i)? 16 : 10); + } + do { + let d = arg & 0xf; + arg >>>= 4; + if (flags.indexOf('0') >= 0 || s == "" || d || arg) { + s = ach[d] + s; + } else if (width) { + s = ' ' + s; + } + } while (--width > 0 || arg); + buffer += s; + break; + + default: + /* + * The supported ANSI C set of types: "dioxXucsfeEgGpn%" + */ + buffer += "(unrecognized printf type %" + type + ")"; + break; + } + } + + buffer += aParts[iPart]; + return buffer; + } + + /** + * stripLeadingZeros(s, fPad) + * + * @param {string} s + * @param {boolean} [fPad] + * @return {string} + */ + static stripLeadingZeros(s, fPad) + { + let cch = s.length; + s = s.replace(/^0+([0-9A-F]+)$/i, "$1"); + if (fPad) s = Str.pad(s, cch, true); + return s; + } + + /** + * trim(s) + * + * @param {string} s + * @return {string} + */ + static trim(s) + { + if (String.prototype.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ""); + } + + /** + * toASCIICode(b) + * + * @param {number} b + * @return {string} + */ + static toASCIICode(b) + { + let s; + if (b != Str.ASCII.CR && b != Str.ASCII.LF) { + s = Str.ASCIICodeMap[b]; + } + if (s) { + s = '<' + s + '>'; + } else { + s = String.fromCharCode(b); + } + return s; + } +} + +/* + * Map special characters to their HTML escape sequences. + */ +Str.HTMLEscapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' +}; + +/* + * Map "unprintable" ASCII codes to mnemonics, to more clearly see what's being printed. + */ +Str.ASCIICodeMap = { + 0x00: "NUL", + 0x01: "SOH", // (CTRL_A) Start of Heading + 0x02: "STX", // (CTRL_B) Start of Text + 0x03: "ETX", // (CTRL_C) End of Text + 0x04: "EOT", // (CTRL_D) End of Transmission + 0x05: "ENQ", // (CTRL_E) Enquiry + 0x06: "ACK", // (CTRL_F) Acknowledge + 0x07: "BEL", // (CTRL_G) Bell + 0x08: "BS", // (CTRL_H) Backspace + 0x09: "TAB", // (CTRL_I) Horizontal Tab (aka HT) + 0x0A: "LF", // (CTRL_J) Line Feed (New Line) + 0x0B: "VT", // (CTRL_K) Vertical Tab + 0x0C: "FF", // (CTRL_L) Form Feed (New Page) + 0x0D: "CR", // (CTRL_M) Carriage Return + 0x0E: "SO", // (CTRL_N) Shift Out + 0x0F: "SI", // (CTRL_O) Shift In + 0x10: "DLE", // (CTRL_P) Data Link Escape + 0x11: "XON", // (CTRL_Q) Device Control 1 (aka DC1) + 0x12: "DC2", // (CTRL_R) Device Control 2 + 0x13: "XOFF", // (CTRL_S) Device Control 3 (aka DC3) + 0x14: "DC4", // (CTRL_T) Device Control 4 + 0x15: "NAK", // (CTRL_U) Negative Acknowledge + 0x16: "SYN", // (CTRL_V) Synchronous Idle + 0x17: "ETB", // (CTRL_W) End of Transmission Block + 0x18: "CAN", // (CTRL_X) Cancel + 0x19: "EM", // (CTRL_Y) End of Medium + 0x1A: "SUB", // (CTRL_Z) Substitute + 0x1B: "ESC", // Escape + 0x1C: "FS", // File Separator + 0x1D: "GS", // Group Separator + 0x1E: "RS", // Record Separator + 0x1F: "US", // Unit Separator + 0x7F: "DEL" +}; + +/* + * Refer to: https://en.wikipedia.org/wiki/Code_page_437 + */ +Str.CP437ToUnicode = [ + '\u0000', '\u263A', '\u263B', '\u2665', '\u2666', '\u2663', '\u2660', '\u2022', + '\u25D8', '\u25CB', '\u25D9', '\u2642', '\u2640', '\u266A', '\u266B', '\u263C', + '\u25BA', '\u25C4', '\u2195', '\u203C', '\u00B6', '\u00A7', '\u25AC', '\u21A8', + '\u2191', '\u2193', '\u2192', '\u2190', '\u221F', '\u2194', '\u25B2', '\u25BC', + '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', + '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', + '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', + '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', + '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', + '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', + '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', + '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', + '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', + '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', + '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', + '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u2302', + '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', + '\u00EA', '\u00EB', '\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', + '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', + '\u00FF', '\u00D6', '\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', + '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA', + '\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', + '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561', '\u2562', '\u2556', + '\u2555', '\u2563', '\u2551', '\u2557', '\u255D', '\u255C', '\u255B', '\u2510', + '\u2514', '\u2534', '\u252C', '\u251C', '\u2500', '\u253C', '\u255E', '\u255F', + '\u255A', '\u2554', '\u2569', '\u2566', '\u2560', '\u2550', '\u256C', '\u2567', + '\u2568', '\u2564', '\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', + '\u256A', '\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580', + '\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5', '\u03C4', + '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6', '\u03B5', '\u2229', + '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320', '\u2321', '\u00F7', '\u2248', + '\u00B0', '\u2219', '\u00B7', '\u221A', '\u207F', '\u00B2', '\u25A0', '\u00A0' +]; + +/* + * TODO: Future home of a complete ASCII table. + */ +Str.ASCII = { + LF: 0x0A, + CR: 0x0D +}; + +Str.TYPES = { + NULL: 0, + BYTE: 1, + WORD: 2, + DWORD: 3, + NUMBER: 4, + STRING: 5, + BOOLEAN: 6, + OBJECT: 7, + ARRAY: 8 +}; + +Str.HexLowerCase = "0123456789abcdef"; +Str.HexUpperCase = "0123456789ABCDEF"; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @typedef {{ + * mask: number, + * shift: number + * }} + */ +var BitField; + +/** + * @typedef {Object.} + */ +var BitFields; + +class Usr { + /** + * binarySearch(a, v, fnCompare) + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point + */ + static binarySearch(a, v, fnCompare) + { + let left = 0; + let right = a.length; + let found = 0; + if (fnCompare === undefined) { + fnCompare = function(a, b) + { + return a > b ? 1 : a < b ? -1 : 0; + }; + } + while (left < right) { + let middle = (left + right) >> 1; + let compareResult; + compareResult = fnCompare(v, a[middle]); + if (compareResult > 0) { + left = middle + 1; + } else { + right = middle; + found = !compareResult; + } + } + return found ? left : ~left; + } + + /** + * binaryInsert(a, v, fnCompare) + * + * If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the + * element is inserted into the array at the appropriate index. + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v is the value to insert + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + */ + static binaryInsert(a, v, fnCompare) + { + let index = Usr.binarySearch(a, v, fnCompare); + if (index < 0) { + a.splice(-(index + 1), 0, v); + } + } + + /** + * getTimestamp() + * + * @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss") + */ + static getTimestamp() + { + return Usr.formatDate("Y-m-d H:i:s"); + } + + /** + * getMonthDays(nMonth, nYear) + * + * Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100), + * so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails + * the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap + * year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year. + * + * TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD, + * so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and + * we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what + * a real RTC actually does. + * + * @param {number} nMonth (1-12) + * @param {number} nYear (normally a 4-digit year, but it may also be mod 100) + * @return {number} the maximum (1-based) day allowed for the specified month and year + */ + static getMonthDays(nMonth, nYear) + { + let nDays = Usr.aMonthDays[nMonth - 1]; + if (nDays == 28) { + if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) { + nDays++; + } + } + return nDays; + } + + /** + * formatDate(sFormat, date) + * + * @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s") + * @param {Date} [date] (default is the current time) + * @return {string} + * + * Supported identifiers in sFormat include: + * + * a: lowercase ante meridiem and post meridiem (am or pm) + * d: day of the month, 2 digits with leading zeros (01,02,...,31) + * D: 3-letter day of the week ("Sun","Mon",...,"Sat") + * F: month ("January","February",...,"December") + * g: hour in 12-hour format, without leading zeros (1,2,...,12) + * h: hour in 24-hour format, without leading zeros (0,1,...,23) + * H: hour in 24-hour format, with leading zeros (00,01,...,23) + * i: minutes, with leading zeros (00,01,...,59) + * j: day of the month, without leading zeros (1,2,...,31) + * l: day of the week ("Sunday","Monday",...,"Saturday") + * m: month, with leading zeros (01,02,...,12) + * M: 3-letter month ("Jan","Feb",...,"Dec") + * n: month, without leading zeros (1,2,...,12) + * s: seconds, with leading zeros (00,01,...,59) + * y: 2-digit year (eg, 14) + * Y: 4-digit year (eg, 2014) + * + * For more inspiration, see: http://php.net/manual/en/function.date.php (of which we support ONLY a subset). + */ + static formatDate(sFormat, date) + { + let sDate = ""; + if (!date) date = new Date(); + let iHour = date.getHours(); + let iDay = date.getDate(); + let iMonth = date.getMonth() + 1; + for (let i = 0; i < sFormat.length; i++) { + let ch; + switch ((ch = sFormat.charAt(i))) { + case 'a': + sDate += (iHour < 12 ? "am" : "pm"); + break; + case 'd': + sDate += ('0' + iDay).slice(-2); + break; + case 'D': + sDate += Usr.asDays[date.getDay()].substr(0, 3); + break; + case 'F': + sDate += Usr.asMonths[iMonth - 1]; + break; + case 'g': + sDate += (!iHour ? 12 : (iHour > 12 ? iHour - 12 : iHour)); + break; + case 'h': + sDate += iHour; + break; + case 'H': + sDate += ('0' + iHour).slice(-2); + break; + case 'i': + sDate += ('0' + date.getMinutes()).slice(-2); + break; + case 'j': + sDate += iDay; + break; + case 'l': + sDate += Usr.asDays[date.getDay()]; + break; + case 'm': + sDate += ('0' + iMonth).slice(-2); + break; + case 'M': + sDate += Usr.asMonths[iMonth - 1].substr(0, 3); + break; + case 'n': + sDate += iMonth; + break; + case 's': + sDate += ('0' + date.getSeconds()).slice(-2); + break; + case 'y': + sDate += ("" + date.getFullYear()).slice(-2); + break; + case 'Y': + sDate += date.getFullYear(); + break; + default: + sDate += ch; + break; + } + } + return sDate; + } + + /** + * defineBitFields(bfs) + * + * Prepares a bit field definition for use with getBitField() and setBitField(); eg: + * + * let bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3}); + * + * 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). + * + * Usr.setBitField(bfs.num, n, 1); + * + * The above set bit field "bfs.num" in numeric variable "n" to the value 1. + * + * @param {Object} bfs + * @return {BitFields} + */ + static defineBitFields(bfs) + { + let bit = 0; + for (let f in bfs) { + let width = bfs[f]; + let mask = ((1 << width) - 1) << bit; + bfs[f] = {mask: mask, shift: bit}; + bit += width; + } + return bfs; + } + + /** + * initBitFields(bfs, ...) + * + * @param {BitFields} bfs + * @param {...number} var_args + * @return {number} a value containing all supplied bit fields + */ + static initBitFields(bfs, var_args) + { + let v = 0, i = 1; + for (let f in bfs) { + if (i >= arguments.length) break; + v = Usr.setBitField(bfs[f], v, arguments[i++]); + } + return v; + } + + /** + * getBitField(bf, v) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @return {number} the value of the bit field in v defined by bf + */ + static getBitField(bf, v) + { + return (v & bf.mask) >> bf.shift; + } + + /** + * setBitField(bf, v, n) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @param {number} n is a value to store in v in the bit field defined by bf + * @return {number} updated v + */ + static setBitField(bf, v, n) + { + return (v & ~bf.mask) | ((n << bf.shift) & bf.mask); + } + + /** + * indexOf(a, t, i) + * + * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it. + * + * @param {Array} a + * @param {*} t + * @param {number} [i] + * @returns {number} + */ + static indexOf(a, t, i) + { + if (Array.prototype.indexOf) { + return a.indexOf(t, i); + } + i = i || 0; + if (i < 0) i += a.length; + if (i < 0) i = 0; + for (let n = a.length; i < n; i++) { + if (i in a && a[i] === t) return i; + } + return -1; + } +} + +Usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; +Usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; +Usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +/** + * getTime() + * + * @return {number} the current time, in milliseconds + */ +Usr.getTime = Date.now || function() { return +new Date(); }; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2018 + */ + + +/* + * According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties + * and functions of JavaScript-in-the-Browser: + * + * Property Description + * --- + * Infinity A numeric value that represents positive/negative infinity + * NaN "Not-a-Number" value + * undefined Indicates that a variable has not been assigned a value + * + * Function Description + * --- + * decodeURI() Decodes a URI + * decodeURIComponent() Decodes a URI component + * encodeURI() Encodes a URI + * encodeURIComponent() Encodes a URI component + * escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead + * eval() Evaluates a string and executes it as if it was script code + * isFinite() Determines whether a value is a finite, legal number + * isNaN() Determines whether a value is an illegal number + * Number() Converts an object's value to a number + * parseFloat() Parses a string and returns a floating point number + * parseInt() Parses a string and returns an integer + * String() Converts an object's value to a string + * unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead + * + * And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions + * of the *window* object. + * + * Property Description + * --- + * closed Returns a Boolean value indicating whether a window has been closed or not + * defaultStatus Sets or returns the default text in the statusbar of a window + * document Returns the Document object for the window (See Document object) + * frames Returns an array of all the frames (including iframes) in the current window + * history Returns the History object for the window (See History object) + * innerHeight Returns the inner height of a window's content area + * innerWidth Returns the inner width of a window's content area + * length Returns the number of frames (including iframes) in a window + * location Returns the Location object for the window (See Location object) + * name Sets or returns the name of a window + * navigator Returns the Navigator object for the window (See Navigator object) + * opener Returns a reference to the window that created the window + * outerHeight Returns the outer height of a window, including toolbars/scrollbars + * outerWidth Returns the outer width of a window, including toolbars/scrollbars + * pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window + * pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window + * parent Returns the parent window of the current window + * screen Returns the Screen object for the window (See Screen object) + * screenLeft Returns the x coordinate of the window relative to the screen + * screenTop Returns the y coordinate of the window relative to the screen + * screenX Returns the x coordinate of the window relative to the screen + * screenY Returns the y coordinate of the window relative to the screen + * self Returns the current window + * status Sets or returns the text in the statusbar of a window + * top Returns the topmost browser window + * + * Method Description + * --- + * alert() Displays an alert box with a message and an OK button + * atob() Decodes a base-64 encoded string + * blur() Removes focus from the current window + * btoa() Encodes a string in base-64 + * clearInterval() Clears a timer set with setInterval() + * clearTimeout() Clears a timer set with setTimeout() + * close() Closes the current window + * confirm() Displays a dialog box with a message and an OK and a Cancel button + * createPopup() Creates a pop-up window + * focus() Sets focus to the current window + * moveBy() Moves a window relative to its current position + * moveTo() Moves a window to the specified position + * open() Opens a new browser window + * print() Prints the content of the current window + * prompt() Displays a dialog box that prompts the visitor for input + * resizeBy() Resizes the window by the specified pixels + * resizeTo() Resizes the window to the specified width and height + * scroll() This method has been replaced by the scrollTo() method. + * scrollBy() Scrolls the content by the specified number of pixels + * scrollTo() Scrolls the content to the specified coordinates + * setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds) + * setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds + * stop() Stops the window from loading + */ + +class Web { + /** + * log(s, type) + * + * For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line) + * for Component.log() to display anything. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + Component.log(s, type); + } + + /** + * notice(s, fPrintOnly, id) + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + */ + static notice(s, fPrintOnly, id) + { + Component.notice(s, fPrintOnly, id); + } + + /** + * alertUser(sMessage) + * + * NOTE: Legacy function for older modules (eg, DiskDump); see Component.alertUser(). + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Web.log(sMessage); + } + } + + /** + * getResource(sURL, type, fAsync, done, progress) + * + * Request the specified resource (sURL), and once the request is complete, notify done(). + * + * If fAsync is true, a done() callback should ALWAYS be supplied; otherwise, you'll have no + * idea when the request is complete or what the response was. done() is passed three parameters: + * + * done(sURL, resource, nErrorCode) + * + * If nErrorCode is zero, resource should contain the requested data; otherwise, an error occurred. + * + * If type is set to a string, that string can be used to control the response format; + * by default, the response format is plain text, but you can specify "arraybuffer" to request arbitrary + * binary data, in which case the returned resource will be a ArrayBuffer rather than a string. + * + * @param {string} sURL + * @param {string|Object|null} [type] (object for POST request, otherwise type of GET request) + * @param {boolean} [fAsync] is true for an asynchronous request; false otherwise (MUST be set for IE) + * @param {function(string,string,number)} [done] + * @param {function(number)} [progress] + * @return {Array|null} Array containing [resource, nErrorCode], or null if no response available (yet) + */ + static getResource(sURL, type = "text", fAsync = false, done, progress) + { + let nErrorCode = 0, resource = null, response = null; + + if (typeof resources == 'object' && (resource = resources[sURL])) { + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + } + else if (fAsync && typeof resources == 'function') { + resources(sURL, function(resource, nErrorCode) + { + if (done) done(sURL, resource, nErrorCode); + }); + return response; + } + + if (Web.getHost() == "pcjs:8088" || NODE) { + /* + * The larger resources that I've put on archive.pcjs.org are assumed to also be available locally + * whenever the hostname is "pcjs" (or NODE is true); otherwise, use "localhost" when debugging locally. + * + * NOTE: http://archive.pcjs.org is currently redirected to https://s3-us-west-2.amazonaws.com/archive.pcjs.org + */ + sURL = sURL.replace(/^(http:\/\/archive\.pcjs\.org|https:\/\/[a-z0-9-]+\.amazonaws\.com\/archive\.pcjs\.org)(\/.*)\/([^\/]*)$/, "$2/archive/$3"); + sURL = sURL.replace(/^https:\/\/jeffpar\.github\.io\/(pcjs-[a-z]+|private-[a-z]+)\/(.*)$/, "/$1/$2"); + } + else { + /* + * TODO: Perhaps it's time for our code in netlib.js to finally add support for HTTPS; for now + * though, it's just as well that the NODE environment assumes all resources are available locally. + */ + sURL = sURL.replace(/^\/(pcjs-[a-z]+|private-[a-z]+)\//, "https://jeffpar.github.io/$1/"); + } + + + let request = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP")); + let fArrayBuffer = false, fXHR2 = (typeof request.responseType === 'string'); + + let callback = function() { + if (request.readyState !== 4) { + if (progress) progress(1); + return null; + } + /* + * The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple + * times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when + * debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests + * pending, any debugging activity means most of them simply get dropped on floor, so what may actually be + * happening are mis-notifications rather than redundant notifications. + * + * request.onreadystatechange = undefined; + */ + /* + * If the request failed due to, say, a CORS policy denial; eg: + * + * Failed to load http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img: + * Redirect from 'http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img' to + * 'http://www.allbootdisks.com/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. + * Origin 'http://pcjs:8088' is therefore not allowed access. + * + * and our request type was "arraybuffer", attempting to access responseText may trigger an exception; eg: + * + * Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's + * 'responseType' is '' or 'text' (was 'arraybuffer'). + * + * We could tiptoe around these potential landmines, but the safest thing to do is wrap this code with try/catch. + */ + try { + resource = fArrayBuffer? request.response : request.responseText; + } catch(err) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + ") exception: " + err.message); + } + /* + * The normal "success" case is a non-null resource and an HTTP status code of 200, but when loading files from the + * local file system (ie, when using the "file:" protocol), we have to be a bit more flexible. + */ + if (resource != null && (request.status == 200 || !request.status && resource.length && Web.getHostProtocol() == "file:")) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + "): returned " + resource.length + " bytes"); + } + else { + nErrorCode = request.status || -1; + Web.log("xmlHTTPRequest(" + sURL + "): error code " + nErrorCode); + } + if (progress) progress(2); + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + }; + + if (fAsync) { + request.onreadystatechange = callback; + } + + if (progress) progress(0); + + if (type && typeof type == "object") { + let sPost = ""; + for (let p in type) { + if (!type.hasOwnProperty(p)) continue; + if (sPost) sPost += "&"; + sPost += p + '=' + encodeURIComponent(type[p]); + } + sPost = sPost.replace(/%20/g, '+'); + if (MAXDEBUG) Web.log("Web.getResource(POST " + sURL + "): " + sPost.length + " bytes"); + request.open("POST", sURL, fAsync); + request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + request.send(sPost); + } else { + if (MAXDEBUG) Web.log("Web.getResource(GET " + sURL + ")"); + request.open("GET", sURL, fAsync); + if (type == "arraybuffer") { + if (fXHR2) { + fArrayBuffer = true; + request.responseType = type; + } else { + request.overrideMimeType("text/plain; charset=x-user-defined"); + } + } + request.send(); + } + + if (!fAsync) { + request.readyState = 4; // this may already be set for synchronous requests, but I don't want to take any chances + response = callback(); + } + return response; + } + + /** + * parseMemoryResource(sURL, sData) + * + * This converts a variety of JSON-style data streams into an Object with the following properties: + * + * aBytes + * aSymbols + * addrLoad + * addrExec + * + * If the source data contains a 'bytes' array, it's passed through to 'aBytes'; alternatively, if + * it contains a 'words' array, the values are converted from 16-bit to 8-bit and stored in 'aBytes', + * and if it contains a 'longs' array, the values are converted from 32-bit longs into bytes and + * stored in 'aBytes'. + * + * Alternatively, if the source data contains a 'data' array, we simply pass that through to the output + * object as: + * + * aData + * + * @param {string} sURL + * @param {string} sData + * @return {Object|null} (resource) + */ + static parseMemoryResource(sURL, sData) + { + let i; + let resource = { + aBytes: null, + aSymbols: null, + addrLoad: null, + addrExec: null + }; + + if (sData.charAt(0) == "[" || sData.charAt(0) == "{") { + try { + let a, ib, data; + + if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"... + /* + * Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid, + * but more recent server configs now display a somewhat friendlier HTML error page. The downside, + * however, is that the original error has been buried, and we've received "data" that isn't actually + * tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message. + */ + throw new Error(sData); + } + + /* + * TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to + * eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in + * IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks + * (perhaps one track per chunk, using regular expressions) and then manually re-assemble it. + * + * However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")") + * is a much easier fix. The only drawback is that we must first quote any unquoted property names + * and remove any comments, because while eval() was cool with them, JSON.parse() is more particular; + * the following RegExp replacements take care of those requirements. + * + * The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and + * while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified), + * I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement + * for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect + * any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so: + * + * ["unrecognized disk path: test.img"] + */ + if (sData.indexOf("0x") < 0 && sData.indexOf("0o") < 0 && sData.substr(0, 2) != '["') { + data = JSON.parse(sData.replace(/([a-z]+):/gm, '"$1":').replace(/\/\/[^\n]*/gm, "")); + } else { + data = eval("(" + sData + ")"); + } + + resource.addrLoad = data['load']; + resource.addrExec = data['exec']; + + if (a = data['bytes']) { + resource.aBytes = a; + } + else if (a = data['words']) { + /* + * Convert all words into bytes + */ + resource.aBytes = new Array(a.length * 2); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + + } + } + else if (a = data['longs']) { + /* + * Convert all dwords (longs) into bytes + */ + resource.aBytes = new Array(a.length * 4); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + resource.aBytes[ib++] = (a[i] >> 16) & 0xff; + resource.aBytes[ib++] = (a[i] >> 24) & 0xff; + } + } + else if (a = data['data']) { + resource.aData = a; + } + else { + resource.aBytes = data; + } + + if (resource.aBytes) { + if (!resource.aBytes.length) { + Component.error("Empty resource: " + sURL); + resource = null; + } + else if (resource.aBytes.length == 1) { + Component.error(resource.aBytes[0]); + resource = null; + } + } + resource.aSymbols = data['symbols']; + + } catch (e) { + Component.error("Resource data error (" + sURL + "): " + e.message); + resource = null; + } + } + else { + /* + * Parse the data manually; we assume it's a series of hex byte-values separated by whitespace. + */ + let ab = []; + let sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, ""); + let asHexData = sHexData.split(" "); + for (i = 0; i < asHexData.length; i++) { + let n = parseInt(asHexData[i], 16); + if (isNaN(n)) { + Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")"); + break; + } + ab.push(n & 0xff); + } + if (i == asHexData.length) resource.aBytes = ab; + } + return resource; + } + + /** + * sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + * + * Send a report (eg, bug report) to the server. + * + * @param {string} sApp (eg, "PCjs") + * @param {string} sVer (eg, "1.02") + * @param {string} sURL (eg, "/devices/pc/machine/5150/mda/64kb/machine.xml") + * @param {string} sUser (ie, the user key, if any) + * @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.* + * @param {string} sReport (eg, unparsed state data) + * @param {string} [sHostName] (default is SITEURL) + */ + static sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + { + let dataPost = {}; + dataPost[ReportAPI.QUERY.APP] = sApp; + dataPost[ReportAPI.QUERY.VER] = sVer; + dataPost[ReportAPI.QUERY.URL] = sURL; + dataPost[ReportAPI.QUERY.USER] = sUser; + dataPost[ReportAPI.QUERY.TYPE] = sType; + dataPost[ReportAPI.QUERY.DATA] = sReport; + let sReportURL = (sHostName? sHostName : SITEURL) + ReportAPI.ENDPOINT; + Web.getResource(sReportURL, dataPost, true); + } + + /** + * getHost() + * + * This is like getHostName() but with the port number, if any. + * + * @return {string} + */ + static getHost() + { + return (window? window.location.host : "localhost"); + } + + /** + * getHostName() + * + * @return {string} + */ + static getHostName() + { + return (window? window.location.hostname : "localhost"); + } + + /** + * getHostOrigin() + * + * This could also be implemented with window.location.origin, but that wasn't originally available in all browsers. + * + * @return {string} + */ + static getHostOrigin() + { + return (window? window.location.protocol + "//" + window.location.host : SITEURL); + } + + /** + * getHostProtocol() + * + * @return {string} + */ + static getHostProtocol() + { + return (window? window.location.protocol : "file:"); + } + + /** + * getHostURL() + * + * @return {string|null} + */ + static getHostURL() + { + return (window? window.location.href : null); + } + + /** + * getUserAgent() + * + * @return {string} + */ + static getUserAgent() + { + return (window? window.navigator.userAgent : ""); + } + + /** + * hasLocalStorage + * + * true if localStorage support exists, is enabled, and works; false otherwise + * + * @return {boolean} + */ + static hasLocalStorage() + { + if (Web.fLocalStorage == null) { + let f = false; + if (window) { + try { + window.localStorage.setItem(Web.sLocalStorageTest, Web.sLocalStorageTest); + f = (window.localStorage.getItem(Web.sLocalStorageTest) == Web.sLocalStorageTest); + window.localStorage.removeItem(Web.sLocalStorageTest); + } catch (e) { + Web.logLocalStorageError(e); + f = false; + } + } + Web.fLocalStorage = f; + } + return Web.fLocalStorage; + } + + /** + * logLocalStorageError(e) + * + * @param {Error} e is an exception + */ + static logLocalStorageError(e) + { + Web.log(e.message, "localStorage error"); + } + + /** + * getLocalStorageItem(sKey) + * + * Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available + * + * @param {string} sKey + * @return {string|null|undefined} sValue + */ + static getLocalStorageItem(sKey) + { + let sValue; + if (window) { + try { + sValue = window.localStorage.getItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + return sValue; + } + + /** + * setLocalStorageItem(sKey, sValue) + * + * @param {string} sKey + * @param {string} sValue + * @return {boolean} true if localStorage is available, false if not + */ + static setLocalStorageItem(sKey, sValue) + { + try { + window.localStorage.setItem(sKey, sValue); + return true; + } catch (e) { + Web.logLocalStorageError(e); + } + return false; + } + + /** + * removeLocalStorageItem(sKey) + * + * @param {string} sKey + */ + static removeLocalStorageItem(sKey) + { + try { + window.localStorage.removeItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + + /** + * getLocalStorageKeys() + * + * @return {Array} + */ + static getLocalStorageKeys() + { + let a = []; + try { + for (let i = 0, c = window.localStorage.length; i < c; i++) { + a.push(window.localStorage.key(i)); + } + } catch (e) { + Web.logLocalStorageError(e); + } + return a; + } + + /** + * reloadPage() + */ + static reloadPage() + { + if (window) window.location.reload(); + } + + /** + * isUserAgent(s) + * + * Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can + * use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE"). + * + * 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating + * the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx); + * they say "public websites should rely on feature detection, rather than browser detection, in order to design + * their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent + * that tries to fool apps into thinking the browser is more like WebKit or Gecko: + * + * Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko + * + * That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained + * some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because + * that's a sledgehammer solution which restores the old user-agent string but also disables other features like + * HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or + * "Trident". + * + * UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't + * any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll + * need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture. + * + * @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values + * @return {boolean} is true if the string was found, false if not + */ + static isUserAgent(s) + { + if (window) { + let userAgent = Web.getUserAgent(); + /* + * Here's one case where we have to be careful with Component, because when isUserAgent() is called by + * the init code below, component.js hasn't been loaded yet. The simple solution for now is to remove the call. + * + * Web.log("agent: " + userAgent); + * + * And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure + * Compiler (v20130823) failing to detect the entire expression as a boolean. + */ + return s == "iOS" && !!userAgent.match(/(iPod|iPhone|iPad)/) && !!userAgent.match(/AppleWebKit/) || s == "MSIE" && !!userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0); + } + return false; + } + + /** + * isMobile(sDevice) + * + * Checks the URL for a "mobile" parameter, and failing that, checks the browser's user-agent string for the + * substring "Mobi", as per Mozilla recommendation: + * + * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent + * + * @param {string} [sDevice] (eg, "iPad" to check for iPad, or "!iPad" to specifically exclude it) + * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not + */ + static isMobile(sDevice) + { + let sMobile = Web.getURLParm("mobile"); + if (sMobile) return sMobile == "true"; + if (Web.isUserAgent("Mobi")) { + if (!sDevice) return true; + let fInvert = sDevice[0] == '!'; + if (fInvert) sDevice = sDevice.substr(1); + return Web.isUserAgent(sDevice) != fInvert; + } + return false; + } + + /** + * findProperty(obj, sProp, sSuffix) + * + * If both sProp and sSuffix are set, then any browser-specific prefixes are inserted between sProp and sSuffix, + * and if a match is found, it is returned without sProp. + * + * For example, if findProperty(document, 'on', 'fullscreenchange') discovers that 'onwebkitfullscreenchange' exists, + * it will return 'webkitfullscreenchange', in preparation for an addEventListener() call. + * + * More commonly, sSuffix is not used, so whatever property is found is returned as-is. + * + * @param {Object|null|undefined} obj + * @param {string} sProp + * @param {string} [sSuffix] + * @return {string|null} + */ + static findProperty(obj, sProp, sSuffix) + { + if (obj) { + for (let i = 0; i < Web.asBrowserPrefixes.length; i++) { + let sName = Web.asBrowserPrefixes[i]; + if (sSuffix) { + sName += sSuffix; + let sEvent = sProp + sName; + if (sEvent in obj) return sName; + } else { + if (!sName) { + sName = sProp[0]; + } else { + sName += sProp[0].toUpperCase(); + } + sName += sProp.substr(1); + if (sName in obj) return sName; + } + } + } + return null; + } + + /** + * getURLParm(sParm) + * + * First looks for sParm exactly as specified, then looks for the lower-case version. + * + * @param {string} sParm + * @return {string|undefined} + */ + static getURLParm(sParm) + { + if (!Web.parmsURL) { + Web.parmsURL = Web.parseURLParms(); + } + return Web.parmsURL[sParm] || Web.parmsURL[sParm.toLowerCase()]; + } + + /** + * parseURLParms(sParms) + * + * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?') + * @return {Object} containing properties for each parameter found + */ + static parseURLParms(sParms) + { + let aParms = {}; + if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used + if (!sParms) { + /* + * Note that window.location.href returns the entire URL, whereas window.location.search + * returns only the parameters, if any (starting with the '?', which we skip over with a substr() call). + */ + sParms = window.location.search.substr(1); + } + let match; + let pl = /\+/g; // RegExp for replacing addition symbol with a space + let search = /([^&=]+)=?([^&]*)/g; + let decode = function(s) + { + return decodeURIComponent(s.replace(pl, " ")); + }; + + while ((match = search.exec(sParms))) { + aParms[decode(match[1])] = decode(match[2]); + } + } + return aParms; + } + + /** + * downloadFile(sData, sType, fBase64, sFileName) + * + * @param {string} sData + * @param {string} sType + * @param {boolean} [fBase64] + * @param {string} [sFileName] + */ + static downloadFile(sData, sType, fBase64, sFileName) + { + let link = null, sAlert; + let sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ","; + + if (typeof sData != 'string' + && typeof Blob == 'function' && typeof URL != 'undefined' && URL && typeof URL.createObjectURL == 'function') { + let blob = new Blob([sData], { type: 'application/octet-stream' }); + sURI = URL.createObjectURL(blob); + } + else if (!Web.isUserAgent("Firefox")) { + sURI += (fBase64? sData : encodeURI(sData)); + } else { + sURI += (fBase64? sData : encodeURIComponent(sData)); + } + if (sFileName) { + link = document.createElement('a'); + if (typeof link.download != 'string') link = null; + } + if (link) { + link.href = sURI; + link.download = sFileName; + document.body.appendChild(link); // Firefox allegedly requires the link to be in the body + link.click(); + document.body.removeChild(link); + sAlert = 'Check your Downloads folder for ' + sFileName + '.'; + // if (Web.isUserAgent("Chrome")) { + // sAlert += '\n\nIn Chrome, after clicking OK, you may ALSO have to select the "Window" menu, choose "Downloads", and then locate this download and select "Keep".'; + // sAlert += '\n\nThis is part of Chrome\'s "Security By Jumping Through Extra Hoops" technology, which is much easier for Google to implement than actually checking for something malicious.'; + // sAlert += '\n\nAnd for the record, there is nothing malicious on the PCjs website.'; + // } + } else { + window.open(sURI); + sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.'; + } + return sAlert; + } + + /** + * onCountRepeat(n, fnRepeat, fnComplete, msDelay) + * + * Call fnRepeat() n times with an msDelay millisecond delay between calls, + * then call fnComplete() when n has been exhausted OR fnRepeat() returns false. + * + * @param {number} n + * @param {function()} fnRepeat + * @param {function()} fnComplete + * @param {number} [msDelay] + */ + static onCountRepeat(n, fnRepeat, fnComplete, msDelay) + { + let fnTimeout = function doCountRepeat() + { + n -= 1; + if (n >= 0) { + if (!fnRepeat()) n = 0; + } + if (n > 0) { + setTimeout(fnTimeout, msDelay || 0); + return; + } + fnComplete(); + }; + fnTimeout(); + } + + /** + * onClickRepeat(e, msDelay, msRepeat, fn) + * + * Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter, + * as long as HTML control Object e has an active "down" event and fn() returns true. + * + * @param {Object} e + * @param {number} msDelay + * @param {number} msRepeat + * @param {function(boolean)} fn is passed false on the first call, true on all repeated calls + */ + static onClickRepeat(e, msDelay, msRepeat, fn) + { + let ms = 0, timer = null, fIgnoreMouseEvents = false; + + let fnRepeat = function doClickRepeat() + { + if (fn(ms === msRepeat)) { + timer = setTimeout(fnRepeat, ms); + ms = msRepeat; + } + }; + e.onmousedown = function() + { + // Web.log("onMouseDown()"); + if (!fIgnoreMouseEvents) { + if (!timer) { + ms = msDelay; + fnRepeat(); + } + } + }; + e.ontouchstart = function() + { + // Web.log("onTouchStart()"); + if (!timer) { + ms = msDelay; + fnRepeat(); + } + }; + e.onmouseup = e.onmouseout = function() + { + // Web.log("onMouseUp()/onMouseOut()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + e.ontouchend = e.ontouchcancel = function() + { + // Web.log("onTouchEnd()/onTouchCancel()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + /* + * Devices that generate ontouch* events ALSO generate onmouse* events, + * and generally do so immediately after all the touch events are complete, + * so unless we want double the action, we need to ignore mouse events. + */ + fIgnoreMouseEvents = true; + }; + } + + /** + * onPageEvent(sName, fn) + * + * For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but + * instead use Web.onInit(), Web.onShow(), and Web.onExit(), respectively. + * + * The only components that should still use onPageEvent() are THIS component (see the bottom of this file) + * and components that need to capture other events (eg, the 'onresize' event in the Video component). + * + * This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers + * for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'], + * etc, themselves. However, that's less of a concern now, because assuming everyone else is now using + * onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one. + * + * NOTE: It's risky to refer to obscure event handlers with "dot" names, because the Closure Compiler may + * erroneously replace them (eg, window.onpageshow is a good example). + * + * @param {string} sFunc + * @param {function()} fn + */ + static onPageEvent(sFunc, fn) + { + if (window) { + let fnPrev = window[sFunc]; + if (typeof fnPrev !== 'function') { + window[sFunc] = fn; + } else { + /* + * TODO: Determine whether there's any value in receiving/sending the Event object that the + * browser provides when it generates the original event. + */ + window[sFunc] = function onWindowEvent() + { + if (fnPrev) fnPrev(); + fn(); + }; + } + } + }; + + /** + * onInit(fn) + * + * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler. + * + * @param {function()} fn + */ + static onInit(fn) + { + Web.aPageEventHandlers['init'].push(fn); + }; + + /** + * onShow(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler. + */ + static onShow(fn) + { + Web.aPageEventHandlers['show'].push(fn); + }; + + /** + * onError(sMessage) + * + * @param {string} sMessage + */ + static onError(sMessage) + { + Web.notice(sMessage + "\n\nIf it happens again, please send the URL to support@pcjs.org. Thanks."); + } + + /** + * onExit(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler. + */ + static onExit(fn) + { + Web.aPageEventHandlers['exit'].push(fn); + }; + + /** + * doPageEvent(afn) + * + * @param {Array.} afn + */ + static doPageEvent(afn) + { + if (Web.fPageEventsEnabled) { + try { + for (let i = 0; i < afn.length; i++) { + afn[i](); + } + } catch (e) { + Web.onError("An unexpected error occurred: " + e.message); + } + } + }; + + /** + * enablePageEvents(fEnable) + * + * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default) + */ + static enablePageEvents(fEnable) + { + if (!Web.fPageEventsEnabled && fEnable) { + Web.fPageEventsEnabled = true; + if (Web.fPageLoaded) Web.sendPageEvent('init'); + if (Web.fPageShowed) Web.sendPageEvent('show'); + return; + } + Web.fPageEventsEnabled = fEnable; + } + + /** + * sendPageEvent(sEvent) + * + * This allows us to manually trigger page events. + * + * @param {string} sEvent (one of 'init', 'show' or 'exit') + */ + static sendPageEvent(sEvent) + { + if (Web.aPageEventHandlers[sEvent]) { + Web.doPageEvent(Web.aPageEventHandlers[sEvent]); + } + } +} + +Web.parmsURL = null; // initialized on first call to parseURLParms() + +Web.aPageEventHandlers = { + 'init': [], // list of window 'onload' handlers + 'show': [], // list of window 'onpageshow' handlers + 'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible) +}; + +Web.asBrowserPrefixes = ['', 'moz', 'ms', 'webkit']; + +Web.fPageLoaded = false; // set once the page's first 'onload' event has occurred +Web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred +Web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents() + +/** + * fLocalStorage + * + * true if localStorage support exists, is enabled, and works; "falsey" otherwise + * + * @type {boolean|null} + */ +Web.fLocalStorage = null; + +/** + * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This isn't cutting it. + * + * @const {string} + */ +Web.sLocalStorageTest = "PCjs.localStorage"; + +Web.onPageEvent('onload', function onPageLoad() { + Web.fPageLoaded = true; + Web.doPageEvent(Web.aPageEventHandlers['init']); +}); + +Web.onPageEvent('onpageshow', function onPageShow() { + Web.fPageShowed = true; + Web.doPageEvent(Web.aPageEventHandlers['show']); +}); + +Web.onPageEvent(Web.isUserAgent("iOS")? 'onpagehide' : (Web.isUserAgent("Opera")? 'onunload' : 'onbeforeunload'), function onPageUnload() { + Web.doPageEvent(Web.aPageEventHandlers['exit']); +}); + +/* + * If this is DEBUG (eg, un-COMPILED) code, then allow the user to override DEBUG with a "debug=false" embedded in + * the URL; note that the Closure Compiler won't let us alter the DEBUG variable, because it's defined as a @define, which + * implies @const as well, so we must resort to modifying it indirectly, using the global window object. + * + * TODO: Consider yet another embedXXX() parameter that would also allow DEBUG to be turned off on a page-by-page basis; + * it's low priority, because it would only affect machines that explicitly request un-COMPILED code, and there are very + * few such machines (eg, /_posts/2015-01-17-pcjs-uncompiled.md). + * + * Deal with Web.getURLParm("backtrack") in /modules/pcx86/lib/defines.js at the same time. + */ +if (DEBUG && window) { + let sDebug = Web.getURLParm("debug"); + if (sDebug == "false") { + window['DEBUG'] = false; + } +} + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2018 + */ + +/* + * All PCjs components now use JSDoc types, primarily so that Google's Closure Compiler will compile + * everything with zero warnings when ADVANCED_OPTIMIZATIONS are enabled. For more information about + * the JSDoc types supported by the Closure Compiler: + * + * https://developers.google.com/closure/compiler/docs/js-for-compiler#types + * + * I also attempted to validate this code with JSLint, but it complained too much; eg, it didn't like + * "while (true)", a tried and "true" programming convention for decades, and it wanted me to replace + * all "++" and "--" operators with "+= 1" and "-= 1", use "(s || '')" instead of "(s? s : '')", etc. + * + * I prefer sticking with traditional C-style idioms, in part because they are more portable. That + * does NOT mean I'm trying to write "portable JavaScript," but some of this code was ported from C code + * I'd written long ago, so portability is good, and I'm not going to throw that away if there's no need. + * + * UPDATE: I've since switched from JSLint to JSHint, which seems to have more reasonable defaults. + * And for new code, I have adopted some popular JavaScript idioms, like "(s || '')", although the need + * for those kinds of expressions will be reduced as I also start adopting some ES6 features, like + * default parameters. + */ + + + +/** + * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, + * it deters us from defining named properties on our components; eg: + * + * this['exports'] = {...} + * + * results in an error: + * + * Cannot do '[]' access on a struct + * + * So, in order to define 'exports', we must override the @struct assumption by annotating + * the class as @unrestricted (or @dict). Note that this must be done both here and in the + * subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference* + * the named property either. + * + * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to + * define every single property the class uses in its constructor, which results in a fair + * bit of redundant initialization, since many properties aren't (and don't need to be) fully + * initialized until the appropriate init(), reset(), restore(), etc. function is called. + * + * The upside, however, may be that since the structure of the class is completely defined by + * the constructor, JavaScript engines may be able to optimize and run more efficiently. + * + * @unrestricted + */ +class Component { + /** + * Component(type, parms, bitsMessage) + * + * A Component object requires: + * + * type: a user-defined type name (eg, "CPU") + * + * and accepts any or all of the following (parms) properties: + * + * id: component ID (default is "") + * name: component name (default is ""; if blank, toString() will use the type name only) + * comment: component comment string (default is undefined) + * + * Component subclasses will usually have additional (parms) properties. + * + * @param {string} type + * @param {Object} [parms] + * @param {number} [bitsMessage] selects message(s) that the component wants to enable (default is 0) + */ + constructor(type, parms, bitsMessage) + { + this.type = type; + + if (!parms) parms = {'id': "", 'name': ""}; + + this.id = parms['id'] || ""; + this.name = parms['name']; + this.comment = parms['comment']; + this.parms = parms; + + /* + * The following Component properties need to be accessible by other machines and/or command scripts; + * well, OK, or we could have exported some new functions to walk the contents of these properties, as we + * did with findMachineComponent(), but this works just as well. + * + * Also, while the double-assignment looks silly (ie, using both dot and bracket property notation), it + * resolves a complaint from the Closure Compiler, because if we use ONLY bracket notation here, then the + * Compiler wants us to change all the other references to bracket notation as well. + */ + this.exports = this['exports'] = {}; + this.bindings = this['bindings'] = {}; + + let i = this.id.indexOf('.'); + if (i < 0) { + this.idMachine = "PCjs"; + this.idComponent = this.id; + } else { + this.idMachine = this.id.substr(0, i); + this.idComponent = this.id.substr(i + 1); + } + + /* + * Gather all the various component flags (booleans) into a single "flags" object, and encourage + * subclasses to do the same, to reduce the property clutter we have to wade through while debugging. + */ + this.flags = { + ready: false, + busy: false, + busyCancel: false, + initDone: false, + powered: false, + unloading: false, + error: false + }; + + this.fnReady = null; + this.clearError(); + this.bitsMessage = bitsMessage || 0; + + this.cmp = null; + this.bus = null; + this.cpu = null; + this.dbg = null; + + /* + * TODO: Consider adding another parameter to the Component() constructor that allows components to tell + * us if they support single or multiple instances per machine. For example, there can be multiple SerialPort + * components per machine, but only one CPU component (some machines also support an FPU, but that component + * is considered separate from the CPU). + * + * It's not critical, but it would help catch machine configuration errors; for example, a machine that mistakenly + * includes two CPU components may, aside from wasting memory, end up with odd side-effects, like unresponsive + * CPU controls. + */ + Component.add(this); + } + + /** + * Component.add(component) + * + * @param {Component} component + */ + static add(component) + { + /* + * This just generates a lot of useless noise, handy in the early days, not so much these days.... + * + * if (DEBUG) Component.log("Component.add(" + component.type + "," + component.id + ")"); + */ + Component.components.push(component); + } + + /** + * Component.addMachine(idMachine) + * + * @param {string} idMachine + */ + static addMachine(idMachine) + { + Component.machines[idMachine] = {}; + } + + /** + * Component.getMachines() + * + * @return {Array.} + */ + static getMachines() + { + return Object.keys(Component.machines); + } + + /** + * Component.addMachineResource(idMachine, sName, data) + * + * @param {string} idMachine + * @param {string|null} sName (name of the resource) + * @param {*} data + */ + static addMachineResource(idMachine, sName, data) + { + /* + * I used to assert(Component.machines[idMachine]), but when we're running as a Node app, embed.js is not used, + * so addMachine() is never called, so resources do not need to be recorded. + */ + if (Component.machines[idMachine] && sName) { + Component.machines[idMachine][sName] = data; + } + } + + /** + * Component.getMachineResources(idMachine) + * + * @param {string} idMachine + * @return {Object|undefined} + */ + static getMachineResources(idMachine) + { + return Component.machines[idMachine]; + } + + /** + * Component.getTime() + * + * @return {number} the current time, in milliseconds + */ + static getTime() + { + return Date.now() || +new Date(); + } + + /** + * Component.log(s, type) + * + * For diagnostic output only. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + if (!COMPILED) { + if (s) { + let sElapsed = "", sMsg = (type? (type + ": ") : "") + s; + if (typeof Usr != "undefined") { + if (Component.msStart === undefined) { + Component.msStart = Component.getTime(); + } + sElapsed = (Component.getTime() - Component.msStart) + "ms: "; + } + sMsg = sMsg.replace(/\r/g, '\\r').replace(/\n/g, ' '); + if (window && window.console) console.log(sElapsed + sMsg); + } + } + } + + /** + * Component.assert(f, s) + * + * Verifies conditions that must be true (for DEBUG builds only). + * + * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds. + * TODO: Add a task to the build process that "asserts" there are no instances of "assertion failure" in RELEASE builds. + * + * @param {boolean} f is the expression we are asserting to be true + * @param {string} [s] is description of the assertion on failure + */ + static assert(f, s) + { + if (DEBUG) { + if (!f) { + if (!s) s = "assertion failure"; + Component.log(s); + throw new Error(s); + } + } + } + + /** + * Component.print(s) + * + * Components that inherit from this class should use this.print(), rather than Component.print(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @this {Component} + * @param {string} s + */ + static print(s) + { + if (!COMPILED) { + let i = s.lastIndexOf('\n'); + if (i >= 0) { + Component.println(s.substr(0, i)); + s = s.substr(i + 1); + } + Component.printBuffer += s; + } + } + + /** + * Component.println(s, type, id) + * + * Components that inherit from this class should use this.println(), rather than Component.println(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + * @param {string} [id] is the caller's ID, if any + */ + static println(s, type, id) + { + if (!COMPILED) { + s = Component.printBuffer + (s || ""); + Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type); + Component.printBuffer = ""; + } + } + + /** + * Component.notice(s, fPrintOnly, id) + * + * notice() is like println() but implies a need for user notification, so we alert() as well. + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + * @return {boolean} + */ + static notice(s, fPrintOnly, id) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.NOTICE, id); + } + if (!fPrintOnly) Component.alertUser((id? (id + ": ") : "") + s); + return true; + } + + /** + * Component.warning(s) + * + * @param {string} s describes the warning + */ + static warning(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.WARNING); + } + Component.alertUser(s); + } + + /** + * Component.error(s) + * + * @param {string} s describes the error; an alert() is displayed as well + */ + static error(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.ERROR); + } + Component.alertUser(s); + } + + /** + * Component.alertUser(sMessage) + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Component.log(sMessage); + } + } + + /** + * Component.confirmUser(sPrompt) + * + * @param {string} sPrompt + * @returns {boolean} true if the user clicked OK, false if Cancel/Close + */ + static confirmUser(sPrompt) + { + let fResponse = false; + if (window) { + fResponse = window.confirm(sPrompt); + } + return fResponse; + } + + /** + * Component.promptUser() + * + * @param {string} sPrompt + * @param {string} [sDefault] + * @returns {string|null} + */ + static promptUser(sPrompt, sDefault) + { + let sResponse = null; + if (window) { + sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault); + } + return sResponse; + } + + /** + * Component.appendControl(control, sText) + * + * @param {Object} control + * @param {string} sText + */ + static appendControl(control, sText) + { + control.value += sText; + /* + * Prevent the + + +
    +
    + +
    +
    + + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + + + +
    +
    + +
    +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + ; + + + + + + + + + + + + + + + : + + + + + + + + + + + + + + + + + desc:'' + + ,href:'' + + + + + + + + + + + + ; + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8088 + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + 1 + + + + + + + null + + + + + + 0 + + + + + + + -1 + + + + + + + -1 + + + + + + + -1 + + + + + + ,model:'',stepping:'',fpu:,cycles:,multiplier:,autoStart:,addrReset:,csStart:,csInterval:,csStop: + + + + + + + + + + + + + + + 8087 + + + + + + + + + + + + ,model:'',stepping:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + true + + + + + + + false + + + + + + {} + + + + + + + + + + + + + + + + + + + chipset + ,model:'',scaleTimers:,sw1:'',sw2:'',sound:,floppies:,monitor:'',dateRTC:'' + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + device + ,type:'',baudReceive:,baudTransmit:,autoMount:'' + + + + + + + + + + + + + + + + + + + + keyboard + ,model:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + parallel + ,adapter:,binding:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + + 0 + + + + + + + 0 + + + + + + + false + + + + + serial + ,adapter:,baudReceive:,baudTransmit:,binding:'',tabSize:,charBOL:,upperCase: + + + + + + + + + + + + + + + + + + + + + + + + + + testctl + ,binding:'',tests:'' + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + 0.5 + + + + + + + + + + + mouse + ,adapter:,binding:'',type:'',scaleMouse:,serial:'' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fdc + ,autoMount:'',sortBy:'' + + + + + + + + + + + + + + + + + + + + + XT + + + + + hdc + ,drives:'',type:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + null + + + + + + + + + + + + + + + + + rom + ,addr:,size:,alias:,file:'',notify:'' + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + null + + + + + + null + + + + + + true + + + + + ram + ,addr:,size:,file:'',load:,exec:,test: + + + + + + + + + + + + + + + + + + + + + null + + + + + + + 256 + + + + + + + 224 + + + + + + + black + + + + + + 0 + + + + + + 0 + + + + + + false + + + + + + 1bpp + + + + + + 0 + + + + + + 0 + + + + + + 1 + + + + + + 0 + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + false + + + + + + 1 + + + + + + 1 + + + + + + + 80 + + + + + + + 25 + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + 0 + + + + + + null + + + + + + 0 + + + + + + 60 + + + + + + 0.5 + + + + + video + + + ,model:'',mode:,screenWidth:,screenHeight:,screenColor:'',screenRotate:,bufferAddr:,bufferRAM:,bufferFormat:'',bufferCols:,bufferRows:,bufferBits:,bufferLeft:,bufferRotate:,memory:,switches:'',scale:,cellWidth:,cellHeight:,charCols:,charRows:,fontROM:'',fontColor:'',touchScreen:'',autoLock:,aspectRatio:,smoothing:,interruptRate:,refreshRate:,flicker: + + + + + + + + + + + + + + + 16 + + + + + + + + + + + + + + + + + debugger + ,base:,commands:'',messages:'' + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + true + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + computer + ,autoPower:,busWidth:,resume:'',state:'' + + + + +
    +
    + + + + + + + + + + <></> + + +
    <machine></machine>
    +
    + + + + diff --git a/versions/pdpjs/1.71.5/document.css b/versions/pdpjs/1.71.5/document.css new file mode 100644 index 0000000000..4281cab24f --- /dev/null +++ b/versions/pdpjs/1.71.5/document.css @@ -0,0 +1,162 @@ +@CHARSET "UTF-8"; + +.page { + margin: 2% 2%; + padding: 2% 2%; + min-width: 30em; + overflow: auto; + font-size: large; + font-family: Helvetica, Arial, sans-serif; + background: #303030; + color: #ccc; + +} +.page-header { +} +.page-header-title { + text-align: center; + +} +.page a { + color: #7fc07f; + text-decoration: none; +} +a.footlink, a.paralink { + text-decoration: none; +} +a.footlink:link, a.paralink:link { + color: blue; +} +a.footlink:visited, a.paralink:visited { + color: blue; +} +.galleryitem { + float: left; + width: 200px; +} +.item { + float: left; + width: 2em; + text-indent: 1em; +} +.list { + margin-left: 3em; + text-indent: 0; + text-align: justify; +} +ul { + list-style: none; +} +div.pnumber { + float: left; + width: 2em; + text-indent: 1em; +} +div.pitem { + margin-left: 10em; +} +p.indent, .justified p { + text-indent: 2em; + text-align: justify; + line-height: 1.5em; +} +p.noindent { + text-indent: 0; + text-align: justify; +} +p.center, .center { + text-align: center; +} +li.para { + margin-top: 1em; + margin-bottom: 1em; +} +.left { + text-align: left; +} +.right { + text-align: right; +} +blockquote.tag { + font-size: small; + font-family: Monaco, Fixed, monospace; + margin-top: 0; + margin-bottom: 0; +} +.blockquote { + padding-left: 1em; + text-indent: 0; + text-align: justify; +} +.italics { + font-style: italic; +} +.medium { + font-size: medium; +} +.small { + font-size: x-small; +} +.smallcaps { + font-variant: small-caps; +} +.strike { + text-decoration: line-through; +} +.summation, .bracelist { + display: inline-block; + position: relative; + vertical-align: middle; + text-align: center; + margin-bottom: 0.5ex; + text-indent: 0; +} +.bracelist-symbol { + font-size: 3em; + vertical-align: -40%; +} +.summation .summation-lower, .summation .summation-upper, .bracelist-item { + display: block; + font-size: 75%; + text-align: center; +} +.summation .summation-upper { + margin-bottom: 0; + margin-left: 0.8ex; + font-style: italic; +} +.summation .summation-lower{ + margin-bottom: -0.6ex; + font-style: italic; +} +.summation .summation-symbol { + font-size: 2em; +} +p sup { + vertical-align: baseline; + position: relative; + bottom: .5em; + font-size: small; +} +p sub { + vertical-align: baseline; + position: relative; + bottom: -.5em; + font-size: small; +} +.footnote { + font-size: medium; + text-indent: 1em; + text-align: justify; + margin-top: .5em; +} +.image-right { + float: right; + margin-left: 1em; + margin-top: 1em; + margin-bottom: 1em; +} +.image-caption { + font-size: small; + text-align: center; +} diff --git a/versions/pdpjs/1.71.5/document.xsl b/versions/pdpjs/1.71.5/document.xsl new file mode 100644 index 0000000000..39f738a5b1 --- /dev/null +++ b/versions/pdpjs/1.71.5/document.xsl @@ -0,0 +1,452 @@ + + + + + +]> + + + + + + + + + +

    +
    + + + + + + + +

    +
    + +

    +
    +
    +
    + + + + + + +
    +
    + + +
    + +   + + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + +

    +
    + + +

    +
    + + +

    +
    + + +
    +
    + + +
    +
    + + + + + + + + + + + + + + +
    +
    + + +
    +
    + + +
  • +
    + + +
    image
    +
    + + +
    +
    + + + + +
    {.}
    +
    + +
    {.}
    +
    +
    +
    + + + + + + + + + + < + > + + + + × + + ÷ + σ + + + + + + + + + + + + { + + + + + + + + + + [] + + + + +
    + +
    +
    + + + , and + + + + + MDY + + + + + + + + + + + + + + + + + + + + January + February + March + April + May + June + July + August + September + October + November + December + + + , + + + + + +

    + +
    +
    + + +
    + {.}
    +
    +
    +
    + + + +

    Timeline

    +
    + +

    +
    +
    + +
    +
    + + + + + + + + + +

    +
    + +
    +
    +
    + + + +

    People

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + +

    +
      + +
    +
    + + + + + + + + + + +
  • + +
  • +
    + + + +

    +
    +

    + +

    +
    +
    + + + + false + + + + + + [Original] + + + + + + + + + + [] + + +
    by
    + + +
    + [Source: + + + + + + + ] +
    +
    +
    + + + +

    Resources

    +
    + +

    +
    +
    + +
    +
    + + +

    + +
    + + + +

    +
    +
      + +
    +
    + + +
  • +
    + + + +

    +
    +
    + +
    +
    + + + +

    +
    + +
    + + + +

    +
    +
      + +
    +
    + + + + + +
      + +
    +
    + + + + +
  • +
    + +
  • +
    + +
  • +
    +
    +
    + + +
  • +
    + + + + + + + + + + +
    + < ="" + + ></> + ></> + /> + +
    +
    + +
    diff --git a/versions/pdpjs/1.71.5/machine.xsl b/versions/pdpjs/1.71.5/machine.xsl new file mode 100644 index 0000000000..a83efe9c6f --- /dev/null +++ b/versions/pdpjs/1.71.5/machine.xsl @@ -0,0 +1,61 @@ + + + + +]> + + + + + + + + + + + + + + + + + js + + + + + + PCjs + + + + +
    + +
    +
    + + + + + , + +
    +
    + + +
    + + + + + + + + + + +
    + +
    diff --git a/versions/pdpjs/1.71.5/manifest.xsl b/versions/pdpjs/1.71.5/manifest.xsl new file mode 100644 index 0000000000..bfdc4184e0 --- /dev/null +++ b/versions/pdpjs/1.71.5/manifest.xsl @@ -0,0 +1,247 @@ + + + + +]> + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Document Manifest

    +
    +
      + + + + None + + + + + + + + + + + + + + + + +
    +
    +
    +

    + +
    +
    +
    + + +
    + + + + + + + + + + + PCjs + + + + +
    + +
    +

    Software Manifest

    +
    +
      + + + + None + + + + + Unknown + + + + + None + + + + + None + + + + + + + + + + + + + UpdatedReleased + + Unknown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + + + + +

    No default machine specified for '' in manifest.xml

    +
    + +
    +
    +
    + + + + -dbg + + + + + + +
    + + + + + Unknown + +
  • +
      + + + + + + + + +
    • + + + + + + + + + + + + + + + + + + + + + + + + + +
        + +
      • + + + + + + +
      • +
        +
      +
      +
    • +
      + + + + + + + + +
    +
  • +
    +
    + +
    diff --git a/versions/pdpjs/1.71.5/outline.xsl b/versions/pdpjs/1.71.5/outline.xsl new file mode 100644 index 0000000000..d3508685e6 --- /dev/null +++ b/versions/pdpjs/1.71.5/outline.xsl @@ -0,0 +1,47 @@ + + + + +]> + + + + + + + + + + + + + + + + + + PCjs<xsl:text> | </xsl:text><xsl:value-of select="title"/> + + + + + +
    +
    + +
    +
    + + + + -dbg + + + + + + +
    + +
    diff --git a/versions/pdpjs/1.71.5/pdp10-uncompiled.js b/versions/pdpjs/1.71.5/pdp10-uncompiled.js new file mode 100644 index 0000000000..3ed0efb3c2 --- /dev/null +++ b/versions/pdpjs/1.71.5/pdp10-uncompiled.js @@ -0,0 +1,28689 @@ +"use strict"; + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @define {string} + */ +var APPVERSION = ""; // this @define is overridden by the Closure Compiler with the version in machines.json + +var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML + +var COPYRIGHT = "Copyright © 2012-2018 Jeff Parsons "; + +var LICENSE = "License: GPL version 3 or later "; + +var CSSCLASS = "pcjs"; + +/** + * @define {string} + */ +var SITEURL = "http://localhost:8088"; // this @define is overridden by the Closure Compiler with "https://www.pcjs.org" + +/** + * @define {boolean} + */ +var COMPILED = false; // this @define is overridden by the Closure Compiler (to true) + +/** + * @define {boolean} + */ +var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code + +/** + * @define {boolean} + */ +var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code + +/** + * @define {boolean} + */ +var PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code + +/* + * RS-232 DB-25 Pin Definitions, mapped to bits 1-25 in a 32-bit status value. + * + * SerialPorts in PCjs machines are considered DTE (Data Terminal Equipment), which means they should be "virtually" + * connected to each other via a null-modem cable, which assumes the following cross-wiring: + * + * G 1 <-> 1 G (Ground) + * TD 2 <-> 3 RD (Received Data) + * RD 3 <-> 2 TD (Transmitted Data) + * RTS 4 <-> 5 CTS (Clear To Send) + * CTS 5 <-> 4 RTS (Request To Send) + * DSR 6+8 <-> 20 DTR (Data Terminal Ready) + * SG 7 <-> 7 SG (Signal Ground) + * DTR 20 <-> 6+8 DSR (Data Set Ready + Carrier Detect) + * RI 22 <-> 22 RI (Ring Indicator) + * + * TODO: Move these definitions to a more appropriate shared file at some point. + */ +var RS232 = { + RTS: { + PIN: 4, + MASK: 0x00000010 + }, + CTS: { + PIN: 5, + MASK: 0x00000020 + }, + DSR: { + PIN: 6, + MASK: 0x00000040 + }, + CD: { + PIN: 8, + MASK: 0x00000100 + }, + DTR: { + PIN: 20, + MASK: 0x00100000 + }, + RI: { + PIN: 22, + MASK: 0x00400000 + } +}; + +/* + * NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser) + */ +var NODE = false; + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Our "DiskDump API", such as it was, used to look like: + * + * http://pcjs.org/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * To make it (a bit) more "REST-like", the above request now looks like: + * + * https://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img + * + * Similarly, our "FileDump API" used to look like: + * + * http://pcjs.org/bin/convrom.php?rom=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * and that request now looks like: + * + * https://www.pcjs.org/api/v1/dump?file=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json + * + * I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the + * the rest of the URL would be (a) confusing, and (b) more work to parse. + */ +var DumpAPI = { + ENDPOINT: "/api/v1/dump", + QUERY: { + DIR: "dir", // value is path of a directory (DiskDump only) + DISK: "disk", // value is path of a disk image (DiskDump only) + FILE: "file", // value is path of a ROM image file (FileDump only) + IMG: "img", // alias for DISK + PATH: "path", // value is path of a one or more files (DiskDump only) + FORMAT: "format", // value is one of FORMAT values below + COMMENTS: "comments", // value is either "true" or "false" + DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise + MBHD: "mbhd", // value is hard drive size in Mb (formerly "mbsize") (DiskDump only) (DEPRECATED) + SIZE: "size" // value is target disk size in Kb (supersedes "mbhd") (DiskDump only) + }, + FORMAT: { + JSON: "json", // default + JSON_GZ: "gz", // gzip is currently used ONLY for compressed JSON + DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only) + HEX: "hex", // deprecated + OCTAL: "octal", // displays data as octal words + BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled + WORDS: "words", // displays data as hex words; normally used only when comments are enabled + LONGS: "longs", // displays data as dwords + IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only) + ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only) + } +}; + +/* + * Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must + * also provide a list of commands which, when combined with the endpoint, define a unique request. + */ +DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH]; +DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE]; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2018 + */ + +var ReportAPI = { + ENDPOINT: "/api/v1/report", + QUERY: { + APP: "app", + VER: "ver", + URL: "url", + USER: "user", + TYPE: "type", + DATA: "data" + }, + TYPE: { + BUG: "bug" + }, + RES: { + OK: "Thank you" + } +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2018 + */ + +/* + * Examples of User API requests: + * + * web.getHostOrigin() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser; + */ +var UserAPI = { + ENDPOINT: "/api/v1/user", + QUERY: { + REQ: "req", // specifies a request + USER: "user", // specifies a user ID + STATE: "state", // specifies a state ID + DATA: "data" // specifies state data + }, + REQ: { + CREATE: "create", // creates a user ID + VERIFY: "verify", // requests verification of a user ID + STORE: "store", // stores a machine state on the server + LOAD: "load" // loads a machine state from the server + }, + RES: { + CODE: "code", + DATA: "data" + }, + CODE: { + OK: "ok", + FAIL: "error" + }, + FAIL: { + DUPLICATE: "user already exists", + VERIFY: "unable to verify user", + BADSTATE: "invalid state parameter", + NOSTATE: "no machine state", + BADLOAD: "unable to load machine state", + BADSTORE: "unable to save machine state" + } +}; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2018 + */ + +var Keys = { + /* + * Keys and/or key combinations that generate common ASCII codes. + * + * NOTE: If you're looking for a general-purpose ASCII code table, see Str.ASCII in strlib.js; + * if something's missing, that's probably the more appropriate table to add it to. + * + * TODO: The Closure Compiler doesn't inline all references to these values, at least those with + * quoted property names, which is why I've 'unquoted' as many of them as possible. One solution + * would be to add mnemonics for all of them, not just the non-printable ones (eg, SPACE instead + * of ' ', AMP instead of '&', etc.) + */ + ASCII: { + BREAK: 0, CTRL_A: 1, CTRL_B: 2, CTRL_C: 3, CTRL_D: 4, CTRL_E: 5, CTRL_F: 6, CTRL_G: 7, + CTRL_H: 8, CTRL_I: 9, CTRL_J: 10, CTRL_K: 11, CTRL_L: 12, CTRL_M: 13, CTRL_N: 14, CTRL_O: 15, + CTRL_P: 16, CTRL_Q: 17, CTRL_R: 18, CTRL_S: 19, CTRL_T: 20, CTRL_U: 21, CTRL_V: 22, CTRL_W: 23, + CTRL_X: 24, CTRL_Y: 25, CTRL_Z: 26, ESC: 27, + ' ': 32, '!': 33, '"': 34, '#': 35, '$': 36, '%': 37, '&': 38, "'": 39, + '(': 40, ')': 41, '*': 42, '+': 43, ',': 44, '-': 45, '.': 46, '/': 47, + '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, + '8': 56, '9': 57, ':': 58, ';': 59, '<': 60, '=': 61, '>': 62, '?': 63, + '@': 64, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, + H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, + P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, + X: 88, Y: 89, Z: 90, '[': 91, '\\':92, ']': 93, '^': 94, '_': 95, + '`': 96, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103, + h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111, + p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119, + x: 120, y: 121, z: 122, '{':123, '|':124, '}':125, '~':126, DEL: 127 + }, + /* + * Browser keyCodes we must pay particular attention to. For the most part, these are non-alphanumeric + * or function keys, some which may require special treatment (eg, preventDefault() if returning false on + * the initial keyDown event is insufficient). + * + * keyCodes for most common ASCII keys can simply use the appropriate ASCII code above. + * + * Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, browsers defined + * them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37). + */ + KEYCODE: { + /* 0x08 */ BS: 8, // BACKSPACE (ASCII.CTRL_H) + /* 0x09 */ TAB: 9, // TAB (ASCII.CTRL_I) + /* 0x0A */ LF: 10, // LINE-FEED (ASCII.CTRL_J) (Some Windows-based browsers used to generate this via CTRL-ENTER) + /* 0x0D */ CR: 13, // CARRIAGE RETURN (ASCII.CTRL_M) + /* 0x10 */ SHIFT: 16, + /* 0x11 */ CTRL: 17, + /* 0x12 */ ALT: 18, + /* 0x13 */ PAUSE: 19, // PAUSE/BREAK + /* 0x14 */ CAPS_LOCK: 20, + /* 0x1B */ ESC: 27, + /* 0x20 */ SPACE: 32, + /* 0x21 */ PGUP: 33, + /* 0x22 */ PGDN: 34, + /* 0x23 */ END: 35, + /* 0x24 */ HOME: 36, + /* 0x25 */ LEFT: 37, + /* 0x26 */ UP: 38, + /* 0x27 */ RIGHT: 39, + /* 0x27 */ FF_QUOTE: 39, + /* 0x28 */ DOWN: 40, + /* 0x2C */ FF_COMMA: 44, + /* 0x2C */ PRTSC: 44, + /* 0x2D */ INS: 45, + /* 0x2E */ DEL: 46, + /* 0x2E */ FF_PERIOD: 46, + /* 0x2F */ FF_SLASH: 47, + /* 0x30 */ ZERO: 48, + /* 0x31 */ ONE: 49, + /* 0x32 */ TWO: 50, + /* 0x33 */ THREE: 51, + /* 0x34 */ FOUR: 52, + /* 0x35 */ FIVE: 53, + /* 0x36 */ SIX: 54, + /* 0x37 */ SEVEN: 55, + /* 0x38 */ EIGHT: 56, + /* 0x39 */ NINE: 57, + /* 0x3B */ FF_SEMI: 59, + /* 0x3D */ FF_EQUALS: 61, + /* 0x5B */ CMD: 91, // aka WIN + /* 0x5B */ FF_LBRACK: 91, + /* 0x5C */ FF_BSLASH: 92, + /* 0x5D */ RCMD: 93, // aka MENU + /* 0x5D */ FF_RBRACK: 93, + /* 0x60 */ NUM_0: 96, + /* 0x60 */ NUM_INS: 96, + /* 0x60 */ FF_BQUOTE: 96, + /* 0x61 */ NUM_1: 97, + /* 0x61 */ NUM_END: 97, + /* 0x62 */ NUM_2: 98, + /* 0x62 */ NUM_DOWN: 98, + /* 0x63 */ NUM_3: 99, + /* 0x63 */ NUM_PGDN: 99, + /* 0x64 */ NUM_4: 100, + /* 0x64 */ NUM_LEFT: 100, + /* 0x65 */ NUM_5: 101, + /* 0x65 */ NUM_CENTER: 101, + /* 0x66 */ NUM_6: 102, + /* 0x66 */ NUM_RIGHT: 102, + /* 0x67 */ NUM_7: 103, + /* 0x67 */ NUM_HOME: 103, + /* 0x68 */ NUM_8: 104, + /* 0x68 */ NUM_UP: 104, + /* 0x69 */ NUM_9: 105, + /* 0x69 */ NUM_PGUP: 105, + /* 0x6A */ NUM_MUL: 106, + /* 0x6B */ NUM_ADD: 107, + /* 0x6D */ NUM_SUB: 109, + /* 0x6E */ NUM_DEL: 110, // aka PERIOD + /* 0x6F */ NUM_DIV: 111, + /* 0x70 */ F1: 112, + /* 0x71 */ F2: 113, + /* 0x72 */ F3: 114, + /* 0x73 */ F4: 115, + /* 0x74 */ F5: 116, + /* 0x75 */ F6: 117, + /* 0x76 */ F7: 118, + /* 0x77 */ F8: 119, + /* 0x78 */ F9: 120, + /* 0x79 */ F10: 121, + /* 0x7A */ F11: 122, + /* 0x7B */ F12: 123, + /* 0x90 */ NUM_LOCK: 144, + /* 0x91 */ SCROLL_LOCK: 145, + /* 0xAD */ FF_DASH: 173, + /* 0xBA */ SEMI: 186, // Firefox: 59 (FF_SEMI) + /* 0xBB */ EQUALS: 187, // Firefox: 61 (FF_EQUALS) + /* 0xBC */ COMMA: 188, + /* 0xBD */ DASH: 189, // Firefox: 173 (FF_DASH) + /* 0xBE */ PERIOD: 190, + /* 0xBF */ SLASH: 191, + /* 0xC0 */ BQUOTE: 192, + /* 0xDB */ LBRACK: 219, + /* 0xDC */ BSLASH: 220, + /* 0xDD */ RBRACK: 221, + /* 0xDE */ QUOTE: 222, + /* 0xE0 */ FF_CMD: 224, // Firefox only (used for both CMD and RCMD) + // + // The following biases use what I'll call Decimal Coded Binary or DCB (the opposite of BCD), + // where the thousands digit is used to store the sum of "binary" digits 1 and/or 2 and/or 4. + // + // Technically, that makes it DCO (Decimal Coded Octal), but then again, BCD should have really + // been called HCD (Hexadecimal Coded Decimal), so if "they" can take liberties, so can I. + // + // ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than on "press". + // + ONDOWN: 1000, + // + // ONRIGHT is a bias we add to browser keyCodes that need to check for a "right" location (default is "left") + // + ONRIGHT: 2000, + // + // FAKE is a bias we add to signal these are fake keyCodes corresponding to internal keystroke combinations. + // The actual values are for internal use only and merely need to be unique and used consistently. + // + FAKE: 4000 + }, + /* + * The set of values that a browser may store in the 'location' property of a keyboard event object + * which we also support. + */ + LOCATION: { + LEFT: 1, + RIGHT: 2, + NUMPAD: 3 + } +}; + +/* + * Check the event object's 'location' property for a non-zero value for the following ONRIGHT keys. + */ +Keys.KEYCODE.NUM_CR = Keys.KEYCODE.CR + Keys.KEYCODE.ONRIGHT; + + +/* + * Maps Firefox keyCodes to their more common keyCode counterparts; a number of entries in this table + * are no longer valid (if indeed they ever were), so they've been commented out. It's likely that I + * simply extended this table to resolve additional differences in other browsers (ie, Opera), but without + * browser-specific checks, it's not safe to perform all the mappings shown below. + */ +Keys.FF_KEYCODES = {}; +Keys.FF_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.KEYCODE.SEMI; // 59 -> 186 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.KEYCODE.EQUALS; // 61 -> 187 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.KEYCODE.DASH; // 173 -> 189 +Keys.FF_KEYCODES[Keys.KEYCODE.FF_CMD] = Keys.KEYCODE.CMD; // 224 -> 91 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_COMMA] = Keys.KEYCODE.COMMA; // 44 -> 188 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_PERIOD] = Keys.KEYCODE.PERIOD; // 46 -> 190 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_SLASH] = Keys.KEYCODE.SLASH; // 47 -> 191 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BQUOTE] = Keys.KEYCODE.BQUOTE; // 96 -> 192 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_LBRACK = Keys.KEYCODE.LBRACK; // 91 -> 219 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_BSLASH] = Keys.KEYCODE.BSLASH; // 92 -> 220 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_RBRACK] = Keys.KEYCODE.RBRACK; // 93 -> 221 +// Keys.FF_KEYCODES[Keys.KEYCODE.FF_QUOTE] = Keys.KEYCODE.QUOTE; // 39 -> 222 + +/* + * Maps non-ASCII keyCodes to their ASCII counterparts + */ +Keys.NONASCII_KEYCODES = {}; +Keys.NONASCII_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['-']; // 173 -> 45 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[';']; // 186 -> 59 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['=']; // 187 -> 61 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['-']; // 189 -> 45 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII[',']; // 188 -> 44 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['.']; // 190 -> 46 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['/']; // 191 -> 47 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['`']; // 192 -> 96 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['[']; // 219 -> 91 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['\\']; // 220 -> 92 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII[']']; // 221 -> 93 +Keys.NONASCII_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII["'"]; // 222 -> 39 + +/* + * Maps unshifted keyCodes to their shifted counterparts; to be used when a shift-key is down. + * Alphabetic characters are handled in code, since they must also take CAPS_LOCK into consideration. + */ +Keys.SHIFTED_KEYCODES = {}; +Keys.SHIFTED_KEYCODES[Keys.ASCII['1']] = Keys.ASCII['!']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['2']] = Keys.ASCII['@']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['3']] = Keys.ASCII['#']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['4']] = Keys.ASCII['$']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['5']] = Keys.ASCII['%']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['6']] = Keys.ASCII['^']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['7']] = Keys.ASCII['&']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['8']] = Keys.ASCII['*']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['9']] = Keys.ASCII['(']; +Keys.SHIFTED_KEYCODES[Keys.ASCII['0']] = Keys.ASCII[')']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SEMI] = Keys.ASCII[':']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.EQUALS] = Keys.ASCII['+']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.COMMA] = Keys.ASCII['<']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.DASH] = Keys.ASCII['_']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.PERIOD] = Keys.ASCII['>']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.SLASH] = Keys.ASCII['?']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BQUOTE] = Keys.ASCII['~']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.LBRACK] = Keys.ASCII['{']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.BSLASH] = Keys.ASCII['|']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.RBRACK] = Keys.ASCII['}']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.QUOTE] = Keys.ASCII['"']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_DASH] = Keys.ASCII['_']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_EQUALS] = Keys.ASCII['+']; +Keys.SHIFTED_KEYCODES[Keys.KEYCODE.FF_SEMI] = Keys.ASCII[':']; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2018 + */ + +class Str { + /** + * isValidInt(s, base) + * + * The built-in parseInt() function has the annoying feature of returning a partial value (ie, + * up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf. + * + * So it's best to use our own Str.parseInt() function, which will in turn use this function to + * validate the entire string. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported + * @return {boolean} true if valid, false if invalid (or the specified base isn't supported) + */ + static isValidInt(s, base) + { + if (!base || base == 10) return s.match(/^-?[0-9]+$/) !== null; + if (base == 16) return s.match(/^-?[0-9a-f]+$/i) !== null; + if (base == 8) return s.match(/^-?[0-7]+$/) !== null; + if (base == 2) return s.match(/^-?[01]+$/) !== null; + return false; + } + + /** + * parseInt(s, base) + * + * This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes + * ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for + * binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values; + * see isValidInt() for details. + * + * The use of multiple prefix/suffix combinations is undefined (although for the record, we process + * prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are + * also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero + * to indicate octal (because such a number could also be decimal or hex). Any number of commas are + * allowed; we remove them all before calling the built-in parseInt(). + * + * More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides + * that the PDP-10's MACRO-10 assembly language supports (decimal, octal, and binary, respectively). + * If this support turns out to adversely affect other debuggers, then it will have to be "conditionalized". + * Similarly, we've added support for "K", "M", and "G" MACRO-10-style suffixes that add 3, 6, or 9 zeros + * to the value to be parsed, respectively. + * + * @param {string} s is the string representation of some number + * @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes + * @return {number|undefined} corresponding value, or undefined if invalid + */ + static parseInt(s, base) + { + let value; + + if (s) { + if (!base) base = 10; + + let ch, chPrefix, chSuffix; + let fCommas = (s.indexOf(',') > 0); + if (fCommas) s = s.replace(/,/g, ''); + + ch = chPrefix = s.charAt(0); + if (chPrefix == '#') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '$') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) { + s = s.substr(1); + } + else { + ch = chPrefix = s.substr(0, 2); + if (chPrefix == '0b' && fCommas || chPrefix == '^B') { + base = 2; + chPrefix = ''; + } + else if (chPrefix == '0o' || chPrefix == '^O') { + base = 8; + chPrefix = ''; + } + else if (chPrefix == '^D') { + base = 10; + chPrefix = ''; + } + else if (chPrefix == '0x') { + base = 16; + chPrefix = ''; + } + if (ch != chPrefix) s = s.substr(2); + } + ch = chSuffix = s.slice(-1); + if (chSuffix == 'Y' || chSuffix == 'y') { + base = 2; + chSuffix = ''; + } + else if (chSuffix == '.') { + base = 10; + chSuffix = ''; + } + else if (chSuffix == 'H' || chSuffix == 'h') { + base = 16; + chSuffix = ''; + } + else if (chSuffix == 'K') { + chSuffix = '000'; + } + else if (chSuffix == 'M') { + chSuffix = '000000'; + } + else if (chSuffix == 'G') { + chSuffix = '000000000'; + } + if (ch != chSuffix) s = s.slice(0, -1) + chSuffix; + /* + * This adds support for the MACRO-10 binary shifting (Bn) suffix, which must be stripped from the + * number before parsing, and then applied to the value after parsing. If n is omitted, 35 is assumed, + * which is a net shift of zero. If n < 35, then a left shift of (35 - n) is required; if n > 35, then + * a right shift of -(35 - n) is required. + */ + let v, shift = 0; + if (base <= 10) { + let match = s.match(/(-?[0-9]+)B([0-9]*)/); + if (match) { + s = match[1]; + shift = 35 - ((match[2] || 35) & 0xff); + } + } + if (Str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) { + /* + * With the need to support larger (eg, 36-bit) integers, truncating to 32 bits is no longer helpful. + * + * value = v|0; + */ + if (shift) { + /* + * Since binary shifting is a logical operation, and since shifting by division only works properly + * with positive numbers, we must convert a negative value to a positive value, by computing the two's + * complement. + */ + if (v < 0) v += Math.pow(2, 36); + if (shift > 0) { + v *= Math.pow(2, shift); + } else { + v = Math.trunc(v / Math.pow(2, -shift)); + } + } + value = v; + } + } + return value; + } + + /** + * toBase(n, radix, cch, sPrefix, nGrouping) + * + * Displays the given number as an unsigned integer using the specified radix and number of digits. + * + * @param {number|*} n + * @param {number} radix (ie, the base) + * @param {number} cch (the desired number of digits) + * @param {string} [sPrefix] (default is none) + * @param {number} [nGrouping] + * @return {string} + */ + static toBase(n, radix, cch, sPrefix = "", nGrouping = 0) + { + /* + * We can't rely entirely on isNaN(), because isNaN(null) returns false, and we can't rely + * entirely on typeof either, because typeof Nan returns "number". Sigh. + * + * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN, + * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those + * values displayed differently. + */ + let s = ""; + if (isNaN(n) || typeof n != "number") { + n = null; + } else { + /* + * Callers that produced an input by dividing by a power of two rather than shifting (in order + * to access more than 32 bits) may produce a fractional result, which ordinarily we would simply + * ignore, but if the integer portion is zero and the sign is negative, we should probably treat + * this value as a sign-extension. + */ + if (n < 0 && n > -1) n = -1; + /* + * Negative values should be two's complemented according to the number of digits; for example, + * 12 octal digits implies an upper limit 8^12. + */ + if (n < 0) { + n += Math.pow(radix, cch); + } + if (n >= Math.pow(radix, cch)) { + cch = Math.ceil(Math.log(n) / Math.log(radix)); + } + } + let g = nGrouping || -1; + while (cch-- > 0) { + if (!g) { + s = ',' + s; + g = nGrouping; + } + if (n == null) { + s = '?' + s; + } else { + let d = n % radix; + d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10); + s = String.fromCharCode(d) + s; + n = Math.trunc(n / radix); + } + g--; + } + return sPrefix + s; + } + + /** + * toBin(n, cch, nGrouping) + * + * Converts an integer to binary, with the specified number of digits (up to a maximum of 36). + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of binary digits (0 or undefined for default of either 8, 18, or 36) + * @param {number} [nGrouping] + * @return {string} the binary representation of n + */ + static toBin(n, cch, nGrouping) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN2) || 1; + let v = Math.abs(n); + if (v <= 0b11111111) { + cch = 8; + } else if (v <= 0b111111111111111111) { + cch = 18; + } else { + cch = 36; + } + } else if (cch > 36) cch = 36; + return Str.toBase(n, 2, cch, "", nGrouping); + } + + /** + * toBinBytes(n, cb, fPrefix) + * + * Converts an integer to binary, with the specified number of bytes (up to the default of 4). + * + * @param {number|null|undefined} n (interpreted as a 32-bit value) + * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum) + * @param {boolean} [fPrefix] + * @return {string} the binary representation of n + */ + static toBinBytes(n, cb, fPrefix) + { + let s = ""; + if (!cb || cb > 4) cb = 4; + for (let i = 0; i < cb; i++) { + if (s) s = ',' + s; + s = Str.toBin(n & 0xff, 8) + s; + n >>= 8; + } + return (fPrefix? "0b" : "") + s; + } + + /** + * toOct(n, cch, fPrefix) + * + * Converts an integer to octal, with the specified number of digits (default of 6; max of 12) + * + * You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6, 8, or 12) + * @param {boolean} [fPrefix] + * @return {string} the octal representation of n + */ + static toOct(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(8)) || 1; + let v = Math.abs(n); + if (v <= 0o777777) { + cch = 6; + } else if (v <= 0o77777777) { + cch = 8; + } else { + cch = 12; + } + } else if (cch > 12) cch = 12; + return Str.toBase(n, 8, cch, fPrefix? "0o" : ""); + } + + /** + * toDec(n, cch) + * + * Converts an integer to decimal, with the specified number of digits (default of 5; max of 11) + * + * You might be tempted to use the built-in n.toString(10) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw + * an exception, whereas this function will return '?' characters. + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 11) + * @return {string} the decimal representation of n + */ + static toDec(n, cch) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.LN10) || 1; + let v = Math.abs(n); + if (v <= 99999) { + cch = 5; + } else { + cch = 11; + } + } else if (cch > 11) cch = 11; + return Str.toBase(n, 10, cch); + } + + /** + * toHex(n, cch, fPrefix) + * + * Converts an integer to hex, with the specified number of digits (default of 4 or 8, max of 9). + * + * You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it + * doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16) + * will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will + * throw an exception, whereas this function will return '?' characters. + * + * NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution, + * taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values: + * + * s = (n < 0? n + 0x100000000 : n).toString(16); + * s = "00000000".substr(0, 8 - s.length) + s; + * s = s.substr(0, cch).toUpperCase(); + * + * @param {number|*} n (supports integers up to 36 bits now) + * @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4, 8, or 9) + * @param {boolean} [fPrefix] + * @return {string} the hex representation of n + */ + static toHex(n, cch, fPrefix) + { + if (!cch) { + // cch = Math.ceil(Math.log(Math.abs(n) + 1) / Math.log(16)) || 1; + let v = Math.abs(n); + if (v <= 0xffff) { + cch = 4; + } else if (v <= 0xffffffff) { + cch = 8; + } else { + cch = 9; + } + } else if (cch > 9) cch = 9; + return Str.toBase(n, 16, cch, fPrefix? "0x" : ""); + } + + /** + * toHexByte(b) + * + * Alias for Str.toHex(b, 2, true) + * + * @param {number|null|undefined} b is a byte value + * @return {string} the hex representation of b + */ + static toHexByte(b) + { + return Str.toHex(b, 2, true); + } + + /** + * toHexWord(w) + * + * Alias for Str.toHex(w, 4, true) + * + * @param {number|null|undefined} w is a word (16-bit) value + * @return {string} the hex representation of w + */ + static toHexWord(w) + { + return Str.toHex(w, 4, true); + } + + /** + * toHexLong(l) + * + * Alias for Str.toHex(l, 8, true) + * + * @param {number|null|undefined} l is a dword (32-bit) value + * @return {string} the hex representation of w + */ + static toHexLong(l) + { + return Str.toHex(l, 8, true); + } + + /** + * getBaseName(sFileName, fStripExt) + * + * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead. + * + * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips the extension only + * if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux"). + * + * @param {string} sFileName + * @param {boolean} [fStripExt] + * @return {string} + */ + static getBaseName(sFileName, fStripExt) + { + let sBaseName = sFileName; + + let i = sFileName.lastIndexOf('/'); + if (i >= 0) sBaseName = sFileName.substr(i + 1); + + /* + * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters. + */ + i = sBaseName.indexOf('&'); + if (i > 0) sBaseName = sBaseName.substr(0, i); + + if (fStripExt) { + i = sBaseName.lastIndexOf("."); + if (i > 0) { + sBaseName = sBaseName.substring(0, i); + } + } + return sBaseName; + } + + /** + * getExtension(sFileName) + * + * This is a poor-man's version of Node's path.extname(), which Node-only components should use instead. + * + * Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it. + * + * @param {string} sFileName + * @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string + */ + static getExtension(sFileName) + { + let sExtension = ""; + let i = sFileName.lastIndexOf("."); + if (i >= 0) { + sExtension = sFileName.substr(i + 1).toLowerCase(); + } + return sExtension; + } + + /** + * endsWith(s, sSuffix) + * + * @param {string} s + * @param {string} sSuffix + * @return {boolean} true if s ends with sSuffix, false if not + */ + static endsWith(s, sSuffix) + { + return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1; + } + + /** + * escapeHTML(sHTML) + * + * @param {string} sHTML + * @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars() + */ + static escapeHTML(sHTML) + { + return sHTML.replace(/[&<>"']/g, function(m) + { + return Str.HTMLEscapeMap[m]; + }); + } + + /** + * replace(sSearch, sReplace, s) + * + * The JavaScript replace() function ALWAYS interprets "$" specially in replacement strings, even when + * the search string is NOT a RegExp; specifically: + * + * $$ Inserts a "$" + * $& Inserts the matched substring + * $` Inserts the portion of the string that precedes the matched substring + * $' Inserts the portion of the string that follows the matched substring + * $n Where n is a positive integer less than 100, inserts the nth parenthesized sub-match string, + * provided the first argument was a RegExp object + * + * So, if a replacement string containing dollar signs passes through a series of replace() calls, untold + * problems could result. Hence, this function, which simply uses the replacement string as-is. + * + * Similar to the JavaScript replace() method (when sSearch is a string), this replaces only ONE occurrence + * (ie, the FIRST occurrence); it might be nice to add options to replace the LAST occurrence and/or ALL + * occurrences, but we'll revisit that later. + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replace(sSearch, sReplace, s) + { + let i = s.indexOf(sSearch); + if (i >= 0) { + s = s.substr(0, i) + sReplace + s.substr(i + sSearch.length); + } + return s; + } + + /** + * replaceAll(sSearch, sReplace, s) + * + * @param {string} sSearch + * @param {string} sReplace + * @param {string} s + * @return {string} + */ + static replaceAll(sSearch, sReplace, s) + { + let a = {}; + a[sSearch] = sReplace; + return Str.replaceArray(a, s); + } + + /** + * replaceArray(a, s) + * + * @param {Object} a + * @param {string} s + * @return {string} + */ + static replaceArray(a, s) + { + let sMatch = ""; + for (let k in a) { + /* + * As noted in: + * + * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/ + * + * inside character classes, only backslash, caret, hyphen and the closing bracket need to be + * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first, + * and the hyphen is last, you can avoid escaping those as well. + */ + k = k.replace(/([\\[\]*{}().+?|$])/g, "\\$1"); + sMatch += (sMatch? '|' : '') + k; + } + return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) + { + return a[m]; + }); + } + + /** + * pad(s, cch, fPadLeft) + * + * NOTE: the maximum amount of padding currently supported is 40 spaces. + * + * @param {string} s is a string + * @param {number} cch is desired length + * @param {boolean} [fPadLeft] (default is padding on the right) + * @return {string} the original string (s) with spaces padding it to the specified length + */ + static pad(s, cch, fPadLeft) + { + let sPadding = " "; + return fPadLeft? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch); + } + + /** + * sprintf(format, ...args) + * + * Copied from the CCjs project (https://github.com/jeffpar/ccjs/blob/master/lib/stdio.js) and extended. + * + * Far from complete, let alone sprintf-compatible, but it's adequate for the handful of sprintf-style format + * specifiers that I use. + * + * TODO: The %c and %s specifiers support a negative width (for left-justified output), but the numeric specifiers + * (eg, %d and %x) do not; they support only positive widths and right-justified output. That's one of the more + * glaring omissions at the moment. + * + * @param {string} format + * @param {...} args + * @return {string} + */ + static sprintf(format, ...args) + { + let buffer = ""; + let aParts = format.split(/%([-+ 0#]*)([0-9]*|\*)(\.[0-9]+|)([hlL]?)([A-Za-z%])/); + + let iArg = 0, iPart; + for (iPart = 0; iPart < aParts.length - 6; iPart += 6) { + + buffer += aParts[iPart]; + + let arg = args[iArg++]; + let flags = aParts[iPart+1]; + let width = aParts[iPart+2]; + if (width == '*') { + width = arg; + arg = args[iArg++]; + } else { + width = +width || 0; + } + let precision = aParts[iPart+3]; + precision = precision? +precision.substr(1) : -1; + let prefix = aParts[iPart+4]; + let type = aParts[iPart+5]; + let ach = null, s; + + switch(type) { + case 'd': + /* + * We could use "arg |= 0", but there may be some value to supporting integers > 32 bits. + * + * Also, unlike the 'X' and 'x' hexadecimal cases, there's no need to explicitly check for a string + * arguments, because the call to trunc() automatically coerces any string value to a (decimal) number. + */ + arg = Math.trunc(arg); + /* falls through */ + + case 'f': + s = Math.trunc(arg) + ""; + if (precision > 0) { + width -= (precision + 1); + } + if (s.length < width) { + if (flags.indexOf('0') >= 0) { + if (arg < 0) width--; + s = ("0000000000" + Math.abs(arg)).slice(-width); + if (arg < 0) s = '-' + s; + } else { + s = (" " + s).slice(-width); + } + } + if (precision > 0) { + arg = Math.round((arg - Math.trunc(arg)) * Math.pow(10, precision)); + s += '.' + ("0000000000" + Math.abs(arg)).slice(-precision); + } + buffer += s; + break; + + case 'c': + arg = String.fromCharCode(arg); + /* falls through */ + + case 's': + if (typeof arg == "string") { + while (arg.length < width) { + if (flags.indexOf('-') >= 0) { + arg += ' '; + } else { + arg = ' ' + arg; + } + } + } + buffer += arg.toString(); + break; + + case 'X': + ach = Str.HexUpperCase; + /* falls through */ + + case 'x': + if (!ach) ach = Str.HexLowerCase; + s = ""; + if (typeof arg == "string") { + /* + * Since we're advised to ALWAYS pass a radix to parseInt(), we must detect explicitly + * hex values ourselves, because using a radix of 10 with any "0x..." value always returns 0. + * + * And if the value CAN be interpreted as decimal, then we MUST interpret it as decimal, because + * we have sprintf() calls in /modules/pcx86/lib/testmon.js that depend on this code to perform + * decimal to hex conversion. We're going to make our own rules here, since passing numbers in + * string form isn't part of the sprintf "spec". + */ + arg = Number.parseInt(arg, arg.match(/(^0x|[a-f])/i)? 16 : 10); + } + do { + let d = arg & 0xf; + arg >>>= 4; + if (flags.indexOf('0') >= 0 || s == "" || d || arg) { + s = ach[d] + s; + } else if (width) { + s = ' ' + s; + } + } while (--width > 0 || arg); + buffer += s; + break; + + default: + /* + * The supported ANSI C set of types: "dioxXucsfeEgGpn%" + */ + buffer += "(unrecognized printf type %" + type + ")"; + break; + } + } + + buffer += aParts[iPart]; + return buffer; + } + + /** + * stripLeadingZeros(s, fPad) + * + * @param {string} s + * @param {boolean} [fPad] + * @return {string} + */ + static stripLeadingZeros(s, fPad) + { + let cch = s.length; + s = s.replace(/^0+([0-9A-F]+)$/i, "$1"); + if (fPad) s = Str.pad(s, cch, true); + return s; + } + + /** + * trim(s) + * + * @param {string} s + * @return {string} + */ + static trim(s) + { + if (String.prototype.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ""); + } + + /** + * toASCIICode(b) + * + * @param {number} b + * @return {string} + */ + static toASCIICode(b) + { + let s; + if (b != Str.ASCII.CR && b != Str.ASCII.LF) { + s = Str.ASCIICodeMap[b]; + } + if (s) { + s = '<' + s + '>'; + } else { + s = String.fromCharCode(b); + } + return s; + } +} + +/* + * Map special characters to their HTML escape sequences. + */ +Str.HTMLEscapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' +}; + +/* + * Map "unprintable" ASCII codes to mnemonics, to more clearly see what's being printed. + */ +Str.ASCIICodeMap = { + 0x00: "NUL", + 0x01: "SOH", // (CTRL_A) Start of Heading + 0x02: "STX", // (CTRL_B) Start of Text + 0x03: "ETX", // (CTRL_C) End of Text + 0x04: "EOT", // (CTRL_D) End of Transmission + 0x05: "ENQ", // (CTRL_E) Enquiry + 0x06: "ACK", // (CTRL_F) Acknowledge + 0x07: "BEL", // (CTRL_G) Bell + 0x08: "BS", // (CTRL_H) Backspace + 0x09: "TAB", // (CTRL_I) Horizontal Tab (aka HT) + 0x0A: "LF", // (CTRL_J) Line Feed (New Line) + 0x0B: "VT", // (CTRL_K) Vertical Tab + 0x0C: "FF", // (CTRL_L) Form Feed (New Page) + 0x0D: "CR", // (CTRL_M) Carriage Return + 0x0E: "SO", // (CTRL_N) Shift Out + 0x0F: "SI", // (CTRL_O) Shift In + 0x10: "DLE", // (CTRL_P) Data Link Escape + 0x11: "XON", // (CTRL_Q) Device Control 1 (aka DC1) + 0x12: "DC2", // (CTRL_R) Device Control 2 + 0x13: "XOFF", // (CTRL_S) Device Control 3 (aka DC3) + 0x14: "DC4", // (CTRL_T) Device Control 4 + 0x15: "NAK", // (CTRL_U) Negative Acknowledge + 0x16: "SYN", // (CTRL_V) Synchronous Idle + 0x17: "ETB", // (CTRL_W) End of Transmission Block + 0x18: "CAN", // (CTRL_X) Cancel + 0x19: "EM", // (CTRL_Y) End of Medium + 0x1A: "SUB", // (CTRL_Z) Substitute + 0x1B: "ESC", // Escape + 0x1C: "FS", // File Separator + 0x1D: "GS", // Group Separator + 0x1E: "RS", // Record Separator + 0x1F: "US", // Unit Separator + 0x7F: "DEL" +}; + +/* + * Refer to: https://en.wikipedia.org/wiki/Code_page_437 + */ +Str.CP437ToUnicode = [ + '\u0000', '\u263A', '\u263B', '\u2665', '\u2666', '\u2663', '\u2660', '\u2022', + '\u25D8', '\u25CB', '\u25D9', '\u2642', '\u2640', '\u266A', '\u266B', '\u263C', + '\u25BA', '\u25C4', '\u2195', '\u203C', '\u00B6', '\u00A7', '\u25AC', '\u21A8', + '\u2191', '\u2193', '\u2192', '\u2190', '\u221F', '\u2194', '\u25B2', '\u25BC', + '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', + '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', + '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', + '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', + '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', + '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', + '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', + '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', + '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', + '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', + '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', + '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u2302', + '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', + '\u00EA', '\u00EB', '\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', + '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', + '\u00FF', '\u00D6', '\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', + '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA', + '\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', + '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561', '\u2562', '\u2556', + '\u2555', '\u2563', '\u2551', '\u2557', '\u255D', '\u255C', '\u255B', '\u2510', + '\u2514', '\u2534', '\u252C', '\u251C', '\u2500', '\u253C', '\u255E', '\u255F', + '\u255A', '\u2554', '\u2569', '\u2566', '\u2560', '\u2550', '\u256C', '\u2567', + '\u2568', '\u2564', '\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', + '\u256A', '\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580', + '\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5', '\u03C4', + '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6', '\u03B5', '\u2229', + '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320', '\u2321', '\u00F7', '\u2248', + '\u00B0', '\u2219', '\u00B7', '\u221A', '\u207F', '\u00B2', '\u25A0', '\u00A0' +]; + +/* + * TODO: Future home of a complete ASCII table. + */ +Str.ASCII = { + LF: 0x0A, + CR: 0x0D +}; + +Str.TYPES = { + NULL: 0, + BYTE: 1, + WORD: 2, + DWORD: 3, + NUMBER: 4, + STRING: 5, + BOOLEAN: 6, + OBJECT: 7, + ARRAY: 8 +}; + +Str.HexLowerCase = "0123456789abcdef"; +Str.HexUpperCase = "0123456789ABCDEF"; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2018 + */ + +/** + * @typedef {{ + * mask: number, + * shift: number + * }} + */ +var BitField; + +/** + * @typedef {Object.} + */ +var BitFields; + +class Usr { + /** + * binarySearch(a, v, fnCompare) + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point + */ + static binarySearch(a, v, fnCompare) + { + let left = 0; + let right = a.length; + let found = 0; + if (fnCompare === undefined) { + fnCompare = function(a, b) + { + return a > b ? 1 : a < b ? -1 : 0; + }; + } + while (left < right) { + let middle = (left + right) >> 1; + let compareResult; + compareResult = fnCompare(v, a[middle]); + if (compareResult > 0) { + left = middle + 1; + } else { + right = middle; + found = !compareResult; + } + } + return found ? left : ~left; + } + + /** + * binaryInsert(a, v, fnCompare) + * + * If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the + * element is inserted into the array at the appropriate index. + * + * @param {Array} a is an array + * @param {number|string|Array|Object} v is the value to insert + * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] + */ + static binaryInsert(a, v, fnCompare) + { + let index = Usr.binarySearch(a, v, fnCompare); + if (index < 0) { + a.splice(-(index + 1), 0, v); + } + } + + /** + * getTimestamp() + * + * @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss") + */ + static getTimestamp() + { + return Usr.formatDate("Y-m-d H:i:s"); + } + + /** + * getMonthDays(nMonth, nYear) + * + * Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100), + * so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails + * the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap + * year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year. + * + * TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD, + * so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and + * we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what + * a real RTC actually does. + * + * @param {number} nMonth (1-12) + * @param {number} nYear (normally a 4-digit year, but it may also be mod 100) + * @return {number} the maximum (1-based) day allowed for the specified month and year + */ + static getMonthDays(nMonth, nYear) + { + let nDays = Usr.aMonthDays[nMonth - 1]; + if (nDays == 28) { + if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) { + nDays++; + } + } + return nDays; + } + + /** + * formatDate(sFormat, date) + * + * @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s") + * @param {Date} [date] (default is the current time) + * @return {string} + * + * Supported identifiers in sFormat include: + * + * a: lowercase ante meridiem and post meridiem (am or pm) + * d: day of the month, 2 digits with leading zeros (01,02,...,31) + * D: 3-letter day of the week ("Sun","Mon",...,"Sat") + * F: month ("January","February",...,"December") + * g: hour in 12-hour format, without leading zeros (1,2,...,12) + * h: hour in 24-hour format, without leading zeros (0,1,...,23) + * H: hour in 24-hour format, with leading zeros (00,01,...,23) + * i: minutes, with leading zeros (00,01,...,59) + * j: day of the month, without leading zeros (1,2,...,31) + * l: day of the week ("Sunday","Monday",...,"Saturday") + * m: month, with leading zeros (01,02,...,12) + * M: 3-letter month ("Jan","Feb",...,"Dec") + * n: month, without leading zeros (1,2,...,12) + * s: seconds, with leading zeros (00,01,...,59) + * y: 2-digit year (eg, 14) + * Y: 4-digit year (eg, 2014) + * + * For more inspiration, see: http://php.net/manual/en/function.date.php (of which we support ONLY a subset). + */ + static formatDate(sFormat, date) + { + let sDate = ""; + if (!date) date = new Date(); + let iHour = date.getHours(); + let iDay = date.getDate(); + let iMonth = date.getMonth() + 1; + for (let i = 0; i < sFormat.length; i++) { + let ch; + switch ((ch = sFormat.charAt(i))) { + case 'a': + sDate += (iHour < 12 ? "am" : "pm"); + break; + case 'd': + sDate += ('0' + iDay).slice(-2); + break; + case 'D': + sDate += Usr.asDays[date.getDay()].substr(0, 3); + break; + case 'F': + sDate += Usr.asMonths[iMonth - 1]; + break; + case 'g': + sDate += (!iHour ? 12 : (iHour > 12 ? iHour - 12 : iHour)); + break; + case 'h': + sDate += iHour; + break; + case 'H': + sDate += ('0' + iHour).slice(-2); + break; + case 'i': + sDate += ('0' + date.getMinutes()).slice(-2); + break; + case 'j': + sDate += iDay; + break; + case 'l': + sDate += Usr.asDays[date.getDay()]; + break; + case 'm': + sDate += ('0' + iMonth).slice(-2); + break; + case 'M': + sDate += Usr.asMonths[iMonth - 1].substr(0, 3); + break; + case 'n': + sDate += iMonth; + break; + case 's': + sDate += ('0' + date.getSeconds()).slice(-2); + break; + case 'y': + sDate += ("" + date.getFullYear()).slice(-2); + break; + case 'Y': + sDate += date.getFullYear(); + break; + default: + sDate += ch; + break; + } + } + return sDate; + } + + /** + * defineBitFields(bfs) + * + * Prepares a bit field definition for use with getBitField() and setBitField(); eg: + * + * let bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3}); + * + * 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). + * + * Usr.setBitField(bfs.num, n, 1); + * + * The above set bit field "bfs.num" in numeric variable "n" to the value 1. + * + * @param {Object} bfs + * @return {BitFields} + */ + static defineBitFields(bfs) + { + let bit = 0; + for (let f in bfs) { + let width = bfs[f]; + let mask = ((1 << width) - 1) << bit; + bfs[f] = {mask: mask, shift: bit}; + bit += width; + } + return bfs; + } + + /** + * initBitFields(bfs, ...) + * + * @param {BitFields} bfs + * @param {...number} var_args + * @return {number} a value containing all supplied bit fields + */ + static initBitFields(bfs, var_args) + { + let v = 0, i = 1; + for (let f in bfs) { + if (i >= arguments.length) break; + v = Usr.setBitField(bfs[f], v, arguments[i++]); + } + return v; + } + + /** + * getBitField(bf, v) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @return {number} the value of the bit field in v defined by bf + */ + static getBitField(bf, v) + { + return (v & bf.mask) >> bf.shift; + } + + /** + * setBitField(bf, v, n) + * + * @param {BitField} bf + * @param {number} v is a value containing bit fields + * @param {number} n is a value to store in v in the bit field defined by bf + * @return {number} updated v + */ + static setBitField(bf, v, n) + { + return (v & ~bf.mask) | ((n << bf.shift) & bf.mask); + } + + /** + * indexOf(a, t, i) + * + * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it. + * + * @param {Array} a + * @param {*} t + * @param {number} [i] + * @returns {number} + */ + static indexOf(a, t, i) + { + if (Array.prototype.indexOf) { + return a.indexOf(t, i); + } + i = i || 0; + if (i < 0) i += a.length; + if (i < 0) i = 0; + for (let n = a.length; i < n; i++) { + if (i in a && a[i] === t) return i; + } + return -1; + } +} + +Usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; +Usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; +Usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +/** + * getTime() + * + * @return {number} the current time, in milliseconds + */ +Usr.getTime = Date.now || function() { return +new Date(); }; + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2018 + */ + + +/* + * According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties + * and functions of JavaScript-in-the-Browser: + * + * Property Description + * --- + * Infinity A numeric value that represents positive/negative infinity + * NaN "Not-a-Number" value + * undefined Indicates that a variable has not been assigned a value + * + * Function Description + * --- + * decodeURI() Decodes a URI + * decodeURIComponent() Decodes a URI component + * encodeURI() Encodes a URI + * encodeURIComponent() Encodes a URI component + * escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead + * eval() Evaluates a string and executes it as if it was script code + * isFinite() Determines whether a value is a finite, legal number + * isNaN() Determines whether a value is an illegal number + * Number() Converts an object's value to a number + * parseFloat() Parses a string and returns a floating point number + * parseInt() Parses a string and returns an integer + * String() Converts an object's value to a string + * unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead + * + * And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions + * of the *window* object. + * + * Property Description + * --- + * closed Returns a Boolean value indicating whether a window has been closed or not + * defaultStatus Sets or returns the default text in the statusbar of a window + * document Returns the Document object for the window (See Document object) + * frames Returns an array of all the frames (including iframes) in the current window + * history Returns the History object for the window (See History object) + * innerHeight Returns the inner height of a window's content area + * innerWidth Returns the inner width of a window's content area + * length Returns the number of frames (including iframes) in a window + * location Returns the Location object for the window (See Location object) + * name Sets or returns the name of a window + * navigator Returns the Navigator object for the window (See Navigator object) + * opener Returns a reference to the window that created the window + * outerHeight Returns the outer height of a window, including toolbars/scrollbars + * outerWidth Returns the outer width of a window, including toolbars/scrollbars + * pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window + * pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window + * parent Returns the parent window of the current window + * screen Returns the Screen object for the window (See Screen object) + * screenLeft Returns the x coordinate of the window relative to the screen + * screenTop Returns the y coordinate of the window relative to the screen + * screenX Returns the x coordinate of the window relative to the screen + * screenY Returns the y coordinate of the window relative to the screen + * self Returns the current window + * status Sets or returns the text in the statusbar of a window + * top Returns the topmost browser window + * + * Method Description + * --- + * alert() Displays an alert box with a message and an OK button + * atob() Decodes a base-64 encoded string + * blur() Removes focus from the current window + * btoa() Encodes a string in base-64 + * clearInterval() Clears a timer set with setInterval() + * clearTimeout() Clears a timer set with setTimeout() + * close() Closes the current window + * confirm() Displays a dialog box with a message and an OK and a Cancel button + * createPopup() Creates a pop-up window + * focus() Sets focus to the current window + * moveBy() Moves a window relative to its current position + * moveTo() Moves a window to the specified position + * open() Opens a new browser window + * print() Prints the content of the current window + * prompt() Displays a dialog box that prompts the visitor for input + * resizeBy() Resizes the window by the specified pixels + * resizeTo() Resizes the window to the specified width and height + * scroll() This method has been replaced by the scrollTo() method. + * scrollBy() Scrolls the content by the specified number of pixels + * scrollTo() Scrolls the content to the specified coordinates + * setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds) + * setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds + * stop() Stops the window from loading + */ + +class Web { + /** + * log(s, type) + * + * For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line) + * for Component.log() to display anything. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + Component.log(s, type); + } + + /** + * notice(s, fPrintOnly, id) + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + */ + static notice(s, fPrintOnly, id) + { + Component.notice(s, fPrintOnly, id); + } + + /** + * alertUser(sMessage) + * + * NOTE: Legacy function for older modules (eg, DiskDump); see Component.alertUser(). + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Web.log(sMessage); + } + } + + /** + * getResource(sURL, type, fAsync, done, progress) + * + * Request the specified resource (sURL), and once the request is complete, notify done(). + * + * If fAsync is true, a done() callback should ALWAYS be supplied; otherwise, you'll have no + * idea when the request is complete or what the response was. done() is passed three parameters: + * + * done(sURL, resource, nErrorCode) + * + * If nErrorCode is zero, resource should contain the requested data; otherwise, an error occurred. + * + * If type is set to a string, that string can be used to control the response format; + * by default, the response format is plain text, but you can specify "arraybuffer" to request arbitrary + * binary data, in which case the returned resource will be a ArrayBuffer rather than a string. + * + * @param {string} sURL + * @param {string|Object|null} [type] (object for POST request, otherwise type of GET request) + * @param {boolean} [fAsync] is true for an asynchronous request; false otherwise (MUST be set for IE) + * @param {function(string,string,number)} [done] + * @param {function(number)} [progress] + * @return {Array|null} Array containing [resource, nErrorCode], or null if no response available (yet) + */ + static getResource(sURL, type = "text", fAsync = false, done, progress) + { + let nErrorCode = 0, resource = null, response = null; + + if (typeof resources == 'object' && (resource = resources[sURL])) { + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + } + else if (fAsync && typeof resources == 'function') { + resources(sURL, function(resource, nErrorCode) + { + if (done) done(sURL, resource, nErrorCode); + }); + return response; + } + + if (Web.getHost() == "pcjs:8088" || NODE) { + /* + * The larger resources that I've put on archive.pcjs.org are assumed to also be available locally + * whenever the hostname is "pcjs" (or NODE is true); otherwise, use "localhost" when debugging locally. + * + * NOTE: http://archive.pcjs.org is currently redirected to https://s3-us-west-2.amazonaws.com/archive.pcjs.org + */ + sURL = sURL.replace(/^(http:\/\/archive\.pcjs\.org|https:\/\/[a-z0-9-]+\.amazonaws\.com\/archive\.pcjs\.org)(\/.*)\/([^\/]*)$/, "$2/archive/$3"); + sURL = sURL.replace(/^https:\/\/jeffpar\.github\.io\/(pcjs-[a-z]+|private-[a-z]+)\/(.*)$/, "/$1/$2"); + } + else { + /* + * TODO: Perhaps it's time for our code in netlib.js to finally add support for HTTPS; for now + * though, it's just as well that the NODE environment assumes all resources are available locally. + */ + sURL = sURL.replace(/^\/(pcjs-[a-z]+|private-[a-z]+)\//, "https://jeffpar.github.io/$1/"); + } + + + let request = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP")); + let fArrayBuffer = false, fXHR2 = (typeof request.responseType === 'string'); + + let callback = function() { + if (request.readyState !== 4) { + if (progress) progress(1); + return null; + } + /* + * The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple + * times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when + * debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests + * pending, any debugging activity means most of them simply get dropped on floor, so what may actually be + * happening are mis-notifications rather than redundant notifications. + * + * request.onreadystatechange = undefined; + */ + /* + * If the request failed due to, say, a CORS policy denial; eg: + * + * Failed to load http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img: + * Redirect from 'http://www.allbootdisks.com/downloads/Disks/Windows_95_Boot_Disk_Download48/Diskette%20Images/Windows95a.img' to + * 'http://www.allbootdisks.com/' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. + * Origin 'http://pcjs:8088' is therefore not allowed access. + * + * and our request type was "arraybuffer", attempting to access responseText may trigger an exception; eg: + * + * Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's + * 'responseType' is '' or 'text' (was 'arraybuffer'). + * + * We could tiptoe around these potential landmines, but the safest thing to do is wrap this code with try/catch. + */ + try { + resource = fArrayBuffer? request.response : request.responseText; + } catch(err) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + ") exception: " + err.message); + } + /* + * The normal "success" case is a non-null resource and an HTTP status code of 200, but when loading files from the + * local file system (ie, when using the "file:" protocol), we have to be a bit more flexible. + */ + if (resource != null && (request.status == 200 || !request.status && resource.length && Web.getHostProtocol() == "file:")) { + if (MAXDEBUG) Web.log("xmlHTTPRequest(" + sURL + "): returned " + resource.length + " bytes"); + } + else { + nErrorCode = request.status || -1; + Web.log("xmlHTTPRequest(" + sURL + "): error code " + nErrorCode); + } + if (progress) progress(2); + if (done) done(sURL, resource, nErrorCode); + return [resource, nErrorCode]; + }; + + if (fAsync) { + request.onreadystatechange = callback; + } + + if (progress) progress(0); + + if (type && typeof type == "object") { + let sPost = ""; + for (let p in type) { + if (!type.hasOwnProperty(p)) continue; + if (sPost) sPost += "&"; + sPost += p + '=' + encodeURIComponent(type[p]); + } + sPost = sPost.replace(/%20/g, '+'); + if (MAXDEBUG) Web.log("Web.getResource(POST " + sURL + "): " + sPost.length + " bytes"); + request.open("POST", sURL, fAsync); + request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + request.send(sPost); + } else { + if (MAXDEBUG) Web.log("Web.getResource(GET " + sURL + ")"); + request.open("GET", sURL, fAsync); + if (type == "arraybuffer") { + if (fXHR2) { + fArrayBuffer = true; + request.responseType = type; + } else { + request.overrideMimeType("text/plain; charset=x-user-defined"); + } + } + request.send(); + } + + if (!fAsync) { + request.readyState = 4; // this may already be set for synchronous requests, but I don't want to take any chances + response = callback(); + } + return response; + } + + /** + * parseMemoryResource(sURL, sData) + * + * This converts a variety of JSON-style data streams into an Object with the following properties: + * + * aBytes + * aSymbols + * addrLoad + * addrExec + * + * If the source data contains a 'bytes' array, it's passed through to 'aBytes'; alternatively, if + * it contains a 'words' array, the values are converted from 16-bit to 8-bit and stored in 'aBytes', + * and if it contains a 'longs' array, the values are converted from 32-bit longs into bytes and + * stored in 'aBytes'. + * + * Alternatively, if the source data contains a 'data' array, we simply pass that through to the output + * object as: + * + * aData + * + * @param {string} sURL + * @param {string} sData + * @return {Object|null} (resource) + */ + static parseMemoryResource(sURL, sData) + { + let i; + let resource = { + aBytes: null, + aSymbols: null, + addrLoad: null, + addrExec: null + }; + + if (sData.charAt(0) == "[" || sData.charAt(0) == "{") { + try { + let a, ib, data; + + if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"... + /* + * Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid, + * but more recent server configs now display a somewhat friendlier HTML error page. The downside, + * however, is that the original error has been buried, and we've received "data" that isn't actually + * tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message. + */ + throw new Error(sData); + } + + /* + * TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to + * eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in + * IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks + * (perhaps one track per chunk, using regular expressions) and then manually re-assemble it. + * + * However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")") + * is a much easier fix. The only drawback is that we must first quote any unquoted property names + * and remove any comments, because while eval() was cool with them, JSON.parse() is more particular; + * the following RegExp replacements take care of those requirements. + * + * The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and + * while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified), + * I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement + * for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect + * any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so: + * + * ["unrecognized disk path: test.img"] + */ + if (sData.indexOf("0x") < 0 && sData.indexOf("0o") < 0 && sData.substr(0, 2) != '["') { + data = JSON.parse(sData.replace(/([a-z]+):/gm, '"$1":').replace(/\/\/[^\n]*/gm, "")); + } else { + data = eval("(" + sData + ")"); + } + + resource.addrLoad = data['load']; + resource.addrExec = data['exec']; + + if (a = data['bytes']) { + resource.aBytes = a; + } + else if (a = data['words']) { + /* + * Convert all words into bytes + */ + resource.aBytes = new Array(a.length * 2); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + + } + } + else if (a = data['longs']) { + /* + * Convert all dwords (longs) into bytes + */ + resource.aBytes = new Array(a.length * 4); + for (i = 0, ib = 0; i < a.length; i++) { + resource.aBytes[ib++] = a[i] & 0xff; + resource.aBytes[ib++] = (a[i] >> 8) & 0xff; + resource.aBytes[ib++] = (a[i] >> 16) & 0xff; + resource.aBytes[ib++] = (a[i] >> 24) & 0xff; + } + } + else if (a = data['data']) { + resource.aData = a; + } + else { + resource.aBytes = data; + } + + if (resource.aBytes) { + if (!resource.aBytes.length) { + Component.error("Empty resource: " + sURL); + resource = null; + } + else if (resource.aBytes.length == 1) { + Component.error(resource.aBytes[0]); + resource = null; + } + } + resource.aSymbols = data['symbols']; + + } catch (e) { + Component.error("Resource data error (" + sURL + "): " + e.message); + resource = null; + } + } + else { + /* + * Parse the data manually; we assume it's a series of hex byte-values separated by whitespace. + */ + let ab = []; + let sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, ""); + let asHexData = sHexData.split(" "); + for (i = 0; i < asHexData.length; i++) { + let n = parseInt(asHexData[i], 16); + if (isNaN(n)) { + Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")"); + break; + } + ab.push(n & 0xff); + } + if (i == asHexData.length) resource.aBytes = ab; + } + return resource; + } + + /** + * sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + * + * Send a report (eg, bug report) to the server. + * + * @param {string} sApp (eg, "PCjs") + * @param {string} sVer (eg, "1.02") + * @param {string} sURL (eg, "/devices/pc/machine/5150/mda/64kb/machine.xml") + * @param {string} sUser (ie, the user key, if any) + * @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.* + * @param {string} sReport (eg, unparsed state data) + * @param {string} [sHostName] (default is SITEURL) + */ + static sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) + { + let dataPost = {}; + dataPost[ReportAPI.QUERY.APP] = sApp; + dataPost[ReportAPI.QUERY.VER] = sVer; + dataPost[ReportAPI.QUERY.URL] = sURL; + dataPost[ReportAPI.QUERY.USER] = sUser; + dataPost[ReportAPI.QUERY.TYPE] = sType; + dataPost[ReportAPI.QUERY.DATA] = sReport; + let sReportURL = (sHostName? sHostName : SITEURL) + ReportAPI.ENDPOINT; + Web.getResource(sReportURL, dataPost, true); + } + + /** + * getHost() + * + * This is like getHostName() but with the port number, if any. + * + * @return {string} + */ + static getHost() + { + return (window? window.location.host : "localhost"); + } + + /** + * getHostName() + * + * @return {string} + */ + static getHostName() + { + return (window? window.location.hostname : "localhost"); + } + + /** + * getHostOrigin() + * + * This could also be implemented with window.location.origin, but that wasn't originally available in all browsers. + * + * @return {string} + */ + static getHostOrigin() + { + return (window? window.location.protocol + "//" + window.location.host : SITEURL); + } + + /** + * getHostProtocol() + * + * @return {string} + */ + static getHostProtocol() + { + return (window? window.location.protocol : "file:"); + } + + /** + * getHostURL() + * + * @return {string|null} + */ + static getHostURL() + { + return (window? window.location.href : null); + } + + /** + * getUserAgent() + * + * @return {string} + */ + static getUserAgent() + { + return (window? window.navigator.userAgent : ""); + } + + /** + * hasLocalStorage + * + * true if localStorage support exists, is enabled, and works; false otherwise + * + * @return {boolean} + */ + static hasLocalStorage() + { + if (Web.fLocalStorage == null) { + let f = false; + if (window) { + try { + window.localStorage.setItem(Web.sLocalStorageTest, Web.sLocalStorageTest); + f = (window.localStorage.getItem(Web.sLocalStorageTest) == Web.sLocalStorageTest); + window.localStorage.removeItem(Web.sLocalStorageTest); + } catch (e) { + Web.logLocalStorageError(e); + f = false; + } + } + Web.fLocalStorage = f; + } + return Web.fLocalStorage; + } + + /** + * logLocalStorageError(e) + * + * @param {Error} e is an exception + */ + static logLocalStorageError(e) + { + Web.log(e.message, "localStorage error"); + } + + /** + * getLocalStorageItem(sKey) + * + * Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available + * + * @param {string} sKey + * @return {string|null|undefined} sValue + */ + static getLocalStorageItem(sKey) + { + let sValue; + if (window) { + try { + sValue = window.localStorage.getItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + return sValue; + } + + /** + * setLocalStorageItem(sKey, sValue) + * + * @param {string} sKey + * @param {string} sValue + * @return {boolean} true if localStorage is available, false if not + */ + static setLocalStorageItem(sKey, sValue) + { + try { + window.localStorage.setItem(sKey, sValue); + return true; + } catch (e) { + Web.logLocalStorageError(e); + } + return false; + } + + /** + * removeLocalStorageItem(sKey) + * + * @param {string} sKey + */ + static removeLocalStorageItem(sKey) + { + try { + window.localStorage.removeItem(sKey); + } catch (e) { + Web.logLocalStorageError(e); + } + } + + /** + * getLocalStorageKeys() + * + * @return {Array} + */ + static getLocalStorageKeys() + { + let a = []; + try { + for (let i = 0, c = window.localStorage.length; i < c; i++) { + a.push(window.localStorage.key(i)); + } + } catch (e) { + Web.logLocalStorageError(e); + } + return a; + } + + /** + * reloadPage() + */ + static reloadPage() + { + if (window) window.location.reload(); + } + + /** + * isUserAgent(s) + * + * Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can + * use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE"). + * + * 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating + * the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx); + * they say "public websites should rely on feature detection, rather than browser detection, in order to design + * their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent + * that tries to fool apps into thinking the browser is more like WebKit or Gecko: + * + * Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko + * + * That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained + * some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because + * that's a sledgehammer solution which restores the old user-agent string but also disables other features like + * HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or + * "Trident". + * + * UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't + * any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll + * need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture. + * + * @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values + * @return {boolean} is true if the string was found, false if not + */ + static isUserAgent(s) + { + if (window) { + let userAgent = Web.getUserAgent(); + /* + * Here's one case where we have to be careful with Component, because when isUserAgent() is called by + * the init code below, component.js hasn't been loaded yet. The simple solution for now is to remove the call. + * + * Web.log("agent: " + userAgent); + * + * And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure + * Compiler (v20130823) failing to detect the entire expression as a boolean. + */ + return s == "iOS" && !!userAgent.match(/(iPod|iPhone|iPad)/) && !!userAgent.match(/AppleWebKit/) || s == "MSIE" && !!userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0); + } + return false; + } + + /** + * isMobile(sDevice) + * + * Checks the URL for a "mobile" parameter, and failing that, checks the browser's user-agent string for the + * substring "Mobi", as per Mozilla recommendation: + * + * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent + * + * @param {string} [sDevice] (eg, "iPad" to check for iPad, or "!iPad" to specifically exclude it) + * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not + */ + static isMobile(sDevice) + { + let sMobile = Web.getURLParm("mobile"); + if (sMobile) return sMobile == "true"; + if (Web.isUserAgent("Mobi")) { + if (!sDevice) return true; + let fInvert = sDevice[0] == '!'; + if (fInvert) sDevice = sDevice.substr(1); + return Web.isUserAgent(sDevice) != fInvert; + } + return false; + } + + /** + * findProperty(obj, sProp, sSuffix) + * + * If both sProp and sSuffix are set, then any browser-specific prefixes are inserted between sProp and sSuffix, + * and if a match is found, it is returned without sProp. + * + * For example, if findProperty(document, 'on', 'fullscreenchange') discovers that 'onwebkitfullscreenchange' exists, + * it will return 'webkitfullscreenchange', in preparation for an addEventListener() call. + * + * More commonly, sSuffix is not used, so whatever property is found is returned as-is. + * + * @param {Object|null|undefined} obj + * @param {string} sProp + * @param {string} [sSuffix] + * @return {string|null} + */ + static findProperty(obj, sProp, sSuffix) + { + if (obj) { + for (let i = 0; i < Web.asBrowserPrefixes.length; i++) { + let sName = Web.asBrowserPrefixes[i]; + if (sSuffix) { + sName += sSuffix; + let sEvent = sProp + sName; + if (sEvent in obj) return sName; + } else { + if (!sName) { + sName = sProp[0]; + } else { + sName += sProp[0].toUpperCase(); + } + sName += sProp.substr(1); + if (sName in obj) return sName; + } + } + } + return null; + } + + /** + * getURLParm(sParm) + * + * First looks for sParm exactly as specified, then looks for the lower-case version. + * + * @param {string} sParm + * @return {string|undefined} + */ + static getURLParm(sParm) + { + if (!Web.parmsURL) { + Web.parmsURL = Web.parseURLParms(); + } + return Web.parmsURL[sParm] || Web.parmsURL[sParm.toLowerCase()]; + } + + /** + * parseURLParms(sParms) + * + * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?') + * @return {Object} containing properties for each parameter found + */ + static parseURLParms(sParms) + { + let aParms = {}; + if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used + if (!sParms) { + /* + * Note that window.location.href returns the entire URL, whereas window.location.search + * returns only the parameters, if any (starting with the '?', which we skip over with a substr() call). + */ + sParms = window.location.search.substr(1); + } + let match; + let pl = /\+/g; // RegExp for replacing addition symbol with a space + let search = /([^&=]+)=?([^&]*)/g; + let decode = function(s) + { + return decodeURIComponent(s.replace(pl, " ")); + }; + + while ((match = search.exec(sParms))) { + aParms[decode(match[1])] = decode(match[2]); + } + } + return aParms; + } + + /** + * downloadFile(sData, sType, fBase64, sFileName) + * + * @param {string} sData + * @param {string} sType + * @param {boolean} [fBase64] + * @param {string} [sFileName] + */ + static downloadFile(sData, sType, fBase64, sFileName) + { + let link = null, sAlert; + let sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ","; + + if (typeof sData != 'string' + && typeof Blob == 'function' && typeof URL != 'undefined' && URL && typeof URL.createObjectURL == 'function') { + let blob = new Blob([sData], { type: 'application/octet-stream' }); + sURI = URL.createObjectURL(blob); + } + else if (!Web.isUserAgent("Firefox")) { + sURI += (fBase64? sData : encodeURI(sData)); + } else { + sURI += (fBase64? sData : encodeURIComponent(sData)); + } + if (sFileName) { + link = document.createElement('a'); + if (typeof link.download != 'string') link = null; + } + if (link) { + link.href = sURI; + link.download = sFileName; + document.body.appendChild(link); // Firefox allegedly requires the link to be in the body + link.click(); + document.body.removeChild(link); + sAlert = 'Check your Downloads folder for ' + sFileName + '.'; + // if (Web.isUserAgent("Chrome")) { + // sAlert += '\n\nIn Chrome, after clicking OK, you may ALSO have to select the "Window" menu, choose "Downloads", and then locate this download and select "Keep".'; + // sAlert += '\n\nThis is part of Chrome\'s "Security By Jumping Through Extra Hoops" technology, which is much easier for Google to implement than actually checking for something malicious.'; + // sAlert += '\n\nAnd for the record, there is nothing malicious on the PCjs website.'; + // } + } else { + window.open(sURI); + sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.'; + } + return sAlert; + } + + /** + * onCountRepeat(n, fnRepeat, fnComplete, msDelay) + * + * Call fnRepeat() n times with an msDelay millisecond delay between calls, + * then call fnComplete() when n has been exhausted OR fnRepeat() returns false. + * + * @param {number} n + * @param {function()} fnRepeat + * @param {function()} fnComplete + * @param {number} [msDelay] + */ + static onCountRepeat(n, fnRepeat, fnComplete, msDelay) + { + let fnTimeout = function doCountRepeat() + { + n -= 1; + if (n >= 0) { + if (!fnRepeat()) n = 0; + } + if (n > 0) { + setTimeout(fnTimeout, msDelay || 0); + return; + } + fnComplete(); + }; + fnTimeout(); + } + + /** + * onClickRepeat(e, msDelay, msRepeat, fn) + * + * Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter, + * as long as HTML control Object e has an active "down" event and fn() returns true. + * + * @param {Object} e + * @param {number} msDelay + * @param {number} msRepeat + * @param {function(boolean)} fn is passed false on the first call, true on all repeated calls + */ + static onClickRepeat(e, msDelay, msRepeat, fn) + { + let ms = 0, timer = null, fIgnoreMouseEvents = false; + + let fnRepeat = function doClickRepeat() + { + if (fn(ms === msRepeat)) { + timer = setTimeout(fnRepeat, ms); + ms = msRepeat; + } + }; + e.onmousedown = function() + { + // Web.log("onMouseDown()"); + if (!fIgnoreMouseEvents) { + if (!timer) { + ms = msDelay; + fnRepeat(); + } + } + }; + e.ontouchstart = function() + { + // Web.log("onTouchStart()"); + if (!timer) { + ms = msDelay; + fnRepeat(); + } + }; + e.onmouseup = e.onmouseout = function() + { + // Web.log("onMouseUp()/onMouseOut()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + e.ontouchend = e.ontouchcancel = function() + { + // Web.log("onTouchEnd()/onTouchCancel()"); + if (timer) { + clearTimeout(timer); + timer = null; + } + /* + * Devices that generate ontouch* events ALSO generate onmouse* events, + * and generally do so immediately after all the touch events are complete, + * so unless we want double the action, we need to ignore mouse events. + */ + fIgnoreMouseEvents = true; + }; + } + + /** + * onPageEvent(sName, fn) + * + * For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but + * instead use Web.onInit(), Web.onShow(), and Web.onExit(), respectively. + * + * The only components that should still use onPageEvent() are THIS component (see the bottom of this file) + * and components that need to capture other events (eg, the 'onresize' event in the Video component). + * + * This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers + * for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'], + * etc, themselves. However, that's less of a concern now, because assuming everyone else is now using + * onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one. + * + * NOTE: It's risky to refer to obscure event handlers with "dot" names, because the Closure Compiler may + * erroneously replace them (eg, window.onpageshow is a good example). + * + * @param {string} sFunc + * @param {function()} fn + */ + static onPageEvent(sFunc, fn) + { + if (window) { + let fnPrev = window[sFunc]; + if (typeof fnPrev !== 'function') { + window[sFunc] = fn; + } else { + /* + * TODO: Determine whether there's any value in receiving/sending the Event object that the + * browser provides when it generates the original event. + */ + window[sFunc] = function onWindowEvent() + { + if (fnPrev) fnPrev(); + fn(); + }; + } + } + }; + + /** + * onInit(fn) + * + * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler. + * + * @param {function()} fn + */ + static onInit(fn) + { + Web.aPageEventHandlers['init'].push(fn); + }; + + /** + * onShow(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler. + */ + static onShow(fn) + { + Web.aPageEventHandlers['show'].push(fn); + }; + + /** + * onError(sMessage) + * + * @param {string} sMessage + */ + static onError(sMessage) + { + Web.notice(sMessage + "\n\nIf it happens again, please send the URL to support@pcjs.org. Thanks."); + } + + /** + * onExit(fn) + * + * @param {function()} fn + * + * Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler. + */ + static onExit(fn) + { + Web.aPageEventHandlers['exit'].push(fn); + }; + + /** + * doPageEvent(afn) + * + * @param {Array.} afn + */ + static doPageEvent(afn) + { + if (Web.fPageEventsEnabled) { + try { + for (let i = 0; i < afn.length; i++) { + afn[i](); + } + } catch (e) { + Web.onError("An unexpected error occurred: " + e.message); + } + } + }; + + /** + * enablePageEvents(fEnable) + * + * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default) + */ + static enablePageEvents(fEnable) + { + if (!Web.fPageEventsEnabled && fEnable) { + Web.fPageEventsEnabled = true; + if (Web.fPageLoaded) Web.sendPageEvent('init'); + if (Web.fPageShowed) Web.sendPageEvent('show'); + return; + } + Web.fPageEventsEnabled = fEnable; + } + + /** + * sendPageEvent(sEvent) + * + * This allows us to manually trigger page events. + * + * @param {string} sEvent (one of 'init', 'show' or 'exit') + */ + static sendPageEvent(sEvent) + { + if (Web.aPageEventHandlers[sEvent]) { + Web.doPageEvent(Web.aPageEventHandlers[sEvent]); + } + } +} + +Web.parmsURL = null; // initialized on first call to parseURLParms() + +Web.aPageEventHandlers = { + 'init': [], // list of window 'onload' handlers + 'show': [], // list of window 'onpageshow' handlers + 'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible) +}; + +Web.asBrowserPrefixes = ['', 'moz', 'ms', 'webkit']; + +Web.fPageLoaded = false; // set once the page's first 'onload' event has occurred +Web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred +Web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents() + +/** + * fLocalStorage + * + * true if localStorage support exists, is enabled, and works; "falsey" otherwise + * + * @type {boolean|null} + */ +Web.fLocalStorage = null; + +/** + * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This isn't cutting it. + * + * @const {string} + */ +Web.sLocalStorageTest = "PCjs.localStorage"; + +Web.onPageEvent('onload', function onPageLoad() { + Web.fPageLoaded = true; + Web.doPageEvent(Web.aPageEventHandlers['init']); +}); + +Web.onPageEvent('onpageshow', function onPageShow() { + Web.fPageShowed = true; + Web.doPageEvent(Web.aPageEventHandlers['show']); +}); + +Web.onPageEvent(Web.isUserAgent("iOS")? 'onpagehide' : (Web.isUserAgent("Opera")? 'onunload' : 'onbeforeunload'), function onPageUnload() { + Web.doPageEvent(Web.aPageEventHandlers['exit']); +}); + +/* + * If this is DEBUG (eg, un-COMPILED) code, then allow the user to override DEBUG with a "debug=false" embedded in + * the URL; note that the Closure Compiler won't let us alter the DEBUG variable, because it's defined as a @define, which + * implies @const as well, so we must resort to modifying it indirectly, using the global window object. + * + * TODO: Consider yet another embedXXX() parameter that would also allow DEBUG to be turned off on a page-by-page basis; + * it's low priority, because it would only affect machines that explicitly request un-COMPILED code, and there are very + * few such machines (eg, /_posts/2015-01-17-pcjs-uncompiled.md). + * + * Deal with Web.getURLParm("backtrack") in /modules/pcx86/lib/defines.js at the same time. + */ +if (DEBUG && window) { + let sDebug = Web.getURLParm("debug"); + if (sDebug == "false") { + window['DEBUG'] = false; + } +} + + + +/** + * @copyright https://www.pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2018 + */ + +/* + * All PCjs components now use JSDoc types, primarily so that Google's Closure Compiler will compile + * everything with zero warnings when ADVANCED_OPTIMIZATIONS are enabled. For more information about + * the JSDoc types supported by the Closure Compiler: + * + * https://developers.google.com/closure/compiler/docs/js-for-compiler#types + * + * I also attempted to validate this code with JSLint, but it complained too much; eg, it didn't like + * "while (true)", a tried and "true" programming convention for decades, and it wanted me to replace + * all "++" and "--" operators with "+= 1" and "-= 1", use "(s || '')" instead of "(s? s : '')", etc. + * + * I prefer sticking with traditional C-style idioms, in part because they are more portable. That + * does NOT mean I'm trying to write "portable JavaScript," but some of this code was ported from C code + * I'd written long ago, so portability is good, and I'm not going to throw that away if there's no need. + * + * UPDATE: I've since switched from JSLint to JSHint, which seems to have more reasonable defaults. + * And for new code, I have adopted some popular JavaScript idioms, like "(s || '')", although the need + * for those kinds of expressions will be reduced as I also start adopting some ES6 features, like + * default parameters. + */ + + + +/** + * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, + * it deters us from defining named properties on our components; eg: + * + * this['exports'] = {...} + * + * results in an error: + * + * Cannot do '[]' access on a struct + * + * So, in order to define 'exports', we must override the @struct assumption by annotating + * the class as @unrestricted (or @dict). Note that this must be done both here and in the + * subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference* + * the named property either. + * + * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to + * define every single property the class uses in its constructor, which results in a fair + * bit of redundant initialization, since many properties aren't (and don't need to be) fully + * initialized until the appropriate init(), reset(), restore(), etc. function is called. + * + * The upside, however, may be that since the structure of the class is completely defined by + * the constructor, JavaScript engines may be able to optimize and run more efficiently. + * + * @unrestricted + */ +class Component { + /** + * Component(type, parms, bitsMessage) + * + * A Component object requires: + * + * type: a user-defined type name (eg, "CPU") + * + * and accepts any or all of the following (parms) properties: + * + * id: component ID (default is "") + * name: component name (default is ""; if blank, toString() will use the type name only) + * comment: component comment string (default is undefined) + * + * Component subclasses will usually have additional (parms) properties. + * + * @param {string} type + * @param {Object} [parms] + * @param {number} [bitsMessage] selects message(s) that the component wants to enable (default is 0) + */ + constructor(type, parms, bitsMessage) + { + this.type = type; + + if (!parms) parms = {'id': "", 'name': ""}; + + this.id = parms['id'] || ""; + this.name = parms['name']; + this.comment = parms['comment']; + this.parms = parms; + + /* + * The following Component properties need to be accessible by other machines and/or command scripts; + * well, OK, or we could have exported some new functions to walk the contents of these properties, as we + * did with findMachineComponent(), but this works just as well. + * + * Also, while the double-assignment looks silly (ie, using both dot and bracket property notation), it + * resolves a complaint from the Closure Compiler, because if we use ONLY bracket notation here, then the + * Compiler wants us to change all the other references to bracket notation as well. + */ + this.exports = this['exports'] = {}; + this.bindings = this['bindings'] = {}; + + let i = this.id.indexOf('.'); + if (i < 0) { + this.idMachine = "PCjs"; + this.idComponent = this.id; + } else { + this.idMachine = this.id.substr(0, i); + this.idComponent = this.id.substr(i + 1); + } + + /* + * Gather all the various component flags (booleans) into a single "flags" object, and encourage + * subclasses to do the same, to reduce the property clutter we have to wade through while debugging. + */ + this.flags = { + ready: false, + busy: false, + busyCancel: false, + initDone: false, + powered: false, + unloading: false, + error: false + }; + + this.fnReady = null; + this.clearError(); + this.bitsMessage = bitsMessage || 0; + + this.cmp = null; + this.bus = null; + this.cpu = null; + this.dbg = null; + + /* + * TODO: Consider adding another parameter to the Component() constructor that allows components to tell + * us if they support single or multiple instances per machine. For example, there can be multiple SerialPort + * components per machine, but only one CPU component (some machines also support an FPU, but that component + * is considered separate from the CPU). + * + * It's not critical, but it would help catch machine configuration errors; for example, a machine that mistakenly + * includes two CPU components may, aside from wasting memory, end up with odd side-effects, like unresponsive + * CPU controls. + */ + Component.add(this); + } + + /** + * Component.add(component) + * + * @param {Component} component + */ + static add(component) + { + /* + * This just generates a lot of useless noise, handy in the early days, not so much these days.... + * + * if (DEBUG) Component.log("Component.add(" + component.type + "," + component.id + ")"); + */ + Component.components.push(component); + } + + /** + * Component.addMachine(idMachine) + * + * @param {string} idMachine + */ + static addMachine(idMachine) + { + Component.machines[idMachine] = {}; + } + + /** + * Component.getMachines() + * + * @return {Array.} + */ + static getMachines() + { + return Object.keys(Component.machines); + } + + /** + * Component.addMachineResource(idMachine, sName, data) + * + * @param {string} idMachine + * @param {string|null} sName (name of the resource) + * @param {*} data + */ + static addMachineResource(idMachine, sName, data) + { + /* + * I used to assert(Component.machines[idMachine]), but when we're running as a Node app, embed.js is not used, + * so addMachine() is never called, so resources do not need to be recorded. + */ + if (Component.machines[idMachine] && sName) { + Component.machines[idMachine][sName] = data; + } + } + + /** + * Component.getMachineResources(idMachine) + * + * @param {string} idMachine + * @return {Object|undefined} + */ + static getMachineResources(idMachine) + { + return Component.machines[idMachine]; + } + + /** + * Component.getTime() + * + * @return {number} the current time, in milliseconds + */ + static getTime() + { + return Date.now() || +new Date(); + } + + /** + * Component.log(s, type) + * + * For diagnostic output only. + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + */ + static log(s, type) + { + if (!COMPILED) { + if (s) { + let sElapsed = "", sMsg = (type? (type + ": ") : "") + s; + if (typeof Usr != "undefined") { + if (Component.msStart === undefined) { + Component.msStart = Component.getTime(); + } + sElapsed = (Component.getTime() - Component.msStart) + "ms: "; + } + sMsg = sMsg.replace(/\r/g, '\\r').replace(/\n/g, ' '); + if (window && window.console) console.log(sElapsed + sMsg); + } + } + } + + /** + * Component.assert(f, s) + * + * Verifies conditions that must be true (for DEBUG builds only). + * + * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds. + * TODO: Add a task to the build process that "asserts" there are no instances of "assertion failure" in RELEASE builds. + * + * @param {boolean} f is the expression we are asserting to be true + * @param {string} [s] is description of the assertion on failure + */ + static assert(f, s) + { + if (DEBUG) { + if (!f) { + if (!s) s = "assertion failure"; + Component.log(s); + throw new Error(s); + } + } + } + + /** + * Component.print(s) + * + * Components that inherit from this class should use this.print(), rather than Component.print(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @this {Component} + * @param {string} s + */ + static print(s) + { + if (!COMPILED) { + let i = s.lastIndexOf('\n'); + if (i >= 0) { + Component.println(s.substr(0, i)); + s = s.substr(i + 1); + } + Component.printBuffer += s; + } + } + + /** + * Component.println(s, type, id) + * + * Components that inherit from this class should use this.println(), rather than Component.println(), because + * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the + * class method would improperly affect any other machines loaded on the same page). + * + * @param {string} [s] is the message text + * @param {string} [type] is the message type + * @param {string} [id] is the caller's ID, if any + */ + static println(s, type, id) + { + if (!COMPILED) { + s = Component.printBuffer + (s || ""); + Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type); + Component.printBuffer = ""; + } + } + + /** + * Component.notice(s, fPrintOnly, id) + * + * notice() is like println() but implies a need for user notification, so we alert() as well. + * + * @param {string} s is the message text + * @param {boolean} [fPrintOnly] + * @param {string} [id] is the caller's ID, if any + * @return {boolean} + */ + static notice(s, fPrintOnly, id) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.NOTICE, id); + } + if (!fPrintOnly) Component.alertUser((id? (id + ": ") : "") + s); + return true; + } + + /** + * Component.warning(s) + * + * @param {string} s describes the warning + */ + static warning(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.WARNING); + } + Component.alertUser(s); + } + + /** + * Component.error(s) + * + * @param {string} s describes the error; an alert() is displayed as well + */ + static error(s) + { + if (!COMPILED) { + Component.println(s, Component.PRINT.ERROR); + } + Component.alertUser(s); + } + + /** + * Component.alertUser(sMessage) + * + * @param {string} sMessage + */ + static alertUser(sMessage) + { + if (window) { + window.alert(sMessage); + } else { + Component.log(sMessage); + } + } + + /** + * Component.confirmUser(sPrompt) + * + * @param {string} sPrompt + * @returns {boolean} true if the user clicked OK, false if Cancel/Close + */ + static confirmUser(sPrompt) + { + let fResponse = false; + if (window) { + fResponse = window.confirm(sPrompt); + } + return fResponse; + } + + /** + * Component.promptUser() + * + * @param {string} sPrompt + * @param {string} [sDefault] + * @returns {string|null} + */ + static promptUser(sPrompt, sDefault) + { + let sResponse = null; + if (window) { + sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault); + } + return sResponse; + } + + /** + * Component.appendControl(control, sText) + * + * @param {Object} control + * @param {string} sText + */ + static appendControl(control, sText) + { + control.value += sText; + /* + * Prevent the