Merge branch 'next-release'
This commit is contained in:
commit
d0480ecb90
297 changed files with 9645 additions and 2541 deletions
|
|
@ -311,9 +311,6 @@ BufferPF.prototype.slice = function(start, end)
|
|||
/**
|
||||
* DiskDump()
|
||||
*
|
||||
* TODO: Honor the caller's mbHD size. At the moment, any hard disk request translates to 10Mb,
|
||||
* since we rely on a "canned" BPB in aDefaultBPBs.
|
||||
*
|
||||
* TODO: If sServerRoot is set, make sure sDiskPath refers to something in either /apps/ or /disks/,
|
||||
* to prevent random enumeration of other server resources.
|
||||
*
|
||||
|
|
@ -322,12 +319,12 @@ BufferPF.prototype.slice = function(start, end)
|
|||
* @param {Array|null} [asExclude] contains filename exclusions, if any
|
||||
* @param {string} [sFormat] is the output format, one of "json"|"data"|"hex"|"bytes"|"img"
|
||||
* @param {boolean|string} [fComments] enables comments and other readability enhancements in the JSON output
|
||||
* @param {string} [mbHD] specifies a hard disk size, in megabytes, when building a new image
|
||||
* @param {string} [sSize] specifies a target disk size, in kilobytes, when building a new image
|
||||
* @param {string|null} [sServerRoot]
|
||||
* @param {string} [sManifestFile]
|
||||
* @param {Object} [argv] optional (experimental) arguments, if any
|
||||
*/
|
||||
function DiskDump(sDiskPath, asExclude, sFormat, fComments, mbHD, sServerRoot, sManifestFile, argv)
|
||||
function DiskDump(sDiskPath, asExclude, sFormat, fComments, sSize, sServerRoot, sManifestFile, argv)
|
||||
{
|
||||
/*
|
||||
* I used to set this.sServerRoot to "sServerRoot || process.cwd()", but in reality, the
|
||||
|
|
@ -341,7 +338,7 @@ function DiskDump(sDiskPath, asExclude, sFormat, fComments, mbHD, sServerRoot, s
|
|||
this.sDiskPath = path.join(this.sServerRoot, sDiskPath);
|
||||
}
|
||||
this.asExclude = asExclude || DiskDump.asExclusions;
|
||||
this.mbHD = mbHD? parseInt(mbHD, 10) : 0;
|
||||
this.kbTarget = sSize|0; // convert the numeric string to a 32-bit number (or 0 if invalid)
|
||||
this.sFormat = (sFormat || DumpAPI.FORMAT.JSON);
|
||||
this.fJSONNative = (this.sFormat == DumpAPI.FORMAT.JSON && !fComments);
|
||||
this.nJSONIndent = 0;
|
||||
|
|
@ -458,6 +455,22 @@ DiskDump.aDefaultBPBs = [
|
|||
0x02, 0x00, // 0x1A: number of heads (2)
|
||||
0x00, 0x00, 0x00, 0x00 // 0x1C: number of hidden sectors (always 0 for non-partitioned media)
|
||||
],
|
||||
[ // define BPB for 720Kb diskette
|
||||
0xEB, 0xFE, 0x90, // 0x00: JMP instruction, following by 8-byte OEM signature
|
||||
0x50, 0x43, 0x4A, 0x53, 0x2E, 0x4F, 0x52, 0x47, // MY_OEM_STRING
|
||||
// 0x49, 0x42, 0x4D, 0x20, 0x20, 0x35, 0x2E, 0x30, // "IBM 5.0" (this is a real OEM signature)
|
||||
0x00, 0x02, // 0x0B: bytes per sector (0x200 or 512)
|
||||
0x02, // 0x0D: sectors per cluster (2)
|
||||
0x01, 0x00, // 0x0E: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (1)
|
||||
0x02, // 0x10: FAT copies (2)
|
||||
0x70, 0x00, // 0x11: root directory entries (0x70 or 112) 0x70 * 0x20 = 0xE00 (1 sector is 0x200 bytes, total of 7 sectors)
|
||||
0xA0, 0x05, // 0x13: number of sectors (0x5A0 or 1440)
|
||||
0xF9, // 0x15: media type
|
||||
0x03, 0x00, // 0x16: sectors per FAT (3)
|
||||
0x09, 0x00, // 0x18: sectors per track (9)
|
||||
0x02, 0x00, // 0x1A: number of heads (2)
|
||||
0x00, 0x00, 0x00, 0x00 // 0x1C: number of hidden sectors (always 0 for non-partitioned media)
|
||||
],
|
||||
[ // define BPB for 1.2Mb diskette
|
||||
0xEB, 0xFE, 0x90, // 0x00: JMP instruction, following by 8-byte OEM signature
|
||||
0x50, 0x43, 0x4A, 0x53, 0x2E, 0x4F, 0x52, 0x47, // MY_OEM_STRING
|
||||
|
|
@ -543,7 +556,8 @@ DiskDump.asTextFileExts = [".MD", ".ME", ".ASM", ".BAS", ".TXT", ".XML"];
|
|||
*
|
||||
* Additional command-line arguments include:
|
||||
*
|
||||
* --mbhd={number}: requests a hard disk image with the given number of megabytes (eg, 10 for a 10mb image)
|
||||
* --mbhd={number}: requests a hard disk image with the given number of megabytes (DEPRECATED)
|
||||
* --size={number}: requests a target disk size with the given number of kilobytes (eg, 360, 720, 1200, 1440, 10000)
|
||||
* --exclude={filename}: specifies a filename that should be excluded from the image; repeat as often as needed
|
||||
* --overwrite: allows the --output option to overwrite an existing file; default is to NOT overwrite
|
||||
* --manifest[={filename}]: update the specified manifest.xml file with details about the disk image
|
||||
|
|
@ -625,7 +639,13 @@ DiskDump.CLI = function()
|
|||
var sManifestTitle = argv['title'];
|
||||
|
||||
if (sDiskPath) {
|
||||
var disk = new DiskDump(sDiskPath, asExclude, argv['format'], argv['comments'], argv['mbhd'], sServerRoot, sManifestFile, argv);
|
||||
var sSize = argv['mbhd'];
|
||||
if (!sSize) {
|
||||
sSize = argv['size'];
|
||||
} else {
|
||||
sSize = (sSize * 1000).toString();
|
||||
}
|
||||
var disk = new DiskDump(sDiskPath, asExclude, argv['format'], argv['comments'], sSize, sServerRoot, sManifestFile, argv);
|
||||
if (sDir) {
|
||||
disk.buildImage(true, function(err) {
|
||||
DiskDump.outputDisk(err, disk, sDiskPath, sOutputFile, fOverwrite, sManifestTitle);
|
||||
|
|
@ -2246,7 +2266,8 @@ DiskDump.prototype.buildImageFromFiles = function(aFiles, done)
|
|||
/*
|
||||
* Put reasonable upper limits on both individual file sizes and the total size of all files.
|
||||
*/
|
||||
var cbMax = (this.mbHD? this.mbHD * 1024 * 1024 : 1440 * 1024);
|
||||
var cbMax = (this.kbTarget || 1440) * 1024;
|
||||
var nTargetSectors = (this.kbTarget? this.kbTarget * 2 : 0);
|
||||
var cbTotal = this.calcFileSizes(aFiles);
|
||||
|
||||
if (fDebug) DiskDump.logConsole("total calculated size for " + aFiles.length + " files/folders: " + cbTotal + " bytes (0x" + str.toHex(cbTotal) + ")");
|
||||
|
|
@ -2269,7 +2290,7 @@ DiskDump.prototype.buildImageFromFiles = function(aFiles, done)
|
|||
* If this BPB is for a hard disk but a hard disk size was not specified, skip it.
|
||||
*/
|
||||
abBoot = DiskDump.aDefaultBPBs[iBPB];
|
||||
if ((abBoot[0x15] == 0xF8) != (this.mbHD > 0)) continue;
|
||||
if ((abBoot[0x15] == 0xF8) != (this.kbTarget >= 10000)) continue;
|
||||
cbSector = abBoot[0x0B] | (abBoot[0x0C] << 8);
|
||||
cSectorsPerCluster = abBoot[0x0D];
|
||||
cbCluster = cbSector * cSectorsPerCluster;
|
||||
|
|
@ -2282,7 +2303,7 @@ DiskDump.prototype.buildImageFromFiles = function(aFiles, done)
|
|||
cHeads = abBoot[0x1A] | (abBoot[0x1B] << 8);
|
||||
cDataSectors = cTotalSectors - cRootSectors - cFATs * cFATSectors + 1;
|
||||
cbAvail = cDataSectors * cbSector;
|
||||
if (cbTotal <= cbAvail) break; // found a BPB that works!
|
||||
if (nTargetSectors && cTotalSectors == nTargetSectors || !nTargetSectors && cbTotal <= cbAvail) break;
|
||||
}
|
||||
|
||||
if (iBPB == DiskDump.aDefaultBPBs.length) {
|
||||
|
|
@ -2318,7 +2339,7 @@ DiskDump.prototype.buildImageFromFiles = function(aFiles, done)
|
|||
/*
|
||||
* Output a Master Boot Record (MBR), if a hard disk image was requested
|
||||
*/
|
||||
if (this.mbHD > 0) {
|
||||
if (this.kbTarget >= 10000) {
|
||||
abSector = this.buildMBR(cHeads, cSectorsPerTrack, cbSector, cTotalSectors);
|
||||
offDisk += this.copyData(offDisk, abSector);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -659,7 +659,7 @@ HTMLOut.logError = function(err, fForce)
|
|||
{
|
||||
var sError = "";
|
||||
if (err) {
|
||||
sError = "htmlout error: " + err.message;
|
||||
sError = "HTMLOut error: " + err.message;
|
||||
if (fConsole || fForce) HTMLOut.logConsole(sError);
|
||||
}
|
||||
return sError;
|
||||
|
|
@ -1459,6 +1459,15 @@ HTMLOut.prototype.getMachineXML = function(sToken, sIndent, aParms, sXMLFile, sS
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we were called from getManifestXML(), then let's fallback to getMarkdownFile() instead.
|
||||
*/
|
||||
if (fFromManifest) {
|
||||
s = sIndent + "<p>" + HTMLOut.logError(err) + " (invalid manifest entry)</p>";
|
||||
obj.getMarkdownFile(obj.sFile, sToken, sIndent, aParms, s);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're still here, one of the following happened:
|
||||
*
|
||||
|
|
@ -1468,23 +1477,13 @@ HTMLOut.prototype.getMachineXML = function(sToken, sIndent, aParms, sXMLFile, sS
|
|||
*
|
||||
* But, instead of displaying a cryptic error message inside our beautiful HTML template, eg:
|
||||
*
|
||||
* htmlout error: ENOENT, open '/Users/Jeff/Sites/pcjs/devices/pc/machine/5160/cga/256kb/win101/debugger/machine.xml'
|
||||
* HTMLOut error: ENOENT, open '/Users/Jeff/Sites/pcjs/devices/pc/machine/5160/cga/256kb/win101/debugger/machine.xml'
|
||||
*
|
||||
* we have one more fallback: a random string! Less useful, but more entertaining. Well, maybe not even that.
|
||||
*
|
||||
* s = HTMLOut.logError(err);
|
||||
*/
|
||||
|
||||
/*
|
||||
* If we were called from getManifestXML(), then let's fallback to getMarkdownFile() instead.
|
||||
*/
|
||||
s = obj.getRandomString(sIndent);
|
||||
if (fFromManifest) {
|
||||
obj.getMarkdownFile(obj.sFile, sToken, sIndent, aParms, s);
|
||||
return;
|
||||
}
|
||||
|
||||
obj.aTokens[sToken] = s;
|
||||
obj.aTokens[sToken] = obj.getRandomString(sIndent);
|
||||
obj.replaceTokens();
|
||||
});
|
||||
};
|
||||
|
|
@ -1703,7 +1702,7 @@ HTMLOut.prototype.getMarkdownFile = function(sFile, sToken, sIndent, aParms, sPr
|
|||
/*
|
||||
* Instead of displaying a cryptic error message inside our beautiful HTML template, eg:
|
||||
*
|
||||
* htmlout error: ENOENT, open '/Users/Jeff/Sites/pcjs/devices/pc/machine/5160/cga/256kb/win101/debugger/README.md'
|
||||
* HTMLOut error: ENOENT, open '/Users/Jeff/Sites/pcjs/devices/pc/machine/5160/cga/256kb/win101/debugger/README.md'
|
||||
*
|
||||
* which is all this will give us:
|
||||
*
|
||||
|
|
|
|||
|
|
@ -720,13 +720,18 @@ HTTPAPI.processDumpAPI = function(req, res)
|
|||
|
||||
sFormat = req.query[DumpAPI.QUERY.FORMAT] || DumpAPI.FORMAT.JSON;
|
||||
fComments = (req.query[DumpAPI.QUERY.COMMENTS]? true : false);
|
||||
var mbHD = req.query[DumpAPI.QUERY.MBHD];
|
||||
var sSize = req.query[DumpAPI.QUERY.MBHD];
|
||||
if (sSize) {
|
||||
sSize = (sSize * 1000).toString();
|
||||
} else {
|
||||
sSize = req.query[DumpAPI.QUERY.SIZE];
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Consider adding support for DiskDump's "exclusion" option to the API interface
|
||||
* (the command-line interface supports it).
|
||||
*/
|
||||
var disk = new DiskDump(sDisk, null, sFormat, fComments, mbHD, sServerRoot);
|
||||
var disk = new DiskDump(sDisk, null, sFormat, fComments, sSize, sServerRoot);
|
||||
if (aCommand[0] == DumpAPI.QUERY.DISK || aCommand[0] == DumpAPI.QUERY.IMG) {
|
||||
disk.loadFile(function(err) {
|
||||
HTTPAPI.dumpDisk(err, disk, res);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -184,6 +184,14 @@ function Computer(parmsComputer, parmsMachine, fSuspended) {
|
|||
}
|
||||
this.dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", this.id));
|
||||
|
||||
/*
|
||||
* Enumerate all Video components for future updateVideo() calls.
|
||||
*/
|
||||
this.aVideo = [];
|
||||
for (var video = null; (video = this.getMachineComponent("Video", video));) {
|
||||
this.aVideo.push(video);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the Bus component
|
||||
*/
|
||||
|
|
@ -1437,6 +1445,65 @@ Computer.prototype.getMachineComponent = function(sType, componentPrev)
|
|||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* updateFocus(fScroll)
|
||||
*
|
||||
* NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button),
|
||||
* the scroll feature has annoying effect on iOS, so we no longer do it by default (fScroll must be true).
|
||||
*
|
||||
* @this {Computer}
|
||||
* @param {boolean} [fScroll]
|
||||
*/
|
||||
Computer.prototype.updateFocus = function(fScroll)
|
||||
{
|
||||
if (this.aVideo.length) {
|
||||
/*
|
||||
* This seems to be recommended work-around to prevent the browser from scrolling the focused element
|
||||
* into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent
|
||||
* is to ensure that keyboard input is fielded properly.
|
||||
*/
|
||||
var x = 0, y = 0;
|
||||
if (fScroll && window) {
|
||||
x = window.scrollX;
|
||||
y = window.scrollY;
|
||||
}
|
||||
/*
|
||||
* TODO: We need a mechanism to determine the "active" display, instead of hard-coding this to aVideo[0].
|
||||
*/
|
||||
this.aVideo[0].setFocus();
|
||||
if (fScroll && window) {
|
||||
window.scrollTo(x, y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* updateStatus()
|
||||
*
|
||||
* @this {Computer}
|
||||
*/
|
||||
Computer.prototype.updateStatus = function()
|
||||
{
|
||||
if (this.panel) this.panel.updateStatus();
|
||||
};
|
||||
|
||||
/**
|
||||
* updateVideo(fForce)
|
||||
*
|
||||
* Any high-frequency updates should be performed here. Avoid DOM updates, since updateVideo() can be called up to
|
||||
* 60 times per second (see VIDEO_UPDATES_PER_SECOND).
|
||||
*
|
||||
* @this {Computer}
|
||||
* @param {boolean} [fForce] (true to force a video update)
|
||||
*/
|
||||
Computer.prototype.updateVideo = function(fForce)
|
||||
{
|
||||
for (var i = 0; i < this.aVideo.length; i++) {
|
||||
this.aVideo[i].updateScreen(fForce);
|
||||
}
|
||||
if (this.panel) this.panel.updateAnimation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Computer.init()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -122,11 +122,6 @@ function CPU(parmsCPU, nCyclesDefault)
|
|||
this.aCounts.nCyclesChecksumInterval = parmsCPU["csInterval"];
|
||||
this.aCounts.nCyclesChecksumStop = parmsCPU["csStop"];
|
||||
|
||||
/*
|
||||
* Initially, no video devices are attached that require CPU-driven updates. initBus() will update this.
|
||||
*/
|
||||
this.aVideo = [];
|
||||
|
||||
this.onRunTimeout = this.runCPU.bind(this); // function onRunTimeout() { cpu.runCPU(); };
|
||||
|
||||
this.setReady();
|
||||
|
|
@ -180,14 +175,6 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
|
||||
this.fpu = cmp.getMachineComponent("FPU");
|
||||
|
||||
/*
|
||||
* Attach the Video component to the CPU, so that the CPU can periodically update
|
||||
* the video display via updateVideo(), as cycles permit.
|
||||
*/
|
||||
for (var video = null; (video = cmp.getMachineComponent("Video", video));) {
|
||||
this.aVideo.push(video);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach the ChipSet component to the CPU so that it can obtain the IDT vector number
|
||||
* of pending hardware interrupts in response to the ChipSet's updateINTR() notifications.
|
||||
|
|
@ -323,7 +310,7 @@ CPU.prototype.autoStart = function()
|
|||
*/
|
||||
if (this.flags.fAutoStart || (!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined) {
|
||||
/*
|
||||
* Now we ALSO set fSetFocus when calling runCPU(), on the assumption that in the "auto-starting" context,
|
||||
* Now we ALSO set fUpdateFocus when calling runCPU(), on the assumption that in the "auto-starting" context,
|
||||
* a machine without focus is like a day without sunshine.
|
||||
*/
|
||||
this.runCPU(true);
|
||||
|
|
@ -483,66 +470,6 @@ CPU.prototype.displayValue = function(sLabel, nValue, cch)
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* updateStatus(fForce)
|
||||
*
|
||||
* This provides periodic Control Panel updates (eg, a few times per second; see STATUS_UPDATES_PER_SECOND).
|
||||
* The X86CPU subclasses updateStatus() to take care of any DOM updates (eg, register values) while the CPU is running.
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {boolean} [fForce]
|
||||
*/
|
||||
CPU.prototype.updateStatus = function(fForce)
|
||||
{
|
||||
if (this.cmp && this.cmp.panel) this.cmp.panel.updateStatus();
|
||||
};
|
||||
|
||||
/**
|
||||
* updateVideo(fForce)
|
||||
*
|
||||
* Any high-frequency updates should be performed here. Avoid DOM updates, since updateVideo() can be called up to
|
||||
* 60 times per second (see VIDEO_UPDATES_PER_SECOND).
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {boolean} [fForce] (true to force a video update)
|
||||
*/
|
||||
CPU.prototype.updateVideo = function(fForce)
|
||||
{
|
||||
for (var i = 0; i < this.aVideo.length; i++) {
|
||||
this.aVideo[i].updateScreen(fForce);
|
||||
}
|
||||
if (this.cmp && this.cmp.panel) this.cmp.panel.updateAnimation();
|
||||
};
|
||||
|
||||
/**
|
||||
* setFocus(fScroll)
|
||||
*
|
||||
* NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button),
|
||||
* the scroll feature has annoying effect on iOS, so we no longer do it by default (fScroll must be true).
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {boolean} [fScroll]
|
||||
*/
|
||||
CPU.prototype.setFocus = function(fScroll)
|
||||
{
|
||||
if (this.aVideo.length) {
|
||||
/*
|
||||
* This seems to be recommended work-around to prevent the browser from scrolling the focused element
|
||||
* into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent
|
||||
* is to ensure that keyboard input is fielded properly.
|
||||
*/
|
||||
var x = 0, y = 0;
|
||||
if (fScroll && window) {
|
||||
x = window.scrollX;
|
||||
y = window.scrollY;
|
||||
}
|
||||
this.aVideo[0].setFocus();
|
||||
if (fScroll && window) {
|
||||
window.scrollTo(x, y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sHTMLType, sBinding, control, sValue)
|
||||
*
|
||||
|
|
@ -823,20 +750,20 @@ CPU.prototype.getSpeedTarget = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* setSpeed(nMultiplier, fSetFocus)
|
||||
* setSpeed(nMultiplier, fUpdateFocus)
|
||||
*
|
||||
* NOTE: This used to return the target speed, in mhz, but no callers appear to care at this point.
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {number} [nMultiplier] is the new proposed multiplier (reverts to 1 if the target was too high)
|
||||
* @param {boolean} [fSetFocus] is true to give the CPU focus
|
||||
* @param {boolean} [fUpdateFocus] is true to update Computer focus
|
||||
* @return {boolean} true if successful, false if not
|
||||
*
|
||||
* @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset,
|
||||
* so that the next effective speed calculation obtains sensible results. In fact, when runCPU() initially calls
|
||||
* setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting).
|
||||
*/
|
||||
CPU.prototype.setSpeed = function(nMultiplier, fSetFocus)
|
||||
CPU.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
|
||||
{
|
||||
var fSuccess = false;
|
||||
if (nMultiplier !== undefined) {
|
||||
|
|
@ -857,7 +784,7 @@ CPU.prototype.setSpeed = function(nMultiplier, fSetFocus)
|
|||
if (controlSpeed) controlSpeed.textContent = sSpeed;
|
||||
this.println("target speed: " + sSpeed);
|
||||
}
|
||||
if (fSetFocus) this.setFocus();
|
||||
if (fUpdateFocus && this.cmp) this.cmp.updateFocus();
|
||||
}
|
||||
this.addCycles(this.nRunCycles);
|
||||
this.nRunCycles = 0;
|
||||
|
|
@ -1014,12 +941,12 @@ CPU.prototype.calcRemainingTime = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* runCPU(fSetFocus)
|
||||
* runCPU(fUpdateFocus)
|
||||
*
|
||||
* @this {CPU}
|
||||
* @param {boolean} [fSetFocus] is true to give the CPU focus
|
||||
* @param {boolean} [fUpdateFocus] is true to update Computer focus
|
||||
*/
|
||||
CPU.prototype.runCPU = function(fSetFocus)
|
||||
CPU.prototype.runCPU = function(fUpdateFocus)
|
||||
{
|
||||
if (!this.setBusy(true)) {
|
||||
this.updateCPU();
|
||||
|
|
@ -1027,7 +954,7 @@ CPU.prototype.runCPU = function(fSetFocus)
|
|||
return;
|
||||
}
|
||||
|
||||
this.startCPU(fSetFocus);
|
||||
this.startCPU(fUpdateFocus);
|
||||
|
||||
/*
|
||||
* calcStartTime() initializes the cycle counter and timestamp for this runCPU() invocation, and optionally
|
||||
|
|
@ -1078,13 +1005,13 @@ CPU.prototype.runCPU = function(fSetFocus)
|
|||
this.aCounts.nCyclesNextVideoUpdate -= nCycles;
|
||||
if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
|
||||
this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
|
||||
this.updateVideo();
|
||||
if (this.cmp) this.cmp.updateVideo();
|
||||
}
|
||||
|
||||
this.aCounts.nCyclesNextStatusUpdate -= nCycles;
|
||||
if (this.aCounts.nCyclesNextStatusUpdate <= 0) {
|
||||
this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate;
|
||||
this.updateStatus();
|
||||
if (this.cmp) this.cmp.updateStatus();
|
||||
}
|
||||
|
||||
this.aCounts.nCyclesNextYield -= nCycles;
|
||||
|
|
@ -1106,13 +1033,13 @@ CPU.prototype.runCPU = function(fSetFocus)
|
|||
};
|
||||
|
||||
/**
|
||||
* startCPU(fSetFocus)
|
||||
* startCPU(fUpdateFocus)
|
||||
*
|
||||
* WARNING: Other components must use runCPU() to get the CPU running; this is a runCPU() helper function only.
|
||||
*
|
||||
* @param {boolean} [fSetFocus]
|
||||
* @param {boolean} [fUpdateFocus]
|
||||
*/
|
||||
CPU.prototype.startCPU = function(fSetFocus)
|
||||
CPU.prototype.startCPU = function(fUpdateFocus)
|
||||
{
|
||||
if (!this.flags.fRunning) {
|
||||
/*
|
||||
|
|
@ -1128,8 +1055,10 @@ CPU.prototype.startCPU = function(fSetFocus)
|
|||
if (this.chipset) this.chipset.setSpeaker();
|
||||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Halt";
|
||||
this.updateStatus(true);
|
||||
if (fSetFocus) this.setFocus(true);
|
||||
if (this.cmp) {
|
||||
this.cmp.updateStatus();
|
||||
if (fUpdateFocus) this.cmp.updateFocus(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1187,8 +1116,10 @@ CPU.prototype.stopCPU = function(fComplete)
|
|||
*/
|
||||
CPU.prototype.updateCPU = function(fForce)
|
||||
{
|
||||
this.updateVideo(fForce);
|
||||
this.updateStatus();
|
||||
if (this.cmp) {
|
||||
this.cmp.updateVideo(fForce);
|
||||
this.cmp.updateStatus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2182,11 +2182,11 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* setFocus()
|
||||
* updateFocus()
|
||||
*
|
||||
* @this {Debugger}
|
||||
*/
|
||||
Debugger.prototype.setFocus = function()
|
||||
Debugger.prototype.updateFocus = function()
|
||||
{
|
||||
if (this.controlDebug) this.controlDebug.focus();
|
||||
};
|
||||
|
|
@ -3945,16 +3945,16 @@ if (DEBUGGER) {
|
|||
};
|
||||
|
||||
/**
|
||||
* runCPU(fSetFocus)
|
||||
* runCPU(fUpdateFocus)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {boolean} [fSetFocus] is true to give the CPU focus
|
||||
* @param {boolean} [fUpdateFocus] is true to update focus
|
||||
* @return {boolean} true if run request successful, false if not
|
||||
*/
|
||||
Debugger.prototype.runCPU = function(fSetFocus)
|
||||
Debugger.prototype.runCPU = function(fUpdateFocus)
|
||||
{
|
||||
if (!this.isCPUAvail()) return false;
|
||||
this.cpu.runCPU(fSetFocus);
|
||||
this.cpu.runCPU(fUpdateFocus);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
@ -4258,7 +4258,7 @@ if (DEBUGGER) {
|
|||
this.println(sStopped);
|
||||
}
|
||||
this.updateStatus(true);
|
||||
this.setFocus();
|
||||
this.updateFocus();
|
||||
this.clearTempBreakpoint(this.cpu.regLIP);
|
||||
}
|
||||
};
|
||||
|
|
@ -7618,7 +7618,7 @@ if (DEBUGGER) {
|
|||
if (this.nStep) {
|
||||
this.setTempBreakpoint(dbgAddr);
|
||||
if (!this.runCPU()) {
|
||||
this.cpu.setFocus();
|
||||
if (this.cmp) this.cmp.updateFocus();
|
||||
this.nStep = 0;
|
||||
}
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -771,7 +771,7 @@ FDC.prototype.initController = function(data)
|
|||
|
||||
if (this.aDrives === undefined) {
|
||||
this.nDrives = 4; // default to the maximum number of drives
|
||||
if (this.chipset) this.nDrives = this.chipset.getSWFloppyDrives();
|
||||
if (this.chipset) this.nDrives = this.chipset.getDIPFloppyDrives();
|
||||
/*
|
||||
* I would prefer to allocate only nDrives, but as discussed in the handling of the FDC.REG_DATA.CMD.SENSE_INT
|
||||
* command, we're faced with situations where the controller must respond to any drive in the range 0-3, regardless
|
||||
|
|
@ -789,7 +789,7 @@ FDC.prototype.initController = function(data)
|
|||
* the drive's physical limits accordingly (ie, max tracks, max heads, and max sectors/track).
|
||||
*/
|
||||
drive = this.aDrives[iDrive] = {};
|
||||
var nKb = (this.chipset? this.chipset.getSWFloppyDriveSize(iDrive) : 0);
|
||||
var nKb = (this.chipset? this.chipset.getDIPFloppyDriveSize(iDrive) : 0);
|
||||
switch(nKb) {
|
||||
case 160:
|
||||
case 180:
|
||||
|
|
@ -1279,7 +1279,7 @@ FDC.prototype.loadSelectedDrive = function(sDisketteName, sDiskettePath, file)
|
|||
|
||||
if (DEBUG) this.println("loading disk " + sDiskettePath + "...");
|
||||
|
||||
while (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, false, file)) {
|
||||
while (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, false, file) < 0) {
|
||||
if (!window.confirm("Click OK to reload the original disk.\n(WARNING: All disk changes will be discarded)")) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1327,7 +1327,7 @@ FDC.prototype.mountDiskette = function(iDrive, sDisketteName, sDiskettePath)
|
|||
* @param {string} sDiskettePath
|
||||
* @param {boolean} [fAutoMount]
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
* @return {boolean} true if diskette (already) loaded, false if queued up (or busy)
|
||||
* @return {number} 1 if diskette loaded, 0 if queued up (or busy), -1 if already loaded
|
||||
*/
|
||||
FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAutoMount, file)
|
||||
{
|
||||
|
|
@ -1336,7 +1336,7 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
|
|||
this.unloadDrive(iDrive, fAutoMount, true);
|
||||
if (drive.fBusy) {
|
||||
this.notice("Drive " + iDrive + " busy");
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
drive.fBusy = true;
|
||||
if (fAutoMount) {
|
||||
|
|
@ -1347,10 +1347,11 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
|
|||
drive.fLocal = !!file;
|
||||
var disk = new Disk(this, drive, DiskAPI.MODE.PRELOAD);
|
||||
if (!disk.load(sDisketteName, sDiskettePath, file, this.doneLoadDiskette)) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return true;
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1436,6 +1437,12 @@ FDC.prototype.doneLoadDiskette = function onFDCLoadNotify(drive, disk, sDiskette
|
|||
drive.nDiskCylinders = aDiskInfo[0];
|
||||
drive.nDiskHeads = aDiskInfo[1];
|
||||
drive.nDiskSectors = aDiskInfo[2];
|
||||
|
||||
/*
|
||||
* Since you usually want the Computer to have focus again after loading a new diskette, let's try automatically
|
||||
* updating the focus after a successful load.
|
||||
*/
|
||||
if (this.cmp) this.cmp.updateFocus();
|
||||
}
|
||||
else {
|
||||
drive.fLocal = false;
|
||||
|
|
@ -1733,6 +1740,85 @@ FDC.prototype.outFDCOutput = function(port, bOut, addrFrom)
|
|||
this.regOutput = bOut;
|
||||
};
|
||||
|
||||
/**
|
||||
* inFDCDiagnostic(port, addrFrom)
|
||||
*
|
||||
* It turns out that any 5170 configuration without an HDC component that attempts to use either the REV2 or REV3
|
||||
* PC AT ROM BIOS will fail with error "601-Diskette Error", unless we also provide this "D/S/P DIAGNOSTIC REGISTER".
|
||||
* The original 5170 REV1 BIOS didn't have this requirement.
|
||||
*
|
||||
* I'm unable to find any documentation on this so-called "D/S/P DIAGNOSTIC REGISTER" (port 0x3F1) or the "D/S/P CARD"
|
||||
* to which the ROM BIOS refers. But it seems clear that if we don't provide the expected response from the DIAGNOSTIC
|
||||
* REGISTER, and there's no HDC to respond to the MULTIPLE DATA RATE CAPABLE test that follows, then an error is inevitable.
|
||||
* Clearly, there is a very intimate relationship between the FDC and HDC portions of this card.
|
||||
*
|
||||
* Here's the relevant code from the REV3 PC AT ROM BIOS (TEST2.ASM):
|
||||
*
|
||||
* ;----- CHECK FOR MULTIPLE DATA RATE CAPABILITY
|
||||
*
|
||||
* J_OK:
|
||||
* MOV DX,03F1H ; D/S/P DIAGNOSTIC REGISTER
|
||||
* IN AL,DX ; READ D/S/P TYPE CODE
|
||||
* AND AL,11111000B ; KEEP ONLY UNIQUE CODE FOR D/S/P
|
||||
* CMP AL,01010000B ; D/S/P CARD - MULTIPLE DATA RATE?
|
||||
* JZ J_OK3 ; IF SO JUMP
|
||||
*
|
||||
* MOV DX,05F7H ; FIXED DISK DIAGNOSTIC REGISTER
|
||||
* IN AL,DX ; READ FIXED DISK TYPE CODE
|
||||
* AND AL,11110000B ; KEEP ONLY UNIQUE CODE FOR F/D
|
||||
* CMP AL,10100000B ; FIXED DISK ADAPTER ?
|
||||
* JZ J_FAIL ; MUST BE COMBO ELSE ERROR
|
||||
*
|
||||
* MOV BL,0FH ; OUTER LOOP COUNT WAIT FOR BUSY OFF
|
||||
* SUB CX,CX
|
||||
* MOV DX,01F7H ; HARD FILE STATUS PORT
|
||||
* J_OK1:
|
||||
* IN AL,DX ; GET THE STATUS
|
||||
* TEST AL,080H ; IS THE CONTROLLER BUSY?
|
||||
* JZ J_OK2 ; CONTINUE IF NOT
|
||||
* LOOP J_OK1 ; TRY AGAIN
|
||||
* DEC BL ; DECREMENT OUTER LOOP
|
||||
* JNZ J_OK1 ; TRY AGAIN IF NOT ZERO
|
||||
* AND AL,0CH ; BITS 2 & 3 = 0 IF MULTI DATA CAPABLE
|
||||
* JZ J_OK3 ; GO IF YES
|
||||
* JMP SHORT J_FAIL ; NO MULTIPLE DATA RATE CAPABILITY
|
||||
* J_OK2:
|
||||
* MOV DX,1F4H ; VERIFY MULTIPLE DATA RATE CAPABLE
|
||||
* MOV AL,055H ; WRITE TO THE CYLINDER BYTE
|
||||
* OUT DX,AL
|
||||
* JMP $+2 ; I/O DELAY
|
||||
* IN AL,DX ; CHECK DATA WRITTEN = DATA READ
|
||||
* CMP AL,055H
|
||||
* JNZ J_FAIL ; GO IF NOT
|
||||
* MOV AL,0AAH ; WRITE ANOTHER PATTERN
|
||||
* OUT DX,AL
|
||||
* JMP $+2 ; I/O DELAY
|
||||
* IN AL,DX
|
||||
* CMP AL,0AAH ; IS DATA PATTERN THE SAME?
|
||||
* JZ J_OK3 ; GO IF SO
|
||||
*
|
||||
* J_FAIL:
|
||||
* OR @MFG_ERR_FLAG+1,DSK_FAIL; <><><><><><><><><><><><><>
|
||||
* ; <><> DISKETTE FAILED <><>
|
||||
* MOV SI,OFFSET E601 ; GET ADDRESS OF MESSAGE
|
||||
* CALL E_MSG ; GO PRINT ERROR MESSAGE
|
||||
* JMP SHORT F15C ; SKIP SETUP IF ERROR
|
||||
*
|
||||
* J_OK3:
|
||||
* OR @LASTRATE,DUAL ; TURN ON DSP/COMBO FLAG
|
||||
*
|
||||
* @this {FDC}
|
||||
* @param {number} port (0x3F1, input only)
|
||||
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
FDC.prototype.inFDCDiagnostic = function(port, addrFrom)
|
||||
{
|
||||
var b = 0x50; // we simply return the expected pattern (01010000B); see code excerpt above
|
||||
this.printMessageIO(port, null, addrFrom, "DIAG", b);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inFDCStatus(port, addrFrom)
|
||||
*
|
||||
|
|
@ -2539,6 +2625,7 @@ FDC.prototype.writeFormat = function(drive, b)
|
|||
* way out and always emulating it. So, consider an FDC parameter to disable that feature for stricter compatibility.
|
||||
*/
|
||||
FDC.aPortInput = {
|
||||
0x3F1: FDC.prototype.inFDCDiagnostic,
|
||||
0x3F4: FDC.prototype.inFDCStatus,
|
||||
0x3F5: FDC.prototype.inFDCData,
|
||||
0x3F7: FDC.prototype.inFDCInput
|
||||
|
|
|
|||
|
|
@ -2958,6 +2958,11 @@ HDC.aXTCPortInput = {
|
|||
0x322: HDC.prototype.inXTCConfig
|
||||
};
|
||||
|
||||
/*
|
||||
* For future reference, the REV2 and REV3 PC AT ROM BIOS also refer to a "FIXED DISK DIAGNOSTIC REGISTER" at
|
||||
* port 0x5F7, but I have no documentation on it, and failure to respond is non-fatal. See the discussion of the
|
||||
* FDC diagnostic register in inFDCDiagnostic() for more details.
|
||||
*/
|
||||
HDC.aATCPortInput = {
|
||||
0x1F0: HDC.prototype.inATCData,
|
||||
0x1F1: HDC.prototype.inATCError,
|
||||
|
|
|
|||
|
|
@ -1187,7 +1187,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
case "caps-lock":
|
||||
this.bindings[id] = control;
|
||||
control.onclick = function onClickCapsLock(event) {
|
||||
if (kbd.cpu) kbd.cpu.setFocus();
|
||||
if (kbd.cmp) kbd.cmp.updateFocus();
|
||||
return kbd.toggleCapsLock();
|
||||
};
|
||||
return true;
|
||||
|
|
@ -1195,7 +1195,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
case "num-lock":
|
||||
this.bindings[id] = control;
|
||||
control.onclick = function onClickNumLock(event) {
|
||||
if (kbd.cpu) kbd.cpu.setFocus();
|
||||
if (kbd.cmp) kbd.cmp.updateFocus();
|
||||
return kbd.toggleNumLock();
|
||||
};
|
||||
return true;
|
||||
|
|
@ -1203,7 +1203,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
case "scroll-lock":
|
||||
this.bindings[id] = control;
|
||||
control.onclick = function onClickScrollLock(event) {
|
||||
if (kbd.cpu) kbd.cpu.setFocus();
|
||||
if (kbd.cmp) kbd.cmp.updateFocus();
|
||||
return kbd.toggleScrollLock();
|
||||
};
|
||||
return true;
|
||||
|
|
@ -1218,7 +1218,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
control.onclick = function(kbd, sKey, simCode) {
|
||||
return function onClickKeyboard(event) {
|
||||
if (!COMPILED && kbd.messageEnabled()) kbd.printMessage(sKey + " clicked", Messages.KEYS);
|
||||
if (kbd.cpu) kbd.cpu.setFocus();
|
||||
if (kbd.cmp) kbd.cmp.updateFocus();
|
||||
kbd.updateShiftState(simCode, true); // future-proofing if/when any LOCK keys are added to CLICKCODES
|
||||
kbd.addActiveKey(simCode, true);
|
||||
};
|
||||
|
|
@ -1255,7 +1255,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
*/
|
||||
this.bindings[id] = control;
|
||||
control.onclick = function onClickTest(event) {
|
||||
if (kbd.cpu) kbd.cpu.setFocus();
|
||||
if (kbd.cmp) kbd.cmp.updateFocus();
|
||||
return kbd.injectKeys(sValue);
|
||||
};
|
||||
return true;
|
||||
|
|
@ -1323,6 +1323,7 @@ Keyboard.prototype.findBinding = function(simCode, sType, fDown)
|
|||
*/
|
||||
Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.cmp = cmp;
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ RAM.prototype.powerDown = function(fSave, fShutdown)
|
|||
RAM.prototype.reset = function()
|
||||
{
|
||||
if (!this.addrRAM && !this.fInstalled && this.chipset) {
|
||||
var baseRAM = this.chipset.getSWMemorySize() * 1024;
|
||||
var baseRAM = this.chipset.getDIPMemorySize() * 1024;
|
||||
if (this.sizeRAM && baseRAM != this.sizeRAM) {
|
||||
this.bus.removeMemory(this.addrRAM, this.sizeRAM);
|
||||
this.fAllocated = false;
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ if (NODE) {
|
|||
* scale: true for font scaling, false (default) to center the display on the screen
|
||||
* charCols: number of character columns
|
||||
* charRows: number of character rows
|
||||
* fontROM: path to .rom file (or a JSON representation) that defines the character set
|
||||
* fontROM: path to .rom file (or a JSON representation) containing the character set
|
||||
* screenColor: background color of the screen canvas (default is black)
|
||||
* touchScreen: string specifying desired touch-screen support (default is ''); see initBus()
|
||||
* autoLock: true to (attempt to) automatically lock the mouse to the canvas (default is false)
|
||||
* touchScreen: string specifying desired touch-screen support (default is none)
|
||||
* autoLock: true to (attempt to) auto-lock the mouse to the canvas (default is false)
|
||||
*
|
||||
* An EGA may specify the following additional properties:
|
||||
*
|
||||
|
|
@ -73,22 +73,23 @@ if (NODE) {
|
|||
* the port level, and whenever reset() is called. setMode() also invokes updateScreen(true),
|
||||
* which forces reallocation of our internal buffer (aCellCache) that mirrors the video buffer.
|
||||
*
|
||||
* The CPU periodically calls updateScreen(), at an assumed rate of 60 times/second,
|
||||
* to update any blinking elements (the cursor and any characters with the blink attribute),
|
||||
* to compare/update the contents of our internal buffer with the video buffer, and to render
|
||||
* any differences between the two buffers into the associated screen canvas, via either
|
||||
* updateChar() or setPixel().
|
||||
* The CPU periodically calls updateVideo(), which in turn calls updateScreen() for each Video
|
||||
* instance. These updates should occur at a rate of 60 times/second, to update any blinking
|
||||
* elements (the cursor and any cells with the blink attribute), to compare/update the contents
|
||||
* of our internal buffer with the video buffer, and to render any differences between the two
|
||||
* buffers into the associated screen canvas, via either updateChar() or setPixel().
|
||||
*
|
||||
* Thanks to the CPU's new block-based memory manager that allows us to sparse-allocate memory
|
||||
* Thanks to the Bus' new block-based memory manager that allows us to sparse-allocate memory
|
||||
* (in 4Kb increments on 20-bit buses, 16Kb increments on 24-bit buses), updateScreen()
|
||||
* can also ask the CPU for the "dirty" state of all the blocks underlying the video buffer,
|
||||
* bypassing the update completely if the buffer is still clean.
|
||||
*
|
||||
* Unfortunately, that optimization is defeated if our count of active blink elements is non-zero,
|
||||
* Sadly, that optimization is defeated if the count of active blink elements is non-zero,
|
||||
* because we must rescan the entire buffer to locate and redraw them all; I'm assuming for now
|
||||
* that, more often than not, blink attributes will not be present, and therefore they're not worth
|
||||
* a separate caching mechanism. If the only blinking element is the cursor, that's no problem,
|
||||
* as we redraw only the one cell containing the cursor (assuming the buffer is otherwise clean).
|
||||
* that, more often than not, very few (if any) blink attributes will be present, and therefore
|
||||
* they're not worth a separate caching mechanism. If the only blinking element is the cursor,
|
||||
* that's no problem, as we redraw only the one cell containing the cursor (assuming the buffer
|
||||
* is otherwise clean).
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
|
|
@ -1000,7 +1001,7 @@ Video.aEGADWToByte[0x80808000|0] = 0xe;
|
|||
Video.aEGADWToByte[0x80808080|0] = 0xf;
|
||||
|
||||
/**
|
||||
* Card(video, iCard, data, cbMemory)
|
||||
* Card(video, nCard, data, cbMemory)
|
||||
*
|
||||
* Creates an object representing an initial video card state;
|
||||
* can also restore a video card from state data created by saveCard().
|
||||
|
|
@ -1011,25 +1012,25 @@ Video.aEGADWToByte[0x80808080|0] = 0xf;
|
|||
*
|
||||
* @constructor
|
||||
* @param {Video} [video]
|
||||
* @param {number} [iCard] (see Video.CARD.*)
|
||||
* @param {number} [nCard] (see Video.CARD.*)
|
||||
* @param {Array|null} [data]
|
||||
* @param {number} [cbMemory] is specified if the card must allocate its own memory buffer
|
||||
*/
|
||||
function Card(video, iCard, data, cbMemory)
|
||||
function Card(video, nCard, data, cbMemory)
|
||||
{
|
||||
/*
|
||||
* If a card was originally not present (eg, EGA), then the state will be empty,
|
||||
* so we need to detect that case and continue indicating that the card is not present.
|
||||
*/
|
||||
if (iCard !== undefined && (!data || data.length)) {
|
||||
if (nCard !== undefined && (!data || data.length)) {
|
||||
|
||||
this.video = video;
|
||||
|
||||
var specs = Video.cardSpecs[iCard];
|
||||
var specs = Video.cardSpecs[nCard];
|
||||
var nMonitorType = video.nMonitorType || specs[5];
|
||||
|
||||
if (!data || data.length < 6) {
|
||||
data = [false, 0, null, null, 0, new Array(iCard < Video.CARD.EGA? Card.CRTC.TOTAL_REGS : Card.CRTC.EGA.TOTAL_REGS)];
|
||||
data = [false, 0, null, null, 0, new Array(nCard < Video.CARD.EGA? Card.CRTC.TOTAL_REGS : Card.CRTC.EGA.TOTAL_REGS)];
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1041,7 +1042,7 @@ function Card(video, iCard, data, cbMemory)
|
|||
this.port = specs[1];
|
||||
}
|
||||
|
||||
this.nCard = iCard;
|
||||
this.nCard = nCard;
|
||||
this.addrBuffer = specs[2]; // default (physical) video buffer address
|
||||
this.sizeBuffer = specs[3]; // default video buffer length (this is the total size, not the current visible size; this.cbScreen is calculated on the fly to reflect the latter)
|
||||
|
||||
|
|
@ -1073,7 +1074,7 @@ function Card(video, iCard, data, cbMemory)
|
|||
this.nCRTCRegs = Card.CRTC.TOTAL_REGS;
|
||||
this.asCRTCRegs = DEBUGGER? Card.CRTC.REGS : [];
|
||||
|
||||
if (iCard >= Video.CARD.EGA) {
|
||||
if (nCard >= Video.CARD.EGA) {
|
||||
this.nCRTCRegs = Card.CRTC.EGA.TOTAL_REGS;
|
||||
this.asCRTCRegs = DEBUGGER? Card.CRTC.EGA_REGS : [];
|
||||
this.initEGA(data[6], nMonitorType);
|
||||
|
|
@ -2500,20 +2501,15 @@ Card.prototype.dumpRegs = function(sName, iReg, aRegs, asRegs)
|
|||
{
|
||||
if (DEBUGGER) {
|
||||
if (!aRegs) {
|
||||
this.dbg.println(sName + ": " + str.toHexByte(iReg));
|
||||
this.dbg.println(sName + ": " + str.toHex(iReg, 2));
|
||||
return;
|
||||
}
|
||||
var i, cchMax = 19, s = "";
|
||||
/*
|
||||
var s = "", i, cchMax = 0;
|
||||
for (i = 0; i < asRegs.length; i++) {
|
||||
if (cchMax < asRegs[i].length) cchMax = asRegs[i].length;
|
||||
}
|
||||
cchMax++;
|
||||
*/
|
||||
var i, cchMax = 18, s = "";
|
||||
for (i = 0; i < asRegs.length; i++) {
|
||||
var reg = (aRegs === this.regCRTData)? this.getCRTCReg(i) : aRegs[i];
|
||||
if (s) s += '\n';
|
||||
s += sName + "[" + str.toHexByte(i) + "]: " + str.pad(asRegs[i], cchMax) + str.toHexByte(aRegs[i]) + (i === iReg? "*" : "");
|
||||
s += sName + "[" + str.toHex(i, 2) + "]: " + str.pad(asRegs[i], cchMax) + (i === iReg? '*' : ' ') + str.toHex(reg, reg > 0xff? 4 : 2);
|
||||
if (reg != null) s += " (" + reg + ".)"
|
||||
}
|
||||
this.dbg.println(s);
|
||||
}
|
||||
|
|
@ -2536,11 +2532,11 @@ Card.prototype.dumpVideoCard = function()
|
|||
this.dumpRegs(" GRC", this.regGRCIndx, this.regGRCData, this.asGRCRegs);
|
||||
this.dumpRegs(" SEQ", this.regSEQIndx, this.regSEQData, this.asSEQRegs);
|
||||
this.dumpRegs(" ATC", this.regATCIndx, this.regATCData, this.asATCRegs);
|
||||
this.dumpRegs(" ATCINDX", this.regATCIndx);
|
||||
this.dbg.println(" ATCDATA: " + this.fATCData);
|
||||
this.dumpRegs(" FEAT", this.regFeat);
|
||||
this.dumpRegs(" MISC", this.regMisc);
|
||||
this.dumpRegs(" STATUS0", this.regStatus0);
|
||||
this.dumpRegs(" ATCINDX", this.regATCIndx);
|
||||
this.dbg.println(" ATCDATA: " + this.fATCData);
|
||||
this.dumpRegs(" FEAT", this.regFeat);
|
||||
this.dumpRegs(" MISC", this.regMisc);
|
||||
this.dumpRegs(" STATUS0", this.regStatus0);
|
||||
/*
|
||||
* There are few more EGA regs we could dump, like GRCPos1, GRCPos2, but does anyone care?
|
||||
*/
|
||||
|
|
@ -2550,19 +2546,19 @@ Card.prototype.dumpVideoCard = function()
|
|||
* TODO: This simply dumps the last value read from the STATUS1 register, not necessarily
|
||||
* its current state; consider dumping getRetraceBits() instead of (or in addition to) this.
|
||||
*/
|
||||
this.dumpRegs(" STATUS1", this.regStatus);
|
||||
this.dumpRegs(" STATUS1", this.regStatus);
|
||||
|
||||
if (this.nCard == Video.CARD.MDA || this.nCard == Video.CARD.CGA) {
|
||||
this.dumpRegs(" MODEREG", this.regMode);
|
||||
this.dumpRegs(" MODEREG", this.regMode);
|
||||
}
|
||||
|
||||
if (this.nCard == Video.CARD.CGA) {
|
||||
this.dumpRegs(" COLOR", this.regColor);
|
||||
this.dumpRegs(" COLOR", this.regColor);
|
||||
}
|
||||
|
||||
if (this.nCard >= Video.CARD.EGA) {
|
||||
this.dbg.println(" LATCHES: 0x" + str.toHex(this.latches));
|
||||
this.dbg.println(" ACCESS: " + str.toHexWord(this.nAccess));
|
||||
this.dbg.println(" LATCHES: " + str.toHex(this.latches));
|
||||
this.dbg.println(" ACCESS: " + str.toHex(this.nAccess, 4));
|
||||
this.dbg.println("Use 'dump video [addr]' to dump video memory");
|
||||
/*
|
||||
* There are few more EGA regs we could dump, like GRCPos1, GRCPos2, but does anyone care?
|
||||
|
|
@ -2782,6 +2778,52 @@ Card.prototype.setMemoryAccess = function(nAccess)
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getCRTCReg()
|
||||
*
|
||||
* @this {Card}
|
||||
* @param {number} iReg
|
||||
* @return {number}
|
||||
*/
|
||||
Card.prototype.getCRTCReg = function(iReg)
|
||||
{
|
||||
var reg = this.regCRTData[iReg];
|
||||
if (reg != null && this.nCard >= Video.CARD.EGA) {
|
||||
var bOvrflowBit8 = 0, bOvrflowBit9 = 0, bMaxScanBit9 = 0;
|
||||
switch(iReg) {
|
||||
case Card.CRTC.EGA.VTOTAL: // 0x06
|
||||
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT8; // 0x01
|
||||
if (this.nCard == Video.CARD.VGA) bOvrflowBit9 = Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT9;
|
||||
break;
|
||||
case Card.CRTC.EGA.CURSOR_START.INDX: // 0x0A
|
||||
if (this.nCard == Video.CARD.EGA) bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.CURSOR_START_BIT8;
|
||||
break;
|
||||
case Card.CRTC.EGA.VRETRACE_START: // 0x10
|
||||
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VRETRACE_START_BIT8; // 0x04
|
||||
if (this.nCard == Video.CARD.VGA) bOvrflowBit9 = Card.CRTC.EGA.OVERFLOW.VRETRACE_START_BIT9;
|
||||
break;
|
||||
case Card.CRTC.EGA.VDISP_END: // 0x12
|
||||
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VDISP_END_BIT8; // 0x02
|
||||
if (this.nCard == Video.CARD.VGA) bOvrflowBit9 = Card.CRTC.EGA.OVERFLOW.VDISP_END_BIT9;
|
||||
break;
|
||||
case Card.CRTC.EGA.VBLANK_START: // 0x15
|
||||
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VBLANK_START_BIT8; // 0x08
|
||||
if (this.nCard == Video.CARD.VGA) bMaxScanBit9 = Card.CRTC.EGA.MAX_SCAN.VBLANK_START_BIT9;
|
||||
break;
|
||||
case Card.CRTC.EGA.LINE_COMPARE: // 0x18
|
||||
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.LINE_COMPARE_BIT8; // 0x10
|
||||
if (this.nCard == Video.CARD.VGA) bMaxScanBit9 = Card.CRTC.EGA.MAX_SCAN.LINE_COMPARE_BIT9;
|
||||
break;
|
||||
}
|
||||
if (bOvrflowBit8) {
|
||||
reg |= ((this.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & bOvrflowBit8)? 0x100 : 0);
|
||||
reg |= ((this.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & bOvrflowBit9)? 0x200 : 0);
|
||||
reg |= ((this.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX] & bMaxScanBit9)? 0x200 : 0);
|
||||
}
|
||||
}
|
||||
return reg;
|
||||
};
|
||||
|
||||
/*
|
||||
* Card Specifications
|
||||
*
|
||||
|
|
@ -2904,7 +2946,9 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.bEGASwitches = 0x09; // our default "switches" setting (see aEGAMonitorSwitches)
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
if (this.chipset && this.sSwitches) {
|
||||
if (this.nCard == Video.CARD.EGA) this.bEGASwitches = this.chipset.parseSwitches(this.sSwitches, this.bEGASwitches);
|
||||
if (this.nCard == Video.CARD.EGA) {
|
||||
this.bEGASwitches = this.chipset.parseDIPSwitches(this.sSwitches, this.bEGASwitches);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -3535,7 +3579,7 @@ Video.prototype.reset = function()
|
|||
* on the EGA's own switch settings instead.
|
||||
*/
|
||||
if (this.chipset) {
|
||||
nMonitorType = this.chipset.getSWVideoMonitor();
|
||||
nMonitorType = this.chipset.getDIPVideoMonitor();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -4693,14 +4737,14 @@ Video.prototype.setDimensions = function()
|
|||
* to use the 9x14 "EGA" color font instead.
|
||||
*
|
||||
* TODO: Can an EGA with a monochrome monitor be programmed for 43-line mode as well? If so,
|
||||
* then we'll need to load another MDA font variation, because we only load an 9x14 font for MDA.
|
||||
* then we'll need to load another MDA font variation, because we only load the 9x14 font for MDA.
|
||||
*/
|
||||
if (this.cardActive === this.cardEGA && this.nFont == Video.FONT.CGA) {
|
||||
if (this.cardEGA.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX] == 7) {
|
||||
if ((this.cardEGA.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX] & Card.CRTC.EGA.MAX_SCAN.SCAN_LINE) == 7) {
|
||||
/*
|
||||
* Vertical resolution of 350 divided by 8 (ie, scan lines 0-7) yields 43 whole rows.
|
||||
*/
|
||||
this.nRows = 43;
|
||||
this.nRows = this.cardEGA.getCRTCReg(Card.CRTC.EGA.VDISP_END) < 350? 43 : 50;
|
||||
}
|
||||
/*
|
||||
* Since we can also be called before any hardware registers have been initialized,
|
||||
|
|
@ -4950,17 +4994,12 @@ Video.prototype.checkMode = function(fForce)
|
|||
}
|
||||
}
|
||||
|
||||
var fSEQDotClock = (card.regSEQData[Card.SEQ.CLOCKING.INDX] & Card.SEQ.CLOCKING.DOTCLOCK);
|
||||
|
||||
var nCRTCVertTotal = card.regCRTData[Card.CRTC.EGA.VTOTAL];
|
||||
nCRTCVertTotal |= ((card.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT8)? 0x100 : 0);
|
||||
if (card.nCard == Video.CARD.VGA) {
|
||||
nCRTCVertTotal |= ((card.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT9)? 0x200 : 0);
|
||||
}
|
||||
|
||||
var nCRTCVertTotal = card.getCRTCReg(Card.CRTC.EGA.VTOTAL);
|
||||
var nCRTCMaxScan = card.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX];
|
||||
var nCRTCModeCtrl = card.regCRTData[Card.CRTC.EGA.MODE_CTRL.INDX];
|
||||
|
||||
var fSEQDotClock = (card.regSEQData[Card.SEQ.CLOCKING.INDX] & Card.SEQ.CLOCKING.DOTCLOCK);
|
||||
|
||||
if (nMode != Video.MODE.UNKNOWN) {
|
||||
if (!(regGRCMisc & Card.GRC.MISC.GRAPHICS)) {
|
||||
/*
|
||||
|
|
@ -4991,6 +5030,11 @@ Video.prototype.checkMode = function(fForce)
|
|||
*/
|
||||
if (card.regGRCData[Card.GRC.MODE.INDX] & Card.GRC.MODE.COLOR256) {
|
||||
if (nCRTCMaxScan & Card.CRTC.EGA.MAX_SCAN.SCAN_LINE) {
|
||||
/*
|
||||
* NOTE: Technically, VDISP_END is one of those CRTC registers that should be read using
|
||||
* card.getCRTCReg(), because there are overflow bits (8 and 9). However, all known modes
|
||||
* always SET bit 8 and CLEAR bit 9, so examining only bits 0-7 is sorta OK.
|
||||
*/
|
||||
if (card.regCRTData[Card.CRTC.EGA.VDISP_END] <= 0x8F) {
|
||||
nMode = Video.MODE.VGA_320X200;
|
||||
}
|
||||
|
|
@ -5052,7 +5096,7 @@ Video.prototype.checkMode = function(fForce)
|
|||
* setMode(nMode, fForce)
|
||||
*
|
||||
* Set fForce to true to update the mode regardless of previous mode, or false to perform
|
||||
* a normal update that bypasses updateScreen() but still calls initCellCache().
|
||||
* a normal update that bypasses updateScreen() but still calls initCache().
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {number|null} nMode
|
||||
|
|
@ -5123,7 +5167,7 @@ Video.prototype.setMode = function(nMode, fForce)
|
|||
}
|
||||
}
|
||||
this.setDimensions();
|
||||
this.invalidateScreen(true);
|
||||
this.invalidateCache(true);
|
||||
this.updateScreen();
|
||||
}
|
||||
return true;
|
||||
|
|
@ -5150,17 +5194,17 @@ Video.prototype.setPixel = function(imageData, x, y, rgb)
|
|||
};
|
||||
|
||||
/**
|
||||
* initCellCache()
|
||||
* initCache()
|
||||
*
|
||||
* Initializes the contents of our internal cell cache.
|
||||
*
|
||||
* TODO: Consider changing this to a cache of RGB values, so that when the buffer is merely being color-cycled,
|
||||
* we don't have to update the entire screen. This will also allow invalidateScreen() to honor the fModified flag,
|
||||
* bypassing initCellCache() when it is false.
|
||||
* we don't have to update the entire screen. This will also allow invalidateCache() to honor the fModified flag,
|
||||
* bypassing initCache() when it is false.
|
||||
*
|
||||
* @this {Video}
|
||||
*/
|
||||
Video.prototype.initCellCache = function()
|
||||
Video.prototype.initCache = function()
|
||||
{
|
||||
this.cBlinkVisible = -1; // invalidate the visible blinking character count, to force updateScreen() to recount
|
||||
this.fCellCacheValid = false;
|
||||
|
|
@ -5171,7 +5215,7 @@ Video.prototype.initCellCache = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* invalidateScreen(fModified)
|
||||
* invalidateCache(fModified)
|
||||
*
|
||||
* Ensure that the next updateScreen() will update every cell; intended for situations where the entire screen needs
|
||||
* to be redrawn, even though the underlying data in the video buffer has not changed (and therefore cleanMemory() will
|
||||
|
|
@ -5182,10 +5226,10 @@ Video.prototype.initCellCache = function()
|
|||
* @this {Video}
|
||||
* @param {boolean} [fModified] (true if the buffer may have been modified, false if only color(s) may have changed)
|
||||
*/
|
||||
Video.prototype.invalidateScreen = function(fModified)
|
||||
Video.prototype.invalidateCache = function(fModified)
|
||||
{
|
||||
if (!fModified) this.fRGBValid = false;
|
||||
this.initCellCache();
|
||||
this.initCache();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -5338,7 +5382,7 @@ Video.prototype.updateChar = function(col, row, data, context)
|
|||
* are the periodic updates coming from the CPU.
|
||||
*
|
||||
* For every cell in the video buffer, compare it to the cell stored in the cell cache, render if it differs,
|
||||
* and then update the cell cache to match. Since initCellCache() sets every cell in the cell cache to an
|
||||
* and then update the cell cache to match. Since initCache() sets every cell in the cell cache to an
|
||||
* invalid value, we're assured that the next call to updateScreen() will redraw the entire (visible) video buffer.
|
||||
*
|
||||
* @this {Video}
|
||||
|
|
@ -5370,7 +5414,7 @@ Video.prototype.updateScreen = function(fForce)
|
|||
if (!fEnabled && !fForce) return;
|
||||
|
||||
if (fForce) {
|
||||
this.initCellCache();
|
||||
this.initCache();
|
||||
}
|
||||
else {
|
||||
/*
|
||||
|
|
@ -5433,7 +5477,14 @@ Video.prototype.updateScreen = function(fForce)
|
|||
* multi-display configuration.
|
||||
*/
|
||||
if ((this.getRetraceBits(card) & Card.CGA.STATUS.VRETRACE) || card.nVertPeriodsStartAddr && card.nVertPeriodsStartAddr < card.nVertPeriods) {
|
||||
card.offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
/*
|
||||
* PARANOIA: Don't call invalidateCache() unless the address we're about to "latch" actually changed.
|
||||
*/
|
||||
var offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
if (card.offStartAddr !== offStartAddr) {
|
||||
card.offStartAddr = offStartAddr;
|
||||
this.invalidateCache();
|
||||
}
|
||||
card.nVertPeriodsStartAddr = 0;
|
||||
}
|
||||
|
||||
|
|
@ -5495,8 +5546,18 @@ Video.prototype.updateScreen = function(fForce)
|
|||
if (!fForce && this.fCellCacheValid && this.bus.cleanMemory(addrScreen, cbScreen)) {
|
||||
if (!fBlinkUpdate) return;
|
||||
if (!this.cBlinkVisible) {
|
||||
if (this.iCellCursor < 0) return;
|
||||
iCell = this.iCellCursor;
|
||||
/*
|
||||
* Note that since iCellCursor is a cell-based (not byte-based) index, we must subtract
|
||||
* offStartAddr, which is also cell-based; subtracting offScreen would not be appropriate,
|
||||
* as it has already been converted to a byte-based offset (remember that in text modes,
|
||||
* cell are words, not bytes).
|
||||
*/
|
||||
iCell = this.iCellCursor - card.offStartAddr;
|
||||
/*
|
||||
* Note that iCellCursor may have already been negative (-1 hides the cursor), and
|
||||
* since offStartAddr should never be negative, we only need one iCell underflow check.
|
||||
*/
|
||||
if (iCell < 0) return;
|
||||
nCells = iCell + 1;
|
||||
}
|
||||
// else if (this.cBlinks & 0x1) return;
|
||||
|
|
@ -5564,6 +5625,12 @@ Video.prototype.updateScreenText = function(addrScreen, addrScreenLimit, iCell,
|
|||
fBlinkEnable = (this.cardActive.regATCData[Card.ATC.MODE.INDX] & Card.ATC.MODE.BLINK_ENABLE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Since iCell is always relative to addrScreen, we must make iCellCursor similarly relative,
|
||||
* otherwise the cursor test below fails when the active video page is something other than page 0.
|
||||
*/
|
||||
var iCellCursor = this.iCellCursor - this.cardActive.offStartAddr;
|
||||
|
||||
if (fBlinkEnable) {
|
||||
dataBlink = (Video.ATTRS.BGND_BLINK << 8);
|
||||
dataMask &= ~dataBlink;
|
||||
|
|
@ -5578,7 +5645,7 @@ Video.prototype.updateScreenText = function(addrScreen, addrScreenLimit, iCell,
|
|||
this.cBlinkVisible++;
|
||||
data &= dataMask;
|
||||
}
|
||||
if (iCell == this.iCellCursor) {
|
||||
if (iCell == iCellCursor) {
|
||||
data |= ((this.cBlinks & 0x1)? (Video.ATTRS.DRAW_CURSOR << 8) : 0);
|
||||
}
|
||||
this.assert(iCell < this.aCellCache.length);
|
||||
|
|
@ -5756,28 +5823,20 @@ Video.prototype.updateScreenGraphicsEGA = function(addrBuffer, addrScreen, addrS
|
|||
if (x < xDirty) xDirty = x;
|
||||
for (iPixel = 0; iPixel < nPixels; iPixel++) {
|
||||
/*
|
||||
* We must follow the golden JavaScript rule of appending "|0" to all hex constants with bit 31 set.
|
||||
* The innocuous use of the bit-wise OR operator has the side-effect of producing a negative value,
|
||||
* matching how entries in Video.aEGADWToByte are initialized (eg, "Video.aEGADWToByte[0x80000000|0]").
|
||||
*/
|
||||
var dwPixel = data & (0x80808080|0);
|
||||
/*
|
||||
* This was the old approach to dealing with negative hex values, by converting them to positive
|
||||
* values that didn't alter the low 32 bits. But it's not ideal, because it requires using values here
|
||||
* and in the array that are outside the signed 32-bit range, potentially triggering floating-point.
|
||||
*
|
||||
* if (dwPixel < 0) dwPixel += 0x100000000;
|
||||
*
|
||||
* An even simpler solution would be to use the unsigned right-shift operator:
|
||||
*
|
||||
* dwPixel >>> 0
|
||||
*
|
||||
* but again, all that does is produce a value outside the signed 32-bit range, which is sub-optimal.
|
||||
* 0x80808080 may LOOK like a 32-bit value, but it is not, because JavaScript treats it as a POSITIVE
|
||||
* number, and therefore outside the normal 32-bit integer range; however, the AND operator guarantees
|
||||
* that the result will be a 32-bit value, so it doesn't matter.
|
||||
*/
|
||||
var dwPixel = data & 0x80808080;
|
||||
this.assert(Video.aEGADWToByte[dwPixel] !== undefined);
|
||||
/*
|
||||
* Since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also ensuring
|
||||
* that bPixel will always default to 0 if an undefined value ever slips through again.
|
||||
* that bPixel will default to 0 if an undefined value ever slips through again.
|
||||
*
|
||||
* How did an undefined value slip through? We had (incorrectly) initialized entries in aEGADWToByte;
|
||||
* for example, we used to set aEGADWToByte[0x80808080] instead of aEGADWToByte[0x80808080|0]. The
|
||||
* former is a POSITIVE index that is outside the 32-bit integer range, whereas the latter is a NEGATIVE
|
||||
* index, which is what this code requires.
|
||||
*/
|
||||
var bPixel = Video.aEGADWToByte[dwPixel] || 0;
|
||||
this.setPixel(this.imageScreenBuffer, x++, y, aPixelColors[bPixel]);
|
||||
|
|
@ -5921,8 +5980,8 @@ Video.prototype.getRetraceBits = function(card)
|
|||
|
||||
/*
|
||||
* NOTE: The CGA bits CGA.STATUS.RETRACE (0x01) and CGA.STATUS.VRETRACE (0x08) match the EGA definitions,
|
||||
* and they also correspond to the MDA bits MDA.STATUS.HDRIVE (0x01) and MDA.STATUS.BWVIDEO (0x08); I'm not sure why
|
||||
* the MDA uses different designations, but the bits appear to serve the same purpose.
|
||||
* and they also correspond to the MDA bits MDA.STATUS.HDRIVE (0x01) and MDA.STATUS.BWVIDEO (0x08); I'm not sure
|
||||
* why the MDA uses different designations, but the bits appear to serve the same purpose.
|
||||
*
|
||||
* TODO: Decide whether this more faithful emulation of the retrace bits should be extended to the MDA/CGA, too;
|
||||
* doing so might slow down the BIOS scroll code a bit, though.
|
||||
|
|
@ -6168,8 +6227,14 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
|
|||
/*
|
||||
* HACK: offStartAddr is supposed to be "latched" ONLY at the start of every VRETRACE interval, but
|
||||
* other "triggers" are helpful; see updateScreen() for details.
|
||||
*
|
||||
* PARANOIA: Don't call invalidateCache() unless the start address we just "latched" actually changed.
|
||||
*/
|
||||
card.offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
var offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
if (card.offStartAddr != offStartAddr) {
|
||||
card.offStartAddr = offStartAddr;
|
||||
this.invalidateCache();
|
||||
}
|
||||
card.nVertPeriodsStartAddr = 0;
|
||||
} else {
|
||||
card.fATCData = false;
|
||||
|
|
@ -6180,7 +6245,7 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
|
|||
this.printMessageIO(port, bOut, addrFrom, "ATC." + card.asATCRegs[iReg]);
|
||||
}
|
||||
card.regATCData[iReg] = bOut;
|
||||
this.invalidateScreen(false);
|
||||
this.invalidateCache(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6516,7 +6581,7 @@ Video.prototype.outDACData = function(port, bOut, addrFrom)
|
|||
var dwNew = (dw & ~(0x3f << this.cardEGA.regDACShift)) | ((bOut & 0x3f) << this.cardEGA.regDACShift);
|
||||
if (dw !== dwNew) {
|
||||
this.cardEGA.regDACData[this.cardEGA.regDACAddr] = dwNew;
|
||||
this.invalidateScreen(false);
|
||||
this.invalidateCache(false);
|
||||
}
|
||||
this.cardEGA.regDACShift += 6;
|
||||
if (this.cardEGA.regDACShift > 12) {
|
||||
|
|
@ -6801,7 +6866,7 @@ Video.prototype.outCGAColor = function(port, bOut, addrFrom)
|
|||
}
|
||||
if (this.cardColor.regColor !== bOut) {
|
||||
this.cardColor.regColor = bOut;
|
||||
this.invalidateScreen(false);
|
||||
this.invalidateCache(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -6915,7 +6980,14 @@ Video.prototype.outCRTCData = function(card, port, bOut, addrFrom)
|
|||
* the vertical period count and latch it later, in updateScreen(), once the count has advanced.
|
||||
*/
|
||||
if (this.getRetraceBits(card) & Card.CGA.STATUS.RETRACE) {
|
||||
card.offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
/*
|
||||
* PARANOIA: Don't call invalidateCache() unless the address we're about to "latch" actually changed.
|
||||
*/
|
||||
var offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
if (card.offStartAddr !== offStartAddr) {
|
||||
card.offStartAddr = offStartAddr;
|
||||
this.invalidateCache();
|
||||
}
|
||||
} else if (!card.nVertPeriodsStartAddr) {
|
||||
card.nVertPeriodsStartAddr = card.nVertPeriods;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1116,4 +1116,8 @@
|
|||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="comment">
|
||||
<xsl:comment><xsl:apply-templates/></xsl:comment>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ var DumpAPI = {
|
|||
FORMAT: "format", // value is one of FORMAT values below
|
||||
COMMENTS: "comments", // value is either "true" or "false"
|
||||
DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise
|
||||
MBHD: "mbhd" // value is hard disk size in Mb (formerly "mbsize") (DiskDump only)
|
||||
MBHD: "mbhd", // value is hard disk size in Mb (formerly "mbsize") (DiskDump only) (DEPRECATED)
|
||||
SIZE: "size" // value is target disk size in Kb (supersedes "mbhd") (DiskDump only)
|
||||
},
|
||||
FORMAT: {
|
||||
JSON: "json", // default
|
||||
|
|
|
|||
|
|
@ -630,21 +630,21 @@ web.downloadFile = function(sData, sType, fBase64, sFileName)
|
|||
sURI += (fBase64? sData : encodeURI(sData));
|
||||
} else {
|
||||
sURI += (fBase64? sData : encodeURIComponent(sData));
|
||||
if (sFileName) {
|
||||
link = document.createElement('a');
|
||||
if (typeof link.download != 'string') link = null;
|
||||
}
|
||||
}
|
||||
if (sFileName) {
|
||||
link = document.createElement('a');
|
||||
if (typeof link.download != 'string') link = null;
|
||||
}
|
||||
if (link) {
|
||||
link.href = sURI;
|
||||
link.download = sFileName;
|
||||
document.body.appendChild(link); // Firefox requires the link to be in the body
|
||||
document.body.appendChild(link); // Firefox allegedly requires the link to be in the body
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
sAlert = 'Check your Downloads folder for "' + sFileName + '"';
|
||||
sAlert = 'Check your Downloads folder for ' + sFileName + '.';
|
||||
} else {
|
||||
window.open(sURI);
|
||||
sAlert = 'Check your browser for a new window/tab containing the requested data';
|
||||
sAlert = 'Check your browser for a new window/tab containing the requested data (' + sFileName + ').';
|
||||
}
|
||||
return sAlert;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue