Fixed assorted EGA and 5170 save/restore/reset problems

This commit is contained in:
Jeff Parsons 2014-10-25 22:49:23 -07:00 committed by jeffpar
commit 95673181b7
12 changed files with 1160 additions and 1010 deletions

View file

@ -190,6 +190,29 @@ Bus.prototype.reset = function()
this.setA20(true);
};
/**
* powerUp(data, fRepower)
*
* We don't need a powerDown() handler, because for largely historical reasons, our state (including the A20 state)
* is saved by saveMemory().
*
* However, we do need a powerUp() handler, because on resumable machines, the Computer's onReset() function calls
* everyone's powerUp() handler rather than their reset() handler.
*
* TODO: Perhaps Computer should be smarter: if there's no powerUp() handler, then fallback to the reset() handler.
* In that case, however, we'd either need to remove the powerUp() stub in Component, or detect the existence of the stub.
*
* @this {Bus}
* @param {Object|null} data (always null because we supply no powerDown() handler)
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
Bus.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) this.reset();
return true;
};
/**
* addMemory(addr, size, fReadOnly, controller)
*

View file

@ -243,7 +243,7 @@ function ChipSet(parmsChipSet)
* needs to be created earlier, so that when other components are initializing their state (eg, when
* HDC calls setCMOSDriveType() or RAM calls addCMOSMemory()), the CMOS will be ready to take their calls.
*/
this.reset();
this.reset(true);
this.setReady();
}
@ -979,7 +979,7 @@ ChipSet.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data) {
this.reset(true);
this.reset();
} else {
if (!this.restore(data)) return false;
}
@ -1000,12 +1000,12 @@ ChipSet.prototype.powerDown = function(fSave)
};
/**
* reset(fSoft)
* reset(fHard)
*
* @this {ChipSet}
* @param {boolean} [fSoft] is true if "soft" reset, otherwise "hard" reset (see below for details)
* @param {boolean} [fHard] true if a machine reset (not just a soft reset)
*/
ChipSet.prototype.reset = function(fSoft)
ChipSet.prototype.reset = function(fHard)
{
/*
* We propagate the sw1Init/sw2Init values to sw1/sw2 at reset; the user is only
@ -1085,7 +1085,7 @@ ChipSet.prototype.reset = function(fSoft)
* and any later ("soft") resets (eg, from powerUp() calls), and make sure the latter preserves
* existing CMOS information.
*/
if (!fSoft) this.abCMOSData = new Array(ChipSet.CMOS.ADDR.TOTAL);
if (fHard) this.abCMOSData = new Array(ChipSet.CMOS.ADDR.TOTAL);
this.initRTCDate(this.sRTCDate);
@ -1340,11 +1340,20 @@ ChipSet.prototype.updateRTCDate = function()
*/
ChipSet.prototype.initCMOSData = function()
{
/*
* On all reset() calls, the RAM component(s) will (re)add their totals, so we have to make sure that
* the addition always starts with 0. That also means that ChipSet must always be initialized before RAM.
*/
var iCMOS;
for (iCMOS = ChipSet.CMOS.ADDR.BASEMEM_LO; iCMOS <= ChipSet.CMOS.ADDR.EXTMEM_HI; iCMOS++) {
this.abCMOSData[iCMOS] = 0;
}
/*
* Make sure all the "checksummed" CMOS bytes are initialized (not just the handful we set below) to ensure
* that the checksum will be valid.
*/
for (var iCMOS = ChipSet.CMOS.ADDR.DIAG; iCMOS < ChipSet.CMOS.ADDR.CHKSUM_HI; iCMOS++) {
for (iCMOS = ChipSet.CMOS.ADDR.DIAG; iCMOS < ChipSet.CMOS.ADDR.CHKSUM_HI; iCMOS++) {
if (this.abCMOSData[iCMOS] === undefined) this.abCMOSData[iCMOS] = 0;
}

View file

@ -174,7 +174,7 @@ function Computer(parmsComputer, parmsMachine, fSuspended) {
}
}
if (DEBUG) this.println("PREFETCH: " + PREFETCH + ", TYPEDARRAYS: " + TYPEDARRAYS);
if (DEBUG) this.messageDebugger("PREFETCH: " + PREFETCH + ", TYPEDARRAYS: " + TYPEDARRAYS);
/*
* Iterate through all the components again and call their initBus() handler, if any
@ -368,7 +368,7 @@ Computer.prototype.wait = function(fn, parms)
return;
}
}
if (MAXDEBUG) this.println("Computer.wait(ready)");
if (DEBUG) this.messageDebugger("Computer.wait(ready)");
fn.call(this, parms);
};
@ -393,7 +393,7 @@ Computer.prototype.validateState = function(stateComputer)
fValid = false;
if (!stateComputer) stateValidate.clear();
} else {
if (MAXDEBUG) this.println("Last state: " + sTimestampComputer + " (validate: " + sTimestampValidate + ")");
if (DEBUG) this.messageDebugger("Last state: " + sTimestampComputer + " (validate: " + sTimestampValidate + ")");
}
}
return fValid;
@ -410,10 +410,10 @@ Computer.prototype.validateState = function(stateComputer)
Computer.prototype.powerOn = function(resume)
{
if (resume === undefined) {
resume = (this.sStateData? Computer.RESUME_AUTO : this.resume);
resume = this.resume || (this.sStateData? Computer.RESUME_AUTO : Computer.RESUME_NONE);
}
if (MAXDEBUG) this.println("Computer.powerOn(" + (resume == Computer.RESUME_REPOWER ? "repower" : (resume ? "resume" : "")) + ")");
if (DEBUG) this.messageDebugger("Computer.powerOn(" + (resume == Computer.RESUME_REPOWER ? "repower" : (resume ? "resume" : "")) + ")");
var fRepower = false;
var fRestore = false;
@ -435,17 +435,23 @@ Computer.prototype.powerOn = function(resume)
if (this.stateFailSafe.load()) {
this.powerReport(stateComputer);
/*
* We already know resume is something other then RESUME_NONE, so we'll go ahead and bump it all the way to
* RESUME_PROMPT, so that the user will be prompted, and if the user declines to restore, the state will be removed.
* We already know resume is something other than RESUME_NONE, so we'll go ahead and bump it
* all the way to RESUME_PROMPT, so that the user will be prompted, and if the user declines to
* restore, the state will be removed.
*/
resume = Computer.RESUME_PROMPT;
/*
* To ensure that the set() below succeeds, we need to call unload(), otherwise it may fail
* with a "read only" error (eg, "TypeError: Cannot assign to read only property 'timestamp'").
*/
this.stateFailSafe.unload();
}
this.stateFailSafe.set(Computer.STATE_TIMESTAMP, usr.getTimestamp());
this.stateFailSafe.store();
var fValidate = this.resume && !this.fServerState;
if (resume == Computer.RESUME_AUTO || web.confirmUser("Click OK to restore previous " + Computer.sAppName + " machine state.")) {
if (resume == Computer.RESUME_AUTO || web.confirmUser("Click OK to restore the previous " + Computer.sAppName + " machine state, or CANCEL to reset the machine.")) {
fRestore = stateComputer.parse();
if (fRestore) {
var sCode = stateComputer.get(UserAPI.RES.CODE);
@ -521,10 +527,10 @@ Computer.prototype.powerOn = function(resume)
var aParms = [stateComputer, resume, fRestore];
if (resume != Computer.RESUME_REPOWER) {
this.wait(this.powerFinish, aParms);
this.wait(this.donePowerOn, aParms);
return;
}
this.powerFinish(aParms);
this.donePowerOn(aParms);
};
/**
@ -540,8 +546,11 @@ Computer.prototype.powerOn = function(resume)
Computer.prototype.powerRestore = function(component, stateComputer, fRepower, fRestore)
{
if (!component.fPowered) {
component.fPowered = true;
if (component.powerUp) {
var data = null;
if (fRestore) {
data = stateComputer.get(component.id);
@ -559,6 +568,7 @@ Computer.prototype.powerRestore = function(component, stateComputer, fRepower, f
data = stateComputer.get(component.id.replace(/[a-z0-9]\./i, '.'));
}
}
/*
* State.get() will return whatever was originally passed to State.set() (eg, an
* Object or a string), but components are supposed to store only Objects, so if a
@ -571,50 +581,51 @@ Computer.prototype.powerRestore = function(component, stateComputer, fRepower, f
* would be a good idea, but this is overkill.
*/
if (typeof data === "string") data = null;
/*
* If computer is null, this is simply a repower notification, which most components
* don't do anything with. Exceptions include: CPU (since it may be halted) and Video
* (since its screen may be "turned off").
*/
if (!component.powerUp(data, fRepower)) {
if (data) {
Component.error("Unable to restore state for " + component.type);
if (!component.powerUp(data, fRepower) && data) {
Component.error("Unable to restore state for " + component.type);
/*
* If this is a resume error for a machine that also has a predefined state
* AND we're not restoring from that state, then throw away the current state,
* prevent any new state from being created, and then force a reload, which will
* hopefully restore us to the functioning predefined state.
*
* TODO: Considering doing this in ALL cases, not just in situations where a
* 'state' exists but we're not actually resuming from it.
*/
if (this.sStatePath && !this.sStateData) {
stateComputer.clear();
this.resume = Computer.RESUME_NONE;
web.reloadPage();
} else {
/*
* If this is a resume error for a machine that also has a predefined state
* AND we're not restoring from that state, then throw away the current state,
* prevent any new state from being created, and then force a reload, which will
* hopefully restore us to the functioning predefined state.
*
* TODO: Considering doing this in ALL cases, not just in situations where a
* 'state' exists but we're not actually resuming from it.
* In all other cases, we set fRestoreError, which should trigger a call to
* powerReport() and then delete the offending state.
*/
if (this.sStatePath && !this.sStateData) {
stateComputer.clear();
this.resume = Computer.RESUME_NONE;
web.reloadPage();
} else {
/*
* In all other cases, we set fRestoreError, which should trigger a call to
* powerReport() and then delete the offending state.
*/
this.fRestoreError = true;
}
/*
* Any failure triggers an automatic to call powerUp() again, without any state,
* in the hopes that the component can recover by performing a reset.
*/
component.powerUp(null);
/*
* We also disable the rest of the restore operation, because it's not clear
* the remaining state information can be trusted; the machine is already in an
* inconsistent state, so we're not likely to make things worse, and the only
* alternative (starting over and performing a state-less reset) isn't likely to make
* the user any happier. But, we'll see... we need some experience with the code.
*/
fRestore = false;
this.fRestoreError = true;
}
/*
* Any failure triggers an automatic to call powerUp() again, without any state,
* in the hopes that the component can recover by performing a reset.
*/
component.powerUp(null);
/*
* We also disable the rest of the restore operation, because it's not clear
* the remaining state information can be trusted; the machine is already in an
* inconsistent state, so we're not likely to make things worse, and the only
* alternative (starting over and performing a state-less reset) isn't likely to make
* the user any happier. But, we'll see... we need some experience with the code.
*/
fRestore = false;
}
}
if (!fRepower && component.comment) {
var asComments = component.comment.split("|");
for (var i = 0; i < asComments.length; i++) {
@ -626,20 +637,20 @@ Computer.prototype.powerRestore = function(component, stateComputer, fRepower, f
};
/**
* powerFinish(aParms)
* donePowerOn(aParms)
*
* This is nothing more than a continuation of powerOn(), giving us the option of calling wait() one more time.
*
* @this {Computer}
* @param {Array} aParms containing [stateComputer, resume, fRestore]
*/
Computer.prototype.powerFinish = function(aParms)
Computer.prototype.donePowerOn = function(aParms)
{
var stateComputer = aParms[0];
var fRepower = (aParms[1] < 0);
var fRestore = aParms[2];
if (DEBUG && this.fPowered) this.println("Computer.powerFinish(): redundant");
if (DEBUG && this.fPowered) this.messageDebugger("Computer.donePowerOn(): redundant");
this.fPowered = true;
@ -682,7 +693,7 @@ Computer.prototype.powerFinish = function(aParms)
*/
Computer.prototype.powerReport = function(stateComputer)
{
if (web.confirmUser("There may be a problem with " + Computer.sAppName + ".\nClick OK to send your " + Computer.sAppName + " machine state to http://" + SITEHOST + ".")) {
if (web.confirmUser("There may be a problem with your " + Computer.sAppName + " machine.\n\nTo help us diagnose it, click OK to send this " + Computer.sAppName + " machine state to http://" + SITEHOST + ".")) {
web.sendReport(Computer.sAppName, Computer.sAppVer, this.url, this.getUserID(), ReportAPI.TYPE.BUG, stateComputer.toString());
}
};
@ -723,7 +734,7 @@ Computer.prototype.powerOff = function(fSave, fShutdown)
var data;
var sState = "none";
if (MAXDEBUG) this.println("Computer.powerOff(" + (fSave ? "save" : "nosave") + (fShutdown ? ",shutdown" : "") + ")");
if (DEBUG) this.messageDebugger("Computer.powerOff(" + (fSave ? "save" : "nosave") + (fShutdown ? ",shutdown" : "") + ")");
var stateComputer = new State(this, Computer.sAppVer);
var stateValidate = new State(this, Computer.sAppVer, Computer.STATE_VALIDATE);
@ -976,7 +987,7 @@ Computer.prototype.queryUserID = function(fPrompt)
Computer.prototype.verifyUserID = function(sUserID)
{
this.sUserID = null;
if (DEBUG) this.log("verifyUserID(" + sUserID + ")");
if (DEBUG) this.messageDebugger("verifyUserID(" + sUserID + ")");
var sRequest = web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUserID;
var response = web.loadResource(sRequest);
var nErrorCode = response[0];
@ -986,16 +997,16 @@ Computer.prototype.verifyUserID = function(sUserID)
response = eval("(" + sResponse + ")");
if (response.code && response.code == UserAPI.CODE.OK) {
web.setLocalStorageItem(Computer.STATE_USERID, response.data);
if (DEBUG) this.println(Computer.STATE_USERID + " updated: " + response.data);
if (DEBUG) this.messageDebugger(Computer.STATE_USERID + " updated: " + response.data);
this.sUserID = response.data;
} else {
if (DEBUG) this.println(response.code + ": " + response.data);
if (DEBUG) this.messageDebugger(response.code + ": " + response.data);
}
} catch (e) {
Component.error(e.message + " (" + sResponse + ")");
}
} else {
if (DEBUG) this.println("invalid response (error " + nErrorCode + ")");
if (DEBUG) this.messageDebugger("invalid response (error " + nErrorCode + ")");
}
return this.sUserID;
};
@ -1010,10 +1021,10 @@ Computer.prototype.getServerStatePath = function()
{
var sStatePath = null;
if (this.sUserID) {
if (MAXDEBUG) this.println(Computer.STATE_USERID + " for load: " + this.sUserID);
if (DEBUG) this.messageDebugger(Computer.STATE_USERID + " for load: " + this.sUserID);
sStatePath = web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.LOAD + '&' + UserAPI.QUERY.USER + '=' + this.sUserID + '&' + UserAPI.QUERY.STATE + '=' + State.key(this, Computer.sAppVer);
} else {
if (MAXDEBUG) this.println(Computer.STATE_USERID + " unavailable");
if (DEBUG) this.messageDebugger(Computer.STATE_USERID + " unavailable");
}
return sStatePath;
};
@ -1033,7 +1044,7 @@ Computer.prototype.saveServerState = function(sUserID, sState)
* tend to blow off alerts() and the like when closing down.
*/
if (sState) {
if (DEBUG) this.println("size of server state: " + sState.length + " bytes");
if (DEBUG) this.messageDebugger("size of server state: " + sState.length + " bytes");
var response = this.storeServerState(sUserID, sState, true);
if (response && response[UserAPI.RES.CODE] == UserAPI.CODE.OK) {
this.notice("Machine state saved to server");
@ -1048,7 +1059,7 @@ Computer.prototype.saveServerState = function(sUserID, sState)
this.resetUserID();
}
} else {
if (DEBUG) this.println("no state to store");
if (DEBUG) this.messageDebugger("no state to store");
}
};
@ -1063,7 +1074,7 @@ Computer.prototype.saveServerState = function(sUserID, sState)
*/
Computer.prototype.storeServerState = function(sUserID, sState, fSync)
{
if (MAXDEBUG) this.println(Computer.STATE_USERID + " for store: " + sUserID);
if (DEBUG) this.messageDebugger(Computer.STATE_USERID + " for store: " + sUserID);
/*
* TODO: Determine whether or not any browsers cancel our request if we're called during a browser "shutdown" event,
* and whether or not it matters if we do an async request (currently, we're not, to try to ensure the request goes through).
@ -1087,7 +1098,7 @@ Computer.prototype.storeServerState = function(sUserID, sState, fSync)
}
sResponse = '{"' + UserAPI.RES.CODE + '":' + response[0] + ',"' + UserAPI.RES.DATA + '":"' + sResponse + '"}';
}
if (MAXDEBUG) this.println(sResponse);
if (DEBUG) this.messageDebugger(sResponse);
return JSON.parse(sResponse);
}
return null;
@ -1110,7 +1121,7 @@ Computer.prototype.onReset = function()
* wants to clutter the UI with confusing options. ;-)
*/
if (this.resume && !this.sResumePath) {
var fSave = (this.resume == Computer.RESUME_AUTO || !web.confirmUser("Click OK to reset the " + Computer.sAppName + " machine state.\n(WARNING: All disk changes will be discarded)"));
var fSave = (this.resume == Computer.RESUME_AUTO || !web.confirmUser("Click OK to save the " + Computer.sAppName + " machine state.\n\nWARNING: If you CANCEL, all disk changes will be discarded."));
this.powerOff(fSave, true);
/*
* Forcing the page to reload is an expedient option, but ugly. It's preferable to call powerOn()

View file

@ -319,7 +319,11 @@ CPU.prototype.setFocus = function()
*/
CPU.prototype.isPowered = function()
{
return this.fPowered;
if (!this.fPowered) {
this.println(this.toString() + " not powered");
return false;
}
return true;
};
/**

View file

@ -234,6 +234,12 @@ function Disk(controller, drive, mode)
{
Component.call(this, "Disk", {'id': controller.idMachine + ".disk" + Disk.nDisks++}, Disk);
/*
* Route all non-Debugger messages (eg, notice() and println() calls) through
* this.controller (eg, controller.notice() and controller.println()), because
* the Computer component is unaware of any Disk objects and therefore will not
* set up the usual overrides when a Control Panel is installed.
*/
this.controller = controller;
this.cmp = controller.cmp;
this.dbg = controller.dbg;
@ -310,6 +316,20 @@ Disk.prototype.initBus = function(cmp, bus, cpu, dbg) {
this.dbg = dbg;
};
/**
* isRemote()
*
* @this {Disk}
* @return {boolean} true if remote disk, false if not
*/
Disk.prototype.isRemote = function() {
/*
* Ironically, we can't rely on fRemote, because that is cleared and set across disconnect and
* reconnect operations. fOnDemand is the next best thing.
*/
return this.fOnDemand;
};
/**
* powerUp(data, fRepower)
*
@ -318,7 +338,7 @@ Disk.prototype.initBus = function(cmp, bus, cpu, dbg) {
*
* The HDC component could have triggered this as well, but its powerUp() function only calls autoMount()
* in case of page (ie, application) reload, which is fine for local disks but insufficient for remote disks,
* which have a server connection that must re-established.
* which have a server connection that must be re-established.
*
* @this {Disk}
* @param {Object|null} data
@ -328,12 +348,29 @@ Disk.prototype.initBus = function(cmp, bus, cpu, dbg) {
Disk.prototype.powerUp = function(data, fRepower) {
if (!fRepower) {
if (this.fOnDemand && !this.fRemote) {
this.load(this.sDiskName, this.sDiskPath);
this.setReady(false);
this.load(this.sDiskName, this.sDiskPath, this.donePowerUp, this);
}
}
return true;
};
/**
* donePowerUp(drive, disk, sDiskName, sDiskPath)
*
* This is a callback issued by the Disk component once the load() from powerUp() has finished.
*
* @this {HDC}
* @param {Object} drive
* @param {Disk} disk is set if the disk was successfully mounted, null if not
* @param {string} sDiskName
* @param {string} sDiskPath
*/
Disk.prototype.donePowerUp = function(drive, disk, sDiskName, sDiskPath)
{
this.setReady(true);
};
/**
* powerDown(fSave, fShutdown)
*
@ -371,14 +408,14 @@ Disk.prototype.powerDown = function(fSave, fShutdown)
}
while ((response = this.findDirtySectors(false))) {
if ((nErrorCode = response[0])) {
this.notice('Unable to save "' + this.sDiskName + '" (error ' + nErrorCode + ')');
this.controller.notice('Unable to save "' + this.sDiskName + '" (error ' + nErrorCode + ')');
break;
}
}
if (fShutdown) {
this.disconnectRemoteDisk();
}
if (!nErrorCode) this.notice(this.sDiskName + " saved");
if (!nErrorCode) this.controller.notice(this.sDiskName + " saved");
}
return true;
};
@ -424,7 +461,7 @@ Disk.prototype.create = function()
};
/**
* load(sDiskName, sDiskPath, fnNotify)
* load(sDiskName, sDiskPath, fnNotify, controller)
*
* TODO: Figure out how we can strongly type fnNotify, because the Closure Compiler has issues with:
*
@ -439,13 +476,14 @@ Disk.prototype.create = function()
* @param {string} sDiskName
* @param {string} sDiskPath
* @param {function(...)} [fnNotify]
* @param {Component} [controller]
*/
Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify)
Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
{
var sDiskURL = sDiskPath;
/*
* We could use this.log() as well, but it wouldn't also log which component initiated the load.
* We could use this.log() as well, but it wouldn't display which component initiated the load.
*/
if (DEBUG) {
var sMessage = 'Disk.load("' + sDiskName + '","' + sDiskPath + '")';
@ -453,9 +491,17 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify)
this.messageDebugger(sMessage);
}
Component.assert(!this.fnNotify);
if (this.fnNotify) {
if (DEBUG) this.controller.log('too many load requests for "' + sDiskName + '" (' + sDiskPath + ')');
return;
}
this.sDiskName = sDiskName;
this.sDiskPath = sDiskPath;
this.fnNotify = fnNotify;
this.controllerNotify = controller || this.controller;
/*
* If there's an occurrence of API_ENDPOINT anywhere in the path, we assume we can use it as-is;
@ -508,11 +554,11 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify)
}
}
}
web.loadResource(sDiskURL, true, null, this, this.onLoadDisk, sDiskPath);
web.loadResource(sDiskURL, true, null, this, this.doneLoad, sDiskPath);
};
/**
* onLoadDisk(sDiskFile, sDiskData, nErrorCode, sDiskPath)
* doneLoad(sDiskFile, sDiskData, nErrorCode, sDiskPath)
*
* This function was originally called mount(). If the mount is successful, we pass the Disk object to the
* caller's fnNotify handler; otherwise, we pass null.
@ -523,7 +569,7 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify)
* @param {number} nErrorCode (response from server if anything other than 200)
* @param {string} sDiskPath (passed through from load() to loadResource())
*/
Disk.prototype.onLoadDisk = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
{
var disk = null;
this.fWriteProtected = false;
@ -531,24 +577,24 @@ Disk.prototype.onLoadDisk = function(sDiskFile, sDiskData, nErrorCode, sDiskPath
if (this.fOnDemand) {
if (!nErrorCode) {
if (DEBUG) this.messageDebugger('Disk.onLoadDisk("' + sDiskFile + '","' + sDiskPath + '")');
if (DEBUG) this.messageDebugger('Disk.doneLoad("' + sDiskFile + '","' + sDiskPath + '")');
this.fRemote = true;
disk = this;
} else {
this.notice('Unable to connect to disk "' + sDiskPath + '" (error ' + nErrorCode + ': ' + sDiskData + ')', fPrintOnly);
this.controller.notice('Unable to connect to disk "' + sDiskPath + '" (error ' + nErrorCode + ': ' + sDiskData + ')', fPrintOnly);
}
}
else if (nErrorCode) {
/*
* This can happen for innocuous reasons, such as the user switching away too quickly,
* forcing the request to be cancelled. And unfortunately, the browser cancels XMLHttpRequest
* requests BEFORE it notifies any page event handlers, so if the Computer's being powered down,
* we won't know that yet. For now, we rely on the lack of a specific error (nErrorCode < 0), and
* suppress the notify() alert if there's no specific error AND the computer is not powered up yet.
* This can happen for innocuous reasons, such as the user switching away too quickly, forcing
* the request to be cancelled. And unfortunately, the browser cancels XMLHttpRequest requests
* BEFORE it notifies any page event handlers, so if the Computer's being powered down, we won't know
* that yet. For now, we rely on the lack of a specific error (nErrorCode < 0), and suppress the
* notify() alert if there's no specific error AND the computer is not powered up yet.
*/
this.notice("Unable to load disk \"" + this.sDiskName + "\" (error " + nErrorCode + ")", fPrintOnly);
this.controller.notice("Unable to load disk \"" + this.sDiskName + "\" (error " + nErrorCode + ")", fPrintOnly);
} else {
if (DEBUG) this.messageDebugger('Disk.onLoadDisk("' + sDiskFile + '","' + sDiskPath + '")');
if (DEBUG) this.messageDebugger('Disk.doneLoad("' + sDiskFile + '","' + sDiskPath + '")');
try {
/*
* The following code was a hack to turn on write-protection for a disk image if there was
@ -735,8 +781,10 @@ Disk.prototype.onLoadDisk = function(sDiskFile, sDiskData, nErrorCode, sDiskPath
Component.error("Disk image error: " + e.message);
}
}
if (this.fnNotify) {
this.fnNotify.call(this.controller, this.drive, disk, this.sDiskName, this.sDiskPath);
this.fnNotify.call(this.controllerNotify, this.drive, disk, this.sDiskName, this.sDiskPath);
this.fnNotify = null;
}
};
@ -1462,7 +1510,7 @@ Disk.prototype.restore = function(deltas)
}
}
if (nChanges < 0) {
this.notice("unable to restore disk '" + this.sDiskName + ": " + sReason);
this.controller.notice("unable to restore disk '" + this.sDiskName + ": " + sReason);
} else {
if (DEBUG) this.messageDebugger('Disk.restore("' + this.sDiskName + '"): restored ' + nChanges + ' change(s)');
}

View file

@ -1186,14 +1186,14 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
this.messageDebugger("loading diskette '" + sDisketteName + "'");
}
var disk = new Disk(this, drive, DiskAPI.MODE.PRELOAD);
disk.load(sDisketteName, sDiskettePath, this.mountDiskette);
disk.load(sDisketteName, sDiskettePath, this.doneLoadDiskette);
return false;
}
return true;
};
/**
* mountDiskette(drive, disk, sDisketteName, sDiskettePath)
* doneLoadDiskette(drive, disk, sDisketteName, sDiskettePath)
*
* @this {FDC}
* @param {Object} drive
@ -1201,7 +1201,7 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
* @param {string} sDisketteName
* @param {string} sDiskettePath
*/
FDC.prototype.mountDiskette = function(drive, disk, sDisketteName, sDiskettePath)
FDC.prototype.doneLoadDiskette = function onFDCLoadNotify(drive, disk, sDisketteName, sDiskettePath)
{
var aDiskInfo;

View file

@ -651,14 +651,14 @@ HDC.prototype.restore = function(data)
};
/**
* initController(data, fReset)
* initController(data, fHard)
*
* @this {HDC}
* @param {Array} [data]
* @param {boolean} [fReset] true if a machine reset (not just a controller reset)
* @param {boolean} [fHard] true if a machine reset (not just a controller reset)
* @return {boolean} true if successful, false if failure
*/
HDC.prototype.initController = function(data, fReset)
HDC.prototype.initController = function(data, fHard)
{
var i = 0;
var fSuccess = true;
@ -730,7 +730,7 @@ HDC.prototype.initController = function(data, fReset)
}
var drive = this.aDrives[iDrive];
var driveConfig = this.aDriveConfigs[iDrive];
if (!this.initDrive(iDrive, drive, driveConfig, dataDrives[iDrive], fReset)) {
if (!this.initDrive(iDrive, drive, driveConfig, dataDrives[iDrive], fHard)) {
fSuccess = false;
}
/*
@ -782,7 +782,7 @@ HDC.prototype.saveController = function()
};
/**
* initDrive(iDrive, drive, driveConfig, data, fReset)
* initDrive(iDrive, drive, driveConfig, data, fHard)
*
* TODO: Consider a separate Drive class that both FDC and HDC can use, since there's a lot of commonality
* between the drive objects created by both controllers. This will clean up overall drive management and allow
@ -793,10 +793,10 @@ HDC.prototype.saveController = function()
* @param {Object} drive
* @param {Object} driveConfig (contains one or more of the following properties: 'name', 'path', 'size', 'type')
* @param {Array} [data]
* @param {boolean} [fReset] true if a machine reset (not just a controller reset)
* @param {boolean} [fHard] true if a machine reset (not just a controller reset)
* @return {boolean} true if successful, false if failure
*/
HDC.prototype.initDrive = function(iDrive, drive, driveConfig, data, fReset)
HDC.prototype.initDrive = function(iDrive, drive, driveConfig, data, fHard)
{
var i = 0;
var fSuccess = true;
@ -860,7 +860,7 @@ HDC.prototype.initDrive = function(iDrive, drive, driveConfig, data, fReset)
/*
* On a full machine reset, pass the current drive type to setCMOSDriveType() (a no-op on pre-CMOS machines)
*/
if (fReset && this.chipset) {
if (fHard && this.chipset) {
this.chipset.setCMOSDriveType(iDrive, drive.type);
}
@ -1093,9 +1093,21 @@ HDC.prototype.seekDrive = function(drive, iSector, nSectors)
HDC.prototype.autoMount = function(fRemount)
{
if (!fRemount) this.cAutoMount = 0;
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
var drive = this.aDrives[iDrive];
if (drive.name && drive.path) {
if (fRemount && drive.disk && drive.disk.isRemote()) {
/*
* The Disk component has its own logic for remounting remote disks, so skip this disk.
*
* TODO: Consider rewriting how ALL disks are automounted/remounted, now that the Disk component
* is receiving its own powerDown() and powerUp() notifications (originally, it didn't receive them).
*/
continue;
}
if (!this.loadDisk(iDrive, drive.name, drive.path, true) && fRemount)
this.setReady(false);
continue;
@ -1132,14 +1144,14 @@ HDC.prototype.loadDisk = function(iDrive, sDiskName, sDiskPath, fAutoMount)
this.messageDebugger("loading " + sDiskName);
}
var disk = drive.disk || new Disk(this, drive, drive.mode);
disk.load(sDiskName, sDiskPath, this.mountDisk);
disk.load(sDiskName, sDiskPath, this.doneLoadDisk);
return false;
};
/**
* mountDisk(drive, disk, sDiskName, sDiskPath)
* doneLoadDisk(drive, disk, sDiskName, sDiskPath)
*
* This is a callback issued by the Disk component once its own mount() operation has finished.
* This is a callback issued by the Disk component once the load() operation has finished.
*
* @this {HDC}
* @param {Object} drive
@ -1147,7 +1159,7 @@ HDC.prototype.loadDisk = function(iDrive, sDiskName, sDiskPath, fAutoMount)
* @param {string} sDiskName
* @param {string} sDiskPath
*/
HDC.prototype.mountDisk = function(drive, disk, sDiskName, sDiskPath)
HDC.prototype.doneLoadDisk = function onHDCLoadNotify(drive, disk, sDiskName, sDiskPath)
{
drive.fBusy = false;
if ((drive.disk = disk)) {

View file

@ -98,7 +98,7 @@ if (typeof module !== 'undefined') {
* @param {Object} [controller] is an optional memory controller component
*/
function Memory(addr, size, fReadOnly, controller) {
this.cb = size;
this.cb = size || 0;
this.adw = null;
this.offset = 0;
this.fReadOnly = fReadOnly;
@ -184,52 +184,6 @@ Memory.prototype = {
this.dbg.message("attempt to write 0x" + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr) + " from " + str.toHexAddr(this.cpu.regIP, this.cpu.segCS.sel));
}
},
/**
* readByteTypedArray(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readByteTypedArray: function(off) {
Component.assert(off >= 0 && off < this.cb);
return this.ab[off];
},
/**
* readWordTypedArray(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readWordTypedArray: function(off) {
Component.assert(off >= 0 && off < this.cb - 1);
return this.dv.getUint16(off, true);
},
/**
* writeByteTypedArray(off, b)
*
* @this {Memory}
* @param {number} off
* @param {number} b
*/
writeByteTypedArray: function(off, b) {
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
this.ab[off] = b;
this.fDirty = true;
},
/**
* writeWordTypedArray(off, w)
*
* @this {Memory}
* @param {number} off
* @param {number} w
*/
writeWordTypedArray: function(off, w) {
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
this.dv.setUint16(off, w, true);
this.fDirty = true;
},
/**
* readByteMemory(off)
*
@ -237,7 +191,7 @@ Memory.prototype = {
* @param {number} off
* @return {number}
*/
readByteMemory: function(off) {
readByteMemory: function readByteMemory(off) {
Component.assert(off >= 0 && off < this.cb);
if (FATARRAYS) {
return this.ab[off];
@ -251,7 +205,7 @@ Memory.prototype = {
* @param {number} off
* @return {number}
*/
readWordMemory: function(off) {
readWordMemory: function readWordMemory(off) {
Component.assert(off >= 0 && off < this.cb - 1);
if (FATARRAYS) {
return this.ab[off] | (this.ab[off + 1] << 8);
@ -274,7 +228,7 @@ Memory.prototype = {
* @param {number} off
* @param {number} b
*/
writeByteMemory: function(off, b) {
writeByteMemory: function writeByteMemory(off, b) {
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
if (FATARRAYS) {
this.ab[off] = b;
@ -292,7 +246,7 @@ Memory.prototype = {
* @param {number} off
* @param {number} w
*/
writeWordMemory: function(off, w) {
writeWordMemory: function writeWordMemory(off, w) {
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
if (FATARRAYS) {
this.ab[off] = (w & 0xff);
@ -311,53 +265,99 @@ Memory.prototype = {
this.fDirty = true;
},
/**
* readByteVerify(off)
* readByteChecked(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readByteVerify: function(off) {
readByteChecked: function readByteChecked(off) {
if (DEBUGGER) this.dbg.checkMemoryRead(this.addr + off);
return this.readByteDirect(off);
},
/**
* readWordVerify(off)
* readWordChecked(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readWordVerify: function(off) {
readWordChecked: function readWordChecked(off) {
if (DEBUGGER) {
this.dbg.checkMemoryRead(this.addr + off) || this.dbg.checkMemoryRead(this.addr + off + 1); // jshint ignore:line
}
return this.readWordDirect(off);
},
/**
* writeByteVerify(off, b)
* writeByteChecked(off, b)
*
* @this {Memory}
* @param {number} off
* @param {number} b
*/
writeByteVerify: function(off, b) {
writeByteChecked: function writeByteChecked(off, b) {
if (DEBUGGER) this.dbg.checkMemoryWrite(this.addr + off);
this.writeByteDirect(off, b);
},
/**
* writeWordVerify(off, w)
* writeWordChecked(off, w)
*
* @this {Memory}
* @param {number} off
* @param {number} w
*/
writeWordVerify: function(off, w) {
writeWordChecked: function writeWordChecked(off, w) {
if (DEBUGGER) {
this.dbg.checkMemoryWrite(this.addr + off) || this.dbg.checkMemoryWrite(this.addr + off + 1); // jshint ignore:line
}
this.writeWordDirect(off, w);
},
/**
* readByteTypedArray(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readByteTypedArray: function readByteTypedArray(off) {
Component.assert(off >= 0 && off < this.cb);
return this.ab[off];
},
/**
* readWordTypedArray(off)
*
* @this {Memory}
* @param {number} off
* @return {number}
*/
readWordTypedArray: function readWordTypedArray(off) {
Component.assert(off >= 0 && off < this.cb - 1);
return this.dv.getUint16(off, true);
},
/**
* writeByteTypedArray(off, b)
*
* @this {Memory}
* @param {number} off
* @param {number} b
*/
writeByteTypedArray: function writeByteTypedArray(off, b) {
Component.assert(off >= 0 && off < this.cb && (b & 0xff) == b);
this.ab[off] = b;
this.fDirty = true;
},
/**
* writeWordTypedArray(off, w)
*
* @this {Memory}
* @param {number} off
* @param {number} w
*/
writeWordTypedArray: function writeWordTypedArray(off, w) {
Component.assert(off >= 0 && off < this.cb - 1 && (w & 0xffff) == w);
this.dv.setUint16(off, w, true);
this.fDirty = true;
},
/**
* save()
*
@ -418,9 +418,17 @@ Memory.prototype = {
*/
restore: function(adw) {
if (this.controller) {
return (adw === null);
return (adw == null);
}
if (this.cb == adw.length << 2) {
/*
* At this point, it's a consistency error for adw to be null; it's happened once already,
* when there was a restore bug in the Video component that added the frame buffer at the video
* card's "spec'ed" address instead of the programmed address, hence there were no controller-owned
* memory blocks installed at the programmed address, and so we arrived here at a block with no
* controller AND no data.
*/
Component.assert(adw != null);
if (adw && this.cb == adw.length << 2) {
var i;
if (FATARRAYS) {
var off = 0;
@ -533,13 +541,13 @@ Memory.prototype = {
if (DEBUGGER) {
if (!fWrite) {
if (this.cReadBreakpoints++ === 0) {
this.setReadAccess(Memory.afnVerify);
this.setReadAccess(Memory.afnChecked);
}
if (DEBUG) this.dbg.println("read breakpoint added to memory block " + str.toHex(this.addr));
}
else {
if (this.cWriteBreakpoints++ === 0) {
this.setWriteAccess(Memory.afnVerify);
this.setWriteAccess(Memory.afnChecked);
}
if (DEBUG) this.dbg.println("write breakpoint added to memory block " + str.toHex(this.addr));
}
@ -573,7 +581,7 @@ Memory.prototype = {
};
Memory.afnMemory = [Memory.prototype.readByteMemory, Memory.prototype.readWordMemory, Memory.prototype.writeByteMemory, Memory.prototype.writeWordMemory];
Memory.afnVerify = [Memory.prototype.readByteVerify, Memory.prototype.readWordVerify, Memory.prototype.writeByteVerify, Memory.prototype.writeWordVerify];
Memory.afnChecked = [Memory.prototype.readByteChecked, Memory.prototype.readWordChecked, Memory.prototype.writeByteChecked, Memory.prototype.writeWordChecked];
if (TYPEDARRAYS) {
Memory.afnTypedArray = [Memory.prototype.readByteTypedArray, Memory.prototype.readWordTypedArray, Memory.prototype.writeByteTypedArray, Memory.prototype.writeWordTypedArray];

View file

@ -225,7 +225,11 @@ State.prototype = {
* @param {Object|string} data
*/
set: function(id, data) {
this[this.id][id] = data;
try {
this[this.id][id] = data;
} catch(e) {
Component.log(e.message)
}
},
/**
* get(id)

View file

@ -1536,19 +1536,21 @@ Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2XOR] = Card.ACCESS.writeByteMode2Xor;
* arrays of nulls, which means that any uninitialized register arrays whose elements were all originally
* undefined come back via the JSON round-trip as *initialized* arrays whose elements are now all null.
*
* I'm a bit surprised, because Crockford would want us to always use the '===' operator to determine whether
* an element is initialized (eg, 'aReg[i] === undefined'), but because of this JSON stupidity, I would have
* to change all such tests to 'aReg[i] === undefined || aReg[i] === null'. Great.
* I'm a bit surprised, because JavaScript purists want us to use the '===' operator to determine
* whether an element is initialized (eg, 'aReg[i] === undefined'), but because of this JSON stupidity,
* that would require all such tests to become 'aReg[i] === undefined || aReg[i] === null'. Great.
*
* The solution is to change such comparisons to 'aReg[i] == null' because undefined is coerced to null but
* numeric values are not. Crockford refers to '==' as an "evil" operator, but yet it's OK for JSON to
* effectively treat 'undefined' the same as 'null'? Give me a break!
* The simple solution is to change such comparisons to 'aReg[i] == null', because undefined is coerced
* to null, whereas numeric values are not.
*
* Someday, perhaps a purist can explain to me why the coercion of '==' is evil, but JSON's coercion of
* 'undefined' values to 'null' values is not.
*
* [What do I mean by "another" frustration? Let me talk to you some day about disallowing hex constants,
* or insisting that property names be quoted, for starters. I think it's fine for JSON.stringify() to
* produce output that adheres to those rules by default -- although some stringify() options to control
* how "portable" the output is would be nice -- but refusing to let JSON.parse() parse objects that are,
* in fact, perfectly parseable, is just JSON being a dick.]
* or insisting that property names be quoted, or refusing to allow comments. I think it's fine for
* JSON.stringify() to produce output that adheres to rules like that -- although some parameters to control
* the output would be nice -- but refusing to let JSON.parse() parse objects that are, in fact, perfectly
* parseable, is just JSON being a dick.]
*
* @this {Card}
* @param {Array|undefined} data
@ -1571,7 +1573,7 @@ Card.prototype.initEGA = function(data, nMonitorType)
/*10*/ 0,
/*11*/ new Array(Card.GRC.TOTAL_REGS),
/*12*/ 0,
/*13*/ this.cbMemory,
/*13*/ [this.addrBuffer, this.sizeBuffer, this.cbMemory],
/*14*/ new Array(this.cbMemory >> 2), // divide cbMemory by 4 since this is an array of DWORDs (8 bits for each of 4 planes)
/*
* Card.ACCESS.WRITE.MODE0 by itself is a pretty good default, but if we choose to "randomize" the screen with
@ -1586,7 +1588,9 @@ Card.prototype.initEGA = function(data, nMonitorType)
/*19*/ 0xffffffff,
/*20*/ 0,
/*21*/ 0xffffffff,
/*22*/ 0
/*22*/ 0,
/*23*/ 0,
/*24*/ 0
];
}
@ -1606,7 +1610,25 @@ Card.prototype.initEGA = function(data, nMonitorType)
this.aGRCRegs = data[11];
this.asGRCRegs = DEBUGGER? Card.GRC.REGS : [];
this.latches = data[12];
Component.assert(this.cbMemory === data[13]);
/*
* Since we originally neglected to save/restore the card's active frame buffer address and length,
* we're now stashing all that information in data[13]. So if we're presented with an old data entry
* that contains only the card's memory size, fix it up.
*
* TODO: This code just creates the required array; the correct frame buffer address and length would
* still need to be calculated from the current GRC registers; checkMode() knows how to do that, but I'm
* not prepared to shoehorn in a call to checkMode() here, and potentially create more issues, for an
* old problem that will eventually disappear anyway.
*/
var a = data[13];
if (typeof a == "number") {
a = [this.addrBuffer, this.sizeBuffer, a];
}
this.addrBuffer = a[0];
this.sizeBuffer = a[1];
Component.assert(this.cbMemory === a[2]);
var cdw = this.cbMemory >> 2;
this.adwMemory = data[14];
if (this.adwMemory && this.adwMemory.length < cdw) {
@ -1682,7 +1704,7 @@ Card.prototype.saveEGA = function()
data[10] = this.iGRCReg;
data[11] = this.aGRCRegs;
data[12] = this.latches;
data[13] = this.cbMemory;
data[13] = [this.addrBuffer, this.sizeBuffer, this.cbMemory];
data[14] = State.compressEvenOdd(this.adwMemory);
data[15] = this.nAccess;
data[16] = this.nReadMapShift;
@ -3552,6 +3574,9 @@ Video.prototype.setMode = function(nMode, fForce)
this.removeCursor();
if (this.addrBuffer) {
if (DEBUG) this.messageDebugger("setMode(" + nMode + "): removing 0x" + str.toHex(this.sizeBuffer) + " bytes from 0x" + str.toHex(this.addrBuffer));
if (!this.bus.removeMemory(this.addrBuffer, this.sizeBuffer)) {
/*
* TODO: Force this failure case and see how well the Video component deals with it.
@ -3567,7 +3592,10 @@ Video.prototype.setMode = function(nMode, fForce)
this.addrBuffer = card.addrBuffer;
this.sizeBuffer = card.sizeBuffer;
if (DEBUG) this.messageDebugger("setMode(" + nMode + "): adding 0x" + str.toHex(this.sizeBuffer) + " bytes to 0x" + str.toHex(this.addrBuffer));
var controller = (card === this.cardEGA? card : null);
if (!this.bus.addMemory(card.addrBuffer, card.sizeBuffer, false, controller)) {
/*
* TODO: Force this failure case and see how well the Video component deals with it.
@ -4088,17 +4116,17 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit)
* JavaScript Alert: if adwMemory contains a 32-bit value such as -1526726656, and then we mask it
* with 0x80808080, we end up with -2147483648, which in a perfect 32-bit world, would be equivalent
* to 0x80000000, which means that when we look up "Video.aEGADWToByte[0x80000000]", we should get
* the entry containing 0x8. But no, in JavaScript, since the original value was negative, it
* still contains a sign bit above the lower 32 bits, which masking with 0x80808080 apparently doesn't
* eliminate (perhaps the mask is sign-extended as well, since there are 52 "significand" bits
* in JavaScript numbers). Anyway, this can be confirmed by looking at dwPixel.toString(16), which
* returns "-80000000". The solution is to check for a negative dwPixel and make it positive.
* the entry containing 0x8. But no, in JavaScript, since the original value was negative, the
* masked value is still negative, because there are 52 "significand" bits in JavaScript numbers,
* whereas bit-wise operations operate ONLY on the low 32 bits.
*
* This can be confirmed by looking at dwPixel.toString(16), which returns "-80000000". The solution
* is to check for a negative dwPixel and make it positive.
*/
if (dwPixel < 0) dwPixel = -dwPixel;
/*
* It's a good thing I had this assertion, which quickly caught the aforementioned problem.
* Moreover, since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm
* now insuring that bPixel will always default to 0 if an undefined value ever slips through again.
* Since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also insuring
* that bPixel will always default to 0 if an undefined value ever slips through again.
*/
Component.assert(Video.aEGADWToByte[dwPixel] !== undefined);
var bPixel = Video.aEGADWToByte[dwPixel] || 0;