Assorted 80386 and Debugger fixes

This commit is contained in:
Jeff Parsons 2015-05-01 14:21:23 -07:00 committed by jeffpar
commit 73e52134ac
20 changed files with 1409 additions and 1372 deletions

View file

@ -20,6 +20,6 @@
<item ref="/apps/c1p/BASIC/OSI/SAMPLE6.BAS">PRESIDENTS</item>
<item ref="/sites/www.osiweb.org/programs/basic/OsiBas/spacewar.bas">SPACEWAR</item>
</control>
<control type="button" binding="loadSerial">Download</control>
<control type="button" binding="loadSerial">Load</control>
<control type="file" binding="mountSerial" style="padding-left:16px"/>
</serial>

View file

@ -9,6 +9,6 @@
<item ref="/apps/c1p/BASIC/OSI/SAMPLE5.BAS">COUNTER</item>
<item ref="/apps/c1p/BASIC/OSI/SAMPLE6.BAS">PRESIDENTS</item>
</control>
<control type="button" binding="loadSerial">Download</control>
<control type="button" binding="loadSerial">Load</control>
<control type="file" binding="mountSerial" style="padding-left:16px"/>
</serial>

View file

@ -45,8 +45,8 @@ Similarly, [1989-04-14.json](1989-04-14/1989-04-14.json) was generated by first
from the two 16Kb BIN files provided by [Al Kossow](http://www.vintage-computer.com/vcforum/member.php?2256-Al-Kossow):
cd 1989-04-14
filedump --file=private/109592-005.U11.bin --merge=private/109591-005.U13.bin --output=private/1989-04-14.rom --format=rom
filedump --file=private/1989-04-14.rom --output=1989-04-14.json
filedump --file=static/109592-005.U11.bin --merge=static/109591-005.U13.bin --output=static/1989-04-14.rom --format=rom
filedump --file=static/1989-04-14.rom --output=1989-04-14.json
Dumping the ROMs
---

View file

@ -23,7 +23,7 @@
<manifest ref="/apps/pc/1981/visicalc/manifest.xml" disk="*"/>
<manifest ref="/apps/pc/1982/esuite/manifest.xml" disk="*"/>
</control>
<control type="button" binding="loadDrive">Download</control>
<control type="button" binding="loadDrive">Load</control>
<control type="description" binding="descDisk" padleft="8px"/>
<control type="file" binding="mountDrive" padtop="8px"/>
</fdc>

View file

@ -23,7 +23,7 @@
<manifest ref="/apps/pc/1981/visicalc/manifest.xml" disk="*"/>
<manifest ref="/apps/pc/1982/esuite/manifest.xml" disk="*"/>
</control>
<control type="button" binding="loadDrive" padright="8px">Download</control>
<control type="button" binding="loadDrive" padright="8px">Load</control>
<control type="description" binding="descDisk" padright="8px"/>
<control type="file" binding="mountDrive"/>
</fdc>

View file

@ -9,6 +9,6 @@
<manifest ref="/disks/pc/games/infocom/zork2/manifest.xml" disk="*"/>
<manifest ref="/disks/pc/games/infocom/zork3/manifest.xml" disk="*"/>
</control>
<control type="button" binding="loadDrive" padright="8px">Download</control>
<control type="button" binding="loadDrive" padright="8px">Load</control>
<control type="description" binding="descDisk" padright="8px"/>
</fdc>

View file

@ -299,7 +299,7 @@ function genSIB(sib) {
sMod = "((this.segData = this.segStack), " + sMod + ")";
}
}
var sBase = (base == 5? "(mod? " + sMod + " : this.getIPWord())" : "this.reg" + aBase[base]);
var sBase = (base == 5? "(mod? " + sMod + " : this.getIPAddr())" : "this.reg" + aBase[base]);
if (index != 4) sBase += " + " + (scale? ("(this.reg" + aIndex[index] + " << " + scale + ")") : ("this.reg" + aIndex[index]));
print(" return " + sBase + ";");
print(" }" + (sib < 255? "," : ""));
@ -473,7 +473,7 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
nCycles = "this.cycleCounts.nEACyclesBase"; // 8086: 5
break;
case 6:
sModAddr = "this.getIPWord()";
sModAddr = "this.getIPAddr()";
sModFunc = "D16";
nCycles = "this.cycleCounts.nEACyclesDisp"; // 8086: 6
break;
@ -540,45 +540,45 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 2:
switch (r_m) {
case 0:
sModAddr = "this.regEBX + this.regESI + this.getIPWord()";
sModAddr = "this.regEBX + this.regESI + this.getIPAddr()";
sModFunc = "BXSID16";
nCycles = "this.cycleCounts.nEACyclesBaseIndexDisp"; // 8086: 11
break;
case 1:
sModAddr = "this.regEBX + this.regEDI + this.getIPWord()";
sModAddr = "this.regEBX + this.regEDI + this.getIPAddr()";
sModFunc = "BXDID16";
nCycles = "this.cycleCounts.nEACyclesBaseIndexDispExtra"; // 8086: 12
break;
case 2:
sModAddr = "this.regEBP + this.regESI + this.getIPWord()";
sModAddr = "this.regEBP + this.regESI + this.getIPAddr()";
sModFunc = "BPSID16";
sModRegSeg = "Stack";
nCycles = "this.cycleCounts.nEACyclesBaseIndexDispExtra"; // 8086: 12
break;
case 3:
sModAddr = "this.regEBP + this.regEDI + this.getIPWord()";
sModAddr = "this.regEBP + this.regEDI + this.getIPAddr()";
sModFunc = "BPDID16";
sModRegSeg = "Stack";
nCycles = "this.cycleCounts.nEACyclesBaseIndexDisp"; // 8086: 11
break;
case 4:
sModAddr = "this.regESI + this.getIPWord()";
sModAddr = "this.regESI + this.getIPAddr()";
sModFunc = "SID16";
nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break;
case 5:
sModAddr = "this.regEDI + this.getIPWord()";
sModAddr = "this.regEDI + this.getIPAddr()";
sModFunc = "DID16";
nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break;
case 6:
sModAddr = "this.regEBP + this.getIPWord()";
sModAddr = "this.regEBP + this.getIPAddr()";
sModFunc = "BPD16";
sModRegSeg = "Stack";
nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break;
case 7:
sModAddr = "this.regEBX + this.getIPWord()";
sModAddr = "this.regEBX + this.getIPAddr()";
sModFunc = "BXD16";
nCycles = "this.cycleCounts.nEACyclesBaseDisp"; // 8086: 9
break;
@ -729,7 +729,7 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
nCycles = "this.cycleCounts.nEACyclesBase";
break;
case 5:
sModAddr = "this.getIPWord()";
sModAddr = "this.getIPAddr()";
sModFunc = "D32";
nCycles = "this.cycleCounts.nEACyclesDisp";
break;
@ -799,43 +799,43 @@ function genMode(a, d, w, mrm, sGroup, sRO) {
case 2:
switch (r_m) {
case 0:
sModAddr = "this.regEAX + this.getIPWord()";
sModAddr = "this.regEAX + this.getIPAddr()";
sModFunc = "EAXD32";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;
case 1:
sModAddr = "this.regECX + this.getIPWord()";
sModAddr = "this.regECX + this.getIPAddr()";
sModFunc = "ECXD32";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;
case 2:
sModAddr = "this.regEDX + this.getIPWord()";
sModAddr = "this.regEDX + this.getIPAddr()";
sModFunc = "EDXD32";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;
case 3:
sModAddr = "this.regEBX + this.getIPWord()";
sModAddr = "this.regEBX + this.getIPAddr()";
sModFunc = "EBXD32";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;
case 4:
sModAddr = "this.getSIBAddr(2) + this.getIPWord()";
sModAddr = "this.getSIBAddr(2) + this.getIPAddr()";
sModFunc = "SIBD32";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;
case 5:
sModAddr = "this.regEBP + this.getIPWord()";
sModAddr = "this.regEBP + this.getIPAddr()";
sModFunc = "EBPD32";
sModRegSeg = "Stack";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;
case 6:
sModAddr = "this.regESI + this.getIPWord()";
sModAddr = "this.regESI + this.getIPAddr()";
sModFunc = "ESID32";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;
case 7:
sModAddr = "this.regEDI + this.getIPWord()";
sModAddr = "this.regEDI + this.getIPAddr()";
sModFunc = "EDID32";
nCycles = "this.cycleCounts.nEACyclesBaseDisp";
break;

View file

@ -1373,9 +1373,28 @@ if (DEBUGGER) {
*/
Debugger.prototype.newAddr = function(off, seg, addr, fData32, fAddr32)
{
if (fData32 === undefined) fData32 = (this.cpu.segCS.dataSize == 4);
if (fAddr32 === undefined) fAddr32 = (this.cpu.segCS.addrSize == 4);
return [off, seg, addr, false, fData32, fAddr32];
};
/**
* checkLimit(aAddr)
*
* @this {Debugger}
* @param {Array} aAddr containing [off, seg, addr]
*/
Debugger.prototype.checkLimit = function(aAddr)
{
if (aAddr[1] != null) {
var limit = this.getSegment(aAddr[1]).limit;
if (aAddr[0] > limit) {
aAddr[0] = 0;
aAddr[2] = null;
}
}
};
/**
* incAddr(aAddr, inc)
*
@ -1391,11 +1410,7 @@ if (DEBUGGER) {
}
if (aAddr[1] != null) {
aAddr[0] += inc;
var limit = this.getSegment(aAddr[1]).limit;
if (aAddr[0] > limit) {
aAddr[0] = 0;
aAddr[2] = null;
}
this.checkLimit(aAddr);
}
};
@ -1408,22 +1423,22 @@ if (DEBUGGER) {
*/
Debugger.prototype.hexAddr = function(aAddr)
{
return aAddr[1] == null? ("%" + str.toHex(aAddr[2])) : this.hexOffset(aAddr[0], aAddr[1], aAddr[4]);
return aAddr[1] == null? ("%" + str.toHex(aAddr[2])) : this.hexOffset(aAddr[0], aAddr[1], aAddr[5]);
};
/**
* hexOffset(off, sel)
* hexOffset(off, sel, fAddr32)
*
* @this {Debugger}
* @param {number} off
* @param {number} [sel]
* @param {boolean} [fData32] is true if 32-bit OPERAND size in effect
* @param {boolean} [fAddr32] is true to force 32-bit ADDRESS size
* @return {string} the hex representation of off (or sel:off)
*/
Debugger.prototype.hexOffset = function(off, sel, fData32)
Debugger.prototype.hexOffset = function(off, sel, fAddr32)
{
if (sel !== undefined) {
return str.toHex(sel, 4) + ":" + str.toHex(off, fData32? 8 : 4);
return str.toHex(sel, 4) + ":" + str.toHex(off, (off & (0xffff0000|0)) || fAddr32? 8 : 4);
}
return str.toHex(off);
};
@ -2958,14 +2973,16 @@ if (DEBUGGER) {
*/
Debugger.prototype.getInstruction = function(aAddr, sComment, nSequence)
{
var aAddrIns = this.newAddr(aAddr[0], aAddr[1], aAddr[2], this.cpu.segCS.addrSize == 4);
var aAddrIns = this.newAddr(aAddr[0], aAddr[1], aAddr[2]);
var bOpcode = this.getByte(aAddr, 1);
/*
* Incorporate the following prefixes into the current instruction's byte stream
* Incorporate the following prefixes into the current instruction byte stream.
* TODO: Determine the actual effect of multiple OS (and/or multiple AS) prefixes.
*/
if (bOpcode == X86.OPCODE.OS || bOpcode == X86.OPCODE.AS) {
var cMax = 2; // let's make sure unfortunate memory contents don't screw us
while ((bOpcode == X86.OPCODE.OS || bOpcode == X86.OPCODE.AS) && cMax--) {
if (bOpcode == X86.OPCODE.OS) {
aAddr[4] = !aAddr[4];
} else {
@ -3134,7 +3151,7 @@ if (DEBUGGER) {
sOperand = str.toHex(this.getShort(aAddr, 2), 4);
break;
case Debugger.TYPE_FARP:
sOperand = this.hexAddr(this.newAddr(this.getWord(aAddr, 2), this.getShort(aAddr, 2), null, aAddr[4]));
sOperand = this.hexAddr(this.newAddr(this.getWord(aAddr, 2), this.getShort(aAddr, 2), null, aAddr[4], aAddr[5]));
break;
default:
sOperand = "imm(" + str.toHexWord(type) + ")";
@ -3550,7 +3567,9 @@ if (DEBUGGER) {
addr = null;
}
}
return [off, seg, addr];
var aAddr = [off, seg, addr];
this.checkLimit(aAddr);
return aAddr;
};
/**
@ -4021,14 +4040,13 @@ if (DEBUGGER) {
* because we always display whole lines. If sLen is omitted/undefined, then we default to 8 lines, regardless
* whether dumping bytes or words.
*
* Also, unlike sAddr, sLen is interpreted as a decimal number, unless a radix specifier in included (eg, "0x100").
*
* And finally, sLen also support the DEBUG.COM-style syntax of a preceding "l" (eg, "l16").
* Also, unlike sAddr, sLen is interpreted as a decimal number, unless a radix specifier is included (eg, "0x100");
* sLen also supports the DEBUG.COM-style syntax of a preceding "l" (eg, "l16").
*
* @this {Debugger}
* @param {string} sCmd
* @param {string|undefined} sAddr
* @param {string|undefined} sLen (if present, it can be preceded by an "l", which we simply ignore; this is purely for historical reasons)
* @param {string|undefined} sLen (if present, it can be preceded by an "l", which we simply ignore)
*/
Debugger.prototype.doDump = function(sCmd, sAddr, sLen)
{
@ -4046,6 +4064,7 @@ if (DEBUGGER) {
this.println("\tdb [a] [#] dump # bytes at address a");
if (BACKTRACK) this.println("\tdi [a] dump backtrack info at address a");
this.println("\tdw [a] [#] dump # words at address a");
this.println("\tdd [a] [#] dump # dwords at address a");
if (sDumpers.length) this.println("dump extensions:\n\t" + sDumpers);
return;
}
@ -4085,32 +4104,30 @@ if (DEBUGGER) {
}
else {
var cLines = 0;
var fWords = (sCmd == "dw");
if (sLen !== undefined) {
var cBytes = (sCmd == "dd"? 4 : (sCmd == "dw"? 2 : 1));
var cNumbers = (16 / cBytes)|0;
if (sLen) {
if (sLen.charAt(0) == "l") sLen = sLen.substr(1);
cLines = +sLen;
if (cLines) cLines = fWords? ((cLines + 7) >> 3) : ((cLines + 15) >> 4);
if (cLines) cLines = ((cLines + cNumbers - 1) / cNumbers)|0;
}
if (!cLines) cLines = 8;
for (var iLine = 0; iLine < cLines; iLine++) {
var bPrev = 0;
var sBytes = "", sChars = "";
var data = 0, iByte = 0;
var sData = "", sChars = "";
sAddr = this.hexAddr(aAddr);
for (var i = 0; i < 16; i++) {
var b = this.getByte(aAddr, 1);
if (fWords) {
if (i & 0x1) {
sBytes += str.toHex(bPrev | (b << 8), 4) + (i == 7? " - " : " ");
}
}
else {
sBytes += str.toHex(b, 2) + (i == 7? "-" : " ");
data |= (b << (iByte++ << 3));
if (iByte == cBytes) {
sData += str.toHex(data, cBytes * 2);
sData += (cBytes == 1? (i == 7? '-' : ' ') : " ");
data = iByte = 0;
}
sChars += (b >= 32 && b < 128? String.fromCharCode(b) : ".");
bPrev = b;
}
if (sDump) sDump += "\n";
sDump += sAddr + " " + sBytes + " " + sChars;
sDump += sAddr + " " + sData + " " + sChars;
}
}
if (sDump) this.println(sDump);

View file

@ -289,7 +289,7 @@ function CompaqController(ram)
this.aBlocksDst = null;
}
CompaqController.ADDR = 0x80C00000;
CompaqController.ADDR =(0x80C00000|0);
CompaqController.MAP_SRC = 0x00FE0000;
CompaqController.MAP_DST = 0x000E0000;
CompaqController.MAP_SIZE = 0x00020000;

View file

@ -173,7 +173,7 @@ var X86 = {
},
LADDR: { // linear address
PDE: { // index of page directory entry
MASK: 0xffc00000,
MASK: 0xffc00000|0,
SHIFT: 20 // (addr & DIR.MASK) >>> DIR.SHIFT yields a page directory offset (ie, index * 4)
},
PTE: { // index of page table entry
@ -183,7 +183,7 @@ var X86 = {
OFFSET: 0x00000fff
},
PTE: {
FRAME: 0xfffff000,
FRAME: 0xfffff000|0,
DIRTY: 0x00000040, // page has been modified
ACCESSED: 0x00000020, // page has been accessed
USER: 0x00000004, // set for user level (CPL 3), clear for supervisor level (CPL 0-2)

View file

@ -2522,7 +2522,7 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control)
* @param {number} addr is a physical (non-segmented) address
* @return {number} byte (8-bit) value at that address
*/
X86CPU.prototype.getByte = function(addr)
X86CPU.prototype.getByte = function getByte(addr)
{
if (BACKTRACK) this.backTrack.btiMemLo = this.bus.readBackTrack(addr);
return this.aMemBlocks[(addr & this.busMask) >>> this.blockShift].readByte(addr & this.blockLimit, addr);
@ -2535,7 +2535,7 @@ X86CPU.prototype.getByte = function(addr)
* @param {number} addr is a physical (non-segmented) address
* @return {number} word (16-bit) value at that address
*/
X86CPU.prototype.getShort = function(addr)
X86CPU.prototype.getShort = function getShort(addr)
{
var off = addr & this.blockLimit;
var iBlock = (addr & this.busMask) >>> this.blockShift;
@ -2562,7 +2562,7 @@ X86CPU.prototype.getShort = function(addr)
* @param {number} addr is a physical (non-segmented) address
* @return {number} long (32-bit) value at that address
*/
X86CPU.prototype.getLong = function(addr)
X86CPU.prototype.getLong = function getLong(addr)
{
var off = addr & this.blockLimit;
var iBlock = (addr & this.busMask) >>> this.blockShift;
@ -2584,7 +2584,7 @@ X86CPU.prototype.getLong = function(addr)
* @param {number} addr is a physical (non-segmented) address
* @param {number} b is the byte (8-bit) value to write (which we truncate to 8 bits; required by opSTOSb)
*/
X86CPU.prototype.setByte = function(addr, b)
X86CPU.prototype.setByte = function setByte(addr, b)
{
if (BACKTRACK) this.bus.writeBackTrack(addr, this.backTrack.btiMemLo);
this.aMemBlocks[(addr & this.busMask) >>> this.blockShift].writeByte(addr & this.blockLimit, b & 0xff, addr);
@ -2597,7 +2597,7 @@ X86CPU.prototype.setByte = function(addr, b)
* @param {number} addr is a physical (non-segmented) address
* @param {number} w is the word (16-bit) value to write (which we truncate to 16 bits to be safe)
*/
X86CPU.prototype.setShort = function(addr, w)
X86CPU.prototype.setShort = function setShort(addr, w)
{
var off = addr & this.blockLimit;
var iBlock = (addr & this.busMask) >>> this.blockShift;
@ -2626,7 +2626,7 @@ X86CPU.prototype.setShort = function(addr, w)
* @param {number} addr is a physical (non-segmented) address
* @param {number} l is the long (32-bit) value to write
*/
X86CPU.prototype.setLong = function(addr, l)
X86CPU.prototype.setLong = function setLong(addr, l)
{
var off = addr & this.blockLimit;
var iBlock = (addr & this.busMask) >>> this.blockShift;

View file

@ -2629,7 +2629,7 @@ X86.fnSGDT = function SGDT(dst, src)
*
* This code is expecting SGDT on an 80286 to set the 6th "undefined" byte to 0xFF.
*/
var addr = this.addrGDT | (this.model == X86.MODEL_80286? 0xff000000 : 0);
var addr = this.addrGDT | (this.model == X86.MODEL_80286? (0xff000000|0) : 0);
this.setLong(this.regEA + 2, addr);
this.nStepCycles -= 11;
}
@ -2981,7 +2981,7 @@ X86.fnSIDT = function SIDT(dst, src)
* As with SGDT, the 6th byte is technically "undefined" on an 80286, but we now set it to 0xFF, for the
* same reasons discussed in SGDT (above).
*/
var addr = this.addrIDT | (this.model == X86.MODEL_80286? 0xff000000 : 0);
var addr = this.addrIDT | (this.model == X86.MODEL_80286? (0xff000000|0) : 0);
this.setLong(this.regEA + 2, addr);
this.nStepCycles -= 12;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -288,7 +288,11 @@ X86Seg.prototype.checkWriteReal = function checkWriteReal(off, cb, fSuppress)
*/
X86Seg.prototype.checkReadProt = function checkReadProt(off, cb, fSuppress)
{
if (off + cb - 1 <= this.limit) {
/*
* Since "off" could be a 32-bit value with the sign bit (bit 31) set, we must convert
* it to an unsigned value using ">>>"; limit was already converted at segment load time.
*/
if ((off >>> 0) + cb - 1 <= this.limit) {
return (this.base + off)|0;
}
return this.checkReadProtDisallowed(off, cb, fSuppress);
@ -305,7 +309,11 @@ X86Seg.prototype.checkReadProt = function checkReadProt(off, cb, fSuppress)
*/
X86Seg.prototype.checkReadProtDown = function checkReadProtDown(off, cb, fSuppress)
{
if (off + cb - 1 > this.limit) {
/*
* Since "off" could be a 32-bit value with the sign bit (bit 31) set, we must convert
* it to an unsigned value using ">>>"; limit was already converted at segment load time.
*/
if ((off >>> 0) + cb - 1 > this.limit) {
return (this.base + off)|0;
}
return this.checkReadProtDisallowed(off, cb, fSuppress);
@ -339,7 +347,11 @@ X86Seg.prototype.checkReadProtDisallowed = function checkReadProtDisallowed(off,
*/
X86Seg.prototype.checkWriteProt = function checkWriteProt(off, cb, fSuppress)
{
if (off + cb - 1 <= this.limit) {
/*
* Since "off" could be a 32-bit value with the sign bit (bit 31) set, we must convert
* it to an unsigned value using ">>>"; limit was already converted at segment load time.
*/
if ((off >>> 0) + cb - 1 <= this.limit) {
return (this.base + off)|0;
}
return this.checkWriteProtDisallowed(off, cb, fSuppress);
@ -356,7 +368,11 @@ X86Seg.prototype.checkWriteProt = function checkWriteProt(off, cb, fSuppress)
*/
X86Seg.prototype.checkWriteProtDown = function checkWriteProtDown(off, cb, fSuppress)
{
if (off + cb - 1 > this.limit) {
/*
* Since "off" could be a 32-bit value with the sign bit (bit 31) set, we must convert
* it to an unsigned value using ">>>"; limit was already converted at segment load time.
*/
if ((off >>> 0) + cb - 1 > this.limit) {
return (this.base + off)|0;
}
return this.checkWriteProtDisallowed(off, cb, fSuppress);
@ -748,7 +764,11 @@ X86Seg.prototype.loadDesc8 = function(addrDesc, sel, fSuppress)
}
this.sel = sel;
this.base = base;
this.limit = limit;
/*
* Since limit could be a 32-bit value with the sign bit (bit 31) set, we convert
* it to an unsigned value now, using ">>>", so that the limit checks won't have to.
*/
this.limit = (limit >>> 0);
this.acc = acc;
this.type = type;
this.ext = ext;

View file

@ -10,7 +10,7 @@
<manifest ref="/apps/pc/1981/visicalc/manifest.xml" disk="*"/>
<disk path="/api/v1/dump?dir=/pubs/pc/programming/Graphics_for_the_IBM_PC/programs">BASIC Programs</disk>
</control>
<control type="button" binding="loadDrive" padright="8px">Download</control>
<control type="button" binding="loadDrive" padright="8px">Load</control>
<control type="description" binding="descDisk" padright="8px"/>
<control type="file" binding="mountDrive"/>
</fdc>