Groundwork for loading local disk images in PCjs
This commit is contained in:
parent
a1a6dd35b6
commit
43860a588c
20 changed files with 1355 additions and 1157 deletions
|
|
@ -163,18 +163,18 @@ C1PComputer.prototype.stop = function(msStart, nCycles)
|
|||
|
||||
/**
|
||||
* @this {C1PComputer}
|
||||
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
|
||||
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PComputer.prototype.setBinding = function(c, t, s, e)
|
||||
C1PComputer.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
switch(s) {
|
||||
switch(sBinding) {
|
||||
case "reset":
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(computer) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(computer) {
|
||||
return function() {
|
||||
computer.reset();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -502,19 +502,19 @@ C1PCPU.prototype.reset = function(fPowerOn)
|
|||
|
||||
/**
|
||||
* @this {C1PCPU}
|
||||
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "run")
|
||||
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "run")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PCPU.prototype.setBinding = function(c, t, s, e)
|
||||
C1PCPU.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
var fBound = false;
|
||||
switch(s) {
|
||||
switch(sBinding) {
|
||||
case "run":
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(cpu) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(cpu) {
|
||||
return function() {
|
||||
if (!cpu.aFlags.fRunning)
|
||||
cpu.run();
|
||||
|
|
@ -527,12 +527,12 @@ C1PCPU.prototype.setBinding = function(c, t, s, e)
|
|||
case "A": case "X": case "Y": case "S": case "PC":
|
||||
case "C": case "Z": case "I": case "D": case "B": case "V": case "N":
|
||||
case "speed":
|
||||
this.bindings[s] = e;
|
||||
this.bindings[sBinding] = control;
|
||||
fBound = true;
|
||||
break;
|
||||
case "setSpeed":
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(cpu) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(cpu) {
|
||||
return function() {
|
||||
var speed = (cpu.speed >= cpu.SPEED_MAX? cpu.SPEED_SLOW : cpu.speed+1);
|
||||
cpu.setSpeed(speed, true);
|
||||
|
|
|
|||
|
|
@ -657,21 +657,23 @@ C1PDiskController.prototype.resetDrive = function(iDrive, iDriveType, nMaxTracks
|
|||
|
||||
/**
|
||||
* @this {C1PDiskController}
|
||||
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listDisk")
|
||||
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listDisk")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PDiskController.prototype.setBinding = function(c, t, s, e)
|
||||
C1PDiskController.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
switch(s) {
|
||||
switch(sBinding) {
|
||||
|
||||
case "listDisk":
|
||||
this.bindings[s] = e;
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
case "loadDisk":
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(controller) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(controller) {
|
||||
return function() {
|
||||
if (controller.bindings["listDisk"]) {
|
||||
var sFilePath = controller.bindings["listDisk"].value;
|
||||
|
|
@ -698,6 +700,7 @@ C1PDiskController.prototype.setBinding = function(c, t, s, e)
|
|||
};
|
||||
}(this);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -388,38 +388,38 @@ C1PKeyboard.prototype.reset = function()
|
|||
|
||||
/**
|
||||
* @this {C1PKeyboard}
|
||||
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "esc", "ctrl-c")
|
||||
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "esc", "ctrl-c")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PKeyboard.prototype.setBinding = function(c, t, s, e)
|
||||
C1PKeyboard.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
/*
|
||||
* I want to bind to the first caller (ie, the Screen), not subsequent ones (eg, the Panel)
|
||||
*/
|
||||
if (this.bindings[s] === undefined) {
|
||||
switch(s) {
|
||||
if (this.bindings[sBinding] === undefined) {
|
||||
switch(sBinding) {
|
||||
case "keyDown":
|
||||
this.bindings[s] = e;
|
||||
e.onkeydown = function(kbd) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onkeydown = function(kbd) {
|
||||
return function(event) {
|
||||
return kbd.keyEvent(event, true);
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
case "keyPress":
|
||||
this.bindings[s] = e;
|
||||
e.onkeypress = function(kbd) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onkeypress = function(kbd) {
|
||||
return function(event) {
|
||||
return kbd.keyPress(event);
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
case "keyUp":
|
||||
this.bindings[s] = e;
|
||||
e.onkeyup = function(kbd) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onkeyup = function(kbd) {
|
||||
return function(event) {
|
||||
return kbd.keyEvent(event, false);
|
||||
};
|
||||
|
|
@ -436,8 +436,8 @@ C1PKeyboard.prototype.setBinding = function(c, t, s, e)
|
|||
* other injected keys, I'll need to avoid clearing the injection buffer on a reset;
|
||||
* currently, reset() resets everything.
|
||||
*/
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(kbd) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(kbd) {
|
||||
return function(event) {
|
||||
if (DEBUG) kbd.println("keyPressSimulate(break)");
|
||||
if (kbd.cmp) kbd.cmp.reset(true);
|
||||
|
|
@ -445,15 +445,15 @@ C1PKeyboard.prototype.setBinding = function(c, t, s, e)
|
|||
}(this);
|
||||
return true;
|
||||
default:
|
||||
if (this.aButtonCodeMap[s] !== undefined) {
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(kbd, sButton, charCode) {
|
||||
if (this.aButtonCodeMap[sBinding] !== undefined) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(kbd, sButton, charCode) {
|
||||
return function(event) {
|
||||
if (DEBUG) kbd.println("keyPressSimulate(" + sButton + ")");
|
||||
if (kbd.cpu) kbd.cpu.setFocus();
|
||||
return !kbd.keyPressSimulate(charCode);
|
||||
};
|
||||
}(this, s, this.aButtonCodeMap[s]);
|
||||
}(this, sBinding, this.aButtonCodeMap[sBinding]);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -100,64 +100,73 @@ C1PSerialPort.prototype.start = function()
|
|||
|
||||
/**
|
||||
* @this {C1PSerialPort}
|
||||
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
|
||||
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PSerialPort.prototype.setBinding = function(c, t, s, e)
|
||||
C1PSerialPort.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
switch(s) {
|
||||
case "listSerial":
|
||||
this.bindings[s] = e;
|
||||
return true;
|
||||
case "loadSerial":
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(serial) {
|
||||
return function() {
|
||||
if (serial.bindings["listSerial"]) {
|
||||
var sFile = serial.bindings["listSerial"].value;
|
||||
// serial.println("loading " + sFile + "...");
|
||||
web.loadResource(sFile, true, null, serial, serial.loadFile);
|
||||
}
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
case "uploadSerial":
|
||||
// Check for availability of FileReader
|
||||
if (window.FileReader && window.File && window.FileList && window.Blob ) {
|
||||
var serial = this;
|
||||
this.bindings[s] = e;
|
||||
var serial = this;
|
||||
|
||||
// Enable "Load Local File" button only if a file is actually selected
|
||||
e.addEventListener('change', function () {
|
||||
var fieldset = e.children[0];
|
||||
switch(sBinding) {
|
||||
|
||||
case "listSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
return true;
|
||||
|
||||
case "loadSerial":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function(event) {
|
||||
if (serial.bindings["listSerial"]) {
|
||||
var sFile = serial.bindings["listSerial"].value;
|
||||
// serial.println("loading " + sFile + "...");
|
||||
web.loadResource(sFile, true, null, serial, serial.loadFile);
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "uploadSerial":
|
||||
/*
|
||||
* Check for availability of FileReader
|
||||
*/
|
||||
if (window.FileReader && window.File && window.FileList && window.Blob) {
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* Enable "Load Local File" button only if a file is actually selected
|
||||
*/
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
|
||||
submit.disabled = (files.length == 0);
|
||||
});
|
||||
|
||||
e.onsubmit = function (event) {
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
// serial.println("uploading " + file.name + "...");
|
||||
reader.onload = function() {
|
||||
// serial.println("loading " + file.name + "...");
|
||||
serial.loadFile(file.name, reader.result.toString(), 0);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
// Prevent reloading of web page after form submission
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else {
|
||||
this.println("FileReader support not available, disabling local file upload");
|
||||
e.parentNode.removeChild(e);
|
||||
this.println("FileReader support not available, disabling local file load");
|
||||
control.parentNode.removeChild(control);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,18 +172,18 @@ C1PVideo.prototype.reset = function(fPowerOn)
|
|||
|
||||
/**
|
||||
* @this {C1PVideo}
|
||||
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "refresh")
|
||||
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "refresh")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
C1PVideo.prototype.setBinding = function(c, t, s, e)
|
||||
C1PVideo.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
switch(s) {
|
||||
switch(sBinding) {
|
||||
case "refresh":
|
||||
this.bindings[s] = e;
|
||||
e.onclick = function(video) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(video) {
|
||||
return function() {
|
||||
if (DEBUG) video.println("refreshScreen()");
|
||||
video.initScreen();
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ var net = require("../../shared/lib/netlib");
|
|||
var proc = require("../../shared/lib/proclib");
|
||||
var str = require("../../shared/lib/strlib");
|
||||
var usr = require("../../shared/lib/usrlib");
|
||||
var DiskAPI = require("../../shared/lib/diskapi");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var X86 = require("../../pcjs-client/lib/x86");
|
||||
|
||||
|
|
@ -177,38 +178,6 @@ DiskDump.sCopyright = "© 2012-2014 by Jeff Parsons (@jeffpar)";
|
|||
DiskDump.sNotice = DiskDump.sAPIURL + " " + DiskDump.sCopyright;
|
||||
DiskDump.sUsage = "Usage: " + DiskDump.sAPIURL + "?" + DumpAPI.QUERY.PATH + "={url}&" + DumpAPI.QUERY.FORMAT + "=json|data|hex|bytes|img";
|
||||
|
||||
/*
|
||||
* TODO: The following descriptions of common disk data structures should be moved to a common (shared) module,
|
||||
* so that client code (eg, PCjs components) can access them, too (eg, something like /my_modules/shared/lib/bpbdefs.js)
|
||||
*/
|
||||
DiskDump.DisketteFormats = {
|
||||
163840: [1,40,8], // media type 0xFE: 1 head (single-sided), 40 cylinders, 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)
|
||||
184320: [1,40,9], // media type 0xFC: 1 head (single-sided), 40 cylinders, 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)
|
||||
327680: [2,40,8], // media type 0xFF: 2 heads (double-sided), 40 cylinders, 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)
|
||||
368640: [2,40,9], // media type 0xFD: 2 heads (double-sided), 40 cylinders, 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)
|
||||
737280: [2,80,9], // media type 0xF9: 2 heads (double-sided), 80 cylinders, 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)
|
||||
1228800: [2,80,15], // media type 0xF9: 2 heads (double-sided), 80 cylinders, 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)
|
||||
1474560: [2,80,18], // media type 0xF0: 2 heads (double-sided), 80 cylinders, 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)
|
||||
2949120: [2,80,36] // media type 0xF0: 2 heads (double-sided), 80 cylinders, 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
|
||||
};
|
||||
|
||||
DiskDump.BPB = {
|
||||
JMP_OPCODE: 0x00, // 1 byte for a JMP opcode, followed by a 1 or 2-byte offset
|
||||
OEM_STRING: 0x03, // 8 bytes
|
||||
SECTOR_BYTES: 0x0B, // 2 bytes: bytes per sector (eg, 0x200 or 512)
|
||||
CLUSTER_SECS: 0x0D, // 1 byte: sectors per cluster (eg, 1)
|
||||
RESERVED_SECS: 0x0E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1)
|
||||
FAT_TOTAL: 0x10, // 1 byte: FAT copies (eg, 2)
|
||||
ROOT_ENTRIES: 0x11, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)
|
||||
SECTOR_TOTAL: 0x13, // 2 bytes: number of sectors (eg, 0x140 or 320)
|
||||
MEDIA_TYPE: 0x15, // 1 byte: media type (eg, 0xFF: 320Kb, 0xFE: 160Kb, 0xFD: 360Kb, 0xFC: 180Kb)
|
||||
FAT_SECS: 0x16, // 2 bytes: sectors per FAT (eg, 1)
|
||||
TRACK_SECS: 0x18, // 2 bytes: sectors per track (eg, 8)
|
||||
HEAD_TOTAL: 0x1A, // 2 bytes: number of heads (eg, 1)
|
||||
HIDDEN_SECS: 0x1C, // 4 bytes: number of hidden sectors (always 0 for non-partitioned media)
|
||||
LARGE_SECS: 0x20 // 4 bytes: number of sectors if SECTOR_TOTAL is zero
|
||||
};
|
||||
|
||||
/*
|
||||
* MY_VOL_LABEL is our default label, used whenever a more suitable label (eg, based on the disk image's folder name)
|
||||
* is not available or not supplied, and MY_OEM_STRING is inserted into any DiskDump-generated diskette images.
|
||||
|
|
@ -2129,8 +2098,8 @@ DiskDump.prototype.convertToJSON = function()
|
|||
*/
|
||||
}
|
||||
|
||||
var bByte0 = this.bufDisk.readUInt8(offBootSector + DiskDump.BPB.JMP_OPCODE);
|
||||
var cbSectorBPB = this.bufDisk.readUInt16LE(offBootSector + DiskDump.BPB.SECTOR_BYTES);
|
||||
var bByte0 = this.bufDisk.readUInt8(offBootSector + DiskAPI.BPB.JMP_OPCODE);
|
||||
var cbSectorBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.SECTOR_BYTES);
|
||||
|
||||
/*
|
||||
* These checks are not only necessary for DOS 1.x diskette images (and other pre-BPB images),
|
||||
|
|
@ -2145,11 +2114,11 @@ DiskDump.prototype.convertToJSON = function()
|
|||
* image whose logical format doesn't agree with its physical structure.
|
||||
*/
|
||||
var fXDFOutput = false;
|
||||
var oDisketteFormat = DiskDump.DisketteFormats[cbDiskData];
|
||||
if (oDisketteFormat) {
|
||||
nHeads = oDisketteFormat[0];
|
||||
nCylinders = oDisketteFormat[1];
|
||||
nSectorsPerTrack = oDisketteFormat[2];
|
||||
var disketteFormat = DiskAPI.DISKETTE_FORMATS[cbDiskData];
|
||||
if (disketteFormat) {
|
||||
nCylinders = disketteFormat[0];
|
||||
nHeads = disketteFormat[1];
|
||||
nSectorsPerTrack = disketteFormat[2];
|
||||
}
|
||||
else {
|
||||
/*
|
||||
|
|
@ -2159,10 +2128,13 @@ DiskDump.prototype.convertToJSON = function()
|
|||
* 0xE9 is JMP with a 2-byte displacement). What else?
|
||||
*/
|
||||
if ((bByte0 == X86.OPCODE.JMP || bByte0 == X86.OPCODE.JMPS) && cbSectorBPB == cbSector) {
|
||||
var nHeadsBPB = this.bufDisk.readUInt16LE(offBootSector + DiskDump.BPB.HEAD_TOTAL);
|
||||
var nSectorsTotalBPB = this.bufDisk.readUInt16LE(offBootSector + DiskDump.BPB.SECTOR_TOTAL);
|
||||
var nSectorsPerTrackBPB = this.bufDisk.readUInt16LE(offBootSector + DiskDump.BPB.TRACK_SECS);
|
||||
|
||||
var nHeadsBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.HEAD_TOTAL);
|
||||
var nSectorsTotalBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.SECTOR_TOTAL);
|
||||
var nSectorsPerTrackBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TRACK_SECS);
|
||||
|
||||
if (nSectorsPerTrackBPB && nHeadsBPB) {
|
||||
|
||||
var nSectorsPerCylinderBPB = nSectorsPerTrackBPB * nHeadsBPB;
|
||||
nHeads = nHeadsBPB;
|
||||
nCylinders = Math.floor(nSectorsTotalBPB / nSectorsPerCylinderBPB);
|
||||
|
|
@ -2727,13 +2699,13 @@ DiskDump.prototype.convertToIMG = function()
|
|||
/*
|
||||
* Mimic the BPB test in convertToJSON(), because we don't want to blast an OEM string into non-DOS diskette images
|
||||
*/
|
||||
var bByte0 = buf.readUInt8(DiskDump.BPB.JMP_OPCODE);
|
||||
var cbSectorBPB = buf.readUInt16LE(DiskDump.BPB.SECTOR_BYTES);
|
||||
var bByte0 = buf.readUInt8(DiskAPI.BPB.JMP_OPCODE);
|
||||
var cbSectorBPB = buf.readUInt16LE(DiskAPI.BPB.SECTOR_BYTES);
|
||||
if ((bByte0 == X86.OPCODE.JMP || bByte0 == X86.OPCODE.JMPS) && cbSectorBPB == 512) {
|
||||
/*
|
||||
* Overwrite the OEM string with our own, so that people know how the image originated
|
||||
*/
|
||||
buf.write(DiskDump.MY_OEM_STRING, DiskDump.BPB.OEM_STRING, DiskDump.MY_OEM_STRING.length);
|
||||
buf.write(DiskDump.MY_OEM_STRING, DiskAPI.BPB.OEM_STRING, DiskDump.MY_OEM_STRING.length);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
|
|
|
|||
|
|
@ -869,10 +869,10 @@ if (DEBUGGER) {
|
|||
];
|
||||
|
||||
Debugger.aaOp0FDescs = {
|
||||
0x00: [Debugger.INS.GRP6, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
||||
0x01: [Debugger.INS.GRP7, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
||||
0x02: [Debugger.INS.LAR, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
||||
0x03: [Debugger.INS.LSL, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
||||
0x00: [Debugger.INS.GRP6, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
||||
0x01: [Debugger.INS.GRP7, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
|
||||
0x02: [Debugger.INS.LAR, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
||||
0x03: [Debugger.INS.LSL, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
|
||||
0x05: [Debugger.INS.LOADALL,Debugger.TYPE_286]
|
||||
};
|
||||
|
||||
|
|
@ -2747,6 +2747,43 @@ if (DEBUGGER) {
|
|||
/**
|
||||
* parseInstruction(sOp, sOperand, addr)
|
||||
*
|
||||
* This generally requires an exact match of both the operation code (sOp) and mode operand
|
||||
* (sOperand) against the aOps[] and aOpMods[] arrays, respectively; however, the regular
|
||||
* expression built from aOpMods and stored in regexOpModes does relax the matching criteria
|
||||
* slightly; ie, a 4-digit hex value ("nnnn") will be satisfied with either 3 or 4 digits, and
|
||||
* similarly, a 2-digit hex address (nn) will be satisfied with either 1 or 2 digits.
|
||||
*
|
||||
* Note that this function does not actually store the instruction into memory, even though it requires
|
||||
* a target address (addr); that parameter is currently needed ONLY for "branch" instructions, because in
|
||||
* order to calculate the branch displacement, it needs to know where the instruction will ultimately be
|
||||
* stored, relative to its target address.
|
||||
*
|
||||
* Another handy feature of this function is its ability to display all available modes for a particular
|
||||
* operation. For example, while in "assemble mode", if one types:
|
||||
*
|
||||
* ldy?
|
||||
*
|
||||
* the Debugger will display:
|
||||
*
|
||||
* supported opcodes:
|
||||
* A0: LDY nn
|
||||
* A4: LDY [nn]
|
||||
* AC: LDY [nnnn]
|
||||
* B4: LDY [nn+X]
|
||||
* BC: LDY [nnnn+X]
|
||||
*
|
||||
* Use of a trailing "?" on any opcode will display all variations of that opcode; no instruction will be
|
||||
* assembled, and the operand parameter, if any, will be ignored.
|
||||
*
|
||||
* Although this function is capable of reporting numerous errors, roughly half of them indicate internal
|
||||
* consistency errors, not user errors; the former should really be asserts, but I'm not comfortable bombing
|
||||
* out because of my error as opposed to their error. The only errors a user should expect to see:
|
||||
*
|
||||
* "unknown operation": sOp is not a valid operation (per aOps)
|
||||
* "unknown operand": sOperand is not a valid operand (per aOpMods)
|
||||
* "unknown instruction": the combination of sOp + sOperand does not exist (per aaOpDescs)
|
||||
* "branch out of range": the branch address, relative to addr, is too far away
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} sOp
|
||||
* @param {string|undefined} sOperand
|
||||
|
|
|
|||
|
|
@ -35,14 +35,13 @@
|
|||
*
|
||||
* 1) creating an empty disk: create()
|
||||
* 2) loading a disk image: load()
|
||||
* 3) mounting a disk image: mount()
|
||||
* 4) getting disk information: info()
|
||||
* 5) dumping disk contents: dump()
|
||||
* 6) seeking a disk sector: seek()
|
||||
* 7) reading data from a sector: read()
|
||||
* 8) writing data to a sector: write()
|
||||
* 9) save disk deltas: save()
|
||||
* 10) restore disk deltas: restore()
|
||||
* 3) getting disk information: info()
|
||||
* 4) dumping disk contents: dump()
|
||||
* 5) seeking a disk sector: seek()
|
||||
* 6) reading data from a sector: read()
|
||||
* 7) writing data to a sector: write()
|
||||
* 8) save disk deltas: save()
|
||||
* 9) restore disk deltas: restore()
|
||||
*
|
||||
* More functionality may be factored out of the FDC and HDC components later and moved here, to
|
||||
* further reduce some of the duplication between them, but the above functionality is a good start.
|
||||
|
|
@ -349,7 +348,7 @@ Disk.prototype.powerUp = function(data, fRepower) {
|
|||
if (!fRepower) {
|
||||
if (this.fOnDemand && !this.fRemote) {
|
||||
this.setReady(false);
|
||||
this.load(this.sDiskName, this.sDiskPath, this.donePowerUp, this);
|
||||
this.load(this.sDiskName, this.sDiskPath, null, this.donePowerUp, this);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
@ -467,12 +466,16 @@ Disk.prototype.create = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* load(sDiskName, sDiskPath, fnNotify, controller)
|
||||
* load(sDiskName, sDiskPath, file, fnNotify)
|
||||
*
|
||||
* TODO: Figure out how we can strongly type fnNotify, because the Closure Compiler has issues with:
|
||||
*
|
||||
* param {function(Component,Object,Disk,string,string)} fnNotify
|
||||
*
|
||||
* for:
|
||||
*
|
||||
* this.fnNotify.call(this.controller, this.drive, disk, this.sDiskName, this.sDiskPath);
|
||||
*
|
||||
* Also, while we're at it, learn if there are ways to:
|
||||
*
|
||||
* 1) declare a function taking NO parameters (ie, generate a warning if any parameters are specified)
|
||||
|
|
@ -481,10 +484,11 @@ Disk.prototype.create = function()
|
|||
* @this {Disk}
|
||||
* @param {string} sDiskName
|
||||
* @param {string} sDiskPath
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
* @param {function(...)} [fnNotify]
|
||||
* @param {Component} [controller]
|
||||
*/
|
||||
Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
|
||||
Disk.prototype.load = function(sDiskName, sDiskPath, file, fnNotify, controller)
|
||||
{
|
||||
var sDiskURL = sDiskPath;
|
||||
|
||||
|
|
@ -497,8 +501,6 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
|
|||
this.messageDebugger(sMessage);
|
||||
}
|
||||
|
||||
Component.assert(!this.fnNotify);
|
||||
|
||||
if (this.fnNotify) {
|
||||
if (DEBUG) this.controller.log('too many load requests for "' + sDiskName + '" (' + sDiskPath + ')');
|
||||
return;
|
||||
|
|
@ -509,6 +511,16 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
|
|||
this.fnNotify = fnNotify;
|
||||
this.controllerNotify = controller || this.controller;
|
||||
|
||||
if (file) {
|
||||
var disk = this;
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
disk.build(reader.result, true);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there's an occurrence of API_ENDPOINT anywhere in the path, we assume we can use it as-is;
|
||||
* ie, that the user has already formed a URL of the type we use ourselves for unconverted disk images.
|
||||
|
|
@ -563,6 +575,60 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
|
|||
web.loadResource(sDiskURL, true, null, this, this.doneLoad, sDiskPath);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* build(buffer, fDirty)
|
||||
*
|
||||
* Builds a disk image from an ArrayBuffer (eg, from a FileReader object), rather than from JSON-encoded data.
|
||||
*
|
||||
* @this {Disk}
|
||||
* @param {?} buffer (we KNOW this is an ArrayBuffer, but we can't seem to convince the Closure Compiler)
|
||||
* @param {boolean} [fDirty] is true if we should mark the entire disk dirty (to ensure that we save/restore it)
|
||||
*/
|
||||
Disk.prototype.build = function(buffer, fDirty)
|
||||
{
|
||||
var disk;
|
||||
var cbDiskData = buffer? buffer.byteLength : 0;
|
||||
var disketteFormat = DiskAPI.DISKETTE_FORMATS[cbDiskData];
|
||||
|
||||
if (disketteFormat) {
|
||||
var ib = 0;
|
||||
var dwChecksum = 0;
|
||||
var cbSector = 512, dwPattern = 0;
|
||||
var dv = new DataView(buffer, 0, cbDiskData);
|
||||
this.aDiskData = new Array(disketteFormat[0]);
|
||||
for (var iCylinder = 0; iCylinder < this.aDiskData.length; iCylinder++) {
|
||||
var cylinder = this.aDiskData[iCylinder] = new Array(disketteFormat[1]);
|
||||
for (var iHead = 0; iHead < cylinder.length; iHead++) {
|
||||
var head = cylinder[iHead] = new Array(disketteFormat[2]);
|
||||
for (var iSector = 0; iSector < head.length; iSector++) {
|
||||
var sector = this.initSector(null, iCylinder, iHead, iSector + 1, cbSector, dwPattern);
|
||||
var cdw = cbSector >> 2;
|
||||
var adw = sector['data'];
|
||||
for (var idw = 0; idw < cdw; idw++, ib += 4) {
|
||||
var dw = adw[idw] = dv.getInt32(ib, true);
|
||||
dwChecksum = (dwChecksum + dw) & 0xffffffff;
|
||||
}
|
||||
if (fDirty) {
|
||||
sector.cModify = cdw;
|
||||
sector.fDirty = true;
|
||||
}
|
||||
head[iSector] = sector;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dwChecksum = dwChecksum;
|
||||
disk = this;
|
||||
} else {
|
||||
this.notice("Unrecognized diskette format (" + cbDiskData + " bytes)");
|
||||
}
|
||||
|
||||
if (this.fnNotify) {
|
||||
this.fnNotify.call(this.controller, this.drive, disk, this.sDiskName, this.sDiskPath);
|
||||
this.fnNotify = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* doneLoad(sDiskFile, sDiskData, nErrorCode, sDiskPath)
|
||||
*
|
||||
|
|
@ -749,27 +815,12 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
|
|||
}
|
||||
delete sector['bytes'];
|
||||
}
|
||||
/*
|
||||
* The current sector should now have ALL the properties of a proper Sector object; ie:
|
||||
*
|
||||
* 'sector': sector number
|
||||
* 'length': size of the sector, in bytes
|
||||
* 'data': array of dwords
|
||||
* 'pattern': dword pattern to use for empty or partial sectors
|
||||
*
|
||||
* In addition, we will maintain the following information on a per-sector basis,
|
||||
* as sectors are modified:
|
||||
*
|
||||
* iModify: index of first modified dword in sector
|
||||
* cModify: number of modified dwords in sector
|
||||
* fDirty: true if sector is dirty, false if clean (or cleaning in progress)
|
||||
*
|
||||
* And for the disk as a whole, we maintain a checksum of the original unmodified data:
|
||||
*
|
||||
* dwChecksum: summation of all dwords in all non-empty sectors
|
||||
*/
|
||||
this.initSector(sector, iCylinder, iHead);
|
||||
/*
|
||||
* For the disk as a whole, we maintain a checksum of the original unmodified data:
|
||||
*
|
||||
* dwChecksum: summation of all dwords in all non-empty sectors
|
||||
*
|
||||
* Pattern-filling of sectors is deferred until absolutely necessary (eg, when a sector is
|
||||
* being written). So all we need to do at this point is checksum all the initial sector data.
|
||||
*/
|
||||
|
|
@ -797,6 +848,20 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
|
|||
/**
|
||||
* initSector(sector, iCylinder, iHead, iSector, cbSector, dwPattern)
|
||||
*
|
||||
* Ensures every sector has ALL the properties of a proper Sector object; ie:
|
||||
*
|
||||
* 'sector': sector number
|
||||
* 'length': size of the sector, in bytes
|
||||
* 'data': array of dwords
|
||||
* 'pattern': dword pattern to use for empty or partial sectors (null for unread remote sectors)
|
||||
*
|
||||
* In addition, we will maintain the following information on a per-sector basis,
|
||||
* as sectors are modified:
|
||||
*
|
||||
* iModify: index of first modified dword in sector
|
||||
* cModify: number of modified dwords in sector
|
||||
* fDirty: true if sector is dirty, false if clean (or cleaning in progress)
|
||||
*
|
||||
* @param {Object} sector
|
||||
* @param {number} iCylinder
|
||||
* @param {number} iHead
|
||||
|
|
|
|||
|
|
@ -143,19 +143,19 @@ function FDC(parmsFDC) {
|
|||
this['dmaWrite'] = this.dmaWrite;
|
||||
this['dmaFormat'] = this.dmaFormat;
|
||||
|
||||
this.pAutoMount = null;
|
||||
this.configAutoMount = null;
|
||||
if (parmsFDC['autoMount']) {
|
||||
this.pAutoMount = parmsFDC['autoMount'];
|
||||
if (typeof this.pAutoMount == "string") {
|
||||
this.configAutoMount = parmsFDC['autoMount'];
|
||||
if (typeof this.configAutoMount == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* the JSON-encoded diskette data.
|
||||
*/
|
||||
this.pAutoMount = eval("(" + parmsFDC['autoMount'] + ")");
|
||||
this.configAutoMount = eval("(" + parmsFDC['autoMount'] + ")");
|
||||
} catch (e) {
|
||||
Component.error("FDC auto-mount error: " + e.message + " (" + parmsFDC['autoMount'] + ")");
|
||||
this.pAutoMount = null;
|
||||
this.configAutoMount = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -412,117 +412,109 @@ FDC.aCmdInfo = {
|
|||
*/
|
||||
FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
var fdc = this;
|
||||
|
||||
switch (sBinding) {
|
||||
case "listDisks":
|
||||
this.bindings[sBinding] = control;
|
||||
/*
|
||||
* Add the special path of "?" to the list, which will prompt the user for a URL.
|
||||
*/
|
||||
var controlOption = window.document.createElement("option");
|
||||
controlOption['value'] = "?";
|
||||
controlOption.innerHTML = "User-defined URL...";
|
||||
|
||||
case "listDisks":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
var addControlOption = function(sValue, sDisplay) {
|
||||
var controlOption;
|
||||
controlOption = window.document.createElement("option");
|
||||
controlOption['value'] = sValue;
|
||||
controlOption.innerHTML = sDisplay;
|
||||
control.appendChild(controlOption);
|
||||
/*
|
||||
* Now add an 'onchange' handler.
|
||||
*/
|
||||
control.onchange = function(fdc, controlDisks) {
|
||||
return function onChangeListDisks() {
|
||||
var controlDesc = fdc.bindings["descDisk"];
|
||||
if (controlDesc) {
|
||||
var controlOption = controlDisks.options[controlDisks.selectedIndex];
|
||||
if (controlOption) {
|
||||
var dataValue = {};
|
||||
var sValue = controlOption.getAttribute("data-value");
|
||||
if (sValue) {
|
||||
try {
|
||||
dataValue = eval("({" + sValue + "})");
|
||||
} catch (e) {
|
||||
Component.error("FDC option error: " + (e.message || e));
|
||||
}
|
||||
}
|
||||
var sDesc = dataValue['desc'];
|
||||
if (sDesc === undefined) sDesc = "";
|
||||
var sHRef = dataValue['href'];
|
||||
if (sHRef !== undefined) sDesc = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sDesc + "</a>";
|
||||
controlDesc.innerHTML = sDesc;
|
||||
}
|
||||
};
|
||||
|
||||
addControlOption("?", "Remote Disk");
|
||||
|
||||
control.onchange = function onChangeListDisks(event) {
|
||||
var controlDesc = fdc.bindings["descDisk"];
|
||||
var controlOption = control.options[control.selectedIndex];
|
||||
if (controlDesc && controlOption) {
|
||||
var dataValue = {};
|
||||
var sValue = controlOption.getAttribute("data-value");
|
||||
if (sValue) {
|
||||
try {
|
||||
dataValue = eval("({" + sValue + "})");
|
||||
} catch (e) {
|
||||
Component.error("FDC option error: " + e.message);
|
||||
}
|
||||
};
|
||||
}(this, control);
|
||||
return true;
|
||||
}
|
||||
var sDesc = dataValue['desc'];
|
||||
if (sDesc === undefined) sDesc = "";
|
||||
var sHRef = dataValue['href'];
|
||||
if (sHRef !== undefined) sDesc = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sDesc + "</a>";
|
||||
controlDesc.innerHTML = sDesc;
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "descDisk":
|
||||
case "listDrives":
|
||||
case "descDisk":
|
||||
case "listDrives":
|
||||
this.bindings[sBinding] = control;
|
||||
/*
|
||||
* I tried going with onclick instead of onchange, so that if you wanted to confirm what's
|
||||
* loaded in a particular drive, you could click the drive control without having to change it.
|
||||
* However, that doesn't seem to work for all browsers, so I've reverted to onchange.
|
||||
*/
|
||||
control.onchange = function onChangeListDrives(event) {
|
||||
var iDrive = str.parseInt(control.value, 10);
|
||||
if (iDrive != null) fdc.displayDiskette(iDrive);
|
||||
};
|
||||
return true;
|
||||
|
||||
case "loadDrive":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickLoadDrive(event) {
|
||||
var controlDisks = fdc.bindings["listDisks"];
|
||||
if (controlDisks) {
|
||||
var sDisketteName = controlDisks.options[controlDisks.selectedIndex].text;
|
||||
var sDiskettePath = controlDisks.value;
|
||||
fdc.loadSelectedDrive(sDisketteName, sDiskettePath);
|
||||
}
|
||||
};
|
||||
return true;
|
||||
|
||||
case "loadLocal":
|
||||
/*
|
||||
* Check for availability of FileReader
|
||||
*/
|
||||
if (window.FileReader && window.File && window.FileList && window.Blob ) {
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
/*
|
||||
* I tried going with onclick instead of onchange, so that if you wanted to confirm what's
|
||||
* loaded in a particular drive, you could click the drive control without having to change it.
|
||||
* However, that doesn't seem to work for all browsers, so I've reverted to onchange.
|
||||
* Enable "Load Local File" button only if a file is actually selected
|
||||
*/
|
||||
control.onchange = function(fdc, controlDrives) {
|
||||
return function onChangeListDrives() {
|
||||
var iDrive = parseInt(controlDrives.value, 10);
|
||||
if (!isNaN(iDrive)) fdc.displayDiskette(iDrive);
|
||||
};
|
||||
}(this, control);
|
||||
return true;
|
||||
control.addEventListener('change', function() {
|
||||
var fieldset = control.children[0];
|
||||
var files = fieldset.children[0].files;
|
||||
var submit = fieldset.children[1];
|
||||
submit.disabled = (files.length == 0);
|
||||
});
|
||||
|
||||
case "loadDrive":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function(fdc) {
|
||||
return function onClickLoadDrive() {
|
||||
var iDrive;
|
||||
var controlDisks = fdc.bindings["listDisks"];
|
||||
var controlDrives = fdc.bindings["listDrives"];
|
||||
if (controlDisks && controlDrives && !isNaN(iDrive = parseInt(controlDrives.value, 10)) && iDrive >= 0 && iDrive < fdc.aDrives.length) {
|
||||
var sDiskettePath = controlDisks.value;
|
||||
if (!sDiskettePath) {
|
||||
fdc.unloadDrive(iDrive);
|
||||
return;
|
||||
}
|
||||
var sDisketteName = controlDisks.options[controlDisks.selectedIndex].text;
|
||||
control.onsubmit = function(event) {
|
||||
var file = event.currentTarget[1].files[0];
|
||||
if (file) {
|
||||
var sDiskettePath = file.name;
|
||||
var sDisketteName = str.getBaseName(sDiskettePath, true);
|
||||
fdc.loadSelectedDrive(sDisketteName, sDiskettePath, file);
|
||||
}
|
||||
/*
|
||||
* Prevent reloading of web page after form submission
|
||||
*/
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else {
|
||||
this.println("FileReader support not available, disabling local file load");
|
||||
control.parentNode.removeChild(control);
|
||||
}
|
||||
return true;
|
||||
|
||||
/*
|
||||
* If the special path of "?" is selected, then we want to prompt the user for a URL. Oh, and
|
||||
* make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
|
||||
* "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
|
||||
*
|
||||
* TODO: This is literally all I've done to support external disk images. There's probably more
|
||||
* I should do, like dynamically updating "listDisks" to include new entries, and adding new entries
|
||||
* to the save/restore data.
|
||||
*/
|
||||
if (sDiskettePath == "?") {
|
||||
sDiskettePath = window.prompt("Enter the URL of a disk image to load.", "");
|
||||
if (!sDiskettePath)
|
||||
return;
|
||||
sDisketteName = str.getBaseName(sDiskettePath);
|
||||
fdc.println("Attempting to load " + sDiskettePath + " as \"" + sDisketteName + "\"");
|
||||
}
|
||||
|
||||
while (fdc.loadDiskette(iDrive, sDisketteName, sDiskettePath, false)) {
|
||||
if (!window.confirm("Click OK to reload the original disk.\n(WARNING: All disk changes will be discarded)")) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* So here's the story: loadDiskette() returned true, which it does ONLY if the specified disk is already
|
||||
* mounted, AND the user clicked OK to reload the original disk image. So we must toss any history we have
|
||||
* for the disk, unload it, and then loop back around to loadDiskette().
|
||||
*
|
||||
* loadDiskette() should NEVER return true the second time, since no disk is loaded. In other words, this
|
||||
* isn't really a loop so much as a one-time retry operation.
|
||||
*/
|
||||
fdc.removeDiskHistory(sDisketteName, sDiskettePath);
|
||||
fdc.unloadDrive(iDrive, false, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fdc.notice("Nothing to load");
|
||||
};
|
||||
}(this);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
@ -1143,17 +1135,17 @@ FDC.prototype.seekDrive = function(drive, iSector, nSectors)
|
|||
FDC.prototype.autoMount = function(fRemount)
|
||||
{
|
||||
if (!fRemount) this.cAutoMount = 0;
|
||||
if (this.pAutoMount) {
|
||||
for (var sDrive in this.pAutoMount) {
|
||||
var pDriveConfig = this.pAutoMount[sDrive];
|
||||
if (pDriveConfig['name'] && pDriveConfig['path']) {
|
||||
if (this.configAutoMount) {
|
||||
for (var sDrive in this.configAutoMount) {
|
||||
var configDrive = this.configAutoMount[sDrive];
|
||||
if (configDrive['name'] && configDrive['path']) {
|
||||
/*
|
||||
* WARNING: This conversion of drive letter to drive number, starting with A:, is very simplistic
|
||||
* and is not guaranteed to match the drive mapping that DOS ultimately uses.
|
||||
*/
|
||||
var iDrive = sDrive.charCodeAt(0) - 0x41;
|
||||
if (iDrive >= 0 && iDrive < this.aDrives.length) {
|
||||
if (!this.loadDiskette(iDrive, pDriveConfig['name'], pDriveConfig['path'], true) && fRemount)
|
||||
if (!this.loadDiskette(iDrive, configDrive['name'], configDrive['path'], true) && fRemount)
|
||||
this.setReady(false);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1165,18 +1157,75 @@ FDC.prototype.autoMount = function(fRemount)
|
|||
};
|
||||
|
||||
/**
|
||||
* loadDiskette(iDrive, sDisketteName, sDiskettePath, fAutoMount)
|
||||
* loadSelectedDrive(sDisketteName, sDiskettePath, file)
|
||||
*
|
||||
* @this {FDC}
|
||||
* @param {string} sDisketteName
|
||||
* @param {string} sDiskettePath
|
||||
* @param {File} [file] is set if there's an associated File object
|
||||
*/
|
||||
FDC.prototype.loadSelectedDrive = function(sDisketteName, sDiskettePath, file)
|
||||
{
|
||||
var iDrive;
|
||||
var controlDrives = this.bindings["listDrives"];
|
||||
if (controlDrives && !isNaN(iDrive = parseInt(controlDrives.value, 10)) && iDrive >= 0 && iDrive < this.aDrives.length) {
|
||||
|
||||
if (!sDiskettePath) {
|
||||
this.unloadDrive(iDrive);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* If the special path of "?" is selected, then we want to prompt the user for a URL. Oh, and
|
||||
* make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
|
||||
* "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
|
||||
*
|
||||
* TODO: This is literally all I've done to support remote disk images. There's probably more
|
||||
* I should do, like dynamically updating "listDisks" to include new entries, and adding new entries
|
||||
* to the save/restore data.
|
||||
*/
|
||||
if (sDiskettePath == "?") {
|
||||
sDiskettePath = window.prompt("Enter the URL of a remote disk image.", "") || "";
|
||||
if (!sDiskettePath) return;
|
||||
sDisketteName = str.getBaseName(sDiskettePath);
|
||||
this.println("Attempting to load " + sDiskettePath + " as \"" + sDisketteName + "\"");
|
||||
}
|
||||
|
||||
this.println("loading disk " + sDiskettePath + "...");
|
||||
|
||||
while (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, false, file)) {
|
||||
if (!window.confirm("Click OK to reload the original disk.\n(WARNING: All disk changes will be discarded)")) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* So here's the story: loadDiskette() returned true, which it does ONLY if the specified disk is already
|
||||
* mounted, AND the user clicked OK to reload the original disk image. So we must toss any history we have
|
||||
* for the disk, unload it, and then loop back around to loadDiskette().
|
||||
*
|
||||
* loadDiskette() should NEVER return true the second time, since no disk is loaded. In other words,
|
||||
* this isn't really a loop so much as a one-time retry operation.
|
||||
*/
|
||||
this.removeDiskHistory(sDisketteName, sDiskettePath);
|
||||
this.unloadDrive(iDrive, false, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.notice("Nothing to load");
|
||||
};
|
||||
|
||||
/**
|
||||
* loadDiskette(iDrive, sDisketteName, sDiskettePath, fAutoMount, file)
|
||||
*
|
||||
* NOTE: If sDiskettePath is already loaded in the drive, nothing needs to be done.
|
||||
*
|
||||
* @this {FDC}
|
||||
* @param {number} iDrive (pre-validated)
|
||||
* @param {number} iDrive
|
||||
* @param {string} sDisketteName
|
||||
* @param {string|null} sDiskettePath
|
||||
* @param {boolean} fAutoMount
|
||||
* @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)
|
||||
*/
|
||||
FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAutoMount)
|
||||
FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAutoMount, file)
|
||||
{
|
||||
var drive = this.aDrives[iDrive];
|
||||
if (sDiskettePath && drive.sDiskettePath != sDiskettePath) {
|
||||
|
|
@ -1192,7 +1241,7 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
|
|||
this.messageDebugger("loading diskette '" + sDisketteName + "'");
|
||||
}
|
||||
var disk = new Disk(this, drive, DiskAPI.MODE.PRELOAD);
|
||||
disk.load(sDisketteName, sDiskettePath, this.doneLoadDiskette);
|
||||
disk.load(sDisketteName, sDiskettePath, file, this.doneLoadDiskette);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1151,7 +1151,7 @@ HDC.prototype.loadDisk = function(iDrive, sDiskName, sDiskPath, fAutoMount)
|
|||
this.messageDebugger("loading " + sDiskName);
|
||||
}
|
||||
var disk = drive.disk || new Disk(this, drive, drive.mode);
|
||||
disk.load(sDiskName, sDiskPath, this.doneLoadDisk);
|
||||
disk.load(sDiskName, sDiskPath, null, this.doneLoadDisk);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -135,9 +135,6 @@ function Memory(addr, size, fReadOnly, controller) {
|
|||
*/
|
||||
if (TYPEDARRAYS) {
|
||||
this.buffer = new ArrayBuffer(size);
|
||||
/**
|
||||
* @type {DataView}
|
||||
*/
|
||||
this.dv = new DataView(this.buffer, 0, size);
|
||||
/*
|
||||
* We could also use dv.getUint8() and dv.setUint8(), but using ab[] to get/set bytes
|
||||
|
|
|
|||
|
|
@ -356,6 +356,14 @@
|
|||
<xsl:when test="@type = 'heading'">
|
||||
<div><xsl:value-of select="."/></div>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'file'">
|
||||
<form class="{$APPCLASS}-{@class}" data-value="{$type},{$binding}">
|
||||
<fieldset>
|
||||
<input type="file"/>
|
||||
<input type="submit" value="Mount" disabled="true"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
</xsl:when>
|
||||
<xsl:when test="@type = 'separator'">
|
||||
<hr/>
|
||||
</xsl:when>
|
||||
|
|
|
|||
|
|
@ -72,4 +72,38 @@ var DiskAPI = {
|
|||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = DiskAPI;
|
||||
/*
|
||||
* Common (supported) diskette formats
|
||||
*/
|
||||
DiskAPI.DISKETTE_FORMATS = {
|
||||
163840: [40,1,8], // media type 0xFE: 40 cylinders, 1 head (single-sided), 8 sectors/track, ( 320 total sectors x 512 bytes/sector == 163840)
|
||||
184320: [40,1,9], // media type 0xFC: 40 cylinders, 1 head (single-sided), 9 sectors/track, ( 360 total sectors x 512 bytes/sector == 184320)
|
||||
327680: [40,2,8], // media type 0xFF: 40 cylinders, 2 heads (double-sided), 8 sectors/track, ( 640 total sectors x 512 bytes/sector == 327680)
|
||||
368640: [40,2,9], // media type 0xFD: 40 cylinders, 2 heads (double-sided), 9 sectors/track, ( 720 total sectors x 512 bytes/sector == 368640)
|
||||
737280: [80,2,9], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 9 sectors/track, (1440 total sectors x 512 bytes/sector == 737280)
|
||||
1228800: [80,2,15], // media type 0xF9: 80 cylinders, 2 heads (double-sided), 15 sectors/track, (2400 total sectors x 512 bytes/sector == 1228800)
|
||||
1474560: [80,2,18], // media type 0xF0: 80 cylinders, 2 heads (double-sided), 18 sectors/track, (2880 total sectors x 512 bytes/sector == 1474560)
|
||||
2949120: [80,2,36] // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
|
||||
};
|
||||
|
||||
/*
|
||||
* BIOS Parameter Block (BPB) offsets in DOS-compatible boot sectors
|
||||
*/
|
||||
DiskAPI.BPB = {
|
||||
JMP_OPCODE: 0x00, // 1 byte for a JMP opcode, followed by a 1 or 2-byte offset
|
||||
OEM_STRING: 0x03, // 8 bytes
|
||||
SECTOR_BYTES: 0x0B, // 2 bytes: bytes per sector (eg, 0x200 or 512)
|
||||
CLUSTER_SECS: 0x0D, // 1 byte: sectors per cluster (eg, 1)
|
||||
RESERVED_SECS: 0x0E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1)
|
||||
FAT_TOTAL: 0x10, // 1 byte: FAT copies (eg, 2)
|
||||
ROOT_ENTRIES: 0x11, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)
|
||||
SECTOR_TOTAL: 0x13, // 2 bytes: number of sectors (eg, 0x140 or 320)
|
||||
MEDIA_TYPE: 0x15, // 1 byte: media type (eg, 0xFF: 320Kb, 0xFE: 160Kb, 0xFD: 360Kb, 0xFC: 180Kb)
|
||||
FAT_SECS: 0x16, // 2 bytes: sectors per FAT (eg, 1)
|
||||
TRACK_SECS: 0x18, // 2 bytes: sectors per track (eg, 8)
|
||||
HEAD_TOTAL: 0x1A, // 2 bytes: number of heads (eg, 1)
|
||||
HIDDEN_SECS: 0x1C, // 4 bytes: number of hidden sectors (always 0 for non-partitioned media)
|
||||
LARGE_SECS: 0x20 // 4 bytes: number of sectors if SECTOR_TOTAL is zero
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = DiskAPI;
|
||||
|
|
|
|||
Loading…
Reference in a new issue