Black Book demo L23-1 works now
This commit is contained in:
parent
9bdb9cbc41
commit
ef3873da6e
4 changed files with 158 additions and 60 deletions
|
|
@ -3099,8 +3099,21 @@ if (DEBUGGER) {
|
|||
*/
|
||||
var fBreak = false;
|
||||
if (!this.nBreakSuppress++) {
|
||||
|
||||
addr = this.mapBreakpoint(addr);
|
||||
for (var i = 1; i < aBreak.length; i++) {
|
||||
|
||||
/*
|
||||
* As discussed in opINT3(), I decided to check for INT3 instructions here: we'll tell the CPU to
|
||||
* stop on INT3 whenever both the INT and HALT message bits are set; a simple "g" command allows you
|
||||
* to continue.
|
||||
*/
|
||||
if (this.messageEnabled(Messages.INT | Messages.HALT)) {
|
||||
if (this.cpu.probeAddr(addr) == X86.OPCODE.INT3) {
|
||||
fBreak = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 1; !fBreak && i < aBreak.length; i++) {
|
||||
|
||||
var dbgAddrBreak = aBreak[i];
|
||||
|
||||
|
|
@ -3129,7 +3142,6 @@ if (DEBUGGER) {
|
|||
this.println("breakpoint hit: " + this.hexAddr(dbgAddrBreak) + " (" + aBreak[0] + ")");
|
||||
}
|
||||
fBreak = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -975,11 +975,11 @@ function Card(video, iCard, data, cbMemory)
|
|||
var monitorSpecs = Video.monitorSpecs[nMonitorType] || Video.monitorSpecs[ChipSet.MONITOR.MONO];
|
||||
|
||||
var nCyclesPerSecond = video.cpu.getCyclesPerSecond(); // eg, 4772727
|
||||
this.nCyclesHorzPeriod = (nCyclesPerSecond / monitorSpecs.nHorzPeriodsPerSec) | 0;
|
||||
this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100) | 0;
|
||||
this.nCyclesVertPeriod = this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame;
|
||||
this.nCyclesVertActive = (this.nCyclesVertPeriod * monitorSpecs.percentVertActive / 100) | 0;
|
||||
this.nInitCycles = (data[7] == null? 0 : data[7]);
|
||||
this.nCyclesHorzPeriod = (nCyclesPerSecond / monitorSpecs.nHorzPeriodsPerSec)|0;
|
||||
this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100)|0;
|
||||
this.nCyclesVertPeriod = (this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame)|0;
|
||||
this.nCyclesVertActive = (this.nCyclesVertPeriod * monitorSpecs.percentVertActive / 100)|0;
|
||||
this.nInitCycles = (data[7] || 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2031,12 +2031,13 @@ Card.prototype.initEGA = function(data, nMonitorType)
|
|||
/*22*/ 0,
|
||||
/*23*/ 0,
|
||||
/*24*/ 0,
|
||||
/*25*/ Card.VGA_ENABLE.ENABLED,
|
||||
/*26*/ Card.DAC.MASK.DEFAULT,
|
||||
/*27*/ 0,
|
||||
/*25*/ 0,
|
||||
/*26*/ Card.VGA_ENABLE.ENABLED,
|
||||
/*27*/ Card.DAC.MASK.DEFAULT,
|
||||
/*28*/ 0,
|
||||
/*29*/ Card.DAC.STATE.MODE_WRITE,
|
||||
/*30*/ new Array(Card.DAC.TOTAL_REGS)
|
||||
/*29*/ 0,
|
||||
/*30*/ Card.DAC.STATE.MODE_WRITE,
|
||||
/*31*/ new Array(Card.DAC.TOTAL_REGS)
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -2112,14 +2113,15 @@ Card.prototype.initEGA = function(data, nMonitorType)
|
|||
this.nSetMapBits = data[22];
|
||||
this.nColorCompare = data[23];
|
||||
this.nColorDontCare = data[24];
|
||||
this.nStartAddress = data[25]; // this is the last CRTC start address latched from CRTC.START_ADDR_HI,CRTC.START_ADDR_LO
|
||||
|
||||
if (this.nCard == Video.CARD.VGA) {
|
||||
this.regVGAEnable = data[25];
|
||||
this.regDACMask = data[26];
|
||||
this.regDACAddr = data[27];
|
||||
this.regDACShift = data[28];
|
||||
this.regDACState = data[29];
|
||||
this.regDACData = data[30];
|
||||
this.regVGAEnable = data[26];
|
||||
this.regDACMask = data[27];
|
||||
this.regDACAddr = data[28];
|
||||
this.regDACShift = data[29];
|
||||
this.regDACState = data[30];
|
||||
this.regDACData = data[31];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -2181,14 +2183,15 @@ Card.prototype.saveEGA = function()
|
|||
data[22] = this.nSetMapBits;
|
||||
data[23] = this.nColorCompare;
|
||||
data[24] = this.nColorDontCare;
|
||||
data[25] = this.nStartAddress;
|
||||
|
||||
if (this.nCard == Video.CARD.VGA) {
|
||||
data[25] = this.regVGAEnable;
|
||||
data[26] = this.regDACMask;
|
||||
data[27] = this.regDACAddr;
|
||||
data[28] = this.regDACShift;
|
||||
data[29] = this.regDACState;
|
||||
data[30] = this.regDACData;
|
||||
data[26] = this.regVGAEnable;
|
||||
data[27] = this.regDACMask;
|
||||
data[28] = this.regDACAddr;
|
||||
data[29] = this.regDACShift;
|
||||
data[30] = this.regDACState;
|
||||
data[31] = this.regDACData;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
|
@ -4625,12 +4628,14 @@ Video.prototype.updateScreen = function(fForce)
|
|||
* unless fForce is set.
|
||||
*/
|
||||
var fEnabled = false;
|
||||
if (this.cardActive) {
|
||||
if (this.cardActive === this.cardEGA) {
|
||||
if (this.cardEGA.regATCIndx & Card.ATC.INDX_PAL_ENABLE) fEnabled = true;
|
||||
var card = this.cardActive;
|
||||
|
||||
if (card) {
|
||||
if (card !== this.cardEGA) {
|
||||
if (card.regMode & Card.CGA.MODE.VIDEO_ENABLE) fEnabled = true;
|
||||
}
|
||||
else {
|
||||
if (this.cardActive.regMode & Card.CGA.MODE.VIDEO_ENABLE) fEnabled = true;
|
||||
if (card.regATCIndx & Card.ATC.INDX_PAL_ENABLE) fEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4668,9 +4673,25 @@ Video.prototype.updateScreen = function(fForce)
|
|||
* to follow. FYI, in these calculations, offScreen does not refer to "off-screen" memory,
|
||||
* but rather the "offset" of the start of visible screen memory.
|
||||
*/
|
||||
var addrScreen = this.cardActive.addrBuffer;
|
||||
var addrScreenLimit = addrScreen + this.cardActive.sizeBuffer;
|
||||
var offScreen = ((this.cardActive.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + this.cardActive.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
var addrScreen = card.addrBuffer;
|
||||
var addrScreenLimit = addrScreen + card.sizeBuffer;
|
||||
|
||||
/*
|
||||
* HACK: nStartAddress is supposed to be "latched" ONLY at the start of every VERT_RETRACE interval;
|
||||
* this is an attempt to honor that behavior, but unfortunately, updateScreen() is currently called at
|
||||
* the CPU's discretion, not necessarily in sync with nCyclesVertPeriod. As a result, we must rely
|
||||
* on other "triggers" to update our latched CRTC start address (eg, see outATC()).
|
||||
*
|
||||
* TODO: Consider matching the CPU's nCyclesNextVideoUpdate to the card's nCyclesVertPeriod, ensuring
|
||||
* that CPU bursts are in sync with VERT_RETRACE. Note, however, that that will be complicated by other
|
||||
* factors, such as the horizontal retrace interval, and the timing requirements of other cards in a
|
||||
* multi-display configuration.
|
||||
*/
|
||||
if (this.getRetraceBits(card) & Card.CGA.STATUS.VERT_RETRACE) {
|
||||
card.nStartAddress = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
}
|
||||
|
||||
var offScreen = card.nStartAddress;
|
||||
|
||||
/*
|
||||
* Any screen (aka "page") offset must be doubled for text modes, due to the attribute bytes.
|
||||
|
|
@ -4682,14 +4703,14 @@ Video.prototype.updateScreen = function(fForce)
|
|||
addrScreen += offScreen;
|
||||
var cbScreen = this.cbScreen;
|
||||
|
||||
if (this.nCard >= Video.CARD.EGA && this.cardActive.regCRTData[Card.CRTC.EGA.OFFSET]) {
|
||||
if (this.nCard >= Video.CARD.EGA && card.regCRTData[Card.CRTC.EGA.OFFSET]) {
|
||||
/*
|
||||
* Pre-EGA, the extent of visible screen memory (cbScreen) was derived from nCols * nRows, but since
|
||||
* then, the logical width of screen memory (nColsLogical) can differ from the visible width (nCols).
|
||||
* We now calculate the logical width, and the compute a new cbScreen in much the same way the original
|
||||
* cbScreen was computed (but without any CGA-related padding considerations).
|
||||
*/
|
||||
this.nColsLogical = this.cardActive.regCRTData[Card.CRTC.EGA.OFFSET] << (this.nFont? 1 : 4);
|
||||
this.nColsLogical = card.regCRTData[Card.CRTC.EGA.OFFSET] << (this.nFont? 1 : 4);
|
||||
cbScreen = ((((this.nColsLogical * (this.nRows-1) + this.nCols) / this.nCellsPerWord) << 1) + this.cbPadding)|0;
|
||||
}
|
||||
|
||||
|
|
@ -5010,6 +5031,48 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit)
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getRetraceBits(card)
|
||||
*
|
||||
* This returns a byte value with two bits set or clear as appropriate: DISP_RETRACE and VERT_RETRACE.
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {Object} card
|
||||
* @return {number}
|
||||
*/
|
||||
Video.prototype.getRetraceBits = function(card)
|
||||
{
|
||||
var b = 0;
|
||||
|
||||
/*
|
||||
* NOTE: The CGA bits CGA.STATUS.DISP_RETRACE (0x01) and CGA.STATUS.VERT_RETRACE (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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
var nCycles = this.cpu.getCycles();
|
||||
var nElapsedCycles = nCycles - card.nInitCycles;
|
||||
if (nElapsedCycles < 0) nElapsedCycles = 0; // TODO: Determine if this ever happens
|
||||
var nCyclesHorzRemain = nElapsedCycles % card.nCyclesHorzPeriod;
|
||||
if (nCyclesHorzRemain > card.nCyclesHorzActive) b |= Card.CGA.STATUS.DISP_RETRACE;
|
||||
var nCyclesVertRemain = nElapsedCycles % card.nCyclesVertPeriod;
|
||||
if (nCyclesVertRemain > card.nCyclesVertActive) b |= Card.CGA.STATUS.VERT_RETRACE | Card.CGA.STATUS.DISP_RETRACE;
|
||||
/*
|
||||
* This is optional: the number of CPU cycles that remain in the current vertical period is all we need to keep
|
||||
* track of (the number of cycles since the card was initialized is fine, too, but that delta can become extremely
|
||||
* large after a while).
|
||||
*
|
||||
* card.nInitCycles = nCycles - nCyclesVertRemain;
|
||||
*
|
||||
* NOTE: Now that we're calling getRetraceBits() more frequently (ie, for internal checks), resetting nInitCycles
|
||||
* in this fashion preserves the vertical period at the expense of the horizontal period, which in turn can cause
|
||||
* grief in ROM BIOS code that requires strict horizontal retrace times. So the above code is now disabled.
|
||||
*/
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inMDAIndx(port, addrFrom)
|
||||
*
|
||||
|
|
@ -5162,6 +5225,11 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
|
|||
this.updateScreen(true);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* HACK: nStartAddress is supposed to be "latched" ONLY at the start of every VERT_RETRACE interval,
|
||||
* but other "triggers" are currently required; see updateScreen() for details.
|
||||
*/
|
||||
this.cardEGA.nStartAddress = ((this.cardEGA.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + this.cardEGA.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
} else {
|
||||
var iReg = this.cardEGA.regATCIndx & Card.ATC.INDX_MASK;
|
||||
if (iReg >= Card.ATC.PALETTE_REGS || !fPalEnabled) {
|
||||
|
|
@ -5873,6 +5941,15 @@ Video.prototype.outCRTCData = function(card, port, bOut, addrFrom)
|
|||
}
|
||||
card.regCRTData[card.regCRTIndx] = bOut;
|
||||
}
|
||||
if (card.regCRTIndx == Card.CRTC.START_ADDR_HI || card.regCRTIndx == Card.CRTC.START_ADDR_LO) {
|
||||
/*
|
||||
* HACK: nStartAddress is supposed to be "latched" ONLY at the start of every VERT_RETRACE interval,
|
||||
* but the best we can currently do is latch it during retrace, as well as other times (eg, see outATC()).
|
||||
*/
|
||||
if (this.getRetraceBits(card) & Card.CGA.STATUS.DISP_RETRACE) {
|
||||
card.nStartAddress = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* During mode changes on the EGA, all the CRTC regs are typically programmed in sequence,
|
||||
* and if that's all that's happening with Card.CRTC.MAX_SCAN_LINE, then we don't want to treat
|
||||
|
|
@ -5938,29 +6015,7 @@ Video.prototype.outCardMode = function(card, bOut, addrFrom)
|
|||
*/
|
||||
Video.prototype.inCardStatus = function(card, addrFrom)
|
||||
{
|
||||
var b = 0;
|
||||
|
||||
/*
|
||||
* NOTE: The CGA bits CGA.STATUS.DISP_RETRACE (0x01) and CGA.STATUS.VERT_RETRACE (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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
var nCycles = this.cpu.getCycles();
|
||||
var nElapsedCycles = nCycles - card.nInitCycles;
|
||||
if (nElapsedCycles < 0) nElapsedCycles = 0; // TODO: Determine if this ever happens
|
||||
var nCyclesHorzRemain = nElapsedCycles % card.nCyclesHorzPeriod;
|
||||
if (nCyclesHorzRemain > card.nCyclesHorzActive) b |= Card.CGA.STATUS.DISP_RETRACE;
|
||||
var nCyclesVertRemain = nElapsedCycles % card.nCyclesVertPeriod;
|
||||
if (nCyclesVertRemain > card.nCyclesVertActive) b |= Card.CGA.STATUS.VERT_RETRACE;
|
||||
/*
|
||||
* This is optional: the number of CPU cycles that remain in the current vertical period is all we need to keep
|
||||
* track of (the number of cycles since the card was initialized is fine, too, but that delta can become extremely
|
||||
* large after a while).
|
||||
*/
|
||||
card.nInitCycles = nCycles - nCyclesVertRemain;
|
||||
var b = this.getRetraceBits(card);
|
||||
|
||||
if (card === this.cardEGA) {
|
||||
/*
|
||||
|
|
@ -6001,9 +6056,13 @@ Video.prototype.inCardStatus = function(card, addrFrom)
|
|||
*
|
||||
* Also, according to http://www.seasip.info/VintagePC/mda.html, on an MDA, bits 7-4 are always ON and
|
||||
* bits 2-1 are always OFF, hence the "OR" of 0xf0.
|
||||
*
|
||||
* TODO: Decide whether to preserve the bits from getRetraceBits() on the MDA/CGA; we're continuing
|
||||
* to do a simple toggle, partly on the theory that that may speed up the CGA BIOS scroll code a bit.
|
||||
*/
|
||||
b = (card.regStatus ^= (Card.CGA.STATUS.DISP_RETRACE | Card.CGA.STATUS.VERT_RETRACE)) | 0xf0;
|
||||
}
|
||||
|
||||
card.regStatus = b;
|
||||
this.printMessageIO(card.port + 6, null, addrFrom, (card === this.cardEGA? "STATUS1" : "STATUS"), b);
|
||||
return b;
|
||||
|
|
|
|||
|
|
@ -3576,7 +3576,7 @@ X86.fnSrcNone = function SrcNone()
|
|||
};
|
||||
|
||||
/**
|
||||
* fnFault(nFault, nError, fHalt)
|
||||
* fnFault(nFault, nError, fHalt, nCycles)
|
||||
*
|
||||
* Helper to dispatch faults.
|
||||
*
|
||||
|
|
@ -3584,8 +3584,9 @@ X86.fnSrcNone = function SrcNone()
|
|||
* @param {number} nFault
|
||||
* @param {number} [nError] (if omitted, no error code will be pushed)
|
||||
* @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded
|
||||
* @param {number} [nCycles] cycle count to pass through to fnINT(), if any
|
||||
*/
|
||||
X86.fnFault = function(nFault, nError, fHalt)
|
||||
X86.fnFault = function(nFault, nError, fHalt, nCycles)
|
||||
{
|
||||
if (!this.aFlags.fComplete) {
|
||||
this.printMessage("Fault " + str.toHexByte(nFault) + " blocked by Debugger", Messages.WARN);
|
||||
|
|
@ -3623,7 +3624,7 @@ X86.fnFault = function(nFault, nError, fHalt)
|
|||
fDispatch = false;
|
||||
}
|
||||
|
||||
if (fDispatch) X86.fnINT.call(this, this.nFault = nFault, nError, 0);
|
||||
if (fDispatch) X86.fnINT.call(this, this.nFault = nFault, nError, nCycles || 0);
|
||||
|
||||
/*
|
||||
* Since this fault is likely being issued in the context of an instruction that hasn't finished
|
||||
|
|
@ -3696,7 +3697,7 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
|
|||
*
|
||||
* When a triple fault shows up, nFault is -1; it displays as 0xff only because we use toHexByte().
|
||||
*/
|
||||
if (bOpcode == X86.OPCODE.INT3) {
|
||||
if (bOpcode == X86.OPCODE.INT3 && !this.addrIDTLimit) {
|
||||
fHalt = false;
|
||||
bitsMessage |= Messages.CPU;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3322,6 +3322,32 @@ X86.opRETF = function RETF()
|
|||
*/
|
||||
X86.opINT3 = function INT3()
|
||||
{
|
||||
/*
|
||||
* To give our own Debugger the ability to stop execution on INT3, I thought about treating this as
|
||||
* a fault rather than an interrupt, in order to leverage the existing Debugger logic inside fnFault()
|
||||
* processing, but that has the unwanted side-effect of rewinding EIP to the INT3 prior to issuing
|
||||
* the interrupt, and the corresponding IRET takes us right back to the INT3.
|
||||
*
|
||||
* X86.fnFault.call(this, X86.EXCEPTION.BREAKPOINT, null, false, this.cycleCounts.nOpCyclesInt3D);
|
||||
*
|
||||
* Then I had the idea of using the fnFaultMessage() function, in much the same way that fnFault()
|
||||
* does for actual faults: if the user turned on the FAULT and HALT message bits, then fnFaultMessage()
|
||||
* would tell us to halt; otherwise, we'd perform the normal fnINT() call.
|
||||
*
|
||||
* if (X86.fnFaultMessage.call(this, X86.EXCEPTION.BREAKPOINT)) {
|
||||
* this.setIP(this.opLIP - this.segCS.base);
|
||||
* return;
|
||||
* }
|
||||
*
|
||||
* However, that makes it a little tedious to get past the INT3 (you have to use a Debugger command
|
||||
* like "t;g"), and a somewhat confusing fault message is displayed; eg:
|
||||
*
|
||||
* Fault 0x03 on opcode 0xB4 at 09CE:0155 (%009E35)
|
||||
*
|
||||
* The best solution was to leave this function alone, and change the Debugger's checkBreakpoint()
|
||||
* function to stop execution on INT3 whenever both the INT and HALT message bits are set; a simple "g"
|
||||
* command allows you to continue.
|
||||
*/
|
||||
X86.fnINT.call(this, X86.EXCEPTION.BREAKPOINT, null, this.cycleCounts.nOpCyclesInt3D);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue