/** * @fileoverview Disk APIs, as defined by httpapi.js and consumed by disk.js * @author Jeff Parsons * @version 1.0 * Created 2014-May-08 * * Copyright © 2012-2016 Jeff Parsons * * This file is part of PCjs, a computer emulation software project at . * * PCjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCjs. If not, * see . * * You are required to include the above copyright notice in every source code file of every * copy or modified version of this work, and to display that copyright notice on every screen * that loads or runs any version of this software (see COPYRIGHT in /modules/shared/lib/defines.js). * * Some PCjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of PCjs * for purposes of the GNU General Public License, and the author does not claim any copyright * as to their contents. */ "use strict"; /* * Our "DiskIO API" looks like: * * http://www.pcjs.org/api/v1/disk?action=open&volume=*10mb.img&mode=demandrw&chs=c:h:s&machine=xxx&user=yyy */ var DiskAPI = { ENDPOINT: "/api/v1/disk", QUERY: { ACTION: "action", // value is one of DiskAPI.ACTION.* VOLUME: "volume", // value is path of a disk image MODE: "mode", // value is one of DiskAPI.MODE.* CHS: "chs", // value is cylinders:heads:sectors:bytes ADDR: "addr", // value is cylinder:head:sector:count MACHINE: "machine", // value is machine token USER: "user", // value is user ID DATA: "data" // value is data to be written }, ACTION: { OPEN: "open", READ: "read", WRITE: "write", CLOSE: "close" }, MODE: { LOCAL: "local", // this mode implies no API (at best, localStorage backing only) PRELOAD: "preload", // this mode implies use of the DumpAPI DEMANDRW: "demandrw", DEMANDRO: "demandro" }, FAIL: { BADACTION: "invalid action", BADUSER: "invalid user", BADVOL: "invalid volume", OPENVOL: "unable to open volume", CREATEVOL: "unable to create volume", WRITEVOL: "unable to write volume", REVOKED: "access revoked" } }; /* * 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) /* * The following are common early hard drive sizes, which we explicitly map to CHS values, since the BPB can mislead us when attempting to calculate total cylinders */ 21368320:[615,4,17] // PC AT 20Mb hard drive (type 2) }; 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 }; /* * 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 = { SECTOR_BYTES: 0x00B, // 2 bytes: bytes per sector (eg, 0x200 or 512) CLUSTER_SECS: 0x00D, // 1 byte: sectors per cluster (eg, 1) RESERVED_SECS: 0x00E, // 2 bytes: reserved sectors; ie, # sectors preceding the first FAT--usually just the boot sector (eg, 1) TOTAL_FATS: 0x010, // 1 byte: FAT copies (eg, 2) ROOT_DIRENTS: 0x011, // 2 bytes: root directory entries (eg, 0x40 or 64) 0x40 * 0x20 = 0x800 (1 sector is 0x200 bytes, total of 4 sectors) TOTAL_SECS: 0x013, // 2 bytes: number of sectors (eg, 0x140 or 320); if zero, refer to LARGE_SECS MEDIA_TYPE: 0x015, // 1 byte: media type (see DiskAPI.FAT.MEDIA_*) FAT_SECS: 0x016, // 2 bytes: sectors per FAT (eg, 1) TRACK_SECS: 0x018, // 2 bytes: sectors per track (eg, 8) TOTAL_HEADS: 0x01A, // 2 bytes: number of heads (eg, 1) HIDDEN_SECS: 0x01C, // 4 bytes: number of hidden sectors (always 0 for non-partitioned media) LARGE_SECS: 0x020 // 4 bytes: number of sectors if TOTAL_SECS is zero }; /* * Media descriptor bytes for DOS-compatible FAT-formatted disks (stored in the first byte of the FAT) */ DiskAPI.FAT = { MEDIA_160KB: 0xFE, // 5.25-inch, 1-sided, 8-sector, 40-track MEDIA_180KB: 0xFC, // 5.25-inch, 1-sided, 9-sector, 40-track MEDIA_320KB: 0xFF, // 5.25-inch, 2-sided, 8-sector, 40-track MEDIA_360KB: 0xFD, // 5.25-inch, 2-sided, 9-sector, 40-track MEDIA_720KB: 0xF9, // 3.5-inch, 2-sided, 9-sector, 80-track MEDIA_1200KB: 0xF9, // 3.5-inch, 2-sided, 15-sector, 80-track MEDIA_1440KB: 0xF0, // 3.5-inch, 2-sided, 18-sector, 80-track 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 = { 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 (NODE) module.exports = DiskAPI;