BACKTRACK support: PCjs can now report which portions of which files are being read into memory
This commit is contained in:
parent
aaebc5856e
commit
197392bce9
5 changed files with 407 additions and 155 deletions
|
|
@ -194,7 +194,7 @@ DiskDump.aDefaultBPBs = [
|
||||||
0xEB, 0xFE, 0x90, // 0x00: JMP instruction, following by 8-byte OEM signature
|
0xEB, 0xFE, 0x90, // 0x00: JMP instruction, following by 8-byte OEM signature
|
||||||
0x49, 0x42, 0x4D, 0x20, 0x20, 0x31, 0x2E, 0x30, // "IBM 1.0" (this is a fake OEM signature)
|
0x49, 0x42, 0x4D, 0x20, 0x20, 0x31, 0x2E, 0x30, // "IBM 1.0" (this is a fake OEM signature)
|
||||||
0x00, 0x02, // 0x0B: bytes per sector (0x200 or 512)
|
0x00, 0x02, // 0x0B: bytes per sector (0x200 or 512)
|
||||||
0x01, // 0x0D: sectors per cluster (2)
|
0x01, // 0x0D: sectors per cluster (1)
|
||||||
0x01, 0x00, // 0x0E: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (1)
|
0x01, 0x00, // 0x0E: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (1)
|
||||||
0x02, // 0x10: FAT copies (2)
|
0x02, // 0x10: FAT copies (2)
|
||||||
0x40, 0x00, // 0x11: root directory entries (0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)
|
0x40, 0x00, // 0x11: root directory entries (0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)
|
||||||
|
|
@ -2113,7 +2113,7 @@ DiskDump.prototype.convertToJSON = function()
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
var bByte0 = this.bufDisk.readUInt8(offBootSector + DiskAPI.BPB.JMP_OPCODE);
|
var bByte0 = this.bufDisk.readUInt8(offBootSector + DiskAPI.BOOT.JMP_OPCODE);
|
||||||
var cbSectorBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.SECTOR_BYTES);
|
var cbSectorBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.SECTOR_BYTES);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -2144,7 +2144,7 @@ DiskDump.prototype.convertToJSON = function()
|
||||||
*/
|
*/
|
||||||
if ((bByte0 == X86.OPCODE.JMP || bByte0 == X86.OPCODE.JMPS) && cbSectorBPB == cbSector) {
|
if ((bByte0 == X86.OPCODE.JMP || bByte0 == X86.OPCODE.JMPS) && cbSectorBPB == cbSector) {
|
||||||
|
|
||||||
var nHeadsBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.HEAD_TOTAL);
|
var nHeadsBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TOTAL_HEADS);
|
||||||
var nSectorsTotalBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TOTAL_SECS);
|
var nSectorsTotalBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TOTAL_SECS);
|
||||||
var nSectorsPerTrackBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TRACK_SECS);
|
var nSectorsPerTrackBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TRACK_SECS);
|
||||||
|
|
||||||
|
|
@ -2714,13 +2714,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
|
* 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(DiskAPI.BPB.JMP_OPCODE);
|
var bByte0 = buf.readUInt8(DiskAPI.BOOT.JMP_OPCODE);
|
||||||
var cbSectorBPB = buf.readUInt16LE(DiskAPI.BPB.SECTOR_BYTES);
|
var cbSectorBPB = buf.readUInt16LE(DiskAPI.BPB.SECTOR_BYTES);
|
||||||
if ((bByte0 == X86.OPCODE.JMP || bByte0 == X86.OPCODE.JMPS) && cbSectorBPB == 512) {
|
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
|
* Overwrite the OEM string with our own, so that people know how the image originated
|
||||||
*/
|
*/
|
||||||
buf.write(DiskDump.MY_OEM_STRING, DiskAPI.BPB.OEM_STRING, DiskDump.MY_OEM_STRING.length);
|
buf.write(DiskDump.MY_OEM_STRING, DiskAPI.BOOT.OEM_STRING, DiskDump.MY_OEM_STRING.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
|
|
||||||
|
|
@ -2815,6 +2815,9 @@ ChipSet.prototype.advanceDMA = function(channel, fInit)
|
||||||
*/
|
*/
|
||||||
chipset.bus.setByteDirect(addrCur, b);
|
chipset.bus.setByteDirect(addrCur, b);
|
||||||
if (BACKTRACK) {
|
if (BACKTRACK) {
|
||||||
|
if (!off && obj.file) {
|
||||||
|
chipset.println('loading ' + obj.file.sPath + '[' + obj.offFile + '] @' + str.toHex(addrCur));
|
||||||
|
}
|
||||||
bto = chipset.bus.addBackTrackObject(obj, bto, off);
|
bto = chipset.bus.addBackTrackObject(obj, bto, off);
|
||||||
chipset.bus.setBackTrackIndex(addrCur, bto, off);
|
chipset.bus.setBackTrackIndex(addrCur, bto, off);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ Disk.prototype.powerDown = function(fSave, fShutdown)
|
||||||
* @param {string} mode
|
* @param {string} mode
|
||||||
* @param {number} nCylinders
|
* @param {number} nCylinders
|
||||||
* @param {number} nHeads
|
* @param {number} nHeads
|
||||||
* @param {number} nSectors
|
* @param {number} nSectors (per track)
|
||||||
* @param {number} cbSector
|
* @param {number} cbSector
|
||||||
*
|
*
|
||||||
* Initializes the disk contents according to the current drive mode and parameters.
|
* Initializes the disk contents according to the current drive mode and parameters.
|
||||||
|
|
@ -504,7 +504,7 @@ Disk.prototype.load = function(sDiskName, sDiskPath, file, fnNotify, controller)
|
||||||
* We could use this.log() as well, but it wouldn't display which component initiated the load.
|
* We could use this.log() as well, but it wouldn't display which component initiated the load.
|
||||||
*/
|
*/
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
var sMessage = 'Disk.load("' + sDiskName + '","' + sDiskPath + '")';
|
var sMessage = 'load("' + sDiskName + '","' + sDiskPath + '")';
|
||||||
this.controller.log(sMessage);
|
this.controller.log(sMessage);
|
||||||
this.messagePrint(sMessage);
|
this.messagePrint(sMessage);
|
||||||
}
|
}
|
||||||
|
|
@ -659,9 +659,10 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
|
||||||
if (this.fOnDemand) {
|
if (this.fOnDemand) {
|
||||||
if (!nErrorCode) {
|
if (!nErrorCode) {
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint('Disk.doneLoad("' + sDiskFile + '","' + sDiskPath + '")');
|
this.messagePrint('doneLoad("' + sDiskFile + '","' + sDiskPath + '")');
|
||||||
}
|
}
|
||||||
this.fRemote = true;
|
this.fRemote = true;
|
||||||
|
this.buildFileTable();
|
||||||
disk = this;
|
disk = this;
|
||||||
} else {
|
} else {
|
||||||
this.controller.notice('Unable to connect to disk "' + sDiskPath + '" (error ' + nErrorCode + ': ' + sDiskData + ')', fPrintOnly);
|
this.controller.notice('Unable to connect to disk "' + sDiskPath + '" (error ' + nErrorCode + ': ' + sDiskData + ')', fPrintOnly);
|
||||||
|
|
@ -678,7 +679,7 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
|
||||||
this.controller.notice("Unable to load disk \"" + this.sDiskName + "\" (error " + nErrorCode + ")", fPrintOnly);
|
this.controller.notice("Unable to load disk \"" + this.sDiskName + "\" (error " + nErrorCode + ")", fPrintOnly);
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint('Disk.doneLoad("' + sDiskFile + '","' + sDiskPath + '")');
|
this.messagePrint('doneLoad("' + sDiskFile + '","' + sDiskPath + '")');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
/*
|
/*
|
||||||
|
|
@ -851,8 +852,8 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
|
||||||
}
|
}
|
||||||
this.aDiskData = aDiskData;
|
this.aDiskData = aDiskData;
|
||||||
this.dwChecksum = dwChecksum;
|
this.dwChecksum = dwChecksum;
|
||||||
|
this.buildFileTable();
|
||||||
disk = this;
|
disk = this;
|
||||||
this.buildSectorMap();
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Component.error("Disk image error: " + e.message);
|
Component.error("Disk image error: " + e.message);
|
||||||
|
|
@ -866,85 +867,199 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* buildSectorMap()
|
* buildFileTable()
|
||||||
*
|
*
|
||||||
* This function builds a mapping object (aSectorMap) that maps sectors to files. Used for BACKTRACK support.
|
* This function builds a complete file table from the (first) FAT volume found on the current disk, and
|
||||||
|
* then updates all the sector objects to point back to the corresponding file. Used for BACKTRACK support.
|
||||||
*
|
*
|
||||||
* Note that while most of the methods in this module use CHS-style parameters, because our primary clients are disk
|
* Note that while most of the methods in this module use CHS-style parameters, because our primary clients
|
||||||
* controllers that deal exclusively with cylinder/head/sector values, here we use 0-based logical sector numbers for
|
* are old disk controllers that deal exclusively with cylinder/head/sector values, here we use 0-based
|
||||||
* the sector map. This is also known as logical block addressing or LBA.
|
* "logical" sector numbers for volume-relative block addresses (aka LBAs or Logical Block Addresses), and
|
||||||
|
* 0-based "physical" sector numbers for disk-relative block addresses (aka PBAs or Physical Block Addresses).
|
||||||
|
*
|
||||||
|
* Our use of the term LBA differs from the popular usage of the term, in which disk controllers use LBA
|
||||||
|
* numbers instead of CHS values. In our world, those controllers would actually be using PBA numbers.
|
||||||
*
|
*
|
||||||
* @this {Disk}
|
* @this {Disk}
|
||||||
*/
|
*/
|
||||||
Disk.prototype.buildSectorMap = function()
|
Disk.prototype.buildFileTable = function()
|
||||||
{
|
{
|
||||||
if (BACKTRACK) {
|
if (BACKTRACK) {
|
||||||
var dir = {};
|
var i, off, dir = {};
|
||||||
|
|
||||||
this.aFileTable = [];
|
this.aFileTable = [];
|
||||||
this.aSectorMap = {};
|
|
||||||
|
dir.pbaVolume = dir.lbaTotal = 0;
|
||||||
|
|
||||||
|
var cbDisk = this.nCylinders * this.nHeads * this.nSectors * this.cbSector;
|
||||||
|
|
||||||
var sectorBoot = this.getSector(0);
|
var sectorBoot = this.getSector(0);
|
||||||
dir.cbSector = this.getSectorData(sectorBoot, DiskAPI.BPB.SECTOR_BYTES, 2);
|
if (!sectorBoot) {
|
||||||
if (dir.cbSector != 512) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
/*
|
this.messagePrint("buildFileTable(): unable to read boot sector");
|
||||||
* TODO: This is likely an MBR on a fixed disk, as opposed to a boot sector on a removable disk; we should come
|
}
|
||||||
* up with a cleaner way of detecting fixed disks, and then eventually deal with all the possible partition types.
|
|
||||||
*/
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dir.lbaTotal = this.getSectorData(sectorBoot, DiskAPI.BPB.TOTAL_SECS, 2) || this.getSectorData(sectorBoot, DiskAPI.BPB.LARGE_SECS, 4);
|
|
||||||
dir.lbaFAT = this.getSectorData(sectorBoot, DiskAPI.BPB.RESERVED_SECS, 2);
|
dir.cbSector = this.getSectorData(sectorBoot, DiskAPI.BPB.SECTOR_BYTES, 2);
|
||||||
dir.lbaRoot = dir.lbaFAT + this.getSectorData(sectorBoot, DiskAPI.BPB.FAT_SECS, 2) * this.getSectorData(sectorBoot, DiskAPI.BPB.FAT_TOTAL, 1);
|
|
||||||
dir.nEntries = this.getSectorData(sectorBoot, DiskAPI.BPB.ROOT_ENTRIES, 2);
|
var fValid = true;
|
||||||
dir.lbaData = dir.lbaRoot + (((dir.nEntries * DiskAPI.DIR.LENGTH) / dir.cbSector) | 0);
|
if (dir.cbSector != this.cbSector) {
|
||||||
dir.nClusterSecs = this.getSectorData(sectorBoot, DiskAPI.BPB.CLUSTER_SECS, 1);
|
/*
|
||||||
|
* When the first sector doesn't appear to contain a valid BPB, the most likely explanations are:
|
||||||
|
*
|
||||||
|
* 1. The image is from a diskette formatted by DOS 1.xx, which didn't use BPBs
|
||||||
|
* 2. The image is a fixed (partitioned) disk and the first sector is actually an MBR
|
||||||
|
* 3. The image is from a diskette that used a non-standard sector size (ie, not 512)
|
||||||
|
*
|
||||||
|
* To start, if this is an 160Kb disk (circa DOS 1.00) or a 320Kb disk (circa DOS 1.10), then we'll
|
||||||
|
* assume it's a 12-bit FAT, set assorted BPB values accordingly, and see if our assumption holds up.
|
||||||
|
*/
|
||||||
|
fValid = false;
|
||||||
|
dir.lbaFAT = 1;
|
||||||
|
dir.nFATBits = 12;
|
||||||
|
dir.lbaRoot = dir.lbaFAT + 2; // both 160Kb and 320Kb disks contained 2 FATs, each containing 1 sector
|
||||||
|
dir.nClusterSecs = 1;
|
||||||
|
dir.cbSector = this.cbSector;
|
||||||
|
|
||||||
|
if (cbDisk == 160 * 1024 && this.getClusterEntry(dir, 0, 0) == DiskAPI.FAT.MEDIA_160KB) {
|
||||||
|
dir.lbaTotal = 320;
|
||||||
|
dir.nEntries = 64;
|
||||||
|
fValid = true;
|
||||||
|
}
|
||||||
|
else if (cbDisk == 320 * 1024 && this.getClusterEntry(dir, 0, 0) == DiskAPI.FAT.MEDIA_320KB) {
|
||||||
|
dir.lbaTotal = 640;
|
||||||
|
dir.nEntries = 112;
|
||||||
|
fValid = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/*
|
||||||
|
* So, this is either a fixed (partitioned) disk, or a disk using a non-standard sector size; let's assume
|
||||||
|
* the former and check for an MBR. For now, we're only going to process the first active partition we find.
|
||||||
|
*/
|
||||||
|
off = DiskAPI.MBR.PARTITIONS.OFFSET;
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
var bStatus = this.getSectorData(sectorBoot, off + DiskAPI.MBR.PARTITIONS.ENTRY.STATUS, 1);
|
||||||
|
if (bStatus == DiskAPI.MBR.PARTITIONS.STATUS.ACTIVE) {
|
||||||
|
dir.pbaVolume = this.getSectorData(sectorBoot, off + DiskAPI.MBR.PARTITIONS.ENTRY.LBA_FIRST, 4);
|
||||||
|
sectorBoot = this.getSector(dir.pbaVolume);
|
||||||
|
if (sectorBoot) fValid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
off += DiskAPI.MBR.PARTITIONS.ENTRY.LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fValid) {
|
||||||
|
if (DEBUG && this.messageEnabled()) {
|
||||||
|
this.messagePrint("buildFileTable(): unrecognized " + cbDisk + "-byte disk image with " + this.cbSector + "-byte sectors");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dir.lbaTotal) {
|
||||||
|
dir.lbaTotal = this.getSectorData(sectorBoot, DiskAPI.BPB.TOTAL_SECS, 2) || this.getSectorData(sectorBoot, DiskAPI.BPB.LARGE_SECS, 4);
|
||||||
|
dir.lbaFAT = this.getSectorData(sectorBoot, DiskAPI.BPB.RESERVED_SECS, 2);
|
||||||
|
dir.lbaRoot = dir.lbaFAT + this.getSectorData(sectorBoot, DiskAPI.BPB.FAT_SECS, 2) * this.getSectorData(sectorBoot, DiskAPI.BPB.TOTAL_FATS, 1);
|
||||||
|
dir.nEntries = this.getSectorData(sectorBoot, DiskAPI.BPB.ROOT_DIRENTS, 2);
|
||||||
|
dir.nClusterSecs = this.getSectorData(sectorBoot, DiskAPI.BPB.CLUSTER_SECS, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.lbaData = dir.lbaRoot + (((dir.nEntries * DiskAPI.DIRENT.LENGTH + (dir.cbSector - 1)) / dir.cbSector) | 0);
|
||||||
dir.nClusters = (((dir.lbaTotal - dir.lbaData) / dir.nClusterSecs) | 0);
|
dir.nClusters = (((dir.lbaTotal - dir.lbaData) / dir.nClusterSecs) | 0);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In all FATs, the first valid cluster number is 2, as 0 is used to indicate a free cluster and 1 is reserved.
|
* In all FATs, the first valid cluster number is 2, as 0 is used to indicate a free cluster and 1 is reserved.
|
||||||
*
|
*
|
||||||
* In a 12-bit FAT, cluster numbers 0xFF0-0xFF6 are reserved, 0xFF7 indicates a bad cluster, and 0xFF8-0xFFF
|
* In a 12-bit FAT chain, the largest valid cluster number (iClusterMax) is 0xFF6; 0xFF7 is reserved for marking
|
||||||
* indicate the last cluster in a chain. Since 12 bits yields 4096 possible values, and since 10 of the values
|
* bad clusters and should NEVER appear in a cluster chain, and 0xFF8-0xFFF are used to indicate the end of a chain.
|
||||||
* (0, 1, and 0xFF8-0xFFF) cannot be used to refer to an actual cluster, that leaves a maximum of 4086 clusters
|
* Reports that cluster numbers 0xFF0-0xFF6 are "reserved" (eg, http://support.microsoft.com/KB/65541) should be
|
||||||
* for a 12-bit FAT; a volume with more than 4086 clusters must be using a 16-bit FAT.
|
* ignored; those numbers may have been considered "reserved" at some early point in FAT's history, but no longer.
|
||||||
*
|
*
|
||||||
* TODO: More research is required to ensure we're setting iClusterLimit appropriately, because 0xFF0 seems the
|
* Since 12 bits yield 4096 possible values, and since 11 of the values (0, 1, and 0xFF7-0xFFF) cannot be used to
|
||||||
* safer limit for a 12-bit FAT, not 0xFF8. Despite Microsoft's claim the cut-over from a 12-bit to a 16-bit FAT
|
* refer to an actual cluster, that leaves a theoretical maximum of 4085 clusters for a 12-bit FAT. However, for
|
||||||
* is 4086 clusters, I'm not sure why that's true, unless there are volumes that actually use clusters >= 0xFF0.
|
* reasons that only a small (and shrinking -- RIP AAR) number of people know, the actual cut-off is 4084.
|
||||||
*
|
*
|
||||||
* Also, perhaps it would be worth setting the limit to match the capacity of the volume, instead of the theoretical
|
* So, a FAT volume with 4084 or fewer clusters uses a 12-bit FAT, a FAT volume with 4085 to 65524 clusters uses
|
||||||
* limit, as that might help us catch more problems.
|
* a 16-bit FAT, and a FAT volume with more than 65524 clusters uses a 32-bit FAT.
|
||||||
|
*
|
||||||
|
* TODO: Eventually add support for FAT32.
|
||||||
*/
|
*/
|
||||||
dir.nFATBits = (dir.nClusters <= 4086? 12 : 16);
|
dir.nFATBits = (dir.nClusters <= DiskAPI.FAT12.MAX_CLUSTERS? 12 : 16);
|
||||||
dir.iClusterLimit = (dir.nFATBits == 12? 0xFF0 : 0xFFF0);
|
dir.iClusterMax = (dir.nFATBits == 12? DiskAPI.FAT12.CLUSNUM_MAX : DiskAPI.FAT16.CLUSNUM_MAX);
|
||||||
this.assert(!((dir.lbaTotal - dir.lbaData) % dir.nClusterSecs));
|
|
||||||
this.assert(!((dir.nEntries * DiskAPI.DIR.LENGTH) % dir.cbSector));
|
if (DEBUG && this.messageEnabled()) {
|
||||||
var aSectors = [];
|
this.messagePrint("buildFileTable()\n\tlbaFAT: " + dir.lbaFAT + "\n\tlbaRoot: " + dir.lbaRoot + "\n\tlbaData: " + dir.lbaData + "\n\tlbaTotal: " + dir.lbaTotal + "\n\tnClusterSecs: " + dir.nClusterSecs + "\n\tnClusters: " + dir.nClusters);
|
||||||
for (var i = dir.lbaRoot; i < dir.lbaData; i++) aSectors.push(i);
|
}
|
||||||
this.getDir(dir, "", aSectors);
|
|
||||||
|
/*
|
||||||
|
* The following assertion is here only to catch anomalies; it is NOT a requirement that the number of data sectors
|
||||||
|
* be a perfect multiple of nClusterSecs, but if it ever happens, it's worth verifying we didn't miscalculate something.
|
||||||
|
*/
|
||||||
|
i = (dir.lbaTotal - dir.lbaData) % dir.nClusterSecs;
|
||||||
|
if (i) {
|
||||||
|
if (DEBUG && this.messageEnabled()) {
|
||||||
|
this.messagePrint("buildFileTable(): " + cbDisk + "-byte disk image wasting " + i + " sectors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similarly, it is NOT a requirement that the size of all root directory entries be a perfect multiple of the sector
|
||||||
|
* size (cbSector), but it may indicate a problem if it's not. Note that when it comes time to read the root directory,
|
||||||
|
* we treat it exactly like any other directory; that is, we ignore the nEntries value and scan the entire contents of
|
||||||
|
* every sector allocated to the directory. TODO: Determine whether DOS reads all root sector contents or only nEntries
|
||||||
|
* (ie, create a test volume where nEntries * 32 is NOT a multiple of cbSector and watch what happens).
|
||||||
|
*/
|
||||||
|
this.assert(!((dir.nEntries * DiskAPI.DIRENT.LENGTH) % dir.cbSector));
|
||||||
|
|
||||||
|
var apba = [];
|
||||||
|
for (var lba = dir.lbaRoot; lba < dir.lbaData; lba++) apba.push(dir.pbaVolume + lba);
|
||||||
|
this.getDir(dir, "", apba);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the sector-to-file mappings now.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < this.aFileTable.length; i++) {
|
||||||
|
var file = this.aFileTable[i];
|
||||||
|
off = 0;
|
||||||
|
for (var iSector = 0; iSector < file.apba.length; iSector++) {
|
||||||
|
this.updateSector(file, file.apba[iSector], off);
|
||||||
|
off += this.cbSector;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getDir(dir, sDir, aSectors)
|
* getDir(dir, sDir, apba)
|
||||||
*
|
*
|
||||||
* @this {Disk}
|
* @this {Disk}
|
||||||
* @param {Object} dir
|
* @param {Object} dir
|
||||||
* @param {string} sDir
|
* @param {string} sDir
|
||||||
* @param {Array.<number>} aSectors
|
* @param {Array.<number>} apba
|
||||||
*/
|
*/
|
||||||
Disk.prototype.getDir = function(dir, sDir, aSectors)
|
Disk.prototype.getDir = function(dir, sDir, apba)
|
||||||
{
|
{
|
||||||
var iStart = this.aFileTable.length;
|
var iStart = this.aFileTable.length;
|
||||||
var nEntriesPerSector = (dir.cbSector / DiskAPI.DIR.LENGTH) | 0;
|
var nEntriesPerSector = (dir.cbSector / DiskAPI.DIRENT.LENGTH) | 0;
|
||||||
|
|
||||||
for (var iSector = 0; iSector < aSectors.length; iSector++) {
|
if (DEBUG && this.messageEnabled()) this.messagePrint("getDir(" + sDir + ")");
|
||||||
var lba = aSectors[iSector];
|
|
||||||
|
dir.sDir = sDir + "\\";
|
||||||
|
for (var iSector = 0; iSector < apba.length; iSector++) {
|
||||||
|
var pba = apba[iSector];
|
||||||
for (var iEntry = 0; iEntry < nEntriesPerSector; iEntry++) {
|
for (var iEntry = 0; iEntry < nEntriesPerSector; iEntry++) {
|
||||||
if (!this.getDirEntry(dir, lba, iEntry)) continue;
|
if (!this.getDirEntry(dir, pba, iEntry)) {
|
||||||
if (DEBUG) {
|
iSector = apba.length;
|
||||||
this.controller.println('"' + dir.sName + '" size=' + dir.cbSize + ' cluster=' + dir.iCluster + ' sectors=' + JSON.stringify(dir.aSectors));
|
break;
|
||||||
if (dir.aSectors.length) this.getSector(dir.aSectors[0]);
|
|
||||||
}
|
}
|
||||||
this.aFileTable.push({sName: sDir + "\\" + dir.sName, bAttr: dir.bAttr, cbSize: dir.cbSize, aSectors: dir.aSectors});
|
if (dir.sName == null || dir.sName == "." || dir.sName == "..") continue;
|
||||||
|
var sPath = dir.sDir + dir.sName;
|
||||||
|
if (DEBUG && this.messageEnabled(Messages.DISK | Messages.DATA)) {
|
||||||
|
this.messagePrint('"' + sPath + '" size=' + dir.cbSize + ' cluster=' + dir.iCluster + ' sectors=' + JSON.stringify(dir.apba));
|
||||||
|
if (dir.apba.length) this.messagePrint(this.dumpSector(this.getSector(dir.apba[0]), dir.apba[0], sPath));
|
||||||
|
}
|
||||||
|
this.aFileTable.push({sPath: sPath, sName: dir.sName, bAttr: dir.bAttr, cbSize: dir.cbSize, apba: dir.apba, disk: this});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -952,30 +1067,28 @@ Disk.prototype.getDir = function(dir, sDir, aSectors)
|
||||||
|
|
||||||
for (var i = iStart; i < iEnd; i++) {
|
for (var i = iStart; i < iEnd; i++) {
|
||||||
var file = this.aFileTable[i];
|
var file = this.aFileTable[i];
|
||||||
if (file.bAttr & DiskAPI.ATTR.SUBDIR && file.aSectors.length) {
|
if (file.bAttr & DiskAPI.ATTR.SUBDIR && file.apba.length) this.getDir(dir, sDir + "\\" + file.sName, file.apba);
|
||||||
this.getDir(dir, sDir + "\\" + file.sName, file.aSectors[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getDirEntry(dir, lba, i)
|
* getDirEntry(dir, pba, i)
|
||||||
*
|
*
|
||||||
* This sets the following properties on the 'dir' object:
|
* This sets the following properties on the 'dir' object:
|
||||||
*
|
*
|
||||||
* sName
|
* sName (null if invalid/deleted entry)
|
||||||
* bAttr
|
* bAttr
|
||||||
* cbSize
|
* cbSize
|
||||||
* iCluster
|
* iCluster
|
||||||
* aSectors (ie, array of lba values)
|
* apba (ie, array of physical block addresses)
|
||||||
*
|
*
|
||||||
* On return, it's the caller's responsibility to copy out any data into a new object
|
* On return, it's the caller's responsibility to copy out any data into a new object
|
||||||
* if it wants to preserve any of the above information.
|
* if it wants to preserve any of the above information.
|
||||||
*
|
*
|
||||||
* This function also caches the following properties in the 'dir' object:
|
* This function also caches the following properties in the 'dir' object:
|
||||||
*
|
*
|
||||||
* lbaDir (of the last directory sector read, if any)
|
* pbaDirCache (of the last directory sector read, if any)
|
||||||
* sectorDir (of the last directory sector read, if any)
|
* sectorDirCache (of the last directory sector read, if any)
|
||||||
*
|
*
|
||||||
* Also, the caller must also set the following 'dir' helper properties, so that clusters
|
* Also, the caller must also set the following 'dir' helper properties, so that clusters
|
||||||
* can be located and converted to sectors (see convertClusterToSectors):
|
* can be located and converted to sectors (see convertClusterToSectors):
|
||||||
|
|
@ -983,33 +1096,42 @@ Disk.prototype.getDir = function(dir, sDir, aSectors)
|
||||||
* lbaFAT
|
* lbaFAT
|
||||||
* lbaData
|
* lbaData
|
||||||
* cbSector
|
* cbSector
|
||||||
* iClusterLimit
|
* iClusterMax
|
||||||
* nClusterSecs
|
* nClusterSecs
|
||||||
* nFATBits
|
* nFATBits
|
||||||
*
|
*
|
||||||
* @this {Disk}
|
* @this {Disk}
|
||||||
* @param {Object} dir (to be filled in)
|
* @param {Object} dir (to be filled in)
|
||||||
* @param {number} lba (a sector of the directory)
|
* @param {number} pba (a sector of the directory)
|
||||||
* @param {number} i (an entry in the directory sector, 0-based)
|
* @param {number} i (an entry in the directory sector, 0-based)
|
||||||
* @returns {boolean} true if valid entry, false if deleted or empty (or no more sectors)
|
* @returns {boolean} true if entry was returned (even if invalid/deleted), false if no more entries
|
||||||
*/
|
*/
|
||||||
Disk.prototype.getDirEntry = function(dir, lba, i)
|
Disk.prototype.getDirEntry = function(dir, pba, i)
|
||||||
{
|
{
|
||||||
if (!dir.sectorDir || !dir.lbaDir || dir.lbaDir != lba) {
|
if (!dir.sectorDirCache || !dir.pbaDirCache || dir.pbaDirCache != pba) {
|
||||||
dir.lbaDir = lba;
|
dir.pbaDirCache = pba;
|
||||||
dir.sectorDir = this.getSector(dir.lbaDir);
|
dir.sectorDirCache = this.getSector(dir.pbaDirCache);
|
||||||
|
if (DEBUG && this.messageEnabled(Messages.DISK | Messages.DATA)) {
|
||||||
|
this.messagePrint(this.dumpSector(dir.sectorDirCache, dir.pbaDirCache, dir.sDir));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (dir.sectorDir) {
|
if (dir.sectorDirCache) {
|
||||||
var off = i * DiskAPI.DIR.LENGTH;
|
var off = i * DiskAPI.DIRENT.LENGTH;
|
||||||
var b = this.getSectorData(dir.sectorDir, off, 1);
|
var b = this.getSectorData(dir.sectorDirCache, off, 1);
|
||||||
if (!b || b == 0xe5) return false;
|
if (b == DiskAPI.DIRENT.UNUSED) {
|
||||||
dir.sName = str.trim(this.getSectorString(dir.sectorDir, off + DiskAPI.DIR.NAME, 8));
|
return false;
|
||||||
var s = str.trim(this.getSectorString(dir.sectorDir, off + DiskAPI.DIR.EXT, 3));
|
}
|
||||||
|
if (b == DiskAPI.DIRENT.INVALID) {
|
||||||
|
dir.sName = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
dir.sName = str.trim(this.getSectorString(dir.sectorDirCache, off + DiskAPI.DIRENT.NAME, 8));
|
||||||
|
var s = str.trim(this.getSectorString(dir.sectorDirCache, off + DiskAPI.DIRENT.EXT, 3));
|
||||||
if (s.length) dir.sName += '.' + s;
|
if (s.length) dir.sName += '.' + s;
|
||||||
dir.bAttr = this.getSectorData(dir.sectorDir, off + DiskAPI.DIR.ATTR, 1);
|
dir.bAttr = this.getSectorData(dir.sectorDirCache, off + DiskAPI.DIRENT.ATTR, 1);
|
||||||
dir.cbSize = this.getSectorData(dir.sectorDir, off + DiskAPI.DIR.SIZE, 2);
|
dir.cbSize = this.getSectorData(dir.sectorDirCache, off + DiskAPI.DIRENT.SIZE, 2);
|
||||||
dir.iCluster = this.getSectorData(dir.sectorDir, off + DiskAPI.DIR.CLUSTER, 2);
|
dir.iCluster = this.getSectorData(dir.sectorDirCache, off + DiskAPI.DIRENT.CLUSTER, 2);
|
||||||
this.convertClusterToSectors(dir);
|
dir.apba = this.convertClusterToSectors(dir);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1020,21 +1142,24 @@ Disk.prototype.getDirEntry = function(dir, lba, i)
|
||||||
*
|
*
|
||||||
* @this {Disk}
|
* @this {Disk}
|
||||||
* @param {Object} dir
|
* @param {Object} dir
|
||||||
|
* @return {Array.<number>} of PBAs (physical block addresses)
|
||||||
*/
|
*/
|
||||||
Disk.prototype.convertClusterToSectors = function(dir)
|
Disk.prototype.convertClusterToSectors = function(dir)
|
||||||
{
|
{
|
||||||
dir.aSectors = [];
|
var apba = [];
|
||||||
var iCluster = dir.iCluster;
|
var iCluster = dir.iCluster;
|
||||||
if (iCluster) {
|
if (iCluster) {
|
||||||
do {
|
do {
|
||||||
this.assert(iCluster >= 2);
|
this.assert(iCluster >= DiskAPI.FAT12.CLUSNUM_MIN);
|
||||||
var lba = dir.lbaData + ((iCluster - 2) * dir.nClusterSecs);
|
var lba = dir.lbaData + ((iCluster - DiskAPI.FAT12.CLUSNUM_MIN) * dir.nClusterSecs);
|
||||||
for (var i = 0; i < dir.nClusterSecs; i++) {
|
for (var i = 0; i < dir.nClusterSecs; i++) {
|
||||||
dir.aSectors.push(lba++);
|
apba.push(dir.pbaVolume + lba++);
|
||||||
}
|
}
|
||||||
iCluster = this.getClusterEntry(dir, iCluster, 0) | this.getClusterEntry(dir, iCluster, 1);
|
iCluster = this.getClusterEntry(dir, iCluster, 0) | this.getClusterEntry(dir, iCluster, 1);
|
||||||
} while (iCluster < dir.iClusterLimit);
|
} while (iCluster <= dir.iClusterMax);
|
||||||
|
this.assert(iCluster != dir.iClusterMax + 1); // make sure we never see CLUSNUM_BAD in a cluster chain
|
||||||
}
|
}
|
||||||
|
return apba;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1050,15 +1175,15 @@ Disk.prototype.getClusterEntry = function(dir, iCluster, iByte)
|
||||||
{
|
{
|
||||||
var w = 0;
|
var w = 0;
|
||||||
var cbitsSector = dir.cbSector * 8;
|
var cbitsSector = dir.cbSector * 8;
|
||||||
var offBits = dir.nFATBits * iCluster;
|
var offBits = dir.nFATBits * iCluster + (iByte? 8 : 0);
|
||||||
var iSec = (offBits / cbitsSector) | 0;
|
var iSector = (offBits / cbitsSector) | 0;
|
||||||
if (!dir.sectorFATCache || !dir.lbaFATCache || dir.lbaFATCache != dir.lbaFAT + iSec) {
|
if (!dir.sectorFATCache || !dir.lbaFATCache || dir.lbaFATCache != dir.lbaFAT + iSector) {
|
||||||
dir.lbaFATCache = dir.lbaFAT + iSec;
|
dir.lbaFATCache = dir.lbaFAT + iSector;
|
||||||
dir.sectorFATCache = this.getSector(dir.lbaFATCache);
|
dir.sectorFATCache = this.getSector(dir.pbaVolume + dir.lbaFATCache);
|
||||||
}
|
}
|
||||||
if (dir.sectorFATCache) {
|
if (dir.sectorFATCache) {
|
||||||
offBits = (offBits % cbitsSector) | 0;
|
offBits = (offBits % cbitsSector) | 0;
|
||||||
var off = (offBits >> 3) + iByte;
|
var off = (offBits >> 3);
|
||||||
w = this.getSectorData(dir.sectorFATCache, off, 1);
|
w = this.getSectorData(dir.sectorFATCache, off, 1);
|
||||||
if (!iByte) {
|
if (!iByte) {
|
||||||
if (offBits & 0x7) w >>= 4;
|
if (offBits & 0x7) w >>= 4;
|
||||||
|
|
@ -1066,6 +1191,7 @@ Disk.prototype.getClusterEntry = function(dir, iCluster, iByte)
|
||||||
if (dir.nFATBits == 16) {
|
if (dir.nFATBits == 16) {
|
||||||
w <<= 8;
|
w <<= 8;
|
||||||
} else {
|
} else {
|
||||||
|
this.assert(dir.nFATBits == 12);
|
||||||
if (offBits & 0x7) {
|
if (offBits & 0x7) {
|
||||||
w <<= 4;
|
w <<= 4;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1078,28 +1204,57 @@ Disk.prototype.getClusterEntry = function(dir, iCluster, iByte)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getSector(lba)
|
* getSector(pba)
|
||||||
*
|
*
|
||||||
* @this {Disk}
|
* @this {Disk}
|
||||||
* @param {number} lba (logical block address)
|
* @param {number} pba (physical block address)
|
||||||
* @return {Object} sector
|
* @return {Object} sector
|
||||||
*/
|
*/
|
||||||
Disk.prototype.getSector = function(lba)
|
Disk.prototype.getSector = function(pba)
|
||||||
{
|
{
|
||||||
var nSectorsPerCylinder = this.nHeads * this.nSectors;
|
var nSectorsPerCylinder = this.nHeads * this.nSectors;
|
||||||
var iCylinder = (lba / nSectorsPerCylinder) | 0;
|
var iCylinder = (pba / nSectorsPerCylinder) | 0;
|
||||||
this.assert(iCylinder < this.nCylinders);
|
this.assert(iCylinder < this.nCylinders);
|
||||||
var nSectorsRemaining = (lba % nSectorsPerCylinder);
|
var nSectorsRemaining = (pba % nSectorsPerCylinder);
|
||||||
var iHead = (nSectorsRemaining / this.nSectors) | 0;
|
var iHead = (nSectorsRemaining / this.nSectors) | 0;
|
||||||
this.assert(iHead < this.nHeads);
|
|
||||||
var iSector = (nSectorsRemaining % this.nSectors);
|
|
||||||
this.assert(iSector < this.nSectors);
|
|
||||||
/*
|
/*
|
||||||
* LBA numbers are 0-based, but the sector numbers in CHS addressing are 1-based, so add one to iSector
|
* PBA numbers are 0-based, but the sector numbers in CHS addressing are 1-based, so add one to iSector
|
||||||
*/
|
*/
|
||||||
var sector = this.seek(iCylinder, iHead, iSector + 1);
|
var iSector = (nSectorsRemaining % this.nSectors) + 1;
|
||||||
if (DEBUG) this.controller.println("logical sector #" + lba + ":\n" + this.dumpSector(sector));
|
return this.seek(iCylinder, iHead, iSector);
|
||||||
return sector;
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* updateSector(file, pba, off)
|
||||||
|
*
|
||||||
|
* Like getSector(), this must convert a PBA into CHS values; consider factoring that conversion code out.
|
||||||
|
*
|
||||||
|
* @this {Disk}
|
||||||
|
* @param {Object} file
|
||||||
|
* @param {number} pba (physical block address from the file's apba)
|
||||||
|
* @param {number} off (file offset corresponding to the given pba of the given file)
|
||||||
|
* @return {boolean} true if successfully updated, false if not
|
||||||
|
*/
|
||||||
|
Disk.prototype.updateSector = function(file, pba, off)
|
||||||
|
{
|
||||||
|
var nSectorsPerCylinder = this.nHeads * this.nSectors;
|
||||||
|
var iCylinder = (pba / nSectorsPerCylinder) | 0;
|
||||||
|
var nSectorsRemaining = (pba % nSectorsPerCylinder);
|
||||||
|
var iHead = (nSectorsRemaining / this.nSectors) | 0;
|
||||||
|
var iSector = (nSectorsRemaining % this.nSectors) + 1;
|
||||||
|
var cylinder, head, sector;
|
||||||
|
if ((cylinder = this.aDiskData[iCylinder]) && (head = cylinder[iHead]) && (sector = head[iSector])) {
|
||||||
|
if (sector.file) {
|
||||||
|
if (DEBUG && this.messageEnabled()) {
|
||||||
|
this.messagePrint('"' + sector.file.sPath + '" cross-linked at offset ' + sector.file.offFile + ' with "' + file.sPath + '" at offset ' + off);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sector.file = file;
|
||||||
|
sector.offFile = off;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1217,14 +1372,14 @@ Disk.prototype.connectRemoteDisk = function(sDiskPath)
|
||||||
* @param {number} iHead
|
* @param {number} iHead
|
||||||
* @param {number} iSector
|
* @param {number} iSector
|
||||||
* @param {number} cbSector
|
* @param {number} cbSector
|
||||||
* @param {number} nSectors
|
* @param {number} nSectors (to read)
|
||||||
* @param {boolean} fAsync
|
* @param {boolean} fAsync
|
||||||
* @param {function(number,boolean)} [done]
|
* @param {function(number,boolean)} [done]
|
||||||
*/
|
*/
|
||||||
Disk.prototype.readRemoteSectors = function(iCylinder, iHead, iSector, cbSector, nSectors, fAsync, done)
|
Disk.prototype.readRemoteSectors = function(iCylinder, iHead, iSector, cbSector, nSectors, fAsync, done)
|
||||||
{
|
{
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint("Disk.readRemoteSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + "," + cbSector + ")");
|
this.messagePrint("readRemoteSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + "," + cbSector + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fRemote) {
|
if (this.fRemote) {
|
||||||
|
|
@ -1252,17 +1407,13 @@ Disk.prototype.readRemoteSectors = function(iCylinder, iHead, iSector, cbSector,
|
||||||
Disk.prototype.doneReadRemoteSectors = function(sURLName, sURLData, nErrorCode, sectorInfo)
|
Disk.prototype.doneReadRemoteSectors = function(sURLName, sURLData, nErrorCode, sectorInfo)
|
||||||
{
|
{
|
||||||
var fAsync = false;
|
var fAsync = false;
|
||||||
|
|
||||||
|
var iCylinder = sectorInfo[0];
|
||||||
|
var iHead = sectorInfo[1];
|
||||||
|
var iSector = sectorInfo[2];
|
||||||
|
var nSectors = sectorInfo[3];
|
||||||
|
|
||||||
if (!nErrorCode) {
|
if (!nErrorCode) {
|
||||||
var iCylinder = sectorInfo[0];
|
|
||||||
var iHead = sectorInfo[1];
|
|
||||||
var iSector = sectorInfo[2];
|
|
||||||
var nSectors = sectorInfo[3];
|
|
||||||
fAsync = sectorInfo[4];
|
|
||||||
|
|
||||||
if (DEBUG && this.messageEnabled()) {
|
|
||||||
this.messagePrint("Disk.doneReadRemoteSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
var abData = JSON.parse(sURLData);
|
var abData = JSON.parse(sURLData);
|
||||||
var offData = 0;
|
var offData = 0;
|
||||||
while (nSectors--) {
|
while (nSectors--) {
|
||||||
|
|
@ -1278,7 +1429,7 @@ Disk.prototype.doneReadRemoteSectors = function(sURLName, sURLData, nErrorCode,
|
||||||
var sector = this.seek(iCylinder, iHead, iSector, true);
|
var sector = this.seek(iCylinder, iHead, iSector, true);
|
||||||
if (!sector) {
|
if (!sector) {
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint("Disk.doneReadRemoteSectors(): seek(" + iCylinder + "," + iHead + "," + iSector + ") failed");
|
this.messagePrint("doneReadRemoteSectors(): seek(" + iCylinder + "," + iHead + "," + iSector + ") failed");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1290,6 +1441,11 @@ Disk.prototype.doneReadRemoteSectors = function(sURLName, sURLData, nErrorCode,
|
||||||
*/
|
*/
|
||||||
iSector++;
|
iSector++;
|
||||||
}
|
}
|
||||||
|
fAsync = sectorInfo[4];
|
||||||
|
} else {
|
||||||
|
if (DEBUG && this.messageEnabled()) {
|
||||||
|
this.messagePrint("doneReadRemoteSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + ") returned error " + nErrorCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var done = sectorInfo[5];
|
var done = sectorInfo[5];
|
||||||
if (done) done(nErrorCode, fAsync);
|
if (done) done(nErrorCode, fAsync);
|
||||||
|
|
@ -1310,7 +1466,7 @@ Disk.prototype.doneReadRemoteSectors = function(sURLName, sURLData, nErrorCode,
|
||||||
* @param {number} iCylinder
|
* @param {number} iCylinder
|
||||||
* @param {number} iHead
|
* @param {number} iHead
|
||||||
* @param {number} iSector
|
* @param {number} iSector
|
||||||
* @param {number} nSectors
|
* @param {number} nSectors (to write)
|
||||||
* @param {Array.<number>} abSectors
|
* @param {Array.<number>} abSectors
|
||||||
* @param {boolean} fAsync
|
* @param {boolean} fAsync
|
||||||
* @return {boolean|Array}
|
* @return {boolean|Array}
|
||||||
|
|
@ -1318,7 +1474,7 @@ Disk.prototype.doneReadRemoteSectors = function(sURLName, sURLData, nErrorCode,
|
||||||
Disk.prototype.writeRemoteSectors = function(iCylinder, iHead, iSector, nSectors, abSectors, fAsync)
|
Disk.prototype.writeRemoteSectors = function(iCylinder, iHead, iSector, nSectors, abSectors, fAsync)
|
||||||
{
|
{
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint("Disk.writeRemoteSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + ")");
|
this.messagePrint("writeRemoteSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fRemote) {
|
if (this.fRemote) {
|
||||||
|
|
@ -1358,15 +1514,14 @@ Disk.prototype.doneWriteRemoteSectors = function(sURLName, sURLData, nErrorCode,
|
||||||
for (var i = iSector - 1; nSectors-- > 0 && i >= 0 && i < this.aDiskData[iCylinder][iHead].length; i++) {
|
for (var i = iSector - 1; nSectors-- > 0 && i >= 0 && i < this.aDiskData[iCylinder][iHead].length; i++) {
|
||||||
var sector = this.aDiskData[iCylinder][iHead][i];
|
var sector = this.aDiskData[iCylinder][iHead][i];
|
||||||
|
|
||||||
if (DEBUG && this.messageEnabled()) {
|
|
||||||
this.messagePrint("Disk.doneWriteRemoteSectors(" + iCylinder + ":" + iHead + ":" + sector['sector'] + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nErrorCode) {
|
if (!nErrorCode) {
|
||||||
if (!sector.fDirty) {
|
if (!sector.fDirty) {
|
||||||
sector.iModify = sector.cModify = 0;
|
sector.iModify = sector.cModify = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (DEBUG && this.messageEnabled()) {
|
||||||
|
this.messagePrint("doneWriteRemoteSectors(" + iCylinder + ":" + iHead + ":" + sector['sector'] + ") returned error " + nErrorCode);
|
||||||
|
}
|
||||||
this.queueDirtySector(sector, false);
|
this.queueDirtySector(sector, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1424,7 +1579,7 @@ Disk.prototype.queueDirtySector = function(sector, fAsync)
|
||||||
this.aDirtyTimestamps.push(usr.getTime());
|
this.aDirtyTimestamps.push(usr.getTime());
|
||||||
|
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint("Disk.queueDirtySector(" + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + "): " + this.aDirtySectors.length + " dirty");
|
this.messagePrint("queueDirtySector(" + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + "): " + this.aDirtySectors.length + " dirty");
|
||||||
}
|
}
|
||||||
|
|
||||||
return fAsync && this.updateWriteTimer();
|
return fAsync && this.updateWriteTimer();
|
||||||
|
|
@ -1495,7 +1650,7 @@ Disk.prototype.findDirtySectors = function(fAsync)
|
||||||
var j = this.aDirtySectors.indexOf(sectorNext);
|
var j = this.aDirtySectors.indexOf(sectorNext);
|
||||||
this.assert(j >= 0, "dirty sector (" + iCylinder + ":" + iHead + ":" + sectorNext['sector'] + ") missing from aDirtySectors");
|
this.assert(j >= 0, "dirty sector (" + iCylinder + ":" + iHead + ":" + sectorNext['sector'] + ") missing from aDirtySectors");
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint("Disk.findDirtySectors(" + iCylinder + ":" + iHead + ":" + sectorNext['sector'] + ")");
|
this.messagePrint("findDirtySectors(" + iCylinder + ":" + iHead + ":" + sectorNext['sector'] + ")");
|
||||||
}
|
}
|
||||||
this.aDirtySectors.splice(j, 1);
|
this.aDirtySectors.splice(j, 1);
|
||||||
this.aDirtyTimestamps.splice(j, 1);
|
this.aDirtyTimestamps.splice(j, 1);
|
||||||
|
|
@ -1670,7 +1825,7 @@ Disk.prototype.read = function(sector, ibSector, fCompare)
|
||||||
var b = -1;
|
var b = -1;
|
||||||
if (sector) {
|
if (sector) {
|
||||||
if (DEBUG && !ibSector && !fCompare && this.messageEnabled()) {
|
if (DEBUG && !ibSector && !fCompare && this.messageEnabled()) {
|
||||||
this.messagePrint("Disk.read(" + this.controller.id + ":" + this.drive.iDrive + "," + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + ")");
|
this.messagePrint("read(" + this.controller.id + ":" + this.drive.iDrive + "," + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + ")");
|
||||||
}
|
}
|
||||||
if (ibSector < sector['length']) {
|
if (ibSector < sector['length']) {
|
||||||
var adw = sector['data'];
|
var adw = sector['data'];
|
||||||
|
|
@ -1697,7 +1852,7 @@ Disk.prototype.write = function(sector, ibSector, b)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (DEBUG && !ibSector && this.messageEnabled()) {
|
if (DEBUG && !ibSector && this.messageEnabled()) {
|
||||||
this.messagePrint("Disk.write(" + this.controller.id + ":" + this.drive.iDrive + "," + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + ")");
|
this.messagePrint("write(" + this.controller.id + ":" + this.drive.iDrive + "," + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ibSector < sector['length']) {
|
if (ibSector < sector['length']) {
|
||||||
|
|
@ -1769,7 +1924,7 @@ Disk.prototype.save = function()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint('Disk.save("' + this.sDiskName + '"): saved ' + (deltas.length - 1) + ' change(s)');
|
this.messagePrint('save("' + this.sDiskName + '"): saved ' + (deltas.length - 1) + ' change(s)');
|
||||||
}
|
}
|
||||||
return deltas;
|
return deltas;
|
||||||
};
|
};
|
||||||
|
|
@ -1899,7 +2054,7 @@ Disk.prototype.restore = function(deltas)
|
||||||
this.controller.notice("unable to restore disk '" + this.sDiskName + ": " + sReason);
|
this.controller.notice("unable to restore disk '" + this.sDiskName + ": " + sReason);
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG && this.messageEnabled()) {
|
if (DEBUG && this.messageEnabled()) {
|
||||||
this.messagePrint('Disk.restore("' + this.sDiskName + '"): restored ' + nChanges + ' change(s)');
|
this.messagePrint('restore("' + this.sDiskName + '"): restored ' + nChanges + ' change(s)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nChanges;
|
return nChanges;
|
||||||
|
|
@ -1939,15 +2094,18 @@ Disk.prototype.toJSON = function()
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dumpSector(sector)
|
* dumpSector(sector, pba, sDesc)
|
||||||
*
|
*
|
||||||
* @param {Object} sector (returned from a previous seek)
|
* @param {Object} sector (returned from a previous seek)
|
||||||
* @return {string|undefined}
|
* @param {number} [pba]
|
||||||
|
* @param {string} [sDesc]
|
||||||
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Disk.prototype.dumpSector = function(sector)
|
Disk.prototype.dumpSector = function(sector, pba, sDesc)
|
||||||
{
|
{
|
||||||
var sDump = "";
|
var sDump = "";
|
||||||
if (DEBUG && sector) {
|
if (DEBUG && sector) {
|
||||||
|
if (pba != null) sDump += "sector " + pba + (sDesc? (" for " + sDesc) : "") + ':';
|
||||||
var sBytes = "", sChars = "";
|
var sBytes = "", sChars = "";
|
||||||
var cbSector = sector['length'];
|
var cbSector = sector['length'];
|
||||||
var cdwData = sector['data'].length;
|
var cdwData = sector['data'].length;
|
||||||
|
|
|
||||||
|
|
@ -86,40 +86,126 @@ DiskAPI.DISKETTE_FORMATS = {
|
||||||
2949120: [80,2,36] // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
|
2949120: [80,2,36] // media type 0xF0: 80 cylinders, 2 heads (double-sided), 36 sectors/track, (5760 total sectors x 512 bytes/sector == 2949120)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DiskAPI.MBR = {
|
||||||
|
PARTITIONS: {
|
||||||
|
OFFSET: 0x1BE,
|
||||||
|
ENTRY: {
|
||||||
|
STATUS: 0x00, // 0x80 if active
|
||||||
|
CHS_FIRST: 0x01, // 3-byte CHS specifier
|
||||||
|
TYPE: 0x04, // see TYPE.*
|
||||||
|
CHS_LAST: 0x05, // 3-byte CHS specifier
|
||||||
|
LBA_FIRST: 0x08,
|
||||||
|
LBA_TOTAL: 0x0C,
|
||||||
|
LENGTH: 0x10
|
||||||
|
},
|
||||||
|
STATUS: {
|
||||||
|
ACTIVE: 0x80
|
||||||
|
},
|
||||||
|
TYPE: {
|
||||||
|
EMPTY: 0x00,
|
||||||
|
FAT12_PRIMARY: 0x01, // DOS 2.0 and up (12-bit FAT)
|
||||||
|
FAT16_PRIMARY: 0x04 // DOS 3.0 and up (16-bit FAT)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SIG_OFFSET: 0x1FE,
|
||||||
|
SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BIOS Parameter Block (BPB) offsets in DOS-compatible boot sectors
|
* Boot sector offsets (and assorted constants) in DOS-compatible boot sectors (DOS 2.0 and up)
|
||||||
|
*
|
||||||
|
* WARNING: I've heard apocryphal stories about SIGNATURE being improperly reversed on some systems
|
||||||
|
* (ie, 0x55AA instead 0xAA55) -- perhaps by a dyslexic programmer -- so be careful out there.
|
||||||
|
*/
|
||||||
|
DiskAPI.BOOT = {
|
||||||
|
JMP_OPCODE: 0x000, // 1 byte for a JMP opcode, followed by a 1 or 2-byte offset
|
||||||
|
OEM_STRING: 0x003, // 8 bytes
|
||||||
|
SIG_OFFSET: 0x1FE,
|
||||||
|
SIGNATURE: 0xAA55 // to be clear, the low byte (at offset 0x1FE) is 0x55 and the high byte (at offset 0x1FF) is 0xAA
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BIOS Parameter Block (BPB) offsets in DOS-compatible boot sectors (DOS 2.0 and up)
|
||||||
*/
|
*/
|
||||||
DiskAPI.BPB = {
|
DiskAPI.BPB = {
|
||||||
JMP_OPCODE: 0x00, // 1 byte for a JMP opcode, followed by a 1 or 2-byte offset
|
SECTOR_BYTES: 0x00B, // 2 bytes: bytes per sector (eg, 0x200 or 512)
|
||||||
OEM_STRING: 0x03, // 8 bytes
|
CLUSTER_SECS: 0x00D, // 1 byte: sectors per cluster (eg, 1)
|
||||||
SECTOR_BYTES: 0x0B, // 2 bytes: bytes per sector (eg, 0x200 or 512)
|
RESERVED_SECS: 0x00E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1)
|
||||||
CLUSTER_SECS: 0x0D, // 1 byte: sectors per cluster (eg, 1)
|
TOTAL_FATS: 0x010, // 1 byte: FAT copies (eg, 2)
|
||||||
RESERVED_SECS: 0x0E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1)
|
ROOT_DIRENTS: 0x011, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)
|
||||||
FAT_TOTAL: 0x10, // 1 byte: FAT copies (eg, 2)
|
TOTAL_SECS: 0x013, // 2 bytes: number of sectors (eg, 0x140 or 320); if zero, refer to LARGE_SECS
|
||||||
ROOT_ENTRIES: 0x11, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors)
|
MEDIA_TYPE: 0x015, // 1 byte: media type (see DiskAPI.FAT.MEDIA_*)
|
||||||
TOTAL_SECS: 0x13, // 2 bytes: number of sectors (eg, 0x140 or 320); if zero, refer to LARGE_SECS
|
FAT_SECS: 0x016, // 2 bytes: sectors per FAT (eg, 1)
|
||||||
MEDIA_TYPE: 0x15, // 1 byte: media type (eg, 0xFF: 320Kb, 0xFE: 160Kb, 0xFD: 360Kb, 0xFC: 180Kb)
|
TRACK_SECS: 0x018, // 2 bytes: sectors per track (eg, 8)
|
||||||
FAT_SECS: 0x16, // 2 bytes: sectors per FAT (eg, 1)
|
TOTAL_HEADS: 0x01A, // 2 bytes: number of heads (eg, 1)
|
||||||
TRACK_SECS: 0x18, // 2 bytes: sectors per track (eg, 8)
|
HIDDEN_SECS: 0x01C, // 4 bytes: number of hidden sectors (always 0 for non-partitioned media)
|
||||||
HEAD_TOTAL: 0x1A, // 2 bytes: number of heads (eg, 1)
|
LARGE_SECS: 0x020 // 4 bytes: number of sectors if TOTAL_SECS is zero
|
||||||
HIDDEN_SECS: 0x1C, // 4 bytes: number of hidden sectors (always 0 for non-partitioned media)
|
|
||||||
LARGE_SECS: 0x20 // 4 bytes: number of sectors if TOTAL_SECS is zero
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Directory Entry offsets in FAT-based disk images
|
* Media descriptor bytes for DOS-compatible FAT-formatted disks (stored in the first byte of the FAT)
|
||||||
*/
|
*/
|
||||||
DiskAPI.DIR = {
|
DiskAPI.FAT = {
|
||||||
NAME: 0x00, // 8 bytes
|
MEDIA_160KB: 0xFE, // 5.25-inch, 1-sided, 8-sector, 40-track
|
||||||
EXT: 0x08, // 3 bytes
|
MEDIA_180KB: 0xFC, // 5.25-inch, 1-sided, 9-sector, 40-track
|
||||||
ATTR: 0x0B, // 1 byte
|
MEDIA_320KB: 0xFF, // 5.25-inch, 2-sided, 8-sector, 40-track
|
||||||
MODTIME: 0x16, // 2 bytes
|
MEDIA_360KB: 0xFD, // 5.25-inch, 2-sided, 9-sector, 40-track
|
||||||
MODDATE: 0x18, // 2 bytes
|
MEDIA_720KB: 0xF9, // 3.5-inch, 2-sided, 9-sector, 80-track
|
||||||
CLUSTER: 0x1A, // 2 bytes
|
MEDIA_1200KB: 0xF9, // 3.5-inch, 2-sided, 15-sector, 80-track
|
||||||
SIZE: 0x1C, // 4 bytes (typically zero for subdirectories)
|
MEDIA_1440KB: 0xF0, // 3.5-inch, 2-sided, 18-sector, 80-track
|
||||||
LENGTH: 0x20 // 32 bytes total
|
MEDIA_2880KB: 0xF0 // 3.5-inch, 2-sided, 36-sector, 80-track
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cluster constants for 12-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs)
|
||||||
|
*/
|
||||||
|
DiskAPI.FAT12 = {
|
||||||
|
MAX_CLUSTERS: 4084,
|
||||||
|
CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain)
|
||||||
|
CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain
|
||||||
|
CLUSNUM_MIN: 2, // smallest valid cluster number
|
||||||
|
CLUSNUM_MAX: 0xFF6, // largest valid cluster number
|
||||||
|
CLUSNUM_BAD: 0xFF7, // bad cluster; this should NEVER appear in cluster chain
|
||||||
|
CLUSNUM_EOC: 0xFF8 // end of chain (actually, anything from 0xFF8-0xFFF indicates EOC)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cluster constants for 16-bit FATs (CLUSNUM_FREE, CLUSNUM_RES and CLUSNUM_MIN are the same for all FATs)
|
||||||
|
*/
|
||||||
|
DiskAPI.FAT16 = {
|
||||||
|
MAX_CLUSTERS: 65524,
|
||||||
|
CLUSNUM_FREE: 0, // this should NEVER appear in cluster chain (except at the start of an empty chain)
|
||||||
|
CLUSNUM_RES: 1, // reserved; this should NEVER appear in cluster chain
|
||||||
|
CLUSNUM_MIN: 2, // smallest valid cluster number
|
||||||
|
CLUSNUM_MAX: 0xFFF6, // largest valid cluster number
|
||||||
|
CLUSNUM_BAD: 0xFFF7, // bad cluster; this should NEVER appear in cluster chain
|
||||||
|
CLUSNUM_EOC: 0xFFF8 // end of chain (actually, anything from 0xFFF8-0xFFFF indicates EOC)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Directory Entry offsets (and assorted constants) in FAT disk images
|
||||||
|
*
|
||||||
|
* NOTE: Versions of DOS prior to 2.0 use INVALID exclusively to mark available directory entries; any entry marked
|
||||||
|
* UNUSED will actually be considered USED. In DOS 2.0 and up, UNUSED was added to indicate that all remaining entries
|
||||||
|
* are unused, relieving it from having to initialize the rest of the sectors in the directory cluster(s). And in fact,
|
||||||
|
* you WILL encounter garbage in subsequent directory sectors if you attempt to read past an UNUSED entry.
|
||||||
|
*/
|
||||||
|
DiskAPI.DIRENT = {
|
||||||
|
NAME: 0x000, // 8 bytes
|
||||||
|
EXT: 0x008, // 3 bytes
|
||||||
|
ATTR: 0x00B, // 1 byte
|
||||||
|
MODTIME: 0x016, // 2 bytes
|
||||||
|
MODDATE: 0x018, // 2 bytes
|
||||||
|
CLUSTER: 0x01A, // 2 bytes
|
||||||
|
SIZE: 0x01C, // 4 bytes (typically zero for subdirectories)
|
||||||
|
LENGTH: 0x20, // 32 bytes total
|
||||||
|
UNUSED: 0x00, // indicates this and all subsequent directory entries are unused
|
||||||
|
INVALID: 0xE5 // indicates this directory entry is unused
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Possible values for DIRENT.ATTR
|
||||||
|
*/
|
||||||
DiskAPI.ATTR = {
|
DiskAPI.ATTR = {
|
||||||
READONLY: 0x01, // PC-DOS 2.0 and up
|
READONLY: 0x01, // PC-DOS 2.0 and up
|
||||||
HIDDEN: 0x02,
|
HIDDEN: 0x02,
|
||||||
|
|
|
||||||
5
pubs/pc/reference/microsoft/README.md
Normal file
5
pubs/pc/reference/microsoft/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
FAT (File Allocation Table) Documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
- [FAT: General Overview of On-Disk Format (v1.02)](static/fatgen102.pdf) (courtesy of [PC DOS Retro](https://sites.google.com/site/pcdosretro/))
|
||||||
|
- [FAT: General Overview of On-Disk Format (v1.03)](static/fatgen103.pdf) (courtesy of [Microsoft Corporation](http://msdn.microsoft.com/en-us/windows/hardware/gg463080))
|
||||||
Loading…
Reference in a new issue