diff --git a/modules/diskdump/lib/diskdump.js b/modules/diskdump/lib/diskdump.js index 69130048d..00f5945b3 100644 --- a/modules/diskdump/lib/diskdump.js +++ b/modules/diskdump/lib/diskdump.js @@ -2145,7 +2145,7 @@ DiskDump.prototype.convertToJSON = function() if ((bByte0 == X86.OPCODE.JMP || bByte0 == X86.OPCODE.JMPS) && cbSectorBPB == cbSector) { var nHeadsBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.HEAD_TOTAL); - var nSectorsTotalBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.SECTOR_TOTAL); + var nSectorsTotalBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TOTAL_SECS); var nSectorsPerTrackBPB = this.bufDisk.readUInt16LE(offBootSector + DiskAPI.BPB.TRACK_SECS); if (nSectorsPerTrackBPB && nHeadsBPB) { diff --git a/modules/pcjs/lib/disk.js b/modules/pcjs/lib/disk.js index 2528a498a..ee25ba7c2 100644 --- a/modules/pcjs/lib/disk.js +++ b/modules/pcjs/lib/disk.js @@ -852,6 +852,7 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath) this.aDiskData = aDiskData; this.dwChecksum = dwChecksum; disk = this; + this.buildSectorMap(); } } catch (e) { Component.error("Disk image error: " + e.message); @@ -864,6 +865,292 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath) } }; +/** + * buildSectorMap() + * + * This function builds a mapping object (aSectorMap) that maps sectors to files. Used for BACKTRACK support. + * + * Note that while most of the methods in this module use CHS-style parameters, because our primary clients are disk + * controllers that deal exclusively with cylinder/head/sector values, here we use 0-based logical sector numbers for + * the sector map. This is also known as logical block addressing or LBA. + * + * @this {Disk} + */ +Disk.prototype.buildSectorMap = function() +{ + if (BACKTRACK) { + var dir = {}; + this.aFileTable = []; + this.aSectorMap = {}; + var sectorBoot = this.getSector(0); + dir.cbSector = this.getSectorData(sectorBoot, DiskAPI.BPB.SECTOR_BYTES, 2); + if (dir.cbSector != 512) { + /* + * 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; + } + 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.FAT_TOTAL, 1); + dir.nEntries = this.getSectorData(sectorBoot, DiskAPI.BPB.ROOT_ENTRIES, 2); + dir.lbaData = dir.lbaRoot + (((dir.nEntries * DiskAPI.DIR.LENGTH) / dir.cbSector) | 0); + dir.nClusterSecs = this.getSectorData(sectorBoot, DiskAPI.BPB.CLUSTER_SECS, 1); + 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 a 12-bit FAT, cluster numbers 0xFF0-0xFF6 are reserved, 0xFF7 indicates a bad cluster, and 0xFF8-0xFFF + * indicate the last cluster in a chain. Since 12 bits yields 4096 possible values, and since 10 of the values + * (0, 1, and 0xFF8-0xFFF) cannot be used to refer to an actual cluster, that leaves a maximum of 4086 clusters + * for a 12-bit FAT; a volume with more than 4086 clusters must be using a 16-bit FAT. + * + * TODO: More research is required to ensure we're setting iClusterLimit appropriately, because 0xFF0 seems the + * 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 + * is 4086 clusters, I'm not sure why that's true, unless there are volumes that actually use clusters >= 0xFF0. + * + * Also, perhaps it would be worth setting the limit to match the capacity of the volume, instead of the theoretical + * limit, as that might help us catch more problems. + */ + dir.nFATBits = (dir.nClusters <= 4086? 12 : 16); + dir.iClusterLimit = (dir.nFATBits == 12? 0xFF0 : 0xFFF0); + this.assert(!((dir.lbaTotal - dir.lbaData) % dir.nClusterSecs)); + this.assert(!((dir.nEntries * DiskAPI.DIR.LENGTH) % dir.cbSector)); + var aSectors = []; + for (var i = dir.lbaRoot; i < dir.lbaData; i++) aSectors.push(i); + this.getDir(dir, "", aSectors); + } +}; + +/** + * getDir(dir, sDir, aSectors) + * + * @this {Disk} + * @param {Object} dir + * @param {string} sDir + * @param {Array.} aSectors + */ +Disk.prototype.getDir = function(dir, sDir, aSectors) +{ + var iStart = this.aFileTable.length; + var nEntriesPerSector = (dir.cbSector / DiskAPI.DIR.LENGTH) | 0; + + for (var iSector = 0; iSector < aSectors.length; iSector++) { + var lba = aSectors[iSector]; + for (var iEntry = 0; iEntry < nEntriesPerSector; iEntry++) { + if (!this.getDirEntry(dir, lba, iEntry)) continue; + if (DEBUG) { + this.controller.println('"' + dir.sName + '" size=' + dir.cbSize + ' cluster=' + dir.iCluster + ' sectors=' + JSON.stringify(dir.aSectors)); + 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}); + } + } + + var iEnd = this.aFileTable.length; + + for (var i = iStart; i < iEnd; i++) { + var file = this.aFileTable[i]; + if (file.bAttr & DiskAPI.ATTR.SUBDIR && file.aSectors.length) { + this.getDir(dir, sDir + "\\" + file.sName, file.aSectors[0]); + } + } +}; + +/** + * getDirEntry(dir, lba, i) + * + * This sets the following properties on the 'dir' object: + * + * sName + * bAttr + * cbSize + * iCluster + * aSectors (ie, array of lba values) + * + * 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. + * + * This function also caches the following properties in the 'dir' object: + * + * lbaDir (of the last directory sector read, if any) + * sectorDir (of the last directory sector read, if any) + * + * Also, the caller must also set the following 'dir' helper properties, so that clusters + * can be located and converted to sectors (see convertClusterToSectors): + * + * lbaFAT + * lbaData + * cbSector + * iClusterLimit + * nClusterSecs + * nFATBits + * + * @this {Disk} + * @param {Object} dir (to be filled in) + * @param {number} lba (a sector of the directory) + * @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) + */ +Disk.prototype.getDirEntry = function(dir, lba, i) +{ + if (!dir.sectorDir || !dir.lbaDir || dir.lbaDir != lba) { + dir.lbaDir = lba; + dir.sectorDir = this.getSector(dir.lbaDir); + } + if (dir.sectorDir) { + var off = i * DiskAPI.DIR.LENGTH; + var b = this.getSectorData(dir.sectorDir, off, 1); + if (!b || b == 0xe5) return false; + dir.sName = str.trim(this.getSectorString(dir.sectorDir, off + DiskAPI.DIR.NAME, 8)); + var s = str.trim(this.getSectorString(dir.sectorDir, off + DiskAPI.DIR.EXT, 3)); + if (s.length) dir.sName += '.' + s; + dir.bAttr = this.getSectorData(dir.sectorDir, off + DiskAPI.DIR.ATTR, 1); + dir.cbSize = this.getSectorData(dir.sectorDir, off + DiskAPI.DIR.SIZE, 2); + dir.iCluster = this.getSectorData(dir.sectorDir, off + DiskAPI.DIR.CLUSTER, 2); + this.convertClusterToSectors(dir); + return true; + } + return false; +}; + +/** + * convertClusterToSectors(dir) + * + * @this {Disk} + * @param {Object} dir + */ +Disk.prototype.convertClusterToSectors = function(dir) +{ + dir.aSectors = []; + var iCluster = dir.iCluster; + if (iCluster) { + do { + this.assert(iCluster >= 2); + var lba = dir.lbaData + ((iCluster - 2) * dir.nClusterSecs); + for (var i = 0; i < dir.nClusterSecs; i++) { + dir.aSectors.push(lba++); + } + iCluster = this.getClusterEntry(dir, iCluster, 0) | this.getClusterEntry(dir, iCluster, 1); + } while (iCluster < dir.iClusterLimit); + } +}; + +/** + * getClusterEntry(dir, iCluster, iByte) + * + * @this {Disk} + * @param {Object} dir + * @param {number} iCluster + * @param {number} iByte (0 for low byte of cluster entry, 1 for high byte) + * @return {number} + */ +Disk.prototype.getClusterEntry = function(dir, iCluster, iByte) +{ + var w = 0; + var cbitsSector = dir.cbSector * 8; + var offBits = dir.nFATBits * iCluster; + var iSec = (offBits / cbitsSector) | 0; + if (!dir.sectorFATCache || !dir.lbaFATCache || dir.lbaFATCache != dir.lbaFAT + iSec) { + dir.lbaFATCache = dir.lbaFAT + iSec; + dir.sectorFATCache = this.getSector(dir.lbaFATCache); + } + if (dir.sectorFATCache) { + offBits = (offBits % cbitsSector) | 0; + var off = (offBits >> 3) + iByte; + w = this.getSectorData(dir.sectorFATCache, off, 1); + if (!iByte) { + if (offBits & 0x7) w >>= 4; + } else { + if (dir.nFATBits == 16) { + w <<= 8; + } else { + if (offBits & 0x7) { + w <<= 4; + } else { + w = (w & 0xf) << 8; + } + } + } + } + return w; +}; + +/** + * getSector(lba) + * + * @this {Disk} + * @param {number} lba (logical block address) + * @return {Object} sector + */ +Disk.prototype.getSector = function(lba) +{ + var nSectorsPerCylinder = this.nHeads * this.nSectors; + var iCylinder = (lba / nSectorsPerCylinder) | 0; + this.assert(iCylinder < this.nCylinders); + var nSectorsRemaining = (lba % nSectorsPerCylinder); + 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 + */ + var sector = this.seek(iCylinder, iHead, iSector + 1); + if (DEBUG) this.controller.println("logical sector #" + lba + ":\n" + this.dumpSector(sector)); + return sector; +}; + +/** + * getSectorData(sector, off, len) + * + * NOTE: Yes, this function is not the most efficient way to read a byte/word/dword value from within + * a sector, but given the different states a sector may be in, it's certainly the simplest and safest, + * and it's not clear that we need to be superfast anyway. + * + * @this {Disk} + * @param {Object} sector + * @param {number} off (byte offset) + * @param {number} len (1 to 4 bytes) + * @return {number} + */ +Disk.prototype.getSectorData = function(sector, off, len) +{ + var dw = 0; + var nShift = 0; + this.assert(len > 0 && len <= 4); + while (len--) { + this.assert(off < sector['length']); + var b = this.read(sector, off++); + this.assert(b >= 0); + if (b < 0) break; + dw |= (b << nShift); + nShift += 8; + } + return dw; +}; + +/** + * getSectorString(sector, off, len) + * + * @this {Disk} + * @param {Object} sector + * @param {number} off (byte offset) + * @param {number} len (use -1 to read a null-terminated string) + * @return {string} + */ +Disk.prototype.getSectorString = function(sector, off, len) +{ + var s = ""; + while (len--) { + var b = this.read(sector, off++); + if (b <= 0) break; + s += String.fromCharCode(b); + } + return s; +}; + /** * initSector(sector, iCylinder, iHead, iSector, cbSector, dwPattern) * @@ -901,60 +1188,6 @@ Disk.prototype.initSector = function(sector, iCylinder, iHead, iSector, cbSector return sector; }; -/** - * onLoadParseSectors(sURLName, sURLData, nErrorCode, sectorInfo) - * - * @param {string} sURLName - * @param {string} sURLData - * @param {number} nErrorCode - * @param {Array} sectorInfo - */ -Disk.prototype.onLoadParseSectors = function(sURLName, sURLData, nErrorCode, sectorInfo) -{ - var fAsync = false; - 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.onLoadParseSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + ")"); - } - - var abData = JSON.parse(sURLData); - var offData = 0; - while (nSectors--) { - /* - * We call seek with fWrite == true to prevent seek() from triggering another call - * to readRemoteSectors() and endlessly recursing. That also forces seek() to: - * - * 1) zero the sector's 'pattern' - * 2) disable warning about reading an uninitialized sector - * - * We KNOW this is an uninitialized sector, because we're about to initialize it. - */ - var sector = this.seek(iCylinder, iHead, iSector, true); - if (!sector) { - if (DEBUG && this.messageEnabled()) { - this.messagePrint("Disk.onLoadParseSectors(): seek(" + iCylinder + "," + iHead + "," + iSector + ") failed"); - } - break; - } - this.fill(sector, abData, offData); - offData += sector['length']; - /* - * We happen to know that when seek() calls readRemoteSectors(), it limits the number of sectors - * to the current track, so the only variable we need to advance is iSector. - */ - iSector++; - } - } - var done = sectorInfo[5]; - if (done) done(nErrorCode, fAsync); -}; - /** * connectRemoteDisk(sDiskPath) * @@ -978,16 +1211,17 @@ Disk.prototype.connectRemoteDisk = function(sDiskPath) }; /** - * readRemoteSectors(iCylinder, iHead, iSector, nSectors, done) + * readRemoteSectors(iCylinder, iHead, iSector, nSectors, fAsync, done) * * @param {number} iCylinder * @param {number} iHead * @param {number} iSector * @param {number} cbSector * @param {number} nSectors + * @param {boolean} fAsync * @param {function(number,boolean)} [done] */ -Disk.prototype.readRemoteSectors = function(iCylinder, iHead, iSector, cbSector, nSectors, done) +Disk.prototype.readRemoteSectors = function(iCylinder, iHead, iSector, cbSector, nSectors, fAsync, done) { if (DEBUG && this.messageEnabled()) { this.messagePrint("Disk.readRemoteSectors(" + iCylinder + ":" + iHead + ":" + iSector + ":" + nSectors + "," + cbSector + ")"); @@ -1001,12 +1235,66 @@ Disk.prototype.readRemoteSectors = function(iCylinder, iHead, iSector, cbSector, sParms += '&' + DiskAPI.QUERY.MACHINE + '=' + this.controller.getMachineID(); sParms += '&' + DiskAPI.QUERY.USER + '=' + this.controller.getUserID(); var sDiskURL = web.getHost() + DiskAPI.ENDPOINT + '?' + sParms; - web.loadResource(sDiskURL, true, null, this, this.onLoadParseSectors, [iCylinder, iHead, iSector, nSectors, true, done]); + web.loadResource(sDiskURL, fAsync, null, this, this.doneReadRemoteSectors, [iCylinder, iHead, iSector, nSectors, fAsync, done]); return; } if (done) done(-1, false); }; +/** + * doneReadRemoteSectors(sURLName, sURLData, nErrorCode, sectorInfo) + * + * @param {string} sURLName + * @param {string} sURLData + * @param {number} nErrorCode + * @param {Array} sectorInfo + */ +Disk.prototype.doneReadRemoteSectors = function(sURLName, sURLData, nErrorCode, sectorInfo) +{ + var fAsync = false; + 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 offData = 0; + while (nSectors--) { + /* + * We call seek with fWrite == true to prevent seek() from triggering another call + * to readRemoteSectors() and endlessly recursing. That also forces seek() to: + * + * 1) zero the sector's 'pattern' + * 2) disable warning about reading an uninitialized sector + * + * We KNOW this is an uninitialized sector, because we're about to initialize it. + */ + var sector = this.seek(iCylinder, iHead, iSector, true); + if (!sector) { + if (DEBUG && this.messageEnabled()) { + this.messagePrint("Disk.doneReadRemoteSectors(): seek(" + iCylinder + "," + iHead + "," + iSector + ") failed"); + } + break; + } + this.fill(sector, abData, offData); + offData += sector['length']; + /* + * We happen to know that when seek() calls readRemoteSectors(), it limits the number of sectors + * to the current track, so the only variable we need to advance is iSector. + */ + iSector++; + } + } + var done = sectorInfo[5]; + if (done) done(nErrorCode, fAsync); +}; + /** * writeRemoteSectors(iCylinder, iHead, iSector, nSectors, abSectors, fAsync) * @@ -1044,11 +1332,48 @@ Disk.prototype.writeRemoteSectors = function(iCylinder, iHead, iSector, nSectors data[DiskAPI.QUERY.USER] = this.controller.getUserID(); data[DiskAPI.QUERY.DATA] = JSON.stringify(abSectors); var sDiskURL = web.getHost() + DiskAPI.ENDPOINT; - return web.loadResource(sDiskURL, fAsync, data, this, this.onWriteCleanSectors, [iCylinder, iHead, iSector, nSectors, fAsync]); + return web.loadResource(sDiskURL, fAsync, data, this, this.doneWriteRemoteSectors, [iCylinder, iHead, iSector, nSectors, fAsync]); } return false; }; +/** + * doneWriteRemoteSectors(sURLName, sURLData, nErrorCode, sectorInfo) + * + * @param {string} sURLName + * @param {string} sURLData + * @param {number} nErrorCode + * @param {Array} sectorInfo + */ +Disk.prototype.doneWriteRemoteSectors = function(sURLName, sURLData, nErrorCode, sectorInfo) +{ + var iCylinder = sectorInfo[0]; + var iHead = sectorInfo[1]; + var iSector = sectorInfo[2]; + var nSectors = sectorInfo[3]; + var fAsync = sectorInfo[4]; + this.fWriteInProgress = false; + + if (iCylinder >= 0 && iCylinder < this.aDiskData.length && iHead >= 0 && iHead < this.aDiskData[iCylinder].length) { + for (var i = iSector - 1; nSectors-- > 0 && i >= 0 && i < this.aDiskData[iCylinder][iHead].length; i++) { + var sector = this.aDiskData[iCylinder][iHead][i]; + + if (DEBUG && this.messageEnabled()) { + this.messagePrint("Disk.doneWriteRemoteSectors(" + iCylinder + ":" + iHead + ":" + sector['sector'] + ")"); + } + + if (!nErrorCode) { + if (!sector.fDirty) { + sector.iModify = sector.cModify = 0; + } + } else { + this.queueDirtySector(sector, false); + } + } + } + if (fAsync) this.updateWriteTimer(); +}; + /** * disconnectRemoteDisk() * @@ -1185,43 +1510,6 @@ Disk.prototype.findDirtySectors = function(fAsync) return false; }; -/** - * onWriteCleanSectors(sURLName, sURLData, nErrorCode, sectorInfo) - * - * @param {string} sURLName - * @param {string} sURLData - * @param {number} nErrorCode - * @param {Array} sectorInfo - */ -Disk.prototype.onWriteCleanSectors = function(sURLName, sURLData, nErrorCode, sectorInfo) -{ - var iCylinder = sectorInfo[0]; - var iHead = sectorInfo[1]; - var iSector = sectorInfo[2]; - var nSectors = sectorInfo[3]; - var fAsync = sectorInfo[4]; - this.fWriteInProgress = false; - - if (iCylinder >= 0 && iCylinder < this.aDiskData.length && iHead >= 0 && iHead < this.aDiskData[iCylinder].length) { - for (var i = iSector - 1; nSectors-- > 0 && i >= 0 && i < this.aDiskData[iCylinder][iHead].length; i++) { - var sector = this.aDiskData[iCylinder][iHead][i]; - - if (DEBUG && this.messageEnabled()) { - this.messagePrint("Disk.onWriteCleanSectors(" + iCylinder + ":" + iHead + ":" + sector['sector'] + ")"); - } - - if (!nErrorCode) { - if (!sector.fDirty) { - sector.iModify = sector.cModify = 0; - } - } else { - this.queueDirtySector(sector, false); - } - } - } - if (fAsync) this.updateWriteTimer(); -}; - /** * info() * @@ -1289,7 +1577,7 @@ Disk.prototype.seek = function(iCylinder, iHead, iSector, fWrite, done) * sector, then we shouldn't need to read it from the server; assume a zero pattern and return. */ sector['pattern'] = 0; - } else if (done) { + } else { var nSectors = 1; /* * We know we need to read at least 1 sector, but let's count the number of trailing sectors @@ -1298,16 +1586,13 @@ Disk.prototype.seek = function(iCylinder, iHead, iSector, fWrite, done) while (++i < track.length) { if (track[i]['pattern'] === null) nSectors++; } - this.readRemoteSectors(iCylinder, iHead, iSector, drive.cbSector, nSectors, function onReadRemoteComplete(err, fAsync) { + this.readRemoteSectors(iCylinder, iHead, iSector, drive.cbSector, nSectors, done != null, function onReadRemoteComplete(err, fAsync) { if (err) sector = null; - // noinspection JSReferencingMutableVariableFromClosure - done(sector, fAsync); + if (done) { //noinspection JSReferencingMutableVariableFromClosure + done(sector, fAsync); + } }); - return null; - } else { - if (DEBUG && this.messageEnabled()) { - this.messagePrint('Disk.seek("' + this.sDiskName + '"): uninitialized sector ' + iCylinder + ':' + iHead + ':' + iSector); - } + return done? null : sector; } } break; @@ -1383,16 +1668,16 @@ Disk.prototype.toBytes = function(sector) Disk.prototype.read = function(sector, ibSector, fCompare) { var b = -1; - - if (DEBUG && !ibSector && !fCompare && this.messageEnabled()) { - this.messagePrint("Disk.read(" + this.controller.id + ":" + this.drive.iDrive + "," + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + ")"); - } - - if (ibSector < sector['length']) { - var adw = sector['data']; - var idw = ibSector >> 2; - var dw = (idw < adw.length ? adw[idw] : sector['pattern']); - b = ((dw >> ((ibSector & 0x3) << 3)) & 0xff); + if (sector) { + if (DEBUG && !ibSector && !fCompare && this.messageEnabled()) { + this.messagePrint("Disk.read(" + this.controller.id + ":" + this.drive.iDrive + "," + sector.iCylinder + ":" + sector.iHead + ":" + sector['sector'] + ")"); + } + if (ibSector < sector['length']) { + var adw = sector['data']; + var idw = ibSector >> 2; + var dw = (idw < adw.length ? adw[idw] : sector['pattern']); + b = ((dw >> ((ibSector & 0x3) << 3)) & 0xff); + } } return b; }; @@ -1661,9 +1946,8 @@ Disk.prototype.toJSON = function() */ Disk.prototype.dumpSector = function(sector) { - var sDump; - if (DEBUG) { - sDump = ""; + var sDump = ""; + if (DEBUG && sector) { var sBytes = "", sChars = ""; var cbSector = sector['length']; var cdwData = sector['data'].length; @@ -1683,7 +1967,7 @@ Disk.prototype.dumpSector = function(sector) sBytes += str.toHexByte(b) + (i % 16 == 7? "-" : " "); sChars += (b >= 32 && b < 128? String.fromCharCode(b) : "."); } - if (sBytes) sDump += sBytes + ' ' + sChars + '\n'; + if (sBytes) sDump += sBytes + ' ' + sChars; } return sDump; }; diff --git a/modules/shared/lib/diskapi.js b/modules/shared/lib/diskapi.js index 14af760d4..99cab7fdd 100644 --- a/modules/shared/lib/diskapi.js +++ b/modules/shared/lib/diskapi.js @@ -97,13 +97,36 @@ DiskAPI.BPB = { 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); if zero, refer to LARGE_SECS + TOTAL_SECS: 0x13, // 2 bytes: number of sectors (eg, 0x140 or 320); if zero, refer to LARGE_SECS 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 + LARGE_SECS: 0x20 // 4 bytes: number of sectors if TOTAL_SECS is zero +}; + +/* + * Directory Entry offsets in FAT-based disk images + */ +DiskAPI.DIR = { + NAME: 0x00, // 8 bytes + EXT: 0x08, // 3 bytes + ATTR: 0x0B, // 1 byte + MODTIME: 0x16, // 2 bytes + MODDATE: 0x18, // 2 bytes + CLUSTER: 0x1A, // 2 bytes + SIZE: 0x1C, // 4 bytes (typically zero for subdirectories) + LENGTH: 0x20 // 32 bytes total +}; + +DiskAPI.ATTR = { + READONLY: 0x01, // PC-DOS 2.0 and up + HIDDEN: 0x02, + SYSTEM: 0x04, + LABEL: 0x08, // PC-DOS 2.0 and up + SUBDIR: 0x10, // PC-DOS 2.0 and up + ARCHIVE: 0x20 // PC-DOS 2.0 and up }; if (typeof module !== 'undefined') module.exports = DiskAPI;