Converted the rest of the PCjs machines to ES6
This commit is contained in:
parent
2b1e171ecb
commit
323a42be37
301 changed files with 60754 additions and 52273 deletions
|
|
@ -26,345 +26,331 @@
|
|||
* as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* BUILD INSTRUCTIONS
|
||||
*
|
||||
* To build C1Pjs (c1p.js), run Google's Closure Compiler, replacing "*.js" with
|
||||
* the input file sequence defined by the "c1pJSFiles" property in package.json:
|
||||
*
|
||||
* java -jar compiler.jar
|
||||
* --compilation_level ADVANCED_OPTIMIZATIONS
|
||||
* --define='DEBUG=false'
|
||||
* --warning_level=VERBOSE
|
||||
* --js *.js
|
||||
* --js_output_file c1p.js
|
||||
*
|
||||
* Google's Closure Compiler (compiler.jar) is documented at
|
||||
* https://developers.google.com/closure/compiler/ and is available
|
||||
* for download here:
|
||||
*
|
||||
* http://closure-compiler.googlecode.com/files/compiler-latest.zip
|
||||
*
|
||||
* The C1Pjs JavaScript files do have some initialization-order dependencies.
|
||||
* If you load the files individually, it's recommended that you load them in
|
||||
* the same order that they're compiled (see above).
|
||||
*
|
||||
* Generally speaking, component.js should be first, computer.js should be
|
||||
* last (of the files based on component.js), and panel.js should be listed
|
||||
* early so that the Control Panel is ready as soon as possible.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PComputer(parmsComputer, modules)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The C1PComputer component expects the following (parmsComputer) properties:
|
||||
*
|
||||
* modules[{}] (from the <module> definition(s) for the computer)
|
||||
*
|
||||
* This component processes all the <module> "start" and "end" specifications
|
||||
* and "wires" everything to a common "address buffer"; namely, the abMemory array.
|
||||
* abMemory encompasses the computer's entire address space, but every component must
|
||||
* play nice and use only its assigned section of abMemory -- and pretend it's an array
|
||||
* of bytes, when in fact it's an array of floating-point values (the only primitive
|
||||
* numeric data type that JavaScript provides).
|
||||
*
|
||||
* This component also insures that all the other components are ready; in particular,
|
||||
* this means that the ROM and Video components have finished loading their resources
|
||||
* and are ready for operation. Other components become ready as soon as we call their
|
||||
* setBuffer() method (eg, CPU, RAM, Keyboard, Debugger, SerialPort, DiskController), and
|
||||
* others, like Panel, become ready even earlier, at the end of their initialization.
|
||||
*
|
||||
* Once every component has indicated it's ready, we call its setPower() notification
|
||||
* function (if it has one; it's optional). We call the CPU's setPower() function last,
|
||||
* so that the CPU is assured that all other components are ready and "powered".
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PComputer(parmsComputer, modules)
|
||||
{
|
||||
Component.call(this, "C1PComputer", parmsComputer);
|
||||
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
Component.subclass(C1PComputer);
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {boolean} [fPowerOn] is true to indicate that we should start the CPU running
|
||||
*/
|
||||
C1PComputer.prototype.reset = function(fPowerOn)
|
||||
{
|
||||
var cpu = null;
|
||||
for (var sType in this.modules) {
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.reset) {
|
||||
if (DEBUG) this.println("resetting " + sType);
|
||||
component.reset();
|
||||
if (sType == "cpu") cpu = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cpu) {
|
||||
cpu.update();
|
||||
if (fPowerOn) cpu.run();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
*
|
||||
* Called by the CPU to notify all component start() handlers
|
||||
*/
|
||||
C1PComputer.prototype.start = function()
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.start) {
|
||||
component.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {number} msStart
|
||||
* @param {number} nCycles
|
||||
*
|
||||
* Called by the CPU to notify all component stop() handlers
|
||||
*/
|
||||
C1PComputer.prototype.stop = function(msStart, nCycles)
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.stop) {
|
||||
component.stop(msStart, nCycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @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
|
||||
*/
|
||||
C1PComputer.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
switch(sBinding) {
|
||||
case "reset":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(computer) {
|
||||
return function() {
|
||||
computer.reset();
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* NOTE: If there are multiple components for a given type, we may need to provide a means of discriminating.
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {string} sType
|
||||
* @param {string} [idRelated] of related component
|
||||
* @param {Component|null} [componentPrev] of previously returned component, if any
|
||||
* @return {Component|null}
|
||||
*/
|
||||
C1PComputer.prototype.getComponentByType = function(sType, idRelated, componentPrev)
|
||||
{
|
||||
if (this.modules[sType]) {
|
||||
return this.modules[sType][0];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
C1PComputer.power = function(computer)
|
||||
{
|
||||
/*
|
||||
* Insure that the ROMs, Video and CPU are all ready before "powering" everything; always "power"
|
||||
* the CPU last, to make sure it doesn't start asking other components to do things before they're ready.
|
||||
class C1PComputer extends Component {
|
||||
/**
|
||||
* C1PComputer(parmsComputer, modules)
|
||||
*
|
||||
* The C1PComputer component expects the following (parmsComputer) properties:
|
||||
*
|
||||
* modules[{}] (from the <module> definition(s) for the computer)
|
||||
*
|
||||
* This component processes all the <module> "start" and "end" specifications
|
||||
* and "wires" everything to a common "address buffer"; namely, the abMemory array.
|
||||
* abMemory encompasses the computer's entire address space, but every component must
|
||||
* play nice and use only its assigned section of abMemory -- and pretend it's an array
|
||||
* of bytes, when in fact it's an array of floating-point values (the only primitive
|
||||
* numeric data type that JavaScript provides).
|
||||
*
|
||||
* This component also insures that all the other components are ready; in particular,
|
||||
* this means that the ROM and Video components have finished loading their resources
|
||||
* and are ready for operation. Other components become ready as soon as we call their
|
||||
* setBuffer() method (eg, CPU, RAM, Keyboard, Debugger, SerialPort, DiskController), and
|
||||
* others, like Panel, become ready even earlier, at the end of their initialization.
|
||||
*
|
||||
* Once every component has indicated it's ready, we call its setPower() notification
|
||||
* function (if it has one; it's optional). We call the CPU's setPower() function last,
|
||||
* so that the CPU is assured that all other components are ready and "powered".
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {Object} parmsComputer
|
||||
* @param {Object} modules
|
||||
*/
|
||||
var cpu = null;
|
||||
for (var sType in computer.modules) {
|
||||
for (var i=0; i < computer.modules[sType].length; i++) {
|
||||
var component = computer.modules[sType][i];
|
||||
if (!component) continue;
|
||||
if (!component.isReady()) {
|
||||
component.isReady(function(computer) {
|
||||
return function() {
|
||||
C1PComputer.power(computer);
|
||||
};
|
||||
}(computer)); // jshint ignore:line
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* The CPU component's setPower() notification handler is a special case: we don't want
|
||||
* to call it until the end (below), after all others have been called.
|
||||
*/
|
||||
if (sType == "cpu")
|
||||
cpu = component;
|
||||
else if (component.setPower) {
|
||||
component.setPower(true, computer);
|
||||
}
|
||||
}
|
||||
constructor(parmsComputer, modules)
|
||||
{
|
||||
super("C1PComputer", parmsComputer);
|
||||
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
/*
|
||||
* The entire computer is finally ready; we call our own setReady() for completeness, not because any
|
||||
* other component actually cares when we're ready.
|
||||
/**
|
||||
* reset(fPowerOn)
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {boolean} [fPowerOn] is true to indicate that we should start the CPU running
|
||||
*/
|
||||
computer.setReady();
|
||||
|
||||
computer.println(C1PJS.APPNAME + " v" + C1PJS.APPVERSION + "\n" + COPYRIGHT);
|
||||
|
||||
/*
|
||||
* Once we get to this point, we're guaranteed that all components are ready, so it's safe to "power" the CPU;
|
||||
* setPower() includes an automatic reset(fPowerOn), so the CPU should begin executing immediately, unless a debugger
|
||||
* is attached.
|
||||
*/
|
||||
if (cpu) cpu.setPower(true, computer);
|
||||
};
|
||||
|
||||
/*
|
||||
* C1PComputer.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "c1pjs-computer", extracting the
|
||||
* JSON-encoded parameters for the C1PComputer constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PComputer component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PComputer.init = function()
|
||||
{
|
||||
/*
|
||||
* In non-COMPILED builds, embedMachine() may have set XMLVERSION.
|
||||
*/
|
||||
if (!COMPILED && XMLVERSION) C1PJS.APPVERSION = XMLVERSION;
|
||||
|
||||
var aeComputers = Component.getElementsByClass(document, C1PJS.APPCLASS, "computer");
|
||||
|
||||
for (var iComputer=0; iComputer < aeComputers.length; iComputer++) {
|
||||
|
||||
var eComputer = aeComputers[iComputer];
|
||||
var parmsComputer = Component.getComponentParms(eComputer);
|
||||
|
||||
var component;
|
||||
var modules = {};
|
||||
|
||||
var abMemory;
|
||||
var addrStart = 0, addrEnd = 0;
|
||||
|
||||
for (var iAddr=0; iAddr < parmsComputer['modules'].length; iAddr++) {
|
||||
var addrInfo = parmsComputer['modules'][iAddr];
|
||||
/*
|
||||
* The first address range (ie, the CPU range) must specify the range for the entire
|
||||
* address space (abMemory), which we allocate and zero-initialize.
|
||||
*
|
||||
* NOTE: We might consider doing what the Video component does on first reset: initializing
|
||||
* the entire memory buffer to random values. However, a constant (eg, 0xA5) might be
|
||||
* more useful, acting as a crude indicator of memory the client code hasn't written yet.
|
||||
*/
|
||||
if (!iAddr) {
|
||||
if (addrInfo['type'] != "cpu") break;
|
||||
addrStart = addrInfo['start'];
|
||||
addrEnd = addrInfo['end'];
|
||||
abMemory = new Array(addrEnd+1 - addrStart);
|
||||
for (var addr=addrStart; addr < abMemory.length; addr++) {
|
||||
abMemory[addr] = 0;
|
||||
reset(fPowerOn)
|
||||
{
|
||||
var cpu = null;
|
||||
for (var sType in this.modules) {
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.reset) {
|
||||
if (DEBUG) this.println("resetting " + sType);
|
||||
component.reset();
|
||||
if (sType == "cpu") cpu = component;
|
||||
}
|
||||
}
|
||||
component = Component.getComponentByID(addrInfo['refID'], parmsComputer['id']);
|
||||
}
|
||||
if (cpu) {
|
||||
cpu.update();
|
||||
if (fPowerOn) cpu.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* start()
|
||||
*
|
||||
* Called by the CPU to notify all component start() handlers.
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
*/
|
||||
start()
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.start) {
|
||||
component.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop(msStart, nCycles)
|
||||
*
|
||||
* Called by the CPU to notify all component stop() handlers
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {number} msStart
|
||||
* @param {number} nCycles
|
||||
*/
|
||||
stop(msStart, nCycles)
|
||||
{
|
||||
for (var sType in this.modules) {
|
||||
if (sType == "cpu") continue;
|
||||
for (var i=0; i < this.modules[sType].length; i++) {
|
||||
var component = this.modules[sType][i];
|
||||
if (component && component.stop) {
|
||||
component.stop(msStart, nCycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @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
|
||||
*/
|
||||
setBinding(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
switch(sBinding) {
|
||||
case "reset":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(computer) {
|
||||
return function() {
|
||||
computer.reset();
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: If there are multiple components for a given type, we may need to provide a means of discriminating.
|
||||
*
|
||||
* @this {C1PComputer}
|
||||
* @param {string} sType
|
||||
* @param {string} [idRelated] of related component
|
||||
* @param {Component|null} [componentPrev] of previously returned component, if any
|
||||
* @return {Component|null}
|
||||
*/
|
||||
getComponentByType(sType, idRelated, componentPrev)
|
||||
{
|
||||
if (this.modules[sType]) {
|
||||
return this.modules[sType][0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static power(computer)
|
||||
{
|
||||
/*
|
||||
* Insure that the ROMs, Video and CPU are all ready before "powering" everything; always "power"
|
||||
* the CPU last, to make sure it doesn't start asking other components to do things before they're ready.
|
||||
*/
|
||||
var cpu = null;
|
||||
for (var sType in computer.modules) {
|
||||
for (var i=0; i < computer.modules[sType].length; i++) {
|
||||
var component = computer.modules[sType][i];
|
||||
if (!component) continue;
|
||||
if (!component.isReady()) {
|
||||
component.isReady(function(computer) {
|
||||
return function() {
|
||||
C1PComputer.power(computer);
|
||||
};
|
||||
}(computer)); // jshint ignore:line
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* The CPU component's setPower() notification handler is a special case: we don't want
|
||||
* to call it until the end (below), after all others have been called.
|
||||
*/
|
||||
if (sType == "cpu")
|
||||
cpu = component;
|
||||
else if (component.setPower) {
|
||||
component.setPower(true, computer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The entire computer is finally ready; we call our own setReady() for completeness, not because any
|
||||
* other component actually cares when we're ready.
|
||||
*/
|
||||
computer.setReady();
|
||||
|
||||
computer.println(C1PJS.APPNAME + " v" + C1PJS.APPVERSION + "\n" + COPYRIGHT);
|
||||
|
||||
/*
|
||||
* Once we get to this point, we're guaranteed that all components are ready, so it's safe to "power" the CPU;
|
||||
* setPower() includes an automatic reset(fPowerOn), so the CPU should begin executing immediately, unless a debugger
|
||||
* is attached.
|
||||
*/
|
||||
if (cpu) cpu.setPower(true, computer);
|
||||
}
|
||||
|
||||
/*
|
||||
* C1PComputer.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "c1pjs-computer", extracting the
|
||||
* JSON-encoded parameters for the C1PComputer constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PComputer component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
/*
|
||||
* In non-COMPILED builds, embedMachine() may have set XMLVERSION.
|
||||
*/
|
||||
if (!COMPILED && XMLVERSION) C1PJS.APPVERSION = XMLVERSION;
|
||||
|
||||
var aeComputers = Component.getElementsByClass(document, C1PJS.APPCLASS, "computer");
|
||||
|
||||
for (var iComputer=0; iComputer < aeComputers.length; iComputer++) {
|
||||
|
||||
var eComputer = aeComputers[iComputer];
|
||||
var parmsComputer = Component.getComponentParms(eComputer);
|
||||
|
||||
var component;
|
||||
var modules = {};
|
||||
|
||||
var abMemory;
|
||||
var addrStart = 0, addrEnd = 0;
|
||||
|
||||
for (var iAddr=0; iAddr < parmsComputer['modules'].length; iAddr++) {
|
||||
var addrInfo = parmsComputer['modules'][iAddr];
|
||||
/*
|
||||
* The first address range (ie, the CPU range) must specify the range for the entire
|
||||
* address space (abMemory), which we allocate and zero-initialize.
|
||||
*
|
||||
* NOTE: We might consider doing what the Video component does on first reset: initializing
|
||||
* the entire memory buffer to random values. However, a constant (eg, 0xA5) might be
|
||||
* more useful, acting as a crude indicator of memory the client code hasn't written yet.
|
||||
*/
|
||||
if (!iAddr) {
|
||||
if (addrInfo['type'] != "cpu") break;
|
||||
addrStart = addrInfo['start'];
|
||||
addrEnd = addrInfo['end'];
|
||||
abMemory = new Array(addrEnd+1 - addrStart);
|
||||
for (var addr=addrStart; addr < abMemory.length; addr++) {
|
||||
abMemory[addr] = 0;
|
||||
}
|
||||
}
|
||||
component = Component.getComponentByID(addrInfo['refID'], parmsComputer['id']);
|
||||
if (component) {
|
||||
var sType = addrInfo['type'];
|
||||
if (modules[sType] === undefined)
|
||||
modules[sType] = [];
|
||||
modules[sType].push(component);
|
||||
if (component.setBuffer && addrInfo['start'] !== undefined) {
|
||||
component.setBuffer(abMemory, addrInfo['start'], addrInfo['end'], modules['cpu'][0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Component.error("no component for <module refid=\"" + addrInfo['refID'] + "\">");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (abMemory === undefined) {
|
||||
Component.error("<module type=\"cpu\"> definition must appear first in the <computer> specification");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Let's see if the Debugger is installed (NOTE: its ID must be "debugger", and only one per machine is supported);
|
||||
* the Debugger needs our setBuffer(), setPower() and reset() notifications, and this relieves us from having an explicit
|
||||
* <module> entry for type="debugger".
|
||||
*/
|
||||
component = Component.getComponentByID('debugger', parmsComputer['id']);
|
||||
if (component) {
|
||||
var sType = addrInfo['type'];
|
||||
if (modules[sType] === undefined)
|
||||
modules[sType] = [];
|
||||
modules[sType].push(component);
|
||||
if (component.setBuffer && addrInfo['start'] !== undefined) {
|
||||
component.setBuffer(abMemory, addrInfo['start'], addrInfo['end'], modules['cpu'][0]);
|
||||
modules['debugger'] = [component];
|
||||
if (component.setBuffer) {
|
||||
component.setBuffer(abMemory, addrStart, addrEnd, modules['cpu'][0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Component.error("no component for <module refid=\"" + addrInfo['refID'] + "\">");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (abMemory === undefined) {
|
||||
Component.error("<module type=\"cpu\"> definition must appear first in the <computer> specification");
|
||||
return;
|
||||
}
|
||||
var computer = new C1PComputer(parmsComputer, modules);
|
||||
|
||||
/*
|
||||
* Let's see if the Debugger is installed (NOTE: its ID must be "debugger", and only one per machine is supported);
|
||||
* the Debugger needs our setBuffer(), setPower() and reset() notifications, and this relieves us from having an explicit
|
||||
* <module> entry for type="debugger".
|
||||
*/
|
||||
component = Component.getComponentByID('debugger', parmsComputer['id']);
|
||||
if (component) {
|
||||
modules['debugger'] = [component];
|
||||
if (component.setBuffer) {
|
||||
component.setBuffer(abMemory, addrStart, addrEnd, modules['cpu'][0]);
|
||||
}
|
||||
}
|
||||
|
||||
var computer = new C1PComputer(parmsComputer, modules);
|
||||
|
||||
/*
|
||||
* Let's see if the Control Panel is installed (NOTE: its ID must be "panel", and only one per machine is supported);
|
||||
* the Panel needs our setPower() notifications, and this relieves us from having an explicit <module> entry for type="panel".
|
||||
*/
|
||||
var panel = Component.getComponentByID('panel', parmsComputer['id']);
|
||||
if (panel) {
|
||||
modules['panel'] = [panel];
|
||||
/*
|
||||
* Iterate through all the other components and update their print methods if the Control Panel has provided overrides.
|
||||
* Let's see if the Control Panel is installed (NOTE: its ID must be "panel", and only one per machine is supported);
|
||||
* the Panel needs our setPower() notifications, and this relieves us from having an explicit <module> entry for type="panel".
|
||||
*/
|
||||
if (panel.controlPrint) {
|
||||
var aComponents = Component.getComponents(parmsComputer['id']);
|
||||
for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
|
||||
component = aComponents[iComponent];
|
||||
if (component == panel) continue;
|
||||
component.notice = panel.notice;
|
||||
component.println = panel.println;
|
||||
component.controlPrint = panel.controlPrint;
|
||||
var panel = Component.getComponentByID('panel', parmsComputer['id']);
|
||||
if (panel) {
|
||||
modules['panel'] = [panel];
|
||||
/*
|
||||
* Iterate through all the other components and update their print methods if the Control Panel has provided overrides.
|
||||
*/
|
||||
if (panel.controlPrint) {
|
||||
var aComponents = Component.getComponents(parmsComputer['id']);
|
||||
for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
|
||||
component = aComponents[iComponent];
|
||||
if (component == panel) continue;
|
||||
component.notice = panel.notice;
|
||||
component.println = panel.println;
|
||||
component.controlPrint = panel.controlPrint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We may eventually add a "Power" button, but for now, all we have is a "Reset" button
|
||||
*/
|
||||
Component.bindComponentControls(computer, eComputer, C1PJS.APPCLASS);
|
||||
|
||||
/*
|
||||
* "Power" the computer automatically
|
||||
*/
|
||||
C1PComputer.power(computer);
|
||||
}
|
||||
|
||||
/*
|
||||
* We may eventually add a "Power" button, but for now, all we have is a "Reset" button
|
||||
*/
|
||||
Component.bindComponentControls(computer, eComputer, C1PJS.APPCLASS);
|
||||
|
||||
/*
|
||||
* "Power" the computer automatically
|
||||
*/
|
||||
C1PComputer.power(computer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize every Computer on the page.
|
||||
*/
|
||||
web.onInit(C1PComputer.init);
|
||||
Web.onInit(C1PComputer.init);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -29,100 +29,107 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PPanel(parmsPanel)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The Panel component has no required (parmsPanel) properties.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PPanel(parmsPanel)
|
||||
{
|
||||
Component.call(this, "C1PPanel", parmsPanel);
|
||||
class C1PPanel extends Component {
|
||||
/**
|
||||
* C1PPanel(parmsPanel)
|
||||
*
|
||||
* The Panel component has no required (parmsPanel) properties.
|
||||
*
|
||||
* @this {C1PPanel}
|
||||
* @param {Object} parmsPanel
|
||||
*/
|
||||
constructor(parmsPanel)
|
||||
{
|
||||
super("C1PPanel", parmsPanel);
|
||||
|
||||
this.flags.powered = false;
|
||||
}
|
||||
|
||||
Component.subclass(C1PPanel);
|
||||
|
||||
/**
|
||||
* The Panel doesn't have any bindings of its own; it passes along all binding requests to
|
||||
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any
|
||||
* component that doesn't recognize the specified binding should simply ignore it.
|
||||
*
|
||||
* @this {C1PPanel}
|
||||
* @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
|
||||
*/
|
||||
C1PPanel.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 Component.prototype.setBinding.call(this, sHTMLType, sBinding, control, sValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PPanel}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
C1PPanel.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.cpu = cmp.getComponentByType("cpu");
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
C1PPanel.init();
|
||||
this.flags.powered = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PPanel.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "panel", extracting the
|
||||
* JSON-encoded parameters for the C1PPanel constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PPanel 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 binding our print()
|
||||
* function to the panel's output control ASAP, and again when the C1PComputer component
|
||||
* is verifying that all components are ready and invoking their setPower() functions.
|
||||
*
|
||||
* Our setPower() method gives us a second opportunity to notify any components that
|
||||
* that might care (eg, C1PCPU, C1PKeyboard, and C1PDebugger) that we have some controls
|
||||
* they might want to use.
|
||||
*/
|
||||
C1PPanel.init = function()
|
||||
{
|
||||
var fReady = false;
|
||||
var aePanels = Component.getElementsByClass(document, C1PJS.APPCLASS, "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 C1PPanel(parmsPanel);
|
||||
/**
|
||||
* The Panel doesn't have any bindings of its own; it passes along all binding requests to
|
||||
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any
|
||||
* component that doesn't recognize the specified binding should simply ignore it.
|
||||
*
|
||||
* @this {C1PPanel}
|
||||
* @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
|
||||
*/
|
||||
setBinding(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 super.setBinding(sHTMLType, sBinding, control, sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PPanel}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
setPower(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.cpu = cmp.getComponentByType("cpu");
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
C1PPanel.init();
|
||||
}
|
||||
Component.bindComponentControls(panel, ePanel, C1PJS.APPCLASS);
|
||||
if (fReady) panel.setReady();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PPanel.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "panel", extracting the
|
||||
* JSON-encoded parameters for the C1PPanel constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PPanel 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 binding our print()
|
||||
* function to the panel's output control ASAP, and again when the C1PComputer component
|
||||
* is verifying that all components are ready and invoking their setPower() functions.
|
||||
*
|
||||
* Our setPower() method gives us a second opportunity to notify any components that
|
||||
* that might care (eg, C1PCPU, C1PKeyboard, and C1PDebugger) that we have some controls
|
||||
* they might want to use.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var fReady = false;
|
||||
var aePanels = Component.getElementsByClass(document, C1PJS.APPCLASS, "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 C1PPanel(parmsPanel);
|
||||
}
|
||||
Component.bindComponentControls(panel, ePanel, C1PJS.APPCLASS);
|
||||
if (fReady) panel.setReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize every Panel module on the page.
|
||||
*/
|
||||
web.onInit(C1PPanel.init);
|
||||
Web.onInit(C1PPanel.init);
|
||||
|
|
|
|||
|
|
@ -29,66 +29,73 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PRAM(parmsRAM)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The RAM component expects the following (parmsRAM) properties:
|
||||
*
|
||||
* size: amount of RAM, in bytes
|
||||
*
|
||||
* NOTE: We may make a note of the specified size, but we will not actually allocate
|
||||
* any memory for the RAM; we wait for the Computer object to tell us where our RAM is,
|
||||
* using the setBuffer() method.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PRAM(parmsRAM)
|
||||
{
|
||||
Component.call(this, "C1PRAM", parmsRAM);
|
||||
}
|
||||
|
||||
Component.subclass(C1PRAM);
|
||||
|
||||
/**
|
||||
* @this {C1PRAM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
C1PRAM.prototype.setBuffer = function(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
// this.offRAM = start;
|
||||
// this.cbRAM = end - start + 1;
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PRAM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "ram", extracting the
|
||||
* JSON-encoded parameters for the C1PRAM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PRAM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PRAM.init = function()
|
||||
{
|
||||
var aeRAM = Component.getElementsByClass(document, C1PJS.APPCLASS, "ram");
|
||||
for (var iRAM=0; iRAM < aeRAM.length; iRAM++) {
|
||||
var eRAM = aeRAM[iRAM];
|
||||
var parmsRAM = Component.getComponentParms(eRAM);
|
||||
var ram = new C1PRAM(parmsRAM);
|
||||
Component.bindComponentControls(ram, eRAM, C1PJS.APPCLASS);
|
||||
class C1PRAM extends Component {
|
||||
/**
|
||||
* C1PRAM(parmsRAM)
|
||||
*
|
||||
* The RAM component expects the following (parmsRAM) properties:
|
||||
*
|
||||
* size: amount of RAM, in bytes
|
||||
*
|
||||
* NOTE: We may make a note of the specified size, but we will not actually allocate
|
||||
* any memory for the RAM; we wait for the Computer object to tell us where our RAM is,
|
||||
* using the setBuffer() method.
|
||||
*
|
||||
* @this {C1PRAM}
|
||||
* @param {Object} parmsRAM
|
||||
*/
|
||||
constructor(parmsRAM)
|
||||
{
|
||||
super("C1PRAM", parmsRAM);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PRAM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
setBuffer(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
// this.offRAM = start;
|
||||
// this.cbRAM = end - start + 1;
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PRAM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "ram", extracting the
|
||||
* JSON-encoded parameters for the C1PRAM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PRAM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeRAM = Component.getElementsByClass(document, C1PJS.APPCLASS, "ram");
|
||||
for (var iRAM=0; iRAM < aeRAM.length; iRAM++) {
|
||||
var eRAM = aeRAM[iRAM];
|
||||
var parmsRAM = Component.getComponentParms(eRAM);
|
||||
var ram = new C1PRAM(parmsRAM);
|
||||
Component.bindComponentControls(ram, eRAM, C1PJS.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all the RAM modules on the page.
|
||||
*/
|
||||
web.onInit(C1PRAM.init);
|
||||
Web.onInit(C1PRAM.init);
|
||||
|
|
|
|||
|
|
@ -29,215 +29,222 @@
|
|||
"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 Str = require("../../shared/es6/strlib");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PROM(parmsROM)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The ROM component expects the following (parmsROM) properties:
|
||||
*
|
||||
* size: amount of ROM, in bytes
|
||||
* image: name of ROM image file
|
||||
*
|
||||
* NOTE: The final location for the ROM image, once loaded, will be specified
|
||||
* by the Computer object, using the setBuffer() method.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @property {function()} convertImage
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PROM(parmsROM)
|
||||
{
|
||||
Component.call(this, "C1PROM", parmsROM);
|
||||
|
||||
this.abMem = null;
|
||||
this.abImage = null;
|
||||
this.cbROM = parmsROM['size'];
|
||||
this.sImage = parmsROM['image'];
|
||||
if (this.sImage) {
|
||||
var sFileURL = this.sImage;
|
||||
/**
|
||||
* If the selected ROM image has a ".json" extension, then we assume it's a pre-converted
|
||||
* JSON-encoded ROM image, so we load it as-is; ditto for files with a ".hex" extension. Otherwise,
|
||||
* we ask our server-side ROM image converter to return the corresponding JSON-encoded data,
|
||||
* in compact form (ie, minimal whitespace, no ASCII data comments, etc).
|
||||
*/
|
||||
var sFileExt = str.getExtension(this.sImage);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sImage + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES;
|
||||
}
|
||||
var rom = this;
|
||||
web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
rom.convertImage(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(C1PROM);
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
C1PROM.prototype.setBuffer = function(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offROM = start;
|
||||
var cbROM = end - start + 1;
|
||||
/*
|
||||
* It's possible that the ROM component didn't specify a size,
|
||||
* in which case just use the size the Computer component has specified.
|
||||
class C1PROM extends Component {
|
||||
/**
|
||||
* C1PROM(parmsROM)
|
||||
*
|
||||
* The ROM component expects the following (parmsROM) properties:
|
||||
*
|
||||
* size: amount of ROM, in bytes
|
||||
* image: name of ROM image file
|
||||
*
|
||||
* NOTE: The final location for the ROM image, once loaded, will be specified
|
||||
* by the Computer object, using the setBuffer() method.
|
||||
*
|
||||
* @this {C1PROM}
|
||||
* @param {Object} parmsROM
|
||||
* @property {function()} convertImage
|
||||
*/
|
||||
if (!this.cbROM)
|
||||
this.cbROM = cbROM;
|
||||
if (cbROM != this.cbROM) {
|
||||
this.setError("computer-specified ROM size (" + str.toHexWord(cbROM) + ") does not match component-specified size (" + str.toHexWord(this.cbROM) + ")");
|
||||
return;
|
||||
}
|
||||
if (cpu) {
|
||||
this.cpu = cpu;
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.copyImage();
|
||||
};
|
||||
constructor(parmsROM)
|
||||
{
|
||||
super("C1PROM", parmsROM);
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
C1PROM.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} [addrFrom]
|
||||
*/
|
||||
C1PROM.prototype.setByte = function(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Beyond reporting this write, we need to "repair" the ROM, using the original image data,
|
||||
* but only if addrFrom is defined (undefined implies this is a write from the Debugger, and
|
||||
* we need to allow the Debugger to modify ROM contents).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_PORT, true);
|
||||
var offset = (addr - this.offROM);
|
||||
Component.assert(offset >= 0 && offset < this.cbROM);
|
||||
if (!this.abImage)
|
||||
this.abMem[this.offROM + offset] = 0;
|
||||
else
|
||||
this.abMem[this.offROM + offset] = this.abImage[offset];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {string} sImageName
|
||||
* @param {string} sImageData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
C1PROM.prototype.convertImage = function(sImageName, sImageData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.println("Error loading ROM \"" + sImageName + "\" (" + nErrorCode + ")");
|
||||
return;
|
||||
}
|
||||
if (sImageData.charAt(0) == "[" || sImageData.charAt(0) == "{") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
|
||||
this.abMem = null;
|
||||
this.abImage = null;
|
||||
this.cbROM = parmsROM['size'];
|
||||
this.sImage = parmsROM['image'];
|
||||
if (this.sImage) {
|
||||
var sFileURL = this.sImage;
|
||||
/**
|
||||
* If the selected ROM image has a ".json" extension, then we assume it's a pre-converted
|
||||
* JSON-encoded ROM image, so we load it as-is; ditto for files with a ".hex" extension. Otherwise,
|
||||
* we ask our server-side ROM image converter to return the corresponding JSON-encoded data,
|
||||
* in compact form (ie, minimal whitespace, no ASCII data comments, etc).
|
||||
*/
|
||||
var rom = eval("(" + sImageData + ")");
|
||||
var ab = rom['bytes'];
|
||||
if (ab) {
|
||||
this.abImage = ab;
|
||||
} else {
|
||||
this.abImage = rom;
|
||||
var sFileExt = Str.getExtension(this.sImage);
|
||||
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
|
||||
sFileURL = Web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sImage + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES;
|
||||
}
|
||||
} catch (e) {
|
||||
this.println("Error processing ROM \"" + sImageName + "\": " + e.message);
|
||||
var rom = this;
|
||||
Web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
rom.convertImage(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
setBuffer(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offROM = start;
|
||||
var cbROM = end - start + 1;
|
||||
/*
|
||||
* It's possible that the ROM component didn't specify a size,
|
||||
* in which case just use the size the Computer component has specified.
|
||||
*/
|
||||
if (!this.cbROM)
|
||||
this.cbROM = cbROM;
|
||||
if (cbROM != this.cbROM) {
|
||||
this.setError("computer-specified ROM size (" + Str.toHexWord(cbROM) + ") does not match component-specified size (" + Str.toHexWord(this.cbROM) + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the ROM image data manually; we assume it's in "simplified" hex form (a series of hex byte-values separated by whitespace)
|
||||
*/
|
||||
var sData = sImageData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asData = sData.split(" ");
|
||||
this.abImage = new Array(asData.length);
|
||||
for (var i=0; i < asData.length; i++) {
|
||||
this.abImage[i] = parseInt(asData[i], 16);
|
||||
if (cpu) {
|
||||
this.cpu = cpu;
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.copyImage();
|
||||
}
|
||||
this.copyImage();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
*/
|
||||
C1PROM.prototype.copyImage = function()
|
||||
{
|
||||
/*
|
||||
* The Computer object may give us the address of the ROM image before we've finished downloading the image,
|
||||
* so both setBuffer() and convertImage() call copyImage(), which in turn will copy the image ONLY when both
|
||||
* pieces are in place. At that point, the component becomes "ready", in much the same way that other components
|
||||
* (eg, CPU and Screen) become "ready" when all their prerequisites are satisfied.
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*/
|
||||
if (!this.isReady()) {
|
||||
if (!this.sImage) {
|
||||
this.setReady();
|
||||
setPower(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
else
|
||||
if (this.abImage && this.abMem) {
|
||||
var cbImage = this.abImage.length;
|
||||
if (cbImage != this.cbROM) {
|
||||
this.setError("ROM image size (" + str.toHexWord(cbImage) + ") does not match component-specified size (" + str.toHexWord(this.cbROM) + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} [addrFrom]
|
||||
*/
|
||||
setByte(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Beyond reporting this write, we need to "repair" the ROM, using the original image data,
|
||||
* but only if addrFrom is defined (undefined implies this is a write from the Debugger, and
|
||||
* we need to allow the Debugger to modify ROM contents).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_PORT, true);
|
||||
var offset = (addr - this.offROM);
|
||||
Component.assert(offset >= 0 && offset < this.cbROM);
|
||||
if (!this.abImage)
|
||||
this.abMem[this.offROM + offset] = 0;
|
||||
else
|
||||
this.abMem[this.offROM + offset] = this.abImage[offset];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
* @param {string} sImageName
|
||||
* @param {string} sImageData
|
||||
* @param {number} nErrorCode (response from server if anything other than 200)
|
||||
*/
|
||||
convertImage(sImageName, sImageData, nErrorCode)
|
||||
{
|
||||
if (nErrorCode) {
|
||||
this.println("Error loading ROM \"" + sImageName + "\" (" + nErrorCode + ")");
|
||||
return;
|
||||
}
|
||||
if (sImageData.charAt(0) == "[" || sImageData.charAt(0) == "{") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
|
||||
*/
|
||||
var rom = eval("(" + sImageData + ")");
|
||||
var ab = rom['bytes'];
|
||||
if (ab) {
|
||||
this.abImage = ab;
|
||||
} else {
|
||||
this.abImage = rom;
|
||||
}
|
||||
} catch (e) {
|
||||
this.println("Error processing ROM \"" + sImageName + "\": " + e.message);
|
||||
return;
|
||||
}
|
||||
if (DEBUG) this.log("copyImage(): copying ROM to " + str.toHexWord(this.offROM) + " (" + str.toHexWord(cbImage) + " bytes)");
|
||||
for (var i=0; i < cbImage; i++) {
|
||||
this.abMem[this.offROM + i] = this.abImage[i];
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Parse the ROM image data manually; we assume it's in "simplified" hex form (a series of hex byte-values separated by whitespace)
|
||||
*/
|
||||
var sData = sImageData.replace(/\n/gm, " ").replace(/ +$/, "");
|
||||
var asData = sData.split(" ");
|
||||
this.abImage = new Array(asData.length);
|
||||
for (var i=0; i < asData.length; i++) {
|
||||
this.abImage[i] = parseInt(asData[i], 16);
|
||||
}
|
||||
}
|
||||
this.copyImage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PROM}
|
||||
*/
|
||||
copyImage()
|
||||
{
|
||||
/*
|
||||
* The Computer object may give us the address of the ROM image before we've finished downloading the image,
|
||||
* so both setBuffer() and convertImage() call copyImage(), which in turn will copy the image ONLY when both
|
||||
* pieces are in place. At that point, the component becomes "ready", in much the same way that other components
|
||||
* (eg, CPU and Screen) become "ready" when all their prerequisites are satisfied.
|
||||
*/
|
||||
if (!this.isReady()) {
|
||||
if (!this.sImage) {
|
||||
this.setReady();
|
||||
}
|
||||
else
|
||||
if (this.abImage && this.abMem) {
|
||||
var cbImage = this.abImage.length;
|
||||
if (cbImage != this.cbROM) {
|
||||
this.setError("ROM image size (" + Str.toHexWord(cbImage) + ") does not match component-specified size (" + Str.toHexWord(this.cbROM) + ")");
|
||||
return;
|
||||
}
|
||||
if (DEBUG) this.log("copyImage(): copying ROM to " + Str.toHexWord(this.offROM) + " (" + Str.toHexWord(cbImage) + " bytes)");
|
||||
for (var i=0; i < cbImage; i++) {
|
||||
this.abMem[this.offROM + i] = this.abImage[i];
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PROM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "rom", extracting the
|
||||
* JSON-encoded parameters for the C1PROM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PROM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PROM.init = function()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(document, C1PJS.APPCLASS, "rom");
|
||||
for (var iROM=0; iROM < aeROM.length; iROM++) {
|
||||
var eROM = aeROM[iROM];
|
||||
var parmsROM = Component.getComponentParms(eROM);
|
||||
var rom = new C1PROM(parmsROM);
|
||||
Component.bindComponentControls(rom, eROM, C1PJS.APPCLASS);
|
||||
/**
|
||||
* C1PROM.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "rom", extracting the
|
||||
* JSON-encoded parameters for the C1PROM constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PROM component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(document, C1PJS.APPCLASS, "rom");
|
||||
for (var iROM=0; iROM < aeROM.length; iROM++) {
|
||||
var eROM = aeROM[iROM];
|
||||
var parmsROM = Component.getComponentParms(eROM);
|
||||
var rom = new C1PROM(parmsROM);
|
||||
Component.bindComponentControls(rom, eROM, C1PJS.APPCLASS);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all the ROM modules on the page.
|
||||
*/
|
||||
web.onInit(C1PROM.init);
|
||||
Web.onInit(C1PROM.init);
|
||||
|
|
|
|||
|
|
@ -29,31 +29,411 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PSerialPort(parmsSerial)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* The SerialPort component has no component-specific parameters.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @unrestricted
|
||||
*/
|
||||
function C1PSerialPort(parmsSerial)
|
||||
{
|
||||
Component.call(this, "C1PSerialPort", parmsSerial);
|
||||
class C1PSerialPort extends Component {
|
||||
/**
|
||||
* C1PSerialPort(parmsSerial)
|
||||
*
|
||||
* The SerialPort component has no component-specific parameters.
|
||||
*
|
||||
* @this {C1PSerialPort}
|
||||
* @param {Object} parmsSerial
|
||||
*/
|
||||
constructor(parmsSerial)
|
||||
{
|
||||
super("C1PSerialPort", parmsSerial);
|
||||
|
||||
this.flags.powered = false;
|
||||
this.fDemo = parmsSerial['demo'];
|
||||
this.flags.powered = false;
|
||||
this.fDemo = parmsSerial['demo'];
|
||||
|
||||
this.reset(true);
|
||||
this.reset(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} [fHard]
|
||||
*/
|
||||
reset(fHard)
|
||||
{
|
||||
/*
|
||||
* Because we reset the machine at the start of a 6502 HEX command file auto-load,
|
||||
* we must avoid tossing the serial port's input buffer in that particular case (2).
|
||||
*/
|
||||
if (fHard || this.autoLoad != C1PSerialPort.AUTOLOAD_6502) {
|
||||
|
||||
this.bInput = -1;
|
||||
this.iInput = 0;
|
||||
this.sInput = "";
|
||||
if (this.fDemo) {
|
||||
this.sInput = "10 PRINT \"HELLO OSI #" + this.getMachineNum() + "\"\n";
|
||||
}
|
||||
|
||||
// this.sOutput = new Array(0);
|
||||
// this.iOutputNext = 0;
|
||||
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
start()
|
||||
{
|
||||
if (this.kbd && this.fDemo) {
|
||||
this.kbd.injectKeys(" C\n\n", 3000); // override the default injection delay (currently 300ms)
|
||||
setTimeout(function(serial) { return function() {serial.startLoad();}; }(this), 12000);
|
||||
}
|
||||
this.fDemo = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
|
||||
* @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
|
||||
*/
|
||||
setBinding(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
var serial = this;
|
||||
|
||||
switch(sBinding) {
|
||||
|
||||
case "listSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
case "loadSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function(event) {
|
||||
if (serial.bindings["listSerial"]) {
|
||||
var sFile = serial.bindings["listSerial"].value;
|
||||
// serial.println("loading " + sFile + "...");
|
||||
Web.getResource(sFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
serial.loadFile(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "mountSerial":
|
||||
/*
|
||||
* Check for non-mobile (desktop) browser and the availability of FileReader
|
||||
*/
|
||||
if (!Web.isMobile() && window && 'FileReader' in window) {
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* Enable "Mount" button only if a file is actually selected
|
||||
*/
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
submit.disabled = !files.length;
|
||||
});
|
||||
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
// serial.println("mounting " + file.name + "...");
|
||||
serial.loadFile(file.name, reader.result.toString(), 0);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else {
|
||||
if (DEBUG) this.log("Local file support not available");
|
||||
control.parentNode.removeChild(/** @type {Node} */ (control));
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
setBuffer(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offPort = start;
|
||||
this.cbPort = end - start + 1;
|
||||
this.offPortLimit = this.offPort + this.cbPort;
|
||||
if ((this.cpu = cpu)) {
|
||||
cpu.addReadNotify(start, end, this, this.getByte);
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*
|
||||
* We make a note of the Computer component, so that we can invoke its reset() method whenever we need to
|
||||
* simulate a warm start, and we query the Keyboard component so that we can use its injectKeys() function.
|
||||
*/
|
||||
setPower(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
startLoad()
|
||||
{
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("LOAD\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string} sFileName
|
||||
* @param {string} sFileData (null if getResource() encountered an error)
|
||||
* @param {number} nResponse from server
|
||||
*/
|
||||
loadFile(sFileName, sFileData, nResponse)
|
||||
{
|
||||
if (!sFileData) {
|
||||
this.println("Error loading file \"" + sFileName + "\" (" + nResponse + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this.iInput = 0;
|
||||
this.sInput = sFileData;
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
|
||||
/*
|
||||
* The following code adds support for loading "65V" files encoded as JSON, which is a cleaner
|
||||
* way to store and deliver those files when they contain binary (non-ASCII) data.
|
||||
*
|
||||
* For example, my 6502 ASSEMBLER/DISASSEMBLER program starts with a conventional "65V" loading
|
||||
* sequence, which loads and launches a small program loader that loads the rest of the program
|
||||
* using a raw (1-to-1) binary format instead of the usual (3-to-1) HEX format used by "65V" files.
|
||||
*
|
||||
* The "rawness" of the binary format also necessitates disabling fConvertLF.
|
||||
*/
|
||||
if (Str.endsWith(sFileName, ".json")) {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded data.
|
||||
*/
|
||||
var s = "";
|
||||
var data = eval("(" + sFileData + ")");
|
||||
var ab = data['bytes'];
|
||||
for (var i = 0; i < ab.length; i++) {
|
||||
s += String.fromCharCode(ab[i]);
|
||||
}
|
||||
this.sInput = s;
|
||||
this.fConvertLF = false;
|
||||
} catch (e) {
|
||||
this.println("Error processing file \"" + sFileName + "\": " + e.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cmp && this.kbd && this.cpu.isRunning()) {
|
||||
this.println("auto-loading " + sFileName);
|
||||
/*
|
||||
* QUESTION: Is this setFocus() call strictly necessary? We're being called in the
|
||||
* context of getResource(), not some user action. If there was an original user action,
|
||||
* then the handler for THAT action should take care to switch focus back, not us.
|
||||
*/
|
||||
this.cpu.setFocus();
|
||||
/*
|
||||
* We interpret the presence of a "." at the beginning of the file as a "65V Monitor"
|
||||
* address-mode command, and consequently treat the file as 6502 HEX command file.
|
||||
*
|
||||
* Anything else is treated as commands for the BASIC interpreter, which we re-initialize
|
||||
* with "NEW" and "LOAD" commands. To prevent that behavior, halt the CPU, perform the load,
|
||||
* and then start it running again. BASIC will start reading the data as soon as you type
|
||||
* LOAD.
|
||||
*/
|
||||
if (this.sInput.charAt(0) != '.') {
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("NEW\nLOAD\n");
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Set autoLoad to AUTOLOAD_6502 before the reset, so that when our reset() method is called,
|
||||
* we'll take care to preserve all the data we just loaded.
|
||||
*/
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_6502;
|
||||
/*
|
||||
* Although the Keyboard allows us to inject any key, even the BREAK key, like so:
|
||||
*
|
||||
* this.kbd.injectKeys(String.fromCharCode(this.kbd.CHARCODE_BREAK))
|
||||
*
|
||||
* it's easier to initiate a reset() ourselves and then start the machine-language load process
|
||||
*/
|
||||
this.cmp.reset(true);
|
||||
this.kbd.injectKeys("ML");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.println(sFileName + " ready to load");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
|
||||
*/
|
||||
getByte(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
/*
|
||||
* WARNING: All I need to do for now is load the COM interface's "data byte"
|
||||
* with the next byte from the virtual cassette data stream -JP
|
||||
*/
|
||||
if (!(addr & 0x01)) {
|
||||
/*
|
||||
* An EVEN address implies they're looking, so if we have a fresh buffer,
|
||||
* then prime the pump.
|
||||
*/
|
||||
if (this.sInput && !this.iInput)
|
||||
this.advanceInput();
|
||||
} else {
|
||||
/*
|
||||
* An ODD address implies they just grabbed a data byte, so prep the next data byte.
|
||||
*/
|
||||
this.advanceInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
|
||||
*/
|
||||
setByte(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this write (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_SERIAL, true);
|
||||
/*
|
||||
* WARNING: I don't yet care what state the CPU puts the port into. When it's time to support serial output,
|
||||
* obviously that will become an issue.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
advanceInput()
|
||||
{
|
||||
if (this.sInput !== undefined) {
|
||||
this.bInput = -1;
|
||||
if (this.iInput < this.sInput.length) {
|
||||
var b = this.sInput.charCodeAt(this.iInput++) & 0xff;
|
||||
if (this.fConvertLF) {
|
||||
if (b == 0x0a) b = 0x0d;
|
||||
}
|
||||
this.bInput = b;
|
||||
// if (DEBUG) this.log("advanceInput(" + Str.toHexByte(b) + ")");
|
||||
}
|
||||
else {
|
||||
this.sInput = "";
|
||||
this.iInput = 0;
|
||||
if (DEBUG) this.log("advanceInput(): out of data");
|
||||
if (this.autoLoad == C1PSerialPort.AUTOLOAD_BASIC && this.kbd) {
|
||||
this.kbd.injectKeys(" \nRUN\n");
|
||||
}
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
this.updateMemory();
|
||||
}
|
||||
// else if (DEBUG) this.log("advanceInput(): no input");
|
||||
}
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
updateMemory()
|
||||
{
|
||||
var offset;
|
||||
/*
|
||||
* Update all the status (even) bytes
|
||||
*/
|
||||
for (offset = this.offPort+0; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? C1PSerialPort.STATUS_DATA : C1PSerialPort.STATUS_NONE);
|
||||
}
|
||||
/*
|
||||
* Update all the data (odd) bytes
|
||||
*/
|
||||
for (offset = this.offPort+1; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? this.bInput : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C1PSerialPort.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "serial", extracting the
|
||||
* JSON-encoded parameters for the C1PSerialPort constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PSerialPort component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeSerial = Component.getElementsByClass(document, C1PJS.APPCLASS, "serial");
|
||||
for (var iSerial=0; iSerial < aeSerial.length; iSerial++) {
|
||||
var eSerial = aeSerial[iSerial];
|
||||
var parmsSerial = Component.getComponentParms(eSerial);
|
||||
var serial = new C1PSerialPort(parmsSerial);
|
||||
Component.bindComponentControls(serial, eSerial, C1PJS.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(C1PSerialPort);
|
||||
|
||||
C1PSerialPort.STATUS_NONE = 0x00;
|
||||
C1PSerialPort.STATUS_DATA = 0x01; // indicates data available
|
||||
|
||||
|
|
@ -68,380 +448,7 @@ C1PSerialPort.AUTOLOAD_NONE = 0;
|
|||
C1PSerialPort.AUTOLOAD_BASIC = 1;
|
||||
C1PSerialPort.AUTOLOAD_6502 = 2;
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} [fHard]
|
||||
*/
|
||||
C1PSerialPort.prototype.reset = function(fHard)
|
||||
{
|
||||
/*
|
||||
* Because we reset the machine at the start of a 6502 HEX command file auto-load,
|
||||
* we must avoid tossing the serial port's input buffer in that particular case (2).
|
||||
*/
|
||||
if (fHard || this.autoLoad != C1PSerialPort.AUTOLOAD_6502) {
|
||||
|
||||
this.bInput = -1;
|
||||
this.iInput = 0;
|
||||
this.sInput = "";
|
||||
if (this.fDemo) {
|
||||
this.sInput = "10 PRINT \"HELLO OSI #" + this.getMachineNum() + "\"\n";
|
||||
}
|
||||
|
||||
// this.sOutput = new Array(0);
|
||||
// this.iOutputNext = 0;
|
||||
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.start = function()
|
||||
{
|
||||
if (this.kbd && this.fDemo) {
|
||||
this.kbd.injectKeys(" C\n\n", 3000); // override the default injection delay (currently 300ms)
|
||||
setTimeout(function(serial) { return function() {serial.startLoad();}; }(this), 12000);
|
||||
}
|
||||
this.fDemo = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
|
||||
* @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
|
||||
*/
|
||||
C1PSerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
var serial = this;
|
||||
|
||||
switch(sBinding) {
|
||||
|
||||
case "listSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
case "loadSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function(event) {
|
||||
if (serial.bindings["listSerial"]) {
|
||||
var sFile = serial.bindings["listSerial"].value;
|
||||
// serial.println("loading " + sFile + "...");
|
||||
web.getResource(sFile, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
serial.loadFile(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "mountSerial":
|
||||
/*
|
||||
* Check for non-mobile (desktop) browser and the availability of FileReader
|
||||
*/
|
||||
if (!web.isMobile() && window && 'FileReader' in window) {
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* Enable "Mount" button only if a file is actually selected
|
||||
*/
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
submit.disabled = !files.length;
|
||||
});
|
||||
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
// serial.println("mounting " + file.name + "...");
|
||||
serial.loadFile(file.name, reader.result.toString(), 0);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else {
|
||||
if (DEBUG) this.log("Local file support not available");
|
||||
control.parentNode.removeChild(/** @type {Node} */ (control));
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {Array} abMemory
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {C1PCPU} cpu
|
||||
*/
|
||||
C1PSerialPort.prototype.setBuffer = function(abMemory, start, end, cpu)
|
||||
{
|
||||
this.abMem = abMemory;
|
||||
this.offPort = start;
|
||||
this.cbPort = end - start + 1;
|
||||
this.offPortLimit = this.offPort + this.cbPort;
|
||||
if ((this.cpu = cpu)) {
|
||||
cpu.addReadNotify(start, end, this, this.getByte);
|
||||
cpu.addWriteNotify(start, end, this, this.setByte);
|
||||
}
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {boolean} fOn
|
||||
* @param {C1PComputer} cmp
|
||||
*
|
||||
* We make a note of the Computer component, so that we can invoke its reset() method whenever we need to
|
||||
* simulate a warm start, and we query the Keyboard component so that we can use its injectKeys() function.
|
||||
*/
|
||||
C1PSerialPort.prototype.setPower = function(fOn, cmp)
|
||||
{
|
||||
if (fOn && !this.flags.powered) {
|
||||
this.flags.powered = true;
|
||||
this.cmp = cmp;
|
||||
this.kbd = cmp.getComponentByType("keyboard");
|
||||
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.startLoad = function()
|
||||
{
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("LOAD\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string} sFileName
|
||||
* @param {string} sFileData (null if getResource() encountered an error)
|
||||
* @param {number} nResponse from server
|
||||
*/
|
||||
C1PSerialPort.prototype.loadFile = function(sFileName, sFileData, nResponse)
|
||||
{
|
||||
if (!sFileData) {
|
||||
this.println("Error loading file \"" + sFileName + "\" (" + nResponse + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this.iInput = 0;
|
||||
this.sInput = sFileData;
|
||||
this.fConvertLF = true;
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
|
||||
/*
|
||||
* The following code adds support for loading "65V" files encoded as JSON, which is a cleaner
|
||||
* way to store and deliver those files when they contain binary (non-ASCII) data.
|
||||
*
|
||||
* For example, my 6502 ASSEMBLER/DISASSEMBLER program starts with a conventional "65V" loading
|
||||
* sequence, which loads and launches a small program loader that loads the rest of the program
|
||||
* using a raw (1-to-1) binary format instead of the usual (3-to-1) HEX format used by "65V" files.
|
||||
*
|
||||
* The "rawness" of the binary format also necessitates disabling fConvertLF.
|
||||
*/
|
||||
if (str.endsWith(sFileName, ".json")) {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be here: parsing the JSON-encoded data.
|
||||
*/
|
||||
var s = "";
|
||||
var data = eval("(" + sFileData + ")");
|
||||
var ab = data['bytes'];
|
||||
for (var i = 0; i < ab.length; i++) {
|
||||
s += String.fromCharCode(ab[i]);
|
||||
}
|
||||
this.sInput = s;
|
||||
this.fConvertLF = false;
|
||||
} catch (e) {
|
||||
this.println("Error processing file \"" + sFileName + "\": " + e.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cmp && this.kbd && this.cpu.isRunning()) {
|
||||
this.println("auto-loading " + sFileName);
|
||||
/*
|
||||
* QUESTION: Is this setFocus() call strictly necessary? We're being called in the
|
||||
* context of getResource(), not some user action. If there was an original user action,
|
||||
* then the handler for THAT action should take care to switch focus back, not us.
|
||||
*/
|
||||
this.cpu.setFocus();
|
||||
/*
|
||||
* We interpret the presence of a "." at the beginning of the file as a "65V Monitor"
|
||||
* address-mode command, and consequently treat the file as 6502 HEX command file.
|
||||
*
|
||||
* Anything else is treated as commands for the BASIC interpreter, which we re-initialize
|
||||
* with "NEW" and "LOAD" commands. To prevent that behavior, halt the CPU, perform the load,
|
||||
* and then start it running again. BASIC will start reading the data as soon as you type
|
||||
* LOAD.
|
||||
*/
|
||||
if (this.sInput.charAt(0) != '.') {
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_BASIC;
|
||||
this.kbd.injectKeys("NEW\nLOAD\n");
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Set autoLoad to AUTOLOAD_6502 before the reset, so that when our reset() method is called,
|
||||
* we'll take care to preserve all the data we just loaded.
|
||||
*/
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_6502;
|
||||
/*
|
||||
* Although the Keyboard allows us to inject any key, even the BREAK key, like so:
|
||||
*
|
||||
* this.kbd.injectKeys(String.fromCharCode(this.kbd.CHARCODE_BREAK))
|
||||
*
|
||||
* it's easier to initiate a reset() ourselves and then start the machine-language load process
|
||||
*/
|
||||
this.cmp.reset(true);
|
||||
this.kbd.injectKeys("ML");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.println(sFileName + " ready to load");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
|
||||
*/
|
||||
C1PSerialPort.prototype.getByte = function(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
/*
|
||||
* WARNING: All I need to do for now is load the COM interface's "data byte"
|
||||
* with the next byte from the virtual cassette data stream -JP
|
||||
*/
|
||||
if (!(addr & 0x01)) {
|
||||
/*
|
||||
* An EVEN address implies they're looking, so if we have a fresh buffer,
|
||||
* then prime the pump.
|
||||
*/
|
||||
if (this.sInput && !this.iInput)
|
||||
this.advanceInput();
|
||||
} else {
|
||||
/*
|
||||
* An ODD address implies they just grabbed a data byte, so prep the next data byte.
|
||||
*/
|
||||
this.advanceInput();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {number} addr
|
||||
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
|
||||
*/
|
||||
C1PSerialPort.prototype.setByte = function(addr, addrFrom)
|
||||
{
|
||||
/*
|
||||
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
|
||||
* the Debugger performed this write (need a special Debugger I/O command if/when you really want to do that).
|
||||
*/
|
||||
if (addrFrom !== undefined) {
|
||||
if (DEBUGGER && this.dbg) this.dbg.messageIO(this, addr, addrFrom, this.dbg.MESSAGE_SERIAL, true);
|
||||
/*
|
||||
* WARNING: I don't yet care what state the CPU puts the port into. When it's time to support serial output,
|
||||
* obviously that will become an issue.
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.advanceInput = function()
|
||||
{
|
||||
if (this.sInput !== undefined) {
|
||||
this.bInput = -1;
|
||||
if (this.iInput < this.sInput.length) {
|
||||
var b = this.sInput.charCodeAt(this.iInput++) & 0xff;
|
||||
if (this.fConvertLF) {
|
||||
if (b == 0x0a) b = 0x0d;
|
||||
}
|
||||
this.bInput = b;
|
||||
// if (DEBUG) this.log("advanceInput(" + str.toHexByte(b) + ")");
|
||||
}
|
||||
else {
|
||||
this.sInput = "";
|
||||
this.iInput = 0;
|
||||
if (DEBUG) this.log("advanceInput(): out of data");
|
||||
if (this.autoLoad == C1PSerialPort.AUTOLOAD_BASIC && this.kbd) {
|
||||
this.kbd.injectKeys(" \nRUN\n");
|
||||
}
|
||||
this.autoLoad = C1PSerialPort.AUTOLOAD_NONE;
|
||||
}
|
||||
this.updateMemory();
|
||||
}
|
||||
// else if (DEBUG) this.log("advanceInput(): no input");
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
*/
|
||||
C1PSerialPort.prototype.updateMemory = function()
|
||||
{
|
||||
var offset;
|
||||
/*
|
||||
* Update all the status (even) bytes
|
||||
*/
|
||||
for (offset = this.offPort+0; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? C1PSerialPort.STATUS_DATA : C1PSerialPort.STATUS_NONE);
|
||||
}
|
||||
/*
|
||||
* Update all the data (odd) bytes
|
||||
*/
|
||||
for (offset = this.offPort+1; offset < this.offPortLimit; offset+=2) {
|
||||
this.abMem[offset] = (this.bInput >= 0? this.bInput : 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* C1PSerialPort.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "serial", extracting the
|
||||
* JSON-encoded parameters for the C1PSerialPort constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a C1PSerialPort component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
C1PSerialPort.init = function()
|
||||
{
|
||||
var aeSerial = Component.getElementsByClass(document, C1PJS.APPCLASS, "serial");
|
||||
for (var iSerial=0; iSerial < aeSerial.length; iSerial++) {
|
||||
var eSerial = aeSerial[iSerial];
|
||||
var parmsSerial = Component.getComponentParms(eSerial);
|
||||
var serial = new C1PSerialPort(parmsSerial);
|
||||
Component.bindComponentControls(serial, eSerial, C1PJS.APPCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every SerialPort module on the page.
|
||||
*/
|
||||
web.onInit(C1PSerialPort.init);
|
||||
Web.onInit(C1PSerialPort.init);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -29,88 +29,15 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var State = require("../../shared/lib/state");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
var State = require("../../shared/es6/state");
|
||||
var PCX86 = require("./defines");
|
||||
var Messages = require("./messages");
|
||||
var ChipSet = require("./chipset");
|
||||
}
|
||||
|
||||
/**
|
||||
* ParallelPort(parmsParallel)
|
||||
*
|
||||
* The ParallelPort component has the following component-specific (parmsParallel) properties:
|
||||
*
|
||||
* adapter: 1 (port 0x3BC), 2 (port 0x378), or 3 (port 0x278); 0 if not defined
|
||||
*
|
||||
* binding: name of a control (based on its "binding" attribute) to bind to this port's I/O
|
||||
*
|
||||
* In the future, we may support 'port' and 'irq' properties that allow the machine to define a
|
||||
* non-standard parallel port configuration, instead of only our pre-defined 'adapter' configurations.
|
||||
*
|
||||
* NOTE: Since the XSL file defines 'adapter' as a number, not a string, there's no need to use
|
||||
* parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal format
|
||||
* was used.
|
||||
*
|
||||
* DOS typically names the Primary adapter "LPT1" and the Secondary adapter "LPT2", but I prefer
|
||||
* to stick to adapter numbers, since not all operating systems follow those naming conventions.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsParallel
|
||||
*/
|
||||
function ParallelPort(parmsParallel) {
|
||||
|
||||
this.iAdapter = parmsParallel['adapter'];
|
||||
|
||||
switch (this.iAdapter) {
|
||||
case 1:
|
||||
this.portBase = 0x3BC;
|
||||
this.nIRQ = ChipSet.IRQ.LPT1;
|
||||
break;
|
||||
case 2:
|
||||
this.portBase = 0x378;
|
||||
this.nIRQ = ChipSet.IRQ.LPT1;
|
||||
break;
|
||||
case 3:
|
||||
this.portBase = 0x278;
|
||||
this.nIRQ = ChipSet.IRQ.LPT2;
|
||||
break;
|
||||
default:
|
||||
Component.warning("Unrecognized parallel adapter #" + this.iAdapter);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* consoleOutput becomes a string that records parallel port output if the 'binding' property is set to the
|
||||
* reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output
|
||||
* or the string length reaches a threshold (currently, 1024 characters).
|
||||
*
|
||||
* @type {string|null}
|
||||
*/
|
||||
this.consoleOutput = null;
|
||||
|
||||
/**
|
||||
* controlIOBuffer is a DOM element bound to the port (currently used for output only; see transmitByte()).
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.controlIOBuffer = null;
|
||||
|
||||
Component.call(this, "ParallelPort", parmsParallel, ParallelPort, Messages.PARALLEL);
|
||||
|
||||
var sBinding = parmsParallel['binding'];
|
||||
if (sBinding == "console") {
|
||||
this.consoleOutput = "";
|
||||
} else {
|
||||
/*
|
||||
* NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing.
|
||||
*/
|
||||
Component.bindExternalControl(this, sBinding, ParallelPort.sIOBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* class ParallelPort
|
||||
* property {number} iAdapter
|
||||
|
|
@ -128,7 +55,387 @@ function ParallelPort(parmsParallel) {
|
|||
* for third-party apps.
|
||||
*/
|
||||
|
||||
Component.subclass(ParallelPort);
|
||||
/**
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* @unrestricted
|
||||
*/
|
||||
class ParallelPort extends Component {
|
||||
/**
|
||||
* ParallelPort(parmsParallel)
|
||||
*
|
||||
* The ParallelPort component has the following component-specific (parmsParallel) properties:
|
||||
*
|
||||
* adapter: 1 (port 0x3BC), 2 (port 0x378), or 3 (port 0x278); 0 if not defined
|
||||
*
|
||||
* binding: name of a control (based on its "binding" attribute) to bind to this port's I/O
|
||||
*
|
||||
* In the future, we may support 'port' and 'irq' properties that allow the machine to define a
|
||||
* non-standard parallel port configuration, instead of only our pre-defined 'adapter' configurations.
|
||||
*
|
||||
* NOTE: Since the XSL file defines 'adapter' as a number, not a string, there's no need to use
|
||||
* parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal format
|
||||
* was used.
|
||||
*
|
||||
* DOS typically names the Primary adapter "LPT1" and the Secondary adapter "LPT2", but I prefer
|
||||
* to stick to adapter numbers, since not all operating systems follow those naming conventions.
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Object} parmsParallel
|
||||
*/
|
||||
constructor(parmsParallel)
|
||||
{
|
||||
super("ParallelPort", parmsParallel, Messages.PARALLEL);
|
||||
|
||||
this.iAdapter = parmsParallel['adapter'];
|
||||
|
||||
switch (this.iAdapter) {
|
||||
case 1:
|
||||
this.portBase = 0x3BC;
|
||||
this.nIRQ = ChipSet.IRQ.LPT1;
|
||||
break;
|
||||
case 2:
|
||||
this.portBase = 0x378;
|
||||
this.nIRQ = ChipSet.IRQ.LPT1;
|
||||
break;
|
||||
case 3:
|
||||
this.portBase = 0x278;
|
||||
this.nIRQ = ChipSet.IRQ.LPT2;
|
||||
break;
|
||||
default:
|
||||
Component.warning("Unrecognized parallel adapter #" + this.iAdapter);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* consoleOutput becomes a string that records parallel port output if the 'binding' property is set to the
|
||||
* reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output
|
||||
* or the string length reaches a threshold (currently, 1024 characters).
|
||||
*
|
||||
* @type {string|null}
|
||||
*/
|
||||
this.consoleOutput = null;
|
||||
|
||||
/**
|
||||
* controlIOBuffer is a DOM element bound to the port (currently used for output only; see transmitByte()).
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
this.controlIOBuffer = null;
|
||||
|
||||
var sBinding = parmsParallel['binding'];
|
||||
if (sBinding == "console") {
|
||||
this.consoleOutput = "";
|
||||
} else {
|
||||
/*
|
||||
* NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing.
|
||||
*/
|
||||
Component.bindExternalControl(this, sBinding, ParallelPort.sIOBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* setBinding(sHTMLType, sBinding, control, sValue)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @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, "buffer")
|
||||
* @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
|
||||
*/
|
||||
setBinding(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
switch (sBinding) {
|
||||
case ParallelPort.sIOBuffer:
|
||||
this.bindings[sBinding] = this.controlIOBuffer = control;
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {DebuggerX86} dbg
|
||||
*/
|
||||
initBus(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
bus.addPortInputTable(this, ParallelPort.aPortInput, this.portBase);
|
||||
bus.addPortOutputTable(this, ParallelPort.aPortOutput, this.portBase);
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown(fSave, fShutdown)
|
||||
{
|
||||
return fSave? this.save() : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
*/
|
||||
reset()
|
||||
{
|
||||
this.initState();
|
||||
}
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the ParallelPort component.
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @return {Object}
|
||||
*/
|
||||
save()
|
||||
{
|
||||
var state = new State(this);
|
||||
state.set(0, this.saveRegisters());
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the ParallelPort component.
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
restore(data)
|
||||
{
|
||||
return this.initState(data[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* initState(data)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Array} [data]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
initState(data)
|
||||
{
|
||||
var i = 0;
|
||||
if (data === undefined) {
|
||||
data = [0, 0, 0];
|
||||
}
|
||||
this.bData = data[i++];
|
||||
this.bStatus = data[i++];
|
||||
this.bControl = data[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* saveRegisters()
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @return {Array}
|
||||
*/
|
||||
saveRegisters()
|
||||
{
|
||||
var i = 0;
|
||||
var data = [];
|
||||
data[i++] = this.bData;
|
||||
data[i++] = this.bStatus;
|
||||
data[i] = this.bControl;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* inData(port, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BC, 0x378, or 0x278)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
inData(port, addrFrom)
|
||||
{
|
||||
var b = this.bData;
|
||||
this.printMessageIO(port, null, addrFrom, "DATA", b);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* inStatus(port, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BD, 0x379, or 0x279)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
inStatus(port, addrFrom)
|
||||
{
|
||||
var b = this.bStatus;
|
||||
this.printMessageIO(port, null, addrFrom, "STAT", b);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* inControl(port, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BE, 0x37A, or 0x27A)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
inControl(port, addrFrom)
|
||||
{
|
||||
var b = this.bControl;
|
||||
this.printMessageIO(port, null, addrFrom, "CTRL", b);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* outData(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BC, 0x378, or 0x278)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
outData(port, bOut, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, bOut, addrFrom, "DATA");
|
||||
this.bData = bOut;
|
||||
this.bStatus |= ParallelPort.STATUS.NOTREADY;
|
||||
if (this.transmitByte(bOut)) {
|
||||
this.bStatus &= ~ParallelPort.STATUS.NOTREADY;
|
||||
}
|
||||
this.updateIRR();
|
||||
}
|
||||
|
||||
/**
|
||||
* outControl(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BE, 0x37A, or 0x27A)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
outControl(port, bOut, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, bOut, addrFrom, "CTRL");
|
||||
this.bControl = bOut;
|
||||
this.updateIRR();
|
||||
}
|
||||
|
||||
/**
|
||||
* updateIRR()
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
*/
|
||||
updateIRR()
|
||||
{
|
||||
if (this.chipset && this.nIRQ) {
|
||||
if ((this.bControl & ParallelPort.CONTROL.IRQ_ENABLE) && !(this.bStatus & ParallelPort.STATUS.NOTREADY)) {
|
||||
this.chipset.setIRR(this.nIRQ);
|
||||
} else {
|
||||
this.chipset.clearIRR(this.nIRQ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* transmitByte(b)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} b
|
||||
* @return {boolean} true if transmitted, false if not
|
||||
*/
|
||||
transmitByte(b)
|
||||
{
|
||||
var fTransmitted = false;
|
||||
|
||||
this.printMessage("transmitByte(" + Str.toHexByte(b) + ")");
|
||||
|
||||
if (this.controlIOBuffer) {
|
||||
if (b == 0x08) {
|
||||
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
|
||||
}
|
||||
else {
|
||||
this.controlIOBuffer.value += String.fromCharCode(b);
|
||||
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
|
||||
}
|
||||
fTransmitted = true;
|
||||
}
|
||||
if (this.consoleOutput != null) {
|
||||
if (b == 0x0A || this.consoleOutput.length >= 1024) {
|
||||
this.println(this.consoleOutput);
|
||||
this.consoleOutput = "";
|
||||
}
|
||||
if (b != 0x0A) {
|
||||
this.consoleOutput += String.fromCharCode(b);
|
||||
}
|
||||
fTransmitted = true;
|
||||
}
|
||||
|
||||
return fTransmitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* ParallelPort.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "parallel", extracting the
|
||||
* JSON-encoded parameters for the ParallelPort constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a ParallelPort component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeParallel = Component.getElementsByClass(document, PCX86.APPCLASS, "parallel");
|
||||
for (var iParallel = 0; iParallel < aeParallel.length; iParallel++) {
|
||||
var eParallel = aeParallel[iParallel];
|
||||
var parmsParallel = Component.getComponentParms(eParallel);
|
||||
var parallel = new ParallelPort(parmsParallel);
|
||||
Component.bindComponentControls(parallel, eParallel, PCX86.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal name used for the I/O buffer control, if any, that we bind to the ParallelPort.
|
||||
|
|
@ -197,288 +504,6 @@ ParallelPort.CONTROL = { // (read/write)
|
|||
IRQ_ENABLE: 0x10 // set to enable interrupts
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sHTMLType, sBinding, control, sValue)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @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, "buffer")
|
||||
* @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
|
||||
*/
|
||||
ParallelPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
switch (sBinding) {
|
||||
case ParallelPort.sIOBuffer:
|
||||
this.bindings[sBinding] = this.controlIOBuffer = control;
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {DebuggerX86} dbg
|
||||
*/
|
||||
ParallelPort.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
bus.addPortInputTable(this, ParallelPort.aPortInput, this.portBase);
|
||||
bus.addPortOutputTable(this, ParallelPort.aPortOutput, this.portBase);
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
ParallelPort.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
ParallelPort.prototype.powerDown = function(fSave, fShutdown)
|
||||
{
|
||||
return fSave? this.save() : true;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
*/
|
||||
ParallelPort.prototype.reset = function()
|
||||
{
|
||||
this.initState();
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the ParallelPort component.
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @return {Object}
|
||||
*/
|
||||
ParallelPort.prototype.save = function()
|
||||
{
|
||||
var state = new State(this);
|
||||
state.set(0, this.saveRegisters());
|
||||
return state.data();
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the ParallelPort component.
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
ParallelPort.prototype.restore = function(data)
|
||||
{
|
||||
return this.initState(data[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* initState(data)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {Array} [data]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
ParallelPort.prototype.initState = function(data)
|
||||
{
|
||||
var i = 0;
|
||||
if (data === undefined) {
|
||||
data = [0, 0, 0];
|
||||
}
|
||||
this.bData = data[i++];
|
||||
this.bStatus = data[i++];
|
||||
this.bControl = data[i];
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* saveRegisters()
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @return {Array}
|
||||
*/
|
||||
ParallelPort.prototype.saveRegisters = function()
|
||||
{
|
||||
var i = 0;
|
||||
var data = [];
|
||||
data[i++] = this.bData;
|
||||
data[i++] = this.bStatus;
|
||||
data[i] = this.bControl;
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* inData(port, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BC, 0x378, or 0x278)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
ParallelPort.prototype.inData = function(port, addrFrom)
|
||||
{
|
||||
var b = this.bData;
|
||||
this.printMessageIO(port, null, addrFrom, "DATA", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inStatus(port, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BD, 0x379, or 0x279)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
ParallelPort.prototype.inStatus = function(port, addrFrom)
|
||||
{
|
||||
var b = this.bStatus;
|
||||
this.printMessageIO(port, null, addrFrom, "STAT", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inControl(port, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BE, 0x37A, or 0x27A)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
ParallelPort.prototype.inControl = function(port, addrFrom)
|
||||
{
|
||||
var b = this.bControl;
|
||||
this.printMessageIO(port, null, addrFrom, "CTRL", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outData(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BC, 0x378, or 0x278)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
ParallelPort.prototype.outData = function(port, bOut, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, bOut, addrFrom, "DATA");
|
||||
this.bData = bOut;
|
||||
this.bStatus |= ParallelPort.STATUS.NOTREADY;
|
||||
if (this.transmitByte(bOut)) {
|
||||
this.bStatus &= ~ParallelPort.STATUS.NOTREADY;
|
||||
}
|
||||
this.updateIRR();
|
||||
};
|
||||
|
||||
/**
|
||||
* outControl(port, bOut, addrFrom)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} port (0x3BE, 0x37A, or 0x27A)
|
||||
* @param {number} bOut
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
|
||||
*/
|
||||
ParallelPort.prototype.outControl = function(port, bOut, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, bOut, addrFrom, "CTRL");
|
||||
this.bControl = bOut;
|
||||
this.updateIRR();
|
||||
};
|
||||
|
||||
/**
|
||||
* updateIRR()
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
*/
|
||||
ParallelPort.prototype.updateIRR = function()
|
||||
{
|
||||
if (this.chipset && this.nIRQ) {
|
||||
if ((this.bControl & ParallelPort.CONTROL.IRQ_ENABLE) && !(this.bStatus & ParallelPort.STATUS.NOTREADY)) {
|
||||
this.chipset.setIRR(this.nIRQ);
|
||||
} else {
|
||||
this.chipset.clearIRR(this.nIRQ);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* transmitByte(b)
|
||||
*
|
||||
* @this {ParallelPort}
|
||||
* @param {number} b
|
||||
* @return {boolean} true if transmitted, false if not
|
||||
*/
|
||||
ParallelPort.prototype.transmitByte = function(b)
|
||||
{
|
||||
var fTransmitted = false;
|
||||
|
||||
this.printMessage("transmitByte(" + str.toHexByte(b) + ")");
|
||||
|
||||
if (this.controlIOBuffer) {
|
||||
if (b == 0x08) {
|
||||
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
|
||||
}
|
||||
else {
|
||||
this.controlIOBuffer.value += String.fromCharCode(b);
|
||||
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
|
||||
}
|
||||
fTransmitted = true;
|
||||
}
|
||||
if (this.consoleOutput != null) {
|
||||
if (b == 0x0A || this.consoleOutput.length >= 1024) {
|
||||
this.println(this.consoleOutput);
|
||||
this.consoleOutput = "";
|
||||
}
|
||||
if (b != 0x0A) {
|
||||
this.consoleOutput += String.fromCharCode(b);
|
||||
}
|
||||
fTransmitted = true;
|
||||
}
|
||||
|
||||
return fTransmitted;
|
||||
};
|
||||
|
||||
/*
|
||||
* Port input notification table
|
||||
*/
|
||||
|
|
@ -496,28 +521,9 @@ ParallelPort.aPortOutput = {
|
|||
0x2: ParallelPort.prototype.outControl
|
||||
};
|
||||
|
||||
/**
|
||||
* ParallelPort.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "parallel", extracting the
|
||||
* JSON-encoded parameters for the ParallelPort constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a ParallelPort component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
ParallelPort.init = function()
|
||||
{
|
||||
var aeParallel = Component.getElementsByClass(document, PCX86.APPCLASS, "parallel");
|
||||
for (var iParallel = 0; iParallel < aeParallel.length; iParallel++) {
|
||||
var eParallel = aeParallel[iParallel];
|
||||
var parmsParallel = Component.getComponentParms(eParallel);
|
||||
var parallel = new ParallelPort(parmsParallel);
|
||||
Component.bindComponentControls(parallel, eParallel, PCX86.APPCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every ParallelPort module on the page.
|
||||
*/
|
||||
web.onInit(ParallelPort.init);
|
||||
Web.onInit(ParallelPort.init);
|
||||
|
||||
if (NODE) module.exports = ParallelPort;
|
||||
|
|
|
|||
|
|
@ -29,290 +29,474 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var State = require("../../shared/lib/state");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var Component = require("../../shared/es6/component");
|
||||
var State = require("../../shared/es6/state");
|
||||
var PCX86 = require("./defines");
|
||||
var Memory = require("./memory");
|
||||
var ROM = require("./rom");
|
||||
}
|
||||
|
||||
/**
|
||||
* RAM(parmsRAM)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* 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)
|
||||
* test: true (default) means don't interfere with any BIOS memory tests, false means "fake a warm boot"
|
||||
*
|
||||
* 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
|
||||
* @unrestricted
|
||||
*/
|
||||
function RAM(parmsRAM)
|
||||
{
|
||||
Component.call(this, "RAM", parmsRAM, RAM);
|
||||
class RAM extends Component {
|
||||
/**
|
||||
* 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)
|
||||
* test: true (default) means don't interfere with any BIOS memory tests, false means "fake a warm boot"
|
||||
*
|
||||
* NOTE: We make a note of the specified size, but no memory is initially allocated for the RAM until the
|
||||
* Computer component calls powerUp().
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {Object} parmsRAM
|
||||
*/
|
||||
constructor(parmsRAM)
|
||||
{
|
||||
super("RAM", parmsRAM);
|
||||
|
||||
this.addrRAM = parmsRAM['addr'];
|
||||
this.sizeRAM = parmsRAM['size'];
|
||||
this.fTestRAM = parmsRAM['test'];
|
||||
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
|
||||
this.fAllocated = false;
|
||||
}
|
||||
this.addrRAM = parmsRAM['addr'];
|
||||
this.sizeRAM = parmsRAM['size'];
|
||||
this.fTestRAM = parmsRAM['test'];
|
||||
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 {X86CPU} cpu
|
||||
* @param {DebuggerX86} dbg
|
||||
*/
|
||||
initBus(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {DebuggerX86} dbg
|
||||
*/
|
||||
RAM.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
this.setReady();
|
||||
};
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
/*
|
||||
* The Computer powers up the CPU last, at which point X86CPU 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.
|
||||
*
|
||||
* The only exception is when there's a custom Memory controller (eg, CompaqController).
|
||||
*/
|
||||
this.reset();
|
||||
if (data && this.controller) {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
powerDown(fSave, fShutdown)
|
||||
{
|
||||
/*
|
||||
* The Computer powers up the CPU last, at which point X86CPU 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.
|
||||
* The Computer powers down the CPU first, at which point X86CPU 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.
|
||||
*
|
||||
* The only exception is when there's a custom Memory controller (eg, CompaqController).
|
||||
*/
|
||||
this.reset();
|
||||
if (data && this.controller) {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
return (fSave && this.controller)? this.save() : true;
|
||||
}
|
||||
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 X86CPU 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.
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* The only exception is when there's a custom Memory controller (eg, CompaqController).
|
||||
* NOTE: When we were initialized, we were given an amount of INSTALLED memory (see sizeRAM above).
|
||||
* The ChipSet component, on the other hand, tells us how much SPECIFIED memory there is -- which,
|
||||
* like a real PC, may not match the amount of installed memory (due to either user error or perhaps
|
||||
* an attempt to prevent some portion of the installed memory from being used).
|
||||
*
|
||||
* However, since we're a virtual machine, we can defer allocation of RAM until we're able to query the
|
||||
* ChipSet component, and then allocate an amount of memory that matches the SPECIFIED memory, making
|
||||
* it easy to reconfigure the machine on the fly and prevent mismatches.
|
||||
*
|
||||
* But, we do that ONLY for the RAM instance configured with an addrRAM of 0x0000, and ONLY if that RAM
|
||||
* object was not given a specific size (see fInstalled). If there are other RAM objects in the system,
|
||||
* they must necessarily specify a non-conflicting, non-zero start address, in which case their sizeRAM
|
||||
* value will never be affected by the ChipSet settings.
|
||||
*
|
||||
* @this {RAM}
|
||||
*/
|
||||
return (fSave && this.controller)? this.save() : true;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* NOTE: When we were initialized, we were given an amount of INSTALLED memory (see sizeRAM above).
|
||||
* The ChipSet component, on the other hand, tells us how much SPECIFIED memory there is -- which,
|
||||
* like a real PC, may not match the amount of installed memory (due to either user error or perhaps
|
||||
* an attempt to prevent some portion of the installed memory from being used).
|
||||
*
|
||||
* However, since we're a virtual machine, we can defer allocation of RAM until we're able to query the
|
||||
* ChipSet component, and then allocate an amount of memory that matches the SPECIFIED memory, making
|
||||
* it easy to reconfigure the machine on the fly and prevent mismatches.
|
||||
*
|
||||
* But, we do that ONLY for the RAM instance configured with an addrRAM of 0x0000, and ONLY if that RAM
|
||||
* object was not given a specific size (see fInstalled). If there are other RAM objects in the system,
|
||||
* they must necessarily specify a non-conflicting, non-zero start address, in which case their sizeRAM
|
||||
* value will never be affected by the ChipSet settings.
|
||||
*
|
||||
* @this {RAM}
|
||||
*/
|
||||
RAM.prototype.reset = function()
|
||||
{
|
||||
if (!this.addrRAM && !this.fInstalled && this.chipset) {
|
||||
var baseRAM = this.chipset.getDIPMemorySize() * 1024;
|
||||
if (this.sizeRAM && baseRAM != this.sizeRAM) {
|
||||
this.bus.removeMemory(this.addrRAM, this.sizeRAM);
|
||||
this.fAllocated = false;
|
||||
reset()
|
||||
{
|
||||
if (!this.addrRAM && !this.fInstalled && this.chipset) {
|
||||
var baseRAM = this.chipset.getDIPMemorySize() * 1024;
|
||||
if (this.sizeRAM && baseRAM != this.sizeRAM) {
|
||||
this.bus.removeMemory(this.addrRAM, this.sizeRAM);
|
||||
this.fAllocated = false;
|
||||
}
|
||||
this.sizeRAM = baseRAM;
|
||||
}
|
||||
this.sizeRAM = baseRAM;
|
||||
}
|
||||
if (!this.fAllocated && this.sizeRAM) {
|
||||
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory.TYPE.RAM)) {
|
||||
this.fAllocated = true;
|
||||
if (!this.fAllocated && this.sizeRAM) {
|
||||
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory.TYPE.RAM)) {
|
||||
this.fAllocated = true;
|
||||
|
||||
/*
|
||||
* NOTE: I'm specifying MAXDEBUG for status() messages because I'm not yet sure I want these
|
||||
* messages buried in the app, since they're seen only when a Control Panel is active. Another
|
||||
* and perhaps better alternative is to add "comment" attributes to the XML configuration file
|
||||
* for these components, which the Computer component will display as it "powers up" components.
|
||||
*/
|
||||
if (MAXDEBUG && this.fInstalled) this.status("specified size overrides SW1");
|
||||
/*
|
||||
* NOTE: I'm specifying MAXDEBUG for status() messages because I'm not yet sure I want these
|
||||
* messages buried in the app, since they're seen only when a Control Panel is active. Another
|
||||
* and perhaps better alternative is to add "comment" attributes to the XML configuration file
|
||||
* for these components, which the Computer component will display as it "powers up" components.
|
||||
*/
|
||||
if (MAXDEBUG && this.fInstalled) this.status("specified size overrides SW1");
|
||||
|
||||
/*
|
||||
* Memory with an ID of "ramCPQ" is reserved for built-in memory located just below the 16Mb
|
||||
* boundary on COMPAQ DeskPro 386 machines.
|
||||
*
|
||||
* Technically, that memory is part of the first 1Mb of memory that also provides up to 640Kb
|
||||
* of conventional memory (ie, memory below 1Mb).
|
||||
*
|
||||
* However, PCx86 doesn't support individual memory allocations that (a) are discontiguous
|
||||
* or (b) dynamically change location. Components must simulate those features by performing
|
||||
* a separate allocation for each starting address, and removing/adding memory allocations
|
||||
* whenever their starting address changes.
|
||||
*
|
||||
* Therefore, a DeskPro 386's first 1Mb of physical memory is allocated by PCx86 in two pieces,
|
||||
* and the second piece must have an ID of "ramCPQ", triggering the additional allocation of
|
||||
* COMPAQ-specific memory-mapped registers.
|
||||
*
|
||||
* See CompaqController for more details.
|
||||
*/
|
||||
if (DESKPRO386) {
|
||||
if (this.idComponent == "ramCPQ") {
|
||||
this.controller = new CompaqController(this);
|
||||
this.bus.addMemory(CompaqController.ADDR, 4, Memory.TYPE.CTRL, this.controller);
|
||||
/*
|
||||
* Memory with an ID of "ramCPQ" is reserved for built-in memory located just below the 16Mb
|
||||
* boundary on COMPAQ DeskPro 386 machines.
|
||||
*
|
||||
* Technically, that memory is part of the first 1Mb of memory that also provides up to 640Kb
|
||||
* of conventional memory (ie, memory below 1Mb).
|
||||
*
|
||||
* However, PCx86 doesn't support individual memory allocations that (a) are discontiguous
|
||||
* or (b) dynamically change location. Components must simulate those features by performing
|
||||
* a separate allocation for each starting address, and removing/adding memory allocations
|
||||
* whenever their starting address changes.
|
||||
*
|
||||
* Therefore, a DeskPro 386's first 1Mb of physical memory is allocated by PCx86 in two pieces,
|
||||
* and the second piece must have an ID of "ramCPQ", triggering the additional allocation of
|
||||
* COMPAQ-specific memory-mapped registers.
|
||||
*
|
||||
* See CompaqController for more details.
|
||||
*/
|
||||
if (DESKPRO386) {
|
||||
if (this.idComponent == "ramCPQ") {
|
||||
this.controller = new CompaqController(this);
|
||||
this.bus.addMemory(CompaqController.ADDR, 4, Memory.TYPE.CTRL, this.controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.fAllocated) {
|
||||
if (!this.fTestRAM) {
|
||||
if (this.fAllocated) {
|
||||
if (!this.fTestRAM) {
|
||||
/*
|
||||
* HACK: Set the word at 40:72 in the ROM BIOS Data Area (RBDA) to 0x1234 to bypass the ROM BIOS
|
||||
* memory storage tests. See rom.js for all RBDA definitions.
|
||||
*/
|
||||
if (MAXDEBUG) this.status("ROM BIOS memory test has been disabled");
|
||||
this.bus.setShortDirect(ROM.BIOS.RESET_FLAG, ROM.BIOS.RESET_FLAG_WARMBOOT);
|
||||
}
|
||||
/*
|
||||
* HACK: Set the word at 40:72 in the ROM BIOS Data Area (RBDA) to 0x1234 to bypass the ROM BIOS
|
||||
* memory storage tests. See rom.js for all RBDA definitions.
|
||||
* Don't add the "ramCPQ" memory to the CMOS total, because addCMOSMemory() will add it to the extended
|
||||
* memory total, which will just confuse the COMPAQ BIOS.
|
||||
*/
|
||||
if (MAXDEBUG) this.status("ROM BIOS memory test has been disabled");
|
||||
this.bus.setShortDirect(ROM.BIOS.RESET_FLAG, ROM.BIOS.RESET_FLAG_WARMBOOT);
|
||||
if (!DESKPRO386 || this.idComponent != "ramCPQ") {
|
||||
if (this.chipset) this.chipset.addCMOSMemory(this.addrRAM, this.sizeRAM);
|
||||
}
|
||||
} else {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
/*
|
||||
* Don't add the "ramCPQ" memory to the CMOS total, because addCMOSMemory() will add it to the extended
|
||||
* memory total, which will just confuse the COMPAQ BIOS.
|
||||
*/
|
||||
if (!DESKPRO386 || this.idComponent != "ramCPQ") {
|
||||
if (this.chipset) this.chipset.addCMOSMemory(this.addrRAM, this.sizeRAM);
|
||||
}
|
||||
} else {
|
||||
Component.error("No RAM allocated");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the RAM component.
|
||||
*
|
||||
* @this {RAM}
|
||||
* @return {Object}
|
||||
*/
|
||||
RAM.prototype.save = function()
|
||||
{
|
||||
var state = new State(this);
|
||||
if (this.controller) state.set(0, this.controller.save());
|
||||
return state.data();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
if (this.controller) return this.controller.restore(data[0]);
|
||||
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, PCX86.APPCLASS, "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, PCX86.APPCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CompaqController(ram)
|
||||
*
|
||||
* DeskPro 386 machines came with a minimum of 1Mb of RAM, which could be configured (via jumpers)
|
||||
* for 256Kb, 512Kb or 640Kb of conventional memory, starting at address 0x00000000, with the
|
||||
* remainder (768Kb, 512Kb, or 384Kb) accessible only at an address just below 0x01000000. In PCx86,
|
||||
* this second chunk of RAM must be separately allocated, with an ID of "ramCPQ".
|
||||
*
|
||||
* The typical configuration was 640Kb of conventional memory, leaving 384Kb accessible at 0x00FA0000.
|
||||
* Presumably, the other configurations (256Kb and 512Kb) would leave 768Kb and 512Kb accessible at
|
||||
* 0x00F40000 and 0x00F80000, respectively.
|
||||
*
|
||||
* The DeskPro 386 also contained two memory-mapped registers at 0x80C00000. The first is a write-only
|
||||
* mapping register that provides the ability to map the 128Kb at 0x00FE0000 to 0x000E0000, replacing
|
||||
* any ROMs in the range 0x000E0000-0x000FFFFF, and optionally write-protecting that 128Kb; internally,
|
||||
* this register corresponds to wMappings.
|
||||
*
|
||||
* The second register is a read-only diagnostics register that indicates jumper configuration and
|
||||
* parity errors; internally, this register corresponds to wSettings.
|
||||
*
|
||||
* To emulate the memory-mapped registers at 0x80C00000, the RAM component allocates a block at that
|
||||
* address using this custom controller once it sees an allocation for "ramCPQ".
|
||||
*
|
||||
* Later, when the addressability of "ramCPQ" memory is altered, we record the blocks in all the
|
||||
* memory slots spanning 0x000E0000-0x000FFFFF, and then update those slots with the blocks from
|
||||
* 0x00FE0000-0x00FFFFFF. Note that only the top 128Kb of "ramCPQ" addressability is affected; the
|
||||
* rest of that memory, ranging anywhere from 256Kb to 640Kb, remains addressable at its original
|
||||
* location. COMPAQ's CEMM and VDISK utilities were generally the only software able to access that
|
||||
* remaining memory (what COMPAQ refers to as "Compaq Built-in Memory").
|
||||
*
|
||||
* @constructor
|
||||
* @param {RAM} ram
|
||||
*/
|
||||
function CompaqController(ram)
|
||||
{
|
||||
this.ram = ram;
|
||||
this.wMappings = CompaqController.MAPPINGS.DEFAULT;
|
||||
/*
|
||||
* TODO: wSettings needs to reflect the actual amount of configured memory....
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the RAM component.
|
||||
*
|
||||
* @this {RAM}
|
||||
* @return {Object}
|
||||
*/
|
||||
this.wSettings = CompaqController.SETTINGS.DEFAULT;
|
||||
this.wRAMSetup = CompaqController.RAMSETUP.DEFAULT;
|
||||
this.aBlocksDst = null;
|
||||
save()
|
||||
{
|
||||
var state = new State(this);
|
||||
if (this.controller) state.set(0, this.controller.save());
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the RAM component.
|
||||
*
|
||||
* @this {RAM}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
restore(data)
|
||||
{
|
||||
if (this.controller) return this.controller.restore(data[0]);
|
||||
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.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeRAM = Component.getElementsByClass(document, PCX86.APPCLASS, "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, PCX86.APPCLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* @unrestricted
|
||||
*/
|
||||
class CompaqController {
|
||||
/**
|
||||
* CompaqController(ram)
|
||||
*
|
||||
* DeskPro 386 machines came with a minimum of 1Mb of RAM, which could be configured (via jumpers)
|
||||
* for 256Kb, 512Kb or 640Kb of conventional memory, starting at address 0x00000000, with the
|
||||
* remainder (768Kb, 512Kb, or 384Kb) accessible only at an address just below 0x01000000. In PCx86,
|
||||
* this second chunk of RAM must be separately allocated, with an ID of "ramCPQ".
|
||||
*
|
||||
* The typical configuration was 640Kb of conventional memory, leaving 384Kb accessible at 0x00FA0000.
|
||||
* Presumably, the other configurations (256Kb and 512Kb) would leave 768Kb and 512Kb accessible at
|
||||
* 0x00F40000 and 0x00F80000, respectively.
|
||||
*
|
||||
* The DeskPro 386 also contained two memory-mapped registers at 0x80C00000. The first is a write-only
|
||||
* mapping register that provides the ability to map the 128Kb at 0x00FE0000 to 0x000E0000, replacing
|
||||
* any ROMs in the range 0x000E0000-0x000FFFFF, and optionally write-protecting that 128Kb; internally,
|
||||
* this register corresponds to wMappings.
|
||||
*
|
||||
* The second register is a read-only diagnostics register that indicates jumper configuration and
|
||||
* parity errors; internally, this register corresponds to wSettings.
|
||||
*
|
||||
* To emulate the memory-mapped registers at 0x80C00000, the RAM component allocates a block at that
|
||||
* address using this custom controller once it sees an allocation for "ramCPQ".
|
||||
*
|
||||
* Later, when the addressability of "ramCPQ" memory is altered, we record the blocks in all the
|
||||
* memory slots spanning 0x000E0000-0x000FFFFF, and then update those slots with the blocks from
|
||||
* 0x00FE0000-0x00FFFFFF. Note that only the top 128Kb of "ramCPQ" addressability is affected; the
|
||||
* rest of that memory, ranging anywhere from 256Kb to 640Kb, remains addressable at its original
|
||||
* location. COMPAQ's CEMM and VDISK utilities were generally the only software able to access that
|
||||
* remaining memory (what COMPAQ refers to as "Compaq Built-in Memory").
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {RAM} ram
|
||||
*/
|
||||
constructor(ram)
|
||||
{
|
||||
this.ram = ram;
|
||||
this.wMappings = CompaqController.MAPPINGS.DEFAULT;
|
||||
/*
|
||||
* TODO: wSettings needs to reflect the actual amount of configured memory....
|
||||
*/
|
||||
this.wSettings = CompaqController.SETTINGS.DEFAULT;
|
||||
this.wRAMSetup = CompaqController.RAMSETUP.DEFAULT;
|
||||
this.aBlocksDst = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the CompaqController component.
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @return {Array}
|
||||
*/
|
||||
save()
|
||||
{
|
||||
return [this.wMappings, this.wRAMSetup];
|
||||
}
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the CompaqController component.
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
restore(data)
|
||||
{
|
||||
this.setByte(0, data[0] & 0xff);
|
||||
this.setByte(2, data[1] & 0xff);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* getByte(off)
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
getByte(off)
|
||||
{
|
||||
/*
|
||||
* Offsets 0-3 correspond to reads from 0x80C00000-0x80C00003; anything outside that range
|
||||
* returns our standard non-responsive value of 0xff.
|
||||
*/
|
||||
var b = 0xff;
|
||||
if (off < 0x02) {
|
||||
b = (off & 0x1)? (this.wSettings >> 8) : (this.wSettings & 0xff);
|
||||
}
|
||||
else if (off < 0x4) {
|
||||
b = (off & 0x1)? (this.wRAMSetup >> 8) : (this.wRAMSetup & 0xff);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* setByte(off, b)
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {number} off (relative to 0x80C00000)
|
||||
* @param {number} b
|
||||
*/
|
||||
setByte(off, b)
|
||||
{
|
||||
if (!off) {
|
||||
/*
|
||||
* This is a write to 0x80C00000
|
||||
*/
|
||||
if (b != (this.wMappings & 0xff)) {
|
||||
var bus = this.ram.bus;
|
||||
if (!(b & CompaqController.MAPPINGS.UNMAPPED)) {
|
||||
if (!this.aBlocksDst) {
|
||||
this.aBlocksDst = bus.getMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE);
|
||||
}
|
||||
/*
|
||||
* You might think that the next three lines could ALSO be moved to the preceding IF,
|
||||
* but it's possible for the write-protection feature to be enabled/disabled separately
|
||||
* from the mapping feature. We could avoid executing this code as well by checking the
|
||||
* current read-write state, but this is an infrequent operation, so there's no point.
|
||||
*/
|
||||
var aBlocks = bus.getMemoryBlocks(CompaqController.MAP_SRC, CompaqController.MAP_SIZE);
|
||||
var type = (b & CompaqController.MAPPINGS.READWRITE)? Memory.TYPE.RAM : Memory.TYPE.ROM;
|
||||
bus.setMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE, aBlocks, type);
|
||||
}
|
||||
else {
|
||||
if (this.aBlocksDst) {
|
||||
bus.setMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE, this.aBlocksDst);
|
||||
this.aBlocksDst = null;
|
||||
}
|
||||
}
|
||||
this.wMappings = (this.wMappings & ~0xff) | b;
|
||||
}
|
||||
}
|
||||
else if (off == 0x2) {
|
||||
/*
|
||||
* This is a write to 0x80C00002
|
||||
*/
|
||||
this.wRAMSetup = (this.wRAMSetup & ~0xff) | b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getMemoryBuffer(addr)
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {number} addr
|
||||
* @return {Array} containing the buffer (and an offset within that buffer)
|
||||
*/
|
||||
getMemoryBuffer(addr)
|
||||
{
|
||||
return CompaqController.BUFFER;
|
||||
}
|
||||
|
||||
/**
|
||||
* getMemoryAccess()
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @return {Array.<function()>}
|
||||
*/
|
||||
getMemoryAccess()
|
||||
{
|
||||
return CompaqController.ACCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* readByte(off, addr)
|
||||
*
|
||||
* NOTE: Even though we asked bus.addMemory() for only 4 bytes, corresponding to the 4 memory-mapped register
|
||||
* locations we must manage, we're at the mercy of the Bus component's physical block allocation granularity,
|
||||
* which, on 80386-based machines, is fixed at 4K (the same as the 80386 page size, to simplify emulation of paging).
|
||||
*
|
||||
* So we must allow for requests outside that 4-byte range.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off (relative to 0x80C00000)
|
||||
* @param {number} [addr]
|
||||
* @return {number}
|
||||
*/
|
||||
static readByte(off, addr)
|
||||
{
|
||||
var b = this.controller.getByte(off);
|
||||
if (DEBUG) {
|
||||
this.controller.ram.printMessage("CompaqController.readByte(" + Str.toHexWord(off) + ") returned " + Str.toHexByte(b), 0, true);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* writeByte(off, b, addr)
|
||||
*
|
||||
* NOTE: Even though we asked bus.addMemory() for only 4 bytes, corresponding to the 4 memory-mapped register
|
||||
* locations we must manage, we're at the mercy of the Bus component's physical memory allocation granularity,
|
||||
* which, on 80386-based machines, is fixed at 4K (the same as the 80386 page size, to simplify emulation of paging).
|
||||
*
|
||||
* So we must allow for requests outside that 4-byte range.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off (relative to 0x80C00000)
|
||||
* @param {number} b
|
||||
* @param {number} [addr]
|
||||
*/
|
||||
static writeByte(off, b, addr)
|
||||
{
|
||||
this.controller.setByte(off, b);
|
||||
/*
|
||||
* All bits in 0x80C00001 and 0x80C00003 are reserved, so we can simply ignore those writes.
|
||||
*/
|
||||
if (DEBUG) {
|
||||
this.controller.ram.printMessage("CompaqController.writeByte(" + Str.toHexWord(off) + "," + Str.toHexByte(b) + ")", 0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompaqController.ADDR = 0x80C00000|0;
|
||||
|
|
@ -408,181 +592,12 @@ CompaqController.RAMSETUP = {
|
|||
DEFAULT: 0x0002 // our default settings (ie, 2Mb, cache disabled)
|
||||
};
|
||||
|
||||
/**
|
||||
* readByte(off, addr)
|
||||
*
|
||||
* NOTE: Even though we asked bus.addMemory() for only 4 bytes, corresponding to the 4 memory-mapped register
|
||||
* locations we must manage, we're at the mercy of the Bus component's physical block allocation granularity,
|
||||
* which, on 80386-based machines, is fixed at 4K (the same as the 80386 page size, to simplify emulation of paging).
|
||||
*
|
||||
* So we must allow for requests outside that 4-byte range.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off (relative to 0x80C00000)
|
||||
* @param {number} [addr]
|
||||
* @return {number}
|
||||
*/
|
||||
CompaqController.readByte = function readCompaqControllerByte(off, addr)
|
||||
{
|
||||
var b = this.controller.getByte(off);
|
||||
if (DEBUG) {
|
||||
this.controller.ram.printMessage("CompaqController.readByte(" + str.toHexWord(off) + ") returned " + str.toHexByte(b), 0, true);
|
||||
}
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* writeByte(off, b, addr)
|
||||
*
|
||||
* NOTE: Even though we asked bus.addMemory() for only 4 bytes, corresponding to the 4 memory-mapped register
|
||||
* locations we must manage, we're at the mercy of the Bus component's physical memory allocation granularity,
|
||||
* which, on 80386-based machines, is fixed at 4K (the same as the 80386 page size, to simplify emulation of paging).
|
||||
*
|
||||
* So we must allow for requests outside that 4-byte range.
|
||||
*
|
||||
* @this {Memory}
|
||||
* @param {number} off (relative to 0x80C00000)
|
||||
* @param {number} b
|
||||
* @param {number} [addr]
|
||||
*/
|
||||
CompaqController.writeByte = function writeCompaqControllerByte(off, b, addr)
|
||||
{
|
||||
this.controller.setByte(off, b);
|
||||
/*
|
||||
* All bits in 0x80C00001 and 0x80C00003 are reserved, so we can simply ignore those writes.
|
||||
*/
|
||||
if (DEBUG) {
|
||||
this.controller.ram.printMessage("CompaqController.writeByte(" + str.toHexWord(off) + "," + str.toHexByte(b) + ")", 0, true);
|
||||
}
|
||||
};
|
||||
|
||||
CompaqController.BUFFER = [null, 0];
|
||||
CompaqController.ACCESS = [CompaqController.readByte, null, null, CompaqController.writeByte, null, null];
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the CompaqController component.
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @return {Array}
|
||||
*/
|
||||
CompaqController.prototype.save = function()
|
||||
{
|
||||
return [this.wMappings, this.wRAMSetup];
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the CompaqController component.
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
CompaqController.prototype.restore = function(data)
|
||||
{
|
||||
this.setByte(0, data[0] & 0xff);
|
||||
this.setByte(2, data[1] & 0xff);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* getByte(off)
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {number} off
|
||||
* @return {number}
|
||||
*/
|
||||
CompaqController.prototype.getByte = function(off)
|
||||
{
|
||||
/*
|
||||
* Offsets 0-3 correspond to reads from 0x80C00000-0x80C00003; anything outside that range
|
||||
* returns our standard non-responsive value of 0xff.
|
||||
*/
|
||||
var b = 0xff;
|
||||
if (off < 0x02) {
|
||||
b = (off & 0x1)? (this.wSettings >> 8) : (this.wSettings & 0xff);
|
||||
}
|
||||
else if (off < 0x4) {
|
||||
b = (off & 0x1)? (this.wRAMSetup >> 8) : (this.wRAMSetup & 0xff);
|
||||
}
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* setByte(off, b)
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {number} off (relative to 0x80C00000)
|
||||
* @param {number} b
|
||||
*/
|
||||
CompaqController.prototype.setByte = function(off, b)
|
||||
{
|
||||
if (!off) {
|
||||
/*
|
||||
* This is a write to 0x80C00000
|
||||
*/
|
||||
if (b != (this.wMappings & 0xff)) {
|
||||
var bus = this.ram.bus;
|
||||
if (!(b & CompaqController.MAPPINGS.UNMAPPED)) {
|
||||
if (!this.aBlocksDst) {
|
||||
this.aBlocksDst = bus.getMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE);
|
||||
}
|
||||
/*
|
||||
* You might think that the next three lines could ALSO be moved to the preceding IF,
|
||||
* but it's possible for the write-protection feature to be enabled/disabled separately
|
||||
* from the mapping feature. We could avoid executing this code as well by checking the
|
||||
* current read-write state, but this is an infrequent operation, so there's no point.
|
||||
*/
|
||||
var aBlocks = bus.getMemoryBlocks(CompaqController.MAP_SRC, CompaqController.MAP_SIZE);
|
||||
var type = (b & CompaqController.MAPPINGS.READWRITE)? Memory.TYPE.RAM : Memory.TYPE.ROM;
|
||||
bus.setMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE, aBlocks, type);
|
||||
}
|
||||
else {
|
||||
if (this.aBlocksDst) {
|
||||
bus.setMemoryBlocks(CompaqController.MAP_DST, CompaqController.MAP_SIZE, this.aBlocksDst);
|
||||
this.aBlocksDst = null;
|
||||
}
|
||||
}
|
||||
this.wMappings = (this.wMappings & ~0xff) | b;
|
||||
}
|
||||
}
|
||||
else if (off == 0x2) {
|
||||
/*
|
||||
* This is a write to 0x80C00002
|
||||
*/
|
||||
this.wRAMSetup = (this.wRAMSetup & ~0xff) | b;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getMemoryBuffer(addr)
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @param {number} addr
|
||||
* @return {Array} containing the buffer (and an offset within that buffer)
|
||||
*/
|
||||
CompaqController.prototype.getMemoryBuffer = function(addr)
|
||||
{
|
||||
return CompaqController.BUFFER;
|
||||
};
|
||||
|
||||
/**
|
||||
* getMemoryAccess()
|
||||
*
|
||||
* @this {CompaqController}
|
||||
* @return {Array.<function()>}
|
||||
*/
|
||||
CompaqController.prototype.getMemoryAccess = function()
|
||||
{
|
||||
return CompaqController.ACCESS;
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the RAM modules on the page.
|
||||
*/
|
||||
web.onInit(RAM.init);
|
||||
Web.onInit(RAM.init);
|
||||
|
||||
if (NODE) module.exports = RAM;
|
||||
|
|
|
|||
|
|
@ -29,99 +29,375 @@
|
|||
"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 Str = require("../../shared/es6/strlib");
|
||||
var Web = require("../../shared/es6/weblib");
|
||||
var DumpAPI = require("../../shared/es6/dumpapi");
|
||||
var Component = require("../../shared/es6/component");
|
||||
var PCX86 = require("./defines");
|
||||
var Memory = require("./memory");
|
||||
}
|
||||
|
||||
/**
|
||||
* ROM(parmsROM)
|
||||
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
|
||||
* which would force us to declare all class properties in the constructor, as well as prevent
|
||||
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
|
||||
*
|
||||
* 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
|
||||
* notify: ID of a component to notify once the ROM is in place (optional)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsROM
|
||||
* @unrestricted
|
||||
*/
|
||||
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:
|
||||
class ROM extends Component {
|
||||
/**
|
||||
* ROM(parmsROM)
|
||||
*
|
||||
* [0xf0000,0xffff0000,0xffff8000]
|
||||
* The ROM component expects the following (parmsROM) properties:
|
||||
*
|
||||
* 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.
|
||||
* addr: physical address of ROM
|
||||
* size: amount of ROM, in bytes
|
||||
* alias: physical alias address (null if none)
|
||||
* file: name of ROM data file
|
||||
* notify: ID of a component to notify once the ROM is in place (optional)
|
||||
*
|
||||
* Most ROMs are not aliased, in which case the 'alias' property should have the default value of null.
|
||||
* 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.
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {Object} parmsROM
|
||||
*/
|
||||
this.addrAlias = parmsROM['alias'];
|
||||
this.sFilePath = parmsROM['file'];
|
||||
constructor(parmsROM)
|
||||
{
|
||||
super("ROM", parmsROM);
|
||||
|
||||
/*
|
||||
* The 'notify' property can now (as of v1.18.2) contain an array of parameters that the notified
|
||||
* component (typically Video) may use as it sees fit. For example, the Video component is generally
|
||||
* interested in knowing the offsets of specific font tables within the ROM, which used to be hard-coded
|
||||
* when all we supported were a few specific IBM video cards, but that's no longer feasible as we move
|
||||
* beyond the original handful of IBM cards.
|
||||
*
|
||||
* It's up to the notified component to decide how to interpret the parameters it receives, if any.
|
||||
*/
|
||||
this.idNotify = parmsROM['notify'];
|
||||
this.aNotifyParms = null;
|
||||
if (this.idNotify) {
|
||||
var i = this.idNotify.indexOf('[');
|
||||
if (i > 0) {
|
||||
try {
|
||||
this.aNotifyParms = eval(this.idNotify.substr(i));
|
||||
} catch (e) {}
|
||||
this.idNotify = this.idNotify.substr(0, i);
|
||||
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'];
|
||||
|
||||
/*
|
||||
* The 'notify' property can now (as of v1.18.2) contain an array of parameters that the notified
|
||||
* component (typically Video) may use as it sees fit. For example, the Video component is generally
|
||||
* interested in knowing the offsets of specific font tables within the ROM, which used to be hard-coded
|
||||
* when all we supported were a few specific IBM video cards, but that's no longer feasible as we move
|
||||
* beyond the original handful of IBM cards.
|
||||
*
|
||||
* It's up to the notified component to decide how to interpret the parameters it receives, if any.
|
||||
*/
|
||||
this.idNotify = parmsROM['notify'];
|
||||
this.aNotifyParms = null;
|
||||
if (this.idNotify) {
|
||||
var i = this.idNotify.indexOf('[');
|
||||
if (i > 0) {
|
||||
try {
|
||||
this.aNotifyParms = eval(this.idNotify.substr(i));
|
||||
} catch (e) {}
|
||||
this.idNotify = this.idNotify.substr(0, i);
|
||||
}
|
||||
}
|
||||
if (this.sFilePath) {
|
||||
var sFileURL = this.sFilePath;
|
||||
var sFileName = Str.getBaseName(sFileURL);
|
||||
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(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);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.sFilePath) {
|
||||
var sFileURL = this.sFilePath;
|
||||
var sFileName = str.getBaseName(sFileURL);
|
||||
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(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';
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {DebuggerX86} dbg
|
||||
*/
|
||||
initBus(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
|
||||
*/
|
||||
powerUp(data, fRepower)
|
||||
{
|
||||
if (this.aSymbols) {
|
||||
if (this.dbg) {
|
||||
this.dbg.addSymbols(this.id, 0, this.addrROM >>> 4, 0, 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
|
||||
*/
|
||||
powerDown(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)
|
||||
*/
|
||||
doneLoad(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}
|
||||
*/
|
||||
copyROM()
|
||||
{
|
||||
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]);
|
||||
}
|
||||
/*
|
||||
* If there's a component we should notify, notify it now, and give it the internal byte array, so that
|
||||
* it doesn't have to ask the CPU for the data. Currently, the only component that uses this notification
|
||||
* option is the Video component, and only when the associated ROM contains font data that it needs.
|
||||
*/
|
||||
if (this.idNotify) {
|
||||
var component = Component.getComponentByID(this.idNotify, this.id);
|
||||
if (component) {
|
||||
component.onROMLoad(this.abROM, this.aNotifyParms);
|
||||
} else {
|
||||
this.notice("Unable to find component: " + this.idNotify);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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}
|
||||
*/
|
||||
addROM(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 bto = null;
|
||||
for (var off = 0; off < this.abROM.length; off++) {
|
||||
this.bus.setByteDirect(addr + off, this.abROM[off]);
|
||||
if (BACKTRACK) {
|
||||
bto = this.bus.addBackTrackObject(this, bto, off);
|
||||
this.bus.writeBackTrackObject(addr + off, bto, off);
|
||||
}
|
||||
}
|
||||
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
|
||||
*/
|
||||
cloneROM(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.
|
||||
*/
|
||||
static init()
|
||||
{
|
||||
var aeROM = Component.getElementsByClass(document, PCX86.APPCLASS, "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, PCX86.APPCLASS);
|
||||
}
|
||||
var rom = this;
|
||||
web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
|
||||
rom.doneLoad(sURL, sResponse, nErrorCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Component.subclass(ROM);
|
||||
|
||||
/*
|
||||
* ROM BIOS Data Area (RBDA) definitions, in physical address form, using the same ALL-CAPS names
|
||||
* found in the original IBM PC ROM BIOS listing. TODO: Fill in remaining RBDA holes.
|
||||
|
|
@ -152,279 +428,9 @@ ROM.BIOS.RESET_FLAG_WARMBOOT = 0x1234; // value stored at ROM.BIOS.RESET_FLAG t
|
|||
* via bus.addMemory().
|
||||
*/
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {ROM}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {X86CPU} cpu
|
||||
* @param {DebuggerX86} 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, 0, this.addrROM >>> 4, 0, 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]);
|
||||
}
|
||||
/*
|
||||
* If there's a component we should notify, notify it now, and give it the internal byte array, so that
|
||||
* it doesn't have to ask the CPU for the data. Currently, the only component that uses this notification
|
||||
* option is the Video component, and only when the associated ROM contains font data that it needs.
|
||||
*/
|
||||
if (this.idNotify) {
|
||||
var component = Component.getComponentByID(this.idNotify, this.id);
|
||||
if (component) {
|
||||
component.onROMLoad(this.abROM, this.aNotifyParms);
|
||||
} else {
|
||||
this.notice("Unable to find component: " + this.idNotify);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 bto = null;
|
||||
for (var off = 0; off < this.abROM.length; off++) {
|
||||
this.bus.setByteDirect(addr + off, this.abROM[off]);
|
||||
if (BACKTRACK) {
|
||||
bto = this.bus.addBackTrackObject(this, bto, off);
|
||||
this.bus.writeBackTrackObject(addr + off, bto, off);
|
||||
}
|
||||
}
|
||||
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, PCX86.APPCLASS, "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, PCX86.APPCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize all the ROM modules on the page.
|
||||
*/
|
||||
web.onInit(ROM.init);
|
||||
Web.onInit(ROM.init);
|
||||
|
||||
if (NODE) module.exports = ROM;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -29,7 +29,6 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Messages = require("./messages");
|
||||
var X86 = require("./x86");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var Messages = require("./messages");
|
||||
var X86 = require("./x86");
|
||||
}
|
||||
|
|
@ -968,7 +968,7 @@ X86.helpCheckFault = function(nFault, nError, fHalt)
|
|||
if (this.messageEnabled(bitsMessage) || fHalt) {
|
||||
|
||||
var fRunning = this.flags.running;
|
||||
var sMessage = "Fault " + str.toHexByte(nFault) + (nError != null? " (" + str.toHexWord(nError) + ")" : "") + " on opcode " + str.toHexByte(bOpcode);
|
||||
var sMessage = "Fault " + Str.toHexByte(nFault) + (nError != null? " (" + Str.toHexWord(nError) + ")" : "") + " on opcode " + Str.toHexByte(bOpcode);
|
||||
if (fHalt && fRunning) sMessage += " (blocked)";
|
||||
|
||||
if (DEBUGGER && this.dbg) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var X86 = require("./x86");
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ X86.modRegByte16 = function(fn)
|
|||
break;
|
||||
default:
|
||||
src = 0;
|
||||
this.assert(false, "modRegByte16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modRegByte16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -404,7 +404,7 @@ X86.modMemByte16 = function(fn)
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modMemByte16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemByte16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -529,7 +529,7 @@ X86.modMemByte16 = function(fn)
|
|||
if (BACKTRACK) this.backTrack.btiBH = this.backTrack.btiEALo;
|
||||
break;
|
||||
default:
|
||||
this.assert(false, "modMemByte16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemByte16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
@ -668,7 +668,7 @@ X86.modGrpByte16 = function(afnGrp, fnSrc) {
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modGrpByte16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modGrpByte16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -907,7 +907,7 @@ X86.modRegShort16 = function(fn)
|
|||
break;
|
||||
default:
|
||||
src = 0;
|
||||
this.assert(false, "modRegShort16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modRegShort16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1128,7 +1128,7 @@ X86.modMemShort16 = function(fn)
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modMemShort16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemShort16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1282,7 +1282,7 @@ X86.modMemShort16 = function(fn)
|
|||
}
|
||||
break;
|
||||
default:
|
||||
this.assert(false, "modMemShort16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemShort16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
@ -1421,7 +1421,7 @@ X86.modGrpShort16 = function(afnGrp, fnSrc) {
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modGrpShort16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modGrpShort16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1660,7 +1660,7 @@ X86.modRegLong16 = function(fn)
|
|||
break;
|
||||
default:
|
||||
src = 0;
|
||||
this.assert(false, "modRegLong16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modRegLong16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1881,7 +1881,7 @@ X86.modMemLong16 = function(fn)
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modMemLong16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemLong16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2035,7 +2035,7 @@ X86.modMemLong16 = function(fn)
|
|||
}
|
||||
break;
|
||||
default:
|
||||
this.assert(false, "modMemLong16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemLong16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
@ -2173,7 +2173,7 @@ X86.modGrpLong16 = function(afnGrp, fnSrc) {
|
|||
dst = this.regEDI;
|
||||
break;
|
||||
default:
|
||||
this.assert(false, "modGrpLong16(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modGrpLong16(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2372,7 +2372,7 @@ X86.modRegByte32 = function(fn)
|
|||
break;
|
||||
default:
|
||||
src = 0;
|
||||
this.assert(false, "modRegByte32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modRegByte32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2580,7 +2580,7 @@ X86.modMemByte32 = function(fn)
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modMemByte32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemByte32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2799,7 +2799,7 @@ X86.modGrpByte32 = function(afnGrp, fnSrc) {
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modGrpByte32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modGrpByte32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2972,7 +2972,7 @@ X86.modRegShort32 = function(fn)
|
|||
break;
|
||||
default:
|
||||
src = 0;
|
||||
this.assert(false, "modRegShort32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modRegShort32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -3193,7 +3193,7 @@ X86.modMemShort32 = function(fn)
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modMemShort32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemShort32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -3441,7 +3441,7 @@ X86.modGrpShort32 = function(afnGrp, fnSrc) {
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modGrpShort32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modGrpShort32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -3614,7 +3614,7 @@ X86.modRegLong32 = function(fn)
|
|||
break;
|
||||
default:
|
||||
src = 0;
|
||||
this.assert(false, "modRegLong32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modRegLong32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -3835,7 +3835,7 @@ X86.modMemLong32 = function(fn)
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modMemLong32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modMemLong32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -4083,7 +4083,7 @@ X86.modGrpLong32 = function(afnGrp, fnSrc) {
|
|||
break;
|
||||
default:
|
||||
dst = 0;
|
||||
this.assert(false, "modGrpLong32(): unrecognized modrm byte " + str.toHexByte(bModRM));
|
||||
this.assert(false, "modGrpLong32(): unrecognized modrm byte " + Str.toHexByte(bModRM));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
"use strict";
|
||||
|
||||
if (NODE) {
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var Str = require("../../shared/es6/strlib");
|
||||
var Messages = require("./messages");
|
||||
var X86 = require("./x86");
|
||||
}
|
||||
|
|
@ -3564,7 +3564,7 @@ X86.opINTn = function()
|
|||
* TODO: Consider swapping out this function whenever setProtMode() changes the mode to V86-mode.
|
||||
*/
|
||||
if (I386 && (this.regPS & X86.PS.VM) && this.nIOPL < 3) {
|
||||
if (DEBUG && this.messageEnabled()) this.printMessage("INT " + str.toHexByte(nInt) + " in v86-mode (IOPL < 3)", true, true);
|
||||
if (DEBUG && this.messageEnabled()) this.printMessage("INT " + Str.toHexByte(nInt) + " in v86-mode (IOPL < 3)", true, true);
|
||||
X86.helpFault.call(this, X86.EXCEPTION.GP_FAULT, 0);
|
||||
return;
|
||||
}
|
||||
|
|
@ -4415,7 +4415,7 @@ X86.opInvalid = function()
|
|||
X86.opUndefined = function()
|
||||
{
|
||||
this.setIP(this.opLIP - this.segCS.base);
|
||||
this.setError("Undefined opcode " + str.toHexByte(this.getByte(this.regLIP)) + " at " + str.toHexLong(this.regLIP));
|
||||
this.setError("Undefined opcode " + Str.toHexByte(this.getByte(this.regLIP)) + " at " + Str.toHexLong(this.regLIP));
|
||||
this.stopCPU();
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -309,4 +309,11 @@ Usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
|
|||
Usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
Usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
/**
|
||||
* getTime()
|
||||
*
|
||||
* @return {number} the current time, in milliseconds
|
||||
*/
|
||||
Usr.getTime = Date.now || function() { return +new Date(); };
|
||||
|
||||
if (NODE) module.exports = Usr;
|
||||
|
|
|
|||
Loading…
Reference in a new issue