Fixed assorted EGA and 5170 save/restore/reset problems
This commit is contained in:
parent
4194c28b20
commit
95673181b7
12 changed files with 1160 additions and 1010 deletions
|
|
@ -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)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue