diff --git a/modules/pc6502/lib/bus.js b/modules/pc6502/lib/bus.js
new file mode 100644
index 000000000..65457f629
--- /dev/null
+++ b/modules/pc6502/lib/bus.js
@@ -0,0 +1,1073 @@
+/**
+ * @fileoverview Implements the PC6502 Bus component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var usr = require("../../shared/lib/usrlib");
+ var Component = require("../../shared/lib/component");
+ var Memory = require("./memory");
+ var Messages = require("./messages");
+ var State = require("./state");
+}
+
+/**
+ * Bus(cpu, dbg)
+ *
+ * The Bus component manages physical memory and I/O address spaces.
+ *
+ * The Bus component has no UI elements, so it does not require an init() handler,
+ * but it still inherits from the Component class and must be allocated like any
+ * other device component. It's currently allocated by the Computer's init() handler,
+ * which then calls the initBus() method of all the other components.
+ *
+ * When initMemory() initializes the entire address space, it also passes aMemBlocks
+ * to the CPU object, so that the CPU can perform its own address-to-block calculations
+ * (essential, for example, when the CPU enables paging).
+ *
+ * For memory beyond the simple needs of the ROM and RAM components (ie, memory-mapped
+ * devices), the address space must still be allocated through the Bus component via
+ * addMemory(). If the component needs something more than simple read/write storage,
+ * it must provide a custom controller.
+ *
+ * All port (I/O) operations are defined by external handlers; they register with us,
+ * and we manage those registrations and provide support for I/O breakpoints, but the
+ * only default I/O behavior we provide is ignoring writes to any unregistered output
+ * ports and returning 0xff from any unregistered input ports.
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsBus
+ * @param {CPUState} cpu
+ * @param {Debugger} dbg
+ */
+function Bus(parmsBus, cpu, dbg)
+{
+ Component.call(this, "Bus", parmsBus, Bus);
+
+ this.cpu = cpu;
+ this.dbg = dbg;
+
+ this.nBusWidth = parmsBus['busWidth'] || 16;
+
+ /*
+ * Compute all Bus memory block parameters, based on the width of the bus.
+ *
+ * Regarding blockTotal, we want to avoid using block overflow expressions like:
+ *
+ * iBlock < this.nBlockTotal? iBlock : 0
+ *
+ * As long as we know that blockTotal is a power of two (eg, 256 or 0x100, in the case of
+ * nBusWidth == 20 and blockSize == 4096), we can define blockMask as (blockTotal - 1) and
+ * rewrite the previous expression as:
+ *
+ * iBlock & this.nBlockMask
+ *
+ * Bus Property Old hard-coded values (when nBusWidth was always 20)
+ * ------------ ----------------------------------------------------
+ * this.nBusLimit 0xfffff
+ * this.nBusMask [same as busLimit]
+ * this.nBlockSize 4096
+ * this.nBlockLen (this.nBlockSize >> 2)
+ * this.nBlockShift 12
+ * this.nBlockLimit 0xfff
+ * this.nBlockTotal ((this.nBusLimit + this.nBlockSize) / this.nBlockSize) | 0
+ * this.nBlockMask (this.nBlockTotal - 1) [ie, 0xff]
+ *
+ * Note that we choose a nBlockShift value (and thus a physical memory block size) based on "buswidth":
+ *
+ * Bus Width Block Shift Block Size
+ * --------- ----------- ----------
+ * 16 bits (64Kb address space): 10 1Kb (64 maximum blocks)
+ * 20 bits (1Mb address space): 12 4Kb (256 maximum blocks)
+ * 24 bits (16Mb address space): 14 16Kb (1K maximum blocks)
+ * 32 bits (4Gb address space); 15 32Kb (128K maximum blocks)
+ *
+ * The coarser block granularities (ie, 16Kb and 32Kb) may cause problems for certain RAM and/or ROM
+ * allocations that are contiguous but are allocated out of order, or that have different controller
+ * requirements. Your choices, for the moment, are either to ensure the allocations are performed in
+ * order, or to choose smaller nBlockShift values (at the expense of a generating a larger block array).
+ */
+ this.addrTotal = Math.pow(2, this.nBusWidth);
+ this.nBusLimit = this.nBusMask = (this.addrTotal - 1) | 0;
+ this.nBlockShift = (this.nBusWidth <= 16)? 10 : ((this.nBusWidth <= 20)? 12 : (this.nBusWidth <= 24? 14 : 15));
+ this.nBlockSize = 1 << this.nBlockShift;
+ this.nBlockLen = this.nBlockSize >> 2;
+ this.nBlockLimit = this.nBlockSize - 1;
+ this.nBlockTotal = (this.addrTotal / this.nBlockSize) | 0;
+ this.nBlockMask = this.nBlockTotal - 1;
+ this.assert(this.nBlockMask <= Bus.BlockInfo.num.mask);
+
+ /*
+ * Lists of I/O notification functions: aPortInputNotify and aPortOutputNotify are arrays, indexed by
+ * port, of sub-arrays which contain:
+ *
+ * [0]: registered function to call for every I/O access
+ *
+ * The registered function is called with the port address, and if the access was triggered by the CPU,
+ * the instruction pointer (IP) at the point of access.
+ *
+ * WARNING: Unlike the (old) read and write memory notification functions, these support only one
+ * pair of input/output functions per port. A more sophisticated architecture could support a list
+ * of chained functions across multiple components, but I doubt that will be necessary here.
+ *
+ * UPDATE: The Debugger now piggy-backs on these arrays to indicate ports for which it wants notification
+ * of I/O. In those cases, the registered component/function elements may or may not be set, but the
+ * following additional element will be set:
+ *
+ * [1]: true to break on I/O, false to ignore I/O
+ *
+ * The false case is important if fPortInputBreakAll and/or fPortOutputBreakAll is set, because it allows the
+ * Debugger to selectively ignore specific ports.
+ */
+ this.aPortInputNotify = [];
+ this.aPortOutputNotify = [];
+ this.fPortInputBreakAll = this.fPortOutputBreakAll = false;
+
+ /*
+ * By default, all I/O ports are 1 byte wide; ports that are wider must add themselves to one or both of
+ * these lists, using addPortInputWidth() and/or addPortOutputWidth().
+ */
+ this.aPortInputWidth = [];
+ this.aPortOutputWidth = [];
+
+ /*
+ * Allocate empty Memory blocks to span the entire physical address space.
+ */
+ this.initMemory();
+
+ this.setReady();
+}
+
+Component.subclass(Bus);
+
+Bus.ERROR = {
+ ADD_MEM_INUSE: 1,
+ ADD_MEM_BADRANGE: 2,
+ SET_MEM_BADRANGE: 4,
+ REM_MEM_BADRANGE: 5
+};
+
+/**
+ * @typedef {number}
+ */
+var BlockInfo;
+
+/**
+ * This defines the BlockInfo bit fields used by scanMemory() when it creates the aBlocks array.
+ *
+ * @typedef {{
+ * num: BitField,
+ * count: BitField,
+ * btmod: BitField,
+ * type: BitField
+ * }}
+ */
+Bus.BlockInfo = usr.defineBitFields({num:20, count:8, btmod:1, type:3});
+
+/**
+ * BusInfo object definition (returned by scanMemory())
+ *
+ * cbTotal: total bytes allocated
+ * cBlocks: total Memory blocks allocated
+ * aBlocks: array of allocated Memory block numbers
+ *
+ * @typedef {{
+ * cbTotal: number,
+ * cBlocks: number,
+ * aBlocks: Array.
+ * }}
+ */
+var BusInfo;
+
+/**
+ * initMemory()
+ *
+ * Allocate enough (empty) Memory blocks to span the entire physical address space.
+ *
+ * @this {Bus}
+ */
+Bus.prototype.initMemory = function()
+{
+ var block = new Memory();
+ block.copyBreakpoints(this.dbg);
+ this.aMemBlocks = new Array(this.nBlockTotal);
+ for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
+ this.aMemBlocks[iBlock] = block;
+ }
+};
+
+/**
+ * reset()
+ *
+ * @this {Bus}
+ */
+Bus.prototype.reset = function()
+{
+};
+
+/**
+ * powerUp(data, fRepower)
+ *
+ * We don't need a powerDown() handler, because for largely historical reasons, our state is saved by saveMemory(),
+ * which called by the CPU.
+ *
+ * However, we do need a powerUp() handler, because on resumable machines, the Computer's onReset() function calls
+ * everyone's powerUp() handler rather than their reset() handler.
+ *
+ * TODO: Perhaps Computer should be smarter: if there's no powerUp() handler, then fallback to the reset() handler.
+ * In that case, however, we'd either need to remove the powerUp() stub in Component, or detect the existence of the stub.
+ *
+ * @this {Bus}
+ * @param {Object|null} data (always null because we supply no powerDown() handler)
+ * @param {boolean} [fRepower]
+ * @return {boolean} true if successful, false if failure
+ */
+Bus.prototype.powerUp = function(data, fRepower)
+{
+ if (!fRepower) this.reset();
+ return true;
+};
+
+/**
+ * addMemory(addr, size, type)
+ *
+ * Adds new Memory blocks to the specified address range. Any Memory blocks previously
+ * added to that range must first be removed via removeMemory(); otherwise, you'll get
+ * an allocation conflict error. This helps prevent address calculation errors, redundant
+ * allocations, etc.
+ *
+ * We've relaxed some of the original requirements (ie, that addresses must start at a
+ * block-granular address, or that sizes must be equal to exactly one or more blocks),
+ * because machines with large block sizes can make it impossible to load certain ROMs at
+ * their required addresses. Every allocation still allocates a whole number of blocks.
+ *
+ * Even so, Bus memory management does NOT provide a general-purpose heap. Most memory
+ * allocations occur during machine initialization and never change. In particular, there
+ * is NO support for removing partial-block allocations.
+ *
+ * Each Memory block keeps track of a start address (addr) and length (used), indicating
+ * the used space within the block; any free space that precedes or follows that used space
+ * can be allocated later, by simply extending the beginning or ending of the previously used
+ * space. However, any holes that might have existed between the original allocation and an
+ * extension are subsumed by the extension.
+ *
+ * @this {Bus}
+ * @param {number} addr is the starting physical address of the request
+ * @param {number} size of the request, in bytes
+ * @param {number} type is one of the Memory.TYPE constants
+ * @return {boolean} true if successful, false if not
+ */
+Bus.prototype.addMemory = function(addr, size, type)
+{
+ var addrNext = addr;
+ var sizeLeft = size;
+ var iBlock = addrNext >>> this.nBlockShift;
+
+ while (sizeLeft > 0 && iBlock < this.aMemBlocks.length) {
+
+ var block = this.aMemBlocks[iBlock];
+ var addrBlock = iBlock * this.nBlockSize;
+ var sizeBlock = this.nBlockSize - (addrNext - addrBlock);
+ if (sizeBlock > sizeLeft) sizeBlock = sizeLeft;
+
+ if (block && block.size) {
+ if (block.type == type) {
+ /*
+ * Where there is already a similar block with a non-zero size, we allow the allocation only if:
+ *
+ * 1) addrNext + sizeLeft <= block.addr (the request precedes the used portion of the current block), or
+ * 2) addrNext >= block.addr + block.used (the request follows the used portion of the current block)
+ */
+ if (addrNext + sizeLeft <= block.addr) {
+ block.used += (block.addr - addrNext);
+ block.addr = addrNext;
+ return true;
+ }
+ if (addrNext >= block.addr + block.used) {
+ var sizeAvail = block.size - (addrNext - addrBlock);
+ if (sizeAvail > sizeLeft) sizeAvail = sizeLeft;
+ block.used = addrNext - block.addr + sizeAvail;
+ addrNext = addrBlock + this.nBlockSize;
+ sizeLeft -= sizeAvail;
+ iBlock++;
+ continue;
+ }
+ }
+ return this.reportError(Bus.ERROR.ADD_MEM_INUSE, addrNext, sizeLeft);
+ }
+
+ var blockNew = new Memory(addrNext, sizeBlock, this.nBlockSize, type);
+ blockNew.copyBreakpoints(this.dbg, block);
+ this.aMemBlocks[iBlock++] = blockNew;
+
+ addrNext = addrBlock + this.nBlockSize;
+ sizeLeft -= sizeBlock;
+ }
+
+ if (sizeLeft <= 0) {
+ this.status(Math.floor(size / 1024) + "Kb " + Memory.TYPE.NAMES[type] + " at " + str.toHexWord(addr));
+ return true;
+ }
+
+ return this.reportError(Bus.ERROR.ADD_MEM_BADRANGE, addr, size);
+};
+
+/**
+ * cleanMemory(addr, size)
+ *
+ * @this {Bus}
+ * @param {number} addr
+ * @param {number} size
+ * @return {boolean} true if all blocks were clean, false if dirty; all blocks are cleaned in the process
+ */
+Bus.prototype.cleanMemory = function(addr, size)
+{
+ var fClean = true;
+ var iBlock = addr >>> this.nBlockShift;
+ while (size > 0 && iBlock < this.aMemBlocks.length) {
+ if (this.aMemBlocks[iBlock].fDirty) {
+ this.aMemBlocks[iBlock].fDirty = fClean = false;
+ this.aMemBlocks[iBlock].fDirtyEver = true;
+ }
+ size -= this.nBlockSize;
+ iBlock++;
+ }
+ return fClean;
+};
+
+/**
+ * scanMemory(info, addr, size)
+ *
+ * Returns a BusInfo object for the specified address range.
+ *
+ * @this {Bus}
+ * @param {Object} [info] previous BusInfo, if any
+ * @param {number} [addr] starting address of range (0 if none provided)
+ * @param {number} [size] size of range, in bytes (up to end of address space if none provided)
+ * @return {Object} updated info (or new info if no previous info provided)
+ */
+Bus.prototype.scanMemory = function(info, addr, size)
+{
+ if (addr == null) addr = 0;
+ if (size == null) size = (this.addrTotal - addr) | 0;
+ if (info == null) info = {cbTotal: 0, cBlocks: 0, aBlocks: []};
+
+ var iBlock = addr >>> this.nBlockShift;
+ var iBlockMax = ((addr + size - 1) >>> this.nBlockShift);
+
+ info.cbTotal = 0;
+ info.cBlocks = 0;
+ while (iBlock <= iBlockMax) {
+ var block = this.aMemBlocks[iBlock];
+ info.cbTotal += block.size;
+ if (block.size) {
+ info.aBlocks.push(usr.initBitFields(Bus.BlockInfo, iBlock, 0, 0, block.type));
+ info.cBlocks++
+ }
+ iBlock++;
+ }
+ return info;
+};
+
+/**
+ * getWidth()
+ *
+ * @this {Bus}
+ * @return {number}
+ */
+Bus.prototype.getWidth = function()
+{
+ return this.nBusWidth;
+};
+
+/**
+ * removeMemory(addr, size)
+ *
+ * Replaces every block in the specified address range with empty Memory blocks that ignore all reads/writes.
+ *
+ * TODO: Update the removeMemory() interface to reflect the relaxed requirements of the addMemory() interface.
+ *
+ * @this {Bus}
+ * @param {number} addr
+ * @param {number} size
+ * @return {boolean} true if successful, false if not
+ */
+Bus.prototype.removeMemory = function(addr, size)
+{
+ if (!(addr & this.nBlockLimit) && size && !(size & this.nBlockLimit)) {
+ var iBlock = addr >>> this.nBlockShift;
+ while (size > 0) {
+ var blockOld = this.aMemBlocks[iBlock];
+ var blockNew = new Memory(addr);
+ blockNew.copyBreakpoints(this.dbg, blockOld);
+ this.aMemBlocks[iBlock++] = blockNew;
+ addr = iBlock * this.nBlockSize;
+ size -= this.nBlockSize;
+ }
+ return true;
+ }
+ return this.reportError(Bus.ERROR.REM_MEM_BADRANGE, addr, size);
+};
+
+/**
+ * getMemoryBlocks(addr, size)
+ *
+ * @this {Bus}
+ * @param {number} addr is the starting physical address
+ * @param {number} size of the request, in bytes
+ * @return {Array} of Memory blocks
+ */
+Bus.prototype.getMemoryBlocks = function(addr, size)
+{
+ var aBlocks = [];
+ var iBlock = addr >>> this.nBlockShift;
+ while (size > 0 && iBlock < this.aMemBlocks.length) {
+ aBlocks.push(this.aMemBlocks[iBlock++]);
+ size -= this.nBlockSize;
+ }
+ return aBlocks;
+};
+
+/**
+ * setMemoryBlocks(addr, size, aBlocks, type)
+ *
+ * If no type is specified, then specified address range uses all the provided blocks as-is;
+ * this form of setMemoryBlocks() is used for complete physical aliases.
+ *
+ * Otherwise, new blocks are allocated with the specified type; the underlying memory from the
+ * provided blocks is still used, but the new blocks may have different access to that memory.
+ *
+ * @this {Bus}
+ * @param {number} addr is the starting physical address
+ * @param {number} size of the request, in bytes
+ * @param {Array} aBlocks as returned by getMemoryBlocks()
+ * @param {number} [type] is one of the Memory.TYPE constants
+ */
+Bus.prototype.setMemoryBlocks = function(addr, size, aBlocks, type)
+{
+ var i = 0;
+ var iBlock = addr >>> this.nBlockShift;
+ while (size > 0 && iBlock < this.aMemBlocks.length) {
+ var block = aBlocks[i++];
+ this.assert(block);
+ if (!block) break;
+ if (type !== undefined) {
+ var blockNew = new Memory(addr);
+ blockNew.clone(block, type, this.dbg);
+ block = blockNew;
+ }
+ this.aMemBlocks[iBlock++] = block;
+ size -= this.nBlockSize;
+ }
+};
+
+/**
+ * getByte(addr)
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @return {number} byte (8-bit) value at that address
+ */
+Bus.prototype.getByte = function(addr)
+{
+ return this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].readByte(addr & this.nBlockLimit, addr);
+};
+
+/**
+ * getByteDirect(addr)
+ *
+ * This is useful for the Debugger and other components that want to bypass getByte() breakpoint detection.
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @return {number} byte (8-bit) value at that address
+ */
+Bus.prototype.getByteDirect = function(addr)
+{
+ return this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].readByteDirect(addr & this.nBlockLimit, addr);
+};
+
+/**
+ * getShort(addr)
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @return {number} word (16-bit) value at that address
+ */
+Bus.prototype.getShort = function(addr)
+{
+ var off = addr & this.nBlockLimit;
+ var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
+ if (off != this.nBlockLimit) {
+ return this.aMemBlocks[iBlock].readShort(off, addr);
+ }
+ return this.aMemBlocks[iBlock++].readByte(off, addr) | (this.aMemBlocks[iBlock & this.nBlockMask].readByte(0, addr + 1) << 8);
+};
+
+/**
+ * getShortDirect(addr)
+ *
+ * This is useful for the Debugger and other components that want to bypass getShort() breakpoint detection.
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @return {number} word (16-bit) value at that address
+ */
+Bus.prototype.getShortDirect = function(addr)
+{
+ var off = addr & this.nBlockLimit;
+ var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
+ if (off != this.nBlockLimit) {
+ return this.aMemBlocks[iBlock].readShortDirect(off, addr);
+ }
+ return this.aMemBlocks[iBlock++].readByteDirect(off, addr) | (this.aMemBlocks[iBlock & this.nBlockMask].readByteDirect(0, addr + 1) << 8);
+};
+
+/**
+ * setByte(addr, b)
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe)
+ */
+Bus.prototype.setByte = function(addr, b)
+{
+ this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].writeByte(addr & this.nBlockLimit, b & 0xff, addr);
+};
+
+/**
+ * setByteDirect(addr, b)
+ *
+ * This is useful for the Debugger and other components that want to bypass breakpoint detection AND read-only
+ * memory protection (for example, this is an interface the ROM component could use to initialize ROM contents).
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe)
+ */
+Bus.prototype.setByteDirect = function(addr, b)
+{
+ this.aMemBlocks[(addr & this.nBusMask) >>> this.nBlockShift].writeByteDirect(addr & this.nBlockLimit, b & 0xff, addr);
+};
+
+/**
+ * setShort(addr, w)
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe)
+ */
+Bus.prototype.setShort = function(addr, w)
+{
+ var off = addr & this.nBlockLimit;
+ var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
+ if (off != this.nBlockLimit) {
+ this.aMemBlocks[iBlock].writeShort(off, w & 0xffff, addr);
+ return;
+ }
+ this.aMemBlocks[iBlock++].writeByte(off, w & 0xff, addr);
+ this.aMemBlocks[iBlock & this.nBlockMask].writeByte(0, (w >> 8) & 0xff, addr + 1);
+};
+
+/**
+ * setShortDirect(addr, w)
+ *
+ * This is useful for the Debugger and other components that want to bypass breakpoint detection AND read-only
+ * memory protection (for example, this is an interface the ROM component could use to initialize ROM contents).
+ *
+ * @this {Bus}
+ * @param {number} addr is a physical address
+ * @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe)
+ */
+Bus.prototype.setShortDirect = function(addr, w)
+{
+ var off = addr & this.nBlockLimit;
+ var iBlock = (addr & this.nBusMask) >>> this.nBlockShift;
+ if (off != this.nBlockLimit) {
+ this.aMemBlocks[iBlock].writeShortDirect(off, w & 0xffff, addr);
+ return;
+ }
+ this.aMemBlocks[iBlock++].writeByteDirect(off, w & 0xff, addr);
+ this.aMemBlocks[iBlock & this.nBlockMask].writeByteDirect(0, (w >> 8) & 0xff, addr + 1);
+};
+
+/**
+ * addMemBreak(addr, fWrite)
+ *
+ * @this {Bus}
+ * @param {number} addr
+ * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
+ */
+Bus.prototype.addMemBreak = function(addr, fWrite)
+{
+ if (DEBUGGER) {
+ var iBlock = addr >>> this.nBlockShift;
+ this.aMemBlocks[iBlock].addBreakpoint(addr & this.nBlockLimit, fWrite);
+ }
+};
+
+/**
+ * removeMemBreak(addr, fWrite)
+ *
+ * @this {Bus}
+ * @param {number} addr
+ * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
+ */
+Bus.prototype.removeMemBreak = function(addr, fWrite)
+{
+ if (DEBUGGER) {
+ var iBlock = addr >>> this.nBlockShift;
+ this.aMemBlocks[iBlock].removeBreakpoint(addr & this.nBlockLimit, fWrite);
+ }
+};
+
+/**
+ * saveMemory(fAll)
+ *
+ * The only memory blocks we save are those marked as dirty, but most likely all of RAM will have been marked dirty,
+ * and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed
+ * what was already there), it's unlikely that would reduce the number of RAM blocks we must save/restore. At least
+ * all the ROM blocks should be clean (except in the unlikely event that the Debugger was used to modify them).
+ *
+ * All dirty blocks will be stored in a single array, as pairs of block numbers and data arrays, like so:
+ *
+ * [iBlock0, [dw0, dw1, ...], iBlock1, [dw0, dw1, ...], ...]
+ *
+ * In a normal 4Kb block, there will be 1K DWORD values in the data array. Remember that each DWORD is a signed 32-bit
+ * integer (because they are formed using bit-wise operator rather than floating-point math operators), so don't be
+ * surprised to see negative numbers in the data.
+ *
+ * The above example assumes "uncompressed" data arrays. If we choose to use "compressed" data arrays, the data arrays
+ * will look like:
+ *
+ * [count0, dw0, count1, dw1, ...]
+ *
+ * where each count indicates how many times the following DWORD value occurs. A data array length less than 1K indicates
+ * that it's compressed, since we'll only store them in compressed form if they actually shrank, and we'll use State
+ * helper methods compress() and decompress() to create and expand the compressed data arrays.
+ *
+ * @this {Bus}
+ * @param {boolean} [fAll] (true to save all non-ROM memory blocks, regardless of their dirty flags)
+ * @return {Array} a
+ */
+Bus.prototype.saveMemory = function(fAll)
+{
+ var i = 0;
+ var a = [];
+
+ for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
+ var block = this.aMemBlocks[iBlock];
+ /*
+ * We have to check both fDirty and fDirtyEver, because we may have called cleanMemory() on some of
+ * the memory blocks (eg, video memory), and while cleanMemory() will clear a dirty block's fDirty flag,
+ * it also sets the dirty block's fDirtyEver flag, which is left set for the lifetime of the machine.
+ */
+ if (fAll && block.type != Memory.TYPE.ROM || block.fDirty || block.fDirtyEver) {
+ a[i++] = iBlock;
+ a[i++] = State.compress(block.save());
+ }
+ }
+
+ return a;
+};
+
+/**
+ * restoreMemory(a)
+ *
+ * This restores the contents of all Memory blocks; called by CPUState.restore().
+ *
+ * In theory, we ONLY have to save/restore block contents. Other block attributes,
+ * like the type, the memory controller (if any), and the active memory access functions,
+ * should already be restored, since every component (re)allocates all the memory blocks
+ * it was using when it's restored. And since the CPU is guaranteed to be the last
+ * component to be restored, all those blocks (and their attributes) should be in place now.
+ *
+ * See saveMemory() for more information on how the memory block contents are saved.
+ *
+ * @this {Bus}
+ * @param {Array} a
+ * @return {boolean} true if successful, false if not
+ */
+Bus.prototype.restoreMemory = function(a)
+{
+ var i;
+ for (i = 0; i < a.length - 1; i += 2) {
+ var iBlock = a[i];
+ var adw = a[i+1];
+ if (adw && adw.length < this.nBlockLen) {
+ adw = State.decompress(adw, this.nBlockLen);
+ }
+ var block = this.aMemBlocks[iBlock];
+ if (!block || !block.restore(adw)) {
+ /*
+ * Either the block to restore hasn't been allocated, indicating a change in the machine
+ * configuration since it was last saved (the most likely explanation) or there's some internal
+ * inconsistency (eg, the block size is wrong).
+ */
+ Component.error("Unable to restore memory block " + iBlock);
+ return false;
+ }
+ }
+ return true;
+};
+
+/**
+ * addPortInputBreak(port)
+ *
+ * @this {Bus}
+ * @param {number} [port]
+ * @return {boolean} true if break on port input enabled, false if disabled
+ */
+Bus.prototype.addPortInputBreak = function(port)
+{
+ if (port === undefined) {
+ this.fPortInputBreakAll = !this.fPortInputBreakAll;
+ return this.fPortInputBreakAll;
+ }
+ if (this.aPortInputNotify[port] === undefined) {
+ this.aPortInputNotify[port] = [null, false];
+ }
+ this.aPortInputNotify[port][1] = !this.aPortInputNotify[port][1];
+ return this.aPortInputNotify[port][1];
+};
+
+/**
+ * addPortInputNotify(start, end, fn)
+ *
+ * Add a port input-notification handler to the list of such handlers.
+ *
+ * @this {Bus}
+ * @param {number} start port address
+ * @param {number} end port address
+ * @param {function(number,number)} fn is called with the port and IP values at the time of the input
+ */
+Bus.prototype.addPortInputNotify = function(start, end, fn)
+{
+ if (fn !== undefined) {
+ for (var port = start; port <= end; port++) {
+ if (this.aPortInputNotify[port] !== undefined) {
+ Component.warning("Input port " + str.toHexWord(port) + " already registered");
+ continue;
+ }
+ this.aPortInputNotify[port] = [fn, false];
+ if (MAXDEBUG) this.log("addPortInputNotify(" + str.toHexWord(port) + ")");
+ }
+ }
+};
+
+/**
+ * addPortInputTable(component, table, offset)
+ *
+ * Add port input-notification handlers from the specified table (a batch version of addPortInputNotify)
+ *
+ * @this {Bus}
+ * @param {Component} component
+ * @param {Object} table
+ * @param {number} [offset] is an optional port offset
+ */
+Bus.prototype.addPortInputTable = function(component, table, offset)
+{
+ if (offset === undefined) offset = 0;
+ for (var port in table) {
+ this.addPortInputNotify(+port + offset, +port + offset, table[port].bind(component));
+ }
+};
+
+/**
+ * addPortInputWidth(port, size)
+ *
+ * By default, all input ports are 1 byte wide; ports that are wider must call this function.
+ *
+ * @this {Bus}
+ * @param {number} port
+ * @param {number} size (1, 2 or 4)
+ */
+Bus.prototype.addPortInputWidth = function(port, size)
+{
+ this.aPortInputWidth[port] = size;
+};
+
+/**
+ * checkPortInputNotify(port, size, addrIP)
+ *
+ * @this {Bus}
+ * @param {number} port
+ * @param {number} size (1, 2 or 4)
+ * @param {number} [addrIP] is the IP value at the time of the input
+ * @return {number} simulated port data
+ *
+ * NOTE: It seems that parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS)
+ * assume that ports for non-existent hardware return 0xff rather than 0x00, hence my new default (0xff) below.
+ */
+Bus.prototype.checkPortInputNotify = function(port, size, addrIP)
+{
+ var data = 0, shift = 0;
+
+ while (size > 0) {
+
+ var aNotify = this.aPortInputNotify[port];
+ var sizePort = this.aPortInputWidth[port] || 1;
+ var maskPort = (sizePort == 1? 0xff : (sizePort == 2? 0xffff : -1));
+ var dataPort = maskPort;
+
+ /*
+ * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O
+ * to a 32-bit port). We probably should pass the size through to the aNotify[0] handler,
+ * and let it decide what to do, but I don't feel like changing all the I/O handlers right now.
+ * The good news, at least, is that the 8-bit handlers would not have to do anything special.
+ * This assert will warn us if this is a pressing need.
+ */
+ this.assert(size >= sizePort);
+
+ if (aNotify !== undefined) {
+ if (aNotify[0]) {
+ dataPort = aNotify[0](port, addrIP);
+ if (dataPort === undefined) {
+ dataPort = maskPort;
+ } else {
+ dataPort &= maskPort;
+ }
+ }
+ if (DEBUGGER && this.dbg && this.fPortInputBreakAll != aNotify[1]) {
+ this.dbg.checkPortInput(port, size, dataPort);
+ }
+ }
+ else {
+ if (DEBUGGER && this.dbg) {
+ this.dbg.messageIO(this, port, null, addrIP);
+ if (this.fPortInputBreakAll) this.dbg.checkPortInput(port, size, dataPort);
+ }
+ }
+
+ data |= dataPort << shift;
+ shift += (sizePort << 3);
+ port += sizePort;
+ size -= sizePort;
+ }
+
+ this.assert(!size);
+ return data;
+};
+
+/**
+ * removePortInputNotify(start, end)
+ *
+ * Remove port input-notification handler(s) (to be ENABLED later if needed)
+ *
+ * @this {Bus}
+ * @param {number} start address
+ * @param {number} end address
+ *
+Bus.prototype.removePortInputNotify = function(start, end)
+ {
+ for (var port = start; port < end; port++) {
+ if (this.aPortInputNotify[port]) {
+ delete this.aPortInputNotify[port];
+ }
+ }
+};
+ */
+
+/**
+ * addPortOutputBreak(port)
+ *
+ * @this {Bus}
+ * @param {number} [port]
+ * @return {boolean} true if break on port output enabled, false if disabled
+ */
+Bus.prototype.addPortOutputBreak = function(port)
+{
+ if (port === undefined) {
+ this.fPortOutputBreakAll = !this.fPortOutputBreakAll;
+ return this.fPortOutputBreakAll;
+ }
+ if (this.aPortOutputNotify[port] === undefined) {
+ this.aPortOutputNotify[port] = [null, false];
+ }
+ this.aPortOutputNotify[port][1] = !this.aPortOutputNotify[port][1];
+ return this.aPortOutputNotify[port][1];
+};
+
+/**
+ * addPortOutputNotify(start, end, fn)
+ *
+ * Add a port output-notification handler to the list of such handlers.
+ *
+ * @this {Bus}
+ * @param {number} start port address
+ * @param {number} end port address
+ * @param {function(number,number)} fn is called with the port and IP values at the time of the output
+ */
+Bus.prototype.addPortOutputNotify = function(start, end, fn)
+{
+ if (fn !== undefined) {
+ for (var port = start; port <= end; port++) {
+ if (this.aPortOutputNotify[port] !== undefined) {
+ Component.warning("Output port " + str.toHexWord(port) + " already registered");
+ continue;
+ }
+ this.aPortOutputNotify[port] = [fn, false];
+ if (MAXDEBUG) this.log("addPortOutputNotify(" + str.toHexWord(port) + ")");
+ }
+ }
+};
+
+/**
+ * addPortOutputTable(component, table, offset)
+ *
+ * Add port output-notification handlers from the specified table (a batch version of addPortOutputNotify)
+ *
+ * @this {Bus}
+ * @param {Component} component
+ * @param {Object} table
+ * @param {number} [offset] is an optional port offset
+ */
+Bus.prototype.addPortOutputTable = function(component, table, offset)
+{
+ if (offset === undefined) offset = 0;
+ for (var port in table) {
+ this.addPortOutputNotify(+port + offset, +port + offset, table[port].bind(component));
+ }
+};
+
+/**
+ * addPortOutputWidth(port, size)
+ *
+ * By default, all output ports are 1 byte wide; ports that are wider must call this function.
+ *
+ * @this {Bus}
+ * @param {number} port
+ * @param {number} size (1, 2 or 4)
+ */
+Bus.prototype.addPortOutputWidth = function(port, size)
+{
+ this.aPortOutputWidth[port] = size;
+};
+
+/**
+ * checkPortOutputNotify(port, size, data, addrIP)
+ *
+ * @this {Bus}
+ * @param {number} port
+ * @param {number} size
+ * @param {number} data
+ * @param {number} [addrIP] is the IP value at the time of the output
+ */
+Bus.prototype.checkPortOutputNotify = function(port, size, data, addrIP)
+{
+ var shift = 0;
+
+ while (size > 0) {
+
+ var aNotify = this.aPortOutputNotify[port];
+ var sizePort = this.aPortOutputWidth[port] || 1;
+ var maskPort = (sizePort == 1? 0xff : (sizePort == 2? 0xffff : -1));
+ var dataPort = (data >>>= shift) & maskPort;
+
+ /*
+ * TODO: We need to decide what to do about 8-bit I/O to a 16-bit port (ditto for 16-bit I/O
+ * to a 32-bit port). We probably should pass the size through to the aNotify[0] handler,
+ * and let it decide what to do, but I don't feel like changing all the I/O handlers right now.
+ * The good news, at least, is that the 8-bit handlers would not have to do anything special.
+ * This assert will warn us if this is a pressing need.
+ */
+ this.assert(size >= sizePort);
+
+ if (aNotify !== undefined) {
+ if (aNotify[0]) {
+ aNotify[0](port, dataPort, addrIP);
+ }
+ if (DEBUGGER && this.dbg && this.fPortOutputBreakAll != aNotify[1]) {
+ this.dbg.checkPortOutput(port, size, dataPort);
+ }
+ }
+ else {
+ if (DEBUGGER && this.dbg) {
+ this.dbg.messageIO(this, port, dataPort, addrIP);
+ if (this.fPortOutputBreakAll) this.dbg.checkPortOutput(port, size, dataPort);
+ }
+ }
+
+ shift += (sizePort << 3);
+ port += sizePort;
+ size -= sizePort;
+ }
+ this.assert(!size);
+};
+
+/**
+ * removePortOutputNotify(start, end)
+ *
+ * Remove port output-notification handler(s) (to be ENABLED later if needed)
+ *
+ * @this {Bus}
+ * @param {number} start address
+ * @param {number} end address
+ *
+Bus.prototype.removePortOutputNotify = function(start, end)
+ {
+ for (var port = start; port < end; port++) {
+ if (this.aPortOutputNotify[port]) {
+ delete this.aPortOutputNotify[port];
+ }
+ }
+};
+ */
+
+/**
+ * reportError(op, addr, size, fQuiet)
+ *
+ * @this {Bus}
+ * @param {number} op
+ * @param {number} addr
+ * @param {number} size
+ * @param {boolean} [fQuiet] (true if any error should be quietly logged)
+ * @return {boolean} false
+ */
+Bus.prototype.reportError = function(op, addr, size, fQuiet)
+{
+ var sError = "Memory block error (" + op + ": " + str.toHex(addr) + "," + str.toHex(size) + ")";
+ if (fQuiet) {
+ if (this.dbg) {
+ this.dbg.message(sError);
+ } else {
+ this.log(sError);
+ }
+ } else {
+ Component.error(sError);
+ }
+ return false;
+};
+
+if (NODE) module.exports = Bus;
diff --git a/modules/pc6502/lib/computer.js b/modules/pc6502/lib/computer.js
new file mode 100644
index 000000000..2b1bb7094
--- /dev/null
+++ b/modules/pc6502/lib/computer.js
@@ -0,0 +1,1638 @@
+/**
+ * @fileoverview Implements the PC6502 Computer component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var usr = require("../../shared/lib/usrlib");
+ var web = require("../../shared/lib/weblib");
+ var UserAPI = require("../../shared/lib/userapi");
+ var ReportAPI = require("../../shared/lib/reportapi");
+ var Component = require("../../shared/lib/component");
+ var Messages = require("./messages");
+ var Bus = require("./bus");
+ var State = require("./state");
+}
+
+/**
+ * Computer(parmsComputer, parmsMachine, fSuspended)
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsComputer
+ * @param {Object} [parmsMachine]
+ * @param {boolean} [fSuspended]
+ *
+ * The Computer component has no required (parmsComputer) properties, but it does
+ * support the following:
+ *
+ * autoPower: true to automatically power the computer (default), false to wait;
+ * false is honored only if a "power" button binding exists.
+ *
+ * busWidth: number of memory address lines (address bits) on the computer's "bus";
+ * 20 is the minimum (and the default), which implies 8086/8088 real-mode addressing,
+ * while 24 is required for 80286 protected-mode addressing. This value is passed
+ * directly through to the Bus component; see that component for more details.
+ *
+ * resume: one of the Computer.RESUME constants, which are as follows:
+ * '0' if resume disabled (default)
+ * '1' if enabled without prompting
+ * '2' if enabled with prompting
+ * '3' if enabled with prompting and auto-delete
+ * or a string containing the path of a predefined JSON-encoded state
+ *
+ * state: the path to JSON-encoded state file (see details regarding 'state' below)
+ *
+ * The parmsMachine object, if provided, may contain any of:
+ *
+ * autoMount: if set, this should override any 'autoMount' property in the FDC's
+ * parmsFDC object.
+ *
+ * autoPower: if set, this should override any 'autoPower' property in the Computer's
+ * parmsComputer object.
+ *
+ * messages: if set, this should override any 'messages' property in the Debugger's
+ * parmsDbg object.
+ *
+ * state: if set, this should override any 'state' property in the Computer's
+ * parmsComputer object.
+ *
+ * url: the location of the machine XML file
+ *
+ * If a predefined state is supplied AND it's successfully loaded, then resume behavior
+ * defaults to '1' (ie, resume enabled without prompting).
+ *
+ * This component insures that all components are ready before "powering" them.
+ *
+ * Different components become ready at different times, and initialization order (ie,
+ * the order the scripts are combined on the page) only partially determines readiness.
+ * This is because components like ROM and Video must finish loading their resource files
+ * before they are ready. Other components become ready after we call their initBus()
+ * function, because they have a Bus or CPU dependency, such as access to memory management
+ * functions. And other components, like CPU and Panel, are ready as soon as their
+ * constructor finishes.
+ *
+ * Once a component has indicated it's ready, we call its powerUp() notification
+ * function (if it has one--it's optional). We call the CPU's powerUp() function last,
+ * so that the CPU is assured that all other components are ready and "powered".
+ */
+function Computer(parmsComputer, parmsMachine, fSuspended) {
+
+ Component.call(this, "Computer", parmsComputer, Computer, Messages.COMPUTER);
+
+ this.flags.fPowered = false;
+
+ this.setMachineParms(parmsMachine);
+
+ this.fAutoPower = this.getMachineParm('autoPower', parmsComputer);
+
+ /*
+ * nPowerChange is 0 while the power state is stable, 1 while power is transitioning
+ * to "on", and -1 while power is transitioning to "off".
+ */
+ this.nPowerChange = 0;
+
+ /*
+ * TODO: Deprecate 'buswidth' (it should have always used camelCase)
+ */
+ this.nBusWidth = parmsComputer['busWidth'] || parmsComputer['buswidth'];
+
+ this.resume = Computer.RESUME_NONE;
+ this.sStateData = null;
+ this.fStateData = false; // remembers if sStateData was loaded
+ this.fServerState = false;
+
+ this.url = this.getMachineParm('url') || "";
+
+ /*
+ * Generate a random number x (where 0 <= x < 1), add 0.1 so that it's guaranteed to be
+ * non-zero, convert to base 36, and chop off the leading digit and "decimal" point.
+ */
+ this.sMachineID = (Math.random() + 0.1).toString(36).substr(2,12);
+ this.sUserID = this.queryUserID();
+
+ /*
+ * Find the appropriate CPU (and Debugger and Control Panel, if any)
+ *
+ * CLOSURE COMPILER TIP: To override the type of a right-hand expression (as we need to do here,
+ * where we know getComponentByType() will only return an CPUState object or null), wrap the expression
+ * in parentheses. I never knew this until I stumbled across it in "Closure: The Definitive Guide".
+ */
+ this.cpu = /** @type {CPUState} */ (Component.getComponentByType("CPU", this.id));
+ if (!this.cpu) {
+ Component.error("Unable to find CPU component");
+ return;
+ }
+ this.dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", this.id));
+
+ /*
+ * Enumerate all Video components for future updateVideo() calls.
+ */
+ this.aVideo = [];
+ for (var video = null; (video = this.getMachineComponent("Video", video));) {
+ this.aVideo.push(video);
+ }
+
+ /*
+ * Initialize the Bus component
+ */
+ this.bus = new Bus({'id': this.idMachine + '.bus', 'busWidth': this.nBusWidth}, this.cpu, this.dbg);
+
+ /*
+ * Iterate through all the components and connect them to the Control Panel, if any
+ */
+ var iComponent, component;
+ var aComponents = Component.getComponents(this.id);
+ this.panel = /** @type {Panel} */ (Component.getComponentByType("Panel", this.id));
+
+ if (this.panel && this.panel.controlPrint) {
+ for (iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ component = aComponents[iComponent];
+ /*
+ * I can think of many "cleaner" ways for the Control Panel component to pass its
+ * notice(), println(), etc, overrides on to all the other components, but it's just
+ * too darn convenient to slam those overrides into the components directly.
+ */
+ component.notice = this.panel.notice;
+ component.println = this.panel.println;
+ component.controlPrint = this.panel.controlPrint;
+ }
+ }
+
+ this.println(Computer.APPNAME + " v" + Computer.APPVERSION + "\n" + Computer.COPYRIGHT + "\n" + Computer.LICENSE);
+
+ if (DEBUG && this.messageEnabled()) this.printMessage("TYPEDARRAYS: " + TYPEDARRAYS);
+
+ /*
+ * Iterate through all the components again and call their initBus() handler, if any
+ */
+ for (iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ component = aComponents[iComponent];
+ if (component.initBus) component.initBus(this, this.bus, this.cpu, this.dbg);
+ }
+
+ var sStatePath = null;
+ var sResume = parmsComputer['resume'];
+ if (sResume !== undefined) {
+ /*
+ * DEPRECATE: This goofiness is a holdover from when the 'resume' property was a string (either a
+ * single-digit string or a path); now it's always a number, so it never has a 'length' property and
+ * the call to parseInt() is unnecessary.
+ */
+ if (sResume.length > 1) {
+ sStatePath = this.sResumePath = sResume;
+ } else {
+ this.resume = parseInt(sResume, 10);
+ }
+ }
+
+ /*
+ * The Computer 'state' property allows a state file to be specified independent of the 'resume' feature;
+ * previously, you could only use 'resume' to load a state file -- which we still support, but loading a state
+ * file that way prevents the machine's state from being saved, since we always resume from the 'resume' file.
+ *
+ * The other wrinkle is on the restore side: we need to IGNORE the 'state' property if a saved state now exists.
+ * So we have to peek at localStorage, and unfortunately, the only way to "peek" is to actually load the data,
+ * but we're not ready to use it yet, so powerUp() has been changed to use any existing stateComputer that we've
+ * already loaded.
+ *
+ * However, there's now a wrinkle to the wrinkle: if a 'state' parameter has been passed via the URL, then that
+ * OVERRIDES everything; it overrides any 'state' Computer parameter AND it disables resume of any saved state in
+ * localStorage (in other words, it prevents fAllowResume from being true, and forcing resume off).
+ */
+ var fAllowResume;
+ var sState = this.getMachineParm('state') || (fAllowResume = true) && parmsComputer['state'];
+
+ if (sState) {
+ sStatePath = this.sStatePath = sState;
+ if (!fAllowResume) {
+ this.fServerState = true;
+ this.resume = Computer.RESUME_NONE;
+ }
+ if (this.resume) {
+ this.stateComputer = new State(this, Computer.APPVERSION);
+ if (this.stateComputer.load()) {
+ sStatePath = null;
+ } else {
+ delete this.stateComputer;
+ }
+ }
+ }
+
+ /*
+ * If sStatePath is set, we must use it. But if there's no sStatePath AND resume is set,
+ * then we have the option of resuming from a server-side state, assuming a valid USERID.
+ */
+ if (!sStatePath && this.resume) {
+ sStatePath = this.getServerStatePath();
+ if (sStatePath) this.fServerState = true;
+ }
+
+ if (!sStatePath) {
+ this.setReady();
+ } else {
+ var cmp = this;
+ web.getResource(sStatePath, null, true, function(sURL, sResource, nErrorCode) {
+ cmp.doneLoad(sURL, sResource, nErrorCode);
+ });
+ }
+
+ if (!this.bindings["power"]) this.fAutoPower = true;
+
+ /*
+ * Power on the computer, giving every component the opportunity to reset or restore itself.
+ */
+ if (!fSuspended && this.fAutoPower) this.wait(this.powerOn);
+}
+
+Component.subclass(Computer);
+
+Computer.APPNAME = APPNAME || "PC6502";
+Computer.APPVERSION = APPVERSION;
+Computer.COPYRIGHT = "Copyright © 2012-2016 Jeff Parsons ";
+
+/*
+ * I think it's a good idea to also display a GPL notice, putting people on notice that even
+ * the "compiled" source code has all the same GPL requirements as the uncompiled source code.
+ */
+Computer.LICENSE = "License: GPL version 3 or later ";
+
+Computer.STATE_FAILSAFE = "failsafe";
+Computer.STATE_VALIDATE = "validate";
+Computer.STATE_TIMESTAMP = "timestamp";
+Computer.STATE_VERSION = "version";
+Computer.STATE_HOSTURL = "url";
+Computer.STATE_BROWSER = "browser";
+Computer.STATE_USERID = "user";
+
+/*
+ * The following constants define all the resume options. Negative values (eg, RESUME_REPOWER) are for
+ * internal use only, and RESUME_DELETE is not documented (it provides a way of deleting ALL saved states
+ * whenever a resume is declined). As a result, the only "end-user" values are 0, 1 and 2.
+ */
+Computer.RESUME_REPOWER = -1; // resume without changing any state (for internal use only)
+Computer.RESUME_NONE = 0; // default (no resume)
+Computer.RESUME_AUTO = 1; // automatically save/restore state
+Computer.RESUME_PROMPT = 2; // automatically save but conditionally restore (WARNING: if restore is declined, any state is discarded)
+Computer.RESUME_DELETE = 3; // same as RESUME_PROMPT but discards ALL machines states whenever ANY machine restore is declined (undocumented)
+
+/**
+ * getMachineID()
+ *
+ * @return {string}
+ */
+Computer.prototype.getMachineID = function()
+{
+ return this.sMachineID;
+};
+
+/**
+ * setMachineParms(parmsMachine)
+ *
+ * If no explicit machine parms were provided, then we check for 'parms' in the bundled resources (if any).
+ *
+ * @param {Object} [parmsMachine]
+ */
+Computer.prototype.setMachineParms = function(parmsMachine)
+{
+ if (!parmsMachine) {
+ var sParms;
+ if (typeof resources == 'object' && (sParms = resources['parms'])) {
+ try {
+ parmsMachine = /** @type {Object} */ (eval("(" + sParms + ")"));
+ } catch(e) {
+ Component.error(e.message + " (" + sParms + ")");
+ }
+ }
+ }
+ this.parmsMachine = parmsMachine;
+};
+
+/**
+ * getMachineParm(sParm, parmsComponent)
+ *
+ * If the machine parameter doesn't exist, we check for a matching component parameter (if parmsComponent is provided),
+ * and failing that, we check the bundled resources (if any).
+ *
+ * At the moment, the only bundled resource request we expect to encounter is 'state'; if it exists, then we return
+ * 'state' back to the caller (ie, the name of the resource), so that the caller will then attempt to load the 'state'
+ * resource to obtain the actual state.
+ *
+ * @param {string} sParm
+ * @param {Object} [parmsComponent]
+ * @return {string|undefined}
+ */
+Computer.prototype.getMachineParm = function(sParm, parmsComponent)
+{
+ /*
+ * When checking parmsURL, the check is allowed be a bit looser, because URL parameters are
+ * user-supplied, whereas most other parameters are developer-supplied. Granted, a developer
+ * may also be sloppy and neglect to use correct case (eg, 'automount' instead of 'autoMount'),
+ * but there are limits to my paranoia.
+ */
+ var sParmLC = sParm.toLowerCase();
+ var value = Component.parmsURL[sParm] || Component.parmsURL[sParmLC];
+
+ if (value === undefined && this.parmsMachine) {
+ value = this.parmsMachine[sParm];
+ }
+ if (value === undefined && parmsComponent) {
+ value = parmsComponent[sParm];
+ }
+ if (value === undefined && typeof resources == 'object' && resources[sParm]) {
+ value = sParm;
+ }
+ return value;
+};
+
+/**
+ * saveMachineParms()
+ *
+ * @return {string|null}
+ */
+Computer.prototype.saveMachineParms = function()
+{
+ return this.parmsMachine? JSON.stringify(this.parmsMachine) : null;
+};
+
+/**
+ * getUserID()
+ *
+ * @return {string}
+ */
+Computer.prototype.getUserID = function()
+{
+ return this.sUserID || "";
+};
+
+/**
+ * doneLoad(sURL, sStateData, nErrorCode)
+ *
+ * @this {Computer}
+ * @param {string} sURL
+ * @param {string} sStateData
+ * @param {number} nErrorCode
+ */
+Computer.prototype.doneLoad = function(sURL, sStateData, nErrorCode)
+{
+ if (!nErrorCode) {
+ this.sStateData = sStateData;
+ this.fStateData = true;
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage("loaded state file " + sURL.replace(this.sUserID || "xxx", "xxx"));
+ }
+ } else {
+ this.sResumePath = null;
+ this.fServerState = false;
+ this.notice('Unable to load machine state from server (error ' + nErrorCode + (sStateData? ': ' + str.trim(sStateData) : '') + ')');
+ }
+ this.setReady();
+};
+
+/**
+ * wait(fn, parms)
+ *
+ * wait() waits until every component is ready (including ourselves, the last component we check), then calls the
+ * specified Computer method.
+ *
+ * TODO: The Closure Compiler makes it difficult for us to define a function type for "fn" that works in all cases;
+ * sometimes we want to pass a function that takes only a "number", and other times we want to pass a function that
+ * takes only an "Array" (the type will mirror that of the "parms" parameter). However, the Closure Compiler insists
+ * that both functions must be declared as accepting both types of parameters. So once again, we must use an untyped
+ * function declaration, instead of something stricter like:
+ *
+ * param {function(this:Computer, (number|Array|undefined)): undefined} fn
+ *
+ * @this {Computer}
+ * @param {function(...)} fn
+ * @param {number|Array} [parms] optional parameters
+ */
+Computer.prototype.wait = function(fn, parms)
+{
+ var computer = this;
+ var aComponents = Component.getComponents(this.id);
+ for (var iComponent = 0; iComponent <= aComponents.length; iComponent++) {
+ var component = (iComponent < aComponents.length ? aComponents[iComponent] : this);
+ if (!component.isReady()) {
+ component.isReady(function onComponentReady() {
+ computer.wait(fn, parms);
+ });
+ return;
+ }
+ }
+ if (DEBUG && this.messageEnabled()) this.printMessage("Computer.wait(ready)");
+ fn.call(this, parms);
+};
+
+/**
+ * validateState(stateComputer)
+ *
+ * NOTE: We clear() stateValidate only when there's no stateComputer.
+ *
+ * @this {Computer}
+ * @param {State|null} [stateComputer]
+ * @return {boolean} true if state passes validation, false if not
+ */
+Computer.prototype.validateState = function(stateComputer)
+{
+ var fValid = true;
+ var stateValidate = new State(this, Computer.APPVERSION, Computer.STATE_VALIDATE);
+ if (stateValidate.load() && stateValidate.parse()) {
+ var sTimestampValidate = stateValidate.get(Computer.STATE_TIMESTAMP);
+ var sTimestampComputer = stateComputer ? stateComputer.get(Computer.STATE_TIMESTAMP) : "unknown";
+ if (sTimestampValidate != sTimestampComputer) {
+ this.notice("Machine state may be out-of-date\n(" + sTimestampValidate + " vs. " + sTimestampComputer + ")\nCheck your browser's local storage limits");
+ fValid = false;
+ if (!stateComputer) stateValidate.clear();
+ } else {
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage("Last state: " + sTimestampComputer + " (validate: " + sTimestampValidate + ")");
+ }
+ }
+ }
+ return fValid;
+};
+
+/**
+ * powerOn(resume)
+ *
+ * Power every component "up", applying any previously available state information.
+ *
+ * @this {Computer}
+ * @param {number} [resume] is a valid RESUME value; default is this.resume
+ */
+Computer.prototype.powerOn = function(resume)
+{
+ if (resume === undefined) {
+ resume = this.resume || (this.sStateData? Computer.RESUME_AUTO : Computer.RESUME_NONE);
+ }
+
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage("Computer.powerOn(" + (resume == Computer.RESUME_REPOWER ? "repower" : (resume ? "resume" : "")) + ")");
+ }
+
+ if (this.nPowerChange) {
+ return;
+ }
+ this.nPowerChange++;
+
+ var fRepower = false;
+ var fRestore = false;
+ this.fRestoreError = false;
+ var stateComputer = this.stateComputer || new State(this, Computer.APPVERSION);
+
+ if (resume == Computer.RESUME_REPOWER) {
+ fRepower = true;
+ }
+ else if (resume > Computer.RESUME_NONE) {
+ if (stateComputer.load(this.sStateData)) {
+ /*
+ * Since we're resuming something (either a predefined state or a state from localStorage), let's
+ * create a "failsafe" checkpoint in localStorage, and destroy it at the end of a successful powerOn().
+ * Which means, of course, that if a previous "failsafe" checkpoint already exists, something bad
+ * may have happened the last time around.
+ */
+ this.stateFailSafe = new State(this, Computer.APPVERSION, Computer.STATE_FAILSAFE);
+ if (this.stateFailSafe.load()) {
+ this.powerReport(stateComputer);
+ /*
+ * We already know resume is something other than RESUME_NONE, so we'll go ahead and bump it
+ * all the way to RESUME_PROMPT, so that the user will be prompted, and if the user declines to
+ * restore, the state will be removed.
+ */
+ resume = Computer.RESUME_PROMPT;
+ /*
+ * To ensure that the set() below succeeds, we need to call unload(), otherwise it may fail
+ * with a "read only" error (eg, "TypeError: Cannot assign to read only property 'timestamp'").
+ */
+ this.stateFailSafe.unload();
+ }
+
+ this.stateFailSafe.set(Computer.STATE_TIMESTAMP, usr.getTimestamp());
+ this.stateFailSafe.store();
+
+ var fValidate = this.resume && !this.fServerState;
+ if (resume == Computer.RESUME_AUTO || web.confirmUser("Click OK to restore the previous " + Computer.APPNAME + " machine state, or CANCEL to reset the machine.")) {
+ fRestore = stateComputer.parse();
+ if (fRestore) {
+ var sCode = stateComputer.get(UserAPI.RES.CODE);
+ var sData = stateComputer.get(UserAPI.RES.DATA);
+ if (sCode) {
+ if (sCode == UserAPI.CODE.OK) {
+ stateComputer.load(sData);
+ } else {
+ /*
+ * A missing (or not yet created) state file is no cause for alarm, but other errors might be
+ */
+ if (sCode == UserAPI.CODE.FAIL && sData != UserAPI.FAIL.NOSTATE) {
+ this.notice("Error: " + sData);
+ if (sData == UserAPI.FAIL.VERIFY) this.resetUserID();
+ } else {
+ this.println(sCode + ": " + sData);
+ }
+ /*
+ * Try falling back to the state that we should have saved in localStorage, as a backup to the
+ * server-side state.
+ */
+ stateComputer.unload(); // discard the invalid server-side state first
+ if (stateComputer.load()) {
+ fRestore = stateComputer.parse();
+ fValidate = true;
+ } else {
+ fRestore = false; // hmmm, there was nothing in localStorage either
+ }
+ }
+ }
+ }
+ /*
+ * If the load/parse was successful, and it was from localStorage (not sStateData),
+ * then we should to try verify that localStorage snapshot is current. One reason it may
+ * NOT be current is if localStorage was full and we got a quota error during the last
+ * powerOff().
+ */
+ if (fValidate) this.validateState(fRestore? stateComputer : null);
+ } else {
+ /*
+ * RESUME_PROMPT indicates we should delete the state if they clicked Cancel to confirm() above.
+ */
+ if (resume == Computer.RESUME_PROMPT) stateComputer.clear();
+ }
+ } else {
+ /*
+ * If there's no state, then there should also be no validation timestamp; if there is, then once again,
+ * we're probably dealing with a quota error.
+ */
+ this.validateState();
+ }
+ delete this.sStateData;
+ delete this.stateComputer;
+ }
+
+ /*
+ * Start powering all components, including any data they may need to restore their state;
+ * we restore power to the CPU last.
+ */
+ var aComponents = Component.getComponents(this.id);
+ for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ var component = aComponents[iComponent];
+ if (component !== this && component != this.cpu) {
+ fRestore = this.powerRestore(component, stateComputer, fRepower, fRestore);
+ }
+ }
+
+ /*
+ * Assuming this is not a repower, we must perform another wait, because some components may
+ * have marked themselves as "not ready" again (eg, the FDC component, if the restore forced it
+ * to mount one or more additional disk images).
+ */
+ var aParms = [stateComputer, resume, fRestore];
+
+ if (resume != Computer.RESUME_REPOWER) {
+ this.wait(this.donePowerOn, aParms);
+ return;
+ }
+ this.donePowerOn(aParms);
+};
+
+/**
+ * powerRestore(component, stateComputer, fRepower, fRestore)
+ *
+ * @this {Computer}
+ * @param {Component} component
+ * @param {State} stateComputer
+ * @param {boolean} fRepower
+ * @param {boolean} fRestore
+ * @return {boolean} true if restore should continue, false if not
+ */
+Computer.prototype.powerRestore = function(component, stateComputer, fRepower, fRestore)
+{
+ if (!component.flags.fPowered) {
+
+ component.flags.fPowered = true;
+
+ if (component.powerUp) {
+
+ var data = null;
+ if (fRestore) {
+ data = stateComputer.get(component.id);
+ if (!data) {
+ /*
+ * This is a hack that makes it possible for a machine whose ID has been
+ * supplemented with a suffix (a single letter or digit) to find object IDs
+ * in states created from a machine without the suffix.
+ *
+ * For example, if a state file was created from a machine with ID "ibm5160"
+ * but the current machine is "ibm5160a", this attempts a second lookup with
+ * "ibm5160", enabling us to find objects that match the original machine ID
+ * (eg, "ibm5160.romEGA").
+ */
+ data = stateComputer.get(component.id.replace(/[a-z0-9]\./i, '.'));
+ }
+ }
+
+ /*
+ * State.get() will return whatever was originally passed to State.set() (eg, an
+ * Object or a string), but components are supposed to store only Objects, so if a
+ * string comes back, something went wrong. By explicitly eliminating "string" data,
+ * the Closure Compiler stops complaining that we might be passing strings to our
+ * powerUp() functions (even though we know we're not).
+ *
+ * TODO: Determine if there's some way to coerce the Closure Compiler into treating
+ * data as Object or null, without having to include this runtime check. An assert
+ * would be a good idea, but this is overkill.
+ */
+ if (typeof data === "string") data = null;
+
+ /*
+ * If computer is null, this is simply a repower notification, which most components
+ * don't do anything with. Exceptions include: CPU (since it may be halted) and Video
+ * (since its screen may be "turned off").
+ */
+ if (!component.powerUp(data, fRepower) && data) {
+
+ Component.error("Unable to restore state for " + component.type);
+ /*
+ * If this is a resume error for a machine that also has a predefined state
+ * AND we're not restoring from that state, then throw away the current state,
+ * prevent any new state from being created, and then force a reload, which will
+ * hopefully restore us to the functioning predefined state.
+ *
+ * TODO: Considering doing this in ALL cases, not just in situations where a
+ * 'state' exists but we're not actually resuming from it.
+ */
+ if (this.sStatePath && !this.fStateData) {
+ stateComputer.clear();
+ this.resume = Computer.RESUME_NONE;
+ web.reloadPage();
+ } else {
+ /*
+ * In all other cases, we set fRestoreError, which should trigger a call to
+ * powerReport() and then delete the offending state.
+ */
+ this.fRestoreError = true;
+ }
+ /*
+ * Any failure triggers an automatic to call powerUp() again, without any state,
+ * in the hopes that the component can recover by performing a reset.
+ */
+ component.powerUp(null);
+ /*
+ * We also disable the rest of the restore operation, because it's not clear
+ * the remaining state information can be trusted; the machine is already in an
+ * inconsistent state, so we're not likely to make things worse, and the only
+ * alternative (starting over and performing a state-less reset) isn't likely to make
+ * the user any happier. But, we'll see... we need some experience with the code.
+ */
+ fRestore = false;
+ }
+ }
+
+ if (!fRepower && component.comment) {
+ var asComments = component.comment.split("|");
+ for (var i = 0; i < asComments.length; i++) {
+ component.status(asComments[i]);
+ }
+ }
+ }
+ return fRestore;
+};
+
+/**
+ * donePowerOn(aParms)
+ *
+ * This is nothing more than a continuation of powerOn(), giving us the option of calling wait() one more time.
+ *
+ * @this {Computer}
+ * @param {Array} aParms containing [stateComputer, resume, fRestore]
+ */
+Computer.prototype.donePowerOn = function(aParms)
+{
+ var stateComputer = aParms[0];
+ var fRepower = (aParms[1] < 0);
+ var fRestore = aParms[2];
+
+ if (DEBUG && this.flags.fPowered && this.messageEnabled()) {
+ this.printMessage("Computer.donePowerOn(): redundant");
+ }
+
+ this.fInitialized = true;
+ this.flags.fPowered = true;
+ var controlPower = this.bindings["power"];
+ if (controlPower) controlPower.textContent = "Shutdown";
+
+ /*
+ * Once we get to this point, we're guaranteed that all components are ready, so it's safe to power the CPU;
+ * the CPU should begin executing immediately, unless a debugger is attached.
+ */
+ if (this.cpu) {
+ /*
+ * TODO: Do we not care about the return value here? (ie, is checking fRestoreError sufficient)?
+ */
+ this.powerRestore(this.cpu, stateComputer, fRepower, fRestore);
+ this.cpu.autoStart();
+ }
+
+ /*
+ * If the state was bad, offer to report it and then delete it. Deleting may be moot, since invariably a new
+ * state will be created on powerOff() before the next powerOn(), but it seems like good paranoia all the same.
+ */
+ if (this.fRestoreError) {
+ this.powerReport(stateComputer);
+ stateComputer.clear();
+ }
+
+ if (!fRepower && this.stateFailSafe) {
+ this.stateFailSafe.clear();
+ delete this.stateFailSafe;
+ }
+
+ this.nPowerChange = 0;
+};
+
+/**
+ * checkPower()
+ *
+ * @this {Computer}
+ * @return {boolean} true if the computer is fully powered, false otherwise
+ */
+Computer.prototype.checkPower = function()
+{
+ if (this.flags.fPowered) return true;
+
+ var component = null, iComponent;
+ var aComponents = Component.getComponents(this.id);
+ for (iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ component = aComponents[iComponent];
+ if (component !== this && !component.flags.fReady) break;
+ }
+ if (iComponent == aComponents.length) {
+ for (iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ component = aComponents[iComponent];
+ if (component !== this && !component.flags.fPowered) break;
+ }
+ }
+ if (iComponent == aComponents.length) component = this;
+ var s = "The " + component.type + " component (" + component.id + ") is not " + (!component.flags.fReady? "ready yet" + (component.fnReady? " (waiting for notification)" : "") : "powered yet") + ".";
+ web.alertUser(s);
+ return false;
+};
+
+/**
+ * powerReport(stateComputer)
+ *
+ * @this {Computer}
+ * @param {State} stateComputer
+ */
+Computer.prototype.powerReport = function(stateComputer)
+{
+ if (web.confirmUser("There may be a problem with your " + Computer.APPNAME + " machine.\n\nTo help us diagnose it, click OK to send this " + Computer.APPNAME + " machine state to http://" + SITEHOST + ".")) {
+ web.sendReport(Computer.APPNAME, Computer.APPVERSION, this.url, this.getUserID(), ReportAPI.TYPE.BUG, stateComputer.toString());
+ }
+};
+
+/**
+ * powerOff(fSave, fShutdown)
+ *
+ * Power every component "down" and optionally save the machine state.
+ *
+ * There's one scenario that powerOff() isn't currently able to deal with very effectively: what to do when
+ * the user switches away while it's still being restored, causing Disk getResource() calls to fail. The
+ * Disk component calls notify() when that happens -- see Disk.mount() -- but the FDC and HDC controllers don't
+ * notify *us* of those problems, so Computer assumes that the restore was completely successful, when in fact
+ * it was only partially successful.
+ *
+ * Then we immediately arrive here to perform a save, following that incomplete restore. It would be wrong to
+ * deal with that incomplete restore by setting fRestoreError, because we don't want to trigger a powerReport()
+ * and the deletion of the previous state, because the state itself was presumably OK. Unfortunately, the new
+ * state we now save will no longer include manually mounted disk images whose remounts were interrupted, so future
+ * restores won't remount them either.
+ *
+ * We could perhaps solve this by having the Disk component notify us in those situations, set a new flag
+ * (fRestoreIncomplete?), and set fSave to false if that's ever set. Be careful though: when fSave is false,
+ * that means MORE than not saving; it also means deleting any previous state, which is NOT what you'd want to
+ * do in a "fRestoreIncomplete" situation. Also, we have to worry about Disk operations that fail for other reasons,
+ * making sure those failures don't interfere with the save process in the same way.
+ *
+ * As it stands, the worst that happens is any manually mounted disk images might have to be manually remounted,
+ * which doesn't seem like a huge problem.
+ *
+ * @this {Computer}
+ * @param {boolean} [fSave] is true to request a saved state
+ * @param {boolean} [fShutdown] is true if the machine is being shut down
+ * @return {string|null} string representing the saved state (or null if error)
+ */
+Computer.prototype.powerOff = function(fSave, fShutdown)
+{
+ var data;
+ var sState = "none";
+
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage("Computer.powerOff(" + (fSave ? "save" : "nosave") + (fShutdown ? ",shutdown" : "") + ")");
+ }
+
+ if (this.nPowerChange) {
+ return null;
+ }
+ this.nPowerChange--;
+
+ var stateComputer = new State(this, Computer.APPVERSION);
+ var stateValidate = new State(this, Computer.APPVERSION, Computer.STATE_VALIDATE);
+
+ var sTimestamp = usr.getTimestamp();
+ stateValidate.set(Computer.STATE_TIMESTAMP, sTimestamp);
+ stateComputer.set(Computer.STATE_TIMESTAMP, sTimestamp);
+ stateComputer.set(Computer.STATE_VERSION, APPVERSION);
+ stateComputer.set(Computer.STATE_HOSTURL, web.getHostURL());
+ stateComputer.set(Computer.STATE_BROWSER, web.getUserAgent());
+
+ /*
+ * Always power the CPU "down" first, just to help insure it doesn't ask other components to do anything
+ * after they're no longer ready.
+ */
+ if (this.cpu && this.cpu.powerDown) {
+ if (fShutdown) this.cpu.stopCPU();
+ data = this.cpu.powerDown(fSave, fShutdown);
+ if (typeof data === "object") stateComputer.set(this.cpu.id, data);
+ if (fShutdown) {
+ this.cpu.flags.fPowered = false;
+ if (data === false) sState = null;
+ }
+ }
+
+ var aComponents = Component.getComponents(this.id);
+ for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ var component = aComponents[iComponent];
+ if (component.flags.fPowered) {
+ if (component.powerDown) {
+ data = component.powerDown(fSave, fShutdown);
+ if (typeof data === "object") stateComputer.set(component.id, data);
+ }
+ if (fShutdown) {
+ component.flags.fPowered = false;
+ if (data === false) sState = null;
+ }
+ }
+ }
+
+ if (sState) {
+ if (fShutdown) {
+ var fClear = false;
+ var fClearAll = false;
+ if (fSave) {
+ if (this.sUserID) {
+ this.saveServerState(this.sUserID, stateComputer.toString());
+ }
+ if (!stateValidate.store() || !stateComputer.store()) {
+ sState = null;
+ /*
+ * New behavior as of v1.13.2: if it appears that localStorage is full, we blow it ALL away.
+ * Dedicated server-side storage is the only way we'll ever be able to reliably preserve a
+ * particular machine's state. Historically, attempting to limp along with whatever localStorage
+ * is left just generates the same useless and annoying warnings over and over.
+ */
+ fClear = fClearAll = true;
+ }
+ }
+ else {
+ /*
+ * I used to ALWAYS clear (ie, delete) any associated computer state, but now I do this only if the
+ * current machine is "resumable", because there are situations where I have two configurations
+ * for the same machine -- one resumable and one not -- and I don't want the latter throwing away the
+ * state of the former.
+ *
+ * So this code is here now strictly for callers to delete the state of a "resumable" machine, not as
+ * some paranoid clean-up operation.
+ *
+ * An undocumented feature of this operation is that if your configuration uses the special 'resume="3"'
+ * value, and you click the "Reset" button, and then you click OK to reset the everything, this will
+ * actually reset EVERYTHING (ie, all localStorage for ALL configs will be reclaimed).
+ */
+ if (this.resume) {
+ fClear = true;
+ fClearAll = (this.resume == Computer.RESUME_DELETE);
+ }
+ }
+ if (fClear) {
+ stateComputer.clear(fClearAll);
+ }
+ } else {
+ sState = stateComputer.toString();
+ }
+ }
+
+ if (fShutdown) {
+ this.flags.fPowered = false;
+ var controlPower = this.bindings["power"];
+ if (controlPower) controlPower.textContent = "Power";
+ }
+
+ this.nPowerChange = 0;
+
+ return sState;
+};
+
+/**
+ * reset()
+ *
+ * Notify all (other) components with a reset() method that the Computer is being reset.
+ *
+ * NOTE: We'd like to reset the Bus first (due to the importance of the A20 line), but since we
+ * allocated the Bus object ourselves, after all the other components were allocated, it ends
+ * up near the end of Component's list of components. Hence the special case for this.bus below.
+ *
+ * @this {Computer}
+ */
+Computer.prototype.reset = function()
+{
+ if (this.bus && this.bus.reset) {
+ /*
+ * TODO: Why does WebStorm think that this.bus.type is undefined? The base class (Component)
+ * constructor defines it.
+ */
+ this.printMessage("Resetting " + this.bus.type);
+ this.bus.reset();
+ }
+ var aComponents = Component.getComponents(this.id);
+ for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ var component = aComponents[iComponent];
+ if (component !== this && component !== this.bus && component.reset) {
+ this.printMessage("Resetting " + component.type);
+ component.reset();
+ }
+ }
+};
+
+/**
+ * start(ms, nCycles)
+ *
+ * Notify all (other) components with a start() method that the CPU has started.
+ *
+ * Note that we're called by runCPU(), which is why we exclude the CPU component,
+ * as well as ourselves.
+ *
+ * @this {Computer}
+ * @param {number} ms
+ * @param {number} nCycles
+ */
+Computer.prototype.start = function(ms, nCycles)
+{
+ var aComponents = Component.getComponents(this.id);
+ for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ var component = aComponents[iComponent];
+ if (component.type == "CPU" || component === this) continue;
+ if (component.start) {
+ component.start(ms, nCycles);
+ }
+ }
+};
+
+/**
+ * stop(ms, nCycles)
+ *
+ * Notify all (other) components with a stop() method that the CPU has stopped.
+ *
+ * Note that we're called by runCPU(), which is why we exclude the CPU component,
+ * as well as ourselves.
+ *
+ * @this {Computer}
+ * @param {number} ms
+ * @param {number} nCycles
+ */
+Computer.prototype.stop = function(ms, nCycles)
+{
+ var aComponents = Component.getComponents(this.id);
+ for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ var component = aComponents[iComponent];
+ if (component.type == "CPU" || component === this) continue;
+ if (component.stop) {
+ component.stop(ms, nCycles);
+ }
+ }
+};
+
+/**
+ * setBinding(sHTMLType, sBinding, control, sValue)
+ *
+ * @this {Computer}
+ * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
+ * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
+ * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
+ * @param {string} [sValue] optional data value
+ * @return {boolean} true if binding was successful, false if unrecognized binding request
+ */
+Computer.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
+{
+ var computer = this;
+
+ switch (sBinding) {
+ case "power":
+ this.bindings[sBinding] = control;
+ control.onclick = function onClickPower() {
+ computer.onPower();
+ };
+ return true;
+
+ case "reset":
+ this.bindings[sBinding] = control;
+ control.onclick = function onClickReset() {
+ computer.onReset();
+ };
+ return true;
+
+ /*
+ * Technically, this binding should now be called "saveState", to clearly distinguish it from
+ * the "Save Machine" control that's normally bound to the savePC() function in save.js. Saving
+ * an entire machine includes everything needed to start/restore the machine; eg, the machine
+ * XML configuration file(s) *and* the JSON-encoded machine state.
+ */
+ case "save":
+ /*
+ * Since this feature depends on the server supporting the PCjs User API (see userapi.js),
+ * and since pcjs.org is no longer running a Node web server, we disable the feature for that
+ * particular host.
+ */
+ if (str.endsWith(web.getHost(), "pcjs.org")) {
+ if (DEBUG) this.log("Remote user API not available");
+ /*
+ * We could also simply hide the control; eg:
+ *
+ * control.style.display = "none";
+ *
+ * but removing the control altogether seems better.
+ */
+ control.parentNode.removeChild(/** @type {Node} */ (control));
+ return false;
+ }
+ this.bindings[sBinding] = control;
+ control.onclick = function onClickSave() {
+ var sUserID = computer.queryUserID(true);
+ if (sUserID) {
+ /*
+ * I modified the test to include a check for sStatePath so that I could save new states
+ * for machines with existing states; otherwise, I'd have no (easy) way of capturing and
+ * updating their state. Making the machine (even temporarily) resumable would have been
+ * one work-around, but it's not appropriate for some machines, as their state is simply
+ * too large (for localStorage anyway, which is the default storage solution).
+ */
+ var fSave = !!(computer.resume && !computer.sResumePath || computer.sStatePath);
+ var sState = computer.powerOff(fSave);
+ if (fSave) {
+ computer.saveServerState(sUserID, sState);
+ } else {
+ computer.notice("Resume disabled, machine state not saved");
+ }
+ }
+ /*
+ * This seemed like a handy alternative, but it turned out to be a no-go, at least for large states:
+ *
+ * var sState = computer.powerOff(true);
+ * if (sState) {
+ * sState = "data:text/json;charset=utf-8," + encodeURIComponent(sState);
+ * window.open(sState);
+ * }
+ *
+ * Perhaps if I embedded the data in a link on the current page instead; eg:
+ *
+ * $('Download').appendTo('#container');
+ */
+ };
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+};
+
+/**
+ * resetUserID()
+ */
+Computer.prototype.resetUserID = function()
+{
+ web.setLocalStorageItem(Computer.STATE_USERID, "");
+ this.sUserID = null;
+};
+
+/**
+ * queryUserID(fPrompt)
+ *
+ * @param {boolean} [fPrompt]
+ * @returns {string|null|undefined}
+ */
+Computer.prototype.queryUserID = function(fPrompt)
+{
+ var sUserID = this.sUserID;
+ if (!sUserID) {
+ sUserID = web.getLocalStorageItem(Computer.STATE_USERID);
+ if (sUserID !== undefined) {
+ if (!sUserID && fPrompt) {
+ /*
+ * NOTE: Warning the user here that "Save" operations are not currently supported by pcjs.org is
+ * merely a precaution, because ordinarily, setBinding() should have already determined if we are
+ * running from pcjs.org and disabled any "Save" button.
+ */
+ sUserID = web.promptUser("Saving machine states on the pcjs.org server is currently unsupported.\n\nIf you're running your own server, enter your user ID below.");
+ if (sUserID) {
+ sUserID = this.verifyUserID(sUserID);
+ if (!sUserID) this.notice("The user ID is invalid.");
+ }
+ }
+ } else if (fPrompt) {
+ this.notice("Browser local storage is not available");
+ }
+ }
+ return sUserID;
+};
+
+/**
+ * verifyUserID(sUserID)
+ *
+ * @this {Computer}
+ * @param {string} sUserID
+ * @return {string} validated user ID, or null if error
+ */
+Computer.prototype.verifyUserID = function(sUserID)
+{
+ this.sUserID = null;
+ var fMessages = DEBUG && this.messageEnabled();
+ if (fMessages) this.printMessage("verifyUserID(" + sUserID + ")");
+ var sRequest = web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUserID;
+ var response = web.getResource(sRequest);
+ var nErrorCode = response[0];
+ var sResponse = response[1];
+ if (!nErrorCode && sResponse) {
+ try {
+ response = eval("(" + sResponse + ")");
+ if (response.code && response.code == UserAPI.CODE.OK) {
+ web.setLocalStorageItem(Computer.STATE_USERID, response.data);
+ if (fMessages) this.printMessage(Computer.STATE_USERID + " updated: " + response.data);
+ this.sUserID = response.data;
+ } else {
+ if (fMessages) this.printMessage(response.code + ": " + response.data);
+ }
+ } catch (e) {
+ Component.error(e.message + " (" + sResponse + ")");
+ }
+ } else {
+ if (fMessages) this.printMessage("invalid response (error " + nErrorCode + ")");
+ }
+ return this.sUserID;
+};
+
+/**
+ * getServerStatePath()
+ *
+ * @this {Computer}
+ * @return {string|null} sStatePath (null if no localStorage or no USERID stored in localStorage)
+ */
+Computer.prototype.getServerStatePath = function()
+{
+ var sStatePath = null;
+ if (this.sUserID) {
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage(Computer.STATE_USERID + " for load: " + this.sUserID);
+ }
+ sStatePath = web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.LOAD + '&' + UserAPI.QUERY.USER + '=' + this.sUserID + '&' + UserAPI.QUERY.STATE + '=' + State.key(this, Computer.APPVERSION);
+ } else {
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage(Computer.STATE_USERID + " unavailable");
+ }
+ }
+ return sStatePath;
+};
+
+/**
+ * saveServerState(sUserID, sState)
+ *
+ * @param {string} sUserID
+ * @param {string|null} sState
+ */
+Computer.prototype.saveServerState = function(sUserID, sState)
+{
+ /*
+ * We must pass fSync == true, because (as I understand it) browsers will blow off any async
+ * requests when a page is being closed. Since our request is synchronous, storeServerState()
+ * should also return a result, but there's not much we can do with it, since browsers ALSO
+ * tend to blow off alerts() and the like when closing down.
+ */
+ if (sState) {
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage("size of server state: " + sState.length + " bytes");
+ }
+ var response = this.storeServerState(sUserID, sState, true);
+ if (response && response[UserAPI.RES.CODE] == UserAPI.CODE.OK) {
+ this.notice("Machine state saved to server");
+ } else if (sState) {
+ var sError = (response && response[UserAPI.RES.DATA]) || UserAPI.FAIL.BADSTORE;
+ if (response[UserAPI.RES.CODE] == UserAPI.CODE.FAIL) {
+ sError = "Error: " + sError;
+ } else {
+ sError = "Error " + response[UserAPI.RES.CODE] + ": " + sError;
+ }
+ this.notice(sError);
+ this.resetUserID();
+ }
+ } else {
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage("no state to store");
+ }
+ }
+};
+
+/**
+ * storeServerState(sUserID, sState, fSync)
+ *
+ * @this {Computer}
+ * @param {string} sUserID
+ * @param {string} sState
+ * @param {boolean} [fSync] is true if we're powering down and should perform a synchronous request (default is async)
+ * @return {*} server response if fSync is true and a response was received; otherwise null
+ */
+Computer.prototype.storeServerState = function(sUserID, sState, fSync)
+{
+ if (DEBUG && this.messageEnabled()) {
+ this.printMessage(Computer.STATE_USERID + " for store: " + sUserID);
+ }
+ /*
+ * TODO: Determine whether or not any browsers cancel our request if we're called during a browser "shutdown" event,
+ * and whether or not it matters if we do an async request (currently, we're not, to try to ensure the request goes through).
+ */
+ var dataPost = {};
+ dataPost[UserAPI.QUERY.REQ] = UserAPI.REQ.STORE;
+ dataPost[UserAPI.QUERY.USER] = sUserID;
+ dataPost[UserAPI.QUERY.STATE] = State.key(this, Computer.APPVERSION);
+ dataPost[UserAPI.QUERY.DATA] = sState;
+ var sRequest = web.getHost() + UserAPI.ENDPOINT;
+ if (!fSync) {
+ web.getResource(sRequest, dataPost, true);
+ } else {
+ var response = web.getResource(sRequest, dataPost);
+ var sResponse = response[0];
+ if (response[1]) {
+ if (sResponse) {
+ var i = sResponse.indexOf('\n');
+ if (i > 0) sResponse = sResponse.substr(0, i);
+ if (!sResponse.indexOf("Error: ")) sResponse = sResponse.substr(7);
+ }
+ sResponse = '{"' + UserAPI.RES.CODE + '":' + response[1] + ',"' + UserAPI.RES.DATA + '":"' + sResponse + '"}';
+ }
+ if (DEBUG && this.messageEnabled()) this.printMessage(sResponse);
+ return JSON.parse(sResponse);
+ }
+ return null;
+};
+
+/**
+ * onPower()
+ *
+ * This handles UI requests to toggle the computer's power (eg, see the "power" button binding).
+ *
+ * @this {Computer}
+ */
+Computer.prototype.onPower = function()
+{
+ if (!this.nPowerChange) {
+ if (!this.flags.fPowered) {
+ this.wait(this.powerOn);
+ } else {
+ this.powerOff(false, true);
+ }
+ }
+};
+
+/**
+ * onReset()
+ *
+ * This handles UI requests to reset the computer's state (eg, see the "reset" button binding).
+ *
+ * @this {Computer}
+ */
+Computer.prototype.onReset = function()
+{
+ /*
+ * I'm going to start with the presumption that it makes little sense for an "unpowered" computer to be "reset";
+ * ditto if the power state is currently being changed.
+ */
+ if (!this.flags.fPowered || this.nPowerChange) return;
+
+ /*
+ * If this is a "resumable" machine (and it's not using a predefined state), then we overload the reset
+ * operation to offer an explicit "save or discard" option first. This is currently the only UI we offer to
+ * discard a machine's state, including any disk changes. The traditional "reset" operation is still available
+ * for non-resumable machines.
+ *
+ * TODO: Break this behavior out into a separate "discard" operation, in case the designer of the machine really
+ * wants to clutter the UI with confusing options. ;-)
+ */
+ if (this.resume && !this.sResumePath) {
+ /*
+ * I used to bypass the prompt if this.resume == Computer.RESUME_AUTO, setting fSave to true automatically,
+ * but that gives the user no means of resetting a resumable machine that contains errors in its resume state.
+ */
+ var fSave = (/* this.resume == Computer.RESUME_AUTO || */ web.confirmUser("Click OK to save changes to this " + Computer.APPNAME + " machine.\n\nWARNING: If you CANCEL, all disk changes will be discarded."));
+ this.powerOff(fSave, true);
+ /*
+ * Forcing the page to reload is an expedient option, but ugly. It's preferable to call powerOn()
+ * and rely on all the components to reset themselves to their default state. The components with
+ * the greatest burden here are FDC and HDC, which must rely on the fReload flag to determine whether
+ * or not to unload/reload all their original auto-mounted disk images.
+ *
+ * However, if we started with a predefined state (ie, sStatePath is set), we take this shortcut, because
+ * we don't (yet) have code in place to gracefully reload the initial state (requires calling getResource()
+ * again); alternatively, we could avoid throwing that state away, but it seems better to save the memory.
+ *
+ * TODO: Make this more graceful, so that we can stop using the reloadPage() sledgehammer.
+ */
+ if (!fSave && this.sStatePath) {
+ web.reloadPage();
+ return;
+ }
+ if (!fSave) this.fReload = true;
+ this.powerOn(Computer.RESUME_NONE);
+ this.fReload = false;
+ } else {
+ this.reset();
+ if (this.cpu) this.cpu.autoStart();
+ }
+};
+
+/**
+ * getMachineComponent(sType, componentPrev)
+ *
+ * @this {Computer}
+ * @param {string} sType
+ * @param {Component|null} [componentPrev] of previously returned component, if any
+ * @return {Component|null}
+ */
+Computer.prototype.getMachineComponent = function(sType, componentPrev)
+{
+ var aComponents = Component.getComponents(this.id);
+ for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
+ var component = aComponents[iComponent];
+ if (componentPrev) {
+ if (componentPrev == component) componentPrev = null;
+ continue;
+ }
+ if (component.type == sType) return component;
+ }
+ return null;
+};
+
+/**
+ * updateFocus(fScroll)
+ *
+ * NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button),
+ * the scroll feature has annoying effect on iOS, so we no longer do it by default (fScroll must be true).
+ *
+ * @this {Computer}
+ * @param {boolean} [fScroll]
+ */
+Computer.prototype.updateFocus = function(fScroll)
+{
+ if (this.aVideo.length) {
+ /*
+ * This seems to be recommended work-around to prevent the browser from scrolling the focused element
+ * into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent
+ * is to ensure that keyboard input is fielded properly.
+ */
+ var x = 0, y = 0;
+ if (fScroll && window) {
+ x = window.scrollX;
+ y = window.scrollY;
+ }
+ /*
+ * TODO: We need a mechanism to determine the "active" display, instead of hard-coding this to aVideo[0].
+ *
+ this.aVideo[0].setFocus();
+ if (fScroll && window) {
+ window.scrollTo(x, y);
+ }
+ */
+ }
+};
+
+/**
+ * updateStatus(fForce)
+ *
+ * If any DOM controls were bound to the CPU, then we need to call its updateStatus() handler; if there are no
+ * such bindings, then cpu.updateStatus() does nothing.
+ *
+ * Similarly, if there's a Panel, then we need to call its updateStatus() handler, in case it created its own canvas
+ * and implemented its own register display (eg, dumpRegisters()); if not, then panel.updateStatus() also does nothing.
+ *
+ * In practice, there will *either* be a Panel with a custom canvas *or* a set of DOM controls bound to the CPU *or*
+ * neither. In theory, there could be BOTH, but that would be unusual.
+ *
+ * TODO: Consider alternate approaches to these largely register-oriented display updates. Ordinarily, we like to
+ * separate logic from presentation, and currently the CPUState contains both, since it's the component that intimately
+ * knows the names, number, sizes, etc, of all the active registers. The Panel component is the logical candidate,
+ * but Panel is an optional component; generally, only machines that include Debugger also include Panel.
+ *
+ * @this {Computer}
+ * @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
+ */
+Computer.prototype.updateStatus = function(fForce)
+{
+ /*
+ * fForce is generally set to true whenever the CPU is transitioning to/from a running state, in which case
+ * cpu.updateStatus() will definitely want to hide/show register contents; however, at other times, when the
+ * CPU is running, constantly updating the DOM controls too frequently can adversely impact overall performance.
+ *
+ * So fForce serves as a hint to help cpu.updateStatus() make a more informed decision. panel.updateStatus()
+ * currently doesn't care, on the theory that canvas updates should be significantly faster than DOM updates,
+ * but we still pass fForce on.
+ */
+ if (this.cpu) this.cpu.updateStatus(fForce);
+ if (this.panel) this.panel.updateStatus(fForce);
+};
+
+/**
+ * updateVideo(n)
+ *
+ * Any high-frequency updates should be performed here (avoid updating DOM elements).
+ *
+ * @this {Computer}
+ * @param {number} n (where 0 <= n < VIDEO_UPDATES_PER_SECOND for a normal update, or -1 for a forced update)
+ */
+Computer.prototype.updateVideo = function(n)
+{
+ for (var i = 0; i < this.aVideo.length; i++) {
+ this.aVideo[i].updateScreen(n);
+ }
+};
+
+/**
+ * Computer.init()
+ *
+ * For every machine represented by an HTML element of class "pcjs-machine", this function
+ * locates the HTML element of class "computer", extracting the JSON-encoded parameters for the
+ * Computer constructor from the element's "data-value" attribute, invoking the constructor to
+ * create a Computer component, and then binding any associated HTML controls to the new component.
+ */
+Computer.init = function()
+{
+ /*
+ * In non-COMPILED builds, embedMachine() may have set XMLVERSION.
+ */
+ if (!COMPILED && XMLVERSION) Computer.APPVERSION = XMLVERSION;
+
+ var aeMachines = Component.getElementsByClass(document, PCJSCLASS + "-machine");
+
+ for (var iMachine = 0; iMachine < aeMachines.length; iMachine++) {
+
+ var eMachine = aeMachines[iMachine];
+ var parmsMachine = Component.getComponentParms(eMachine);
+
+ var aeComputers = Component.getElementsByClass(eMachine, PCJSCLASS, "computer");
+
+ for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) {
+
+ var eComputer = aeComputers[iComputer];
+ var parmsComputer = Component.getComponentParms(eComputer);
+
+ /*
+ * We set fSuspended in the Computer constructor because we want to "power up" the
+ * computer ourselves, after any/all bindings are in place.
+ */
+ var computer = new Computer(parmsComputer, parmsMachine, true);
+
+ if (DEBUG && computer.messageEnabled()) {
+ computer.printMessage("onInit(" + computer.flags.fPowered + ")");
+ }
+
+ /*
+ * Bind any "power", "reset" and "save" buttons. An "erase" button was also considered,
+ * but "reset" now provides a way to force the machine to start from scratch again, so "erase"
+ * may be redundant now.
+ */
+ Component.bindComponentControls(computer, eComputer, PCJSCLASS);
+
+ /*
+ * Power on the computer, giving every component the opportunity to reset or restore itself.
+ */
+ if (computer.fAutoPower) computer.wait(computer.powerOn);
+ }
+ }
+};
+
+/**
+ * Computer.show()
+ *
+ * When exit() is using an "onbeforeunload" handler, this "onpageshow" handler allows us to repower everything,
+ * without either resetting or restoring. We call powerOn() with a special resume value (RESUME_REPOWER) if the
+ * computer is already marked as "ready", meaning the browser didn't change anything. This "repower" process
+ * should be very quick, essentially just marking all components as powered again (so that, for example, the Video
+ * component will start drawing again) and firing the CPU up again.
+ */
+Computer.show = function()
+{
+ var aeComputers = Component.getElementsByClass(document, PCJSCLASS, "computer");
+ for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) {
+ var eComputer = aeComputers[iComputer];
+ var parmsComputer = Component.getComponentParms(eComputer);
+ var computer = /** @type {Computer} */ (Component.getComponentByType("Computer", parmsComputer['id']));
+ if (computer) {
+
+ if (DEBUG && computer.messageEnabled()) {
+ computer.printMessage("onShow(" + computer.fInitialized + "," + computer.flags.fPowered + ")");
+ }
+
+ if (computer.fInitialized && !computer.flags.fPowered) {
+ /**
+ * Repower the computer, notifying every component to continue running as-is.
+ */
+ computer.powerOn(Computer.RESUME_REPOWER);
+ }
+ }
+ }
+};
+
+/**
+ * Computer.exit()
+ *
+ * The Computer is currently the only component that uses an "exit" handler, which web.onExit() defines as
+ * either an "unload" or "onbeforeunload" handler. This gives us the opportunity to save the machine state,
+ * using our powerOff() function, before the page goes away.
+ *
+ * It's worth noting that "onbeforeunload" offers one nice feature when used instead of "onload": the entire
+ * page (and therefore this entire application) is retained in its current state by the browser (well, some
+ * browsers), so that if you go to a new URL, either by entering a new URL in the same window/tab, or by pressing
+ * the FORWARD button, and then you press the BACK button, the page is immediately restored to its previous state.
+ *
+ * In fact, that's how some browsers operate whether you have an "onbeforeunload" handler or not; in other words,
+ * an "onbeforeunload" handler doesn't change the page retention behavior of the browser. By contrast, the mere
+ * presence of an "onunload" handler generally causes a browser to throw the page away once the handler returns.
+ *
+ * However, in order to safely use "onbeforeunload", we must add yet another handler ("onpageshow") to repower
+ * everything, without either resetting or restoring. Hence, the Computer.show() function, which calls powerOn()
+ * with a special resume value (RESUME_REPOWER) if the computer is already marked as "ready", meaning the browser
+ * didn't change anything. This "repower" process should be very quick, essentially just marking all components as
+ * powered again (so that, for example, the Video component will start drawing again) and firing the CPU up again.
+ *
+ * Reportedly, some browsers (eg, Opera) don't support "onbeforeunload", in which case Component will have to use
+ * "unload" instead. But even when the page must be rebuilt from scratch, the combination of browser cache and
+ * localStorage means the simulation should be restored and become operational almost immediately.
+ */
+Computer.exit = function()
+{
+ var aeComputers = Component.getElementsByClass(document, PCJSCLASS, "computer");
+ for (var iComputer = 0; iComputer < aeComputers.length; iComputer++) {
+ var eComputer = aeComputers[iComputer];
+ var parmsComputer = Component.getComponentParms(eComputer);
+ var computer = /** @type {Computer} */ (Component.getComponentByType("Computer", parmsComputer['id']));
+ if (computer) {
+
+ if (DEBUG && computer.messageEnabled()) {
+ computer.printMessage("onExit(" + computer.flags.fPowered + ")");
+ }
+
+ if (computer.flags.fPowered) {
+ /**
+ * Power off the computer, giving every component an opportunity to save its state,
+ * but only if 'resume' has been set AND there is no valid resume path (because if a valid resume
+ * path exists, we'll always load our state from there, and not from whatever we save here).
+ */
+ computer.powerOff(!!(computer.resume && !computer.sResumePath), true);
+ }
+ }
+ }
+};
+
+/*
+ * Initialize every Computer on the page.
+ */
+web.onInit(Computer.init);
+web.onShow(Computer.show);
+web.onExit(Computer.exit);
+
+if (NODE) module.exports = Computer;
diff --git a/modules/pc6502/lib/cpu.js b/modules/pc6502/lib/cpu.js
new file mode 100644
index 000000000..f1502d48e
--- /dev/null
+++ b/modules/pc6502/lib/cpu.js
@@ -0,0 +1,1128 @@
+/**
+ * @fileoverview Controls the PC6502 CPU component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var usr = require("../../shared/lib/usrlib");
+ var Component = require("../../shared/lib/component");
+ var Messages = require("./messages");
+}
+
+/**
+ * CPU(parmsCPU, nCyclesDefault)
+ *
+ * The CPU class supports the following (parmsCPU) properties:
+ *
+ * cycles: the machine's base cycles per second; the CPUState constructor will
+ * provide us with a default (based on the CPU model) to use as a fallback.
+ *
+ * multiplier: base cycle multiplier; default is 1.
+ *
+ * autoStart: true to automatically start, false to not, or null if "it depends";
+ * null is the default, which means do not autostart UNLESS there is no Debugger
+ * and no "Run" button (ie, no way to manually start the machine).
+ *
+ * csStart: the number of cycles that runCPU() must wait before generating
+ * checksum records; -1 if disabled. checksum records are a diagnostic aid
+ * used to help compare one CPU run to another.
+ *
+ * csInterval: the number of cycles that runCPU() must execute before
+ * generating a checksum record; -1 if disabled.
+ *
+ * csStop: the number of cycles to stop generating checksum records.
+ *
+ * This component is primarily responsible for interfacing the CPU with the outside
+ * world (eg, Panel and Debugger components), and managing overall CPU operation.
+ *
+ * It is extended by the CPUState component, where the simulation control logic resides.
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsCPU
+ * @param {number} nCyclesDefault
+ */
+function CPU(parmsCPU, nCyclesDefault)
+{
+ Component.call(this, "CPU", parmsCPU, CPU, Messages.CPU);
+
+ var nCycles = parmsCPU['cycles'] || nCyclesDefault;
+
+ var nMultiplier = parmsCPU['multiplier'] || 1;
+
+ this.aCounts = {};
+ this.aCounts.nCyclesPerSecond = nCycles;
+ this.aCounts.nVideoUpdates = 0;
+
+ /*
+ * nCyclesMultiplier replaces the old "speed" variable (0, 1, 2) and eliminates the need for
+ * the constants (SPEED_SLOW, SPEED_FAST and SPEED_MAX). The UI simply doubles the multiplier
+ * until we've exceeded the host's speed limit and then starts the multiplier over at 1.
+ */
+ this.aCounts.nCyclesMultiplier = nMultiplier;
+ this.aCounts.mhzDefault = Math.round(this.aCounts.nCyclesPerSecond / 10000) / 100;
+ /*
+ * TODO: Take care of this with an initial setSpeed() call instead?
+ */
+ this.aCounts.mhzTarget = this.aCounts.mhzDefault * this.aCounts.nCyclesMultiplier;
+
+ /*
+ * We add a number of flags to the set initialized by Component
+ */
+ this.flags.fRunning = false;
+ this.flags.fStarting = false;
+ this.flags.fAutoStart = parmsCPU['autoStart'];
+
+ /*
+ * TODO: Add some UI for fDisplayLiveRegs (either an XML property, or a UI checkbox, or both)
+ */
+ this.flags.fDisplayLiveRegs = false;
+
+ /*
+ * Get checksum parameters, if any. runCPU() behavior is not affected until fChecksum
+ * is true, which won't happen until resetChecksum() is called with nCyclesChecksumInterval
+ * ("csInterval") set to a positive value.
+ *
+ * As above, any of these parameters can also be set with the Debugger's execution options
+ * command ("x"); for example, "x cs int 5000" will set nCyclesChecksumInterval to 5000
+ * and call resetChecksum().
+ */
+ this.flags.fChecksum = false;
+ this.aCounts.nChecksum = this.aCounts.nCyclesChecksumNext = 0;
+ this.aCounts.nCyclesChecksumStart = parmsCPU["csStart"];
+ this.aCounts.nCyclesChecksumInterval = parmsCPU["csInterval"];
+ this.aCounts.nCyclesChecksumStop = parmsCPU["csStop"];
+
+ this.onRunTimeout = this.runCPU.bind(this); // function onRunTimeout() { cpu.runCPU(); };
+
+ this.setReady();
+}
+
+Component.subclass(CPU);
+
+/*
+ * Constants that control the frequency at which various updates should occur.
+ *
+ * These values do NOT control the simulation directly. Instead, they are used by
+ * calcCycles(), which uses the nCyclesPerSecond passed to the constructor as a starting
+ * point and computes the following variables:
+ *
+ * this.aCounts.nCyclesPerYield (this.aCounts.nCyclesPerSecond / CPU.YIELDS_PER_SECOND)
+ * this.aCounts.nCyclesPerVideoUpdate (this.aCounts.nCyclesPerSecond / CPU.VIDEO_UPDATES_PER_SECOND)
+ * this.aCounts.nCyclesPerStatusUpdate (this.aCounts.nCyclesPerSecond / CPU.STATUS_UPDATES_PER_SECOND)
+ *
+ * The above variables are also multiplied by any cycle multiplier in effect, via setSpeed(),
+ * and then they're used to initialize another set of variables for each runCPU() iteration:
+ *
+ * this.aCounts.nCyclesNextYield <= this.aCounts.nCyclesPerYield
+ * this.aCounts.nCyclesNextVideoUpdate <= this.aCounts.nCyclesPerVideoUpdate
+ * this.aCounts.nCyclesNextStatusUpdate <= this.aCounts.nCyclesPerStatusUpdate
+ */
+CPU.YIELDS_PER_SECOND = 30;
+CPU.VIDEO_UPDATES_PER_SECOND = 60;
+CPU.STATUS_UPDATES_PER_SECOND = 2;
+
+CPU.BUTTONS = ["power", "reset"];
+
+/**
+ * initBus(cmp, bus, cpu, dbg)
+ *
+ * @this {CPU}
+ * @param {Computer} cmp
+ * @param {Bus} bus
+ * @param {CPU} cpu
+ * @param {Debugger} dbg
+ */
+CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
+{
+ this.cmp = cmp;
+ this.bus = bus;
+ this.dbg = dbg;
+
+ for (var i = 0; i < CPU.BUTTONS.length; i++) {
+ var control = this.bindings[CPU.BUTTONS[i]];
+ if (control) this.cmp.setBinding(null, CPU.BUTTONS[i], control);
+ }
+
+ /*
+ * We need to know the refresh rate (and corresponding interrupt rate, if any) of the Video component.
+ */
+ var video = cmp.getMachineComponent("Video");
+ this.refreshRate = video && video.getRefreshRate() || CPU.VIDEO_UPDATES_PER_SECOND;
+
+ /*
+ * Attach the ChipSet component to the CPU so that it can be notified whenever the CPU stops and starts.
+ */
+ this.chipset = cmp.getMachineComponent("ChipSet");
+
+ /*
+ * We've already saved the parmsCPU 'autoStart' setting, but there may be a machine (or URL) override.
+ */
+ var sAutoStart = cmp.getMachineParm('autoStart');
+ if (sAutoStart != null) {
+ this.flags.fAutoStart = (sAutoStart == "true"? true : (sAutoStart == "false"? false : !!sAutoStart));
+ }
+
+ this.setReady();
+};
+
+/**
+ * reset()
+ *
+ * @this {CPU}
+ */
+CPU.prototype.reset = function()
+{
+ this.aCounts.nVideoUpdates = 0;
+};
+
+/**
+ * save()
+ *
+ * This is a placeholder for save support (overridden by the CPUState component).
+ *
+ * @this {CPU}
+ * @return {Object|null}
+ */
+CPU.prototype.save = function()
+{
+ return null;
+};
+
+/**
+ * restore(data)
+ *
+ * This is a placeholder for restore support (overridden by the CPUState component).
+ *
+ * @this {CPU}
+ * @param {Object} data
+ * @return {boolean} true if restore successful, false if not
+ */
+CPU.prototype.restore = function(data)
+{
+ return false;
+};
+
+/**
+ * powerUp(data, fRepower)
+ *
+ * @this {CPU}
+ * @param {Object|null} data
+ * @param {boolean} [fRepower]
+ * @return {boolean} true if successful, false if failure
+ */
+CPU.prototype.powerUp = function(data, fRepower)
+{
+ if (!fRepower) {
+ if (!data || !this.restore) {
+ this.reset();
+ } else {
+ this.resetCycles();
+ if (!this.restore(data)) return false;
+ this.resetChecksum();
+ }
+ /*
+ * Give the Debugger a chance to do/print something once we've powered up
+ */
+ if (DEBUGGER && this.dbg) {
+ this.dbg.init();
+ } else {
+ /*
+ * The Computer (this.cmp) knows if there's a Control Panel (this.cmp.panel), and the Control Panel
+ * knows if there's a "print" control (this.cmp.panel.controlPrint), and if there IS a "print" control
+ * but no debugger, the machine is probably misconfigured (most likely, the page simply neglected to
+ * load the Debugger component).
+ *
+ * However, we don't actually need to check all that; it's always safe use println(), regardless whether
+ * a Control Panel with a "print" control is present or not.
+ */
+ this.println("No debugger detected");
+ }
+ }
+ /*
+ * The Computer component (which is responsible for all powerDown and powerUp notifications)
+ * is now responsible for managing a component's fPowered flag, not us.
+ *
+ * this.flags.fPowered = true;
+ */
+ this.updateCPU();
+ return true;
+};
+
+/**
+ * powerDown(fSave, fShutdown)
+ *
+ * @this {CPU}
+ * @param {boolean} [fSave]
+ * @param {boolean} [fShutdown]
+ * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
+ */
+CPU.prototype.powerDown = function(fSave, fShutdown)
+{
+ /*
+ * The Computer component (which is responsible for all powerDown and powerUp notifications)
+ * is now responsible for managing a component's fPowered flag, not us.
+ *
+ * this.flags.fPowered = false;
+ */
+ return fSave? this.save() : true;
+};
+
+/**
+ * autoStart()
+ *
+ * @this {CPU}
+ * @return {boolean} true if started, false if not
+ */
+CPU.prototype.autoStart = function()
+{
+ /*
+ * Start running automatically on power-up, assuming there's no Debugger and no "Run" button
+ */
+ if (this.flags.fAutoStart || (!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined) {
+ /*
+ * Now we ALSO set fUpdateFocus when calling runCPU(), on the assumption that in the "auto-starting" context,
+ * a machine without focus is like a day without sunshine.
+ */
+ this.runCPU(true);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * isPowered()
+ *
+ * @this {CPU}
+ * @return {boolean}
+ */
+CPU.prototype.isPowered = function()
+{
+ if (!this.flags.fPowered) {
+ this.println(this.toString() + " not powered");
+ return false;
+ }
+ return true;
+};
+
+/**
+ * isRunning()
+ *
+ * @this {CPU}
+ * @return {boolean}
+ */
+CPU.prototype.isRunning = function()
+{
+ return this.flags.fRunning;
+};
+
+/**
+ * getChecksum()
+ *
+ * This will be implemented by the CPUState component.
+ *
+ * @this {CPU}
+ * @return {number} a 32-bit summation of key elements of the current CPU state (used by the CPU checksum code)
+ */
+CPU.prototype.getChecksum = function()
+{
+ return 0;
+};
+
+/**
+ * resetChecksum()
+ *
+ * If checksum generation is enabled (fChecksum is true), this resets the running 32-bit checksum and the
+ * cycle counter that will trigger the next displayChecksum(); called by resetCycles(), which is called whenever
+ * the CPU is reset or restored.
+ *
+ * @this {CPU}
+ * @return {boolean} true if checksum generation enabled, false if not
+ */
+CPU.prototype.resetChecksum = function()
+{
+ if (this.aCounts.nCyclesChecksumStart === undefined) this.aCounts.nCyclesChecksumStart = 0;
+ if (this.aCounts.nCyclesChecksumInterval === undefined) this.aCounts.nCyclesChecksumInterval = -1;
+ if (this.aCounts.nCyclesChecksumStop === undefined) this.aCounts.nCyclesChecksumStop = -1;
+ this.flags.fChecksum = (this.aCounts.nCyclesChecksumStart >= 0 && this.aCounts.nCyclesChecksumInterval > 0);
+ if (this.flags.fChecksum) {
+ this.aCounts.nChecksum = 0;
+ this.aCounts.nCyclesChecksumNext = this.aCounts.nCyclesChecksumStart - this.nTotalCycles;
+ /*
+ * this.aCounts.nCyclesChecksumNext = this.aCounts.nCyclesChecksumStart + this.aCounts.nCyclesChecksumInterval -
+ * (this.nTotalCycles % this.aCounts.nCyclesChecksumInterval);
+ */
+ return true;
+ }
+ return false;
+};
+
+/**
+ * updateChecksum(nCycles)
+ *
+ * When checksum generation is enabled (fChecksum is true), runCPU() asks stepCPU() to execute a minimum
+ * number of cycles (1), effectively limiting execution to a single instruction, and then we're called with
+ * the exact number cycles that were actually executed. This should give us instruction-granular checksums
+ * at precise intervals that are 100% repeatable.
+ *
+ * @this {CPU}
+ * @param {number} nCycles
+ */
+CPU.prototype.updateChecksum = function(nCycles)
+{
+ if (this.flags.fChecksum) {
+ /*
+ * Get a 32-bit summation of the current CPU state and add it to our running 32-bit checksum
+ */
+ var fDisplay = false;
+ this.aCounts.nChecksum = (this.aCounts.nChecksum + this.getChecksum())|0;
+ this.aCounts.nCyclesChecksumNext -= nCycles;
+ if (this.aCounts.nCyclesChecksumNext <= 0) {
+ this.aCounts.nCyclesChecksumNext += this.aCounts.nCyclesChecksumInterval;
+ fDisplay = true;
+ }
+ if (this.aCounts.nCyclesChecksumStop >= 0) {
+ if (this.aCounts.nCyclesChecksumStop <= this.getCycles()) {
+ this.aCounts.nCyclesChecksumInterval = this.aCounts.nCyclesChecksumStop = -1;
+ this.resetChecksum();
+ this.stopCPU();
+ fDisplay = true;
+ }
+ }
+ if (fDisplay) this.displayChecksum();
+ }
+};
+
+/**
+ * displayChecksum()
+ *
+ * When checksum generation is enabled (fChecksum is true), this is called to provide a crude log of all
+ * checksums generated at the specified cycle intervals, as specified by the "csStart" and "csInterval" parmsCPU
+ * properties).
+ *
+ * @this {CPU}
+ */
+CPU.prototype.displayChecksum = function()
+{
+ this.println(this.getCycles() + " cycles: " + "checksum=" + str.toHex(this.aCounts.nChecksum));
+};
+
+/**
+ * displayValue(sLabel, nValue, cch)
+ *
+ * This is principally for displaying register values, but in reality, it can be used to display any
+ * numeric (hex) value bound to the given label.
+ *
+ * @this {CPU}
+ * @param {string} sLabel
+ * @param {number} nValue
+ * @param {number} cch
+ */
+CPU.prototype.displayValue = function(sLabel, nValue, cch)
+{
+ if (this.bindings[sLabel]) {
+ if (nValue === undefined) {
+ this.setError("Value for " + sLabel + " is invalid");
+ this.stopCPU();
+ }
+ var sVal;
+ if (!this.flags.fRunning || this.flags.fDisplayLiveRegs) {
+ sVal = str.toHex(nValue, cch);
+ } else {
+ sVal = "--------".substr(0, cch);
+ }
+ /*
+ * TODO: Determine if this test actually avoids any redrawing when a register hasn't changed, and/or if
+ * we should maintain our own (numeric) cache of displayed register values (to avoid creating these temporary
+ * string values that will have to garbage-collected), and/or if this is actually slower, and/or if I'm being
+ * too obsessive.
+ */
+ if (this.bindings[sLabel].textContent != sVal) this.bindings[sLabel].textContent = sVal;
+ }
+};
+
+/**
+ * setBinding(sHTMLType, sBinding, control, sValue)
+ *
+ * @this {CPU}
+ * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
+ * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "run")
+ * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
+ * @param {string} [sValue] optional data value
+ * @return {boolean} true if binding was successful, false if unrecognized binding request
+ */
+CPU.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
+{
+ var cpu = this;
+ var fBound = false;
+
+ switch (sBinding) {
+ case "power":
+ case "reset":
+ /*
+ * The "power" and "reset" buttons are functions of the entire computer, not just the CPU,
+ * but it's not always convenient to stick a power button in the Computer component definition,
+ * so we record those bindings here and pass them on to the Computer component in initBus().
+ */
+ this.bindings[sBinding] = control;
+ fBound = true;
+ break;
+
+ case "run":
+ this.bindings[sBinding] = control;
+ control.onclick = function onClickRun() {
+ if (!cpu.cmp || !cpu.cmp.checkPower()) return;
+ if (!cpu.flags.fRunning)
+ cpu.runCPU(true);
+ else
+ cpu.stopCPU(true);
+ };
+ fBound = true;
+ break;
+
+ case "speed":
+ this.bindings[sBinding] = control;
+ fBound = true;
+ break;
+
+ case "setSpeed":
+ this.bindings[sBinding] = control;
+ control.onclick = function onClickSetSpeed() {
+ cpu.setSpeed(cpu.aCounts.nCyclesMultiplier << 1, true);
+ };
+ control.textContent = this.getSpeedTarget();
+ fBound = true;
+ break;
+
+ default:
+ break;
+ }
+ return fBound;
+};
+
+/**
+ * setBurstCycles(nCycles)
+ *
+ * This function is used by the ChipSet component whenever a very low timer count is set,
+ * in anticipation of the timer requiring an update sooner than the normal nCyclesPerYield
+ * period in runCPU() would normally provide.
+ *
+ * @this {CPU}
+ * @param {number} nCycles is the target number of cycles to drop the current burst to
+ * @return {boolean}
+ */
+CPU.prototype.setBurstCycles = function(nCycles)
+{
+ if (this.flags.fRunning) {
+ var nDelta = this.nStepCycles - nCycles;
+ /*
+ * NOTE: If nDelta is negative, we will actually be increasing nStepCycles and nBurstCycles.
+ * Which is OK, but if we're also taking snapshots of the cycle counts, to make sure that instruction
+ * costs are being properly assessed, then we need to update nSnapCycles as well.
+ *
+ * TODO: If the delta is negative, we could simply ignore the request, but we must first carefully
+ * consider the impact on the ChipSet timers, if any.
+ */
+ // if (DEBUG) this.nSnapCycles -= nDelta;
+ this.nStepCycles -= nDelta;
+ this.nBurstCycles -= nDelta;
+ return true;
+ }
+ return false;
+};
+
+/**
+ * addCycles(nCycles, fEndStep)
+ *
+ * @this {CPU}
+ * @param {number} nCycles
+ * @param {boolean} [fEndStep]
+ */
+CPU.prototype.addCycles = function(nCycles, fEndStep)
+{
+ this.nTotalCycles += nCycles;
+ if (fEndStep) {
+ this.nBurstCycles = this.nStepCycles = 0;
+ }
+};
+
+/**
+ * calcCycles(fRecalc)
+ *
+ * Calculate the number of cycles to process for each "burst" of CPU activity. The size of a burst
+ * is driven by the following values:
+ *
+ * CPU.YIELDS_PER_SECOND (eg, 30)
+ * CPU.VIDEO_UPDATES_PER_SECOND (eg, 60)
+ * CPU.STATUS_UPDATES_PER_SECOND (eg, 5)
+ *
+ * The largest of the above values forces the size of the burst to its smallest value. Let's say that
+ * largest value is 30. Assuming nCyclesPerSecond is 1,000,000, that results in bursts of 33,333 cycles.
+ *
+ * At the end of each burst, we subtract burst cycles from yield, video, and status cycle "threshold"
+ * counters. Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time
+ * to the time we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time
+ * remaining, we sleep the remaining time (or 0ms if there's no remaining time), and then restart runCPU().
+ *
+ * Similarly, whenever the "next video update" cycle counter goes to (or below) zero, we call updateVideo(),
+ * and whenever the "next status update" cycle counter goes to (or below) zero, we call updateStatus().
+ *
+ * @this {CPU}
+ * @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the most recent
+ * speed calculation (see calcSpeed).
+ */
+CPU.prototype.calcCycles = function(fRecalc)
+{
+ /*
+ * Calculate the most cycles we're allowed to execute in a single "burst"
+ */
+ var nMostUpdatesPerSecond = CPU.YIELDS_PER_SECOND;
+ if (nMostUpdatesPerSecond < this.refreshRate) nMostUpdatesPerSecond = this.refreshRate;
+ if (nMostUpdatesPerSecond < CPU.STATUS_UPDATES_PER_SECOND) nMostUpdatesPerSecond = CPU.STATUS_UPDATES_PER_SECOND;
+
+ /*
+ * Calculate cycle "per" values for the yield, video update, and status update cycle counters
+ */
+ var vMultiplier = 1;
+ if (fRecalc) {
+ if (this.aCounts.nCyclesMultiplier > 1 && this.aCounts.mhz) {
+ vMultiplier = (this.aCounts.mhz / this.aCounts.mhzDefault);
+ }
+ }
+
+ this.aCounts.msPerYield = Math.round(1000 / CPU.YIELDS_PER_SECOND);
+ this.aCounts.nCyclesPerBurst = Math.floor(this.aCounts.nCyclesPerSecond / nMostUpdatesPerSecond * vMultiplier);
+ this.aCounts.nCyclesPerYield = Math.floor(this.aCounts.nCyclesPerSecond / CPU.YIELDS_PER_SECOND * vMultiplier);
+ this.aCounts.nCyclesPerVideoUpdate = Math.floor(this.aCounts.nCyclesPerSecond / this.refreshRate * vMultiplier);
+ this.aCounts.nCyclesPerStatusUpdate = Math.floor(this.aCounts.nCyclesPerSecond / CPU.STATUS_UPDATES_PER_SECOND * vMultiplier);
+
+ /*
+ * And initialize "next" yield, video update, and status update cycle "threshold" counters to those "per" values
+ */
+ if (!fRecalc) {
+ this.aCounts.nCyclesNextYield = this.aCounts.nCyclesPerYield;
+ this.aCounts.nCyclesNextVideoUpdate = this.aCounts.nCyclesPerVideoUpdate;
+ this.aCounts.nCyclesNextStatusUpdate = this.aCounts.nCyclesPerStatusUpdate;
+ }
+ this.aCounts.nCyclesRecalc = 0;
+};
+
+/**
+ * getCycles(fScaled)
+ *
+ * getCycles() returns the number of cycles executed so far. Note that we can be called after
+ * runCPU() OR during runCPU(), perhaps from a handler triggered during the current run's stepCPU(),
+ * so nRunCycles must always be adjusted by number of cycles stepCPU() was asked to run (nBurstCycles),
+ * less the number of cycles it has yet to run (nStepCycles).
+ *
+ * nRunCycles is zeroed whenever the CPU is halted or the CPU speed is changed, which is why we also
+ * have nTotalCycles, which accumulates all nRunCycles before we zero it. However, nRunCycles and
+ * nTotalCycles eventually get reset by calcSpeed(), to avoid overflow, so components that rely on
+ * getCycles() returning steadily increasing values should also be prepared for a reset at any time.
+ *
+ * @this {CPU}
+ * @param {boolean} [fScaled] is true if the caller wants a cycle count relative to a multiplier of 1
+ * @return {number}
+ */
+CPU.prototype.getCycles = function(fScaled)
+{
+ var nCycles = this.nTotalCycles + this.nRunCycles + this.nBurstCycles - this.nStepCycles;
+ if (fScaled && this.aCounts.nCyclesMultiplier > 1 && this.aCounts.mhz > this.aCounts.mhzDefault) {
+ /*
+ * We could scale the current cycle count by the current effective speed (this.aCounts.mhz); eg:
+ *
+ * nCycles = Math.round(nCycles / (this.aCounts.mhz / this.aCounts.mhzDefault));
+ *
+ * but that speed will fluctuate somewhat: large fluctuations at first, but increasingly smaller
+ * fluctuations after each burst of instructions that runCPU() executes.
+ *
+ * Alternatively, we can scale the cycle count by the multiplier, which is good in that the
+ * multiplier doesn't vary once the user changes it, but a potential downside is that the
+ * multiplier might be set too high, resulting in a target speed that's higher than the effective
+ * speed is able to reach.
+ *
+ * Also, if multipliers were always limited to a power-of-two, then this could be calculated
+ * with a simple shift. However, only the "setSpeed" UI binding limits it that way; the Debugger
+ * interface allows any value, as does the CPU "multiplier" parmsCPU property (from the machine's
+ * XML file).
+ */
+ nCycles = Math.round(nCycles / this.aCounts.nCyclesMultiplier);
+ }
+ return nCycles;
+};
+
+/**
+ * getCyclesPerSecond()
+ *
+ * This returns the CPU's "base" speed (ie, the original cycles per second defined for the machine)
+ *
+ * @this {CPU}
+ * @return {number}
+ */
+CPU.prototype.getCyclesPerSecond = function()
+{
+ return this.aCounts.nCyclesPerSecond;
+};
+
+/**
+ * resetCycles()
+ *
+ * Resets speed and cycle information as part of any reset() or restore(); this typically occurs during powerUp().
+ * It's important that this be called BEFORE the actual restore() call, because restore() may want to call setSpeed(),
+ * which in turn assumes that all the cycle counts have been initialized to sensible values.
+ *
+ * @this {CPU}
+ */
+CPU.prototype.resetCycles = function()
+{
+ this.aCounts.mhz = 0;
+ this.nTotalCycles = this.nRunCycles = this.nBurstCycles = this.nStepCycles = 0;
+ this.resetChecksum();
+ this.setSpeed(1);
+};
+
+/**
+ * getSpeed()
+ *
+ * @this {CPU}
+ * @return {number} the current speed multiplier
+ */
+CPU.prototype.getSpeed = function()
+{
+ return this.aCounts.nCyclesMultiplier;
+};
+
+/**
+ * getSpeedCurrent()
+ *
+ * @this {CPU}
+ * @return {string} the current speed, in mhz, as a string formatted to two decimal places
+ */
+CPU.prototype.getSpeedCurrent = function()
+{
+ /*
+ * TODO: Has toFixed() been "fixed" in all browsers (eg, IE) to return a rounded value now?
+ */
+ return ((this.flags.fRunning && this.aCounts.mhz)? (this.aCounts.mhz.toFixed(2) + "Mhz") : "Stopped");
+};
+
+/**
+ * getSpeedTarget()
+ *
+ * @this {CPU}
+ * @return {string} the target speed, in mhz, as a string formatted to two decimal places
+ */
+CPU.prototype.getSpeedTarget = function()
+{
+ /*
+ * TODO: Has toFixed() been "fixed" in all browsers (eg, IE) to return a rounded value now?
+ */
+ return this.aCounts.mhzTarget.toFixed(2) + "Mhz";
+};
+
+/**
+ * setSpeed(nMultiplier, fUpdateFocus)
+ *
+ * NOTE: This used to return the target speed, in mhz, but no callers appear to care at this point.
+ *
+ * @this {CPU}
+ * @param {number} [nMultiplier] is the new proposed multiplier (reverts to 1 if the target was too high)
+ * @param {boolean} [fUpdateFocus] is true to update Computer focus
+ * @return {boolean} true if successful, false if not
+ *
+ * @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset,
+ * so that the next effective speed calculation obtains sensible results. In fact, when runCPU() initially calls
+ * setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting).
+ */
+CPU.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
+{
+ var fSuccess = false;
+ if (nMultiplier !== undefined) {
+ /*
+ * If we haven't reached 80% (0.8) of the current target speed, revert to a multiplier of one (1).
+ */
+ if (this.aCounts.mhz / this.aCounts.mhzTarget < 0.8) {
+ nMultiplier = 1;
+ } else {
+ fSuccess = true;
+ }
+ this.aCounts.nCyclesMultiplier = nMultiplier;
+ var mhz = this.aCounts.mhzDefault * this.aCounts.nCyclesMultiplier;
+ if (this.aCounts.mhzTarget != mhz) {
+ this.aCounts.mhzTarget = mhz;
+ var sSpeed = this.getSpeedTarget();
+ var controlSpeed = this.bindings["setSpeed"];
+ if (controlSpeed) controlSpeed.textContent = sSpeed;
+ this.println("target speed: " + sSpeed);
+ }
+ if (fUpdateFocus && this.cmp) this.cmp.updateFocus();
+ }
+ this.addCycles(this.nRunCycles);
+ this.nRunCycles = 0;
+ this.aCounts.msStartRun = usr.getTime();
+ this.aCounts.msEndThisRun = 0;
+ this.calcCycles();
+ return fSuccess;
+};
+
+/**
+ * calcSpeed(nCycles, msElapsed)
+ *
+ * @this {CPU}
+ * @param {number} nCycles
+ * @param {number} msElapsed
+ */
+CPU.prototype.calcSpeed = function(nCycles, msElapsed)
+{
+ if (msElapsed) {
+ this.aCounts.mhz = Math.round(nCycles / (msElapsed * 10)) / 100;
+ if (msElapsed >= 86400000) {
+ this.nTotalCycles = 0;
+ this.setSpeed(); // reset all counters once per day so that we never have to worry about overflow
+ }
+ }
+};
+
+/**
+ * calcStartTime()
+ *
+ * @this {CPU}
+ */
+CPU.prototype.calcStartTime = function()
+{
+ if (this.aCounts.nCyclesRecalc >= this.aCounts.nCyclesPerSecond) {
+ this.calcCycles(true);
+ }
+ this.aCounts.nCyclesThisRun = 0;
+ this.aCounts.msStartThisRun = usr.getTime();
+
+ /*
+ * Try to detect situations where the browser may have throttled us, such as when the user switches
+ * to a different tab; in those situations, Chrome and Safari may restrict setTimeout() callbacks
+ * to roughly one per second.
+ *
+ * Another scenario: the user resizes the browser window. setTimeout() callbacks are not throttled,
+ * but there can still be enough of a lag between the callbacks that CPU speed will be noticeably
+ * erratic if we don't compensate for it here.
+ *
+ * We can detect throttling/lagging by verifying that msEndThisRun (which was set at the end of the
+ * previous run and includes any requested sleep time) is comparable to the current msStartThisRun;
+ * if the delta is significant, we compensate by bumping msStartRun forward by that delta.
+ *
+ * This shouldn't be triggered when the Debugger halts the CPU, because setSpeed() -- which is called
+ * whenever the CPU starts running again -- zeroes msEndThisRun.
+ *
+ * This also won't do anything about other internal delays; for example, Debugger message() calls.
+ * By the time the message() function has called yieldCPU(), the cost of the message has already been
+ * incurred, so it will be end up being charged against the instruction(s) that triggered it.
+ *
+ * TODO: Consider calling yieldCPU() sooner from message(), so that it can arrange for the msEndThisRun
+ * "snapshot" to occur sooner; it's unclear, however, whether that will really improve the CPU's ability
+ * to hit its target speed, since you would expect any instruction that displays a message to be an
+ * EXTREMELY slow instruction.
+ */
+ if (this.aCounts.msEndThisRun) {
+ var msDelta = this.aCounts.msStartThisRun - this.aCounts.msEndThisRun;
+ if (msDelta > this.aCounts.msPerYield) {
+ if (MAXDEBUG) this.println("large time delay: " + msDelta + "ms");
+ this.aCounts.msStartRun += msDelta;
+ /*
+ * Bumping msStartRun forward should NEVER cause it to exceed msStartThisRun; however, just
+ * in case, I make absolutely sure it cannot happen, since doing so could result in negative
+ * speed calculations.
+ */
+ this.assert(this.aCounts.msStartRun <= this.aCounts.msStartThisRun);
+ if (this.aCounts.msStartRun > this.aCounts.msStartThisRun) {
+ this.aCounts.msStartRun = this.aCounts.msStartThisRun;
+ }
+ }
+ }
+};
+
+/**
+ * calcRemainingTime()
+ *
+ * @this {CPU}
+ * @return {number}
+ */
+CPU.prototype.calcRemainingTime = function()
+{
+ this.aCounts.msEndThisRun = usr.getTime();
+
+ var msYield = this.aCounts.msPerYield;
+ if (this.aCounts.nCyclesThisRun) {
+ /*
+ * Normally, we would assume we executed a full quota of work over msPerYield, but since the CPU
+ * now has the option of calling yieldCPU(), that might not be true. If nCyclesThisRun is correct, then
+ * the ratio of nCyclesThisRun/nCyclesPerYield should represent the percentage of work we performed,
+ * and so applying that percentage to msPerYield should give us a better estimate of work vs. time.
+ */
+ msYield = Math.round(msYield * this.aCounts.nCyclesThisRun / this.aCounts.nCyclesPerYield);
+ }
+
+ var msElapsedThisRun = this.aCounts.msEndThisRun - this.aCounts.msStartThisRun;
+ var msRemainsThisRun = msYield - msElapsedThisRun;
+
+ /*
+ * We could pass only "this run" results to calcSpeed():
+ *
+ * nCycles = this.aCounts.nCyclesThisRun;
+ * msElapsed = msElapsedThisRun;
+ *
+ * but it seems preferable to use longer time periods and hopefully get a more accurate speed.
+ *
+ * Also, if msRemainsThisRun >= 0 && this.aCounts.nCyclesMultiplier == 1, we could pass these results instead:
+ *
+ * nCycles = this.aCounts.nCyclesThisRun;
+ * msElapsed = this.aCounts.msPerYield;
+ *
+ * to insure that we display a smooth, constant N Mhz. But for now, I prefer seeing any fluctuations.
+ */
+ var nCycles = this.nRunCycles;
+ var msElapsed = this.aCounts.msEndThisRun - this.aCounts.msStartRun;
+
+ if (MAXDEBUG && msRemainsThisRun < 0 && this.aCounts.nCyclesMultiplier > 1) {
+ this.println("warning: updates @" + msElapsedThisRun + "ms (prefer " + Math.round(msYield) + "ms)");
+ }
+
+ this.calcSpeed(nCycles, msElapsed);
+
+ if (msRemainsThisRun < 0 || this.aCounts.mhz < this.aCounts.mhzTarget) {
+ /*
+ * If the last burst took MORE time than we allotted (ie, it's taking more than 1 second to simulate
+ * nCyclesPerSecond), all we can do is yield for as little time as possible (ie, 0ms) and hope that the
+ * simulation is at least usable.
+ */
+ msRemainsThisRun = 0;
+ }
+
+ /*
+ * Last but not least, update nCyclesRecalc, so that when runCPU() starts up again and calls calcStartTime(),
+ * it'll be ready to decide if calcCycles() should be called again.
+ */
+ this.aCounts.nCyclesRecalc += this.aCounts.nCyclesThisRun;
+
+ if (DEBUG && this.messageEnabled(Messages.LOG) && msRemainsThisRun) {
+ this.log("calcRemainingTime: " + msRemainsThisRun + "ms to sleep after " + this.aCounts.msEndThisRun + "ms");
+ }
+
+ this.aCounts.msEndThisRun += msRemainsThisRun;
+ return msRemainsThisRun;
+};
+
+/**
+ * runCPU(fUpdateFocus)
+ *
+ * @this {CPU}
+ * @param {boolean} [fUpdateFocus] is true to update Computer focus
+ */
+CPU.prototype.runCPU = function(fUpdateFocus)
+{
+ if (!this.setBusy(true)) {
+ this.updateCPU();
+ if (this.cmp) this.cmp.stop(usr.getTime(), this.getCycles());
+ return;
+ }
+
+ this.startCPU(fUpdateFocus);
+
+ /*
+ * calcStartTime() initializes the cycle counter and timestamp for this runCPU() invocation, and optionally
+ * recalculates the the maximum number of cycles for each burst if the nCyclesRecalc threshold has been reached.
+ */
+ this.calcStartTime();
+ try {
+ do {
+ var nCyclesPerBurst = (this.flags.fChecksum? 1 : this.aCounts.nCyclesPerBurst);
+
+ /*
+ * nCyclesPerBurst is how many cycles we WANT to run on each iteration of stepCPU(), but it may run
+ * significantly less (or slightly more, since we can't execute partial instructions).
+ */
+ this.stepCPU(nCyclesPerBurst);
+
+ /*
+ * nBurstCycles, less any remaining nStepCycles, is how many cycles stepCPU() ACTUALLY ran (nCycles).
+ * We add that to nCyclesThisRun, as well as nRunCycles, which is the cycle count since the CPU first
+ * started running.
+ */
+ var nCycles = this.nBurstCycles - this.nStepCycles;
+ this.nRunCycles += nCycles;
+ this.aCounts.nCyclesThisRun += nCycles;
+ this.addCycles(0, true);
+ this.updateChecksum(nCycles);
+
+ this.aCounts.nCyclesNextVideoUpdate -= nCycles;
+ if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
+ this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
+ if (this.cmp) this.cmp.updateVideo(this.aCounts.nVideoUpdates++);
+ if (this.aCounts.nVideoUpdates > this.refreshRate) this.aCounts.nVideoUpdates = 0;
+ }
+
+ this.aCounts.nCyclesNextStatusUpdate -= nCycles;
+ if (this.aCounts.nCyclesNextStatusUpdate <= 0) {
+ this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate;
+ if (this.cmp) this.cmp.updateStatus();
+ }
+
+ this.aCounts.nCyclesNextYield -= nCycles;
+ if (this.aCounts.nCyclesNextYield <= 0) {
+ this.aCounts.nCyclesNextYield += this.aCounts.nCyclesPerYield;
+ break;
+ }
+ } while (this.flags.fRunning);
+ }
+ catch (e) {
+ this.stopCPU();
+ this.updateCPU();
+ if (this.cmp) this.cmp.stop(usr.getTime(), this.getCycles());
+ this.setBusy(false);
+ this.setError(e.stack || e.message);
+ return;
+ }
+ setTimeout(this.onRunTimeout, this.calcRemainingTime());
+};
+
+/**
+ * startCPU(fUpdateFocus)
+ *
+ * WARNING: Other components must use runCPU() to get the CPU running; this is a runCPU() helper function only.
+ *
+ * @param {boolean} [fUpdateFocus]
+ */
+CPU.prototype.startCPU = function(fUpdateFocus)
+{
+ if (!this.flags.fRunning) {
+ /*
+ * setSpeed() without a speed parameter leaves the selected speed in place, but also resets the
+ * cycle counter and timestamp for the current series of runCPU() calls, calculates the maximum number
+ * of cycles for each burst based on the last known effective CPU speed, and resets the nCyclesRecalc
+ * threshold counter.
+ */
+ this.setSpeed();
+ if (this.cmp) this.cmp.start(this.aCounts.msStartRun, this.getCycles());
+ this.flags.fRunning = true;
+ this.flags.fStarting = true;
+ if (this.chipset) this.chipset.start();
+ var controlRun = this.bindings["run"];
+ if (controlRun) controlRun.textContent = "Halt";
+ if (this.cmp) {
+ this.cmp.updateStatus(true);
+ if (fUpdateFocus) this.cmp.updateFocus(true);
+ }
+ }
+};
+
+/**
+ * stepCPU(nMinCycles)
+ *
+ * This will be implemented by the CPUState component.
+ *
+ * @this {CPU}
+ * @param {number} nMinCycles (0 implies a single-step, and therefore breakpoints should be ignored)
+ * @return {number} of cycles executed; 0 indicates that the last instruction was not executed
+ */
+CPU.prototype.stepCPU = function(nMinCycles)
+{
+ return 0;
+};
+
+/**
+ * stopCPU(fComplete)
+ *
+ * For use by any component that wants to stop the CPU.
+ *
+ * This similar to yieldCPU(), but it doesn't need to zero nCyclesNextYield to break out of runCPU();
+ * it simply needs to clear fRunning (well, "simply" may be oversimplifying a bit....)
+ *
+ * @this {CPU}
+ * @param {boolean} [fComplete]
+ */
+CPU.prototype.stopCPU = function(fComplete)
+{
+ this.isBusy(true);
+ this.nBurstCycles -= this.nStepCycles;
+ this.nStepCycles = 0;
+ this.addCycles(this.nRunCycles);
+ this.nRunCycles = 0;
+ if (this.flags.fRunning) {
+ this.flags.fRunning = false;
+ if (this.chipset) this.chipset.stop();
+ var controlRun = this.bindings["run"];
+ if (controlRun) controlRun.textContent = "Run";
+ }
+ this.flags.fComplete = fComplete;
+};
+
+/**
+ * updateCPU(fForce)
+ *
+ * This used to be performed at the end of every stepCPU(), but runCPU() -- which relies upon
+ * stepCPU() -- needed to have more control over when these updates are performed. However, for
+ * other callers of stepCPU(), such as the Debugger, the combination of stepCPU() + updateCPU()
+ * provides the old behavior.
+ *
+ * @this {CPU}
+ * @param {boolean} [fForce] (true to force a video update; used by the Debugger)
+ */
+CPU.prototype.updateCPU = function(fForce)
+{
+ if (this.cmp) {
+ this.cmp.updateVideo(-1);
+ this.cmp.updateStatus(fForce);
+ }
+};
+
+/**
+ * yieldCPU()
+ *
+ * Similar to stopCPU() with regard to how it resets various cycle countdown values, but the CPU
+ * remains in a "running" state.
+ *
+ * @this {CPU}
+ */
+CPU.prototype.yieldCPU = function()
+{
+ this.aCounts.nCyclesNextYield = 0; // this will break us out of runCPU(), once we break out of stepCPU()
+ this.nBurstCycles -= this.nStepCycles;
+ this.nStepCycles = 0; // this will break us out of stepCPU()
+ // if (DEBUG) this.nSnapCycles = this.nBurstCycles;
+ /*
+ * The Debugger calls yieldCPU() after every message() to ensure browser responsiveness, but it looks
+ * odd for those messages to show CPU state changes but for the CPU's own status display to not (ditto
+ * for the Video display), so I've added this call to try to keep things looking synchronized.
+ */
+ this.updateCPU();
+};
+
+if (NODE) module.exports = CPU;
diff --git a/modules/pc6502/lib/cpudef.js b/modules/pc6502/lib/cpudef.js
new file mode 100644
index 000000000..49ad464b7
--- /dev/null
+++ b/modules/pc6502/lib/cpudef.js
@@ -0,0 +1,92 @@
+/**
+ * @fileoverview Defines PC6502 CPU constants.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+var CPUDef = {
+ /*
+ * CPU model numbers (supported)
+ */
+ MODEL_6502: 6502,
+
+ /*
+ * This constant is used to mark points in the code where the physical address being returned
+ * is invalid and should not be used.
+ *
+ * In a 32-bit CPU, -1 (ie, 0xffffffff) could actually be a valid address, so consider changing
+ * ADDR_INVALID to NaN or null (which is also why all ADDR_INVALID tests should use strict equality
+ * operators).
+ *
+ * The main reason I'm NOT using NaN or null now is my concern that, by mixing non-numbers
+ * (specifically, values outside the range of signed 32-bit integers), performance may suffer.
+ *
+ * WARNING: Like many of the properties defined here, ADDR_INVALID is a common constant, which the
+ * Closure Compiler will happily inline (with or without @const annotations; in fact, I've yet to
+ * see a @const annotation EVER improve automatic inlining). However, if you don't make ABSOLUTELY
+ * certain that this file is included BEFORE the first reference to any of these properties, that
+ * automatic inlining will no longer occur.
+ */
+ ADDR_INVALID: -1,
+
+ /*
+ * Processor Status flag definitions (stored in regPS)
+ */
+ PS: {
+ SF: 0x80, // this.BIT_PN = 0x80; // N = sign
+ OF: 0x40, // this.BIT_PV = 0x40; // V = overflow
+ BF: 0x10, // this.BIT_PB = 0x10; // B = break
+ DF: 0x08, // this.BIT_PD = 0x08; // D = decimal
+ IF: 0x04, // this.BIT_PI = 0x04; // I = interrupt
+ ZF: 0x02, // this.BIT_PZ = 0x02; // Z = zero
+ CF: 0x01 // this.BIT_PC = 0x01; // C = carry
+ },
+ VECTOR: {
+ NMI: 0xFFFA, // this.VECTOR_NMI = 0xfffa;
+ RESET: 0xFFFC, // this.VECTOR_RESET = 0xfffc;
+ IRQ: 0xFFFE // this.VECTOR_IRQ = 0xfffe;
+ },
+ /*
+ * Opcode definitions
+ */
+ OPCODE: {
+ JSR: 0x20,
+ /*
+ * opSim operation codes
+ */
+ SIM: 0x02,
+ SIMOP_HLT: 0x00,
+ SIMOP_MSG: 0x01
+ // to be continued....
+ }
+};
+
+if (NODE) module.exports = CPUDef;
diff --git a/modules/pc6502/lib/cpuops.js b/modules/pc6502/lib/cpuops.js
new file mode 100644
index 000000000..de4f832d5
--- /dev/null
+++ b/modules/pc6502/lib/cpuops.js
@@ -0,0 +1,2208 @@
+/**
+ * @fileoverview Implements PC6502 opcode handlers.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var Messages = require("./messages");
+ var CPUDef = require("./CPUDef");
+}
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBRK = function()
+{ // opcode 0x00
+ // PC++;
+ this.regPC++;
+ // STACK(S--) = PCH;
+ this.abMem[this.regS--] = (this.regPC >> 8);
+ this.regS |= 0x100;
+ // STACK(S--) = PCL;
+ this.abMem[this.regS--] = (this.regPC & 0xff);
+ this.regS |= 0x100;
+ // B = 1;
+ this.regP |= 0x10;
+ // C = LAZY_C; Z = LAZY_Z; V = LAZY_V; N = LAZY_N;
+ this.regP = this.getRegP();
+ // STACK(S--) = P;
+ this.abMem[this.regS--] = this.regP;
+ this.regS |= 0x100;
+ // B = 0;
+ this.regP &= 0xef;
+ // EA = 0xFFFE;
+ this.regEA = 0xFFFE;
+ // PC = M;
+ this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAindx = function()
+{ // opcode 0x01
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAzp = function()
+{ // opcode 0x05
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opASLzp = function()
+{ // opcode 0x06
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // RC = ML << 1;
+ this.regRC = this.abMem[this.regEAWrite] << 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opPHP = function()
+{ // opcode 0x08
+ this.regP = this.getRegP();
+ // STACK(S--) = P;
+ this.abMem[this.regS--] = this.regP;
+ this.regS |= 0x100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAimm = function()
+{ // opcode 0x09
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opASLacc = function()
+{ // opcode 0x0a
+ // RC = A << 1;
+ this.regRC = this.regA << 1;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAabs = function()
+{ // opcode 0x0d
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opASLabs = function()
+{ // opcode 0x0e
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RC = ML << 1;
+ this.regRC = this.abMem[this.regEAWrite] << 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBPL = function()
+{ // opcode 0x10
+ // PC = PC + (LAZY_N == 0? SBYTE(PC) : 0) + 1;
+ this.regPC += (!(this.regRN & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAindy = function()
+{ // opcode 0x11
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAzpx = function()
+{ // opcode 0x15
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opASLzpx = function()
+{ // opcode 0x16
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // RC = ML << 1;
+ this.regRC = this.abMem[this.regEAWrite] << 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCLC = function()
+{ // opcode 0x18
+ // SET_LAZY_C(0);
+ this.regRC = 0x00;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAabsy = function()
+{ // opcode 0x19
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA |= this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opORAabsx = function()
+{ // opcode 0x1d
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // A = A | ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA |= this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opASLabsx = function()
+{ // opcode 0x1e
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // RC = ML << 1;
+ this.regRC = this.abMem[this.regEAWrite] << 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opJSRabs = function()
+{ // opcode 0x20
+ // EA = PC; PC += 1;
+ this.regEA = this.regPC++;
+ // STACK(S--) = PCH;
+ this.abMem[this.regS--] = (this.regPC >> 8);
+ this.regS |= 0x100;
+ // STACK(S--) = PCL;
+ this.abMem[this.regS--] = (this.regPC & 0xff);
+ this.regS |= 0x100;
+ // PC = M;
+ this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDindx = function()
+{ // opcode 0x21
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBITzp = function()
+{ // opcode 0x24
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // SET_LAZY_Z((A & ML) == 0);
+ this.regRZ = (this.regA & this.abMem[this.regEA]);
+ // SET_LAZY_N(ML7);
+ this.regRN = ((this.regRN & 0x7f) | (this.abMem[this.regEA] & 0x80));
+ // SET_LAZY_V(ML6);
+ this.regRV = 0; this.regRU = ((this.abMem[this.regEA] & 0x40)? 0x80 : 0x00);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDzp = function()
+{ // opcode 0x25
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opROLzp = function()
+{ // opcode 0x26
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RC = RC << 1;
+ this.regRC <<= 1;
+ // RCL0 = RCH1;
+ this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0));
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opPLP = function()
+{ // opcode 0x28
+ // P = STACK(++S);
+ this.regS = ((this.regS+1) & 0xff) | 0x100;
+ this.regP = this.abMem[this.regS];
+ // SET_LAZY_C(C);
+ this.regRC = ((this.regP & 0x01)? 0x0100 : 0);
+ // SET_LAZY_Z(Z);
+ this.regRZ = (!(this.regP & 0x02)? 0x01 : 0);
+ // SET_LAZY_N(N);
+ this.regRN = (this.regP & 0x80);
+ // SET_LAZY_V(V);
+ this.regRV = 0; this.regRU = ((this.regP & 0x40)? 0x80 : 0x00);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDimm = function()
+{ // opcode 0x29
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opROLacc = function()
+{ // opcode 0x2a
+ // RCL = A;
+ this.regRC = ((this.regRC & 0xff00) | this.regA);
+ // RC = RC << 1;
+ this.regRC <<= 1;
+ // RCL0 = RCH1;
+ this.regRC = ((this.regRC & 0xfffe) | ((this.regRC & 0x0200)? 0x0001 : 0));
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBITabs = function()
+{ // opcode 0x2c
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // SET_LAZY_Z((A & ML) == 0);
+ this.regRZ = (this.regA & this.abMem[this.regEA]);
+ // SET_LAZY_N(ML7);
+ this.regRN = ((this.regRN & 0x7f) | (this.abMem[this.regEA] & 0x80));
+ // SET_LAZY_V(ML6);
+ this.regRV = 0; this.regRU = ((this.abMem[this.regEA] & 0x40)? 0x80 : 0x00);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDabs = function()
+{ // opcode 0x2d
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opROLabs = function()
+{ // opcode 0x2e
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RC = RC << 1;
+ this.regRC <<= 1;
+ // RCL0 = RCH1;
+ this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0));
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBMI = function()
+{ // opcode 0x30
+ // PC = PC + (LAZY_N != 0? SBYTE(PC) : 0) + 1;
+ this.regPC += ((this.regRN & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDindy = function()
+{ // opcode 0x31
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDzpx = function()
+{ // opcode 0x35
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opROLzpx = function()
+{ // opcode 0x36
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RC = RC << 1;
+ this.regRC <<= 1;
+ // RCL0 = RCH1;
+ this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0));
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSEC = function()
+{ // opcode 0x38
+ // SET_LAZY_C(1);
+ this.regRC = 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDabsy = function()
+{ // opcode 0x39
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opANDabsx = function()
+{ // opcode 0x3d
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // A = A & ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opROLabsx = function()
+{ // opcode 0x3e
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RC = RC << 1;
+ this.regRC <<= 1;
+ // RCL0 = RCH1;
+ this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0));
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opRTI = function()
+{ // opcode 0x40
+ // P = STACK(++S);
+ this.regS = ((this.regS+1) & 0xff) | 0x100;
+ this.regP = this.abMem[this.regS];
+ // SET_LAZY_C(C);
+ this.regRC = ((this.regP & 0x01)? 0x0100 : 0);
+ // SET_LAZY_Z(Z);
+ this.regRZ = (!(this.regP & 0x02)? 0x01 : 0);
+ // SET_LAZY_N(N);
+ this.regRN = (this.regP & 0x80);
+ // SET_LAZY_V(V);
+ this.regRV = 0; this.regRU = ((this.regP & 0x40)? 0x80 : 0x00);
+ // PCL = STACK(++S);
+ // PCH = STACK(++S);
+ this.regS = ((this.regS+2) & 0xff) | 0x100;
+ this.regPC = (this.abMem[(this.regS-1) | 0x100]) | (this.abMem[this.regS] << 8);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORindx = function()
+{ // opcode 0x41
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // A = A ^ ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORzp = function()
+{ // opcode 0x45
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // A = A ^ ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLSRzp = function()
+{ // opcode 0x46
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // SET_LAZY_C(ML0);
+ this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0));
+ // ML = RCL = ML >> 1;
+ this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff);
+ // SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = (this.regRC & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opPHA = function()
+{ // opcode 0x48
+ // STACK(S--) = A;
+ this.abMem[this.regS--] = this.regA;
+ this.regS |= 0x100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORimm = function()
+{ // opcode 0x49
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // A = A ^ ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLSRacc = function()
+{ // opcode 0x4a
+ // SET_LAZY_C( A0);
+ this.regRC = ((this.regRC & 0xfeff) | ((this.regA & 0x01)? 0x0100 : 0));
+ // A = RCL = A >> 1;
+ this.regA = ((this.regRC = ((this.regRC & 0xff00) | (this.regA >> 1))) & 0xff);
+ // SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opJMPimm16 = function()
+{ // opcode 0x4c
+ // EA = PC;
+ this.regEA = this.regPC;
+ // PC += 2;
+ // this.regPC += 2;
+ // PC = M;
+ this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORabs = function()
+{ // opcode 0x4d
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // A = A ^ ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLSRabs = function()
+{ // opcode 0x4e
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // SET_LAZY_C(ML0);
+ this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0));
+ // ML = RCL = ML >> 1;
+ this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff);
+ // SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBVC = function()
+{ // opcode 0x50
+ // PC = PC + (LAZY_V == 0? SBYTE(PC) : 0) + 1;
+ this.regPC += (!((((this.regRV & 0xff) ^ this.regRU) ^ (this.regRV >> 1)) & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORindy = function()
+{ // opcode 0x51
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = this.abMem[this.regPC++];
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // A = A ^ ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORzpx = function()
+{ // opcode 0x55
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // A = A ^ ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLSRzpx = function()
+{ // opcode 0x56
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // SET_LAZY_C(ML0);
+ this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0));
+ // ML = RCL = ML >> 1;
+ this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff);
+ // SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = (this.regRC & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCLI = function()
+{ // opcode 0x58
+ // I = 0;
+ this.regP &= 0xfb;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORabsy = function()
+{ // opcode 0x59
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // A = A ^ ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opEORabsx = function()
+{ // opcode 0x5d
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // A = A ^ ML; SET_LAZY_NZ(A)
+ this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLSRabsx = function()
+{ // opcode 0x5e
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // SET_LAZY_C(ML0);
+ this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0));
+ // ML = RCL = ML >> 1;
+ this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff);
+ // SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opRTS = function()
+{ // opcode 0x60
+ // PCL = STACK(++S);
+ // PCH = STACK(++S);
+ // PC++;
+ this.regS = ((this.regS+2) & 0xff) | 0x100;
+ this.regPC = (((this.abMem[(this.regS-1) | 0x100])) | ((this.abMem[this.regS]) << 8)) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCindx = function()
+{ // opcode 0x61
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // RC = (A + ML + LAZY_C);
+ this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCindxBCD = function()
+{ // opcode 0x61
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCzp = function()
+{ // opcode 0x65
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // RC = (A + ML + LAZY_C);
+ this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCzpBCD = function()
+{ // opcode 0x65
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opRORzp = function()
+{ // opcode 0x66
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RCH1 = RCL0;
+ this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0));
+ // RC = RC >> 1;
+ this.regRC >>= 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opPLA = function()
+{ // opcode 0x68
+ // A = STACK(++S); SET_LAZY_NZ(A);
+ this.regS = ((this.regS+1) & 0xff) | 0x100;
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regS];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCimm = function()
+{ // opcode 0x69
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // RC = (A + ML + LAZY_C);
+ this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCimmBCD = function()
+{ // opcode 0x69
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opRORacc = function()
+{ // opcode 0x6a
+ // RCL = A;
+ this.regRC = ((this.regRC & 0xff00) | this.regA);
+ // RCH1 = RCL0;
+ this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0));
+ // RC = RC >> 1;
+ this.regRC >>= 1;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ *
+ * NOTE from Wikipedia: "The 6502's memory indirect jump instruction, JMP (), is partially broken.
+ * If is hex xxFF (i.e., any word ending in FF), the processor will not jump to the address stored in xxFF and xxFF+1 as expected,
+ * but rather the one defined by xxFF and xx00. This defect continued through the entire NMOS line, but was corrected in the CMOS derivatives."
+ */
+CPUDef.opJMPabs16 = function()
+{ // opcode 0x6c
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // PC = M;
+ this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCabs = function()
+{ // opcode 0x6d
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RC = (A + ML + LAZY_C);
+ this.regRC =(this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCabsBCD = function()
+{ // opcode 0x6d
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opRORabs = function()
+{ // opcode 0x6e
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RCH1 = RCL0;
+ this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0));
+ // RC = RC >> 1;
+ this.regRC >>= 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBVS = function()
+{ // opcode 0x70
+ // PC = PC + (LAZY_V != 0? SBYTE(PC) : 0) + 1;
+ this.regPC += (((((this.regRV & 0xff) ^ this.regRU) ^ (this.regRV >> 1)) & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCindy = function()
+{ // opcode 0x71
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // RC = (A + ML + LAZY_C);
+ this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCindyBCD = function()
+{ // opcode 0x71
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCzpx = function()
+{ // opcode 0x75
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // RC = (A + ML + LAZY_C);
+ this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCzpxBCD = function()
+{ // opcode 0x75
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opRORzpx = function()
+{ // opcode 0x76
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RCH1 = RCL0;
+ this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0));
+ // RC = RC >> 1;
+ this.regRC >>= 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSEI = function()
+{ // opcode 0x78
+ // I = 1;
+ this.regP |= 0x04;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCabsy = function()
+{ // opcode 0x79
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // RC = (A + ML + LAZY_C);
+ this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCabsyBCD = function()
+{ // opcode 0x79
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCabsx = function()
+{ // opcode 0x7d
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // RC = (A + ML + LAZY_C);
+ this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opADCabsxBCD = function()
+{ // opcode 0x7d
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // A = this.addBCD(A,ML);
+ this.regA = this.addBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opRORabsx = function()
+{ // opcode 0x7e
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // RCL = ML;
+ this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]);
+ // RCH1 = RCL0;
+ this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0));
+ // RC = RC >> 1;
+ this.regRC >>= 1;
+ // ML = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTAindx = function()
+{ // opcode 0x81
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEAWrite = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEAWrite = (this.abMem[this.regEAWrite] | (this.abMem[this.regEAWrite+1] << 8));
+ // ML = A;
+ this.abMem[this.regEAWrite] = this.regA;
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTYzp = function()
+{ // opcode 0x84
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // ML = Y;
+ this.abMem[this.regEAWrite] = this.regY;
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTAzp = function()
+{ // opcode 0x85
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // ML = A;
+ this.abMem[this.regEAWrite] = this.regA;
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTXzp = function()
+{ // opcode 0x86
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // ML = X;
+ this.abMem[this.regEAWrite] = this.regX;
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opDEY = function()
+{ // opcode 0x88
+ // Y = ((Y - 1) & 0xff);
+ this.regY = ((this.regY - 1) & 0xff);
+ // SET_LAZY_NZ(Y);
+ this.regRN = this.regRZ = (this.regY);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opTXA = function()
+{ // opcode 0x8a
+ // A = X; SET_LAZY_NZ(X);
+ this.regRN = this.regRZ = this.regA = this.regX;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTYabs = function()
+{ // opcode 0x8c
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // ML = Y;
+ this.abMem[this.regEAWrite] = this.regY;
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTAabs = function()
+{ // opcode 0x8d
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // ML = A;
+ this.abMem[this.regEAWrite] = this.regA;
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTXabs = function()
+{ // opcode 0x8e
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // ML = X;
+ this.abMem[this.regEAWrite] = this.regX;
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBCC = function()
+{ // opcode 0x90
+ // PC = PC + (LAZY_C == 0? SBYTE(PC) : 0) + 1;
+ this.regPC += (!(this.regRC & 0x0100)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTAindy = function()
+{ // opcode 0x91
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEAWrite = (this.abMem[this.regPC++]);
+ this.regEAWrite = (this.abMem[this.regEAWrite] | (this.abMem[this.regEAWrite+1] << 8)) + this.regY;
+ // ML = A;
+ this.abMem[this.regEAWrite] = this.regA;
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTYzpx = function()
+{ // opcode 0x94
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // ML = Y;
+ this.abMem[this.regEAWrite] = this.regY;
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTAzpx = function()
+{ // opcode 0x95
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // ML = A;
+ this.abMem[this.regEAWrite] = this.regA;
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTXzpy = function()
+{ // opcode 0x96
+ // EA = (BYTE(PC++)+Y) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regY) & 0xff;
+ // ML = X;
+ this.abMem[this.regEAWrite] = this.regX;
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opTYA = function()
+{ // opcode 0x98
+ // A = Y; SET_LAZY_NZ(Y);
+ this.regRN = this.regRZ = this.regA = this.regY;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTAabsy = function()
+{ // opcode 0x99
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // ML = A;
+ this.abMem[this.regEAWrite] = this.regA;
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opTXS = function()
+{ // opcode 0x9a
+ // S = X;
+ this.regS = this.regX | 0x100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSTAabsx = function()
+{ // opcode 0x9d
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // ML = A;
+ this.abMem[this.regEAWrite] = this.regA;
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDYimm = function()
+{ // opcode 0xa0
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // Y = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regY = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAindx = function()
+{ // opcode 0xa1
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // A = ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDXimm = function()
+{ // opcode 0xa2
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // X = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regX = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDYzp = function()
+{ // opcode 0xa4
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // Y = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regY = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAzp = function()
+{ // opcode 0xa5
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // A = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDXzp = function()
+{ // opcode 0xa6
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // X = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regX = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opTAY = function()
+{ // opcode 0xa8
+ // Y = A; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regY = this.regA;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAimm = function()
+{ // opcode 0xa9
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // A = ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opTAX = function()
+{ // opcode 0xaa
+ // X = A; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regX = this.regA;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDYabs = function()
+{ // opcode 0xac
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // Y = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regY = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAabs = function()
+{ // opcode 0xad
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // A = ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDXabs = function()
+{ // opcode 0xae
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // X = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regX = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBCS = function()
+{ // opcode 0xb0
+ // PC = PC + (LAZY_C != 0? SBYTE(PC) : 0) + 1;
+ this.regPC += ((this.regRC & 0x0100)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAindy = function()
+{ // opcode 0xb1
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // A = ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDYzpx = function()
+{ // opcode 0xb4
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // Y = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regY = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAzpx = function()
+{ // opcode 0xb5
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // A = ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDXzpy = function()
+{ // opcode 0xb6
+ // EA = (BYTE(PC++)+Y) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regY) & 0xff;
+ // X = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regX = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCLV = function()
+{ // opcode 0xb8
+ // SET_LAZY_V(0);
+ this.regRV = 0; this.regRU = 0;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAabsy = function()
+{ // opcode 0xb9
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // A = ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opTSX = function()
+{ // opcode 0xba
+ // X = S; SET_LAZY_NZ(S);
+ this.regRN = this.regRZ = this.regX = this.regS & 0xff;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDYabsx = function()
+{ // opcode 0xbc
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // Y = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regY = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDAabsx = function()
+{ // opcode 0xbd
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // A = ML; SET_LAZY_NZ(A);
+ this.regRN = this.regRZ = this.regA = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opLDXabsy = function()
+{ // opcode 0xbe
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // X = ML; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.regX = this.abMem[this.regEA];
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCPYimm = function()
+{ // opcode 0xc0
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // RC = Y - ML;
+ this.regRC = this.regY - this.abMem[this.regEA];
+ // SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = (this.regRC);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPindx = function()
+{ // opcode 0xc1
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCPYzp = function()
+{ // opcode 0xc4
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // RC = Y - ML;
+ this.regRC = this.regY - this.abMem[this.regEA];
+ // SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = (this.regRC);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPzp = function()
+{ // opcode 0xc5
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opDECzp = function()
+{ // opcode 0xc6
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // ML = ML - 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opINY = function()
+{ // opcode 0xc8
+ // Y = ((Y + 1) & 0xff);
+ this.regY = ((this.regY + 1) & 0xff);
+ // SET_LAZY_NZ(Y);
+ this.regRN = this.regRZ = (this.regY);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPimm = function()
+{ // opcode 0xc9
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opDEX = function()
+{ // opcode 0xca
+ // X = ((X - 1) & 0xff); SET_LAZY_NZ(X);
+ this.regRN = this.regRZ = this.regX = ((this.regX - 1) & 0xff);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCPYabs = function()
+{ // opcode 0xcc
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RC = Y - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = this.regY - this.abMem[this.regEA];
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPabs = function()
+{ // opcode 0xcd
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opDECabs = function()
+{ // opcode 0xce
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // ML = ML - 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBNE = function()
+{ // opcode 0xd0
+ // PC = PC + (LAZY_Z == 0? SBYTE(PC) : 0) + 1;
+ this.regPC += ((this.regRZ & 0xff)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPindy = function()
+{ // opcode 0xd1
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPzpx = function()
+{ // opcode 0xd5
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opDECzpx = function()
+{ // opcode 0xd6
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // ML = ML - 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCLD = function()
+{ // opcode 0xd8
+ // D = 0;
+ this.clearBCD();
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPabsy = function()
+{ // opcode 0xd9
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCMPabsx = function()
+{ // opcode 0xdd
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // RC = A - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opDECabsx = function()
+{ // opcode 0xde
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // ML = ML - 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCPXimm = function()
+{ // opcode 0xe0
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // RC = X - ML;
+ this.regRC = this.regX - this.abMem[this.regEA];
+ // SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = (this.regRC);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCindx = function()
+{ // opcode 0xe1
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCindxBCD = function()
+{ // opcode 0xe1
+ // EA = WORD((BYTE(PC++)+X) & 0xff);
+ this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff;
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8));
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCPXzp = function()
+{ // opcode 0xe4
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // RC = X - ML;
+ this.regRC = this.regX - this.abMem[this.regEA];
+ // SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = (this.regRC);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCzp = function()
+{ // opcode 0xe5
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCzpBCD = function()
+{ // opcode 0xe5
+ // EA = BYTE(PC++);
+ this.regEA = this.abMem[this.regPC++];
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opINCzp = function()
+{ // opcode 0xe6
+ // EA = BYTE(PC++);
+ this.regEAWrite = this.abMem[this.regPC++];
+ // ML = ML + 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opINX = function()
+{ // opcode 0xe8
+ // X = ((X + 1) & 0xff);
+ this.regX = ((this.regX + 1) & 0xff);
+ // SET_LAZY_NZ(X);
+ this.regRN = this.regRZ = (this.regX);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCimm = function()
+{ // opcode 0xe9
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCimmBCD = function()
+{ // opcode 0xe9
+ // EA = PC++;
+ this.regEA = this.regPC++;
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opNOP = function()
+{ // opcode 0xea
+ //
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opCPXabs = function()
+{ // opcode 0xec
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RC = X - ML; SET_LAZY_NZ(RC);
+ this.regRN = this.regRZ = this.regRC = this.regX - this.abMem[this.regEA];
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCabs = function()
+{ // opcode 0xed
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCabsBCD = function()
+{ // opcode 0xed
+ // EA = WORD(PC); PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opINCabs = function()
+{ // opcode 0xee
+ // EA = WORD(PC); PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ // ML = ML + 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opBEQ = function()
+{ // opcode 0xf0
+ // PC = PC + (LAZY_Z == 1? SBYTE(PC) : 0) + 1;
+ this.regPC += (!(this.regRZ & 0xff)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCindy = function()
+{ // opcode 0xf1
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCindyBCD = function()
+{ // opcode 0xf1
+ // EA = WORD(BYTE(PC++))+Y;
+ this.regEA = (this.abMem[this.regPC++]);
+ this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY;
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCzpx = function()
+{ // opcode 0xf5
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCzpxBCD = function()
+{ // opcode 0xf5
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opINCzpx = function()
+{ // opcode 0xf6
+ // EA = (BYTE(PC++)+X) & 0xff;
+ this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff;
+ // ML = ML + 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff);
+ // W = 1;
+ // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP)
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSED = function()
+{ // opcode 0xf8
+ // D = 1;
+ this.setBCD();
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCabsy = function()
+{ // opcode 0xf9
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCabsyBCD = function()
+{ // opcode 0xf9
+ // EA = WORD(PC)+Y; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY;
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCabsx = function()
+{ // opcode 0xfd
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // RC = (A - ML - !LAZY_C);
+ this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1));
+ // SET_LAZY_OV(A,ML,RC);
+ this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC;
+ // A = RCL; SET_LAZY_NZ(RCL);
+ this.regRN = this.regRZ = this.regA = (this.regRC & 0xff);
+ // SET_LAZY_C(!LAZY_C);
+ this.regRC ^= 0x0100;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSBCabsxBCD = function()
+{ // opcode 0xfd
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // A = this.subBCD(A,ML);
+ this.regA = this.subBCD(this.regA, this.abMem[this.regEA]);
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opINCabsx = function()
+{ // opcode 0xfe
+ // EA = WORD(PC)+X; PC += 2;
+ this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX;
+ // ML = ML + 1; SET_LAZY_NZ(ML);
+ this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff);
+ // W = 1;
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opSim = function()
+{
+ var addr;
+ var bSimOp = this.abMem[this.regPC++];
+ switch(bSimOp) {
+
+ case this.SIMOP_HLT:
+ this.println("HALT");
+ this.halt();
+ break;
+
+ case this.SIMOP_MSG:
+ addr = this.regPC; // currently we're using "inline" strings
+ // addr = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8));
+ var s = "";
+ while (addr < this.abMem.length) {
+ var b = this.abMem[addr++];
+ if (!b) break;
+ s += String.fromCharCode(b);
+ }
+ this.regPC = addr; // update regPC as long as we're doing "inline" strings
+ /*
+ * Before simply printing the string, what kinds of handy substitutions should we provide?
+ *
+ * eg: %A for this.regA, %X for this.regX, etc
+ */
+ s = s.replace(/%A/g, str.toHex(this.regA, 2)).replace(/%X/g, str.toHex(this.regX, 2)).replace(/%Y/g, str.toHex(this.regY, 2));
+ this.println(s);
+ /*
+ * To make printing "smoother", let's force a yield
+ */
+ this.yieldCPU();
+ break;
+
+ default:
+ this.regPC -= 2;
+ this.println("undefined opSim: " + str.toHexByte(bSimOp) + " at " + str.toHexWord(this.regPC));
+ this.halt();
+ }
+};
+
+/**
+ * @this {CPUState}
+ */
+CPUDef.opUndefined = function()
+{
+ var b = this.abMem[--this.regPC];
+ this.println("undefined opcode: " + str.toHexByte(b) + " at " + str.toHexWord(this.regPC));
+ this.halt();
+};
diff --git a/modules/pc6502/lib/cpustate.js b/modules/pc6502/lib/cpustate.js
new file mode 100644
index 000000000..8cc495f3b
--- /dev/null
+++ b/modules/pc6502/lib/cpustate.js
@@ -0,0 +1,1434 @@
+/**
+ * @fileoverview Implements the PC6502 CPU component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var web = require("../../shared/lib/weblib");
+ var Component = require("../../shared/lib/component");
+ var Messages = require("./messages");
+ var Memory = require("./memory");
+ var State = require("./state");
+ var CPU = require("./cpu");
+ var CPUDef = require("./cpudef");
+}
+
+/**
+ * CPUState(parmsCPU)
+ *
+ * The CPUState class uses the following (parmsCPU) properties:
+ *
+ * model: a string (eg, "8080") that should match one of the CPUDef.MODEL_* values
+ *
+ * This extends the CPU class and passes any remaining parmsCPU properties to the CPU class
+ * constructor, along with a default speed (cycles per second) based on the specified (or default)
+ * CPU model number.
+ *
+ * The CPUState class was initially written to simulate a 8080 microprocessor, although over time
+ * it may evolved to support other microprocessors (eg, the Zilog Z80).
+ *
+ * @constructor
+ * @extends CPU
+ * @param {Object} parmsCPU
+ */
+function CPUState(parmsCPU)
+{
+ this.model = +parmsCPU['model'] || CPUDef.MODEL_8080;
+
+ var nCyclesDefault = 0;
+ switch(this.model) {
+ case CPUDef.MODEL_8080:
+ default:
+ nCyclesDefault = 1000000;
+ break;
+ }
+
+ CPU.call(this, parmsCPU, nCyclesDefault);
+
+ /*
+ * Initialize processor operation to match the requested model
+ */
+ this.initProcessor();
+
+ /*
+ * A variety of stepCPU() state variables that don't strictly need to be initialized before the first
+ * stepCPU() call, but it's good form to do so.
+ */
+ this.resetCycles();
+ this.flags.fComplete = this.flags.fDebugCheck = false;
+
+ /*
+ * If there are no live registers to display, then updateStatus() can skip a bit....
+ */
+ this.cLiveRegs = 0;
+
+ /*
+ * Array of halt handlers, if any (see addHaltCheck)
+ */
+ this.afnHalt = [];
+ this.addrReset = 0x0000;
+
+ /*
+ * This initial resetRegs() call is important to create all the registers, so that if/when we call restore(),
+ * it will have something to fill in.
+ */
+ this.resetRegs();
+}
+
+Component.subclass(CPUState, CPU);
+
+/**
+ * addHaltCheck(fn)
+ *
+ * Records a function that will be called during HLT opcode processing.
+ *
+ * @this {CPUState}
+ * @param {function(number)} fn
+ */
+CPUState.prototype.addHaltCheck = function(fn)
+{
+ this.afnHalt.push(fn);
+};
+
+/**
+ * initProcessor()
+ *
+ * Interestingly, if I dynamically generate aOps as an array of functions bound to "this", using the bind()
+ * method, overall performance is worse. You would think that eliminating the need to use the call() method
+ * on every opcode function invocation would be helpful, but it's not. I'm not sure exactly why yet; perhaps
+ * a Closure Compiler optimization is defeated when generating the function array at run-time instead of at
+ * compile-time.
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.initProcessor = function()
+{
+ /*
+ * This 256-entry array of opcode functions is at the heart of the CPU engine: step(n).
+ *
+ * It might be worth trying a switch() statement instead, to see how the performance compares,
+ * but I suspect that will vary quite a bit across JavaScript engines; for now, I'm putting my
+ * money on array lookup.
+ */
+ this.aOpcodeFuncs = [
+ CPUDef.opBRK, // 0x00
+ CPUDef.opORAindx, // 0x01
+ CPUDef.opSim, // 0x02
+ CPUDef.opUndefined, // 0x03
+ CPUDef.opUndefined, // 0x04
+ CPUDef.opORAzp, // 0x05
+ CPUDef.opASLzp, // 0x06
+ CPUDef.opUndefined, // 0x07
+ CPUDef.opPHP, // 0x08
+ CPUDef.opORAimm, // 0x09
+ CPUDef.opASLacc, // 0x0a
+ CPUDef.opUndefined, // 0x0b
+ CPUDef.opUndefined, // 0x0c
+ CPUDef.opORAabs, // 0x0d
+ CPUDef.opASLabs, // 0x0e
+ CPUDef.opUndefined, // 0x0f
+ CPUDef.opBPL, // 0x10
+ CPUDef.opORAindy, // 0x11
+ CPUDef.opUndefined, // 0x12
+ CPUDef.opUndefined, // 0x13
+ CPUDef.opUndefined, // 0x14
+ CPUDef.opORAzpx, // 0x15
+ CPUDef.opASLzpx, // 0x16
+ CPUDef.opUndefined, // 0x17
+ CPUDef.opCLC, // 0x18
+ CPUDef.opORAabsy, // 0x19
+ CPUDef.opUndefined, // 0x1a
+ CPUDef.opUndefined, // 0x1b
+ CPUDef.opUndefined, // 0x1c
+ CPUDef.opORAabsx, // 0x1d
+ CPUDef.opASLabsx, // 0x1e
+ CPUDef.opUndefined, // 0x1f
+ CPUDef.opJSRabs, // 0x20
+ CPUDef.opANDindx, // 0x21
+ CPUDef.opUndefined, // 0x22
+ CPUDef.opUndefined, // 0x23
+ CPUDef.opBITzp, // 0x24
+ CPUDef.opANDzp, // 0x25
+ CPUDef.opROLzp, // 0x26
+ CPUDef.opUndefined, // 0x27
+ CPUDef.opPLP, // 0x28
+ CPUDef.opANDimm, // 0x29
+ CPUDef.opROLacc, // 0x2a
+ CPUDef.opUndefined, // 0x2b
+ CPUDef.opBITabs, // 0x2c
+ CPUDef.opANDabs, // 0x2d
+ CPUDef.opROLabs, // 0x2e
+ CPUDef.opUndefined, // 0x2f
+ CPUDef.opBMI, // 0x30
+ CPUDef.opANDindy, // 0x31
+ CPUDef.opUndefined, // 0x32
+ CPUDef.opUndefined, // 0x33
+ CPUDef.opUndefined, // 0x34
+ CPUDef.opANDzpx, // 0x35
+ CPUDef.opROLzpx, // 0x36
+ CPUDef.opUndefined, // 0x37
+ CPUDef.opSEC, // 0x38
+ CPUDef.opANDabsy, // 0x39
+ CPUDef.opUndefined, // 0x3a
+ CPUDef.opUndefined, // 0x3b
+ CPUDef.opUndefined, // 0x3c
+ CPUDef.opANDabsx, // 0x3d
+ CPUDef.opROLabsx, // 0x3e
+ CPUDef.opUndefined, // 0x3f
+ CPUDef.opRTI, // 0x40
+ CPUDef.opEORindx, // 0x41
+ CPUDef.opUndefined, // 0x42
+ CPUDef.opUndefined, // 0x43
+ CPUDef.opUndefined, // 0x44
+ CPUDef.opEORzp, // 0x45
+ CPUDef.opLSRzp, // 0x46
+ CPUDef.opUndefined, // 0x47
+ CPUDef.opPHA, // 0x48
+ CPUDef.opEORimm, // 0x49
+ CPUDef.opLSRacc, // 0x4a
+ CPUDef.opUndefined, // 0x4b
+ CPUDef.opJMPimm16, // 0x4c
+ CPUDef.opEORabs, // 0x4d
+ CPUDef.opLSRabs, // 0x4e
+ CPUDef.opUndefined, // 0x4f
+ CPUDef.opBVC, // 0x50
+ CPUDef.opEORindy, // 0x51
+ CPUDef.opUndefined, // 0x52
+ CPUDef.opUndefined, // 0x53
+ CPUDef.opUndefined, // 0x54
+ CPUDef.opEORzpx, // 0x55
+ CPUDef.opLSRzpx, // 0x56
+ CPUDef.opUndefined, // 0x57
+ CPUDef.opCLI, // 0x58
+ CPUDef.opEORabsy, // 0x59
+ CPUDef.opUndefined, // 0x5a
+ CPUDef.opUndefined, // 0x5b
+ CPUDef.opUndefined, // 0x5c
+ CPUDef.opEORabsx, // 0x5d
+ CPUDef.opLSRabsx, // 0x5e
+ CPUDef.opUndefined, // 0x5f
+ CPUDef.opRTS, // 0x60
+ CPUDef.opADCindx, // 0x61
+ CPUDef.opUndefined, // 0x62
+ CPUDef.opUndefined, // 0x63
+ CPUDef.opUndefined, // 0x64
+ CPUDef.opADCzp, // 0x65
+ CPUDef.opRORzp, // 0x66
+ CPUDef.opUndefined, // 0x67
+ CPUDef.opPLA, // 0x68
+ CPUDef.opADCimm, // 0x69
+ CPUDef.opRORacc, // 0x6a
+ CPUDef.opUndefined, // 0x6b
+ CPUDef.opJMPabs16, // 0x6c
+ CPUDef.opADCabs, // 0x6d
+ CPUDef.opRORabs, // 0x6e
+ CPUDef.opUndefined, // 0x6f
+ CPUDef.opBVS, // 0x70
+ CPUDef.opADCindy, // 0x71
+ CPUDef.opUndefined, // 0x72
+ CPUDef.opUndefined, // 0x73
+ CPUDef.opUndefined, // 0x74
+ CPUDef.opADCzpx, // 0x75
+ CPUDef.opRORzpx, // 0x76
+ CPUDef.opUndefined, // 0x77
+ CPUDef.opSEI, // 0x78
+ CPUDef.opADCabsy, // 0x79
+ CPUDef.opUndefined, // 0x7a
+ CPUDef.opUndefined, // 0x7b
+ CPUDef.opUndefined, // 0x7c
+ CPUDef.opADCabsx, // 0x7d
+ CPUDef.opRORabsx, // 0x7e
+ CPUDef.opUndefined, // 0x7f
+ CPUDef.opUndefined, // 0x80
+ CPUDef.opSTAindx, // 0x81
+ CPUDef.opUndefined, // 0x82
+ CPUDef.opUndefined, // 0x83
+ CPUDef.opSTYzp, // 0x84
+ CPUDef.opSTAzp, // 0x85
+ CPUDef.opSTXzp, // 0x86
+ CPUDef.opUndefined, // 0x87
+ CPUDef.opDEY, // 0x88
+ CPUDef.opUndefined, // 0x89
+ CPUDef.opTXA, // 0x8a
+ CPUDef.opUndefined, // 0x8b
+ CPUDef.opSTYabs, // 0x8c
+ CPUDef.opSTAabs, // 0x8d
+ CPUDef.opSTXabs, // 0x8e
+ CPUDef.opUndefined, // 0x8f
+ CPUDef.opBCC, // 0x90
+ CPUDef.opSTAindy, // 0x91
+ CPUDef.opUndefined, // 0x92
+ CPUDef.opUndefined, // 0x93
+ CPUDef.opSTYzpx, // 0x94
+ CPUDef.opSTAzpx, // 0x95
+ CPUDef.opSTXzpy, // 0x96
+ CPUDef.opUndefined, // 0x97
+ CPUDef.opTYA, // 0x98
+ CPUDef.opSTAabsy, // 0x99
+ CPUDef.opTXS, // 0x9a
+ CPUDef.opUndefined, // 0x9b
+ CPUDef.opUndefined, // 0x9c
+ CPUDef.opSTAabsx, // 0x9d
+ CPUDef.opUndefined, // 0x9e
+ CPUDef.opUndefined, // 0x9f
+ CPUDef.opLDYimm, // 0xa0
+ CPUDef.opLDAindx, // 0xa1
+ CPUDef.opLDXimm, // 0xa2
+ CPUDef.opUndefined, // 0xa3
+ CPUDef.opLDYzp, // 0xa4
+ CPUDef.opLDAzp, // 0xa5
+ CPUDef.opLDXzp, // 0xa6
+ CPUDef.opUndefined, // 0xa7
+ CPUDef.opTAY, // 0xa8
+ CPUDef.opLDAimm, // 0xa9
+ CPUDef.opTAX, // 0xaa
+ CPUDef.opUndefined, // 0xab
+ CPUDef.opLDYabs, // 0xac
+ CPUDef.opLDAabs, // 0xad
+ CPUDef.opLDXabs, // 0xae
+ CPUDef.opUndefined, // 0xaf
+ CPUDef.opBCS, // 0xb0
+ CPUDef.opLDAindy, // 0xb1
+ CPUDef.opUndefined, // 0xb2
+ CPUDef.opUndefined, // 0xb3
+ CPUDef.opLDYzpx, // 0xb4
+ CPUDef.opLDAzpx, // 0xb5
+ CPUDef.opLDXzpy, // 0xb6
+ CPUDef.opUndefined, // 0xb7
+ CPUDef.opCLV, // 0xb8
+ CPUDef.opLDAabsy, // 0xb9
+ CPUDef.opTSX, // 0xba
+ CPUDef.opUndefined, // 0xbb
+ CPUDef.opLDYabsx, // 0xbc
+ CPUDef.opLDAabsx, // 0xbd
+ CPUDef.opLDXabsy, // 0xbe
+ CPUDef.opUndefined, // 0xbf
+ CPUDef.opCPYimm, // 0xc0
+ CPUDef.opCMPindx, // 0xc1
+ CPUDef.opUndefined, // 0xc2
+ CPUDef.opUndefined, // 0xc3
+ CPUDef.opCPYzp, // 0xc4
+ CPUDef.opCMPzp, // 0xc5
+ CPUDef.opDECzp, // 0xc6
+ CPUDef.opUndefined, // 0xc7
+ CPUDef.opINY, // 0xc8
+ CPUDef.opCMPimm, // 0xc9
+ CPUDef.opDEX, // 0xca
+ CPUDef.opUndefined, // 0xcb
+ CPUDef.opCPYabs, // 0xcc
+ CPUDef.opCMPabs, // 0xcd
+ CPUDef.opDECabs, // 0xce
+ CPUDef.opUndefined, // 0xcf
+ CPUDef.opBNE, // 0xd0
+ CPUDef.opCMPindy, // 0xd1
+ CPUDef.opUndefined, // 0xd2
+ CPUDef.opUndefined, // 0xd3
+ CPUDef.opUndefined, // 0xd4
+ CPUDef.opCMPzpx, // 0xd5
+ CPUDef.opDECzpx, // 0xd6
+ CPUDef.opUndefined, // 0xd7
+ CPUDef.opCLD, // 0xd8
+ CPUDef.opCMPabsy, // 0xd9
+ CPUDef.opUndefined, // 0xda
+ CPUDef.opUndefined, // 0xdb
+ CPUDef.opUndefined, // 0xdc
+ CPUDef.opCMPabsx, // 0xdd
+ CPUDef.opDECabsx, // 0xde
+ CPUDef.opUndefined, // 0xdf
+ CPUDef.opCPXimm, // 0xe0
+ CPUDef.opSBCindx, // 0xe1
+ CPUDef.opUndefined, // 0xe2
+ CPUDef.opUndefined, // 0xe3
+ CPUDef.opCPXzp, // 0xe4
+ CPUDef.opSBCzp, // 0xe5
+ CPUDef.opINCzp, // 0xe6
+ CPUDef.opUndefined, // 0xe7
+ CPUDef.opINX, // 0xe8
+ CPUDef.opSBCimm, // 0xe9
+ CPUDef.opNOP, // 0xea
+ CPUDef.opUndefined, // 0xeb
+ CPUDef.opCPXabs, // 0xec
+ CPUDef.opSBCabs, // 0xed
+ CPUDef.opINCabs, // 0xee
+ CPUDef.opUndefined, // 0xef
+ CPUDef.opBEQ, // 0xf0
+ CPUDef.opSBCindy, // 0xf1
+ CPUDef.opUndefined, // 0xf2
+ CPUDef.opUndefined, // 0xf3
+ CPUDef.opUndefined, // 0xf4
+ CPUDef.opSBCzpx, // 0xf5
+ CPUDef.opINCzpx, // 0xf6
+ CPUDef.opUndefined, // 0xf7
+ CPUDef.opSED, // 0xf8
+ CPUDef.opSBCabsy, // 0xf9
+ CPUDef.opUndefined, // 0xfa
+ CPUDef.opUndefined, // 0xfb
+ CPUDef.opUndefined, // 0xfc
+ CPUDef.opSBCabsx, // 0xfd
+ CPUDef.opINCabsx, // 0xfe
+ CPUDef.opUndefined // 0xff
+ ];
+};
+
+/**
+ * reset()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.reset = function()
+{
+ if (this.flags.fRunning) this.stopCPU();
+ this.resetRegs();
+ this.resetCycles();
+ this.clearError(); // clear any fatal error/exception that setError() may have flagged
+ this.parent.reset.call(this);
+};
+
+/**
+ * resetRegs()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.resetRegs = function()
+{
+ this.regA = 0;
+ this.regB = 0;
+ this.regC = 0;
+ this.regD = 0;
+ this.regE = 0;
+ this.regH = 0;
+ this.regL = 0;
+ this.setSP(0);
+ this.setPC(this.addrReset);
+
+ /*
+ * This resets the Processor Status flags (regPS), along with all the internal "result registers".
+ */
+ this.setPS(0);
+
+ /*
+ * intFlags contains some internal states we use to indicate whether a hardware interrupt (INTFLAG.INTR) or
+ * Trap software interrupt (INTR.TRAP) has been requested, as well as when we're in a "HLT" state (INTFLAG.HALT)
+ * that requires us to wait for a hardware interrupt (INTFLAG.INTR) before continuing execution.
+ */
+ this.intFlags = CPUDef.INTFLAG.NONE;
+};
+
+/**
+ * setReset(addr)
+ *
+ * @this {CPUState}
+ * @param {number} addr
+ */
+CPUState.prototype.setReset = function(addr)
+{
+ this.addrReset = addr;
+ this.setPC(addr);
+};
+
+/**
+ * getChecksum()
+ *
+ * @this {CPUState}
+ * @return {number} a 32-bit summation of key elements of the current CPU state (used by the CPU checksum code)
+ */
+CPUState.prototype.getChecksum = function()
+{
+ var sum = (this.regA + this.regB + this.regC + this.regD + this.regE + this.regH + this.regL)|0;
+ sum = (sum + this.getSP() + this.getPC() + this.getPS())|0;
+ return sum;
+};
+
+/**
+ * save()
+ *
+ * This implements save support for the CPUState component.
+ *
+ * @this {CPUState}
+ * @return {Object|null}
+ */
+CPUState.prototype.save = function()
+{
+ var state = new State(this);
+ state.set(0, [this.regA, this.regB, this.regC, this.regD, this.regE, this.regH, this.regL, this.getSP(), this.getPC(), this.getPS()]);
+ state.set(1, [this.intFlags, this.nTotalCycles, this.getSpeed()]);
+ state.set(2, this.bus.saveMemory());
+ return state.data();
+};
+
+/**
+ * restore(data)
+ *
+ * This implements restore support for the CPUState component.
+ *
+ * @this {CPUState}
+ * @param {Object} data
+ * @return {boolean} true if restore successful, false if not
+ */
+CPUState.prototype.restore = function(data)
+{
+ var a = data[0];
+ this.regA = a[0];
+ this.regB = a[1];
+ this.regC = a[2];
+ this.regD = a[3];
+ this.regE = a[4];
+ this.regH = a[5];
+ this.regL = a[6];
+ this.setSP(a[7]);
+ this.setPC(a[8]);
+ this.setPS(a[9]);
+ a = data[1];
+ this.intFlags = a[0];
+ this.nTotalCycles = a[1];
+ this.setSpeed(a[3]);
+ return this.bus.restoreMemory(data[2]);
+};
+
+/**
+ * setBinding(sHTMLType, sBinding, control, sValue)
+ *
+ * @this {CPUState}
+ * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
+ * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "AX")
+ * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
+ * @param {string} [sValue] optional data value
+ * @return {boolean} true if binding was successful, false if unrecognized binding request
+ */
+CPUState.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
+{
+ var fBound = false;
+ switch (sBinding) {
+ case "A":
+ case "B":
+ case "C":
+ case "BC":
+ case "D":
+ case "E":
+ case "DE":
+ case "H":
+ case "L":
+ case "HL":
+ case "SP":
+ case "PC":
+ case "PS":
+ case "IF":
+ case "SF":
+ case "ZF":
+ case "AF":
+ case "PF":
+ case "CF":
+ this.bindings[sBinding] = control;
+ this.cLiveRegs++;
+ fBound = true;
+ break;
+ default:
+ fBound = this.parent.setBinding.call(this, sHTMLType, sBinding, control);
+ break;
+ }
+ return fBound;
+};
+
+/**
+ * getBC()
+ *
+ * @this {CPUState}
+ * @return {number}
+ */
+CPUState.prototype.getBC = function()
+{
+ return (this.regB << 8) | this.regC;
+};
+
+/**
+ * setBC(w)
+ *
+ * @this {CPUState}
+ * @param {number} w
+ */
+CPUState.prototype.setBC = function(w)
+{
+ this.regB = (w >> 8) & 0xff;
+ this.regC = w & 0xff;
+};
+
+/**
+ * getDE()
+ *
+ * @this {CPUState}
+ * @return {number}
+ */
+CPUState.prototype.getDE = function()
+{
+ return (this.regD << 8) | this.regE;
+};
+
+/**
+ * setDE(w)
+ *
+ * @this {CPUState}
+ * @param {number} w
+ */
+CPUState.prototype.setDE = function(w)
+{
+ this.regD = (w >> 8) & 0xff;
+ this.regE = w & 0xff;
+};
+
+/**
+ * getHL()
+ *
+ * @this {CPUState}
+ * @return {number}
+ */
+CPUState.prototype.getHL = function()
+{
+ return (this.regH << 8) | this.regL;
+};
+
+/**
+ * setHL(w)
+ *
+ * @this {CPUState}
+ * @param {number} w
+ */
+CPUState.prototype.setHL = function(w)
+{
+ this.regH = (w >> 8) & 0xff;
+ this.regL = w & 0xff;
+};
+
+/**
+ * getSP()
+ *
+ * @this {CPUState}
+ * @return {number}
+ */
+CPUState.prototype.getSP = function()
+{
+ return this.regSP;
+};
+
+/**
+ * setSP(off)
+ *
+ * @this {CPUState}
+ * @param {number} off
+ */
+CPUState.prototype.setSP = function(off)
+{
+ this.regSP = off & 0xffff;
+};
+
+/**
+ * getPC()
+ *
+ * @this {CPUState}
+ * @return {number}
+ */
+CPUState.prototype.getPC = function()
+{
+ return this.regPC;
+};
+
+/**
+ * offPC()
+ *
+ * @this {CPUState}
+ * @param {number} off
+ * @return {number}
+ */
+CPUState.prototype.offPC = function(off)
+{
+ return (this.regPC + off) & 0xffff;
+};
+
+/**
+ * setPC(off)
+ *
+ * @this {CPUState}
+ * @param {number} off
+ */
+CPUState.prototype.setPC = function(off)
+{
+ this.regPC = off & 0xffff;
+};
+
+/**
+ * clearCF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.clearCF = function()
+{
+ this.resultZeroCarry &= 0xff;
+};
+
+/**
+ * getCF()
+ *
+ * @this {CPUState}
+ * @return {number} 0 or 1 (CPUDef.PS.CF)
+ */
+CPUState.prototype.getCF = function()
+{
+ return (this.resultZeroCarry & 0x100)? CPUDef.PS.CF : 0;
+};
+
+/**
+ * setCF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.setCF = function()
+{
+ this.resultZeroCarry |= 0x100;
+};
+
+/**
+ * updateCF(CF)
+ *
+ * @this {CPUState}
+ * @param {number} CF (0x000 or 0x100)
+ */
+CPUState.prototype.updateCF = function(CF)
+{
+ this.resultZeroCarry = (this.resultZeroCarry & 0xff) | CF;
+};
+
+/**
+ * clearPF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.clearPF = function()
+{
+ if (this.getPF()) this.resultParitySign ^= 0x1;
+};
+
+/**
+ * getPF()
+ *
+ * @this {CPUState}
+ * @return {number} 0 or CPUDef.PS.PF
+ */
+CPUState.prototype.getPF = function()
+{
+ return (CPUDef.PARITY[this.resultParitySign & 0xff])? CPUDef.PS.PF : 0;
+};
+
+/**
+ * setPF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.setPF = function()
+{
+ if (!this.getPF()) this.resultParitySign ^= 0x1;
+};
+
+/**
+ * clearAF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.clearAF = function()
+{
+ this.resultAuxOverflow = (this.resultParitySign & 0x10) | (this.resultAuxOverflow & ~0x10);
+};
+
+/**
+ * getAF()
+ *
+ * @this {CPUState}
+ * @return {number} 0 or CPUDef.PS.AF
+ */
+CPUState.prototype.getAF = function()
+{
+ return ((this.resultParitySign ^ this.resultAuxOverflow) & 0x10)? CPUDef.PS.AF : 0;
+};
+
+/**
+ * setAF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.setAF = function()
+{
+ this.resultAuxOverflow = (~this.resultParitySign & 0x10) | (this.resultAuxOverflow & ~0x10);
+};
+
+/**
+ * clearZF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.clearZF = function()
+{
+ this.resultZeroCarry |= 0xff;
+};
+
+/**
+ * getZF()
+ *
+ * @this {CPUState}
+ * @return {number} 0 or CPUDef.PS.ZF
+ */
+CPUState.prototype.getZF = function()
+{
+ return (this.resultZeroCarry & 0xff)? 0 : CPUDef.PS.ZF;
+};
+
+/**
+ * setZF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.setZF = function()
+{
+ this.resultZeroCarry &= ~0xff;
+};
+
+/**
+ * clearSF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.clearSF = function()
+{
+ if (this.getSF()) this.resultParitySign ^= 0xc0;
+};
+
+/**
+ * getSF()
+ *
+ * @this {CPUState}
+ * @return {number} 0 or CPUDef.PS.SF
+ */
+CPUState.prototype.getSF = function()
+{
+ return (this.resultParitySign & 0x80)? CPUDef.PS.SF : 0;
+};
+
+/**
+ * setSF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.setSF = function()
+{
+ if (!this.getSF()) this.resultParitySign ^= 0xc0;
+};
+
+/**
+ * clearIF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.clearIF = function()
+{
+ this.regPS &= ~CPUDef.PS.IF;
+};
+
+/**
+ * getIF()
+ *
+ * @this {CPUState}
+ * @return {number} 0 or CPUDef.PS.IF
+ */
+CPUState.prototype.getIF = function()
+{
+ return (this.regPS & CPUDef.PS.IF);
+};
+
+/**
+ * setIF()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.setIF = function()
+{
+ this.regPS |= CPUDef.PS.IF;
+};
+
+/**
+ * getPS()
+ *
+ * @this {CPUState}
+ * @return {number}
+ */
+CPUState.prototype.getPS = function()
+{
+ return (this.regPS & ~CPUDef.PS.RESULT) | (this.getSF() | this.getZF() | this.getAF() | this.getPF() | this.getCF());
+};
+
+/**
+ * setPS(regPS)
+ *
+ * @this {CPUState}
+ * @param {number} regPS
+ */
+CPUState.prototype.setPS = function(regPS)
+{
+ this.resultZeroCarry = this.resultParitySign = this.resultAuxOverflow = 0;
+ if (regPS & CPUDef.PS.CF) this.resultZeroCarry |= 0x100;
+ if (!(regPS & CPUDef.PS.PF)) this.resultParitySign |= 0x01;
+ if (regPS & CPUDef.PS.AF) this.resultAuxOverflow |= 0x10;
+ if (!(regPS & CPUDef.PS.ZF)) this.resultZeroCarry |= 0xff;
+ if (regPS & CPUDef.PS.SF) this.resultParitySign ^= 0xc0;
+ this.regPS = (this.regPS & ~(CPUDef.PS.RESULT | CPUDef.PS.INTERNAL)) | (regPS & CPUDef.PS.INTERNAL) | CPUDef.PS.SET;
+ Component.assert((regPS & CPUDef.PS.RESULT) == (this.getPS() & CPUDef.PS.RESULT));
+};
+
+/**
+ * getPSW()
+ *
+ * @this {CPUState}
+ * @return {number}
+ */
+CPUState.prototype.getPSW = function()
+{
+ return (this.getPS() & CPUDef.PS.MASK) | (this.regA << 8);
+};
+
+/**
+ * setPSW(w)
+ *
+ * @this {CPUState}
+ * @param {number} w
+ */
+CPUState.prototype.setPSW = function(w)
+{
+ this.setPS((w & CPUDef.PS.MASK) | (this.regPS & ~CPUDef.PS.MASK));
+ this.regA = w >> 8;
+};
+
+/**
+ * addByte(src)
+ *
+ * @this {CPUState}
+ * @param {number} src
+ * @return {number} regA + src
+ */
+CPUState.prototype.addByte = function(src)
+{
+ this.resultAuxOverflow = this.regA ^ src;
+ return this.resultParitySign = (this.resultZeroCarry = this.regA + src) & 0xff;
+};
+
+/**
+ * addByteCarry(src)
+ *
+ * @this {CPUState}
+ * @param {number} src
+ * @return {number} regA + src + carry
+ */
+CPUState.prototype.addByteCarry = function(src)
+{
+ this.resultAuxOverflow = this.regA ^ src;
+ return this.resultParitySign = (this.resultZeroCarry = this.regA + src + ((this.resultZeroCarry & 0x100)? 1 : 0)) & 0xff;
+};
+
+/**
+ * andByte(src)
+ *
+ * Ordinarily, one would expect the Auxiliary Carry flag (AF) to be clear after this operation,
+ * but apparently the 8080 will set AF if bit 3 in either operand is set.
+ *
+ * @this {CPUState}
+ * @param {number} src
+ * @return {number} regA & src
+ */
+CPUState.prototype.andByte = function(src)
+{
+ this.resultZeroCarry = this.resultParitySign = this.resultAuxOverflow = this.regA & src;
+ if ((this.regA | src) & 0x8) this.resultAuxOverflow ^= 0x10; // set AF by inverting bit 4 in resultAuxOverflow
+ return this.resultZeroCarry;
+};
+
+/**
+ * decByte(b)
+ *
+ * We perform this operation using 8-bit two's complement arithmetic, by negating and then adding
+ * the implied src of 1. This appears to mimic how the 8080 manages the Auxiliary Carry flag (AF).
+ *
+ * @this {CPUState}
+ * @param {number} b
+ * @return {number}
+ */
+CPUState.prototype.decByte = function(b)
+{
+ this.resultAuxOverflow = b ^ 0xff;
+ b = this.resultParitySign = (b + 0xff) & 0xff;
+ this.resultZeroCarry = (this.resultZeroCarry & ~0xff) | b;
+ return b;
+};
+
+/**
+ * incByte(b)
+ *
+ * @this {CPUState}
+ * @param {number} b
+ * @return {number}
+ */
+CPUState.prototype.incByte = function(b)
+{
+ this.resultAuxOverflow = b;
+ b = this.resultParitySign = (b + 1) & 0xff;
+ this.resultZeroCarry = (this.resultZeroCarry & ~0xff) | b;
+ return b;
+};
+
+/**
+ * orByte(src)
+ *
+ * @this {CPUState}
+ * @param {number} src
+ * @return {number} regA | src
+ */
+CPUState.prototype.orByte = function(src)
+{
+ return this.resultParitySign = this.resultZeroCarry = this.resultAuxOverflow = this.regA | src;
+};
+
+/**
+ * subByte(src)
+ *
+ * We perform this operation using 8-bit two's complement arithmetic, by inverting src, adding
+ * src + 1, and then inverting the resulting carry (resultZeroCarry ^ 0x100). This appears to mimic
+ * how the 8080 manages the Auxiliary Carry flag (AF).
+ *
+ * This function is also used as a cmpByte() function; compare instructions simply ignore the
+ * return value.
+ *
+ * Example: A=66, SUI $10
+ *
+ * If we created the two's complement of 0x10 by negating it, there would just be one addition:
+ *
+ * 0110 0110 (0x66)
+ * + 1111 0000 (0xF0) (ie, -0x10)
+ * ---------
+ * 1 0101 0110 (0x56)
+ *
+ * But in order to mimic the 8080's AF flag, we must perform the two's complement of src in two steps,
+ * inverting it before the add, and then incrementing after the add; eg:
+ *
+ * 0110 0110 (0x66)
+ * + 1110 1111 (0xEF) (ie, ~0x10)
+ * ---------
+ * 1 0101 0101 (0x55)
+ * + 0000 0001 (0x01)
+ * ---------
+ * 1 0101 0110 (0x56)
+ *
+ * @this {CPUState}
+ * @param {number} src
+ * @return {number} regA - src
+ */
+CPUState.prototype.subByte = function(src)
+{
+ src ^= 0xff;
+ this.resultAuxOverflow = this.regA ^ src;
+ return this.resultParitySign = (this.resultZeroCarry = (this.regA + src + 1) ^ 0x100) & 0xff;
+};
+
+/**
+ * subByteBorrow(src)
+ *
+ * We perform this operation using 8-bit two's complement arithmetic, using logic similar to subByte(),
+ * but changing the final increment to a conditional increment, because if the Carry flag (CF) is set, then
+ * we don't need to perform the increment at all.
+ *
+ * This mimics the behavior of subByte() when the Carry flag (CF) is clear, and hopefully also mimics how the
+ * 8080 manages the Auxiliary Carry flag (AF) when the Carry flag (CF) is set.
+ *
+ * @this {CPUState}
+ * @param {number} src
+ * @return {number} regA - src - carry
+ */
+CPUState.prototype.subByteBorrow = function(src)
+{
+ src ^= 0xff;
+ this.resultAuxOverflow = this.regA ^ src;
+ return this.resultParitySign = (this.resultZeroCarry = (this.regA + src + ((this.resultZeroCarry & 0x100)? 0 : 1)) ^ 0x100) & 0xff;
+};
+
+/**
+ * xorByte(src)
+ *
+ * @this {CPUState}
+ * @param {number} src
+ * @return {number} regA ^ src
+ */
+CPUState.prototype.xorByte = function(src)
+{
+ return this.resultParitySign = this.resultZeroCarry = this.resultAuxOverflow = this.regA ^ src;
+};
+
+/**
+ * getByte(addr)
+ *
+ * @this {CPUState}
+ * @param {number} addr is a linear address
+ * @return {number} byte (8-bit) value at that address
+ */
+CPUState.prototype.getByte = function(addr)
+{
+ return this.bus.getByte(addr);
+};
+
+/**
+ * getWord(addr)
+ *
+ * @this {CPUState}
+ * @param {number} addr is a linear address
+ * @return {number} word (16-bit) value at that address
+ */
+CPUState.prototype.getWord = function(addr)
+{
+ return this.bus.getShort(addr);
+};
+
+/**
+ * setByte(addr, b)
+ *
+ * @this {CPUState}
+ * @param {number} addr is a linear address
+ * @param {number} b is the byte (8-bit) value to write (which we truncate to 8 bits; required by opSTOSb)
+ */
+CPUState.prototype.setByte = function(addr, b)
+{
+ this.bus.setByte(addr, b);
+};
+
+/**
+ * setWord(addr, w)
+ *
+ * @this {CPUState}
+ * @param {number} addr is a linear address
+ * @param {number} w is the word (16-bit) value to write (which we truncate to 16 bits to be safe)
+ */
+CPUState.prototype.setWord = function(addr, w)
+{
+ this.bus.setShort(addr, w);
+};
+
+/**
+ * getPCByte()
+ *
+ * @this {CPUState}
+ * @return {number} byte at the current PC; PC advanced by 1
+ */
+CPUState.prototype.getPCByte = function()
+{
+ var b = this.getByte(this.regPC);
+ this.setPC(this.regPC + 1);
+ return b;
+};
+
+/**
+ * getPCWord()
+ *
+ * @this {CPUState}
+ * @return {number} word at the current PC; PC advanced by 2
+ */
+CPUState.prototype.getPCWord = function()
+{
+ var w = this.getWord(this.regPC);
+ this.setPC(this.regPC + 2);
+ return w;
+};
+
+/**
+ * popWord()
+ *
+ * @this {CPUState}
+ * @return {number} word popped from the current SP; SP increased by 2
+ */
+CPUState.prototype.popWord = function()
+{
+ var w = this.getWord(this.regSP);
+ this.setSP(this.regSP + 2);
+ return w;
+};
+
+/**
+ * pushWord(w)
+ *
+ * @this {CPUState}
+ * @param {number} w is the word (16-bit) value to push at current SP; SP decreased by 2
+ */
+CPUState.prototype.pushWord = function(w)
+{
+ this.setSP(this.regSP - 2);
+ this.setWord(this.regSP, w);
+};
+
+/**
+ * checkINTR()
+ *
+ * @this {CPUState}
+ * @return {boolean} true if h/w interrupt has just been acknowledged, false if not
+ */
+CPUState.prototype.checkINTR = function()
+{
+ if ((this.intFlags & CPUDef.INTFLAG.INTR) && this.getIF()) {
+ var bRST = CPUDef.OPCODE.RST0 | ((this.intFlags & CPUDef.INTFLAG.INTL) << 3);
+ this.clearINTR();
+ this.clearIF();
+ this.aOps[bRST].call(this);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * clearINTR()
+ *
+ * @this {CPUState}
+ */
+CPUState.prototype.clearINTR = function()
+{
+ this.intFlags &= ~(CPUDef.INTFLAG.INTL | CPUDef.INTFLAG.INTR);
+};
+
+/**
+ * requestINTR(nLevel)
+ *
+ * This is called by any component that wants to request a h/w interrupt.
+ *
+ * NOTE: We allow INTR to be set regardless of the current state of interrupt flag (IF), on the theory
+ * that if/when the CPU briefly turns interrupts off, it shouldn't lose the last h/w interrupt requested.
+ * So instead of ignoring INTR here, checkINTR() ignores INTR as long as the interrupt flag (IF) is clear.
+ *
+ * The downside is that, as long as the CPU has interrupts disabled, an active INTR state will slow stepCPU()
+ * down slightly. We could avoid that by introducing a two-stage interrupt tracking system, where a separate
+ * variable keeps track of the last interrupt requested whenever the interrupt flag (IF) is clear, and when
+ * setIF() finally occurs, that interrupt is propagated to intFlags. But for now, we're going to assume that
+ * scenario is rare.
+ *
+ * @this {CPUState}
+ * @param {number} nLevel (0-7)
+ */
+CPUState.prototype.requestINTR = function(nLevel)
+{
+ this.intFlags = (this.intFlags & ~CPUDef.INTFLAG.INTL) | nLevel | CPUDef.INTFLAG.INTR;
+};
+
+/**
+ * updateReg(sReg, nValue, cch)
+ *
+ * This function helps updateStatus() by massaging the register names and values according to
+ * CPU type before passing the call to displayValue(); in the "old days", updateStatus() called
+ * displayValue() directly (although then it was called displayReg()).
+ *
+ * @this {CPUState}
+ * @param {string} sReg
+ * @param {number} nValue
+ * @param {number} [cch] (default is 2 hex digits)
+ */
+CPUState.prototype.updateReg = function(sReg, nValue, cch)
+{
+ this.displayValue(sReg, nValue, cch || 2);
+};
+
+/**
+ * updateStatus(fForce)
+ *
+ * This provides periodic Control Panel updates (eg, a few times per second; see STATUS_UPDATES_PER_SECOND).
+ * this is where we take care of any DOM updates (eg, register values) while the CPU is running.
+ *
+ * Any high-frequency updates should be performed in updateVideo(), which should avoid DOM updates, since updateVideo()
+ * can be called up to 60 times per second (see VIDEO_UPDATES_PER_SECOND).
+ *
+ * @this {CPUState}
+ * @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
+ */
+CPUState.prototype.updateStatus = function(fForce)
+{
+ if (this.cLiveRegs) {
+ if (fForce || !this.flags.fRunning || this.flags.fDisplayLiveRegs) {
+ this.updateReg("A", this.regA);
+ this.updateReg("B", this.regB);
+ this.updateReg("C", this.regC);
+ this.updateReg("BC", this.getBC(), 4);
+ this.updateReg("D", this.regD);
+ this.updateReg("E", this.regE);
+ this.updateReg("DE", this.getDE(), 4);
+ this.updateReg("H", this.regH);
+ this.updateReg("L", this.regL);
+ this.updateReg("HL", this.getHL(), 4);
+ this.updateReg("SP", this.getSP(), 4);
+ this.updateReg("PC", this.getPC(), 4);
+ var regPS = this.getPS();
+ this.updateReg("PS", regPS, 4);
+ this.updateReg("IF", (regPS & CPUDef.PS.IF)? 1 : 0, 1);
+ this.updateReg("SF", (regPS & CPUDef.PS.SF)? 1 : 0, 1);
+ this.updateReg("ZF", (regPS & CPUDef.PS.ZF)? 1 : 0, 1);
+ this.updateReg("AF", (regPS & CPUDef.PS.AF)? 1 : 0, 1);
+ this.updateReg("PF", (regPS & CPUDef.PS.PF)? 1 : 0, 1);
+ this.updateReg("CF", (regPS & CPUDef.PS.CF)? 1 : 0, 1);
+ }
+ }
+ var controlSpeed = this.bindings["speed"];
+ if (controlSpeed) controlSpeed.textContent = this.getSpeedCurrent();
+};
+
+/**
+ * stepCPU(nMinCycles)
+ *
+ * NOTE: Single-stepping should not be confused with the Trap flag; single-stepping is a Debugger
+ * operation that's completely independent of Trap status. The CPU can go in and out of Trap mode,
+ * in and out of h/w interrupt service routines (ISRs), etc, but from the Debugger's perspective,
+ * they're all one continuous stream of instructions that can be stepped or run at will. Moreover,
+ * stepping vs. running should never change the behavior of the simulation.
+ *
+ * As a result, the Debugger's complete independence means you can run other 8086/8088 debuggers
+ * (eg, DEBUG) inside the simulation without interference; you can even "debug" them with the Debugger.
+ *
+ * @this {CPUState}
+ * @param {number} nMinCycles (0 implies a single-step, and therefore breakpoints should be ignored)
+ * @return {number} of cycles executed; 0 indicates a pre-execution condition (ie, an execution breakpoint
+ * was hit), -1 indicates a post-execution condition (eg, a read or write breakpoint was hit), and a positive
+ * number indicates successful completion of that many cycles (which should always be >= nMinCycles).
+ */
+CPUState.prototype.stepCPU = function(nMinCycles)
+{
+ /*
+ * The Debugger uses fComplete to determine if the instruction completed (true) or was interrupted
+ * by a breakpoint or some other exceptional condition (false). NOTE: this does NOT include JavaScript
+ * exceptions, which stepCPU() expects the caller to catch using its own exception handler.
+ *
+ * The CPU relies on the use of stopCPU() rather than fComplete, because the CPU never single-steps
+ * (ie, nMinCycles is always some large number), whereas the Debugger does. And conversely, when the
+ * Debugger is single-stepping (even when performing multiple single-steps), fRunning is never set,
+ * so stopCPU() would have no effect as far as the Debugger is concerned.
+ */
+ this.flags.fComplete = true;
+
+ /*
+ * fDebugCheck is true if we need to "check" every instruction with the Debugger.
+ */
+ var fDebugCheck = this.flags.fDebugCheck = (DEBUGGER && this.dbg && this.dbg.checksEnabled());
+
+ /*
+ * nDebugState is checked only when fDebugCheck is true, and its sole purpose is to tell the first call
+ * to checkInstruction() that it can skip breakpoint checks, and that will be true ONLY when fStarting is
+ * true OR nMinCycles is zero (the latter means the Debugger is single-stepping).
+ *
+ * Once we snap fStarting, we clear it, because technically, we've moved beyond "starting" and have
+ * officially "started" now.
+ */
+ var nDebugState = (!nMinCycles)? -1 : (this.flags.fStarting? 0 : 1);
+ this.flags.fStarting = false;
+
+ /*
+ * We move the minimum cycle count to nStepCycles (the number of cycles left to step), so that other
+ * functions have the ability to force that number to zero (eg, stopCPU()), and thus we don't have to check
+ * any other criteria to determine whether we should continue stepping or not.
+ */
+ this.nBurstCycles = this.nStepCycles = nMinCycles;
+
+ do {
+ if (this.intFlags) {
+ if (this.checkINTR()) {
+ if (!nMinCycles) {
+ this.assert(DEBUGGER); // nMinCycles of zero should be generated ONLY by the Debugger
+ if (DEBUGGER) {
+ this.println("interrupt dispatched");
+ break;
+ }
+ }
+ }
+ else if (this.intFlags & CPUDef.INTFLAG.HALT) {
+ /*
+ * As discussed in opHLT(), the CPU is never REALLY halted by a HLT instruction; instead,
+ * opHLT() sets CPUDef.INTFLAG.HALT, signalling to us that we're free to end the current burst
+ * AND that we should not execute any more instructions until checkINTR() indicates a hardware
+ * interrupt has been requested.
+ *
+ * One downside to this approach is that it *might* appear to the careful observer that we
+ * executed a full complement of instructions during bursts where CPUDef.INTFLAG.HALT was set,
+ * when in fact we did not. However, the steady advance of the overall cycle count, and thus
+ * the steady series calls to stepCPU(), is needed to ensure that timer updates, video updates,
+ * etc, all continue to occur at the expected rates.
+ *
+ * If necessary, we can add another bookkeeping cycle counter (eg, one that keeps tracks of the
+ * number of cycles during which we did not actually execute any instructions).
+ */
+ this.nStepCycles = 0;
+ break;
+ }
+ }
+
+ if (DEBUGGER && fDebugCheck) {
+ if (this.dbg.checkInstruction(this.regPC, nDebugState)) {
+ this.stopCPU();
+ break;
+ }
+ nDebugState = 1;
+ }
+
+ this.aOps[this.getPCByte()].call(this);
+
+ } while (this.nStepCycles > 0);
+
+ return (this.flags.fComplete? this.nBurstCycles - this.nStepCycles : (this.flags.fComplete === undefined? 0 : -1));
+};
+
+/**
+ * CPUState.init()
+ *
+ * This function operates on every HTML element of class "cpu", extracting the
+ * JSON-encoded parameters for the CPUState constructor from the element's "data-value"
+ * attribute, invoking the constructor (which in turn invokes the CPU constructor)
+ * to create a CPUState component, and then binding any associated HTML controls to the
+ * new component.
+ */
+CPUState.init = function()
+{
+ var aeCPUs = Component.getElementsByClass(document, PCJSCLASS, "cpu");
+ for (var iCPU = 0; iCPU < aeCPUs.length; iCPU++) {
+ var eCPU = aeCPUs[iCPU];
+ var parmsCPU = Component.getComponentParms(eCPU);
+ var cpu = new CPUState(parmsCPU);
+ Component.bindComponentControls(cpu, eCPU, PCJSCLASS);
+ }
+};
+
+/*
+ * Initialize every CPU module on the page
+ */
+web.onInit(CPUState.init);
+
+if (NODE) module.exports = CPUState;
diff --git a/modules/pc6502/lib/debugger.js b/modules/pc6502/lib/debugger.js
new file mode 100644
index 000000000..8c946b1cb
--- /dev/null
+++ b/modules/pc6502/lib/debugger.js
@@ -0,0 +1,4795 @@
+/**
+ * @fileoverview Implements the PC6502 Debugger component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (DEBUGGER) {
+ if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var usr = require("../../shared/lib/usrlib");
+ var web = require("../../shared/lib/weblib");
+ var Component = require("../../shared/lib/component");
+ var Messages = require("./messages");
+ var Memory = require("./memory");
+ var Keyboard = require("./keyboard");
+ var State = require("./state");
+ var CPU = require("./cpu");
+ var CPUDef = require("./cpudef");
+ }
+}
+
+/**
+ * Debugger Address Object
+ *
+ * addr address
+ * fTemporary true if this is a temporary breakpoint address
+ * sCmd set for breakpoint addresses if there's an associated command string
+ * aCmds preprocessed commands (from sCmd)
+ *
+ * @typedef {{
+ * addr:(number|undefined),
+ * fTemporary:(boolean|undefined),
+ * sCmd:(string|undefined),
+ * aCmds:(Array.|undefined)
+ * }}
+ */
+var DbgAddr;
+
+/**
+ * Debugger(parmsDbg)
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsDbg
+ *
+ * The Debugger component supports the following optional (parmsDbg) properties:
+ *
+ * commands: string containing zero or more commands, separated by ';'
+ *
+ * messages: string containing zero or more message categories to enable;
+ * multiple categories must be separated by '|' or ';'. Parsed by messageInit().
+ *
+ * The Debugger component is an optional component that implements a variety of user
+ * commands for controlling the CPU, dumping and editing memory, etc.
+ */
+function Debugger(parmsDbg)
+{
+ if (DEBUGGER) {
+
+ Component.call(this, "Debugger", parmsDbg, Debugger);
+
+ this.style = Debugger.STYLE_8080;
+
+ /*
+ * These keep track of instruction activity, but only when tracing or when Debugger checks
+ * have been enabled (eg, one or more breakpoints have been set).
+ *
+ * They are zeroed by the reset() notification handler. cInstructions is advanced by
+ * stepCPU() and checkInstruction() calls. nCycles is updated by every stepCPU() or stop()
+ * call and simply represents the number of cycles performed by the last run of instructions.
+ */
+ this.nCycles = 0;
+ this.cOpcodes = this.cOpcodesStart = 0;
+
+ /*
+ * Most commands that require an address call parseAddr(), which defaults to dbgAddrNextCode
+ * or dbgAddrNextData when no address has been given. doDump() and doUnassemble(), in turn,
+ * update dbgAddrNextData and dbgAddrNextCode, respectively, when they're done.
+ *
+ * For TEMPORARY breakpoint addresses, we set fTemporary to true, so that they can be automatically
+ * cleared when they're hit.
+ */
+ this.dbgAddrNextCode = this.newAddr();
+ this.dbgAddrNextData = this.newAddr();
+
+ /*
+ * This maintains command history. New commands are inserted at index 0 of the array.
+ * When Enter is pressed on an empty input buffer, we default to the command at aPrevCmds[0].
+ */
+ this.iPrevCmd = -1;
+ this.aPrevCmds = [];
+
+ /*
+ * fAssemble is true when "assemble mode" is active, false when not.
+ */
+ this.fAssemble = false;
+ this.dbgAddrAssemble = this.newAddr();
+
+ /*
+ * aSymbolTable is an array of SymbolTable objects, one per ROM or other chunk of address space,
+ * where each object contains the following properties:
+ *
+ * sModule
+ * addr (physical address, if any; eg, symbols for a ROM)
+ * len
+ * aSymbols
+ * aOffsets
+ *
+ * See addSymbols() for more details, since that's how callers add sets of symbols to the table.
+ */
+ this.aSymbolTable = [];
+
+ /*
+ * aVariables is an object with properties that grows as setVariable() assigns more variables;
+ * each property corresponds to one variable, where the property name is the variable name (ie,
+ * a string beginning with a letter or underscore, followed by zero or more additional letters,
+ * digits, or underscores) and the property value is the variable's numeric value. See doVar()
+ * and setVariable() for details.
+ *
+ * Note that parseValue(), through its reliance on str.parseInt(), assumes a default base of 16
+ * if no base is explicitly indicated (eg, a trailing decimal period), and if you define variable
+ * names containing exclusively hex alpha characters (a-f), those variables will take precedence
+ * over the corresponding hex values. In other words, if you define variables "a" and "b", you
+ * will no longer be able to simply type "a" or "b" to specify the decimal values 10 or 11.
+ */
+ this.aVariables = {};
+
+ /*
+ * clearBreakpoints() initializes the breakpoints lists: aBreakExec is a list of addresses
+ * to halt on whenever attempting to execute an instruction at the corresponding address,
+ * and aBreakRead and aBreakWrite are lists of addresses to halt on whenever a read or write,
+ * respectively, occurs at the corresponding address.
+ *
+ * NOTE: Curiously, after upgrading the Google Closure Compiler from v20141215 to v20150609,
+ * the resulting compiled code would crash in clearBreakpoints(), because the (renamed) aBreakRead
+ * property was already defined. To eliminate whatever was confusing the Closure Compiler, I've
+ * explicitly initialized all the properties that clearBreakpoints() (re)initializes.
+ */
+ this.aBreakExec = this.aBreakRead = this.aBreakWrite = [];
+ this.clearBreakpoints();
+
+ /*
+ * The new "bn" command allows you to specify a number of instructions to execute and then stop;
+ * "bn 0" disables any outstanding count.
+ */
+ this.nBreakIns = 0;
+
+ /*
+ * Execution history is allocated by historyInit() whenever checksEnabled() conditions change.
+ * Execution history is updated whenever the CPU calls checkInstruction(), which will happen
+ * only when checksEnabled() returns true (eg, whenever one or more breakpoints have been set).
+ * This ensures that, by default, the CPU runs as fast as possible.
+ */
+ this.historyInit();
+
+ /*
+ * Initialize Debugger message support
+ */
+ this.afnDumpers = [];
+ this.messageInit(parmsDbg['messages']);
+
+ this.sInitCommands = parmsDbg['commands'];
+
+ /*
+ * Make it easier to access Debugger commands from an external REPL (eg, the WebStorm
+ * "live" console window); eg:
+ *
+ * $('r')
+ * $('dw 0:0')
+ * $('h')
+ * ...
+ */
+ var dbg = this;
+ if (window) {
+ if (window['$'] === undefined) {
+ window['$'] = function(s) { return dbg.doCommands(s); };
+ }
+ } else {
+ if (global['$'] === undefined) {
+ global['$'] = function(s) { return dbg.doCommands(s); };
+ }
+ }
+
+ } // endif DEBUGGER
+}
+
+if (DEBUGGER) {
+
+ Component.subclass(Debugger);
+
+ /*
+ * NOTE: Every Debugger property from here to the first prototype function definition (initBus()) is a
+ * considered a "class constant"; most of them use our "all-caps" convention (and all of them SHOULD, but
+ * that wouldn't help us catch any bugs).
+ *
+ * Technically, all of them should ALSO be preceded by a "@const" annotation, but that's a lot of work and it
+ * really clutters the code. I wish the Closure Compiler had a way to annotate every definition with a given
+ * section with a single annotation....
+ *
+ * Bugs can slip through the cracks without those annotations; for example, I unthinkingly redefined TYPE_SI
+ * at one point, and if all the definitions had been preceded by an "@const", that mistake would have been
+ * caught at compile-time.
+ */
+
+ Debugger.COMMANDS = {
+ '?': "help/print",
+ 'a [#]': "assemble", // TODO: Implement this command someday
+ 'b [#]': "breakpoint", // multiple variations (use b? to list them)
+ 'c': "clear output",
+ 'd [#]': "dump memory", // additional syntax: d [#] [l#], where l# is a number of bytes to dump
+ 'e [#]': "edit memory",
+ 'f': "frequencies",
+ 'g [#]': "go [to #]",
+ 'h': "halt",
+ 'i [#]': "input port #",
+ 'if': "eval expression",
+ 'k': "stack trace",
+ "ln": "list nearest symbol(s)",
+ 'm': "messages",
+ 'o [#]': "output port #",
+ 'p': "step over", // other variations: pr (step and dump registers)
+ 'print': "print expression",
+ 'r': "dump/set registers",
+ 'reset': "reset machine",
+ 's': "set options",
+ 't [#]': "trace", // other variations: tr (trace and dump registers)
+ 'u [#]': "unassemble",
+ 'v': "print version",
+ 'var': "assign variable"
+ };
+
+ Debugger.STYLE_8080 = 8080;
+ Debugger.STYLE_8086 = 8086;
+
+ /*
+ * CPU instruction ordinals
+ */
+ Debugger.INS = {
+ NONE: 0, ACI: 1, ADC: 2, ADD: 3, ADI: 4, ANA: 5, ANI: 6, CALL: 7,
+ CC: 8, CM: 9, CNC: 10, CNZ: 11, CP: 12, CPE: 13, CPO: 14, CZ: 15,
+ CMA: 16, CMC: 17, CMP: 18, CPI: 19, DAA: 20, DAD: 21, DCR: 22, DCX: 23,
+ DI: 24, EI: 25, HLT: 26, IN: 27, INR: 28, INX: 29, JMP: 30, JC: 31,
+ JM: 32, JNC: 33, JNZ: 34, JP: 35, JPE: 36, JPO: 37, JZ: 38, LDA: 39,
+ LDAX: 40, LHLD: 41, LXI: 42, MOV: 43, MVI: 44, NOP: 45, ORA: 46, ORI: 47,
+ OUT: 48, PCHL: 49, POP: 50, PUSH: 51, RAL: 52, RAR: 53, RET: 54, RC: 55,
+ RM: 56, RNC: 57, RNZ: 58, RP: 59, RPE: 60, RPO: 61, RZ: 62, RLC: 63,
+ RRC: 64, RST: 65, SBB: 66, SBI: 67, SHLD: 68, SPHL: 69, STA: 70, STAX: 71,
+ STC: 72, SUB: 73, SUI: 74, XCHG: 75, XRA: 76, XRI: 77, XTHL: 78
+ };
+
+ /*
+ * CPU instruction names (mnemonics), indexed by CPU instruction ordinal (above)
+ *
+ * If you change the default style, using the "s" command (eg, "s 8086"), then the 8086 table
+ * will be used instead. TODO: Add a "s z80" command for Z80-style mnemonics.
+ */
+ Debugger.INS_NAMES = [
+ "NONE", "ACI", "ADC", "ADD", "ADI", "ANA", "ANI", "CALL",
+ "CC", "CM", "CNC", "CNZ", "CP", "CPE", "CPO", "CZ",
+ "CMA", "CMC", "CMP", "CPI", "DAA", "DAD", "DCR", "DCX",
+ "DI", "EI", "HLT", "IN", "INR", "INX", "JMP", "JC",
+ "JM", "JNC", "JNZ", "JP", "JPE", "JPO", "JZ", "LDA",
+ "LDAX", "LHLD", "LXI", "MOV", "MVI", "NOP", "ORA", "ORI",
+ "OUT", "PCHL", "POP", "PUSH", "RAL", "RAR", "RET", "RC",
+ "RM", "RNC", "RNZ", "RP", "RPE", "RPO", "RZ", "RLC",
+ "RRC", "RST", "SBB", "SBI", "SHLD", "SPHL", "STA", "STAX",
+ "STC", "SUB", "SUI", "XCHG", "XRA", "XRI", "XTHL"
+ ];
+
+ Debugger.INS_NAMES_8086 = [
+ "NONE", "ADC", "ADC", "ADD", "ADD", "AND", "AND", "CALL",
+ "CALLC", "CALLS", "CALLNC", "CALLNZ", "CALLNS", "CALLP", "CALLNP", "CALLZ",
+ "NOT", "CMC", "CMP", "CMP", "DAA", "ADD", "DEC", "DEC",
+ "CLI", "STI", "HLT", "IN", "INC", "INC", "JMP", "JC",
+ "JS", "JNC", "JNZ", "JNS", "JP", "JNP", "JZ", "MOV",
+ "MOV", "MOV", "MOV", "MOV", "MOV", "NOP", "OR", "OR",
+ "OUT", "JMP", "POP", "PUSH", "RCL", "RCR", "RET", "RETC",
+ "RETS", "RETNC", "RETNZ", "RETNS", "RETP", "RETNP", "RETZ", "ROL",
+ "ROR", "RST", "SBB", "SBB", "MOV", "MOV", "MOV", "MOV",
+ "STC", "SUB", "SUB", "XCHG", "XOR", "XOR", "XCHG"
+ ];
+
+ Debugger.REG_B = 0x00;
+ Debugger.REG_C = 0x01;
+ Debugger.REG_D = 0x02;
+ Debugger.REG_E = 0x03;
+ Debugger.REG_H = 0x04;
+ Debugger.REG_L = 0x05;
+ Debugger.REG_M = 0x06;
+ Debugger.REG_A = 0x07;
+ Debugger.REG_BC = 0x08;
+ Debugger.REG_DE = 0x09;
+ Debugger.REG_HL = 0x0A;
+ Debugger.REG_SP = 0x0B;
+ Debugger.REG_PC = 0x0C;
+ Debugger.REG_PS = 0x0D;
+ Debugger.REG_PSW = 0x0E; // aka AF if Z80-style mnemonics
+
+ /*
+ * NOTE: "PS" is the complete processor status, which includes bits like the Interrupt flag (IF),
+ * which is NOT the same as "PSW", which is the low 8 bits of "PS" combined with "A" in the high byte.
+ */
+ Debugger.REGS = [
+ "B", "C", "D", "E", "H", "L", "M", "A", "BC", "DE", "HL", "SP", "PC", "PS", "PSW"
+ ];
+
+ /*
+ * Operand type descriptor masks and definitions
+ */
+ Debugger.TYPE_SIZE = 0x000F; // size field
+ Debugger.TYPE_MODE = 0x00F0; // mode field
+ Debugger.TYPE_IREG = 0x0F00; // implied register field
+ Debugger.TYPE_OTHER = 0xF000; // "other" field
+
+ /*
+ * TYPE_SIZE values
+ */
+ Debugger.TYPE_NONE = 0x0000; // (all other TYPE fields ignored)
+ Debugger.TYPE_BYTE = 0x0001; // byte, regardless of operand size
+ Debugger.TYPE_SBYTE = 0x0002; // byte sign-extended to word
+ Debugger.TYPE_WORD = 0x0003; // word (16-bit value)
+
+ /*
+ * TYPE_MODE values
+ */
+ Debugger.TYPE_REG = 0x0010; // register
+ Debugger.TYPE_IMM = 0x0020; // immediate data
+ Debugger.TYPE_ADDR = 0x0033; // immediate (word) address
+ Debugger.TYPE_MEM = 0x0040; // memory reference
+ Debugger.TYPE_INT = 0x0080; // interrupt level encoded in instruction (bits 3-5)
+
+ /*
+ * TYPE_IREG values, based on the REG_* constants.
+ *
+ * NOte that TYPE_M isn't really a register, just an alternative form of TYPE_HL | TYPE_MEM.
+ */
+ Debugger.TYPE_A = (Debugger.REG_A << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
+ Debugger.TYPE_B = (Debugger.REG_B << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
+ Debugger.TYPE_C = (Debugger.REG_C << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
+ Debugger.TYPE_D = (Debugger.REG_D << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
+ Debugger.TYPE_E = (Debugger.REG_E << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
+ Debugger.TYPE_H = (Debugger.REG_H << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
+ Debugger.TYPE_L = (Debugger.REG_L << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
+ Debugger.TYPE_M = (Debugger.REG_M << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_MEM);
+ Debugger.TYPE_BC = (Debugger.REG_BC << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
+ Debugger.TYPE_DE = (Debugger.REG_DE << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
+ Debugger.TYPE_HL = (Debugger.REG_HL << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
+ Debugger.TYPE_SP = (Debugger.REG_SP << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
+ Debugger.TYPE_PC = (Debugger.REG_PC << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
+ Debugger.TYPE_PSW = (Debugger.REG_PSW<< 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
+
+ /*
+ * TYPE_OTHER bit definitions
+ */
+ Debugger.TYPE_IN = 0x1000; // operand is input
+ Debugger.TYPE_OUT = 0x2000; // operand is output
+ Debugger.TYPE_BOTH = (Debugger.TYPE_IN | Debugger.TYPE_OUT);
+ Debugger.TYPE_OPT = 0x4000; // optional operand (ie, normally omitted in 8080 assembly language)
+ Debugger.TYPE_UNDOC = 0x8000; // opcode is an undocumented alternative encoding
+
+ /*
+ * The aaOpDescs array is indexed by opcode, and each element is a sub-array (aOpDesc) that describes
+ * the corresponding opcode. The sub-elements are as follows:
+ *
+ * [0]: {number} of the opcode name (see INS.*)
+ * [1]: {number} containing the destination operand descriptor bit(s), if any
+ * [2]: {number} containing the source operand descriptor bit(s), if any
+ * [3]: {number} containing the occasional third operand descriptor bit(s), if any
+ *
+ * These sub-elements are all optional. If [0] is not present, the opcode is undefined; if [1] is not
+ * present (or contains zero), the opcode has no (or only implied) operands; if [2] is not present, the
+ * opcode has only a single operand. And so on.
+ *
+ * Additional default rules:
+ *
+ * 1) If no TYPE_OTHER bits are specified for the first (destination) operand, TYPE_OUT is assumed;
+ * 2) If no TYPE_OTHER bits are specified for the second (source) operand, TYPE_IN is assumed;
+ * 3) If no size is specified for the second operand, the size is assumed to match the first operand.
+ */
+ Debugger.aaOpDescs = [
+ /* 0x00 */ [Debugger.INS.NOP],
+ /* 0x01 */ [Debugger.INS.LXI, Debugger.TYPE_BC, Debugger.TYPE_IMM],
+ /* 0x02 */ [Debugger.INS.STAX, Debugger.TYPE_BC | Debugger.TYPE_MEM, Debugger.TYPE_A | Debugger.TYPE_OPT],
+ /* 0x03 */ [Debugger.INS.INX, Debugger.TYPE_BC],
+ /* 0x04 */ [Debugger.INS.INR, Debugger.TYPE_B],
+ /* 0x05 */ [Debugger.INS.DCR, Debugger.TYPE_B],
+ /* 0x06 */ [Debugger.INS.MVI, Debugger.TYPE_B, Debugger.TYPE_IMM],
+ /* 0x07 */ [Debugger.INS.RLC],
+ /* 0x08 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
+ /* 0x09 */ [Debugger.INS.DAD, Debugger.TYPE_HL | Debugger.TYPE_OPT, Debugger.TYPE_BC],
+ /* 0x0A */ [Debugger.INS.LDAX, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_BC | Debugger.TYPE_MEM],
+ /* 0x0B */ [Debugger.INS.DCX, Debugger.TYPE_BC],
+ /* 0x0C */ [Debugger.INS.INR, Debugger.TYPE_C],
+ /* 0x0D */ [Debugger.INS.DCR, Debugger.TYPE_C],
+ /* 0x0E */ [Debugger.INS.MVI, Debugger.TYPE_C, Debugger.TYPE_IMM],
+ /* 0x0F */ [Debugger.INS.RRC],
+ /* 0x10 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
+ /* 0x11 */ [Debugger.INS.LXI, Debugger.TYPE_DE, Debugger.TYPE_IMM],
+ /* 0x12 */ [Debugger.INS.STAX, Debugger.TYPE_DE | Debugger.TYPE_MEM, Debugger.TYPE_A | Debugger.TYPE_OPT],
+ /* 0x13 */ [Debugger.INS.INX, Debugger.TYPE_DE],
+ /* 0x14 */ [Debugger.INS.INR, Debugger.TYPE_D],
+ /* 0x15 */ [Debugger.INS.DCR, Debugger.TYPE_D],
+ /* 0x16 */ [Debugger.INS.MVI, Debugger.TYPE_D, Debugger.TYPE_IMM],
+ /* 0x17 */ [Debugger.INS.RAL],
+ /* 0x18 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
+ /* 0x19 */ [Debugger.INS.DAD, Debugger.TYPE_HL | Debugger.TYPE_OPT, Debugger.TYPE_DE],
+ /* 0x1A */ [Debugger.INS.LDAX, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_DE | Debugger.TYPE_MEM],
+ /* 0x1B */ [Debugger.INS.DCX, Debugger.TYPE_DE],
+ /* 0x1C */ [Debugger.INS.INR, Debugger.TYPE_E],
+ /* 0x1D */ [Debugger.INS.DCR, Debugger.TYPE_E],
+ /* 0x1E */ [Debugger.INS.MVI, Debugger.TYPE_E, Debugger.TYPE_IMM],
+ /* 0x1F */ [Debugger.INS.RAR],
+ /* 0x20 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
+ /* 0x21 */ [Debugger.INS.LXI, Debugger.TYPE_HL, Debugger.TYPE_IMM],
+ /* 0x22 */ [Debugger.INS.SHLD, Debugger.TYPE_ADDR | Debugger.TYPE_MEM, Debugger.TYPE_HL | Debugger.TYPE_OPT],
+ /* 0x23 */ [Debugger.INS.INX, Debugger.TYPE_HL],
+ /* 0x24 */ [Debugger.INS.INR, Debugger.TYPE_H],
+ /* 0x25 */ [Debugger.INS.DCR, Debugger.TYPE_H],
+ /* 0x26 */ [Debugger.INS.MVI, Debugger.TYPE_H, Debugger.TYPE_IMM],
+ /* 0x27 */ [Debugger.INS.DAA],
+ /* 0x28 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
+ /* 0x29 */ [Debugger.INS.DAD, Debugger.TYPE_HL | Debugger.TYPE_OPT, Debugger.TYPE_HL],
+ /* 0x2A */ [Debugger.INS.LHLD, Debugger.TYPE_HL | Debugger.TYPE_OPT, Debugger.TYPE_ADDR | Debugger.TYPE_MEM],
+ /* 0x2B */ [Debugger.INS.DCX, Debugger.TYPE_HL],
+ /* 0x2C */ [Debugger.INS.INR, Debugger.TYPE_L],
+ /* 0x2D */ [Debugger.INS.DCR, Debugger.TYPE_L],
+ /* 0x2E */ [Debugger.INS.MVI, Debugger.TYPE_L, Debugger.TYPE_IMM],
+ /* 0x2F */ [Debugger.INS.CMA, Debugger.TYPE_A | Debugger.TYPE_OPT],
+ /* 0x30 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
+ /* 0x31 */ [Debugger.INS.LXI, Debugger.TYPE_SP, Debugger.TYPE_IMM],
+ /* 0x32 */ [Debugger.INS.STA, Debugger.TYPE_ADDR | Debugger.TYPE_MEM, Debugger.TYPE_A | Debugger.TYPE_OPT],
+ /* 0x33 */ [Debugger.INS.INX, Debugger.TYPE_SP],
+ /* 0x34 */ [Debugger.INS.INR, Debugger.TYPE_M],
+ /* 0x35 */ [Debugger.INS.DCR, Debugger.TYPE_M],
+ /* 0x36 */ [Debugger.INS.MVI, Debugger.TYPE_M, Debugger.TYPE_IMM],
+ /* 0x37 */ [Debugger.INS.STC],
+ /* 0x38 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
+ /* 0x39 */ [Debugger.INS.DAD, Debugger.TYPE_HL | Debugger.TYPE_OPT, Debugger.TYPE_SP],
+ /* 0x3A */ [Debugger.INS.LDA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_ADDR | Debugger.TYPE_MEM],
+ /* 0x3B */ [Debugger.INS.DCX, Debugger.TYPE_SP],
+ /* 0x3C */ [Debugger.INS.INR, Debugger.TYPE_A],
+ /* 0x3D */ [Debugger.INS.DCR, Debugger.TYPE_A],
+ /* 0x3E */ [Debugger.INS.MVI, Debugger.TYPE_A, Debugger.TYPE_IMM],
+ /* 0x3F */ [Debugger.INS.CMC],
+ /* 0x40 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_B],
+ /* 0x41 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_C],
+ /* 0x42 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_D],
+ /* 0x43 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_E],
+ /* 0x44 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_H],
+ /* 0x45 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_L],
+ /* 0x46 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_M],
+ /* 0x47 */ [Debugger.INS.MOV, Debugger.TYPE_B, Debugger.TYPE_A],
+ /* 0x48 */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_B],
+ /* 0x49 */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_C],
+ /* 0x4A */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_D],
+ /* 0x4B */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_E],
+ /* 0x4C */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_H],
+ /* 0x4D */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_L],
+ /* 0x4E */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_M],
+ /* 0x4F */ [Debugger.INS.MOV, Debugger.TYPE_C, Debugger.TYPE_A],
+ /* 0x50 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_B],
+ /* 0x51 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_C],
+ /* 0x52 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_D],
+ /* 0x53 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_E],
+ /* 0x54 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_H],
+ /* 0x55 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_L],
+ /* 0x56 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_M],
+ /* 0x57 */ [Debugger.INS.MOV, Debugger.TYPE_D, Debugger.TYPE_A],
+ /* 0x58 */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_B],
+ /* 0x59 */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_C],
+ /* 0x5A */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_D],
+ /* 0x5B */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_E],
+ /* 0x5C */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_H],
+ /* 0x5D */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_L],
+ /* 0x5E */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_M],
+ /* 0x5F */ [Debugger.INS.MOV, Debugger.TYPE_E, Debugger.TYPE_A],
+ /* 0x60 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_B],
+ /* 0x61 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_C],
+ /* 0x62 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_D],
+ /* 0x63 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_E],
+ /* 0x64 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_H],
+ /* 0x65 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_L],
+ /* 0x66 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_M],
+ /* 0x67 */ [Debugger.INS.MOV, Debugger.TYPE_H, Debugger.TYPE_A],
+ /* 0x68 */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_B],
+ /* 0x69 */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_C],
+ /* 0x6A */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_D],
+ /* 0x6B */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_E],
+ /* 0x6C */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_H],
+ /* 0x6D */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_L],
+ /* 0x6E */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_M],
+ /* 0x6F */ [Debugger.INS.MOV, Debugger.TYPE_L, Debugger.TYPE_A],
+ /* 0x70 */ [Debugger.INS.MOV, Debugger.TYPE_M, Debugger.TYPE_B],
+ /* 0x71 */ [Debugger.INS.MOV, Debugger.TYPE_M, Debugger.TYPE_C],
+ /* 0x72 */ [Debugger.INS.MOV, Debugger.TYPE_M, Debugger.TYPE_D],
+ /* 0x73 */ [Debugger.INS.MOV, Debugger.TYPE_M, Debugger.TYPE_E],
+ /* 0x74 */ [Debugger.INS.MOV, Debugger.TYPE_M, Debugger.TYPE_H],
+ /* 0x75 */ [Debugger.INS.MOV, Debugger.TYPE_M, Debugger.TYPE_L],
+ /* 0x76 */ [Debugger.INS.HLT],
+ /* 0x77 */ [Debugger.INS.MOV, Debugger.TYPE_M, Debugger.TYPE_A],
+ /* 0x78 */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_B],
+ /* 0x79 */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_C],
+ /* 0x7A */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_D],
+ /* 0x7B */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_E],
+ /* 0x7C */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_H],
+ /* 0x7D */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_L],
+ /* 0x7E */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_M],
+ /* 0x7F */ [Debugger.INS.MOV, Debugger.TYPE_A, Debugger.TYPE_A],
+ /* 0x80 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0x81 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0x82 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0x83 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0x84 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0x85 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0x86 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0x87 */ [Debugger.INS.ADD, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0x88 */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0x89 */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0x8A */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0x8B */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0x8C */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0x8D */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0x8E */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0x8F */ [Debugger.INS.ADC, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0x90 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0x91 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0x92 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0x93 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0x94 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0x95 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0x96 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0x97 */ [Debugger.INS.SUB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0x98 */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0x99 */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0x9A */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0x9B */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0x9C */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0x9D */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0x9E */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0x9F */ [Debugger.INS.SBB, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0xA0 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0xA1 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0xA2 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0xA3 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0xA4 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0xA5 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0xA6 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0xA7 */ [Debugger.INS.ANA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0xA8 */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0xA9 */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0xAA */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0xAB */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0xAC */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0xAD */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0xAE */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0xAF */ [Debugger.INS.XRA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0xB0 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0xB1 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0xB2 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0xB3 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0xB4 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0xB5 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0xB6 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0xB7 */ [Debugger.INS.ORA, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0xB8 */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_B],
+ /* 0xB9 */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_C],
+ /* 0xBA */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_D],
+ /* 0xBB */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_E],
+ /* 0xBC */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_H],
+ /* 0xBD */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_L],
+ /* 0xBE */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_M],
+ /* 0xBF */ [Debugger.INS.CMP, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_A],
+ /* 0xC0 */ [Debugger.INS.RNZ],
+ /* 0xC1 */ [Debugger.INS.POP, Debugger.TYPE_BC],
+ /* 0xC2 */ [Debugger.INS.JNZ, Debugger.TYPE_ADDR],
+ /* 0xC3 */ [Debugger.INS.JMP, Debugger.TYPE_ADDR],
+ /* 0xC4 */ [Debugger.INS.CNZ, Debugger.TYPE_ADDR],
+ /* 0xC5 */ [Debugger.INS.PUSH, Debugger.TYPE_BC],
+ /* 0xC6 */ [Debugger.INS.ADI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xC7 */ [Debugger.INS.RST, Debugger.TYPE_INT],
+ /* 0xC8 */ [Debugger.INS.RZ],
+ /* 0xC9 */ [Debugger.INS.RET],
+ /* 0xCA */ [Debugger.INS.JZ, Debugger.TYPE_ADDR],
+ /* 0xCB */ [Debugger.INS.JMP, Debugger.TYPE_ADDR | Debugger.TYPE_UNDOC],
+ /* 0xCC */ [Debugger.INS.CZ, Debugger.TYPE_ADDR],
+ /* 0xCD */ [Debugger.INS.CALL, Debugger.TYPE_ADDR],
+ /* 0xCE */ [Debugger.INS.ACI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xCF */ [Debugger.INS.RST, Debugger.TYPE_INT],
+ /* 0xD0 */ [Debugger.INS.RNC],
+ /* 0xD1 */ [Debugger.INS.POP, Debugger.TYPE_DE],
+ /* 0xD2 */ [Debugger.INS.JNC, Debugger.TYPE_ADDR],
+ /* 0xD3 */ [Debugger.INS.OUT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE,Debugger.TYPE_A | Debugger.TYPE_OPT],
+ /* 0xD4 */ [Debugger.INS.CNC, Debugger.TYPE_ADDR],
+ /* 0xD5 */ [Debugger.INS.PUSH, Debugger.TYPE_DE],
+ /* 0xD6 */ [Debugger.INS.SUI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xD7 */ [Debugger.INS.RST, Debugger.TYPE_INT],
+ /* 0xD8 */ [Debugger.INS.RC],
+ /* 0xD9 */ [Debugger.INS.RET, Debugger.TYPE_UNDOC],
+ /* 0xDA */ [Debugger.INS.JC, Debugger.TYPE_ADDR],
+ /* 0xDB */ [Debugger.INS.IN, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xDC */ [Debugger.INS.CC, Debugger.TYPE_ADDR],
+ /* 0xDD */ [Debugger.INS.CALL, Debugger.TYPE_ADDR | Debugger.TYPE_UNDOC],
+ /* 0xDE */ [Debugger.INS.SBI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xDF */ [Debugger.INS.RST, Debugger.TYPE_INT],
+ /* 0xE0 */ [Debugger.INS.RPO],
+ /* 0xE1 */ [Debugger.INS.POP, Debugger.TYPE_HL],
+ /* 0xE2 */ [Debugger.INS.JPO, Debugger.TYPE_ADDR],
+ /* 0xE3 */ [Debugger.INS.XTHL, Debugger.TYPE_SP | Debugger.TYPE_MEM| Debugger.TYPE_OPT, Debugger.TYPE_HL | Debugger.TYPE_OPT],
+ /* 0xE4 */ [Debugger.INS.CPO, Debugger.TYPE_ADDR],
+ /* 0xE5 */ [Debugger.INS.PUSH, Debugger.TYPE_HL],
+ /* 0xE6 */ [Debugger.INS.ANI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xE7 */ [Debugger.INS.RST, Debugger.TYPE_INT],
+ /* 0xE8 */ [Debugger.INS.RPE],
+ /* 0xE9 */ [Debugger.INS.PCHL, Debugger.TYPE_HL],
+ /* 0xEA */ [Debugger.INS.JPE, Debugger.TYPE_ADDR],
+ /* 0xEB */ [Debugger.INS.XCHG, Debugger.TYPE_HL | Debugger.TYPE_OPT, Debugger.TYPE_DE | Debugger.TYPE_OPT],
+ /* 0xEC */ [Debugger.INS.CPE, Debugger.TYPE_ADDR],
+ /* 0xED */ [Debugger.INS.CALL, Debugger.TYPE_ADDR | Debugger.TYPE_UNDOC],
+ /* 0xEE */ [Debugger.INS.XRI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xEF */ [Debugger.INS.RST, Debugger.TYPE_INT],
+ /* 0xF0 */ [Debugger.INS.RP],
+ /* 0xF1 */ [Debugger.INS.POP, Debugger.TYPE_PSW],
+ /* 0xF2 */ [Debugger.INS.JP, Debugger.TYPE_ADDR],
+ /* 0xF3 */ [Debugger.INS.DI],
+ /* 0xF4 */ [Debugger.INS.CP, Debugger.TYPE_ADDR],
+ /* 0xF5 */ [Debugger.INS.PUSH, Debugger.TYPE_PSW],
+ /* 0xF6 */ [Debugger.INS.ORI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xF7 */ [Debugger.INS.RST, Debugger.TYPE_INT],
+ /* 0xF8 */ [Debugger.INS.RM],
+ /* 0xF9 */ [Debugger.INS.SPHL, Debugger.TYPE_SP | Debugger.TYPE_OPT, Debugger.TYPE_HL | Debugger.TYPE_OPT],
+ /* 0xFA */ [Debugger.INS.JM, Debugger.TYPE_ADDR],
+ /* 0xFB */ [Debugger.INS.EI],
+ /* 0xFC */ [Debugger.INS.CM, Debugger.TYPE_ADDR],
+ /* 0xFD */ [Debugger.INS.CALL, Debugger.TYPE_ADDR | Debugger.TYPE_UNDOC],
+ /* 0xFE */ [Debugger.INS.CPI, Debugger.TYPE_A | Debugger.TYPE_OPT, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
+ /* 0xFF */ [Debugger.INS.RST, Debugger.TYPE_INT]
+ ];
+
+ /*
+ * Message categories supported by the messageEnabled() function and other assorted message
+ * functions. Each category has a corresponding bit value that can be combined (ie, OR'ed) as
+ * needed. The Debugger's message command ("m") is used to turn message categories on and off,
+ * like so:
+ *
+ * m port on
+ * m port off
+ * ...
+ *
+ * NOTE: The order of these categories can be rearranged, alphabetized, etc, as desired; just be
+ * aware that changing the bit values could break saved Debugger states (not a huge concern, just
+ * something to be aware of).
+ */
+ Debugger.MESSAGES = {
+ "cpu": Messages.CPU,
+ "bus": Messages.BUS,
+ "mem": Messages.MEM,
+ "port": Messages.PORT,
+ "chipset": Messages.CHIPSET,
+ "keyboard": Messages.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages()
+ "key": Messages.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
+ "video": Messages.VIDEO,
+ "fdc": Messages.FDC,
+ "disk": Messages.DISK,
+ "serial": Messages.SERIAL,
+ "speaker": Messages.SPEAKER,
+ "computer": Messages.COMPUTER,
+ "log": Messages.LOG,
+ "warn": Messages.WARN,
+ /*
+ * Now we turn to message actions rather than message types; for example, setting "halt"
+ * on or off doesn't enable "halt" messages, but rather halts the CPU on any message above.
+ */
+ "halt": Messages.HALT
+ };
+
+ Debugger.HISTORY_LIMIT = DEBUG? 100000 : 1000;
+
+ /**
+ * initBus(bus, cpu, dbg)
+ *
+ * @this {Debugger}
+ * @param {Computer} cmp
+ * @param {Bus} bus
+ * @param {CPUState} cpu
+ * @param {Debugger} dbg
+ */
+ Debugger.prototype.initBus = function(cmp, bus, cpu, dbg)
+ {
+ this.bus = bus;
+ this.cpu = cpu;
+ this.cmp = cmp;
+
+ /*
+ * Re-initialize Debugger message support if necessary
+ */
+ var sMessages = cmp.getMachineParm('messages');
+ if (sMessages) this.messageInit(sMessages);
+
+ this.aaOpDescs = Debugger.aaOpDescs;
+
+ this.messageDump(Messages.BUS, function onDumpBus(asArgs) { dbg.dumpBus(asArgs); });
+
+ this.setReady();
+ };
+
+ /**
+ * setBinding(sHTMLType, sBinding, control, sValue)
+ *
+ * @this {Debugger}
+ * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
+ * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "debugInput")
+ * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
+ * @param {string} [sValue] optional data value
+ * @return {boolean} true if binding was successful, false if unrecognized binding request
+ */
+ Debugger.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
+ {
+ var dbg = this;
+ switch (sBinding) {
+
+ case "debugInput":
+ this.bindings[sBinding] = control;
+ this.controlDebug = control;
+ /*
+ * For halted machines, this is fine, but for auto-start machines, it can be annoying.
+ *
+ * control.focus();
+ */
+ control.onkeydown = function onKeyDownDebugInput(event) {
+ var sCmds;
+ if (event.keyCode == Keyboard.KEYCODE.CR) {
+ sCmds = control.value;
+ control.value = "";
+ dbg.doCommands(sCmds, true);
+ }
+ else if (event.keyCode == Keyboard.KEYCODE.ESC) {
+ control.value = sCmds = "";
+ }
+ else {
+ if (event.keyCode == Keyboard.KEYCODE.UP) {
+ if (dbg.iPrevCmd < dbg.aPrevCmds.length - 1) {
+ sCmds = dbg.aPrevCmds[++dbg.iPrevCmd];
+ }
+ }
+ else if (event.keyCode == Keyboard.KEYCODE.DOWN) {
+ if (dbg.iPrevCmd > 0) {
+ sCmds = dbg.aPrevCmds[--dbg.iPrevCmd];
+ } else {
+ sCmds = "";
+ dbg.iPrevCmd = -1;
+ }
+ }
+ if (sCmds != null) {
+ var cch = sCmds.length;
+ control.value = sCmds;
+ control.setSelectionRange(cch, cch);
+ }
+ }
+ if (sCmds != null && event.preventDefault) event.preventDefault();
+ };
+ return true;
+
+ case "debugEnter":
+ this.bindings[sBinding] = control;
+ web.onClickRepeat(
+ control,
+ 500, 100,
+ function onClickDebugEnter(fRepeat) {
+ if (dbg.controlDebug) {
+ var sCmds = dbg.controlDebug.value;
+ dbg.controlDebug.value = "";
+ dbg.doCommands(sCmds, true);
+ return true;
+ }
+ if (DEBUG) dbg.log("no debugger input buffer");
+ return false;
+ }
+ );
+ return true;
+
+ case "step":
+ this.bindings[sBinding] = control;
+ web.onClickRepeat(
+ control,
+ 500, 100,
+ function onClickStep(fRepeat) {
+ var fCompleted = false;
+ if (!dbg.isBusy(true)) {
+ dbg.setBusy(true);
+ fCompleted = dbg.stepCPU(fRepeat? 1 : 0);
+ dbg.setBusy(false);
+ }
+ return fCompleted;
+ }
+ );
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+ };
+
+ /**
+ * updateFocus()
+ *
+ * @this {Debugger}
+ */
+ Debugger.prototype.updateFocus = function()
+ {
+ if (this.controlDebug) this.controlDebug.focus();
+ };
+
+ /**
+ * getAddr(dbgAddr, fWrite, nb)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr|null|undefined} dbgAddr
+ * @param {boolean} [fWrite]
+ * @param {number} [nb] number of bytes to check (1 or 2); default is 1
+ * @return {number} is the corresponding linear address, or CPUDef.ADDR_INVALID
+ */
+ Debugger.prototype.getAddr = function(dbgAddr, fWrite, nb)
+ {
+ var addr = dbgAddr && dbgAddr.addr;
+ if (addr == null) {
+ addr = CPUDef.ADDR_INVALID;
+ }
+ return addr;
+ };
+
+ /**
+ * getByte(dbgAddr, inc)
+ *
+ * We must route all our memory requests through the CPU now, in case paging is enabled.
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {number} [inc]
+ * @return {number}
+ */
+ Debugger.prototype.getByte = function(dbgAddr, inc)
+ {
+ var b = 0xff;
+ var addr = this.getAddr(dbgAddr, false, 1);
+ if (addr !== CPUDef.ADDR_INVALID) {
+ b = this.bus.getByteDirect(addr);
+ if (inc) this.incAddr(dbgAddr, inc);
+ }
+ return b;
+ };
+
+ /**
+ * getWord(dbgAddr, fAdvance)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {boolean} [fAdvance]
+ * @return {number}
+ */
+ Debugger.prototype.getWord = function(dbgAddr, fAdvance)
+ {
+ return this.getShort(dbgAddr, fAdvance? 2 : 0);
+ };
+
+ /**
+ * getShort(dbgAddr, inc)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {number} [inc]
+ * @return {number}
+ */
+ Debugger.prototype.getShort = function(dbgAddr, inc)
+ {
+ var w = 0xffff;
+ var addr = this.getAddr(dbgAddr, false, 2);
+ if (addr !== CPUDef.ADDR_INVALID) {
+ w = this.bus.getShortDirect(addr);
+ if (inc) this.incAddr(dbgAddr, inc);
+ }
+ return w;
+ };
+
+ /**
+ * setByte(dbgAddr, b, inc)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {number} b
+ * @param {number} [inc]
+ */
+ Debugger.prototype.setByte = function(dbgAddr, b, inc)
+ {
+ var addr = this.getAddr(dbgAddr, true, 1);
+ if (addr !== CPUDef.ADDR_INVALID) {
+ this.bus.setByteDirect(addr, b);
+ if (inc) this.incAddr(dbgAddr, inc);
+ this.cpu.updateCPU(true); // we set fForce to true in case video memory was the target
+ }
+ };
+
+ /**
+ * setShort(dbgAddr, w, inc)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {number} w
+ * @param {number} [inc]
+ */
+ Debugger.prototype.setShort = function(dbgAddr, w, inc)
+ {
+ var addr = this.getAddr(dbgAddr, true, 2);
+ if (addr !== CPUDef.ADDR_INVALID) {
+ this.bus.setShortDirect(addr, w);
+ if (inc) this.incAddr(dbgAddr, inc);
+ this.cpu.updateCPU(true); // we set fForce to true in case video memory was the target
+ }
+ };
+
+ /**
+ * newAddr(addr)
+ *
+ * Returns a NEW DbgAddr object, initialized with specified values and/or defaults.
+ *
+ * @this {Debugger}
+ * @param {number} [addr]
+ * @return {DbgAddr}
+ */
+ Debugger.prototype.newAddr = function(addr)
+ {
+ return {addr: addr, fTemporary: false};
+ };
+
+ /**
+ * setAddr(dbgAddr, addr)
+ *
+ * Updates an EXISTING DbgAddr object, initialized with specified values and/or defaults.
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {number} addr
+ * @return {DbgAddr}
+ */
+ Debugger.prototype.setAddr = function(dbgAddr, addr)
+ {
+ dbgAddr.addr = addr;
+ dbgAddr.fTemporary = false;
+ return dbgAddr;
+ };
+
+ /**
+ * packAddr(dbgAddr)
+ *
+ * Packs a DbgAddr object into an Array suitable for saving in a machine state object.
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @return {Array}
+ */
+ Debugger.prototype.packAddr = function(dbgAddr)
+ {
+ return [dbgAddr.addr, dbgAddr.fTemporary];
+ };
+
+ /**
+ * unpackAddr(aAddr)
+ *
+ * Unpacks a DbgAddr object from an Array created by packAddr() and restored from a saved machine state.
+ *
+ * @this {Debugger}
+ * @param {Array} aAddr
+ * @return {DbgAddr}
+ */
+ Debugger.prototype.unpackAddr = function(aAddr)
+ {
+ return {addr: aAddr[0], fTemporary: aAddr[1]};
+ };
+
+ /**
+ * parseAddr(sAddr, fCode, fNoChecks, fPrint)
+ *
+ * Address evaluation and validation (eg, range checks) are no longer performed at this stage. That's
+ * done later, by getAddr(), which returns CPUDef.ADDR_INVALID for invalid segments, out-of-range offsets,
+ * etc. The Debugger's low-level get/set memory functions verify all getAddr() results, but even if an
+ * invalid address is passed through to the Bus memory interfaces, the address will simply be masked with
+ * Bus.nBusLimit; in the case of CPUDef.ADDR_INVALID, that will generally refer to the top of the physical
+ * address space.
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sAddr
+ * @param {boolean} [fCode] (true if target is code, false if target is data)
+ * @param {boolean} [fNoChecks] (true when setting breakpoints that may not be valid now, but will be later)
+ * @param {boolean} [fPrint]
+ * @return {DbgAddr|null|undefined}
+ */
+ Debugger.prototype.parseAddr = function(sAddr, fCode, fNoChecks, fPrint)
+ {
+ var dbgAddr;
+ var dbgAddrNext = (fCode? this.dbgAddrNextCode : this.dbgAddrNextData);
+ var addr = dbgAddrNext.addr;
+ if (sAddr !== undefined) {
+ sAddr = this.parseReference(sAddr);
+ dbgAddr = this.findSymbolAddr(sAddr);
+ if (dbgAddr) return dbgAddr;
+ addr = this.parseExpression(sAddr, fPrint);
+ }
+ if (addr != null) {
+ dbgAddr = this.newAddr(addr);
+ }
+ return dbgAddr;
+ };
+
+ /**
+ * parseAddrOptions(dbdAddr, sOptions)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {string} [sOptions]
+ */
+ Debugger.prototype.parseAddrOptions = function(dbgAddr, sOptions)
+ {
+ if (sOptions) {
+ var a = sOptions.match(/(['"])(.*?)\1/);
+ if (a) {
+ dbgAddr.aCmds = this.parseCommand(dbgAddr.sCmd = a[2]);
+ }
+ }
+ };
+
+ /**
+ * incAddr(dbgAddr, inc)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {number} [inc] contains value to increment dbgAddr by (default is 1)
+ */
+ Debugger.prototype.incAddr = function(dbgAddr, inc)
+ {
+ if (dbgAddr.addr != null) {
+ dbgAddr.addr += (inc || 1);
+ }
+ };
+
+ /**
+ * toHexOffset(off)
+ *
+ * @this {Debugger}
+ * @param {number|null|undefined} [off]
+ * @return {string} the hex representation of off
+ */
+ Debugger.prototype.toHexOffset = function(off)
+ {
+ return str.toHex(off, 4);
+ };
+
+ /**
+ * toHexAddr(dbgAddr)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @return {string} the hex representation of the address
+ */
+ Debugger.prototype.toHexAddr = function(dbgAddr)
+ {
+ return this.toHexOffset(dbgAddr.addr);
+ };
+
+ /**
+ * getSZ(dbgAddr, cchMax)
+ *
+ * Gets zero-terminated (aka "ASCIIZ") string from dbgAddr. It also stops at the first '$', in case this is
+ * a '$'-terminated string -- mainly because I'm lazy and didn't feel like writing a separate get() function.
+ * Yes, a zero-terminated string containing a '$' will be prematurely terminated, and no, I don't care.
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {number} [cchMax] (default is 256)
+ * @return {string} (and dbgAddr advanced past the terminating zero)
+ */
+ Debugger.prototype.getSZ = function(dbgAddr, cchMax)
+ {
+ var s = "";
+ cchMax = cchMax || 256;
+ while (s.length < cchMax) {
+ var b = this.getByte(dbgAddr, 1);
+ if (!b || b == 0x24 || b >= 127) break;
+ s += (b >= 32? String.fromCharCode(b) : '.');
+ }
+ return s;
+ };
+
+ /**
+ * dumpBlocks(aBlocks, sAddr)
+ *
+ * @this {Debugger}
+ * @param {Array} aBlocks
+ * @param {string} [sAddr] (optional block address)
+ */
+ Debugger.prototype.dumpBlocks = function(aBlocks, sAddr)
+ {
+ var addr = 0, i = 0, n = aBlocks.length;
+
+ if (sAddr) {
+ addr = this.getAddr(this.parseAddr(sAddr));
+ if (addr === CPUDef.ADDR_INVALID) {
+ this.println("invalid address: " + sAddr);
+ return;
+ }
+ i = addr >>> this.bus.nBlockShift;
+ n = 1;
+ }
+
+ this.println("blockid physical blockaddr used size type");
+ this.println("-------- --------- ---------- ------ ------ ----");
+
+ var typePrev = -1, cPrev = 0;
+ while (n--) {
+ var block = aBlocks[i];
+ if (block.type == typePrev) {
+ if (!cPrev++) this.println("...");
+ } else {
+ typePrev = block.type;
+ var sType = Memory.TYPE.NAMES[typePrev];
+ if (block) {
+ this.println(str.toHex(block.id) + " %" + str.toHex(i << this.bus.nBlockShift) + " %%" + str.toHex(block.addr) + " " + str.toHexWord(block.used) + " " + str.toHexWord(block.size) + " " + sType);
+ }
+ if (typePrev != Memory.TYPE.NONE) typePrev = -1;
+ cPrev = 0;
+ }
+ addr += this.bus.nBlockSize;
+ i++;
+ }
+ };
+
+ /**
+ * dumpBus(asArgs)
+ *
+ * Dumps Bus allocations.
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs (asArgs[0] is an optional block address)
+ */
+ Debugger.prototype.dumpBus = function(asArgs)
+ {
+ this.dumpBlocks(this.bus.aMemBlocks, asArgs[0]);
+ };
+
+ /**
+ * dumpHistory(sPrev, sLines)
+ *
+ * If sLines is not a number, it can be a instruction filter. However, for the moment, the only
+ * supported filter is "call", which filters the history buffer for all CALL and RET instructions
+ * from the specified previous point forward.
+ *
+ * @this {Debugger}
+ * @param {string} [sPrev] is a (decimal) number of instructions to rewind to (default is 10)
+ * @param {string} [sLines] is a (decimal) number of instructions to print (default is, again, 10)
+ */
+ Debugger.prototype.dumpHistory = function(sPrev, sLines)
+ {
+ var sMore = "";
+ var cHistory = 0;
+ var iHistory = this.iOpcodeHistory;
+ var aHistory = this.aOpcodeHistory;
+
+ if (aHistory.length) {
+ var nPrev = +sPrev || this.nextHistory;
+ var nLines = +sLines || 10;
+
+ if (isNaN(nPrev)) {
+ nPrev = nLines;
+ } else {
+ sMore = "more ";
+ }
+
+ if (nPrev > aHistory.length) {
+ this.println("note: only " + aHistory.length + " available");
+ nPrev = aHistory.length;
+ }
+
+ iHistory -= nPrev;
+ if (iHistory < 0) {
+ /*
+ * If the dbgAddr of the last aHistory element contains a valid selector, wrap around.
+ */
+ if (aHistory[aHistory.length - 1].addr == null) {
+ nPrev = iHistory + nPrev;
+ iHistory = 0;
+ } else {
+ iHistory += aHistory.length;
+ }
+ }
+
+ var aFilters = [];
+ if (sLines == "call") {
+ nLines = 100000;
+ aFilters = ["CALL"];
+ }
+
+ if (sPrev !== undefined) {
+ this.println(nPrev + " instructions earlier:");
+ }
+
+ /*
+ * TODO: The following is necessary to prevent dumpHistory() from causing additional (or worse, recursive)
+ * faults due to segmented addresses that are no longer valid, but the only alternative is to dramatically
+ * increase the amount of memory used to store instruction history (eg, storing copies of all the instruction
+ * bytes alongside the execution addresses).
+ *
+ * For now, we're living dangerously, so that our history dumps actually work.
+ *
+ * this.nSuppressBreaks++;
+ *
+ * If you re-enable this protection, be sure to re-enable the decrement below, too.
+ */
+ while (nLines > 0 && iHistory != this.iOpcodeHistory) {
+
+ var dbgAddr = aHistory[iHistory++];
+ if (dbgAddr.addr == null) break;
+
+ /*
+ * We must create a new dbgAddr from the address in aHistory, because dbgAddr was
+ * a reference, not a copy, and we don't want getInstruction() modifying the original.
+ */
+ var dbgAddrNew = this.newAddr(dbgAddr.addr);
+
+ var sComment = "history";
+ var nSequence = nPrev--;
+ if (DEBUG && dbgAddr.cycleCount != null) {
+ sComment = "cycles";
+ nSequence = dbgAddr.cycleCount;
+ }
+
+ var sInstruction = this.getInstruction(dbgAddrNew, sComment, nSequence);
+
+ if (!aFilters.length || sInstruction.indexOf(aFilters[0]) >= 0) {
+ this.println(sInstruction);
+ }
+
+ /*
+ * If there were OPERAND or ADDRESS overrides on the previous instruction, getInstruction()
+ * will have automatically disassembled additional bytes, so skip additional history entries.
+ */
+ if (dbgAddrNew.cOverrides) {
+ iHistory += dbgAddrNew.cOverrides; nLines -= dbgAddrNew.cOverrides; nPrev -= dbgAddrNew.cOverrides;
+ }
+
+ if (iHistory >= aHistory.length) iHistory = 0;
+ this.nextHistory = nPrev;
+ cHistory++;
+ nLines--;
+ }
+ /*
+ * See comments above.
+ *
+ * this.nSuppressBreaks--;
+ */
+ }
+
+ if (!cHistory) {
+ this.println("no " + sMore + "history available");
+ this.nextHistory = undefined;
+ }
+ };
+
+ /**
+ * messageInit(sEnable)
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sEnable contains zero or more message categories to enable, separated by '|'
+ */
+ Debugger.prototype.messageInit = function(sEnable)
+ {
+ this.dbg = this;
+ this.bitsMessage = this.bitsWarning = Messages.WARN;
+ this.sMessagePrev = null;
+ /*
+ * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects,
+ * but externally, we allow the user to specify "keys"; "kbd" is also allowed as shorthand for "keyboard".
+ */
+ var aEnable = this.parseCommand(sEnable.replace("keys","key").replace("kbd","keyboard"), false, '|');
+ if (aEnable.length) {
+ for (var m in Debugger.MESSAGES) {
+ if (usr.indexOf(aEnable, m) >= 0) {
+ this.bitsMessage |= Debugger.MESSAGES[m];
+ this.println(m + " messages enabled");
+ }
+ }
+ }
+ };
+
+ /**
+ * messageDump(bitMessage, fnDumper)
+ *
+ * @this {Debugger}
+ * @param {number} bitMessage is one Messages category flag
+ * @param {function(Array.)} fnDumper is a function the Debugger can use to dump data for that category
+ * @return {boolean} true if successfully registered, false if not
+ */
+ Debugger.prototype.messageDump = function(bitMessage, fnDumper)
+ {
+ for (var m in Debugger.MESSAGES) {
+ if (bitMessage == Debugger.MESSAGES[m]) {
+ this.afnDumpers[m] = fnDumper;
+ return true;
+ }
+ }
+ return false;
+ };
+
+ /**
+ * getRegIndex(sReg, off)
+ *
+ * @this {Debugger}
+ * @param {string} sReg
+ * @param {number} [off] optional offset into sReg
+ * @return {number} register index, or -1 if not found
+ */
+ Debugger.prototype.getRegIndex = function(sReg, off)
+ {
+ var i;
+ sReg = sReg.toUpperCase();
+ if (off == null) {
+ i = usr.indexOf(Debugger.REGS, sReg);
+ } else {
+ i = usr.indexOf(Debugger.REGS, sReg.substr(off, 2));
+ if (i < 0) i = usr.indexOf(Debugger.REGS, sReg.substr(off, 1));
+ }
+ return i;
+ };
+
+ /**
+ * getRegString(iReg)
+ *
+ * @this {Debugger}
+ * @param {number} iReg
+ * @return {string}
+ */
+ Debugger.prototype.getRegString = function(iReg)
+ {
+ var cch = 0;
+ var n = this.getRegValue(iReg);
+ if (n !== undefined) {
+ switch(iReg) {
+ case Debugger.REG_A:
+ case Debugger.REG_B:
+ case Debugger.REG_C:
+ case Debugger.REG_D:
+ case Debugger.REG_E:
+ case Debugger.REG_H:
+ case Debugger.REG_L:
+ case Debugger.REG_M:
+ cch = 2;
+ break;
+ case Debugger.REG_BC:
+ case Debugger.REG_DE:
+ case Debugger.REG_HL:
+ case Debugger.REG_SP:
+ case Debugger.REG_PC:
+ case Debugger.REG_PS:
+ case Debugger.REG_PSW:
+ cch = 4;
+ break;
+ }
+ }
+ return cch? str.toHex(n, cch) : "??";
+ };
+
+ /**
+ * getRegValue(iReg)
+ *
+ * @this {Debugger}
+ * @param {number} iReg
+ * @return {number|undefined}
+ */
+ Debugger.prototype.getRegValue = function(iReg)
+ {
+ var n;
+ if (iReg >= 0) {
+ var cpu = this.cpu;
+ switch(iReg) {
+ case Debugger.REG_A:
+ n = cpu.regA;
+ break;
+ case Debugger.REG_B:
+ n = cpu.regB;
+ break;
+ case Debugger.REG_C:
+ n = cpu.regC;
+ break;
+ case Debugger.REG_BC:
+ n = cpu.getBC();
+ break;
+ case Debugger.REG_D:
+ n = cpu.regD;
+ break;
+ case Debugger.REG_E:
+ n = cpu.regE;
+ break;
+ case Debugger.REG_DE:
+ n = cpu.getDE();
+ break;
+ case Debugger.REG_H:
+ n = cpu.regH;
+ break;
+ case Debugger.REG_L:
+ n = cpu.regL;
+ break;
+ case Debugger.REG_HL:
+ n = cpu.getHL();
+ break;
+ case Debugger.REG_M:
+ n = cpu.getByte(cpu.getHL());
+ break;
+ case Debugger.REG_SP:
+ n = cpu.getSP();
+ break;
+ case Debugger.REG_PC:
+ n = cpu.getPC();
+ break;
+ case Debugger.REG_PS:
+ n = cpu.getPS();
+ break;
+ case Debugger.REG_PSW:
+ n = cpu.getPSW();
+ break;
+ default:
+ break;
+ }
+ }
+ return n;
+ };
+
+ /**
+ * replaceRegs(s)
+ *
+ * @this {Debugger}
+ * @param {string} s
+ * @return {string}
+ */
+ Debugger.prototype.replaceRegs = function(s)
+ {
+ /*
+ * Replace any references first; this means that register references inside the reference
+ * do NOT need to be prefixed with '@'.
+ */
+ s = this.parseReference(s);
+
+ /*
+ * Replace every @XX (or @XXX), where XX (or XXX) is a register, with the register's value.
+ */
+ var i = 0;
+ var b, sChar, sAddr, dbgAddr, sReplace;
+ while ((i = s.indexOf('@', i)) >= 0) {
+ var iReg = this.getRegIndex(s, i + 1);
+ if (iReg >= 0) {
+ s = s.substr(0, i) + this.getRegString(iReg) + s.substr(i + 1 + Debugger.REGS[iReg].length);
+ }
+ i++;
+ }
+ /*
+ * Replace every #XX, where XX is a hex byte value, with the corresponding ASCII character (if printable).
+ */
+ i = 0;
+ while ((i = s.indexOf('#', i)) >= 0) {
+ sChar = s.substr(i+1, 2);
+ b = str.parseInt(sChar, 16);
+ if (b != null && b >= 32 && b < 128) {
+ sReplace = sChar + " '" + String.fromCharCode(b) + "'";
+ s = s.replace('#' + sChar, sReplace);
+ i += sReplace.length;
+ continue;
+ }
+ i++;
+ }
+ /*
+ * Replace every $XXXX:XXXX, where XXXX:XXXX is a segmented address, with the zero-terminated string at that address.
+ */
+ i = 0;
+ while ((i = s.indexOf('$', i)) >= 0) {
+ sAddr = s.substr(i+1, 9);
+ dbgAddr = this.parseAddr(sAddr);
+ if (dbgAddr) {
+ sReplace = sAddr + ' "' + this.getSZ(dbgAddr) + '"';
+ s = s.replace('$' + sAddr, sReplace);
+ i += sReplace.length;
+ continue;
+ }
+ i++;
+ }
+ /*
+ * Replace every ^XXXX:XXXX, where XXXX:XXXX is a segmented address, with the FCB filename stored at that address.
+ */
+ i = 0;
+ while ((i = s.indexOf('^', i)) >= 0) {
+ sAddr = s.substr(i+1, 9);
+ dbgAddr = this.parseAddr(sAddr);
+ if (dbgAddr) {
+ this.incAddr(dbgAddr);
+ sReplace = sAddr + ' "' + this.getSZ(dbgAddr, 11) + '"';
+ s = s.replace('^' + sAddr, sReplace);
+ i += sReplace.length;
+ continue;
+ }
+ i++;
+ }
+ return s;
+ };
+
+ /**
+ * message(sMessage, fAddress)
+ *
+ * @this {Debugger}
+ * @param {string} sMessage is any caller-defined message string
+ * @param {boolean} [fAddress] is true to display the current CS:IP
+ */
+ Debugger.prototype.message = function(sMessage, fAddress)
+ {
+ if (fAddress) {
+ sMessage += " at " + this.toHexAddr(this.newAddr(this.cpu.getPC()));
+ }
+
+ if (this.sMessagePrev && sMessage == this.sMessagePrev) return;
+ this.sMessagePrev = sMessage;
+
+ if (this.bitsMessage & Messages.HALT) {
+ this.stopCPU();
+ sMessage += " (cpu halted)";
+ }
+
+ this.println(sMessage); // + " (" + this.cpu.getCycles() + " cycles)"
+
+ /*
+ * We have no idea what the frequency of println() calls might be; all we know is that they easily
+ * screw up the CPU's careful assumptions about cycles per burst. So we call yieldCPU() after every
+ * message, to effectively end the current burst and start fresh.
+ *
+ * TODO: See CPU.calcStartTime() for a discussion of why we might want to call yieldCPU() *before*
+ * we display the message.
+ */
+ if (this.cpu) this.cpu.yieldCPU();
+ };
+
+ /**
+ * messageIO(component, port, bOut, addrFrom, name, bIn, bitsMessage)
+ *
+ * Most (if not all) port handlers should provide a name for their respective ports, so if no name is provided,
+ * we assume this is an unknown port, and display a message by default.
+ *
+ * @this {Debugger}
+ * @param {Component} component
+ * @param {number} port
+ * @param {number|null} bOut if an output operation
+ * @param {number|null} [addrFrom]
+ * @param {string|null} [name] of the port, if any
+ * @param {number|null} [bIn] is the input value, if known, on an input operation
+ * @param {number} [bitsMessage] is one or more Messages category flag(s)
+ */
+ Debugger.prototype.messageIO = function(component, port, bOut, addrFrom, name, bIn, bitsMessage)
+ {
+ bitsMessage |= Messages.PORT;
+ if (name == null || (this.bitsMessage & bitsMessage) == bitsMessage) {
+ this.message(component.idComponent + '.' + (bOut != null? "outPort" : "inPort") + '(' + str.toHexWord(port) + ',' + (name? name : "unknown") + (bOut != null? ',' + str.toHexByte(bOut) : "") + ')' + (bIn != null? (": " + str.toHexByte(bIn)) : "") + (addrFrom != null? (" at " + this.toHexOffset(addrFrom)) : ""));
+ }
+ };
+
+ /**
+ * init()
+ *
+ * @this {Debugger}
+ */
+ Debugger.prototype.init = function()
+ {
+ this.println("Type ? for help with PC6502 Debugger commands");
+ this.updateStatus();
+ if (this.sInitCommands) {
+ var sCmds = this.sInitCommands;
+ this.sInitCommands = null;
+ this.doCommands(sCmds);
+ }
+ };
+
+ /**
+ * historyInit(fQuiet)
+ *
+ * This function is intended to be called by the constructor, reset(), addBreakpoint(), findBreakpoint()
+ * and any other function that changes the checksEnabled() criteria used to decide whether checkInstruction()
+ * should be called.
+ *
+ * That is, if the history arrays need to be allocated and haven't already been allocated, then allocate them,
+ * and if the arrays are no longer needed, then deallocate them.
+ *
+ * @this {Debugger}
+ * @param {boolean} [fQuiet]
+ */
+ Debugger.prototype.historyInit = function(fQuiet)
+ {
+ var i;
+ if (!this.checksEnabled()) {
+ if (this.aOpcodeHistory && this.aOpcodeHistory.length && !fQuiet) {
+ this.println("instruction history buffer freed");
+ }
+ this.iOpcodeHistory = 0;
+ this.aOpcodeHistory = [];
+ this.aaOpcodeCounts = [];
+ return;
+ }
+ if (!this.aOpcodeHistory || !this.aOpcodeHistory.length) {
+ this.aOpcodeHistory = new Array(Debugger.HISTORY_LIMIT);
+ for (i = 0; i < this.aOpcodeHistory.length; i++) {
+ /*
+ * Preallocate dummy Addr (Array) objects in every history slot, so that
+ * checkInstruction() doesn't need to call newAddr() on every slot update.
+ */
+ this.aOpcodeHistory[i] = this.newAddr();
+ }
+ this.iOpcodeHistory = 0;
+ if (!fQuiet) {
+ this.println("instruction history buffer allocated");
+ }
+ }
+ if (!this.aaOpcodeCounts || !this.aaOpcodeCounts.length) {
+ this.aaOpcodeCounts = new Array(256);
+ for (i = 0; i < this.aaOpcodeCounts.length; i++) {
+ this.aaOpcodeCounts[i] = [i, 0];
+ }
+ }
+ };
+
+ /**
+ * runCPU(fUpdateFocus)
+ *
+ * @this {Debugger}
+ * @param {boolean} [fUpdateFocus] is true to update focus
+ * @return {boolean} true if run request successful, false if not
+ */
+ Debugger.prototype.runCPU = function(fUpdateFocus)
+ {
+ if (!this.isCPUAvail()) return false;
+ this.cpu.runCPU(fUpdateFocus);
+ return true;
+ };
+
+ /**
+ * stepCPU(nCycles, fRegs, fUpdateCPU)
+ *
+ * @this {Debugger}
+ * @param {number} nCycles (0 for one instruction without checking breakpoints)
+ * @param {boolean} [fRegs] is true to display registers after step (default is false)
+ * @param {boolean} [fUpdateCPU] is false to disable calls to updateCPU() (default is true)
+ * @return {boolean}
+ */
+ Debugger.prototype.stepCPU = function(nCycles, fRegs, fUpdateCPU)
+ {
+ if (!this.isCPUAvail()) return false;
+
+ this.nCycles = 0;
+
+ if (!nCycles) {
+ /*
+ * When single-stepping, the CPU won't call checkInstruction(), which is good for
+ * avoiding breakpoints, but bad for instruction data collection if checks are enabled.
+ * So we call checkInstruction() ourselves.
+ */
+ if (this.checksEnabled()) this.checkInstruction(this.cpu.getPC(), 0);
+ }
+ try {
+ var nCyclesStep = this.cpu.stepCPU(nCycles);
+ if (nCyclesStep > 0) {
+ this.nCycles += nCyclesStep;
+ this.cpu.addCycles(nCyclesStep, true);
+ this.cpu.updateChecksum(nCyclesStep);
+ this.cOpcodes++;
+ }
+ }
+ catch(exception) {
+ if (typeof exception != "number") {
+ var e = exception;
+ this.nCycles = 0;
+ this.cpu.setError(e.stack || e.message);
+ }
+ }
+
+ /*
+ * Because we called cpu.stepCPU() and not cpu.runCPU(), we must nudge the cpu's update code,
+ * and then update our own state. Normally, the only time fUpdateCPU will be false is when doTrace()
+ * is calling us in a loop, in which case it will perform its own updateCPU() when it's done.
+ */
+ if (fUpdateCPU !== false) this.cpu.updateCPU();
+
+ this.updateStatus(fRegs || false);
+ return (this.nCycles > 0);
+ };
+
+ /**
+ * stopCPU()
+ *
+ * @this {Debugger}
+ * @param {boolean} [fComplete]
+ */
+ Debugger.prototype.stopCPU = function(fComplete)
+ {
+ if (this.cpu) this.cpu.stopCPU(fComplete);
+ };
+
+ /**
+ * updateStatus(fRegs)
+ *
+ * @this {Debugger}
+ * @param {boolean} [fRegs] (default is true)
+ */
+ Debugger.prototype.updateStatus = function(fRegs)
+ {
+ if (fRegs === undefined) fRegs = true;
+
+ this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());
+ /*
+ * this.nStep used to be a simple boolean, but now it's 0 (or undefined)
+ * if inactive, 1 if stepping over an instruction without a register dump, or 2
+ * if stepping over an instruction with a register dump.
+ */
+ if (!fRegs || this.nStep == 1)
+ this.doUnassemble();
+ else {
+ this.doRegisters();
+ }
+ };
+
+ /**
+ * isCPUAvail()
+ *
+ * Make sure the CPU is ready (finished initializing), not busy (already running), and not in an error state.
+ *
+ * @this {Debugger}
+ * @return {boolean}
+ */
+ Debugger.prototype.isCPUAvail = function()
+ {
+ if (!this.cpu)
+ return false;
+ if (!this.cpu.isReady())
+ return false;
+ if (!this.cpu.isPowered())
+ return false;
+ if (this.cpu.isBusy())
+ return false;
+ return !this.cpu.isError();
+ };
+
+ /**
+ * powerUp(data, fRepower)
+ *
+ * @this {Debugger}
+ * @param {Object|null} data
+ * @param {boolean} [fRepower]
+ * @return {boolean} true if successful, false if failure
+ */
+ Debugger.prototype.powerUp = function(data, fRepower)
+ {
+ if (!fRepower) {
+ /*
+ * Because Debugger save/restore support is somewhat limited (and didn't always exist),
+ * we deviate from the typical save/restore design pattern: instead of reset OR restore,
+ * we always reset and then perform a (potentially limited) restore.
+ */
+ this.reset(true);
+
+ // this.println(data? "resuming" : "powering up");
+
+ if (data && this.restore) {
+ if (!this.restore(data)) return false;
+ }
+ }
+ return true;
+ };
+
+ /**
+ * powerDown(fSave, fShutdown)
+ *
+ * @this {Debugger}
+ * @param {boolean} [fSave]
+ * @param {boolean} [fShutdown]
+ * @return {Object|boolean}
+ */
+ Debugger.prototype.powerDown = function(fSave, fShutdown)
+ {
+ if (fShutdown) this.println(fSave? "suspending" : "shutting down");
+ return fSave? this.save() : true;
+ };
+
+ /**
+ * reset(fQuiet)
+ *
+ * This is a notification handler, called by the Computer, to inform us of a reset.
+ *
+ * @this {Debugger}
+ * @param {boolean} fQuiet (true only when called from our own powerUp handler)
+ */
+ Debugger.prototype.reset = function(fQuiet)
+ {
+ this.historyInit();
+ this.cOpcodes = this.cOpcodesStart = 0;
+ this.sMessagePrev = null;
+ this.nCycles = 0;
+ this.dbgAddrNextCode = this.newAddr(this.cpu.getPC());
+ /*
+ * fRunning is set by start() and cleared by stop(). In addition, we clear
+ * it here, so that if the CPU is reset while running, we can prevent stop()
+ * from unnecessarily dumping the CPU state.
+ */
+ this.flags.fRunning = false;
+ this.clearTempBreakpoint();
+ if (!fQuiet) this.updateStatus();
+ };
+
+ /**
+ * save()
+ *
+ * This implements (very rudimentary) save support for the Debugger component.
+ *
+ * @this {Debugger}
+ * @return {Object}
+ */
+ Debugger.prototype.save = function()
+ {
+ var state = new State(this);
+ state.set(0, this.packAddr(this.dbgAddrNextCode));
+ state.set(1, this.packAddr(this.dbgAddrAssemble));
+ state.set(2, [this.aPrevCmds, this.fAssemble, this.bitsMessage]);
+ state.set(3, this.aSymbolTable);
+ return state.data();
+ };
+
+ /**
+ * restore(data)
+ *
+ * This implements (very rudimentary) restore support for the Debugger component.
+ *
+ * @this {Debugger}
+ * @param {Object} data
+ * @return {boolean} true if successful, false if failure
+ */
+ Debugger.prototype.restore = function(data)
+ {
+ var i = 0;
+ if (data[2] !== undefined) {
+ this.dbgAddrNextCode = this.unpackAddr(data[i++]);
+ this.dbgAddrAssemble = this.unpackAddr(data[i++]);
+ this.aPrevCmds = data[i][0];
+ if (typeof this.aPrevCmds == "string") this.aPrevCmds = [this.aPrevCmds];
+ this.fAssemble = data[i][1];
+ this.bitsMessage |= data[i][2]; // keep our current message bits set, and simply "add" any extra bits defined by the saved state
+ }
+ if (data[3]) this.aSymbolTable = data[3];
+ return true;
+ };
+
+ /**
+ * start(ms, nCycles)
+ *
+ * This is a notification handler, called by the Computer, to inform us the CPU has started.
+ *
+ * @this {Debugger}
+ * @param {number} ms
+ * @param {number} nCycles
+ */
+ Debugger.prototype.start = function(ms, nCycles)
+ {
+ if (!this.nStep) this.println("running");
+ this.flags.fRunning = true;
+ this.msStart = ms;
+ this.nCyclesStart = nCycles;
+ };
+
+ /**
+ * stop(ms, nCycles)
+ *
+ * This is a notification handler, called by the Computer, to inform us the CPU has now stopped.
+ *
+ * @this {Debugger}
+ * @param {number} ms
+ * @param {number} nCycles
+ */
+ Debugger.prototype.stop = function(ms, nCycles)
+ {
+ if (this.flags.fRunning) {
+ this.flags.fRunning = false;
+ this.nCycles = nCycles - this.nCyclesStart;
+ if (!this.nStep) {
+ var sStopped = "stopped";
+ if (this.nCycles) {
+ var msTotal = ms - this.msStart;
+ var nCyclesPerSecond = (msTotal > 0? Math.round(this.nCycles * 1000 / msTotal) : 0);
+ sStopped += " (";
+ if (this.checksEnabled()) {
+ sStopped += this.cOpcodes + " opcodes, ";
+ /*
+ * $ops displays progress by calculating cOpcodes - cOpcodesStart, so before
+ * zeroing cOpcodes, we should subtract cOpcodes from cOpcodesStart (since we're
+ * effectively subtracting cOpcodes from cOpcodes as well).
+ */
+ this.cOpcodesStart -= this.cOpcodes;
+ this.cOpcodes = 0;
+ }
+ sStopped += this.nCycles + " cycles, " + msTotal + " ms, " + nCyclesPerSecond + " hz)";
+ } else {
+ if (this.messageEnabled(Messages.HALT)) {
+ /*
+ * It's possible the user is trying to 'g' past a fault that was blocked by helpCheckFault()
+ * for the Debugger's benefit; if so, it will continue to be blocked, so try displaying a helpful
+ * message (another helpful tip would be to simply turn off the "halt" message category).
+ */
+ sStopped += " (use the 't' command to execute blocked faults)";
+ }
+ }
+ this.println(sStopped);
+ }
+ this.updateStatus(true);
+ this.updateFocus();
+ this.clearTempBreakpoint(this.cpu.getPC());
+ }
+ };
+
+ /**
+ * checksEnabled(fRelease)
+ *
+ * This "check" function is called by the CPU; we indicate whether or not every instruction needs to be checked.
+ *
+ * Originally, this returned true even when there were only read and/or write breakpoints, but those breakpoints
+ * no longer require the intervention of checkInstruction(); the Bus component automatically swaps in/out appropriate
+ * "checked" Memory access functions to deal with those breakpoints in the corresponding Memory blocks. So I've
+ * simplified the test below.
+ *
+ * @this {Debugger}
+ * @param {boolean} [fRelease] is true for release criteria only; default is false (any criteria)
+ * @return {boolean} true if every instruction needs to pass through checkInstruction(), false if not
+ */
+ Debugger.prototype.checksEnabled = function(fRelease)
+ {
+ return ((DEBUG && !fRelease)? true : (this.aBreakExec.length > 1 || !!this.nBreakIns));
+ };
+
+ /**
+ * checkInstruction(addr, nState)
+ *
+ * This "check" function is called by the CPU to inform us about the next instruction to be executed,
+ * giving us an opportunity to look for "exec" breakpoints and update opcode frequencies and instruction history.
+ *
+ * @this {Debugger}
+ * @param {number} addr
+ * @param {number} nState is < 0 if stepping, 0 if starting, or > 0 if running
+ * @return {boolean} true if breakpoint hit, false if not
+ */
+ Debugger.prototype.checkInstruction = function(addr, nState)
+ {
+ var cpu = this.cpu;
+
+ if (nState > 0) {
+ if (this.nBreakIns && !--this.nBreakIns) {
+ return true;
+ }
+ if (this.checkBreakpoint(addr, 1, this.aBreakExec)) {
+ return true;
+ }
+ }
+
+ /*
+ * The rest of the instruction tracking logic can only be performed if historyInit() has allocated the
+ * necessary data structures. Note that there is no explicit UI for enabling/disabling history, other than
+ * adding/removing breakpoints, simply because it's breakpoints that trigger the call to checkInstruction();
+ * well, OK, and a few other things now, like enabling Messages.INT messages.
+ */
+ if (nState >= 0 && this.aaOpcodeCounts.length) {
+ this.cOpcodes++;
+ var bOpcode = this.bus.getByteDirect(addr);
+ if (bOpcode != null) {
+ this.aaOpcodeCounts[bOpcode][1]++;
+ var dbgAddr = this.aOpcodeHistory[this.iOpcodeHistory];
+ this.setAddr(dbgAddr, cpu.getPC());
+ if (DEBUG) dbgAddr.cycleCount = cpu.getCycles();
+ if (++this.iOpcodeHistory == this.aOpcodeHistory.length) this.iOpcodeHistory = 0;
+ }
+ }
+ return false;
+ };
+
+ /**
+ * checkMemoryRead(addr, nb)
+ *
+ * This "check" function is called by a Memory block to inform us that a memory read occurred, giving us an
+ * opportunity to track the read if we want, and look for a matching "read" breakpoint, if any.
+ *
+ * In the "old days", it would be an error for this call to fail to find a matching Debugger breakpoint, but now
+ * Memory blocks have no idea whether the Debugger or the machine's Debug register(s) triggered this "checked" read.
+ *
+ * If we return true, we "trump" the machine's Debug register(s); false allows normal Debug register processing.
+ *
+ * @this {Debugger}
+ * @param {number} addr
+ * @param {number} [nb] (# of bytes; default is 1)
+ * @return {boolean} true if breakpoint hit, false if not
+ */
+ Debugger.prototype.checkMemoryRead = function(addr, nb)
+ {
+ if (this.checkBreakpoint(addr, nb || 1, this.aBreakRead)) {
+ this.stopCPU(true);
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * checkMemoryWrite(addr, nb)
+ *
+ * This "check" function is called by a Memory block to inform us that a memory write occurred, giving us an
+ * opportunity to track the write if we want, and look for a matching "write" breakpoint, if any.
+ *
+ * In the "old days", it would be an error for this call to fail to find a matching Debugger breakpoint, but now
+ * Memory blocks have no idea whether the Debugger or the machine's Debug register(s) triggered this "checked" write.
+ *
+ * If we return true, we "trump" the machine's Debug register(s); false allows normal Debug register processing.
+ *
+ * @this {Debugger}
+ * @param {number} addr
+ * @param {number} [nb] (# of bytes; default is 1)
+ * @return {boolean} true if breakpoint hit, false if not
+ */
+ Debugger.prototype.checkMemoryWrite = function(addr, nb)
+ {
+ if (this.checkBreakpoint(addr, nb || 1, this.aBreakWrite)) {
+ this.stopCPU(true);
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * checkPortInput(port, size, data)
+ *
+ * This "check" function is called by the Bus component to inform us that port input occurred.
+ *
+ * @this {Debugger}
+ * @param {number} port
+ * @param {number} size
+ * @param {number} data
+ * @return {boolean} true if breakpoint hit, false if not
+ */
+ Debugger.prototype.checkPortInput = function(port, size, data)
+ {
+ /*
+ * We trust that the Bus component won't call us unless we told it to, so we halt unconditionally
+ */
+ this.println("break on input from port " + str.toHexWord(port) + ": " + str.toHex(data));
+ this.stopCPU(true);
+ return true;
+ };
+
+ /**
+ * checkPortOutput(port, size, data)
+ *
+ * This "check" function is called by the Bus component to inform us that port output occurred.
+ *
+ * @this {Debugger}
+ * @param {number} port
+ * @param {number} size
+ * @param {number} data
+ * @return {boolean} true if breakpoint hit, false if not
+ */
+ Debugger.prototype.checkPortOutput = function(port, size, data)
+ {
+ /*
+ * We trust that the Bus component won't call us unless we told it to, so we halt unconditionally
+ */
+ this.println("break on output to port " + str.toHexWord(port) + ": " + str.toHex(data));
+ this.stopCPU(true);
+ return true;
+ };
+
+ /**
+ * clearBreakpoints()
+ *
+ * @this {Debugger}
+ */
+ Debugger.prototype.clearBreakpoints = function()
+ {
+ var i, dbgAddr;
+ this.aBreakExec = ["bp"];
+ if (this.aBreakRead !== undefined) {
+ for (i = 1; i < this.aBreakRead.length; i++) {
+ dbgAddr = this.aBreakRead[i];
+ this.bus.removeMemBreak(this.getAddr(dbgAddr), false);
+ }
+ }
+ this.aBreakRead = ["br"];
+ if (this.aBreakWrite !== undefined) {
+ for (i = 1; i < this.aBreakWrite.length; i++) {
+ dbgAddr = this.aBreakWrite[i];
+ this.bus.removeMemBreak(this.getAddr(dbgAddr), true);
+ }
+ }
+ this.aBreakWrite = ["bw"];
+ /*
+ * nSuppressBreaks ensures we can't get into an infinite loop where a breakpoint lookup requires
+ * reading a segment descriptor via getSegment(), and that triggers more memory reads, which triggers
+ * more breakpoint checks.
+ */
+ this.nSuppressBreaks = 0;
+ };
+
+ /**
+ * addBreakpoint(aBreak, dbgAddr, fTemporary)
+ *
+ * In case you haven't already figured this out, all our breakpoint commands use the address
+ * to identify a breakpoint, not an incrementally assigned breakpoint index like other debuggers;
+ * see doBreak() for details.
+ *
+ * This has a few implications, one being that you CANNOT set more than one kind of breakpoint
+ * on a single address. In practice, that's rarely a problem, because you can almost always set
+ * a different breakpoint on a neighboring address.
+ *
+ * Also, there is one exception to the "one address, one breakpoint" rule, and that involves
+ * temporary breakpoints (ie, one-time execution breakpoints that either a "p" or "g" command
+ * may create to step over a chunk of code). Those breakpoints automatically clear themselves,
+ * so there usually isn't any need to refer to them using breakpoint commands.
+ *
+ * TODO: Consider supporting the more "traditional" breakpoint index syntax; the current
+ * address-based syntax was implemented solely for expediency and consistency. At the same time,
+ * also consider a more WDEB386-like syntax, where "br" is used to set a variety of access-specific
+ * breakpoints, using modifiers like "r1", "r2", "w1", "w2, etc.
+ *
+ * @this {Debugger}
+ * @param {Array} aBreak
+ * @param {DbgAddr} dbgAddr
+ * @param {boolean} [fTemporary]
+ * @return {boolean} true if breakpoint added, false if already exists
+ */
+ Debugger.prototype.addBreakpoint = function(aBreak, dbgAddr, fTemporary)
+ {
+ var fSuccess = true;
+
+ // this.nSuppressBreaks++;
+
+ /*
+ * Instead of complaining that a breakpoint already exists (as we used to do), we now
+ * allow breakpoints to be re-set; this makes it easier to update any commands that may
+ * be associated with the breakpoint.
+ *
+ * The only exception: we DO allow a temporary breakpoint at an address where there may
+ * already be a breakpoint, so that you can easily step ("p" or "g") over such addresses.
+ */
+ if (!fTemporary) {
+ this.findBreakpoint(aBreak, dbgAddr, true, false, true);
+ }
+
+ if (aBreak != this.aBreakExec) {
+ var addr = this.getAddr(dbgAddr);
+ if (addr === CPUDef.ADDR_INVALID) {
+ this.println("invalid address: " + this.toHexAddr(dbgAddr));
+ fSuccess = false;
+ } else {
+ this.bus.addMemBreak(addr, aBreak == this.aBreakWrite);
+ }
+ }
+
+ if (fSuccess) {
+ aBreak.push(dbgAddr);
+ if (fTemporary) {
+ dbgAddr.fTemporary = true;
+ }
+ else {
+ this.printBreakpoint(aBreak, aBreak.length-1, "set");
+ this.historyInit();
+ }
+ }
+
+ // this.nSuppressBreaks--;
+
+ return fSuccess;
+ };
+
+ /**
+ * findBreakpoint(aBreak, dbgAddr, fRemove, fTemporary, fQuiet)
+ *
+ * @this {Debugger}
+ * @param {Array} aBreak
+ * @param {DbgAddr} dbgAddr
+ * @param {boolean} [fRemove]
+ * @param {boolean} [fTemporary]
+ * @param {boolean} [fQuiet]
+ * @return {boolean} true if found, false if not
+ */
+ Debugger.prototype.findBreakpoint = function(aBreak, dbgAddr, fRemove, fTemporary, fQuiet)
+ {
+ var fFound = false;
+ var addr = this.getAddr(dbgAddr);
+ for (var i = 1; i < aBreak.length; i++) {
+ var dbgAddrBreak = aBreak[i];
+ if (addr == this.getAddr(dbgAddrBreak)) {
+ if (!fTemporary || dbgAddrBreak.fTemporary) {
+ fFound = true;
+ if (fRemove) {
+ if (!dbgAddrBreak.fTemporary && !fQuiet) {
+ this.printBreakpoint(aBreak, i, "cleared");
+ }
+ aBreak.splice(i, 1);
+ if (aBreak != this.aBreakExec) {
+ this.bus.removeMemBreak(addr, aBreak == this.aBreakWrite);
+ }
+ /*
+ * We'll mirror the logic in addBreakpoint() and leave the history buffer alone if this
+ * was a temporary breakpoint.
+ */
+ if (!dbgAddrBreak.fTemporary) {
+ this.historyInit();
+ }
+ break;
+ }
+ if (!fQuiet) this.printBreakpoint(aBreak, i, "exists");
+ break;
+ }
+ }
+ }
+ return fFound;
+ };
+
+ /**
+ * listBreakpoints(aBreak)
+ *
+ * @this {Debugger}
+ * @param {Array} aBreak
+ * @return {number} of breakpoints listed, 0 if none
+ */
+ Debugger.prototype.listBreakpoints = function(aBreak)
+ {
+ for (var i = 1; i < aBreak.length; i++) {
+ this.printBreakpoint(aBreak, i);
+ }
+ return aBreak.length - 1;
+ };
+
+ /**
+ * printBreakpoint(aBreak, i, sAction)
+ *
+ * @this {Debugger}
+ * @param {Array} aBreak
+ * @param {number} i
+ * @param {string} [sAction]
+ */
+ Debugger.prototype.printBreakpoint = function(aBreak, i, sAction)
+ {
+ var dbgAddr = aBreak[i];
+ this.println(aBreak[0] + ' ' + this.toHexAddr(dbgAddr) + (sAction? (' ' + sAction) : (dbgAddr.sCmd? (' "' + dbgAddr.sCmd + '"') : '')));
+ };
+
+ /**
+ * setTempBreakpoint(dbgAddr)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr of new temp breakpoint
+ */
+ Debugger.prototype.setTempBreakpoint = function(dbgAddr)
+ {
+ this.addBreakpoint(this.aBreakExec, dbgAddr, true);
+ };
+
+ /**
+ * clearTempBreakpoint(addr)
+ *
+ * @this {Debugger}
+ * @param {number|undefined} [addr] clear all temp breakpoints if no address specified
+ */
+ Debugger.prototype.clearTempBreakpoint = function(addr)
+ {
+ if (addr !== undefined) {
+ this.checkBreakpoint(addr, 1, this.aBreakExec, true);
+ this.nStep = 0;
+ } else {
+ for (var i = 1; i < this.aBreakExec.length; i++) {
+ var dbgAddrBreak = this.aBreakExec[i];
+ if (dbgAddrBreak.fTemporary) {
+ if (!this.findBreakpoint(this.aBreakExec, dbgAddrBreak, true, true)) break;
+ i = 0;
+ }
+ }
+ }
+ };
+
+ /**
+ * checkBreakpoint(addr, nb, aBreak, fTemporary)
+ *
+ * @this {Debugger}
+ * @param {number} addr
+ * @param {number} nb (# of bytes)
+ * @param {Array} aBreak
+ * @param {boolean} [fTemporary]
+ * @return {boolean} true if breakpoint has been hit, false if not
+ */
+ Debugger.prototype.checkBreakpoint = function(addr, nb, aBreak, fTemporary)
+ {
+ /*
+ * Time to check for execution breakpoints; note that this should be done BEFORE updating frequency
+ * or history data (see checkInstruction), since we might not actually execute the current instruction.
+ */
+ var fBreak = false;
+
+ if (!this.nSuppressBreaks++) {
+
+ for (var i = 1; !fBreak && i < aBreak.length; i++) {
+
+ var dbgAddrBreak = aBreak[i];
+
+ if (fTemporary && !dbgAddrBreak.fTemporary) continue;
+
+ /*
+ * We used to calculate the linear address of the breakpoint at the time the
+ * breakpoint was added, so that a breakpoint set in one mode (eg, in real-mode)
+ * would still work as intended if the mode changed later (eg, to protected-mode).
+ *
+ * However, that created difficulties setting protected-mode breakpoints in segments
+ * that might not be defined yet, or that could move in physical memory.
+ *
+ * If you want to create a real-mode breakpoint that will break regardless of mode,
+ * use the physical address of the real-mode memory location instead.
+ */
+ var addrBreak = this.getAddr(dbgAddrBreak);
+ for (var n = 0; n < nb; n++) {
+ if (addr + n == addrBreak) {
+ var a;
+ fBreak = true;
+ if (dbgAddrBreak.fTemporary) {
+ this.findBreakpoint(aBreak, dbgAddrBreak, true, true);
+ fTemporary = true;
+ }
+ if (a = dbgAddrBreak.aCmds) {
+ /*
+ * When one or more commands are attached to a breakpoint, we don't halt by default.
+ * Instead, we set fBreak to true only if, at the completion of all the commands, the
+ * CPU is halted; in other words, you should include "h" as one of the breakpoint commands
+ * if you want the breakpoint to stop execution.
+ *
+ * Another useful command is "if", which will return false if the expression is false,
+ * at which point we'll jump ahead to the next "else" command, and if there isn't an "else",
+ * we abort.
+ */
+ fBreak = false;
+ for (var j = 0; j < a.length; j++) {
+ if (!this.doCommand(a[j], true)) {
+ if (a[j].indexOf("if")) {
+ fBreak = true; // the failed command wasn't "if", so abort
+ break;
+ }
+ var k = j + 1;
+ for (; k < a.length; k++) {
+ if (!a[k].indexOf("else")) break;
+ j++;
+ }
+ if (k == a.length) { // couldn't find an "else" after the "if", so abort
+ fBreak = true;
+ break;
+ }
+ /*
+ * If we're still here, we'll execute the "else" command (which is just a no-op),
+ * followed by any remaining commands.
+ */
+ }
+ }
+ if (!this.cpu.isRunning()) fBreak = true;
+ }
+ if (fBreak) {
+ if (!fTemporary) this.printBreakpoint(aBreak, i, "hit");
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ this.nSuppressBreaks--;
+
+ return fBreak;
+ };
+
+ /**
+ * getInstruction(dbgAddr, sComment, nSequence)
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {string} [sComment] is an associated comment
+ * @param {number} [nSequence] is an associated sequence number, undefined if none
+ * @return {string} (and dbgAddr is updated to the next instruction)
+ */
+ Debugger.prototype.getInstruction = function(dbgAddr, sComment, nSequence)
+ {
+ var dbgAddrIns = this.newAddr(dbgAddr.addr);
+
+ var bOpcode = this.getByte(dbgAddr, 1);
+
+ var asOpcodes = this.style != Debugger.STYLE_8086? Debugger.INS_NAMES : Debugger.INS_NAMES_8086;
+ var aOpDesc = this.aaOpDescs[bOpcode];
+ var iIns = aOpDesc[0];
+
+ var sOperands = "";
+ var sOpcode = asOpcodes[iIns];
+ var cOperands = aOpDesc.length - 1;
+ var typeSizeDefault = Debugger.TYPE_NONE, type;
+
+ for (var iOperand = 1; iOperand <= cOperands; iOperand++) {
+
+ var disp, off, cch;
+ var sOperand = "";
+
+ type = aOpDesc[iOperand];
+ if (type === undefined) continue;
+ if ((type & Debugger.TYPE_OPT) && this.style == Debugger.STYLE_8080) continue;
+
+ var typeMode = type & Debugger.TYPE_MODE;
+ if (!typeMode) continue;
+
+ var typeSize = type & Debugger.TYPE_SIZE;
+ if (!typeSize) {
+ type |= typeSizeDefault;
+ } else {
+ typeSizeDefault = typeSize;
+ }
+
+ var typeOther = type & Debugger.TYPE_OTHER;
+ if (!typeOther) {
+ type |= (iOperand == 1? Debugger.TYPE_OUT : Debugger.TYPE_IN);
+ }
+
+ if (typeMode & Debugger.TYPE_IMM) {
+ sOperand = this.getImmOperand(type, dbgAddr);
+ }
+ else if (typeMode & Debugger.TYPE_REG) {
+ sOperand = this.getRegOperand((type & Debugger.TYPE_IREG) >> 8, type, dbgAddr);
+ }
+ else if (typeMode & Debugger.TYPE_INT) {
+ sOperand = ((bOpcode >> 3) & 0x7).toString();
+ }
+
+ if (!sOperand || !sOperand.length) {
+ sOperands = "INVALID";
+ break;
+ }
+ if (sOperands.length > 0) sOperands += ',';
+ sOperands += (sOperand || "???");
+ }
+
+ var sBytes = "";
+ var sLine = this.toHexAddr(dbgAddrIns) + ' ';
+ if (dbgAddrIns.addr !== CPUDef.ADDR_INVALID && dbgAddr.addr !== CPUDef.ADDR_INVALID) {
+ do {
+ sBytes += str.toHex(this.getByte(dbgAddrIns, 1), 2);
+ if (dbgAddrIns.addr == null) break;
+ } while (dbgAddrIns.addr != dbgAddr.addr);
+ }
+
+ sLine += str.pad(sBytes, 10);
+ sLine += (type & Debugger.TYPE_UNDOC)? '*' : ' ';
+ sLine += str.pad(sOpcode, 7);
+ if (sOperands) sLine += ' ' + sOperands;
+
+ if (sComment) {
+ sLine = str.pad(sLine, 40) + ';' + sComment;
+ if (!this.cpu.flags.fChecksum) {
+ sLine += (nSequence != null? '=' + nSequence.toString() : "");
+ } else {
+ var nCycles = this.cpu.getCycles();
+ sLine += "cycles=" + nCycles.toString() + " cs=" + str.toHex(this.cpu.aCounts.nChecksum);
+ }
+ }
+ return sLine;
+ };
+
+ /**
+ * getImmOperand(type, dbgAddr)
+ *
+ * @this {Debugger}
+ * @param {number} type
+ * @param {DbgAddr} dbgAddr
+ * @return {string} operand
+ */
+ Debugger.prototype.getImmOperand = function(type, dbgAddr)
+ {
+ var sOperand = ' ';
+ var typeSize = type & Debugger.TYPE_SIZE;
+
+ switch (typeSize) {
+ case Debugger.TYPE_BYTE:
+ sOperand = str.toHex(this.getByte(dbgAddr, 1), 2);
+ break;
+ case Debugger.TYPE_SBYTE:
+ sOperand = str.toHex((this.getByte(dbgAddr, 1) << 24) >> 24, 4);
+ break;
+ case Debugger.TYPE_WORD:
+ sOperand = str.toHex(this.getShort(dbgAddr, 2), 4);
+ break;
+ default:
+ return "imm(" + str.toHexWord(type) + ')';
+ }
+ if (this.style == Debugger.STYLE_8086 && (type & Debugger.TYPE_MEM)) {
+ sOperand = '[' + sOperand + ']';
+ } else if (!(type & Debugger.TYPE_REG)) {
+ sOperand = (this.style == Debugger.STYLE_8080? '$' : "0x") + sOperand;
+ }
+ return sOperand;
+ };
+
+ /**
+ * getRegOperand(iReg, type, dbgAddr)
+ *
+ * @this {Debugger}
+ * @param {number} iReg
+ * @param {number} type
+ * @param {DbgAddr} dbgAddr
+ * @return {string} operand
+ */
+ Debugger.prototype.getRegOperand = function(iReg, type, dbgAddr)
+ {
+ /*
+ * Although this breaks with 8080 assembler conventions, I'm going to experiment with some different
+ * mnemonics; specifically, "[HL]" instead of "M". This is also more in keeping with how getImmOperand()
+ * displays memory references (ie, by enclosing them in brackets).
+ */
+ var sOperand = Debugger.REGS[iReg];
+ if (this.style == Debugger.STYLE_8086 && (type & Debugger.TYPE_MEM)) {
+ if (iReg == Debugger.REG_M) {
+ sOperand = "HL";
+ }
+ sOperand = '[' + sOperand + ']';
+ }
+ return sOperand;
+ };
+
+ /**
+ * parseInstruction(sOp, sOperand, addr)
+ *
+ * TODO: Unimplemented. See parseInstruction() in modules/c1pjs/lib/debugger.js for a working implementation.
+ *
+ * @this {Debugger}
+ * @param {string} sOp
+ * @param {string|undefined} sOperand
+ * @param {DbgAddr} dbgAddr of memory where this instruction is being assembled
+ * @return {Array.} of opcode bytes; if the instruction can't be parsed, the array will be empty
+ */
+ Debugger.prototype.parseInstruction = function(sOp, sOperand, dbgAddr)
+ {
+ var aOpBytes = [];
+ this.println("not supported yet");
+ return aOpBytes;
+ };
+
+ /**
+ * getFlagOutput(sFlag)
+ *
+ * @this {Debugger}
+ * @param {string} sFlag
+ * @return {string} value of flag
+ */
+ Debugger.prototype.getFlagOutput = function(sFlag)
+ {
+ var b;
+ switch (sFlag) {
+ case "IF":
+ b = this.cpu.getIF();
+ break;
+ case "SF":
+ b = this.cpu.getSF();
+ break;
+ case "ZF":
+ b = this.cpu.getZF();
+ break;
+ case "AF":
+ b = this.cpu.getAF();
+ break;
+ case "PF":
+ b = this.cpu.getPF();
+ break;
+ case "CF":
+ b = this.cpu.getCF();
+ break;
+ default:
+ b = 0;
+ break;
+ }
+ return sFlag.charAt(0) + (b? '1' : '0') + ' ';
+ };
+
+ /**
+ * getRegOutput(iReg)
+ *
+ * @this {Debugger}
+ * @param {number} iReg
+ * @return {string}
+ */
+ Debugger.prototype.getRegOutput = function(iReg)
+ {
+ var sReg = Debugger.REGS[iReg];
+ return sReg + '=' + this.getRegString(iReg) + ' ';
+ };
+
+ /**
+ * getRegDump()
+ *
+ * Sample 8080 register dump:
+ *
+ * A=00 BC=0000 DE=0000 HL=0000 SP=0000 I0 S0 Z0 A0 P0 C0
+ * 0000 00 NOP
+ *
+ * @this {Debugger}
+ * @return {string}
+ */
+ Debugger.prototype.getRegDump = function()
+ {
+ var s;
+ s = this.getRegOutput(Debugger.REG_A) +
+ this.getRegOutput(Debugger.REG_BC) +
+ this.getRegOutput(Debugger.REG_DE) +
+ this.getRegOutput(Debugger.REG_HL) +
+ this.getRegOutput(Debugger.REG_SP) +
+ this.getFlagOutput("IF") + this.getFlagOutput("SF") + this.getFlagOutput("ZF") +
+ this.getFlagOutput("AF") + this.getFlagOutput("PF") + this.getFlagOutput("CF");
+ return s;
+ };
+
+ Debugger.aBinOpPrecedence = {
+ '||': 0, // logical OR
+ '&&': 1, // logical AND
+ '|': 2, // bitwise OR
+ '^': 3, // bitwise XOR
+ '&': 4, // bitwise AND
+ '!=': 5, // inequality
+ '==': 5, // equality
+ '>=': 6, // greater than or equal to
+ '>': 6, // greater than
+ '<=': 6, // less than or equal to
+ '<': 6, // less than
+ '>>>': 7, // unsigned bitwise right shift
+ '>>': 7, // bitwise right shift
+ '<<': 7, // bitwise left shift
+ '-': 8, // subtraction
+ '+': 8, // addition
+ '%': 9, // remainder
+ '/': 9, // division
+ '*': 9 // multiplication
+ };
+
+ /**
+ * evalExpression(aVals, aOps, cOps)
+ *
+ * In Node, if you set a variable to 0x80000001; ie:
+ *
+ * foo=0x80000001|0
+ *
+ * and then calculate foo*foo using "(foo*foo).toString(2)", the result is:
+ *
+ * '11111111111111111111111111111100000000000000000000000000000000'
+ *
+ * which is slightly incorrect because it has overflowed JavaScript's floating-point precision.
+ *
+ * 0x80000001 in decimal is -2147483647, so the product is 4611686014132420609, which is 0x3FFFFFFF00000001.
+ *
+ * @this {Debugger}
+ * @param {Array.} aVals
+ * @param {Array.} aOps
+ * @param {number} [cOps] (default is all)
+ * @return {boolean} true if successful, false if error
+ */
+ Debugger.prototype.evalExpression = function(aVals, aOps, cOps)
+ {
+ cOps = cOps || -1;
+ while (cOps-- && aOps.length) {
+ var chOp = aOps.pop();
+ if (aVals.length < 2) return false;
+ var valNew;
+ var val2 = aVals.pop();
+ var val1 = aVals.pop();
+ switch(chOp) {
+ case '*':
+ valNew = val1 * val2;
+ break;
+ case '/':
+ if (!val2) return false;
+ valNew = val1 / val2;
+ break;
+ case '%':
+ if (!val2) return false;
+ valNew = val1 % val2;
+ break;
+ case '+':
+ valNew = val1 + val2;
+ break;
+ case '-':
+ valNew = val1 - val2;
+ break;
+ case '<<':
+ valNew = val1 << val2;
+ break;
+ case '>>':
+ valNew = val1 >> val2;
+ break;
+ case '>>>':
+ valNew = val1 >>> val2;
+ break;
+ case '<':
+ valNew = (val1 < val2? 1 : 0);
+ break;
+ case '<=':
+ valNew = (val1 <= val2? 1 : 0);
+ break;
+ case '>':
+ valNew = (val1 > val2? 1 : 0);
+ break;
+ case '>=':
+ valNew = (val1 >= val2? 1 : 0);
+ break;
+ case '==':
+ valNew = (val1 == val2? 1 : 0);
+ break;
+ case '!=':
+ valNew = (val1 != val2? 1 : 0);
+ break;
+ case '&':
+ valNew = val1 & val2;
+ break;
+ case '^':
+ valNew = val1 ^ val2;
+ break;
+ case '|':
+ valNew = val1 | val2;
+ break;
+ case '&&':
+ valNew = (val1 && val2? 1 : 0);
+ break;
+ case '||':
+ valNew = (val1 || val2? 1 : 0);
+ break;
+ default:
+ return false;
+ }
+ aVals.push(valNew|0);
+ }
+ return true;
+ };
+
+ /**
+ * parseExpression(sExp, fPrint)
+ *
+ * A quick-and-dirty expression parser. It takes an expression like:
+ *
+ * EDX+EDX*4+12345678
+ *
+ * and builds a value stack in aVals and a "binop" (binary operator) stack in aOps:
+ *
+ * aVals aOps
+ * ----- ----
+ * EDX +
+ * EDX *
+ * 4 +
+ * ...
+ *
+ * We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
+ * predecessor is encountered, evaluate, and push the result back onto aVals.
+ *
+ * Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
+ *
+ * However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
+ * {...} (braces), as well as address references by using [...] (brackets).
+ *
+ * Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
+ * multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
+ * and I didn't want parentheses taking on a new meaning in message strings.
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sExp
+ * @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
+ * @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
+ */
+ Debugger.prototype.parseExpression = function(sExp, fPrint)
+ {
+ var value;
+
+ if (sExp) {
+ /*
+ * First process (and eliminate) any references, aka sub-expressions.
+ */
+ sExp = this.parseReference(sExp);
+
+ var i = 0;
+ var fError = false;
+ var sExpOrig = sExp;
+ var aVals = [], aOps = [];
+ /*
+ * All browsers (including, I believe, IE9 and up) support the following idiosyncrasy of a regexp split():
+ * when the regexp uses a capturing pattern, the resulting array will include entries for all the pattern
+ * matches along with the non-matches. This effectively means that, in the set of expressions that we
+ * support, all even entries in asValues will contain "values" and all odd entries will contain "operators".
+ *
+ * And although I tried to list the supported operators in "precedential" order, bitwise operators must
+ * be out-of-order so that we don't mistakenly match either '>' or '<' when they're part of '>>' or '<<'.
+ */
+ var regExp = /(\|\||&&|\||^|&|!=|==|>=|>>>|>>|>|<=|<<|<|-|\+|%|\/|\*)/;
+ var asValues = sExp.split(regExp);
+ while (i < asValues.length) {
+ var sValue = asValues[i++];
+ var cchValue = sValue.length;
+ var s = str.trim(sValue);
+ if (!s) {
+ fError = true;
+ break;
+ }
+ var v = this.parseValue(s, null, fPrint === false);
+ if (v === undefined) {
+ fError = true;
+ fPrint = false;
+ break;
+ }
+ aVals.push(v);
+ if (i == asValues.length) break;
+ var sOp = asValues[i++], cchOp = sOp.length;
+ this.assert(Debugger.aBinOpPrecedence[sOp] != null);
+ if (aOps.length && Debugger.aBinOpPrecedence[sOp] < Debugger.aBinOpPrecedence[aOps[aOps.length-1]]) {
+ this.evalExpression(aVals, aOps, 1);
+ }
+ aOps.push(sOp);
+ sExp = sExp.substr(cchValue + cchOp);
+ }
+ if (!this.evalExpression(aVals, aOps) || aVals.length != 1) {
+ fError = true;
+ }
+ if (!fError) {
+ value = aVals.pop();
+ if (fPrint) this.printValue(null, value);
+ } else {
+ if (fPrint) this.println("error parsing '" + sExpOrig + "' at character " + (sExpOrig.length - sExp.length));
+ }
+ }
+ return value;
+ };
+
+ /**
+ * parseReference(s)
+ *
+ * Returns the given string with any "{expression}" sequences replaced with the value of the expression,
+ * and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
+ * addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
+ * (define intermediate variables if needed).
+ *
+ * @this {Debugger}
+ * @param {string} s
+ * @return {string}
+ */
+ Debugger.prototype.parseReference = function(s)
+ {
+ var a;
+ while (a = s.match(/\{(.*?)}/)) {
+ if (a[1].indexOf('{') >= 0) break; // unsupported nested brace(s)
+ var value = this.parseExpression(a[1]);
+ s = s.replace('{' + a[1] + '}', value != null? str.toHex(value) : "undefined");
+ }
+ while (a = s.match(/\[(.*?)]/)) {
+ if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
+ var dbgAddr = this.parseAddr(a[1]);
+ s = s.replace('[' + a[1] + ']', dbgAddr? str.toHex(this.getWord(dbgAddr), 4) : "undefined");
+ }
+ return this.parseSysVars(s);
+ };
+
+ /**
+ * parseSysVars(s)
+ *
+ * Returns the given string with any recognized "$var" replaced with its value; eg:
+ *
+ * $ops: the number of opcodes executed since the last time it was displayed (or reset)
+ *
+ * @this {Debugger}
+ * @param {string} s
+ * @return {string}
+ */
+ Debugger.prototype.parseSysVars = function(s)
+ {
+ var a;
+ while (a = s.match(/\$([a-z]+)/i)) {
+ var v = null;
+ switch(a[1].toLowerCase()) {
+ case "ops":
+ v = this.cOpcodes - this.cOpcodesStart;
+ break;
+ }
+ if (v == null) break;
+ s = s.replace(a[0], v.toString());
+ }
+ return s;
+ };
+
+ /**
+ * parseValue(sValue, sName, fQuiet)
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sValue
+ * @param {string|null} [sName] is the name of the value, if any
+ * @param {boolean} [fQuiet]
+ * @return {number|undefined} numeric value, or undefined if sValue is either undefined or invalid
+ */
+ Debugger.prototype.parseValue = function(sValue, sName, fQuiet)
+ {
+ var value;
+ if (sValue !== undefined) {
+ var iReg = this.getRegIndex(sValue);
+ if (iReg >= 0) {
+ value = this.getRegValue(iReg);
+ } else {
+ value = this.getVariable(sValue);
+ if (value === undefined) value = str.parseInt(sValue);
+ }
+ if (value === undefined && !fQuiet) this.println("invalid " + (sName? sName : "value") + ": " + sValue);
+ } else {
+ if (!fQuiet) this.println("missing " + (sName || "value"));
+ }
+ return value;
+ };
+
+ /**
+ * printValue(sVar, value)
+ *
+ * @this {Debugger}
+ * @param {string|null} sVar
+ * @param {number|undefined} value
+ * @return {boolean} true if value defined, false if not
+ */
+ Debugger.prototype.printValue = function(sVar, value)
+ {
+ var sValue;
+ var fDefined = false;
+ if (value !== undefined) {
+ fDefined = true;
+ sValue = str.toHexLong(value) + " " + value + ". (" + str.toBinBytes(value) + ")";
+ }
+ sVar = (sVar != null? (sVar + ": ") : "");
+ this.println(sVar + sValue);
+ return fDefined;
+ };
+
+ /**
+ * printVariable(sVar)
+ *
+ * @this {Debugger}
+ * @param {string} [sVar]
+ * @return {boolean} true if all value(s) defined, false if not
+ */
+ Debugger.prototype.printVariable = function(sVar)
+ {
+ if (sVar) {
+ return this.printValue(sVar, this.aVariables[sVar]);
+ }
+ var cVariables = 0;
+ for (sVar in this.aVariables) {
+ this.printValue(sVar, this.aVariables[sVar]);
+ cVariables++;
+ }
+ return cVariables > 0;
+ };
+
+ /**
+ * delVariable(sVar)
+ *
+ * @this {Debugger}
+ * @param {string} sVar
+ */
+ Debugger.prototype.delVariable = function(sVar)
+ {
+ delete this.aVariables[sVar];
+ };
+
+ /**
+ * getVariable(sVar)
+ *
+ * @this {Debugger}
+ * @param {string} sVar
+ * @return {number|undefined}
+ */
+ Debugger.prototype.getVariable = function(sVar)
+ {
+ return this.aVariables[sVar];
+ };
+
+ /**
+ * setVariable(sVar, value)
+ *
+ * @this {Debugger}
+ * @param {string} sVar
+ * @param {number} value
+ */
+ Debugger.prototype.setVariable = function(sVar, value)
+ {
+ this.aVariables[sVar] = value;
+ };
+
+ /**
+ * comparePairs(p1, p2)
+ *
+ * @this {Debugger}
+ * @param {number|string|Array|Object} p1
+ * @param {number|string|Array|Object} p2
+ * @return {number}
+ */
+ Debugger.prototype.comparePairs = function(p1, p2)
+ {
+ return p1[0] > p2[0]? 1 : p1[0] < p2[0]? -1 : 0;
+ };
+
+ /**
+ * addSymbols(sModule, addr, len, aSymbols)
+ *
+ * As filedump.js (formerly convrom.php) explains, aSymbols is a JSON-encoded object whose properties consist
+ * of all the symbols (in upper-case), and the values of those properties are objects containing any or all of
+ * the following properties:
+ *
+ * 'v': the value of an absolute (unsized) value
+ * 'b': either 1, 2, 4 or undefined if an unsized value
+ * 's': either a hard-coded segment or undefined
+ * 'o': the offset of the symbol within the associated address space
+ * 'l': the original-case version of the symbol, present only if it wasn't originally upper-case
+ * 'a': annotation for the specified offset; eg, the original assembly language, with optional comment
+ *
+ * To that list of properties, we also add:
+ *
+ * 'p': the physical address (calculated whenever both 's' and 'o' properties are defined)
+ *
+ * Note that values for any 'v', 'b', 's' and 'o' properties are unquoted decimal values, and the values
+ * for any 'l' or 'a' properties are quoted strings. Also, if double-quotes were used in any of the original
+ * annotation ('a') values, they will have been converted to two single-quotes, so we're responsible for
+ * converting them back to individual double-quotes.
+ *
+ * For example:
+ * {
+ * 'HF_PORT': {
+ * 'v':800
+ * },
+ * 'HDISK_INT': {
+ * 'b':4, 's':0, 'o':52
+ * },
+ * 'ORG_VECTOR': {
+ * 'b':4, 's':0, 'o':76
+ * },
+ * 'CMD_BLOCK': {
+ * 'b':1, 's':64, 'o':66
+ * },
+ * 'DISK_SETUP': {
+ * 'o':3
+ * },
+ * '.40': {
+ * 'o':40, 'a':"MOV AX,WORD PTR ORG_VECTOR ;GET DISKETTE VECTOR"
+ * }
+ * }
+ *
+ * If a symbol only has an offset, then that offset value can be assigned to the symbol property directly:
+ *
+ * 'DISK_SETUP': 3
+ *
+ * The last property is an example of an "anonymous" entry, for offsets where there is no associated symbol.
+ * Such entries are identified by a period followed by a unique number (usually the offset of the entry), and
+ * they usually only contain offset ('o') and annotation ('a') properties. I could eliminate the leading
+ * period, but it offers a very convenient way of quickly discriminating among genuine vs. anonymous symbols.
+ *
+ * We add all these entries to our internal symbol table, which is an array of 4-element arrays, each of which
+ * look like:
+ *
+ * [addr, len, aSymbols, aOffsets]
+ *
+ * There are two basic symbol operations: findSymbol(), which takes an address and finds the symbol, if any,
+ * at that address, and findSymbolAddr(), which takes a string and attempts to match it to a non-anonymous
+ * symbol with a matching offset ('o') property.
+ *
+ * To implement findSymbol() efficiently, addSymbols() creates an array of [offset, sSymbol] pairs
+ * (aOffsets), one pair for each symbol that corresponds to an offset within the specified address space.
+ *
+ * We guarantee the elements of aOffsets are in offset order, because we build it using binaryInsert();
+ * it's quite likely that the MAP file already ordered all its symbols in offset order, but since they're
+ * hand-edited files, we can't assume that, and we need to ensure that findSymbol()'s binarySearch() operates
+ * properly.
+ *
+ * @this {Debugger}
+ * @param {string|null} sModule
+ * @param {number|null} addr (physical address where the symbols are located, if the memory is physical; eg, ROM)
+ * @param {number} len (the size of the region, in bytes)
+ * @param {Object} aSymbols (collection of symbols in this group; the format of this collection is described below)
+ */
+ Debugger.prototype.addSymbols = function(sModule, addr, len, aSymbols)
+ {
+ var dbgAddr = {};
+ var aOffsets = [];
+ for (var sSymbol in aSymbols) {
+ var symbol = aSymbols[sSymbol];
+ if (typeof symbol == "number") {
+ aSymbols[sSymbol] = symbol = {'o': symbol};
+ }
+ var offSymbol = symbol['o'];
+ var sAnnotation = symbol['a'];
+ if (offSymbol !== undefined) {
+ usr.binaryInsert(aOffsets, [offSymbol >>> 0, sSymbol], this.comparePairs);
+ }
+ if (sAnnotation) symbol['a'] = sAnnotation.replace(/''/g, "\"");
+ }
+ var symbolTable = {
+ sModule: sModule,
+ addr: addr,
+ len: len,
+ aSymbols: aSymbols,
+ aOffsets: aOffsets
+ };
+ this.aSymbolTable.push(symbolTable);
+ };
+
+ /**
+ * dumpSymbols()
+ *
+ * TODO: Add "numerical" and "alphabetical" dump options. This is simply dumping them in whatever
+ * order they appeared in the original MAP file.
+ *
+ * @this {Debugger}
+ */
+ Debugger.prototype.dumpSymbols = function()
+ {
+ for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
+ var symbolTable = this.aSymbolTable[iTable];
+ for (var sSymbol in symbolTable.aSymbols) {
+ if (sSymbol.charAt(0) == '.') continue;
+ var symbol = symbolTable.aSymbols[sSymbol];
+ var offSymbol = symbol['o'];
+ if (offSymbol === undefined) continue;
+ var sSymbolOrig = symbolTable.aSymbols[sSymbol]['l'];
+ if (sSymbolOrig) sSymbol = sSymbolOrig;
+ this.println(this.toHexOffset(offSymbol) + ' ' + sSymbol);
+ }
+ }
+ };
+
+ /**
+ * findSymbol(dbgAddr, fNearest)
+ *
+ * Search aSymbolTable for dbgAddr, and return an Array for the corresponding symbol (empty if not found).
+ *
+ * If fNearest is true, and no exact match was found, then the Array returned will contain TWO sets of
+ * entries: [0]-[3] will refer to closest preceding symbol, and [4]-[7] will refer to the closest subsequent symbol.
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @param {boolean} [fNearest]
+ * @return {Array} where [0] == symbol name, [1] == symbol value, [2] == any annotation, and [3] == any associated comment
+ */
+ Debugger.prototype.findSymbol = function(dbgAddr, fNearest)
+ {
+ var aSymbol = [];
+ var addrSymbol = this.getAddr(dbgAddr) >>> 0;
+ for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
+ var symbolTable = this.aSymbolTable[iTable];
+ var addr = symbolTable.addr >>> 0;
+ var len = symbolTable.len;
+ if (addrSymbol >= addr && addrSymbol < addr + len) {
+ var offSymbol = addrSymbol - addr;
+ var result = usr.binarySearch(symbolTable.aOffsets, [offSymbol], this.comparePairs);
+ if (result >= 0) {
+ this.returnSymbol(iTable, result, aSymbol);
+ }
+ else if (fNearest) {
+ result = ~result;
+ this.returnSymbol(iTable, result-1, aSymbol);
+ this.returnSymbol(iTable, result, aSymbol);
+ }
+ break;
+ }
+ }
+ return aSymbol;
+ };
+
+ /**
+ * findSymbolAddr(sSymbol)
+ *
+ * Search aSymbolTable for sSymbol, and if found, return a dbgAddr (same as parseAddr())
+ *
+ * @this {Debugger}
+ * @param {string} sSymbol
+ * @return {DbgAddr|undefined}
+ */
+ Debugger.prototype.findSymbolAddr = function(sSymbol)
+ {
+ var dbgAddr;
+ if (sSymbol.match(/^[a-z_][a-z0-9_]*$/i)) {
+ var sUpperCase = sSymbol.toUpperCase();
+ for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
+ var symbolTable = this.aSymbolTable[iTable];
+ var symbol = symbolTable.aSymbols[sUpperCase];
+ if (symbol !== undefined) {
+ var offSymbol = symbol['o'];
+ if (offSymbol !== undefined) {
+ /*
+ * We assume that every ROM is ORG'ed at 0x0000, and therefore unless the symbol has an
+ * explicitly-defined segment, we return the segment associated with the entire group; for
+ * a ROM, that segment is normally "addrROM >>> 4". Down the road, we may want/need to
+ * support a special symbol entry (eg, ".ORG") that defines an alternate origin.
+ */
+ dbgAddr = this.newAddr(offSymbol);
+ }
+ /*
+ * The symbol matched, but it wasn't for an address (no 'o' offset), and there's no point
+ * looking any farther, since each symbol appears only once, so we indicate it's an unknown symbol.
+ */
+ break;
+ }
+ }
+ }
+ return dbgAddr;
+ };
+
+ /**
+ * returnSymbol(iTable, iOffset, aSymbol)
+ *
+ * Helper function for findSymbol().
+ *
+ * @param {number} iTable
+ * @param {number} iOffset
+ * @param {Array} aSymbol is updated with the specified symbol, if it exists
+ */
+ Debugger.prototype.returnSymbol = function(iTable, iOffset, aSymbol)
+ {
+ var symbol = {};
+ var aOffsets = this.aSymbolTable[iTable].aOffsets;
+ var offset = 0, sSymbol = null;
+ if (iOffset >= 0 && iOffset < aOffsets.length) {
+ offset = aOffsets[iOffset][0];
+ sSymbol = aOffsets[iOffset][1];
+ }
+ if (sSymbol) {
+ symbol = this.aSymbolTable[iTable].aSymbols[sSymbol];
+ sSymbol = (sSymbol.charAt(0) == '.'? null : (symbol['l'] || sSymbol));
+ }
+ aSymbol.push(sSymbol);
+ aSymbol.push(offset);
+ aSymbol.push(symbol['a']);
+ aSymbol.push(symbol['c']);
+ };
+
+ /**
+ * doHelp()
+ *
+ * @this {Debugger}
+ */
+ Debugger.prototype.doHelp = function()
+ {
+ var s = "commands:";
+ for (var sCommand in Debugger.COMMANDS) {
+ s += '\n' + str.pad(sCommand, 7) + Debugger.COMMANDS[sCommand];
+ }
+ if (!this.checksEnabled()) s += "\nnote: frequency/history disabled if no exec breakpoints";
+ this.println(s);
+ };
+
+ /**
+ * doAssemble(asArgs)
+ *
+ * This always receives the complete argument array, where the order of the arguments is:
+ *
+ * [0]: the assemble command (assumed to be "a")
+ * [1]: the target address (eg, "200")
+ * [2]: the operation code, aka instruction name (eg, "adc")
+ * [3]: the operation mode operand, if any (eg, "14", "[1234]", etc)
+ *
+ * The Debugger enters "assemble mode" whenever only the first (or first and second) arguments are present.
+ * As long as "assemble mode is active, the user can omit the first two arguments on all later assemble commands
+ * until "assemble mode" is cancelled with an empty command line; the command processor automatically prepends "a"
+ * and the next available target address to the argument array.
+ *
+ * Entering "assemble mode" is optional; one could enter a series of fully-qualified assemble commands; eg:
+ *
+ * a ff00 cld
+ * a ff01 ldx 28
+ * ...
+ *
+ * without ever entering "assemble mode", but of course, that requires more typing and doesn't take advantage
+ * of automatic target address advancement (see dbgAddrAssemble).
+ *
+ * NOTE: As the previous example implies, you can even assemble new instructions into ROM address space;
+ * as our setByte() function explains, the ROM write-notification handlers only refuse writes from the CPU.
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs is the complete argument array, beginning with the "a" command in asArgs[0]
+ */
+ Debugger.prototype.doAssemble = function(asArgs)
+ {
+ var dbgAddr = this.parseAddr(asArgs[1], true);
+ if (!dbgAddr) return;
+
+ this.dbgAddrAssemble = dbgAddr;
+ if (asArgs[2] === undefined) {
+ this.println("begin assemble at " + this.toHexAddr(dbgAddr));
+ this.fAssemble = true;
+ this.cpu.updateCPU();
+ return;
+ }
+
+ var aOpBytes = this.parseInstruction(asArgs[2], asArgs[3], dbgAddr);
+ if (aOpBytes.length) {
+ for (var i = 0; i < aOpBytes.length; i++) {
+ this.setByte(dbgAddr, aOpBytes[i], 1);
+ }
+ /*
+ * Since getInstruction() also updates the specified address, dbgAddrAssemble is automatically advanced.
+ */
+ this.println(this.getInstruction(this.dbgAddrAssemble));
+ }
+ };
+
+ /**
+ * doBreak(sCmd, sAddr, sOptions)
+ *
+ * As the "help" output below indicates, the following breakpoint commands are supported:
+ *
+ * bp [a] set exec breakpoint on linear addr [a]
+ * br [a] set read breakpoint on linear addr [a]
+ * bw [a] set write breakpoint on linear addr [a]
+ * bc [a] clear breakpoint on linear addr [a] (use "*" for all breakpoints)
+ * bl list breakpoints
+ *
+ * to which we have recently added the following I/O breakpoint commands:
+ *
+ * bi [p] toggle input breakpoint on port [p] (use "*" for all input ports)
+ * bo [p] toggle output breakpoint on port [p] (use "*" for all output ports)
+ *
+ * These two new commands operate as toggles so that if "*" is used to trap all input (or output),
+ * you can also use these commands to NOT trap specific ports.
+ *
+ * bn [n] break after [n] instructions
+ *
+ * TODO: Update the "bl" command to include any/all I/O breakpoints, and the "bc" command to
+ * clear them. Because "bi" and "bo" commands are piggy-backing on Bus functions, those breakpoints
+ * are currently outside the realm of what the "bl" and "bc" commands are aware of.
+ *
+ * @this {Debugger}
+ * @param {string} sCmd
+ * @param {string|undefined} [sAddr]
+ * @param {string} [sOptions] (the rest of the breakpoint command-line)
+ */
+ Debugger.prototype.doBreak = function(sCmd, sAddr, sOptions)
+ {
+ if (sAddr == '?') {
+ this.println("breakpoint commands:");
+ this.println("\tbi [p]\ttoggle break on input port [p]");
+ this.println("\tbo [p]\ttoggle break on output port [p]");
+ this.println("\tbp [a]\tset exec breakpoint at addr [a]");
+ this.println("\tbr [a]\tset read breakpoint at addr [a]");
+ this.println("\tbw [a]\tset write breakpoint at addr [a]");
+ this.println("\tbc [a]\tclear breakpoint at addr [a]");
+ this.println("\tbl\tlist all breakpoints");
+ this.println("\tbn [n]\tbreak after [n] instruction(s)");
+ return;
+ }
+
+ var sParm = sCmd.charAt(1);
+ if (sParm == 'l') {
+ var cBreaks = 0;
+ cBreaks += this.listBreakpoints(this.aBreakExec);
+ cBreaks += this.listBreakpoints(this.aBreakRead);
+ cBreaks += this.listBreakpoints(this.aBreakWrite);
+ if (!cBreaks) this.println("no breakpoints");
+ return;
+ }
+
+ if (sParm == 'n') {
+ this.nBreakIns = this.parseValue(sAddr);
+ this.println("break after " + this.nBreakIns + " instruction(s)");
+ return;
+ }
+
+ if (sAddr === undefined) {
+ this.println("missing breakpoint address");
+ return;
+ }
+
+ var dbgAddr = this.newAddr();
+ if (sAddr != '*') {
+ dbgAddr = this.parseAddr(sAddr, true, true);
+ if (!dbgAddr) return;
+ }
+
+ sAddr = str.toHexWord(dbgAddr.addr);
+
+ if (sParm == 'c') {
+ if (dbgAddr.addr == null) {
+ this.clearBreakpoints();
+ this.println("all breakpoints cleared");
+ return;
+ }
+ if (this.findBreakpoint(this.aBreakExec, dbgAddr, true))
+ return;
+ if (this.findBreakpoint(this.aBreakRead, dbgAddr, true))
+ return;
+ if (this.findBreakpoint(this.aBreakWrite, dbgAddr, true))
+ return;
+ this.println("breakpoint missing: " + this.toHexAddr(dbgAddr));
+ return;
+ }
+
+ if (sParm == 'i') {
+ this.println("breakpoint " + (this.bus.addPortInputBreak(dbgAddr.addr)? "enabled" : "cleared") + ": port " + sAddr + " (input)");
+ return;
+ }
+
+ if (sParm == 'o') {
+ this.println("breakpoint " + (this.bus.addPortOutputBreak(dbgAddr.addr)? "enabled" : "cleared") + ": port " + sAddr + " (output)");
+ return;
+ }
+
+ if (dbgAddr.addr == null) return;
+
+ this.parseAddrOptions(dbgAddr, sOptions);
+
+ if (sParm == 'p') {
+ this.addBreakpoint(this.aBreakExec, dbgAddr);
+ return;
+ }
+ if (sParm == 'r') {
+ this.addBreakpoint(this.aBreakRead, dbgAddr);
+ return;
+ }
+ if (sParm == 'w') {
+ this.addBreakpoint(this.aBreakWrite, dbgAddr);
+ return;
+ }
+ this.println("unknown breakpoint command: " + sParm);
+ };
+
+ /**
+ * doClear(sCmd)
+ *
+ * @this {Debugger}
+ * @param {string} [sCmd] (eg, "cls" or "clear")
+ */
+ Debugger.prototype.doClear = function(sCmd)
+ {
+ /*
+ * TODO: There should be a clear() component method that the Control Panel overrides to perform this function.
+ */
+ if (this.controlPrint) this.controlPrint.value = "";
+ };
+
+ /**
+ * doDump(asArgs)
+ *
+ * The length parameter is interpreted as a number of bytes, in hex, which we convert to the appropriate number
+ * of lines, because we always display whole lines. If the length is omitted/undefined, it defaults to 0x80 (128.)
+ * bytes, which normally translates to 8 lines.
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs (formerly sCmd, [sAddr], [sLen] and [sBytes])
+ */
+ Debugger.prototype.doDump = function(asArgs)
+ {
+ var m;
+ var sCmd = asArgs[0];
+ var sAddr = asArgs[1];
+ var sLen = asArgs[2];
+ var sBytes = asArgs[3];
+
+ if (sAddr == '?') {
+ var sDumpers = "";
+ for (m in Debugger.MESSAGES) {
+ if (this.afnDumpers[m]) {
+ if (sDumpers) sDumpers += ',';
+ sDumpers = sDumpers + m;
+ }
+ }
+ sDumpers += ",state,symbols";
+ this.println("dump memory commands:");
+ this.println("\tdb [a] [#] dump # bytes at address a");
+ this.println("\tdw [a] [#] dump # words at address a");
+ this.println("\tdd [a] [#] dump # dwords at address a");
+ this.println("\tdh [#] [#] dump # instructions from history");
+ if (sDumpers.length) this.println("dump extension commands:\n\t" + sDumpers);
+ return;
+ }
+
+ if (sAddr == "state") {
+ var sState = this.cmp.powerOff(true);
+ if (sLen == "console") {
+ /*
+ * Console buffers are notoriously small, and even the following code, which breaks the
+ * data into parts (eg, "d state console 1", "d state console 2", etc) just isn't that helpful.
+ *
+ * var nPart = +sBytes;
+ * if (nPart) sState = sState.substr(1000000 * (nPart-1), 1000000);
+ *
+ * So, the best way to capture a large machine state is to use the new "Save Machine" link
+ * that downloads a machine's entire state. Alternatively, run your own local server and use
+ * server-side storage. Take a look at the "Save" binding in computer.js, which binds an HTML
+ * control to the computer.powerOff() and computer.saveServerState() functions.
+ */
+ console.log(sState);
+ } else {
+ this.doClear();
+ this.println(sState);
+ }
+ return;
+ }
+
+ if (sAddr == "symbols") {
+ this.dumpSymbols();
+ return;
+ }
+
+ if (sCmd == "d") {
+ for (m in Debugger.MESSAGES) {
+ if (asArgs[1] == m) {
+ var fnDumper = this.afnDumpers[m];
+ if (fnDumper) {
+ asArgs.shift();
+ asArgs.shift();
+ fnDumper(asArgs);
+ } else {
+ this.println("no dump registered for " + sAddr);
+ }
+ return;
+ }
+ }
+ if (!sAddr) sCmd = this.sCmdDumpPrev || "db";
+ } else {
+ this.sCmdDumpPrev = sCmd;
+ }
+
+ if (sCmd == "dh") {
+ this.dumpHistory(sAddr, sLen);
+ return;
+ }
+
+ var dbgAddr = this.parseAddr(sAddr);
+ if (!dbgAddr) return;
+
+ var len = 0; // 0 is not a default; it triggers the appropriate default below
+ if (sLen) {
+ if (sLen.charAt(0) == 'l') {
+ sLen = sLen.substr(1) || sBytes;
+ }
+ len = this.parseValue(sLen) >>> 0; // negative lengths not allowed
+ if (len > 0x10000) len = 0x10000; // prevent bad user (or variable) input from producing excessive output
+ }
+
+ var sDump = "";
+ var size = (sCmd == "dd"? 4 : (sCmd == "dw"? 2 : 1));
+ var cb = (size * len) || 128;
+ var cLines = ((cb + 15) >> 4) || 1;
+
+ while (cLines-- && cb > 0) {
+ var data = 0, iByte = 0, i;
+ var sData = "", sChars = "";
+ sAddr = this.toHexAddr(dbgAddr);
+ for (i = 16; i > 0 && cb > 0; i--) {
+ var b = this.getByte(dbgAddr, 1);
+ data |= (b << (iByte++ << 3));
+ if (iByte == size) {
+ sData += str.toHex(data, size * 2);
+ sData += (size == 1? (i == 9? '-' : ' ') : " ");
+ data = iByte = 0;
+ }
+ sChars += (b >= 32 && b < 128? String.fromCharCode(b) : '.');
+ cb--;
+ }
+ if (sDump) sDump += '\n';
+ sDump += sAddr + " " + sData + ((i == 0)? (' ' + sChars) : "");
+ }
+
+ if (sDump) this.println(sDump);
+ this.dbgAddrNextData = dbgAddr;
+ };
+
+ /**
+ * doEdit(asArgs)
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs
+ */
+ Debugger.prototype.doEdit = function(asArgs)
+ {
+ var size = 1;
+ var mask = 0xff;
+ var fnGet = this.getByte;
+ var fnSet = this.setByte;
+ if (asArgs[0] == "ew") {
+ size = 2;
+ mask = 0xffff;
+ fnGet = this.getShort;
+ fnSet = this.setShort;
+ }
+ var cch = size << 1;
+
+ var sAddr = asArgs[1];
+ if (sAddr == null) {
+ this.println("edit memory commands:");
+ this.println("\teb [a] [...] edit bytes at address a");
+ this.println("\tew [a] [...] edit words at address a");
+ return;
+ }
+
+ var dbgAddr = this.parseAddr(sAddr);
+ if (!dbgAddr) return;
+
+ for (var i = 2; i < asArgs.length; i++) {
+ var vNew = this.parseExpression(asArgs[i]);
+ if (vNew === undefined) {
+ this.println("unrecognized value: " + asArgs[i]);
+ break;
+ }
+ if (vNew & ~mask) {
+ this.println("warning: " + str.toHex(vNew) + " exceeds " + size + "-byte value");
+ }
+ var vOld = fnGet.call(this, dbgAddr);
+ this.println("changing " + this.toHexAddr(dbgAddr) + " from 0x" + str.toHex(vOld, cch) + " to 0x" + str.toHex(vNew, cch));
+ fnSet.call(this, dbgAddr, vNew, size);
+ }
+ };
+
+ /**
+ * doFreqs(sParm)
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sParm
+ */
+ Debugger.prototype.doFreqs = function(sParm)
+ {
+ if (sParm == '?') {
+ this.println("frequency commands:");
+ this.println("\tclear\tclear all frequency counts");
+ return;
+ }
+ var i;
+ var cData = 0;
+ if (this.aaOpcodeCounts) {
+ if (sParm == "clear") {
+ for (i = 0; i < this.aaOpcodeCounts.length; i++)
+ this.aaOpcodeCounts[i] = [i, 0];
+ this.println("frequency data cleared");
+ cData++;
+ }
+ else if (sParm !== undefined) {
+ this.println("unknown frequency command: " + sParm);
+ cData++;
+ }
+ else {
+ var aaSortedOpcodeCounts = this.aaOpcodeCounts.slice();
+ aaSortedOpcodeCounts.sort(function(p, q) {
+ return q[1] - p[1];
+ });
+ var asOpcodes = this.style != Debugger.STYLE_8086? Debugger.INS_NAMES : Debugger.INS_NAMES_8086;
+ for (i = 0; i < aaSortedOpcodeCounts.length; i++) {
+ var bOpcode = aaSortedOpcodeCounts[i][0];
+ var cFreq = aaSortedOpcodeCounts[i][1];
+ if (cFreq) {
+ this.println((asOpcodes[this.aaOpDescs[bOpcode][0]] + " ").substr(0, 5) + " (" + str.toHexByte(bOpcode) + "): " + cFreq + " times");
+ cData++;
+ }
+ }
+ }
+ }
+ if (!cData) {
+ this.println("no frequency data available");
+ }
+ };
+
+ /**
+ * doHalt(fQuiet)
+ *
+ * @this {Debugger}
+ * @param {boolean} [fQuiet]
+ */
+ Debugger.prototype.doHalt = function(fQuiet)
+ {
+ var sMsg;
+ if (this.flags.fRunning) {
+ sMsg = "halting";
+ this.stopCPU();
+ } else {
+ if (this.isBusy(true)) return;
+ sMsg = "already halted";
+ }
+ if (!fQuiet) this.println(sMsg);
+ };
+
+ /**
+ * doIf(sCmd, fQuiet)
+ *
+ * NOTE: Don't forget that the default base for all numeric constants is 16 (hex), so when you evaluate
+ * an expression like "a==10", it will compare the value of the variable "a" to 0x10; use a trailing period
+ * (eg, "10.") if you really intend decimal.
+ *
+ * Also, if no variable named "a" exists, "a" will evaluate to 0x0A, so the expression "a==10" becomes
+ * "0x0A==0x10" (false), whereas the expression "a==10." becomes "0x0A==0x0A" (true).
+ *
+ * @this {Debugger}
+ * @param {string} sCmd
+ * @param {boolean} [fQuiet]
+ * @return {boolean} true if expression is non-zero, false if zero (or undefined due to a parse error)
+ */
+ Debugger.prototype.doIf = function(sCmd, fQuiet)
+ {
+ sCmd = str.trim(sCmd);
+ if (!this.parseExpression(sCmd)) {
+ if (!fQuiet) this.println("false: " + sCmd);
+ return false;
+ }
+ if (!fQuiet) this.println("true: " + sCmd);
+ return true;
+ };
+
+ /**
+ * doInfo(asArgs)
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs
+ * @return {boolean} true only if the instruction info command ("n") is supported
+ */
+ Debugger.prototype.doInfo = function(asArgs)
+ {
+ if (DEBUG) {
+ this.println("msPerYield: " + this.cpu.aCounts.msPerYield);
+ this.println("nCyclesPerBurst: " + this.cpu.aCounts.nCyclesPerBurst);
+ this.println("nCyclesPerYield: " + this.cpu.aCounts.nCyclesPerYield);
+ this.println("nCyclesPerVideoUpdate: " + this.cpu.aCounts.nCyclesPerVideoUpdate);
+ this.println("nCyclesPerStatusUpdate: " + this.cpu.aCounts.nCyclesPerStatusUpdate);
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * doInput(sPort)
+ *
+ * Simulate a 1-byte port input operation.
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sPort
+ */
+ Debugger.prototype.doInput = function(sPort)
+ {
+ if (!sPort || sPort == '?') {
+ this.println("input commands:");
+ this.println("\ti [p]\tread port [p]");
+ /*
+ * TODO: Regarding this warning, consider adding an "unchecked" version of
+ * bus.checkPortInputNotify(), since all Debugger memory accesses are unchecked, too.
+ *
+ * All port I/O handlers ARE aware when the Debugger is calling (addrFrom is undefined),
+ * but changing them all to be non-destructive would take time, and situations where you
+ * actually want to affect the hardware state are just as likely as not....
+ */
+ this.println("warning: port accesses can affect hardware state");
+ return;
+ }
+ var port = this.parseValue(sPort);
+ if (port !== undefined) {
+ var bIn = this.bus.checkPortInputNotify(port, 1);
+ this.println(str.toHexWord(port) + ": " + str.toHexByte(bIn));
+ }
+ };
+
+ /**
+ * doVar(sCmd)
+ *
+ * The command must be of the form "{variable} = [{expression}]", where expression may contain constants,
+ * operators, registers, symbols, other variables, or nothing at all; in the latter case, the variable, if
+ * any, is deleted.
+ *
+ * Other supported shorthand: "var" with no parameters prints the values of all variables, and "var {variable}"
+ * prints the value of the specified variable.
+ *
+ * @this {Debugger}
+ * @param {string} sCmd
+ * @return {boolean} true if valid "var" assignment, false if not
+ */
+ Debugger.prototype.doVar = function(sCmd)
+ {
+ var a = sCmd.match(/^\s*([A-Z_]?[A-Z0-9_]*)\s*(=?)\s*(.*)$/i);
+ if (a) {
+ if (!a[1]) {
+ if (!this.printVariable()) this.println("no variables");
+ return true; // it's not considered an error to print an empty list of variables
+ }
+ if (!a[2]) {
+ return this.printVariable(a[1]);
+ }
+ if (!a[3]) {
+ this.delVariable(a[1]);
+ return true; // it's not considered an error to delete a variable that didn't exist
+ }
+ var v = this.parseExpression(a[3]);
+ if (v !== undefined) {
+ this.setVariable(a[1], v);
+ return true;
+ }
+ return false;
+ }
+ this.println("invalid assignment:" + sCmd);
+ return false;
+ };
+
+ /**
+ * doList(sAddr, fPrint)
+ *
+ * @this {Debugger}
+ * @param {string} sAddr
+ * @param {boolean} [fPrint]
+ * @return {string|null}
+ */
+ Debugger.prototype.doList = function(sAddr, fPrint)
+ {
+ var sSymbol = null;
+
+ var dbgAddr = this.parseAddr(sAddr, true);
+ if (dbgAddr) {
+ var addr = this.getAddr(dbgAddr);
+ var aSymbol = this.findSymbol(dbgAddr, true);
+ if (aSymbol.length) {
+ var nDelta, sDelta, s;
+ if (aSymbol[0]) {
+ sDelta = "";
+ nDelta = dbgAddr.addr - aSymbol[1];
+ if (nDelta) sDelta = " + " + str.toHexWord(nDelta);
+ s = aSymbol[0] + " (" + this.toHexOffset(aSymbol[1]) + ')' + sDelta;
+ if (fPrint) this.println(s);
+ sSymbol = s;
+ }
+ if (aSymbol.length > 4 && aSymbol[4]) {
+ sDelta = "";
+ nDelta = aSymbol[5] - dbgAddr.addr;
+ if (nDelta) sDelta = " - " + str.toHexWord(nDelta);
+ s = aSymbol[4] + " (" + this.toHexOffset(aSymbol[5]) + ')' + sDelta;
+ if (fPrint) this.println(s);
+ if (!sSymbol) sSymbol = s;
+ }
+ } else {
+ if (fPrint) this.println("no symbols");
+ }
+ }
+ return sSymbol;
+ };
+
+ /**
+ * doMessages(asArgs)
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs
+ */
+ Debugger.prototype.doMessages = function(asArgs)
+ {
+ var m;
+ var fCriteria = null;
+ var sCategory = asArgs[1];
+ if (sCategory == '?') sCategory = undefined;
+
+ if (sCategory !== undefined) {
+ var bitsMessage = 0;
+ if (sCategory == "all") {
+ bitsMessage = (0xffffffff|0) & ~(Messages.HALT | Messages.KEYS | Messages.LOG);
+ sCategory = null;
+ } else if (sCategory == "on") {
+ fCriteria = true;
+ sCategory = null;
+ } else if (sCategory == "off") {
+ fCriteria = false;
+ sCategory = null;
+ } else {
+ /*
+ * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects,
+ * but externally, we allow the user to specify "keys"; "kbd" is also allowed as shorthand for "keyboard".
+ */
+ if (sCategory == "keys") sCategory = "key";
+ if (sCategory == "kbd") sCategory = "keyboard";
+ for (m in Debugger.MESSAGES) {
+ if (sCategory == m) {
+ bitsMessage = Debugger.MESSAGES[m];
+ fCriteria = !!(this.bitsMessage & bitsMessage);
+ break;
+ }
+ }
+ if (!bitsMessage) {
+ this.println("unknown message category: " + sCategory);
+ return;
+ }
+ }
+ if (bitsMessage) {
+ if (asArgs[2] == "on") {
+ this.bitsMessage |= bitsMessage;
+ fCriteria = true;
+ }
+ else if (asArgs[2] == "off") {
+ this.bitsMessage &= ~bitsMessage;
+ fCriteria = false;
+ }
+ }
+ }
+
+ /*
+ * Display those message categories that match the current criteria (on or off)
+ */
+ var n = 0;
+ var sCategories = "";
+ for (m in Debugger.MESSAGES) {
+ if (!sCategory || sCategory == m) {
+ var bitMessage = Debugger.MESSAGES[m];
+ var fEnabled = !!(this.bitsMessage & bitMessage);
+ if (fCriteria !== null && fCriteria != fEnabled) continue;
+ if (sCategories) sCategories += ',';
+ if (!(++n % 10)) sCategories += "\n\t"; // jshint ignore:line
+ /*
+ * Internally, we use "key" instead of "keys", since the latter is a method on JavasScript objects,
+ * but externally, we allow the user to specify "keys".
+ */
+ if (m == "key") m = "keys";
+ sCategories += m;
+ }
+ }
+
+ if (sCategory === undefined) {
+ this.println("message commands:\n\tm [category] [on|off]\tturn categories on/off");
+ }
+
+ this.println((fCriteria !== null? (fCriteria? "messages on: " : "messages off: ") : "message categories:\n\t") + (sCategories || "none"));
+
+ this.historyInit(); // call this just in case Messages.INT was turned on
+ };
+
+ /**
+ * doOptions(asArgs)
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs
+ */
+ Debugger.prototype.doOptions = function(asArgs)
+ {
+ switch (asArgs[1]) {
+ case "8080":
+ this.style = Debugger.STYLE_8080;
+ break;
+
+ case "8086":
+ this.style = Debugger.STYLE_8086;
+ break;
+
+ case "cs":
+ var nCycles;
+ if (asArgs[3] !== undefined) nCycles = +asArgs[3]; // warning: decimal instead of hex conversion
+ switch (asArgs[2]) {
+ case "int":
+ this.cpu.aCounts.nCyclesChecksumInterval = nCycles;
+ break;
+ case "start":
+ this.cpu.aCounts.nCyclesChecksumStart = nCycles;
+ break;
+ case "stop":
+ this.cpu.aCounts.nCyclesChecksumStop = nCycles;
+ break;
+ default:
+ this.println("unknown cs option");
+ return;
+ }
+ if (nCycles !== undefined) {
+ this.cpu.resetChecksum();
+ }
+ this.println("checksums " + (this.cpu.flags.fChecksum? "enabled" : "disabled"));
+ return;
+
+ case "sp":
+ if (asArgs[2] !== undefined) {
+ if (!this.cpu.setSpeed(+asArgs[2])) {
+ this.println("warning: using 1x multiplier, previous target not reached");
+ }
+ }
+ this.println("target speed: " + this.cpu.getSpeedTarget() + " (" + this.cpu.getSpeed() + "x)");
+ return;
+
+ case "?":
+ this.println("debugger options:");
+ this.println("\t8080\t\tselect 8080-style mnemonics");
+ this.println("\t8086\t\tselect 8086-style mnemonics");
+ this.println("\tcs int #\tset checksum cycle interval to #");
+ this.println("\tcs start #\tset checksum cycle start count to #");
+ this.println("\tcs stop #\tset checksum cycle stop count to #");
+ this.println("\tsp #\t\tset speed multiplier to #");
+ break;
+
+ default:
+ if (asArgs[1]) {
+ this.println("unknown option: " + asArgs[1]);
+ return;
+ }
+ break;
+ }
+ this.println(this.style + "-style mnemonics enabled");
+ };
+
+ /**
+ * doOutput(sPort, sByte)
+ *
+ * Simulate a 1-byte port output operation.
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sPort
+ * @param {string|undefined} sByte (string representation of 1 byte)
+ */
+ Debugger.prototype.doOutput = function(sPort, sByte)
+ {
+ if (!sPort || sPort == '?') {
+ this.println("output commands:");
+ this.println("\to [p] [b]\twrite byte [b] to port [p]");
+ /*
+ * TODO: Regarding this warning, consider adding an "unchecked" version of
+ * bus.checkPortOutputNotify(), since all Debugger memory accesses are unchecked, too.
+ *
+ * All port I/O handlers ARE aware when the Debugger is calling (addrFrom is undefined),
+ * but changing them all to be non-destructive would take time, and situations where you
+ * actually want to affect the hardware state are just as likely as not....
+ */
+ this.println("warning: port accesses can affect hardware state");
+ return;
+ }
+ var port = this.parseValue(sPort, "port #");
+ var bOut = this.parseValue(sByte);
+ if (port !== undefined && bOut !== undefined) {
+ this.bus.checkPortOutputNotify(port, 1, bOut);
+ this.println(str.toHexWord(port) + ": " + str.toHexByte(bOut));
+ }
+ };
+
+ /**
+ * doRegisters(asArgs, fInstruction)
+ *
+ * @this {Debugger}
+ * @param {Array.} [asArgs]
+ * @param {boolean} [fInstruction] (true to include the current instruction; default is true)
+ */
+ Debugger.prototype.doRegisters = function(asArgs, fInstruction)
+ {
+ if (asArgs && asArgs[1] == '?') {
+ this.println("register commands:");
+ this.println("\tr\tdump registers");
+ this.println("\trx [#]\tset flag or register x to [#]");
+ return;
+ }
+
+ var cpu = this.cpu;
+ if (fInstruction == null) fInstruction = true;
+
+ if (asArgs != null && asArgs.length > 1) {
+ var sReg = asArgs[1];
+ var sValue = null;
+ var i = sReg.indexOf('=');
+ if (i > 0) {
+ sValue = sReg.substr(i + 1);
+ sReg = sReg.substr(0, i);
+ }
+ else if (asArgs.length > 2) {
+ sValue = asArgs[2];
+ }
+ else {
+ this.println("missing value for " + asArgs[1]);
+ return;
+ }
+
+ var fValid = false;
+ var w = this.parseExpression(sValue);
+
+ if (w !== undefined) {
+ fValid = true;
+ var sRegMatch = sReg.toUpperCase();
+ switch (sRegMatch) {
+ case "A":
+ cpu.regA = w & 0xff;
+ break;
+ case "B":
+ cpu.regB = w & 0xff;
+ break;
+ case "BC":
+ cpu.regB = ((w >> 8) & 0xff);
+ /* falls through */
+ case "C":
+ cpu.regC = w & 0xff;
+ break;
+ case "D":
+ cpu.regD = w & 0xff;
+ break;
+ case "DE":
+ cpu.regD = ((w >> 8) & 0xff);
+ /* falls through */
+ case "E":
+ cpu.regE = w & 0xff;
+ break;
+ case "H":
+ cpu.regH = w & 0xff;
+ break;
+ case "HL":
+ cpu.regH = ((w >> 8) & 0xff);
+ /* falls through */
+ case "L":
+ cpu.regL = w & 0xff;
+ break;
+ case "SP":
+ cpu.setSP(w);
+ break;
+ case "PC":
+ cpu.setPC(w);
+ this.dbgAddrNextCode = this.newAddr(cpu.getPC());
+ break;
+ case "PS":
+ cpu.setPS(w);
+ break;
+ case "PSW":
+ cpu.setPSW(w);
+ break;
+ case "CF":
+ if (w) cpu.setCF(); else cpu.clearCF();
+ break;
+ case "PF":
+ if (w) cpu.setPF(); else cpu.clearPF();
+ break;
+ case "AF":
+ if (w) cpu.setAF(); else cpu.clearAF();
+ break;
+ case "ZF":
+ if (w) cpu.setZF(); else cpu.clearZF();
+ break;
+ case "SF":
+ if (w) cpu.setSF(); else cpu.clearSF();
+ break;
+ case "IF":
+ if (w) cpu.setIF(); else cpu.clearIF();
+ break;
+ default:
+ this.println("unknown register: " + sReg);
+ return;
+ }
+ }
+ if (!fValid) {
+ this.println("invalid value: " + sValue);
+ return;
+ }
+ cpu.updateCPU();
+ this.println("updated registers:");
+ }
+
+ this.println(this.getRegDump());
+
+ if (fInstruction) {
+ this.dbgAddrNextCode = this.newAddr(cpu.getPC());
+ this.doUnassemble(this.toHexAddr(this.dbgAddrNextCode));
+ }
+ };
+
+ /**
+ * doRun(sCmd, sAddr, sOptions, fQuiet)
+ *
+ * @this {Debugger}
+ * @param {string} sCmd
+ * @param {string|undefined} [sAddr]
+ * @param {string} [sOptions] (the rest of the breakpoint command-line)
+ * @param {boolean} [fQuiet]
+ */
+ Debugger.prototype.doRun = function(sCmd, sAddr, sOptions, fQuiet)
+ {
+ if (sCmd == "gt") {
+ this.fIgnoreNextCheckFault = true;
+ }
+ if (sAddr !== undefined) {
+ var dbgAddr = this.parseAddr(sAddr, true);
+ if (!dbgAddr) return;
+ this.parseAddrOptions(dbgAddr, sOptions);
+ this.setTempBreakpoint(dbgAddr);
+ }
+ if (!this.runCPU(true)) {
+ if (!fQuiet) this.println("cpu busy or unavailable, run command ignored");
+ }
+ };
+
+ /**
+ * doPrint(sCmd)
+ *
+ * NOTE: If the string to print is a quoted string, then we run it through replaceRegs(), so that
+ * you can take advantage of all the special replacement options used for software interrupt logging.
+ *
+ * @this {Debugger}
+ * @param {string} sCmd
+ */
+ Debugger.prototype.doPrint = function(sCmd)
+ {
+ sCmd = str.trim(sCmd);
+ var a = sCmd.match(/^(['"])(.*?)\1$/);
+ if (!a) {
+ this.parseExpression(sCmd, true);
+ } else {
+ this.println(this.replaceRegs(a[2]));
+ }
+ };
+
+ /**
+ * doStep(sCmd)
+ *
+ * @this {Debugger}
+ * @param {string} [sCmd] "p" or "pr"
+ */
+ Debugger.prototype.doStep = function(sCmd)
+ {
+ var fCallStep = true;
+ var fRegs = (sCmd == "pr"? 1 : 0);
+ /*
+ * Set up the value for this.nStep (ie, 1 or 2) depending on whether the user wants
+ * a subsequent register dump ("pr") or not ("p").
+ */
+ var nStep = 1 + fRegs;
+ if (!this.nStep) {
+ var dbgAddr = this.newAddr(this.cpu.getPC());
+ var bOpcode = this.getByte(dbgAddr);
+
+ switch (bOpcode) {
+ case CPUDef.OPCODE.CALL:
+ if (fCallStep) {
+ this.nStep = nStep;
+ this.incAddr(dbgAddr, 3);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (this.nStep) {
+ this.setTempBreakpoint(dbgAddr);
+ if (!this.runCPU()) {
+ if (this.cmp) this.cmp.updateFocus();
+ this.nStep = 0;
+ }
+ /*
+ * A successful run will ultimately call stop(), which will in turn call clearTempBreakpoint(),
+ * which will clear nStep, so there's your assurance that nStep will be reset. Now we may have
+ * stopped for reasons unrelated to the temporary breakpoint, but that's OK.
+ */
+ } else {
+ this.doTrace(fRegs? "tr" : "t");
+ }
+ } else {
+ this.println("step in progress");
+ }
+ };
+
+ /**
+ * getCall(dbgAddr)
+ *
+ * Given a possible return address (typically from the stack), look for a matching CALL (or INT) that
+ * immediately precedes that address.
+ *
+ * @this {Debugger}
+ * @param {DbgAddr} dbgAddr
+ * @return {string|null} CALL instruction at or near dbgAddr, or null if none
+ */
+ Debugger.prototype.getCall = function(dbgAddr)
+ {
+ var sCall = null;
+ var addr = dbgAddr.addr;
+ var addrOrig = addr;
+ for (var n = 1; n <= 6 && !!addr; n++) {
+ if (n > 2) {
+ dbgAddr.addr = addr;
+ var s = this.getInstruction(dbgAddr);
+ if (s.indexOf("CALL") >= 0) {
+ /*
+ * Verify that the length of this CALL (or INT), when added to the address of the CALL (or INT),
+ * matches the original return address. We do this by getting the string index of the opcode bytes,
+ * subtracting that from the string index of the next space, and dividing that difference by two,
+ * to yield the length of the CALL (or INT) instruction, in bytes.
+ */
+ var i = s.indexOf(' ');
+ var j = s.indexOf(' ', i+1);
+ if (addr + (j - i - 1)/2 == addrOrig) {
+ sCall = s;
+ break;
+ }
+ }
+ }
+ addr--;
+ }
+ dbgAddr.addr = addrOrig;
+ return sCall;
+ };
+
+ /**
+ * doStackTrace(sCmd, sAddr)
+ *
+ * Use "k" for a normal stack trace and "ks" for a stack trace with symbolic info.
+ *
+ * @this {Debugger}
+ * @param {string} [sCmd]
+ * @param {string} [sAddr] (not used yet)
+ */
+ Debugger.prototype.doStackTrace = function(sCmd, sAddr)
+ {
+ if (sAddr == '?') {
+ this.println("stack trace commands:");
+ this.println("\tk\tshow frame addresses");
+ this.println("\tks\tshow symbol information");
+ return;
+ }
+
+ var nFrames = 10, cFrames = 0;
+ var dbgAddrCall = this.newAddr();
+ var dbgAddrStack = this.newAddr(this.cpu.getSP());
+ this.println("stack trace for " + this.toHexAddr(dbgAddrStack));
+
+ while (cFrames < nFrames) {
+ var sCall = null, sCallPrev = null, cTests = 256;
+ while ((dbgAddrStack.addr >>> 0) < 0x10000) {
+ dbgAddrCall.addr = this.getWord(dbgAddrStack, true);
+ /*
+ * Because we're using the auto-increment feature of getWord(), and because that will automatically
+ * wrap the offset around the end of the segment, we must also check the addr property to detect the wrap.
+ */
+ if (dbgAddrStack.addr == null || !cTests--) break;
+ sCall = this.getCall(dbgAddrCall);
+ if (sCall) break;
+ }
+ /*
+ * The sCallPrev check eliminates duplicate sequential calls, which are usually (but not always)
+ * indicative of a false positive, in which case the previous call is probably bogus as well, but
+ * at least we won't duplicate that mistake. Of course, there are always exceptions, recursion
+ * being one of them, but it's rare that we're debugging recursive code.
+ */
+ if (!sCall || sCall == sCallPrev) break;
+ var sSymbol = null;
+ if (sCmd == "ks") {
+ var a = sCall.match(/[0-9A-F]+$/);
+ if (a) sSymbol = this.doList(a[0]);
+ }
+ sCall = str.pad(sCall, 50) + " ;" + (sSymbol || "stack=" + this.toHexAddr(dbgAddrStack)); // + " return=" + this.toHexAddr(dbgAddrCall));
+ this.println(sCall);
+ sCallPrev = sCall;
+ cFrames++;
+ }
+ if (!cFrames) this.println("no return addresses found");
+ };
+
+ /**
+ * doTrace(sCmd, sCount)
+ *
+ * The "t" and "tr" commands interpret the count as a number of instructions, and since
+ * we call the Debugger's stepCPU() for each iteration, a single instruction includes
+ * any/all prefixes; the CPU's stepCPU() treats prefixes as discrete operations. The only
+ * difference between "t" and "tr": the former displays only the next instruction, while
+ * the latter also displays the (updated) registers.
+ *
+ * The "tc" command interprets the count as a number of cycles rather than instructions,
+ * allowing you to quickly execute large chunks of instructions with a single command; it
+ * doesn't display anything until the the chunk has finished.
+ *
+ * However, generally a more useful command is "bn", which allows you to break after some
+ * number of instructions have been executed (as opposed to some number of cycles).
+ *
+ * @this {Debugger}
+ * @param {string} [sCmd] ("t", "tc", or "tr")
+ * @param {string} [sCount] # of instructions to step
+ */
+ Debugger.prototype.doTrace = function(sCmd, sCount)
+ {
+ var dbg = this;
+ var fRegs = (sCmd != "t");
+ var nCount = this.parseValue(sCount, null, true) || 1;
+ var nCycles = (nCount == 1? 0 : 1);
+ if (sCmd == "tc") {
+ nCycles = nCount;
+ nCount = 1;
+ }
+ web.onCountRepeat(
+ nCount,
+ function onCountStep() {
+ return dbg.setBusy(true) && dbg.stepCPU(nCycles, fRegs, false);
+ },
+ function onCountStepComplete() {
+ /*
+ * We explicitly called stepCPU() with fUpdateCPU === false, because repeatedly
+ * calling updateCPU() can be very slow, especially when fDisplayLiveRegs is true,
+ * so once the repeat count has been exhausted, we must perform a final updateCPU().
+ */
+ dbg.cpu.updateCPU();
+ dbg.setBusy(false);
+ }
+ );
+ };
+
+ /**
+ * doUnassemble(sAddr, sAddrEnd, n)
+ *
+ * @this {Debugger}
+ * @param {string} [sAddr]
+ * @param {string} [sAddrEnd]
+ * @param {number} [n]
+ */
+ Debugger.prototype.doUnassemble = function(sAddr, sAddrEnd, n)
+ {
+ var dbgAddr = this.parseAddr(sAddr, true);
+ if (!dbgAddr) return;
+
+ if (n === undefined) n = 1;
+
+ var cb = 0x100;
+ if (sAddrEnd !== undefined) {
+
+ var dbgAddrEnd = this.parseAddr(sAddrEnd, true);
+ if (!dbgAddrEnd || dbgAddrEnd.addr < dbgAddr.addr) return;
+
+ cb = dbgAddrEnd.addr - dbgAddr.addr;
+ if (!DEBUG && cb > 0x100) {
+ /*
+ * Limiting the amount of disassembled code to 256 bytes in non-DEBUG builds is partly to
+ * prevent the user from wedging the browser by dumping too many lines, but also a recognition
+ * that, in non-DEBUG builds, this.println() keeps print output buffer truncated to 8Kb anyway.
+ */
+ this.println("range too large");
+ return;
+ }
+ n = -1;
+ }
+
+ var cLines = 0;
+ var sInstruction;
+
+ while (cb > 0 && n--) {
+
+ var nSequence = (this.isBusy(false) || this.nStep)? this.nCycles : null;
+ var sComment = (nSequence != null? "cycles" : null);
+ var aSymbol = this.findSymbol(dbgAddr);
+
+ var addr = dbgAddr.addr; // we snap dbgAddr.addr *after* calling findSymbol(), which re-evaluates it
+
+ if (aSymbol[0] && n) {
+ if (!cLines && n || aSymbol[0].indexOf('+') < 0) {
+ var sLabel = aSymbol[0] + ':';
+ if (aSymbol[2]) sLabel += ' ' + aSymbol[2];
+ this.println(sLabel);
+ }
+ }
+
+ if (aSymbol[3]) {
+ sComment = aSymbol[3];
+ nSequence = null;
+ }
+
+ sInstruction = this.getInstruction(dbgAddr, sComment, nSequence);
+
+ this.println(sInstruction);
+ this.dbgAddrNextCode = dbgAddr;
+ cb -= dbgAddr.addr - addr;
+ cLines++;
+ }
+ };
+
+ /**
+ * parseCommand(sCmd, fSave, chSep)
+ *
+ * @this {Debugger}
+ * @param {string|undefined} sCmd
+ * @param {boolean} [fSave] is true to save the command, false if not
+ * @param {string} [chSep] is the command separator character (default is ';')
+ * @return {Array.}
+ */
+ Debugger.prototype.parseCommand = function(sCmd, fSave, chSep)
+ {
+ if (fSave) {
+ if (!sCmd) {
+ if (this.fAssemble) {
+ sCmd = "end";
+ } else {
+ sCmd = this.aPrevCmds[this.iPrevCmd+1];
+ }
+ } else {
+ if (this.iPrevCmd < 0 && this.aPrevCmds.length) {
+ this.iPrevCmd = 0;
+ }
+ if (this.iPrevCmd < 0 || sCmd != this.aPrevCmds[this.iPrevCmd]) {
+ this.aPrevCmds.splice(0, 0, sCmd);
+ this.iPrevCmd = 0;
+ }
+ this.iPrevCmd--;
+ }
+ }
+ var a = [];
+ if (sCmd) {
+ /*
+ * With the introduction of breakpoint commands (ie, quoted command sequences
+ * associated with a breakpoint), we can no longer perform simplistic splitting.
+ *
+ * a = sCmd.split(chSep || ';');
+ * for (var i = 0; i < a.length; i++) a[i] = str.trim(a[i]);
+ *
+ * We may now split on semi-colons ONLY if they are outside a quoted sequence.
+ *
+ * Also, to allow quoted strings *inside* breakpoint commands, we first replace all
+ * DOUBLE double-quotes with single quotes.
+ */
+ sCmd = sCmd.toLowerCase().replace(/""/g, "'");
+
+ var iPrev = 0;
+ var chQuote = null;
+ chSep = chSep || ';';
+ /*
+ * NOTE: Processing charAt() up to and INCLUDING length is not a typo; we're taking
+ * advantage of the fact that charAt() with an invalid index returns an empty string,
+ * allowing us to use the same substring() call to capture the final portion of sCmd.
+ *
+ * In a sense, it allows us to pretend that the string ends with a zero terminator.
+ */
+ for (var i = 0; i <= sCmd.length; i++) {
+ var ch = sCmd.charAt(i);
+ if (ch == '"' || ch == "'") {
+ if (!chQuote) {
+ chQuote = ch;
+ } else if (ch == chQuote) {
+ chQuote = null;
+ }
+ }
+ else if (ch == chSep && !chQuote || !ch) {
+ /*
+ * Recall that substring() accepts starting (inclusive) and ending (exclusive)
+ * indexes, whereas substr() accepts a starting index and a length. We need the former.
+ */
+ a.push(str.trim(sCmd.substring(iPrev, i)));
+ iPrev = i + 1;
+ }
+ }
+ }
+ return a;
+ };
+
+ /**
+ * shiftArgs(asArgs)
+ *
+ * Used with any command (eg, "r") that allows but doesn't require whitespace between command and first argument.
+ *
+ * @this {Debugger}
+ * @param {Array.} asArgs
+ * @return {Array.}
+ */
+ Debugger.prototype.shiftArgs = function(asArgs)
+ {
+ if (asArgs && asArgs.length) {
+ var s0 = asArgs[0];
+ var ch0 = s0.charAt(0);
+ for (var i = 1; i < s0.length; i++) {
+ var ch = s0.charAt(i);
+ if (ch0 == '?' || ch0 == 'r' || ch < 'a' || ch > 'z') {
+ asArgs[0] = s0.substr(i);
+ asArgs.unshift(s0.substr(0, i));
+ break;
+ }
+ }
+ }
+ return asArgs;
+ };
+
+ /**
+ * doCommand(sCmd, fQuiet)
+ *
+ * @this {Debugger}
+ * @param {string} sCmd
+ * @param {boolean} [fQuiet]
+ * @return {boolean} true if command processed, false if unrecognized
+ */
+ Debugger.prototype.doCommand = function(sCmd, fQuiet)
+ {
+ var result = true;
+
+ try {
+ if (!sCmd.length || sCmd == "end") {
+ if (this.fAssemble) {
+ this.println("ended assemble at " + this.toHexAddr(this.dbgAddrAssemble));
+ this.dbgAddrNextCode = this.dbgAddrAssemble;
+ this.fAssemble = false;
+ }
+ sCmd = "";
+ }
+ else if (!fQuiet) {
+ var sPrompt = ">> ";
+ this.println(sPrompt + sCmd);
+ }
+
+ var ch = sCmd.charAt(0);
+ if (ch == '"' || ch == "'") return true;
+
+ /*
+ * Zap the previous message buffer to ensure the new command's output is not tossed out as a repeat.
+ */
+ this.sMessagePrev = null;
+
+ /*
+ * I've relaxed the !isBusy() requirement, to maximize our ability to issue Debugger commands externally.
+ */
+ if (this.isReady() /* && !this.isBusy(true) */ && sCmd.length > 0) {
+
+ if (this.fAssemble) {
+ sCmd = "a " + this.toHexAddr(this.dbgAddrAssemble) + ' ' + sCmd;
+ }
+
+ var asArgs = this.shiftArgs(sCmd.replace(/ +/g, ' ').split(' '));
+
+ switch (asArgs[0].charAt(0)) {
+ case 'a':
+ this.doAssemble(asArgs);
+ break;
+ case 'b':
+ this.doBreak(asArgs[0], asArgs[1], sCmd);
+ break;
+ case 'c':
+ this.doClear(asArgs[0]);
+ break;
+ case 'd':
+ if (!COMPILED && sCmd == "debug") {
+ window.DEBUG = true;
+ this.println("DEBUG checks on");
+ break;
+ }
+ this.doDump(asArgs);
+ break;
+ case 'e':
+ if (asArgs[0] == "else") break;
+ this.doEdit(asArgs);
+ break;
+ case 'f':
+ this.doFreqs(asArgs[1]);
+ break;
+ case 'g':
+ this.doRun(asArgs[0], asArgs[1], sCmd, fQuiet);
+ break;
+ case 'h':
+ this.doHalt(fQuiet);
+ break;
+ case 'i':
+ if (asArgs[0] == "if") {
+ if (!this.doIf(sCmd.substr(2), fQuiet)) {
+ result = false;
+ }
+ break;
+ }
+ this.doInput(asArgs[1]);
+ break;
+ case 'k':
+ this.doStackTrace(asArgs[0], asArgs[1]);
+ break;
+ case 'l':
+ if (asArgs[0] == "ln") {
+ this.doList(asArgs[1], true);
+ break;
+ }
+ break;
+ case 'm':
+ this.doMessages(asArgs);
+ break;
+ case 'o':
+ this.doOutput(asArgs[1], asArgs[2]);
+ break;
+ case 'p':
+ if (asArgs[0] == "print") {
+ this.doPrint(sCmd.substr(5));
+ break;
+ }
+ this.doStep(asArgs[0]);
+ break;
+ case 'r':
+ if (sCmd == "reset") {
+ if (this.cmp) this.cmp.reset();
+ break;
+ }
+ this.doRegisters(asArgs);
+ break;
+ case 's':
+ this.doOptions(asArgs);
+ break;
+ case 't':
+ this.doTrace(asArgs[0], asArgs[1]);
+ break;
+ case 'u':
+ this.doUnassemble(asArgs[1], asArgs[2], 8);
+ break;
+ case 'v':
+ if (asArgs[0] == "var") {
+ if (!this.doVar(sCmd.substr(3))) {
+ result = false;
+ }
+ break;
+ }
+ this.println((APPNAME + " version " + (XMLVERSION || APPVERSION) + " (" + this.cpu.model + (COMPILED? ",RELEASE" : (DEBUG? ",DEBUG" : ",NODEBUG")) + (TYPEDARRAYS? ",TYPEDARRAYS" : (BYTEARRAYS? ",BYTEARRAYS" : ",LONGARRAYS")) + ')');
+ this.println(web.getUserAgent());
+ break;
+ case '?':
+ if (asArgs[1]) {
+ this.doPrint(sCmd.substr(1));
+ break;
+ }
+ this.doHelp();
+ break;
+ case 'n':
+ if (!COMPILED && sCmd == "nodebug") {
+ window.DEBUG = false;
+ this.println("DEBUG checks off");
+ break;
+ }
+ if (this.doInfo(asArgs)) break;
+ /* falls through */
+ default:
+ this.println("unknown command: " + sCmd);
+ result = false;
+ break;
+ }
+ }
+ } catch(e) {
+ this.println("debugger error: " + (e.stack || e.message));
+ result = false;
+ }
+ return result;
+ };
+
+ /**
+ * doCommands(sCmds, fSave)
+ *
+ * @this {Debugger}
+ * @param {string} sCmds
+ * @param {boolean} [fSave]
+ * @return {boolean} true if all commands processed, false if not
+ */
+ Debugger.prototype.doCommands = function(sCmds, fSave)
+ {
+ var a = this.parseCommand(sCmds, fSave);
+ for (var s in a) {
+ if (!this.doCommand(a[s])) return false;
+ }
+ return true;
+ };
+
+ /**
+ * Debugger.init()
+ *
+ * This function operates on every HTML element of class "debugger", extracting the
+ * JSON-encoded parameters for the Debugger constructor from the element's "data-value"
+ * attribute, invoking the constructor to create a Debugger component, and then binding
+ * any associated HTML controls to the new component.
+ */
+ Debugger.init = function()
+ {
+ var aeDbg = Component.getElementsByClass(document, PCJSCLASS, "debugger");
+ for (var iDbg = 0; iDbg < aeDbg.length; iDbg++) {
+ var eDbg = aeDbg[iDbg];
+ var parmsDbg = Component.getComponentParms(eDbg);
+ var dbg = new Debugger(parmsDbg);
+ Component.bindComponentControls(dbg, eDbg, PCJSCLASS);
+ }
+ };
+
+ /*
+ * Initialize every Debugger module on the page (as IF there's ever going to be more than one ;-))
+ */
+ web.onInit(Debugger.init);
+
+} // endif DEBUGGER
+
+if (NODE) module.exports = Debugger;
diff --git a/modules/pc6502/lib/defines.js b/modules/pc6502/lib/defines.js
new file mode 100644
index 000000000..a0be1c910
--- /dev/null
+++ b/modules/pc6502/lib/defines.js
@@ -0,0 +1,81 @@
+/**
+ * @fileoverview PC6502-specific compile-time definitions.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+/**
+ * @define {string}
+ */
+var PCJSCLASS = "pc8080"; // this @define is the default application class (formerly APPCLASS) to use
+
+/**
+ * @define {boolean}
+ *
+ * WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
+ * In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
+ * nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
+ * "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
+ * debugger.js from the compilation process altogether.
+ *
+ * However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
+ * I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
+ * (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
+ */
+var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
+
+/**
+ * @define {boolean}
+ *
+ * BYTEARRAYS is a Closure Compiler compile-time option that allocates an Array of numbers for every Memory block,
+ * where each a number represents ONE byte; very wasteful, but potentially slightly faster.
+ *
+ * See the Memory component for details.
+ */
+var BYTEARRAYS = false;
+
+/**
+ * TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time-only option, but I've
+ * added Memory access functions for typed arrays (see Memory.afnTypedArray), so support can be enabled dynamically now.
+ *
+ * See the Memory component for details.
+ */
+var TYPEDARRAYS = (typeof ArrayBuffer !== 'undefined');
+
+if (NODE) {
+ global.PCJSCLASS = PCJSCLASS;
+ global.DEBUGGER = DEBUGGER;
+ global.BYTEARRAYS = BYTEARRAYS;
+ global.TYPEDARRAYS = TYPEDARRAYS;
+ /*
+ * TODO: When we're "required" by Node, should we return anything via module.exports?
+ */
+}
diff --git a/modules/pc6502/lib/keyboard.js b/modules/pc6502/lib/keyboard.js
new file mode 100644
index 000000000..586b3e735
--- /dev/null
+++ b/modules/pc6502/lib/keyboard.js
@@ -0,0 +1,237 @@
+/**
+ * @fileoverview Implements the PC6502 Keyboard component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var web = require("../../shared/lib/weblib");
+ var Component = require("../../shared/lib/component");
+ var Messages = require("./messages");
+ var State = require("./state");
+ var CPU = require("./cpu");
+}
+
+/**
+ * Keyboard(parmsKbd)
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsKbd
+ */
+function Keyboard(parmsKbd)
+{
+ Component.call(this, "Keyboard", parmsKbd, Keyboard, Messages.KEYBOARD);
+
+ this.setReady();
+}
+
+Component.subclass(Keyboard);
+
+/**
+ * Alphanumeric and other common (printable) ASCII codes.
+ *
+ * TODO: Determine what we can do to get ALL constants like these inlined (enum doesn't seem to
+ * get the job done); the problem seems to be limited to property references that use quotes, which
+ * is why I've 'unquoted' as many of them as possible.
+ *
+ * @enum {number}
+ */
+Keyboard.ASCII = {
+ CTRL_A: 1, CTRL_C: 3, CTRL_Z: 26,
+ ' ': 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
+};
+
+/**
+ * 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). This conflict is
+ * discussed further in the definition of CLICKCODE below.
+ *
+ * @enum {number}
+ */
+Keyboard.KEYCODE = {
+ /* 0x08 */ BS: 8,
+ /* 0x09 */ TAB: 9,
+ /* 0x0A */ LF: 10,
+ /* 0x0D */ CR: 13,
+ /* 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,
+ /* 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_INS: 96, // 0
+ /* 0x60 */ FF_BQUOTE: 96,
+ /* 0x61 */ NUM_END: 97, // 1
+ /* 0x62 */ NUM_DOWN: 98, // 2
+ /* 0x63 */ NUM_PGDN: 99, // 3
+ /* 0x64 */ NUM_LEFT: 100, // 4
+ /* 0x65 */ NUM_CENTER: 101, // 5
+ /* 0x66 */ NUM_RIGHT: 102, // 6
+ /* 0x67 */ NUM_HOME: 103, // 7
+ /* 0x68 */ NUM_UP: 104, // 8
+ /* 0x69 */ NUM_PGUP: 105, // 9
+ /* 0x6A */ NUM_MUL: 106,
+ /* 0x6B */ NUM_ADD: 107,
+ /* 0x6D */ NUM_SUB: 109,
+ /* 0x6E */ NUM_DEL: 110, // .
+ /* 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
+ /* 0xBB */ EQUALS: 187, // Firefox: 61
+ /* 0xBC */ COMMA: 188, // Firefox: 44
+ /* 0xBD */ DASH: 189, // Firefox: 173
+ /* 0xBE */ PERIOD: 190, // Firefox: 46
+ /* 0xBF */ SLASH: 191, // Firefox: 47
+ /* 0xC0 */ BQUOTE: 192, // Firefox: 96
+ /* 0xDB */ LBRACK: 219, // Firefox: 91
+ /* 0xDC */ BSLASH: 220, // Firefox: 92
+ /* 0xDD */ RBRACK: 221, // Firefox: 93
+ /* 0xDE */ QUOTE: 222, // Firefox: 39
+ /* 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
+};
+
+/*
+ * Maps "stupid" keyCodes to their "non-stupid" counterparts
+ */
+Keyboard.STUPID_KEYCODES = {};
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.SEMI] = Keyboard.ASCII[';']; // 186 -> 59
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.EQUALS] = Keyboard.ASCII['=']; // 187 -> 61
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.COMMA] = Keyboard.ASCII[',']; // 188 -> 44
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.DASH] = Keyboard.ASCII['-']; // 189 -> 45
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.PERIOD] = Keyboard.ASCII['.']; // 190 -> 46
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.SLASH] = Keyboard.ASCII['/']; // 191 -> 47
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.BQUOTE] = Keyboard.ASCII['`']; // 192 -> 96
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.LBRACK] = Keyboard.ASCII['[']; // 219 -> 91
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.BSLASH] = Keyboard.ASCII['\\']; // 220 -> 92
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.RBRACK] = Keyboard.ASCII[']']; // 221 -> 93
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.QUOTE] = Keyboard.ASCII["'"]; // 222 -> 39
+Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.FF_DASH] = Keyboard.ASCII['-'];
+
+/**
+ * Keyboard.init()
+ *
+ * This function operates on every HTML element of class "keyboard", extracting the
+ * JSON-encoded parameters for the Keyboard constructor from the element's "data-value"
+ * attribute, invoking the constructor to create a Keyboard component, and then binding
+ * any associated HTML controls to the new component.
+ */
+Keyboard.init = function()
+{
+ var aeKbd = Component.getElementsByClass(document, PCJSCLASS, "keyboard");
+ for (var iKbd = 0; iKbd < aeKbd.length; iKbd++) {
+ var eKbd = aeKbd[iKbd];
+ var parmsKbd = Component.getComponentParms(eKbd);
+ var kbd = new Keyboard(parmsKbd);
+ Component.bindComponentControls(kbd, eKbd, PCJSCLASS);
+ }
+};
+
+/*
+ * Initialize every Keyboard module on the page.
+ */
+web.onInit(Keyboard.init);
+
+if (NODE) module.exports = Keyboard;
diff --git a/modules/pc6502/lib/memory.js b/modules/pc6502/lib/memory.js
new file mode 100644
index 000000000..5dba300ca
--- /dev/null
+++ b/modules/pc6502/lib/memory.js
@@ -0,0 +1,825 @@
+/**
+ * @fileoverview Implements the PC6502 Memory component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var Component = require("../../shared/lib/component");
+ var Messages = require("./messages");
+ var CPUDef = require("./cpudef");
+}
+
+/**
+ * @class DataView
+ * @property {function(number,boolean):number} getUint8
+ * @property {function(number,number,boolean)} setUint8
+ * @property {function(number,boolean):number} getUint16
+ * @property {function(number,number,boolean)} setUint16
+ * @property {function(number,boolean):number} getInt32
+ * @property {function(number,number,boolean)} setInt32
+ */
+
+var littleEndian = (TYPEDARRAYS? (function() {
+ var buffer = new ArrayBuffer(2);
+ new DataView(buffer).setUint16(0, 256, true);
+ return new Uint16Array(buffer)[0] === 256;
+})() : false);
+
+/**
+ * Memory(addr, used, size, type)
+ *
+ * The Bus component allocates Memory objects so that each has a memory buffer with a
+ * block-granular starting address and an address range equal to bus.nBlockSize; however,
+ * the size of any given Memory object's underlying buffer can be either zero or bus.nBlockSize;
+ * memory read/write functions for empty (buffer-less) blocks are mapped to readNone/writeNone.
+ *
+ * The Bus allocates empty blocks for the entire address space during initialization, so that
+ * any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM
+ * components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate
+ * as many new blockSize Memory objects as the ranges require. Partial Memory blocks could
+ * also be supported in theory, but in practice, they're not.
+ *
+ * Because Memory blocks now allow us to have a "sparse" address space, we could choose to
+ * take the memory hit of allocating 4K arrays per block, where each element stores only one byte,
+ * instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords
+ * (LONGARRAYS) and shifting/masking bytes/words to/from dwords; in theory, byte accesses would
+ * be faster and word accesses somewhat less faster.
+ *
+ * However, preliminary testing of that feature (BYTEARRAYS) did not yield significantly faster
+ * performance, so it is OFF by default to minimize our memory consumption. Using TYPEDARRAYS
+ * would seem best, but as discussed in defines.js, it's off by default, because it doesn't perform
+ * as well as LONGARRAYS; the other advantage of TYPEDARRAYS is that it should theoretically use
+ * about 1/2 the memory of LONGARRAYS (32-bit elements vs 64-bit numbers), but I value speed over
+ * size at this point. Also, not all JavaScript implementations support TYPEDARRAYS (IE9 is probably
+ * the only real outlier: it lacks typed arrays but otherwise has all the necessary HTML5 support).
+ *
+ * WARNING: Since Memory blocks are low-level objects that have no UI requirements, they
+ * do not inherit from the Component class, so if you want to use any Component class methods,
+ * such as Component.assert(), use the corresponding Debugger methods instead (assuming a debugger
+ * is available).
+ *
+ * @constructor
+ * @param {number|null} [addr] of lowest used address in block
+ * @param {number} [used] portion of block in bytes (0 for none); must be a multiple of 4
+ * @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4
+ * @param {number} [type] is one of the Memory.TYPE constants (default is Memory.TYPE.NONE)
+ */
+function Memory(addr, used, size, type)
+{
+ var i;
+ this.id = (Memory.idBlock += 2);
+ this.adw = null;
+ this.offset = 0;
+ this.addr = addr;
+ this.used = used;
+ this.size = size || 0;
+ this.type = type || Memory.TYPE.NONE;
+ this.fReadOnly = (type == Memory.TYPE.ROM);
+ this.copyBreakpoints(); // initialize the block's Debugger info; the caller will reinitialize
+
+ /*
+ * TODO: Study the impact of dirty block tracking. The original purposes were to allow saveMemory()
+ * to save only dirty blocks, and to enable the Video component to quickly detect changes to the video buffer.
+ * But the benefit to saveMemory() is minimal, and the Video component has other options; for example, it now
+ * uses a custom memory controller for all EGA/VGA video modes, which performs its own dirty block tracking,
+ * and that could easily be extended to the older MDA/CGA video modes, which still use conventional memory blocks.
+ * Alternatively, we could restrict the use of dirty block tracking to certain memory types (eg, VIDEO memory).
+ *
+ * However, a quick test with dirty block tracking disabled didn't yield a noticeable improvement in performance,
+ * so I think the overhead of our block-based architecture is swamping the impact of these micro-updates.
+ */
+ this.fDirty = this.fDirtyEver = false;
+
+ /*
+ * For empty memory blocks, all we need to do is ensure all access functions are mapped to "none" handlers.
+ */
+ if (!size) {
+ this.setAccess();
+ return;
+ }
+
+ /*
+ * This is the normal case: allocate a buffer that provides 8 bits of data per address;
+ * no controller is required because our default memory access functions (see afnMemory)
+ * know how to deal with this simple 1-1 mapping of addresses to bytes and words.
+ *
+ * TODO: Consider initializing the memory array to random (or pseudo-random) values in DEBUG
+ * mode; pseudo-random might be best, to help make any bugs reproducible.
+ */
+ if (TYPEDARRAYS) {
+ this.buffer = new ArrayBuffer(size);
+ this.dv = new DataView(this.buffer, 0, size);
+ /*
+ * If littleEndian is true, we can use ab[], aw[] and adw[] directly; well, we can use them
+ * whenever the offset is a multiple of 1, 2 or 4, respectively. Otherwise, we must fallback to
+ * dv.getUint8()/dv.setUint8(), dv.getUint16()/dv.setUint16() and dv.getInt32()/dv.setInt32().
+ */
+ this.ab = new Uint8Array(this.buffer, 0, size);
+ this.aw = new Uint16Array(this.buffer, 0, size >> 1);
+ this.adw = new Int32Array(this.buffer, 0, size >> 2);
+ this.setAccess(littleEndian? Memory.afnArrayLE : Memory.afnArrayBE);
+ } else {
+ if (BYTEARRAYS) {
+ this.ab = new Array(size);
+ } else {
+ /*
+ * NOTE: This is the default mode of operation (!TYPEDARRAYS && !BYTEARRAYS), because it
+ * seems to provide the best performance; and although in theory, that performance might
+ * come at twice the overhead of TYPEDARRAYS, it's increasingly likely that the JavaScript
+ * runtime will notice that all we ever store are 32-bit values, and optimize accordingly.
+ */
+ this.adw = new Array(size >> 2);
+ for (i = 0; i < this.adw.length; i++) this.adw[i] = 0;
+ }
+ this.setAccess(Memory.afnMemory);
+ }
+}
+
+/*
+ * Basic memory types
+ *
+ * RAM is the most conventional memory type, providing full read/write capability to x86-compatible (ie,
+ * 'little endian") storage. ROM is equally conventional, except that the fReadOnly property is set,
+ * disabling writes. VIDEO is treated exactly like RAM, unless a controller is provided. Both RAM and
+ * VIDEO memory are always considered writable, and even ROM can be written using the Bus setByteDirect()
+ * interface (which in turn uses the Memory writeByteDirect() interface), allowing the ROM component to
+ * initialize its own memory. The CTRL type is used to identify memory-mapped devices that do not need
+ * any default storage and always provide their own controller.
+ *
+ * Unallocated regions of the address space contain a special memory block of type NONE that contains
+ * no storage. Mapping every addressible location to a memory block allows all accesses to be routed in
+ * exactly the same manner, without resorting to any range or processor checks.
+ *
+ * These types are not mutually exclusive. For example, VIDEO memory could be allocated as RAM, with or
+ * without a custom controller (the original Monochrome and CGA video cards used read/write storage that
+ * was indistinguishable from RAM), and CTRL memory could be allocated as an empty block of any type, with
+ * a custom controller. A few types are required for certain features (eg, ROM is required if you want
+ * read-only memory), but the larger purpose of these types is to help document the caller's intent and to
+ * provide the Control Panel with the ability to highlight memory regions accordingly.
+ */
+Memory.TYPE = {
+ NONE: 0,
+ RAM: 1,
+ ROM: 2,
+ VIDEO: 3,
+ CTRL: 4,
+ COLORS: ["black", "blue", "green", "cyan"],
+ NAMES: ["NONE", "RAM", "ROM", "VID", "H/W"]
+};
+
+/*
+ * Last used block ID (used for debugging only)
+ */
+Memory.idBlock = 0;
+
+/**
+ * adjustEndian(dw)
+ *
+ * @param {number} dw
+ * @return {number}
+ */
+Memory.adjustEndian = function(dw) {
+ if (TYPEDARRAYS && !littleEndian) {
+ dw = (dw << 24) | ((dw << 8) & 0x00ff0000) | ((dw >> 8) & 0x0000ff00) | (dw >>> 24);
+ }
+ return dw;
+};
+
+Memory.prototype = {
+ constructor: Memory,
+ parent: null,
+ /**
+ * init(addr)
+ *
+ * Quick reinitializer when reusing a Memory block.
+ *
+ * @this {Memory}
+ * @param {number} addr
+ */
+ init: function(addr) {
+ this.addr = addr;
+ },
+ /**
+ * clone(mem, type)
+ *
+ * Converts the current Memory block (this) into a clone of the given Memory block (mem),
+ * and optionally overrides the current block's type with the specified type.
+ *
+ * @this {Memory}
+ * @param {Memory} mem
+ * @param {number} [type]
+ * @param {Debugger} [dbg]
+ */
+ clone: function(mem, type, dbg) {
+ /*
+ * Original memory block IDs are even; cloned memory block IDs are odd;
+ * the original ID of the current block is lost, but that's OK, since it was presumably
+ * produced merely to become a clone.
+ */
+ this.id = mem.id | 0x1;
+ this.used = mem.used;
+ this.size = mem.size;
+ if (type) {
+ this.type = type;
+ this.fReadOnly = (type == Memory.TYPE.ROM);
+ }
+ if (TYPEDARRAYS) {
+ this.buffer = mem.buffer;
+ this.dv = mem.dv;
+ this.ab = mem.ab;
+ this.aw = mem.aw;
+ this.adw = mem.adw;
+ this.setAccess(littleEndian? Memory.afnArrayLE : Memory.afnArrayBE);
+ } else {
+ if (BYTEARRAYS) {
+ this.ab = mem.ab;
+ } else {
+ this.adw = mem.adw;
+ }
+ this.setAccess(Memory.afnMemory);
+ }
+ this.copyBreakpoints(dbg, mem);
+ },
+ /**
+ * save()
+ *
+ * This gets the contents of a Memory block as an array of 32-bit values; used by Bus.saveMemory(),
+ * which in turn is called by CPUState.save().
+ *
+ * Memory blocks with custom memory controllers do NOT save their contents; that's the responsibility
+ * of the controller component.
+ *
+ * @this {Memory}
+ * @return {Array|Int32Array|null}
+ */
+ save: function() {
+ var adw, i;
+ if (BYTEARRAYS) {
+ adw = new Array(this.size >> 2);
+ var off = 0;
+ for (i = 0; i < adw.length; i++) {
+ adw[i] = this.ab[off] | (this.ab[off + 1] << 8) | (this.ab[off + 2] << 16) | (this.ab[off + 3] << 24);
+ off += 4;
+ }
+ }
+ else if (TYPEDARRAYS) {
+ /*
+ * It might be tempting to just return a copy of Int32Array(this.buffer, 0, this.size >> 2),
+ * but we can't be sure of the "endianness" of an Int32Array -- which would be OK if the array
+ * was always saved/restored on the same machine, but there's no guarantee of that, either.
+ * So we use getInt32() and require little-endian values.
+ *
+ * Moreover, an Int32Array isn't treated by JSON.stringify() and JSON.parse() exactly like
+ * a normal array; it's serialized as an Object rather than an Array, so it lacks a "length"
+ * property and causes problems for State.store() and State.parse().
+ */
+ adw = new Array(this.size >> 2);
+ for (i = 0; i < adw.length; i++) {
+ adw[i] = this.dv.getInt32(i << 2, true);
+ }
+ }
+ else {
+ adw = this.adw;
+ }
+ return adw;
+ },
+ /**
+ * restore(adw)
+ *
+ * This restores the contents of a Memory block from an array of 32-bit values;
+ * used by Bus.restoreMemory(), which is called by CPUState.restore(), after all other
+ * components have been restored and thus all Memory blocks have been allocated
+ * by their respective components.
+ *
+ * @this {Memory}
+ * @param {Array|null} adw
+ * @return {boolean} true if successful, false if block size mismatch
+ */
+ restore: function(adw) {
+ /*
+ * At this point, it's a consistency error for adw to be null; it's happened once already,
+ * when there was a restore bug in the Video component that added the frame buffer at the video
+ * card's "spec'ed" address instead of the programmed address, so there were no controller-owned
+ * memory blocks installed at the programmed address, and so we arrived here at a block with
+ * no controller AND no data.
+ */
+ Component.assert(adw != null);
+
+ if (adw && this.size == adw.length << 2) {
+ var i;
+ if (BYTEARRAYS) {
+ var off = 0;
+ for (i = 0; i < adw.length; i++) {
+ this.ab[off] = adw[i] & 0xff;
+ this.ab[off + 1] = (adw[i] >> 8) & 0xff;
+ this.ab[off + 2] = (adw[i] >> 16) & 0xff;
+ this.ab[off + 3] = (adw[i] >> 24) & 0xff;
+ off += 4;
+ }
+ } else if (TYPEDARRAYS) {
+ for (i = 0; i < adw.length; i++) {
+ this.dv.setInt32(i << 2, adw[i], true);
+ }
+ } else {
+ this.adw = adw;
+ }
+ this.fDirty = true;
+ return true;
+ }
+ return false;
+ },
+ /**
+ * setAccess(afn, fDirect)
+ *
+ * If no function table is specified, a default is selected based on the Memory type.
+ *
+ * @this {Memory}
+ * @param {Array.} [afn] function table
+ * @param {boolean} [fDirect] (true to update direct access functions as well; default is true)
+ */
+ setAccess: function(afn, fDirect) {
+ if (!afn) {
+ Component.assert(this.type == Memory.TYPE.NONE);
+ afn = Memory.afnNone;
+ }
+ this.setReadAccess(afn, fDirect);
+ this.setWriteAccess(afn, fDirect);
+ },
+ /**
+ * setReadAccess(afn, fDirect)
+ *
+ * @this {Memory}
+ * @param {Array.} afn
+ * @param {boolean} [fDirect]
+ */
+ setReadAccess: function(afn, fDirect) {
+ if (!fDirect || !this.cReadBreakpoints) {
+ this.readByte = afn[0] || this.readNone;
+ this.readShort = afn[1] || this.readShortDefault;
+ }
+ if (fDirect || fDirect === undefined) {
+ this.readByteDirect = afn[0] || this.readNone;
+ this.readShortDirect = afn[1] || this.readShortDefault;
+ }
+ },
+ /**
+ * setWriteAccess(afn, fDirect)
+ *
+ * @this {Memory}
+ * @param {Array.} afn
+ * @param {boolean} [fDirect]
+ */
+ setWriteAccess: function(afn, fDirect) {
+ if (!fDirect || !this.cWriteBreakpoints) {
+ this.writeByte = !this.fReadOnly && afn[2] || this.writeNone;
+ this.writeShort = !this.fReadOnly && afn[3] || this.writeShortDefault;
+ }
+ if (fDirect || fDirect === undefined) {
+ this.writeByteDirect = afn[2] || this.writeNone;
+ this.writeShortDirect = afn[3] || this.writeShortDefault;
+ }
+ },
+ /**
+ * resetReadAccess()
+ *
+ * @this {Memory}
+ */
+ resetReadAccess: function() {
+ this.readByte = this.readByteDirect;
+ this.readShort = this.readShortDirect;
+ },
+ /**
+ * resetWriteAccess()
+ *
+ * @this {Memory}
+ */
+ resetWriteAccess: function() {
+ this.writeByte = this.fReadOnly? this.writeNone : this.writeByteDirect;
+ this.writeShort = this.fReadOnly? this.writeShortDefault : this.writeShortDirect;
+ },
+ /**
+ * printAddr(sMessage)
+ *
+ * @this {Memory}
+ * @param {string} sMessage
+ */
+ printAddr: function(sMessage) {
+ if (DEBUG && this.dbg && this.dbg.messageEnabled(Messages.MEM)) {
+ this.dbg.printMessage(sMessage + ' ' + (this.addr != null? ('%' + str.toHex(this.addr)) : '#' + this.id), true);
+ }
+ },
+ /**
+ * addBreakpoint(off, fWrite)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {boolean} fWrite
+ */
+ addBreakpoint: function(off, fWrite) {
+ if (!fWrite) {
+ if (this.cReadBreakpoints++ === 0) {
+ this.setReadAccess(Memory.afnChecked, false);
+ }
+ if (DEBUG) this.printAddr("read breakpoint added to memory block");
+ }
+ else {
+ if (this.cWriteBreakpoints++ === 0) {
+ this.setWriteAccess(Memory.afnChecked, false);
+ }
+ if (DEBUG) this.printAddr("write breakpoint added to memory block");
+ }
+ },
+ /**
+ * removeBreakpoint(off, fWrite)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {boolean} fWrite
+ */
+ removeBreakpoint: function(off, fWrite) {
+ if (!fWrite) {
+ if (--this.cReadBreakpoints === 0) {
+ this.resetReadAccess();
+ if (DEBUG) this.printAddr("all read breakpoints removed from memory block");
+ }
+ Component.assert(this.cReadBreakpoints >= 0);
+ }
+ else {
+ if (--this.cWriteBreakpoints === 0) {
+ this.resetWriteAccess();
+ if (DEBUG) this.printAddr("all write breakpoints removed from memory block");
+ }
+ Component.assert(this.cWriteBreakpoints >= 0);
+ }
+ },
+ /**
+ * copyBreakpoints(dbg, mem)
+ *
+ * @this {Memory}
+ * @param {Debugger} [dbg]
+ * @param {Memory} [mem] (outgoing Memory block to copy breakpoints from, if any)
+ */
+ copyBreakpoints: function(dbg, mem) {
+ this.dbg = dbg;
+ this.cReadBreakpoints = this.cWriteBreakpoints = 0;
+ if (mem) {
+ if ((this.cReadBreakpoints = mem.cReadBreakpoints)) {
+ this.setReadAccess(Memory.afnChecked, false);
+ }
+ if ((this.cWriteBreakpoints = mem.cWriteBreakpoints)) {
+ this.setWriteAccess(Memory.afnChecked, false);
+ }
+ }
+ },
+ /**
+ * readNone(off)
+ *
+ * Previously, this always returned 0x00, but the initial memory probe by the COMPAQ DeskPro 386 ROM BIOS
+ * writes 0x0000 to the first word of every 64Kb block in the nearly 16Mb address space it supports, and
+ * if it reads back 0x0000, it will initially think that LOTS of RAM exists, only to be disappointed later
+ * when it performs a more exhaustive memory test, generating unwanted error messages in the process.
+ *
+ * TODO: Determine if we should have separate readByteNone(), readShortNone() and readLongNone() functions
+ * to return 0xff, 0xffff and 0xffffffff|0, respectively. This seems sufficient for now, as it seems unlikely
+ * that a system would require nonexistent memory locations to return ALL bits set.
+ *
+ * Also, I'm reluctant to address that potential issue by simply returning -1, because to date, the above
+ * Memory interfaces have always returned values that are properly masked to 8, 16 or 32 bits, respectively.
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readNone: function readNone(off, addr) {
+ if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
+ this.dbg.message("attempt to read invalid block %" + str.toHex(this.addr), true);
+ }
+ return 0xff;
+ },
+ /**
+ * writeNone(off, v, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} v (could be either a byte or word value, since we use the same handler for both kinds of accesses)
+ * @param {number} addr
+ */
+ writeNone: function writeNone(off, v, addr) {
+ if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
+ this.dbg.message("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr), true);
+ }
+ },
+ /**
+ * readShortDefault(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readShortDefault: function readShortDefault(off, addr) {
+ return this.readByte(off++, addr++) | (this.readByte(off, addr) << 8);
+ },
+ /**
+ * writeShortDefault(off, w, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} w
+ * @param {number} addr
+ */
+ writeShortDefault: function writeShortDefault(off, w, addr) {
+ this.writeByte(off++, w & 0xff, addr++);
+ this.writeByte(off, w >> 8, addr);
+ },
+ /**
+ * readByteMemory(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readByteMemory: function readByteMemory(off, addr) {
+ if (BYTEARRAYS) {
+ return this.ab[off];
+ }
+ return ((this.adw[off >> 2] >>> ((off & 0x3) << 3)) & 0xff);
+ },
+ /**
+ * readShortMemory(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readShortMemory: function readShortMemory(off, addr) {
+ if (BYTEARRAYS) {
+ return this.ab[off] | (this.ab[off + 1] << 8);
+ }
+ var w;
+ var idw = off >> 2;
+ var nShift = (off & 0x3) << 3;
+ var dw = (this.adw[idw] >> nShift);
+ if (nShift < 24) {
+ w = dw & 0xffff;
+ } else {
+ w = (dw & 0xff) | ((this.adw[idw + 1] & 0xff) << 8);
+ }
+ return w;
+ },
+ /**
+ * writeByteMemory(off, b, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} b
+ * @param {number} addr
+ */
+ writeByteMemory: function writeByteMemory(off, b, addr) {
+ if (BYTEARRAYS) {
+ this.ab[off] = b;
+ } else {
+ var idw = off >> 2;
+ var nShift = (off & 0x3) << 3;
+ this.adw[idw] = (this.adw[idw] & ~(0xff << nShift)) | (b << nShift);
+ }
+ this.fDirty = true;
+ },
+ /**
+ * writeShortMemory(off, w, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} w
+ * @param {number} addr
+ */
+ writeShortMemory: function writeShortMemory(off, w, addr) {
+ if (BYTEARRAYS) {
+ this.ab[off] = (w & 0xff);
+ this.ab[off + 1] = (w >> 8);
+ } else {
+ var idw = off >> 2;
+ var nShift = (off & 0x3) << 3;
+ if (nShift < 24) {
+ this.adw[idw] = (this.adw[idw] & ~(0xffff << nShift)) | (w << nShift);
+ } else {
+ this.adw[idw] = (this.adw[idw] & 0x00ffffff) | (w << 24);
+ idw++;
+ this.adw[idw] = (this.adw[idw] & (0xffffff00|0)) | (w >> 8);
+ }
+ }
+ this.fDirty = true;
+ },
+ /**
+ * readByteChecked(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readByteChecked: function readByteChecked(off, addr) {
+ if (DEBUGGER && this.dbg && this.addr != null) {
+ this.dbg.checkMemoryRead(this.addr + off);
+ }
+ return this.readByteDirect(off, addr);
+ },
+ /**
+ * readShortChecked(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readShortChecked: function readShortChecked(off, addr) {
+ if (DEBUGGER && this.dbg && this.addr != null) {
+ this.dbg.checkMemoryRead(this.addr + off, 2);
+ }
+ return this.readShortDirect(off, addr);
+ },
+ /**
+ * writeByteChecked(off, b, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @param {number} b
+ */
+ writeByteChecked: function writeByteChecked(off, b, addr) {
+ if (DEBUGGER && this.dbg && this.addr != null) {
+ this.dbg.checkMemoryWrite(this.addr + off);
+ }
+ if (this.fReadOnly) this.writeNone(off, b, addr); else this.writeByteDirect(off, b, addr);
+ },
+ /**
+ * writeShortChecked(off, w, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @param {number} w
+ */
+ writeShortChecked: function writeShortChecked(off, w, addr) {
+ if (DEBUGGER && this.dbg && this.addr != null) {
+ this.dbg.checkMemoryWrite(this.addr + off, 2)
+ }
+ if (this.fReadOnly) this.writeNone(off, w, addr); else this.writeShortDirect(off, w, addr);
+ },
+ /**
+ * readByteBE(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readByteBE: function readByteBE(off, addr) {
+ return this.ab[off];
+ },
+ /**
+ * readByteLE(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readByteLE: function readByteLE(off, addr) {
+ return this.ab[off];
+ },
+ /**
+ * readShortBE(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readShortBE: function readShortBE(off, addr) {
+ return this.dv.getUint16(off, true);
+ },
+ /**
+ * readShortLE(off, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @return {number}
+ */
+ readShortLE: function readShortLE(off, addr) {
+ /*
+ * TODO: It remains to be seen if there's any advantage to checking the offset for an aligned read
+ * vs. always reading the bytes separately; it seems a safe bet for longs, but it's less clear for shorts.
+ */
+ return (off & 0x1)? (this.ab[off] | (this.ab[off+1] << 8)) : this.aw[off >> 1];
+ },
+ /**
+ * writeByteBE(off, b, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} b
+ * @param {number} addr
+ */
+ writeByteBE: function writeByteBE(off, b, addr) {
+ this.ab[off] = b;
+ this.fDirty = true;
+ },
+ /**
+ * writeByteLE(off, b, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @param {number} b
+ */
+ writeByteLE: function writeByteLE(off, b, addr) {
+ this.ab[off] = b;
+ this.fDirty = true;
+ },
+ /**
+ * writeShortBE(off, w, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @param {number} w
+ */
+ writeShortBE: function writeShortBE(off, w, addr) {
+ this.dv.setUint16(off, w, true);
+ this.fDirty = true;
+ },
+ /**
+ * writeShortLE(off, w, addr)
+ *
+ * @this {Memory}
+ * @param {number} off
+ * @param {number} addr
+ * @param {number} w
+ */
+ writeShortLE: function writeShortLE(off, w, addr) {
+ /*
+ * TODO: It remains to be seen if there's any advantage to checking the offset for an aligned write
+ * vs. always writing the bytes separately; it seems a safe bet for longs, but it's less clear for shorts.
+ */
+ if (off & 0x1) {
+ this.ab[off] = w;
+ this.ab[off+1] = w >> 8;
+ } else {
+ this.aw[off >> 1] = w;
+ }
+ this.fDirty = true;
+ }
+};
+
+/*
+ * This is the effective definition of afnNone, but we need not fully define it, because setAccess()
+ * uses these defaults when any of the 6 handlers (ie, 3 read handlers and 3 write handlers) are undefined.
+ *
+Memory.afnNone = [Memory.prototype.readNone, Memory.prototype.readShortDefault, Memory.prototype.writeNone, Memory.prototype.writeShortDefault];
+ */
+
+Memory.afnNone = [];
+Memory.afnMemory = [Memory.prototype.readByteMemory, Memory.prototype.readShortMemory, Memory.prototype.writeByteMemory, Memory.prototype.writeShortMemory];
+Memory.afnChecked = [Memory.prototype.readByteChecked, Memory.prototype.readShortChecked, Memory.prototype.writeByteChecked, Memory.prototype.writeShortChecked];
+
+if (TYPEDARRAYS) {
+ Memory.afnArrayBE = [Memory.prototype.readByteBE, Memory.prototype.readShortBE, Memory.prototype.writeByteBE, Memory.prototype.writeShortBE];
+ Memory.afnArrayLE = [Memory.prototype.readByteLE, Memory.prototype.readShortLE, Memory.prototype.writeByteLE, Memory.prototype.writeShortLE];
+}
+
+if (NODE) module.exports = Memory;
diff --git a/modules/pc6502/lib/messages.js b/modules/pc6502/lib/messages.js
new file mode 100644
index 000000000..567de03a0
--- /dev/null
+++ b/modules/pc6502/lib/messages.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Defines message categories.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+var Messages = {
+ CPU: 0x00000001,
+ BUS: 0x00000040,
+ MEM: 0x00000080,
+ PORT: 0x00000100,
+ CHIPSET: 0x00008000,
+ KEYBOARD: 0x00010000,
+ KEYS: 0x00020000,
+ VIDEO: 0x00040000,
+ FDC: 0x00080000,
+ DISK: 0x00200000,
+ SERIAL: 0x00800000,
+ SPEAKER: 0x02000000,
+ COMPUTER: 0x04000000,
+ LOG: 0x20000000,
+ WARN: 0x40000000,
+ HALT: 0x80000000|0
+};
+
+if (NODE) module.exports = Messages;
diff --git a/modules/pc6502/lib/nodebugger.js b/modules/pc6502/lib/nodebugger.js
new file mode 100644
index 000000000..1fd18747d
--- /dev/null
+++ b/modules/pc6502/lib/nodebugger.js
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Compile-time definitions for Debugger-less configurations.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+/*
+ * WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
+ * In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
+ * nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
+ * "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
+ * debugger.js from the compilation process altogether.
+ *
+ * However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
+ * I would still like to skip loading debugger.js altogether. To do that, we must arrange for this additional file,
+ * nodebugger.js, to be loaded immediately after defines.js, *explicitly* overriding the previously defined value
+ * of DEBUGGER with *false*.
+ */
+DEBUGGER = false;
diff --git a/modules/pc6502/lib/panel.js b/modules/pc6502/lib/panel.js
new file mode 100644
index 000000000..ec9b248b1
--- /dev/null
+++ b/modules/pc6502/lib/panel.js
@@ -0,0 +1,185 @@
+/**
+ * @fileoverview Implements the PC6502 Panel component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var usr = require("../../shared/lib/usrlib");
+ var web = require("../../shared/lib/weblib");
+ var Component = require("../../shared/lib/component");
+ var Bus = require("./bus");
+ var Memory = require("./memory");
+ var CPUDef = require("./cpudef");
+}
+
+/**
+ * Panel(parmsPanel)
+ *
+ * The Panel component has no required (parmsPanel) properties.
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsPanel
+ */
+function Panel(parmsPanel)
+{
+ Component.call(this, "Panel", parmsPanel, Panel);
+}
+
+Component.subclass(Panel);
+
+/**
+ * setBinding(sHTMLType, sBinding, control, sValue)
+ *
+ * Most panel layouts don't have bindings of their own, so we pass along all binding requests to the
+ * Computer, CPU, Keyboard and Debugger components first. The order shouldn't matter, since any component
+ * that doesn't recognize the specified binding should simply ignore it.
+ *
+ * @this {Panel}
+ * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
+ * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
+ * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
+ * @param {string} [sValue] optional data value
+ * @return {boolean} true if binding was successful, false if unrecognized binding request
+ */
+Panel.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
+{
+ if (this.cmp && this.cmp.setBinding(sHTMLType, sBinding, control, sValue)) return true;
+ if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control, sValue)) return true;
+ if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control, sValue)) return true;
+ if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control, sValue)) return true;
+ return this.parent.setBinding.call(this, sHTMLType, sBinding, control, sValue);
+};
+
+/**
+ * initBus(cmp, bus, cpu, dbg)
+ *
+ * @this {Panel}
+ * @param {Computer} cmp
+ * @param {Bus} bus
+ * @param {CPUState} cpu
+ * @param {Debugger} dbg
+ */
+Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
+{
+ this.cmp = cmp;
+ this.bus = bus;
+ this.cpu = cpu;
+ this.dbg = dbg;
+ this.kbd = cmp.getMachineComponent("Keyboard");
+};
+
+/**
+ * powerUp(data, fRepower)
+ *
+ * @this {Panel}
+ * @param {Object|null} data
+ * @param {boolean} [fRepower]
+ * @return {boolean} true if successful, false if failure
+ */
+Panel.prototype.powerUp = function(data, fRepower)
+{
+ if (!fRepower) Panel.init();
+ return true;
+};
+
+/**
+ * powerDown(fSave, fShutdown)
+ *
+ * @this {Panel}
+ * @param {boolean} [fSave]
+ * @param {boolean} [fShutdown]
+ * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
+ */
+Panel.prototype.powerDown = function(fSave, fShutdown)
+{
+ return true;
+};
+
+/**
+ * updateStatus(fForce)
+ *
+ * Update function for Panels containing elements with high-frequency display requirements.
+ *
+ * For older (and slower) DOM-based display elements, those are sill being managed by the CPUState component,
+ * so it has its own updateStatus() handler.
+ *
+ * The Computer's updateStatus() handler is currently responsible for calling both our handler and the CPU's handler.
+ *
+ * @this {Panel}
+ * @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
+ */
+Panel.prototype.updateStatus = function(fForce)
+{
+};
+
+/**
+ * Panel.init()
+ *
+ * This function operates on every HTML element of class "panel", extracting the
+ * JSON-encoded parameters for the Panel constructor from the element's "data-value"
+ * attribute, invoking the constructor to create a Panel component, and then binding
+ * any associated HTML controls to the new component.
+ *
+ * NOTE: Unlike most other component init() functions, this one is designed to be
+ * called multiple times: once at load time, so that we can bind our print()
+ * function to the panel's output control ASAP, and again when the Computer component
+ * is verifying that all components are ready and invoking their powerUp() functions.
+ *
+ * Our powerUp() method gives us a second opportunity to notify any components that
+ * that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they
+ * might want to use.
+ */
+Panel.init = function()
+{
+ var fReady = false;
+ var aePanels = Component.getElementsByClass(document, PCJSCLASS, "panel");
+ for (var iPanel=0; iPanel < aePanels.length; iPanel++) {
+ var ePanel = aePanels[iPanel];
+ var parmsPanel = Component.getComponentParms(ePanel);
+ var panel = Component.getComponentByID(parmsPanel['id']);
+ if (!panel) {
+ fReady = true;
+ panel = new Panel(parmsPanel);
+ }
+ Component.bindComponentControls(panel, ePanel, PCJSCLASS);
+ if (fReady) panel.setReady();
+ }
+};
+
+/*
+ * Initialize every Panel module on the page.
+ */
+web.onInit(Panel.init);
+
+if (NODE) module.exports = Panel;
diff --git a/modules/pc6502/lib/ram.js b/modules/pc6502/lib/ram.js
new file mode 100644
index 000000000..9eb3e874d
--- /dev/null
+++ b/modules/pc6502/lib/ram.js
@@ -0,0 +1,197 @@
+/**
+ * @fileoverview Implements the PC6502 RAM component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var web = require("../../shared/lib/weblib");
+ var Component = require("../../shared/lib/component");
+ var Memory = require("./memory");
+ var ROM = require("./rom");
+ var State = require("./state");
+}
+
+/**
+ * RAM(parmsRAM)
+ *
+ * The RAM component expects the following (parmsRAM) properties:
+ *
+ * addr: starting physical address of RAM (default is 0)
+ * size: amount of RAM, in bytes (default is 0, which means defer to motherboard switch settings)
+ *
+ * NOTE: We make a note of the specified size, but no memory is initially allocated for the RAM until the
+ * Computer component calls powerUp().
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsRAM
+ */
+function RAM(parmsRAM)
+{
+ Component.call(this, "RAM", parmsRAM, RAM);
+
+ this.addrRAM = parmsRAM['addr'];
+ this.sizeRAM = parmsRAM['size'];
+ this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
+ this.fAllocated = false;
+}
+
+Component.subclass(RAM);
+
+/**
+ * initBus(cmp, bus, cpu, dbg)
+ *
+ * @this {RAM}
+ * @param {Computer} cmp
+ * @param {Bus} bus
+ * @param {CPUState} cpu
+ * @param {Debugger} dbg
+ */
+RAM.prototype.initBus = function(cmp, bus, cpu, dbg)
+{
+ this.bus = bus;
+ this.cpu = cpu;
+ this.dbg = dbg;
+ this.setReady();
+};
+
+/**
+ * powerUp(data, fRepower)
+ *
+ * @this {RAM}
+ * @param {Object|null} data
+ * @param {boolean} [fRepower]
+ * @return {boolean} true if successful, false if failure
+ */
+RAM.prototype.powerUp = function(data, fRepower)
+{
+ if (!fRepower) {
+ /*
+ * The Computer powers up the CPU last, at which point CPUState state is restored,
+ * which includes the Bus state, and since we use the Bus to allocate all our memory,
+ * memory contents are already restored for us, so we don't need the usual restore
+ * logic. We just need to call reset(), to allocate memory for the RAM.
+ */
+ this.reset();
+ }
+ return true;
+};
+
+/**
+ * powerDown(fSave, fShutdown)
+ *
+ * @this {RAM}
+ * @param {boolean} [fSave]
+ * @param {boolean} [fShutdown]
+ * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
+ */
+RAM.prototype.powerDown = function(fSave, fShutdown)
+{
+ /*
+ * The Computer powers down the CPU first, at which point CPUState state is saved,
+ * which includes the Bus state, and since we use the Bus component to allocate all
+ * our memory, memory contents are already saved for us, so we don't need the usual
+ * save logic.
+ */
+ return (fSave)? this.save() : true;
+};
+
+/**
+ * reset()
+ *
+ * @this {RAM}
+ */
+RAM.prototype.reset = function()
+{
+ if (!this.fAllocated && this.sizeRAM) {
+ if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory.TYPE.RAM)) {
+ this.fAllocated = true;
+ }
+ }
+ if (!this.fAllocated) {
+ Component.error("No RAM allocated");
+ }
+};
+
+/**
+ * save()
+ *
+ * This implements save support for the RAM component.
+ *
+ * @this {RAM}
+ * @return {Object}
+ */
+RAM.prototype.save = function()
+{
+ return null;
+};
+
+/**
+ * restore(data)
+ *
+ * This implements restore support for the RAM component.
+ *
+ * @this {RAM}
+ * @param {Object} data
+ * @return {boolean} true if successful, false if failure
+ */
+RAM.prototype.restore = function(data)
+{
+ return true;
+};
+
+/**
+ * RAM.init()
+ *
+ * This function operates on every HTML element of class "ram", extracting the
+ * JSON-encoded parameters for the RAM constructor from the element's "data-value"
+ * attribute, invoking the constructor to create a RAM component, and then binding
+ * any associated HTML controls to the new component.
+ */
+RAM.init = function()
+{
+ var aeRAM = Component.getElementsByClass(document, PCJSCLASS, "ram");
+ for (var iRAM = 0; iRAM < aeRAM.length; iRAM++) {
+ var eRAM = aeRAM[iRAM];
+ var parmsRAM = Component.getComponentParms(eRAM);
+ var ram = new RAM(parmsRAM);
+ Component.bindComponentControls(ram, eRAM, PCJSCLASS);
+ }
+};
+
+/*
+ * Initialize all the RAM modules on the page.
+ */
+web.onInit(RAM.init);
+
+if (NODE) module.exports = RAM;
diff --git a/modules/pc6502/lib/rom.js b/modules/pc6502/lib/rom.js
new file mode 100644
index 000000000..996c3d3ab
--- /dev/null
+++ b/modules/pc6502/lib/rom.js
@@ -0,0 +1,388 @@
+/**
+ * @fileoverview Implements the PC6502 ROM component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var web = require("../../shared/lib/weblib");
+ var DumpAPI = require("../../shared/lib/dumpapi");
+ var Component = require("../../shared/lib/component");
+ var Memory = require("./memory");
+ var CPUDef = require("./cpudef");
+}
+
+/**
+ * ROM(parmsROM)
+ *
+ * The ROM component expects the following (parmsROM) properties:
+ *
+ * addr: physical address of ROM
+ * size: amount of ROM, in bytes
+ * alias: physical alias address (null if none)
+ * file: name of ROM data file
+ * writable: true to make ROM writable (default is false)
+ *
+ * NOTE: The ROM data will not be copied into place until the Bus is ready (see initBus()) AND the
+ * ROM data file has finished loading (see doneLoad()).
+ *
+ * Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM you received
+ * is the ROM you expected.
+ *
+ * Finally, while making ROM "writable" may seem a contradiction in terms, I want to be able to load selected
+ * CP/M binary files into memory purely for testing purposes, and the RAM component has no "file" option, so the
+ * simplest solution was to add the option to load binary files into memory as "writable" ROMs.
+ *
+ * Moreover, if a "writable" ROM is installed at addr 0x100, that triggers our "Fake CP/M" support, providing
+ * a quick-and-dirty means of loading simple CP/M test binaries. See addROM() for details.
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsROM
+ */
+function ROM(parmsROM)
+{
+ Component.call(this, "ROM", parmsROM, ROM);
+
+ this.abROM = null;
+ this.addrROM = parmsROM['addr'];
+ this.sizeROM = parmsROM['size'];
+
+ /*
+ * The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of
+ * physical addresses; eg:
+ *
+ * [0xf0000,0xffff0000,0xffff8000]
+ *
+ * We could have overloaded 'addr' to accomplish the same thing, but I think it's better to have any
+ * aliased locations listed under a separate property.
+ *
+ * Most ROMs are not aliased, in which case the 'alias' property should have the default value of null.
+ */
+ this.addrAlias = parmsROM['alias'];
+
+ this.sFilePath = parmsROM['file'];
+ this.sFileName = str.getBaseName(this.sFilePath);
+
+ if (this.sFilePath) {
+ var sFileURL = this.sFilePath;
+ if (DEBUG) this.log('load("' + sFileURL + '")');
+ /*
+ * If the selected ROM file has a ".json" extension, then we assume it's pre-converted
+ * JSON-encoded ROM data, so we load it as-is; ditto for ROM files with a ".hex" extension.
+ * Otherwise, we ask our server-side ROM converter to return the file in a JSON-compatible format.
+ */
+ var sFileExt = str.getExtension(this.sFileName);
+ if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
+ sFileURL = web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFilePath + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true';
+ }
+ var rom = this;
+ web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
+ rom.doneLoad(sURL, sResponse, nErrorCode);
+ });
+ }
+}
+
+Component.subclass(ROM);
+
+/*
+ * NOTE: There's currently no need for this component to have a reset() function, since
+ * once the ROM data is loaded, it can't be changed, so there's nothing to reinitialize.
+ *
+ * OK, well, I take that back, because the Debugger, if installed, has the ability to modify
+ * ROM contents, so in that case, having a reset() function that restores the original ROM data
+ * might be useful; then again, it might not, depending on what you're trying to debug.
+ *
+ * If we do add reset(), then we'll want to change copyROM() to hang onto the original
+ * ROM data; currently, we release it after copying it into the read-only memory allocated
+ * via bus.addMemory().
+ */
+
+/**
+ * initBus(cmp, bus, cpu, dbg)
+ *
+ * @this {ROM}
+ * @param {Computer} cmp
+ * @param {Bus} bus
+ * @param {CPUState} cpu
+ * @param {Debugger} dbg
+ */
+ROM.prototype.initBus = function(cmp, bus, cpu, dbg)
+{
+ this.bus = bus;
+ this.cpu = cpu;
+ this.dbg = dbg;
+ this.copyROM();
+};
+
+/**
+ * powerUp(data, fRepower)
+ *
+ * @this {ROM}
+ * @param {Object|null} data
+ * @param {boolean} [fRepower]
+ * @return {boolean} true if successful, false if failure
+ */
+ROM.prototype.powerUp = function(data, fRepower)
+{
+ if (this.aSymbols) {
+ if (this.dbg) {
+ this.dbg.addSymbols(this.id, this.addrROM, this.sizeROM, this.aSymbols);
+ }
+ /*
+ * Our only role in the handling of symbols is to hand them off to the Debugger at our
+ * first opportunity. Now that we've done that, our copy of the symbols, if any, are toast.
+ */
+ delete this.aSymbols;
+ }
+ return true;
+};
+
+/**
+ * powerDown(fSave, fShutdown)
+ *
+ * Since we have nothing to do on powerDown(), and no state to return, we could simply omit
+ * this function. But it doesn't hurt anything, and maybe we'll use our state to save something
+ * useful down the road, like user-defined symbols (ie, symbols that the Debugger may have
+ * created, above and beyond those symbols we automatically loaded, if any, along with the ROM).
+ *
+ * @this {ROM}
+ * @param {boolean} [fSave]
+ * @param {boolean} [fShutdown]
+ * @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
+ */
+ROM.prototype.powerDown = function(fSave, fShutdown)
+{
+ return true;
+};
+
+/**
+ * doneLoad(sURL, sROMData, nErrorCode)
+ *
+ * @this {ROM}
+ * @param {string} sURL
+ * @param {string} sROMData
+ * @param {number} nErrorCode (response from server if anything other than 200)
+ */
+ROM.prototype.doneLoad = function(sURL, sROMData, nErrorCode)
+{
+ if (nErrorCode) {
+ this.notice("Unable to load system ROM (error " + nErrorCode + ": " + sURL + ")");
+ return;
+ }
+
+ Component.addMachineResource(this.idMachine, sURL, sROMData);
+
+ if (sROMData.charAt(0) == "[" || sROMData.charAt(0) == "{") {
+ try {
+ /*
+ * The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
+ */
+ var rom = eval("(" + sROMData + ")");
+ var ab = rom['bytes'];
+ var adw = rom['data'];
+
+ if (ab) {
+ this.abROM = ab;
+ }
+ else if (adw) {
+ /*
+ * Convert all the DWORDs into BYTEs, so that subsequent code only has to deal with abROM.
+ */
+ this.abROM = new Array(adw.length * 4);
+ for (var idw = 0, ib = 0; idw < adw.length; idw++) {
+ this.abROM[ib++] = adw[idw] & 0xff;
+ this.abROM[ib++] = (adw[idw] >> 8) & 0xff;
+ this.abROM[ib++] = (adw[idw] >> 16) & 0xff;
+ this.abROM[ib++] = (adw[idw] >> 24) & 0xff;
+ }
+ }
+ else {
+ this.abROM = rom;
+ }
+
+ this.aSymbols = rom['symbols'];
+
+ if (!this.abROM.length) {
+ Component.error("Empty ROM: " + sURL);
+ return;
+ }
+ else if (this.abROM.length == 1) {
+ Component.error(this.abROM[0]);
+ return;
+ }
+ } catch (e) {
+ this.notice("ROM data error: " + e.message);
+ return;
+ }
+ }
+ else {
+ /*
+ * Parse the ROM data manually; we assume it's in "simplified" hex form (a series of hex byte-values
+ * separated by whitespace).
+ */
+ var sHexData = sROMData.replace(/\n/gm, " ").replace(/ +$/, "");
+ var asHexData = sHexData.split(" ");
+ this.abROM = new Array(asHexData.length);
+ for (var i = 0; i < asHexData.length; i++) {
+ this.abROM[i] = str.parseInt(asHexData[i], 16);
+ }
+ }
+ this.copyROM();
+};
+
+/**
+ * copyROM()
+ *
+ * This function is called by both initBus() and doneLoad(), but it cannot copy the the ROM data into place
+ * until after initBus() has received the Bus component AND doneLoad() has received the abROM data. When both
+ * those criteria are satisfied, the component becomes "ready".
+ *
+ * @this {ROM}
+ */
+ROM.prototype.copyROM = function()
+{
+ if (!this.isReady()) {
+ if (!this.sFilePath) {
+ this.setReady();
+ }
+ else if (this.abROM && this.bus) {
+ /*
+ * If no explicit size was specified, then use whatever the actual size is.
+ */
+ if (!this.sizeROM) {
+ this.sizeROM = this.abROM.length;
+ }
+ if (this.abROM.length != this.sizeROM) {
+ /*
+ * Note that setError() sets the component's fError flag, which in turn prevents setReady() from
+ * marking the component ready. TODO: Revisit this decision. On the one hand, it sounds like a
+ * good idea to stop the machine in its tracks whenever a setError() occurs, but there may also be
+ * times when we'd like to forge ahead anyway.
+ */
+ this.setError("ROM size (" + str.toHexLong(this.abROM.length) + ") does not match specified size (" + str.toHexLong(this.sizeROM) + ")");
+ }
+ else if (this.addROM(this.addrROM)) {
+
+ var aliases = [];
+ if (typeof this.addrAlias == "number") {
+ aliases.push(this.addrAlias);
+ } else if (this.addrAlias != null && this.addrAlias.length) {
+ aliases = this.addrAlias;
+ }
+ for (var i = 0; i < aliases.length; i++) {
+ this.cloneROM(aliases[i]);
+ }
+ /*
+ * We used to hang onto the original ROM data so that we could restore any bytes the CPU overwrote,
+ * using memory write-notification handlers, but with the introduction of read-only memory blocks, that's
+ * no longer necessary.
+ *
+ * TODO: Consider an option to retain the ROM data, and give the user some way of restoring ROMs.
+ * That may be useful for "resumable" machines that save/restore all dirty block of memory, regardless
+ * whether they're ROM or RAM. However, the only way to modify a machine's ROM is with the Debugger,
+ * and Debugger users should know better.
+ */
+ delete this.abROM;
+ }
+ this.setReady();
+ }
+ }
+};
+
+/**
+ * addROM(addr)
+ *
+ * @this {ROM}
+ * @param {number} addr
+ * @return {boolean}
+ */
+ROM.prototype.addROM = function(addr)
+{
+ if (this.bus.addMemory(addr, this.sizeROM, Memory.TYPE.ROM)) {
+ if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexLong(addr) + " (" + str.toHexLong(this.abROM.length) + " bytes)");
+ var i;
+ for (i = 0; i < this.abROM.length; i++) {
+ this.bus.setByteDirect(addr + i, this.abROM[i]);
+ }
+ return true;
+ }
+ /*
+ * We don't need to report an error here, because addMemory() already takes care of that.
+ */
+ return false;
+};
+
+/**
+ * cloneROM(addr)
+ *
+ * For ROMs with one or more alias addresses, we used to call addROM() for each address. However,
+ * that obviously wasted memory, since each alias was an independent copy, and if you used the
+ * Debugger to edit the ROM in one location, the changes would not appear in the other location(s).
+ *
+ * Now that the Bus component provides low-level getMemoryBlocks() and setMemoryBlocks() methods
+ * to manually get and set the blocks of any memory range, it is now possible to create true aliases.
+ *
+ * @this {ROM}
+ * @param {number} addr
+ */
+ROM.prototype.cloneROM = function(addr)
+{
+ var aBlocks = this.bus.getMemoryBlocks(this.addrROM, this.sizeROM);
+ this.bus.setMemoryBlocks(addr, this.sizeROM, aBlocks);
+};
+
+/**
+ * ROM.init()
+ *
+ * This function operates on every HTML element of class "rom", extracting the
+ * JSON-encoded parameters for the ROM constructor from the element's "data-value"
+ * attribute, invoking the constructor to create a ROM component, and then binding
+ * any associated HTML controls to the new component.
+ */
+ROM.init = function()
+{
+ var aeROM = Component.getElementsByClass(document, PCJSCLASS, "rom");
+ for (var iROM = 0; iROM < aeROM.length; iROM++) {
+ var eROM = aeROM[iROM];
+ var parmsROM = Component.getComponentParms(eROM);
+ var rom = new ROM(parmsROM);
+ Component.bindComponentControls(rom, eROM, PCJSCLASS);
+ }
+};
+
+/*
+ * Initialize all the ROM modules on the page.
+ */
+web.onInit(ROM.init);
+
+if (NODE) module.exports = ROM;
diff --git a/modules/pc6502/lib/state.js b/modules/pc6502/lib/state.js
new file mode 100644
index 000000000..e258b6045
--- /dev/null
+++ b/modules/pc6502/lib/state.js
@@ -0,0 +1,397 @@
+/**
+ * @fileoverview The State class used by PCjs machines.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var web = require("./../../shared/lib/weblib");
+ var Component = require("./../../shared/lib/component");
+ var Messages = require("./messages");
+}
+
+/**
+ * State(component, sVersion, sSuffix)
+ *
+ * State objects are used by components to save/restore their state.
+ *
+ * During a save operation, components add data to a State object via set(),
+ * and then return the resulting data using data().
+ *
+ * During a restore operation, the Computer component passes the results of each
+ * data() call back to the originating component.
+ *
+ * WARNING: Since State objects are low-level objects that have no UI requirements,
+ * they do not inherit from the Component class, so you should only use class methods
+ * of Component, such as Component.assert(), or Debugger methods if the Debugger
+ * is available.
+ *
+ * @constructor
+ * @param {Component} component
+ * @param {string} [sVersion] is used to append a major version number to the key
+ * @param {string} [sSuffix] is used to append any additional suffixes to the key
+ */
+function State(component, sVersion, sSuffix) {
+ this.id = component.id;
+ this.key = State.key(component, sVersion, sSuffix);
+ this.dbg = component.dbg;
+ this.unload(component.parms);
+}
+
+/**
+ * State.key(component, sVersion, sSuffix)
+ *
+ * This encapsulates the key generation code.
+ *
+ * @param {Component} component
+ * @param {string} [sVersion] is used to append a major version number to the key
+ * @param {string} [sSuffix] is used to append any additional suffixes to the key
+ * @return {string} key
+ */
+State.key = function(component, sVersion, sSuffix) {
+ var key = component.id;
+ if (sVersion) {
+ var i = sVersion.indexOf('.');
+ if (i > 0) key += ".v" + sVersion.substr(0, i);
+ }
+ if (sSuffix) {
+ key += "." + sSuffix;
+ }
+ return key;
+};
+
+/**
+ * State.compress(aSrc)
+ *
+ * @param {Array.|null} aSrc
+ * @return {Array.|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
+ */
+State.compress = function(aSrc) {
+ if (aSrc) {
+ var iSrc = 0;
+ var iComp = 0;
+ var aComp = [];
+ while (iSrc < aSrc.length) {
+ var n = aSrc[iSrc];
+ Component.assert(n !== undefined);
+ var iCompare = iSrc + 1;
+ while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
+ aComp[iComp++] = iCompare - iSrc;
+ aComp[iComp++] = n;
+ iSrc = iCompare;
+ }
+ if (aComp.length < aSrc.length) return aComp;
+ }
+ return aSrc;
+};
+
+/**
+ * State.decompress(aComp)
+ *
+ * @param {Array.} aComp
+ * @param {number} nLength is expected length of decompressed data
+ * @return {Array.}
+ */
+State.decompress = function(aComp, nLength) {
+ var iDst = 0;
+ var aDst = new Array(nLength);
+ var iComp = 0;
+ while (iComp < aComp.length - 1) {
+ var c = aComp[iComp++];
+ var n = aComp[iComp++];
+ while (c--) {
+ aDst[iDst++] = n;
+ }
+ }
+ Component.assert(aDst.length == nLength);
+ return aDst;
+};
+
+/**
+ * State.compressEvenOdd(aSrc)
+ *
+ * This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
+ * followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
+ * addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
+ * for compress(), but the best case for compressEvenOdd().
+ *
+ * One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
+ * and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
+ * and return an uninitialized array.
+ *
+ * @param {Array.|null} aSrc
+ * @return {Array.|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
+ */
+State.compressEvenOdd = function(aSrc) {
+ if (aSrc) {
+ var iComp = 0, aComp = [];
+ if (aSrc[0] !== undefined) {
+ for (var off = 0; off < 2; off++) {
+ var iSrc = off;
+ while (iSrc < aSrc.length) {
+ var n = aSrc[iSrc];
+ var iCompare = iSrc + 2;
+ while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
+ aComp[iComp++] = (iCompare - iSrc) >> 1;
+ aComp[iComp++] = n;
+ iSrc = iCompare;
+ }
+ }
+ }
+ if (aComp.length < aSrc.length) return aComp;
+ }
+ return aSrc;
+};
+
+/**
+ * State.decompressEvenOdd(aComp, nLength)
+ *
+ * This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
+ * that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
+ * if you used even/odd compression, then you must use even/odd decompression.
+ *
+ * @param {Array.} aComp
+ * @param {number} nLength is expected length of decompressed data
+ * @return {Array.}
+ */
+State.decompressEvenOdd = function(aComp, nLength) {
+ var iDst = 0;
+ var aDst = new Array(nLength);
+ var iComp = 0;
+ while (iComp < aComp.length - 1) {
+ var c = aComp[iComp++];
+ var n = aComp[iComp++];
+ while (c--) {
+ aDst[iDst] = n;
+ iDst += 2;
+ }
+ /*
+ * The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
+ * the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
+ * done.
+ */
+ Component.assert(iDst <= nLength || iComp == aComp.length);
+ if (iDst == nLength) iDst = 1;
+ }
+ Component.assert(aDst.length == nLength);
+ return aDst;
+};
+
+State.prototype = {
+ constructor: State,
+ /**
+ * set(id, data)
+ *
+ * @this {State}
+ * @param {number|string} id
+ * @param {Object|string} data
+ */
+ set: function(id, data) {
+ try {
+ this[this.id][id] = data;
+ } catch(e) {
+ Component.log(e.message);
+ }
+ },
+ /**
+ * get(id)
+ *
+ * @this {State}
+ * @param {number|string} id
+ * @return {Object|string|null}
+ */
+ get: function(id) {
+ return this[this.id][id] || null;
+ },
+ /**
+ * value()
+ *
+ * Use this instead of data() if you haven't called parse() yet.
+ *
+ * @this {State}
+ * @return {string}
+ */
+ value: function() {
+ return this[this.id];
+ },
+ /**
+ * data()
+ *
+ * @this {State}
+ * @return {Object}
+ */
+ data: function() {
+ return this[this.id];
+ },
+ /**
+ * load(s)
+ *
+ * WARNING: Make sure you follow this call with either a call to parse() or unload(),
+ * because any stringified data that we've loaded isn't usable until it's been parsed.
+ *
+ * @this {State}
+ * @param {Object|string|null} [s]
+ * @return {boolean} true if state exists in localStorage, false if not
+ */
+ load: function(s) {
+ if (s) {
+ this[this.id] = s;
+ this.fLoaded = true;
+ return true;
+ }
+ if (this.fLoaded) {
+ /*
+ * This is assumed to be a redundant load().
+ */
+ return true;
+ }
+ if (web.hasLocalStorage()) {
+ s = web.getLocalStorageItem(this.key);
+ if (s) {
+ this[this.id] = s;
+ this.fLoaded = true;
+ if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes loaded");
+ return true;
+ }
+ }
+ return false;
+ },
+ /**
+ * parse()
+ *
+ * This completes the load() operation, by parsing what was loaded, on the assumption there
+ * might be some benefit to deferring parsing until we've given the user a chance to confirm.
+ * Otherwise, load() could have just as easily done this, too.
+ *
+ * @this {State}
+ * @return {boolean} true if successful, false if error
+ */
+ parse: function() {
+ var fSuccess = true;
+ try {
+ this[this.id] = JSON.parse(this[this.id]);
+ } catch (e) {
+ Component.error(e.message || e);
+ fSuccess = false;
+ }
+ return fSuccess;
+ },
+ /**
+ * store()
+ *
+ * @this {State}
+ * @return {boolean} true if successful, false if error
+ */
+ store: function() {
+ var fSuccess = true;
+ if (web.hasLocalStorage()) {
+ var s = JSON.stringify(this[this.id]);
+ if (web.setLocalStorageItem(this.key, s)) {
+ if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes stored");
+ } else {
+ /*
+ * WARNING: Because browsers tend to disable all alerts() during an "unload" operation,
+ * it's unlikely anyone will ever see the "quota" errors that occur at this point. Need to
+ * think of some way to notify the user that there's a problem, and offer a way of cleaning
+ * up old states.
+ */
+ Component.error("Unable to store " + s.length + " bytes in browser local storage");
+ fSuccess = false;
+ }
+ }
+ return fSuccess;
+ },
+ /**
+ * toString()
+ *
+ * We can't know whether this might be called before parse() or after parse(), so we check.
+ * If before, then this[this.id] will still be in string form; if after, it will be an Object.
+ *
+ * @this {State}
+ * @return {string} JSON-encoded state
+ */
+ toString: function() {
+ var value = this[this.id];
+ return (typeof value == "string"? value : JSON.stringify(value));
+ },
+ /**
+ * unload(parms)
+ *
+ * This discards any data saved via set() or loaded via load(), creating an empty State object.
+ * Note that you have to follow this call with an explicit call to store() if you want to remove
+ * the state from localStorage as well.
+ *
+ * @this {State}
+ * @param {Object} [parms]
+ */
+ unload: function(parms) {
+ this[this.id] = {};
+ if (parms) this.set("parms", parms);
+ this.fLoaded = false;
+ },
+ /**
+ * clear(fAll)
+ *
+ * This unloads the current state, and then clears ALL localStorage for the current machine,
+ * independent of version, to reduce the chance of orphaned states wasting part of our limited allocation.
+ *
+ * @this {State}
+ * @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
+ */
+ clear: function(fAll) {
+ this.unload();
+ var aKeys = web.getLocalStorageKeys();
+ for (var i = 0; i < aKeys.length; i++) {
+ var sKey = aKeys[i];
+ if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
+ web.removeLocalStorageItem(sKey);
+ if (DEBUG) this.printString("localStorage(" + sKey + ") removed");
+ aKeys.splice(i, 1);
+ i = 0;
+ }
+ }
+ },
+ /**
+ * printString(s)
+ *
+ * @this {State}
+ * @param {string} s is any caller-defined string
+ */
+ printString: function(s) {
+ if (DEBUG && DEBUGGER && this.dbg) {
+ if (this.dbg.messageEnabled(Messages.LOG)) {
+ this.dbg.message(s);
+ }
+ }
+ }
+};
+
+if (NODE) module.exports = State;
diff --git a/modules/pc6502/lib/video.js b/modules/pc6502/lib/video.js
new file mode 100644
index 000000000..02c41cb85
--- /dev/null
+++ b/modules/pc6502/lib/video.js
@@ -0,0 +1,719 @@
+/**
+ * @fileoverview Implements the PC6502 Video component.
+ * @author Jeff Parsons
+ * @version 1.0
+ * Created 2016-May-12
+ *
+ * Copyright © 2012-2016 Jeff Parsons
+ *
+ * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
+ * at and .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every source code file of every
+ * copy or modified version of this work, and to display that copyright notice on every screen
+ * that loads or runs any version of this software (see Computer.COPYRIGHT).
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of the
+ * PCjs program for purposes of the GNU General Public License, and the author does not claim
+ * any copyright as to their contents.
+ */
+
+"use strict";
+
+if (NODE) {
+ var str = require("../../shared/lib/strlib");
+ var web = require("../../shared/lib/weblib");
+ var DumpAPI = require("../../shared/lib/dumpapi");
+ var Component = require("../../shared/lib/component");
+ var Memory = require("./memory");
+ var Messages = require("./messages");
+ var State = require("./state");
+}
+
+/**
+ * Video(parmsVideo, canvas, context, textarea, container)
+ *
+ * The Video component can be configured with the following (parmsVideo) properties:
+ *
+ * screenWidth: width of the screen canvas, in pixels
+ * screenHeight: height of the screen canvas, in pixels
+ * screenColor: background color of the screen canvas (default is black)
+ * screenRotate: the amount of counter-clockwise screen rotation required (eg, -90 or 270)
+ * aspectRatio (eg, 1.33)
+ * bufferAddr: the starting address of the frame buffer (eg, 0x2400)
+ * bufferCols: the width of a single frame buffer row, in pixels (eg, 256)
+ * bufferRows: the number of frame buffer rows (eg, 224)
+ * bufferBits: the number of bits per column (default is 1)
+ * bufferLeft: the bit position of the left-most pixel in a byte (default is 0; CGA uses 7)
+ * bufferRotate: the amount of counter-clockwise buffer rotation required (eg, -90 or 270)
+ * interruptRate: normally the same as (or some multiple of) refreshRate (eg, 120)
+ * refreshRate: how many times updateScreen() should be performed per second (eg, 60)
+ *
+ * We record all the above values now, but we defer creation of the frame buffer until our initBus()
+ * handler is called. At that point, we will also compute the extent of the frame buffer, determine the
+ * appropriate "cell" size (ie, the number of pixels that updateScreen() will fetch and process at once),
+ * and then allocate our cell cache.
+ *
+ * Why interruptRate in addition to refreshRate? A higher interrupt rate is required for Space Invaders,
+ * because even though the CRT refreshes at 60Hz, the CRT controller interrupts the CPU *twice* per
+ * refresh (once after the top half of the screen has been redrawn, and again after the bottom half has
+ * been redrawn), so we need an interrupt rate of 120Hz. We pass the higher rate on to the CPU, so that
+ * it will call updateScreen() more frequently, but we still limit our screen updates to every *other* call.
+ *
+ * bufferRotate is an alternative to screenRotate; you may set one or the other (but not both) to -90 to
+ * enable different approaches to counter-clockwise 90-degree image rotation. screenRotate uses canvas
+ * transformation methods (translate(), rotate(), and scale()), while bufferRotate inverts the dimensions
+ * of the off-screen buffer and then relies on setPixel() to "rotate" the data into it.
+ *
+ * @constructor
+ * @extends Component
+ * @param {Object} parmsVideo
+ * @param {Object} [canvas]
+ * @param {Object} [context]
+ * @param {Object} [textarea]
+ * @param {Object} [container]
+ */
+function Video(parmsVideo, canvas, context, textarea, container)
+{
+ var video = this;
+ this.fGecko = web.isUserAgent("Gecko/");
+ var i, sEvent, asWebPrefixes = ['', 'moz', 'ms', 'webkit'];
+
+ Component.call(this, "Video", parmsVideo, Video, Messages.VIDEO);
+
+ this.cxScreen = parmsVideo['screenWidth'];
+ this.cyScreen = parmsVideo['screenHeight'];
+
+ this.addrBuffer = parmsVideo['bufferAddr'];
+ this.cxBuffer = parmsVideo['bufferCols'];
+ this.cyBuffer = parmsVideo['bufferRows'];
+ this.nBitsPerPixel = parmsVideo['bufferBits'] || 1;
+ this.iBitFirstPixel = parmsVideo['bufferLeft'] || 0;
+ this.rotateBuffer = parmsVideo['bufferRotate'];
+ if (this.rotateBuffer) {
+ this.rotateBuffer = this.rotateBuffer % 360;
+ if (this.rotateBuffer > 0) this.rotateBuffer -= 360;
+ if (this.rotateBuffer != -90) {
+ this.notice("unsupported buffer rotation: " + this.rotateBuffer);
+ this.rotateBuffer = 0;
+ }
+ }
+
+ this.interruptRate = parmsVideo['interruptRate'];
+ this.refreshRate = parmsVideo['refreshRate'] || 60;
+
+ this.canvasScreen = canvas;
+ this.contextScreen = context;
+ this.textareaScreen = textarea;
+ this.inputScreen = textarea || canvas || null;
+
+ /*
+ * Support for disabling (or, less commonly, enabling) image smoothing, which all browsers
+ * seem to support now (well, OK, I still have to test the latest MS Edge browser), despite
+ * it still being labelled "experimental technology". Let's hope the browsers standardize
+ * on this. I see other options emerging, like the CSS property "image-rendering: pixelated"
+ * that's apparently been added to Chrome. Sigh.
+ */
+ var fSmoothing = parmsVideo['smoothing'];
+ var sSmoothing = Component.parmsURL['smoothing'];
+ if (sSmoothing) fSmoothing = (sSmoothing == "true");
+ if (fSmoothing != null) {
+ for (i = 0; i < asWebPrefixes.length; i++) {
+ sEvent = asWebPrefixes[i];
+ if (!sEvent) {
+ sEvent = 'imageSmoothingEnabled';
+ } else {
+ sEvent += 'ImageSmoothingEnabled';
+ }
+ if (this.contextScreen[sEvent] !== undefined) {
+ this.contextScreen[sEvent] = fSmoothing;
+ break;
+ }
+ }
+ }
+
+ this.rotateScreen = parmsVideo['screenRotate'];
+ if (this.rotateScreen) {
+ this.rotateScreen = this.rotateScreen % 360;
+ if (this.rotateScreen > 0) this.rotateScreen -= 360;
+ if (this.rotateScreen != -90) {
+ this.notice("unsupported screen rotation: " + this.rotateScreen);
+ this.rotateScreen = 0;
+ } else {
+ this.contextScreen.translate(0, this.cyScreen);
+ this.contextScreen.rotate((this.rotateScreen * Math.PI)/180);
+ this.contextScreen.scale(this.cyScreen/this.cxScreen, this.cxScreen/this.cyScreen);
+ }
+ }
+
+ this.initColors();
+
+ /*
+ * Allocate off-screen buffers.
+ */
+ var cxBuffer = this.cxBuffer;
+ var cyBuffer = this.cyBuffer;
+ if (this.rotateBuffer) {
+ cxBuffer = this.cyBuffer;
+ cyBuffer = this.cxBuffer;
+ }
+ this.imageBuffer = this.contextScreen.createImageData(cxBuffer, cyBuffer);
+ this.canvasBuffer = document.createElement("canvas");
+ this.canvasBuffer.width = cxBuffer;
+ this.canvasBuffer.height = cyBuffer;
+ this.contextBuffer = this.canvasBuffer.getContext("2d");
+
+ /*
+ * Here's the gross code to handle full-screen support across all supported browsers. The lack of standards
+ * is exasperating; browsers can't agree on 'full' or 'Full, 'request' or 'Request', 'screen' or 'Screen', and
+ * while some browsers honor other browser prefixes, most browsers don't.
+ */
+ this.container = container;
+ if (this.container) {
+ this.container.doFullScreen = container['requestFullscreen'] || container['msRequestFullscreen'] || container['mozRequestFullScreen'] || container['webkitRequestFullscreen'];
+ if (this.container.doFullScreen) {
+ for (i = 0; i < asWebPrefixes.length; i++) {
+ sEvent = asWebPrefixes[i] + 'fullscreenchange';
+ if ('on' + sEvent in document) {
+ var onFullScreenChange = function() {
+ var fFullScreen = (document['fullscreenElement'] || document['msFullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement']);
+ video.notifyFullScreen(fFullScreen? true : false);
+ };
+ document.addEventListener(sEvent, onFullScreenChange, false);
+ break;
+ }
+ }
+ for (i = 0; i < asWebPrefixes.length; i++) {
+ sEvent = asWebPrefixes[i] + 'fullscreenerror';
+ if ('on' + sEvent in document) {
+ var onFullScreenError = function() {
+ video.notifyFullScreen(null);
+ };
+ document.addEventListener(sEvent, onFullScreenError, false);
+ break;
+ }
+ }
+ }
+ }
+}
+
+Component.subclass(Video);
+
+Video.COLORS = {
+ OVERLAY_TOP: 0,
+ OVERLAY_BOTTOM: 1,
+ OVERLAY_TOTAL: 2
+};
+
+/**
+ * initBus(cmp, bus, cpu, dbg)
+ *
+ * @this {Video}
+ * @param {Computer} cmp
+ * @param {Bus} bus
+ * @param {CPUState} cpu
+ * @param {Debugger} dbg
+ */
+Video.prototype.initBus = function(cmp, bus, cpu, dbg)
+{
+ this.cmp = cmp;
+ this.bus = bus;
+ this.cpu = cpu;
+ this.dbg = dbg;
+
+ /*
+ * Compute the size of the frame buffer and allocate.
+ */
+ this.sizeBuffer = ((this.cxBuffer * this.nBitsPerPixel) >> 3) * this.cyBuffer;
+ if (this.bus.addMemory(this.addrBuffer, this.sizeBuffer, Memory.TYPE.VIDEO)) {
+ /*
+ * Compute the number of cells and initialize the cell cache.
+ */
+ this.nCellCache = this.sizeBuffer >> 1;
+ this.nPixelsPerCell = (16 / this.nBitsPerPixel)|0;
+ this.initCache();
+ }
+ this.setReady();
+};
+
+/**
+ * setBinding(sHTMLType, sBinding, control, sValue)
+ *
+ * @this {Video}
+ * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
+ * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "refresh")
+ * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
+ * @param {string} [sValue] optional data value
+ * @return {boolean} true if binding was successful, false if unrecognized binding request
+ */
+Video.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
+{
+ var video = this;
+
+ switch (sBinding) {
+ case "fullScreen":
+ this.bindings[sBinding] = control;
+ if (this.container && this.container.doFullScreen) {
+ control.onclick = function onClickFullScreen() {
+ if (DEBUG) video.printMessage("fullScreen()");
+ video.doFullScreen();
+ };
+ } else {
+ if (DEBUG) this.log("FullScreen API not available");
+ control.parentNode.removeChild(/** @type {Node} */ (control));
+ }
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+};
+
+/**
+ * doFullScreen()
+ *
+ * @this {Video}
+ * @return {boolean} true if request successful, false if not (eg, failed OR not supported)
+ */
+Video.prototype.doFullScreen = function()
+{
+ var fSuccess = false;
+ if (this.container) {
+ if (this.container.doFullScreen) {
+ /*
+ * Styling the container with a width of "100%" and a height of "auto" works great when the aspect ratio
+ * of our virtual screen is at least roughly equivalent to the physical screen's aspect ratio, but now that
+ * we support virtual VGA screens with an aspect ratio of 1.33, that's very much out of step with modern
+ * wide-screen monitors, which usually have an aspect ratio of 1.6 or greater.
+ *
+ * And unfortunately, none of the browsers I've tested appear to make any attempt to scale our container to
+ * the physical screen's dimensions, so the bottom of our screen gets clipped. To prevent that, I reduce
+ * the width from 100% to whatever percentage will accommodate the entire height of the virtual screen.
+ *
+ * NOTE: Mozilla recommends both a width and a height of "100%", but all my tests suggest that using "auto"
+ * for height works equally well, so I'm sticking with it, because "auto" is also consistent with how I've
+ * implemented a responsive canvas when the browser window is being resized.
+ */
+ var sWidth = "100%";
+ var sHeight = "auto";
+ if (screen && screen.width && screen.height) {
+ var aspectPhys = screen.width / screen.height;
+ var aspectVirt = this.cxScreen / this.cyScreen;
+ if (aspectPhys > aspectVirt) {
+ sWidth = Math.round(aspectVirt / aspectPhys * 100) + '%';
+ }
+ // TODO: We may need to someday consider the case of a physical screen with an aspect ratio < 1.0....
+ }
+ if (!this.fGecko) {
+ this.container.style.width = sWidth;
+ this.container.style.height = sHeight;
+ } else {
+ /*
+ * Sadly, the above code doesn't work for Firefox, because as http://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode
+ * explains:
+ *
+ * 'It's worth noting a key difference here between the Gecko and WebKit implementations at this time:
+ * Gecko automatically adds CSS rules to the element to stretch it to fill the screen: "width: 100%; height: 100%".
+ *
+ * Which would be OK if Gecko did that BEFORE we're called, but apparently it does that AFTER, effectively
+ * overwriting our careful calculations. So we style the inner element (canvasScreen) instead, which
+ * requires even more work to ensure that the canvas is properly centered. FYI, this solution is consistent
+ * with Mozilla's recommendation for working around their automatic CSS rules:
+ *
+ * '[I]f you're trying to emulate WebKit's behavior on Gecko, you need to place the element you want
+ * to present inside another element, which you'll make fullscreen instead, and use CSS rules to adjust
+ * the inner element to match the appearance you want.'
+ */
+ this.canvasScreen.style.width = sWidth;
+ this.canvasScreen.style.width = sWidth;
+ this.canvasScreen.style.display = "block";
+ this.canvasScreen.style.margin = "auto";
+ }
+ this.container.style.backgroundColor = "black";
+ this.container.doFullScreen();
+ fSuccess = true;
+ }
+ this.setFocus();
+ }
+ return fSuccess;
+};
+
+/**
+ * notifyFullScreen(fFullScreen)
+ *
+ * @this {Video}
+ * @param {boolean|null} fFullScreen (null if there was a full-screen error)
+ */
+Video.prototype.notifyFullScreen = function(fFullScreen)
+{
+ if (!fFullScreen && this.container) {
+ if (!this.fGecko) {
+ this.container.style.width = this.container.style.height = "";
+ } else {
+ this.canvasScreen.style.width = this.canvasScreen.style.height = "";
+ }
+ }
+ this.printMessage("notifyFullScreen(" + fFullScreen + ")", true);
+};
+
+/**
+ * setFocus()
+ *
+ * @this {Video}
+ */
+Video.prototype.setFocus = function()
+{
+ if (this.inputScreen) this.inputScreen.focus();
+};
+
+/**
+ * getRefreshRate()
+ *
+ * @this {Video}
+ * @return {number}
+ */
+Video.prototype.getRefreshRate = function()
+{
+ return Math.max(this.refreshRate, this.interruptRate);
+};
+
+/**
+ * initCache()
+ *
+ * Initializes the contents of our internal cell cache.
+ *
+ * @this {Video}
+ */
+Video.prototype.initCache = function()
+{
+ this.fCellCacheValid = false;
+ if (this.aCellCache === undefined || this.aCellCache.length != this.nCellCache) {
+ this.aCellCache = new Array(this.nCellCache);
+ }
+};
+
+/**
+ * initColors()
+ *
+ * This creates an array of nColors, with additional OVERLAY_TOTAL colors tacked on to the end of the array.
+ *
+ * @this {Video}
+ */
+Video.prototype.initColors = function()
+{
+ var rgbBlack = [0x00, 0x00, 0x00, 0xff];
+ var rgbWhite = [0xff, 0xff, 0xff, 0xff];
+ var rgbGreen = [0x00, 0xff, 0x00, 0xff];
+ var rgbYellow = [0xff, 0xff, 0x00, 0xff];
+ this.nColors = (1 << this.nBitsPerPixel);
+ this.aRGB = new Array(this.nColors + Video.COLORS.OVERLAY_TOTAL);
+ this.aRGB[0] = rgbBlack;
+ this.aRGB[1] = rgbWhite;
+ this.aRGB[this.nColors + Video.COLORS.OVERLAY_TOP] = rgbYellow;
+ this.aRGB[this.nColors + Video.COLORS.OVERLAY_BOTTOM] = rgbGreen;
+};
+
+/**
+ * setPixel(imageBuffer, x, y, bPixel)
+ *
+ * @this {Video}
+ * @param {Object} imageBuffer
+ * @param {number} x
+ * @param {number} y
+ * @param {number} bPixel (ie, an index into aRGB)
+ */
+Video.prototype.setPixel = function(imageBuffer, x, y, bPixel)
+{
+ var index;
+ if (!this.rotateBuffer) {
+ index = (x + y * imageBuffer.width);
+ } else {
+ index = (imageBuffer.height - x - 1) * imageBuffer.width + y;
+ }
+ if (bPixel) {
+ if (x >= 208 && x < 236) {
+ bPixel = this.nColors + Video.COLORS.OVERLAY_TOP;
+ }
+ else if (x >= 28 && x < 72) {
+ bPixel = this.nColors + Video.COLORS.OVERLAY_BOTTOM;
+ }
+ }
+ var rgb = this.aRGB[bPixel];
+ index *= rgb.length;
+ imageBuffer.data[index] = rgb[0];
+ imageBuffer.data[index+1] = rgb[1];
+ imageBuffer.data[index+2] = rgb[2];
+ imageBuffer.data[index+3] = rgb[3];
+};
+
+/**
+ * updateScreen(n)
+ *
+ * Propagates the video buffer to the cell cache and updates the screen with any changes. Forced updates
+ * are generally internal updates triggered by an I/O operation or other state change, while non-forced updates
+ * are the periodic updates coming from the CPU.
+ *
+ * For every cell in the video buffer, compare it to the cell stored in the cell cache, render if it differs,
+ * and then update the cell cache to match. Since initCache() sets every cell in the cell cache to an
+ * invalid value, we're assured that the next call to updateScreen() will redraw the entire (visible) video buffer.
+ *
+ * @this {Video}
+ * @param {number} n (where 0 <= n < getRefreshRate() for a normal update, or -1 for a forced update)
+ */
+Video.prototype.updateScreen = function(n)
+{
+ if (n >= 0) {
+ if (!(n & 1)) {
+ /*
+ * On even updates, call cpu.requestINTR(1), and also update our copy of the screen.
+ */
+ this.cpu.requestINTR(1);
+ } else {
+ /*
+ * On odd updates, call cpu.requestINTR(2), but do NOT update our copy of the screen, because
+ * the machine has presumably only updated the top half of the frame buffer at this point; it will
+ * update the bottom half of the frame buffer after acknowledging this interrupt.
+ */
+ this.cpu.requestINTR(2);
+ return;
+ }
+
+ /*
+ * Since this is not a forced update, if our cell cache is valid AND the buffer is clean, then do nothing.
+ */
+ if (this.fCellCacheValid && this.bus.cleanMemory(this.addrBuffer, this.sizeBuffer)) {
+ return;
+ }
+ }
+
+ var addr = this.addrBuffer;
+ var addrLimit = addr + this.sizeBuffer;
+
+ var iCell = 0;
+ var nPixelShift = 1;
+
+ var xBuffer = 0, yBuffer = 0;
+ var xDirty = this.cxBuffer, xMaxDirty = 0, yDirty = this.cyBuffer, yMaxDirty = 0;
+
+ var nShiftInit = 0;
+ var nShiftPixel = this.nBitsPerPixel;
+ var nMask = (1 << nShiftPixel) - 1;
+ if (this.iBitFirstPixel) {
+ nShiftPixel = -nShiftPixel;
+ nShiftInit = 16 + nShiftPixel;
+ }
+
+ while (addr < addrLimit) {
+ var data = this.bus.getShortDirect(addr);
+ this.assert(iCell < this.aCellCache.length);
+ if (this.fCellCacheValid && data === this.aCellCache[iCell]) {
+ xBuffer += this.nPixelsPerCell;
+ } else {
+ this.aCellCache[iCell] = data;
+ var nShift = nShiftInit;
+ if (nShift) data = ((data >> 8) | ((data & 0xff) << 8));
+ if (xBuffer < xDirty) xDirty = xBuffer;
+ var cPixels = this.nPixelsPerCell;
+ while (cPixels--) {
+ var bPixel = (data >> nShift) & nMask;
+ this.setPixel(this.imageBuffer, xBuffer++, yBuffer, bPixel);
+ nShift += nShiftPixel;
+ }
+ if (xBuffer > xMaxDirty) xMaxDirty = xBuffer;
+ if (yBuffer < yDirty) yDirty = yBuffer;
+ if (yBuffer >= yMaxDirty) yMaxDirty = yBuffer + 1;
+ }
+ addr += 2; iCell++;
+ if (xBuffer >= this.cxBuffer) {
+ xBuffer = 0; yBuffer++;
+ if (yBuffer > this.cyBuffer) break;
+ }
+ }
+
+ this.fCellCacheValid = true;
+
+ /*
+ * Instead of blasting the ENTIRE imageBuffer into contextBuffer, and then blasting the ENTIRE
+ * canvasBuffer onto contextScreen, even for the smallest change, let's try to be a bit smarter about
+ * the update (well, to the extent that the canvas APIs permit).
+ */
+ if (xDirty < this.cxBuffer) {
+ var cxDirty = xMaxDirty - xDirty;
+ var cyDirty = yMaxDirty - yDirty;
+ if (this.rotateBuffer) {
+ /*
+ * If rotateBuffer is set, then it must be -90, so we must "rotate" the dirty coordinates as well,
+ * because they are relative to the frame buffer, not the rotated image buffer. Alternatively, you
+ * can use the following call to blast the ENTIRE imageBuffer into contextBuffer instead:
+ *
+ * this.contextBuffer.putImageData(this.imageBuffer, 0, 0);
+ */
+ var xDirtyOrig = xDirty, cxDirtyOrig = cxDirty;
+ //noinspection JSSuspiciousNameCombination
+ xDirty = yDirty;
+ cxDirty = cyDirty;
+ yDirty = this.cxBuffer - (xDirtyOrig + cxDirtyOrig);
+ cyDirty = cxDirtyOrig;
+ }
+ this.contextBuffer.putImageData(this.imageBuffer, 0, 0, xDirty, yDirty, cxDirty, cyDirty);
+ this.contextScreen.drawImage(this.canvasBuffer, 0, 0, this.canvasBuffer.width, this.canvasBuffer.height, 0, 0, this.cxScreen, this.cyScreen);
+ }
+};
+
+/**
+ * Video.init()
+ *
+ * This function operates on every HTML element of class "video", extracting the
+ * JSON-encoded parameters for the Video constructor from the element's "data-value"
+ * attribute, invoking the constructor to create a Video component, and then binding
+ * any associated HTML controls to the new component.
+ */
+Video.init = function()
+{
+ var aeVideo = Component.getElementsByClass(document, PCJSCLASS, "video");
+ for (var iVideo = 0; iVideo < aeVideo.length; iVideo++) {
+ var eVideo = aeVideo[iVideo];
+ var parmsVideo = Component.getComponentParms(eVideo);
+
+ var eCanvas = document.createElement("canvas");
+ if (eCanvas === undefined || !eCanvas.getContext) {
+ eVideo.innerHTML = "
Missing <canvas> support. Please try a newer web browser.";
+ return;
+ }
+
+ eCanvas.setAttribute("class", "pcjs-canvas");
+ eCanvas.setAttribute("width", parmsVideo['screenWidth']);
+ eCanvas.setAttribute("height", parmsVideo['screenHeight']);
+ eCanvas.style.backgroundColor = parmsVideo['screenColor'];
+
+ /*
+ * The "contenteditable" attribute on a canvas element NOTICEABLY slows down canvas drawing on
+ * Safari as soon as you give the canvas focus (ie, click away from the canvas, and drawing speeds
+ * up; click on the canvas, and drawing slows down). So the "transparent textarea hack" that we
+ * once employed as only a work-around for Android devices is now our default.
+ *
+ * eCanvas.setAttribute("contenteditable", "true");
+ *
+ * HACK: A canvas style of "auto" provides for excellent responsive canvas scaling in EVERY browser
+ * except IE9/IE10, so I recalculate the appropriate CSS height every time the parent DIV is resized;
+ * IE11 works without this hack, so we take advantage of the fact that IE11 doesn't identify as "MSIE".
+ *
+ * The other reason it's good to keep this particular hack limited to IE9/IE10 is that most other
+ * browsers don't actually support an 'onresize' handler on anything but the window object.
+ */
+ eCanvas.style.height = "auto";
+ if (web.getUserAgent().indexOf("MSIE") >= 0) {
+ eVideo.onresize = function(eParent, eChild, cx, cy) {
+ return function onResizeVideo() {
+ eChild.style.height = (((eParent.clientWidth * cy) / cx) | 0) + "px";
+ };
+ }(eVideo, eCanvas, parmsVideo['screenWidth'], parmsVideo['screenHeight']);
+ eVideo.onresize();
+ }
+ /*
+ * The following is a related hack that allows the user to force the screen to use a particular aspect
+ * ratio if an 'aspect' attribute or URL parameter is set. Initially, it's just for testing purposes
+ * until we figure out a better UI. And note that we use our web.onPageEvent() helper function to make
+ * sure we don't trample any other 'onresize' handler(s) attached to the window object.
+ */
+ var aspect = +(parmsVideo['aspect'] || Component.parmsURL['aspect']);
+ /*
+ * No 'aspect' parameter yields NaN, which is falsey, and anything else must satisfy my arbitrary
+ * constraints of 0.3 <= aspect <= 3.33, to prevent any useless (or worse, browser-blowing) results.
+ */
+ if (aspect && aspect >= 0.3 && aspect <= 3.33) {
+ web.onPageEvent('onresize', function(eParent, eChild, aspectRatio) {
+ return function onResizeWindow() {
+ /*
+ * Since aspectRatio is the target width/height, we have:
+ *
+ * eParent.clientWidth / eChild.style.height = aspectRatio
+ *
+ * which means that:
+ *
+ * eChild.style.height = eParent.clientWidth / aspectRatio
+ *
+ * so for example, if aspectRatio is 16:9, or 1.78, and clientWidth = 640,
+ * then the calculated height should approximately 360.
+ */
+ eChild.style.height = ((eParent.clientWidth / aspectRatio)|0) + "px";
+ };
+ }(eVideo, eCanvas, aspect));
+ window['onresize']();
+ }
+ eVideo.appendChild(eCanvas);
+
+ /*
+ * HACK: Android-based browsers, like the Silk (Amazon) browser and Chrome for Android, don't honor the
+ * "contenteditable" attribute; that is, when the canvas receives focus, they don't activate the on-screen
+ * keyboard. So my fallback is to create a transparent textarea on top of the canvas.
+ *
+ * The parent DIV must have a style of "position:relative" (alternatively, a class of "pcjs-container"),
+ * so that we can position the textarea using absolute coordinates. Also, we don't want the textarea to be
+ * visible, but we must use "opacity:0" instead of "visibility:hidden", because the latter seems to prevent
+ * the element from receiving events. These styling requirements are taken care of in components.css
+ * (see references to the "pcjs-video-object" class).
+ *
+ * UPDATE: Unfortunately, Android keyboards like to compose whole words before transmitting any of the
+ * intervening characters; our textarea's keyDown/keyUp event handlers DO receive intervening key events,
+ * but their keyCode property is ZERO. Virtually the only usable key event we receive is the Enter key.
+ * Android users will have to use machines that include their own on-screen "soft keyboard", or use an
+ * external keyboard.
+ *
+ * The following attempt to use a password-enabled input field didn't work any better on Android. You could
+ * clearly see the overlaid semi-transparent input field, but none of the input characters were passed along,
+ * with the exception of the "Go" (Enter) key.
+ *
+ * var eInput = document.createElement("input");
+ * eInput.setAttribute("type", "password");
+ * eInput.setAttribute("style", "position:absolute; left:0; top:0; width:100%; height:100%; opacity:0.5");
+ * eVideo.appendChild(eInput);
+ *
+ * See this Chromium issue for more information: https://code.google.com/p/chromium/issues/detail?id=118639
+ */
+ var eTextArea = document.createElement("textarea");
+
+ /*
+ * As noted in keyboard.js, the keyboard on an iOS device tends to pop up with the SHIFT key depressed,
+ * which is not the initial keyboard state that the Keyboard component expects, so hopefully turning off
+ * these "auto" attributes will help.
+ */
+ if (web.isUserAgent("iOS")) {
+ eTextArea.setAttribute("autocapitalize", "off");
+ eTextArea.setAttribute("autocorrect", "off");
+ }
+ eVideo.appendChild(eTextArea);
+
+ /*
+ * Now we can create the Video object, record it, and wire it up to the associated document elements.
+ */
+ var eContext = eCanvas.getContext("2d");
+ var video = new Video(parmsVideo, eCanvas, eContext, eTextArea /* || eInput */, eVideo);
+
+ /*
+ * Bind any video-specific controls (eg, the Refresh button). There are no essential controls, however;
+ * even the "Refresh" button is just a diagnostic tool, to ensure that the screen contents are up-to-date.
+ */
+ Component.bindComponentControls(video, eVideo, PCJSCLASS);
+ }
+};
+
+/*
+ * Initialize every Video module on the page.
+ */
+web.onInit(Video.init);
+
+if (NODE) module.exports = Video;