#include "woz.h" #include #include "crc32.h" #include "nibutil.h" #include "version.h" // Block number we start packing data bits after (Woz 2.0 images) #define STARTBLOCK 3 #if defined(AIIE) || defined(TEENSYDUINO) #define LAZYFD #include "fscompat.h" #endif #ifdef TEENSYDUINO // This junk is for my AiiE project. I need to abstract it out better. #include "iocompat.h" #define SKIPCHECKSUM #define malloc extmem_malloc #define free extmem_free #define calloc extmem_calloc #define realloc extmem_realloc #endif #define PREP_SECTION(fd, t) { \ uint32_t type = t; \ if (!write32(fd, type)) \ return false; \ if (!write32(fd, 0)) \ return false; \ curpos = lseek(fd, 0, SEEK_CUR); \ } #define END_SECTION(fd) { \ long endpos = lseek(fd, 0, SEEK_CUR); \ lseek(fd, curpos-4, SEEK_SET); \ uint32_t chunksize = endpos - curpos; \ if (!write32(fd, chunksize)) \ return false; \ lseek(fd, endpos, SEEK_SET); \ } Woz::Woz(bool verbose, uint8_t dumpflags) { fd = -1; trackPointer = 0; trackBitIdx = 0x80; trackBitCounter = 0; trackByteFromDataTrack = 255; trackLoopCounter = 0; imageType = T_AUTO; metaData = NULL; this->verbose = verbose; this->dumpflags = dumpflags; memset(&quarterTrackMap, 255, sizeof(quarterTrackMap)); memset(&di, 0, sizeof(diskInfo)); memset(&tracks, 0, sizeof(tracks)); randPtr = 0; } Woz::~Woz() { if (fd != -1) { close(fd); fd = -1; } for (int i=0; i<160; i++) { if (tracks[i].trackData) { free(tracks[i].trackData); tracks[i].trackData = NULL; } } if (metaData) { free(metaData); metaData = NULL; } } // external interface for a disk subsystem to write a bit bool Woz::writeNextWozBit(uint8_t datatrack, uint8_t bit) { if (datatrack == 0xFF) { printf("ERROR: tried to write bit on half-track; not implemented\n"); return true; } if (!tracks[datatrack].trackData) { fprintf(stderr, "ERROR: tried to writeNextWozBit to a data track that's not loaded, and we can't possibly tell which QT that should be\n"); return false; } if (trackByteFromDataTrack != datatrack) { // FIXME what if trackpointer is out of bounds for this track trackByte = tracks[datatrack].trackData[trackPointer]; trackByteFromDataTrack = datatrack; } if (bit) trackByte |= trackBitIdx; else trackByte &= ~trackBitIdx; tracks[datatrack].trackData[trackPointer] = trackByte; advanceBitStream(datatrack); tracks[datatrack].dirty = true; return true; } // external interface for a disk interface to write a byte bool Woz::writeNextWozByte(uint8_t datatrack, uint8_t b) { if (datatrack == 0xFF) { // Not on a track, so pretend to write but throw it away. FIXME: // probably want to create a new Woz track entry here. fprintf(stderr, "ERROR: tried to write to a half track; not implemented\n"); return true; } if (!tracks[datatrack].trackData) { fprintf(stderr, "ERROR: tried to write to a track that's not loaded, and it's not possible to tell what QT was meant\n"); return false; } // We could be byte-aligned, but it's not guaranteed, so this // handles it bitwise. for (uint8_t i=0; i<8; i++) { writeNextWozBit(datatrack, b & (1 << (7-i)) ? 1 : 0); } return true; } uint8_t Woz::getNextWozBit(uint8_t datatrack) { if (datatrack >= 160) { if (datatrack != 255) { fprintf(stderr, "datatrack %d out of range\n", datatrack); exit(1); } return 0; } if (!tracks[datatrack].trackData) { fprintf(stderr, "ERROR: getNextWozBit was called without the track being cached, and it can't possibly know which QT to load it from\n"); return 0; } if (trackByteFromDataTrack != datatrack) { // FIXME what if trackPointer is out of bounds for this track trackByte = tracks[datatrack].trackData[trackPointer]; trackByteFromDataTrack = datatrack; } // It's assumed that trackByte is set properly when we get here. It // should be set when we load image or change tracks, and it's // changed again when we advanceBitStream. uint8_t ret = (trackByte & trackBitIdx) ? 1 : 0; advanceBitStream(datatrack); return ret; } void Woz::advanceBitStream(uint8_t datatrack) { trackBitCounter++; trackBitIdx >>= 1; if (!trackBitIdx) { trackBitIdx = 0x80; trackPointer++; // FIXME this is kinda redundant since we're checking // trackBitCounter after, but we want to not load from out of // bounds here, so unless we always set trackByte after the bit // range check below I'm not sure we can get rid of this one if ((di.version == 2 && trackPointer < tracks[datatrack].blockCount*512) || (di.version == 1 && trackPointer < NIBTRACKSIZE) ) { trackByte = tracks[datatrack].trackData[trackPointer]; trackByteFromDataTrack = datatrack; } } // This could have " || trackPointer >= // tracks[datatrack].bitCount/8" but it should be totally redundant if (trackBitCounter >= tracks[datatrack].bitCount) { trackPointer = 0; trackBitIdx = 0x80; trackBitCounter = 0; trackLoopCounter++; trackByte = tracks[datatrack].trackData[trackPointer]; trackByteFromDataTrack = datatrack; } } uint8_t Woz::fakeBit() { // 30% should be 1s, but I'm not biasing the data here, so this is // more like 50% 1s. if (randPtr == 0) { randPtr = 0x80; randData = (uint8_t) ((float)256 * rand() / (RAND_MAX + 1.0)); } uint8_t ret = (randData & randPtr) ? 1 : 0; randPtr >>= 1; return ret; } uint8_t Woz::nextDiskBit(uint8_t datatrack) { if (!tracks[datatrack].trackData) { fprintf(stderr, "ERROR: nextDiskBit was called without the track being cached, and it can't possibly know which QT to load it from\n"); return 0; } /* FIXME: this needs evaluation. We want the fake bit work, but can't do it at the expense of being 1 bit behind -- b/c that would mean switching tracks drops a bit; and seek-and-write would leave an excess bit in place too... static uint8_t head_window = 0; head_window <<= 1; head_window |= getNextWozBit(datatrack); if ((head_window & 0x0f) != 0x00) { return (head_window & 0x02) >> 1; } else { return fakeBit(); }*/ // Until the above is figured out, we're gonna just return what's on the datastream return getNextWozBit(datatrack); } uint8_t Woz::nextDiskByte(uint8_t datatrack) { if (!tracks[datatrack].trackData) { fprintf(stderr, "ERROR: nextDiskByte was called without the track being cached, and it can't possibly know which QT to load it from\n"); return 0; } uint8_t d = 0; while ((d & 0x80) == 0) { d <<= 1; d |= nextDiskBit(datatrack); } return d; } static bool write8(int fd, uint8_t v) { if (write(fd, &v, 1) != 1) return false; return true; } static bool write16(int fd, uint16_t v) { if (!write8(fd, v & 0xFF)) return false; v >>= 8; if (!write8(fd, v & 0xFF)) return false; return true; } static bool write32(int fd, uint32_t v) { for (int i=0; i<4; i++) { if (!write8(fd, v&0xFF)) return false; v >>= 8; } return true; } static bool read8(int fd, uint8_t *toWhere) { uint8_t r; if (read(fd, &r, 1) != 1) return false; *toWhere = r; return true; } static bool read16(int fd, uint16_t *toWhere) { uint16_t ret = 0; for (int i=0; i<2; i++) { uint8_t r; if (!read8(fd, &r)) { return false; } ret >>= 8; ret |= (r<<8); } *toWhere = ret; return true; } static bool read32(int fd, uint32_t *toWhere) { uint32_t ret = 0; for (int i=0; i<4; i++) { uint8_t r; if (!read8(fd, &r)) { return false; } ret >>= 8; ret |= (r<<24); } *toWhere = ret; return true; } bool Woz::writeFile(const char *filename, uint8_t forceType) { if (forceType == T_AUTO) { // Try to determine type from the file extension const char *p = strrchr(filename, '.'); if (!p) { fprintf(stderr, "Unable to determine file type of '%s'\n", filename); return false; } if (strcasecmp(p, ".woz") == 0) { forceType = T_WOZ; } else if (strcasecmp(p, ".dsk") == 0 || strcasecmp(p, ".do") == 0) { forceType = T_DSK; } else if (strcasecmp(p, ".po") == 0) { forceType = T_PO; } else if (strcasecmp(p, ".nib") == 0) { forceType = T_NIB; } else { fprintf(stderr, "Unable to determine file type of '%s'\n", filename); return false; } } switch (forceType) { case T_WOZ: return writeWozFile(filename, forceType); case T_DSK: case T_PO: return writeDskFile(filename, forceType); case T_NIB: return writeNibFile(filename); default: fprintf(stderr, "Unknown disk type; unable to write\n"); return false; } } bool Woz::writeWozFile(const char *filename, uint8_t subtype) { int fdout = -1; fdout = open(filename, O_TRUNC|O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); if (fdout == -1) { perror("ERROR: Unable to open output file"); return false; } bool retval = writeWozFile(fdout, subtype); close(fdout); return retval; } bool Woz::writeWozFile(int fdout, uint8_t subtype) { int version = 2; // FIXME figure out from subtype bool retval = false; uint32_t tmp32; // scratch 32-bit value off_t crcPos, endPos; off_t curpos; // used in macros to dynamically tell what size the chunks are uint32_t crcDataSize; uint8_t *crcData = NULL; if (version > 2 || !version) { fprintf(stderr, "ERROR: version must be 1 or 2\n"); goto done; } lseek(fdout, 0, SEEK_SET); // header if (version == 1) { tmp32 = 0x315A4F57; } else { tmp32 = 0x325A4F57; } if (!write32(fdout, tmp32)) { fprintf(stderr, "ERROR: failed to write\n"); goto done; } tmp32 = 0x0A0D0AFF; if (!write32(fdout, tmp32)) { fprintf(stderr, "ERROR: failed to write\n"); goto done; } // We'll come back and write the checksum later crcPos = lseek(fdout, 0, SEEK_CUR); tmp32 = 0; if (!write32(fdout, tmp32)) { fprintf(stderr, "ERROR: failed to write\n"); goto done; } PREP_SECTION(fdout, 0x4F464E49); // 'INFO' if (!writeInfoChunk(version, fdout)) { fprintf(stderr, "ERROR: failed to write INFO chunk\n"); goto done; } END_SECTION(fdout); PREP_SECTION(fdout, 0x50414D54); // 'TMAP' if (!writeTMAPChunk(version, fdout)) { fprintf(stderr, "ERROR: failed to write TMAP chunk\n"); goto done; } END_SECTION(fdout); PREP_SECTION(fdout, 0x534B5254); // 'TRKS' if (!writeTRKSChunk(version, fdout)) { fprintf(stderr, "ERROR: failed to write TRKS chunk\n"); goto done; } END_SECTION(fdout); // Write the metadata if we have any if (metaData) { PREP_SECTION(fdout, 0x4154454D); // 'META' if (write(fdout, metaData, strlen(metaData)) != strlen(metaData)) { fprintf(stderr, "ERROR: failed to write META chunk\n"); goto done; } END_SECTION(fdout); } // FIXME: missing the WRIT chunk, if it exists // Fix up the checksum. Optional; the spec says it can be 0 meaning // "don't verify" #ifndef SKIPCHECKSUM endPos = lseek(fdout, 0, SEEK_CUR); crcDataSize = endPos-crcPos-4; crcData = (uint8_t *)malloc(crcDataSize); if (!crcData) { fprintf(stderr, "ERROR: failed to malloc crc data chunk\n"); goto done; } // Read the data in for checksumming if (lseek(fdout, crcPos+4, SEEK_SET) == -1) { fprintf(stderr, "ERROR: failed to fseek to crcPos+4 (0x%llX)\n", crcPos+4); goto done; } tmp32 = read(fdout, crcData, crcDataSize); if (tmp32 != crcDataSize) { fprintf(stderr, "ERROR: failed to read in data for checksum [read %d, wanted %d]\n", tmp32, crcDataSize); goto done; } tmp32 = compute_crc_32(crcData, crcDataSize); // Write it back out lseek(fdout, crcPos, SEEK_SET); if (!write32(fdout, tmp32)) { fprintf(stderr, "ERROR: failed to write CRC\n"); goto done; } #endif for (int i=0; i<160; i++) { tracks[i].dirty = false; } retval = true; done: if (crcData) free(crcData); return retval; } bool Woz::writeDskFile(const char *filename, uint8_t subtype) { int fdout = open(filename, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); if (fdout == -1) { perror("Failed to open output file"); exit(1); } bool retval = writeDskFile(fdout, subtype); close(fdout); return retval; } bool Woz::writeDskFile(int fdout, uint8_t subtype) { if (isSynchronized()) { fprintf(stderr, "WARNING: disk image has synchronized tracks; it may not work as a DSK or NIB file.\n"); } lseek(fdout, 0, SEEK_SET); uint8_t sectorData[256*16]; for (int phystrack=0; phystrack<35; phystrack++) { if (!decodeWozTrackToDsk(phystrack, subtype, sectorData)) { fprintf(stderr, "Failed to decode track %d; aborting\n", phystrack); exit(1); } ssize_t numWritten = write(fdout, sectorData, 256*16); if (numWritten != 256*16) { perror("Failed[2] to write to track; aborting"); exit(1); } } for (int i=0; i<160; i++) { tracks[i].dirty = false; } return true; } bool Woz::writeNibFile(const char *filename) { int fdout = open(filename, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); if (fdout == -1) { perror("Failed to open output file"); exit(1); } bool retval = writeNibFile(fdout); close(fdout); return retval; } bool Woz::writeNibFile(int fdout) { if (isSynchronized()) { fprintf(stderr, "WARNING: disk image has synchronized tracks; it may not work as a DSK or NIB file.\n"); } lseek(fdout, 0, SEEK_SET); nibSector nibData[16]; for (int phystrack=0; phystrack<35; phystrack++) { if (!decodeWozTrackToNibFromDataTrack(quarterTrackMap[phystrack*4], nibData)) { fprintf(stderr, "Failed to decode track %d; aborting\n", phystrack); exit(1); } if (write(fdout, nibData, NIBTRACKSIZE) != NIBTRACKSIZE) { fprintf(stderr, "Failed[1] to write track %d; aborting\n", phystrack); exit(1); } } for (int i=0; i<160; i++) { tracks[i].dirty = false; } return true; } void Woz::_initInfo() { di.version = 2; di.diskType = 1; di.writeProtected = 0; di.synchronized = 0; di.cleaned = 0; sprintf(di.creator, "%.32s", VERSION_STRING); di.diskSides = 1; di.bootSectorFormat = 0; di.optimalBitTiming = 32; di.compatHardware = 0; di.requiredRam = 0; di.largestTrack = 13; // reset all the track data for (int i=0; i<160; i++) { memset(&tracks[i], 0, sizeof(trackInfo)); } // Construct a default quarter-track mapping for (int i=0; i<140; i++) { if ((i+1)/4 < 35) { quarterTrackMap[i] = ((i-2) % 4 == 0) ? 0xFF : ((i+1)/4); } else { quarterTrackMap[i] = 0xFF; } } } // Only used if we didn't preload a data track; the load we perform // differs based on the image type we originally read from bool Woz::loadMissingTrackFromImage(uint8_t datatrack) { // If we're going to malloc a new one, then find all the other ones // that might be malloc'd and purge them if we're // autoFlushTrackData==true (trying to limit memory use) if (autoFlushTrackData == true) { for (int i=0; i<160; i++) { // Don't flush any tracks that are dirty if (tracks[i].trackData && !tracks[i].dirty) { free(tracks[i].trackData); tracks[i].trackData = NULL; } } } // Based on the source image type, load the data track we're looking for if (imageType == T_WOZ) { // If the source was WOZ, just load the datatrack directly return readWozDataTrack(datatrack); } else if (imageType == T_PO || imageType == T_DSK) { // If the source was a DSK file, then the datatrack was mapped directly // from the physical track if (datatrack >= 35) { // There are only 35 tracks; the remainder are blank. tracks[datatrack].trackData = NULL; return true; } uint8_t phystrack = datatrack; // used for clarity of which kind of track we mean, below static uint8_t sectorData[256*16]; lseek(fd, 256*16*phystrack, SEEK_SET); if (read(fd, sectorData, 256*16) != 256*16) { fprintf(stderr, "Failed to read sector\n"); return false; } tracks[datatrack].trackData = (uint8_t *)calloc(NIBTRACKSIZE, 1); if (!tracks[datatrack].trackData) { fprintf(stderr, "Failed to malloc track data\n"); return false; } tracks[datatrack].startingBlock = STARTBLOCK + 13*phystrack; tracks[datatrack].blockCount = 13; uint32_t sizeInBits = nibblizeTrack(tracks[datatrack].trackData, sectorData, imageType, phystrack); tracks[datatrack].bitCount = sizeInBits; // ... reality. return true; } else if (imageType == T_NIB) { if (datatrack >= 35) { // There are only 35 tracks; the remainder are blank. tracks[datatrack].trackData = NULL; return true; } // If the source was a NIB file, then the datatrack is directly // mapped 1:1 to the physical track uint8_t phystrack = datatrack; // used for clarity of which kind of track we mean, below tracks[datatrack].trackData = (uint8_t *)malloc(NIBTRACKSIZE); if (!tracks[datatrack].trackData) { printf("Failed to malloc track data\n"); return false; } lseek(fd, NIBTRACKSIZE * phystrack, SEEK_SET); read(fd, tracks[datatrack].trackData, NIBTRACKSIZE); // FIXME: no error checking tracks[datatrack].startingBlock = STARTBLOCK + 13*phystrack; tracks[datatrack].blockCount = 13; tracks[datatrack].bitCount = NIBTRACKSIZE*8; return true; } printf("ERROR: don't know how we reached this point\n"); return false; } bool Woz::readDskFile(const char *filename, bool preloadTracks, uint8_t subtype) { bool retval = false; autoFlushTrackData = !preloadTracks; imageType = subtype; if (fd != -1) close(fd); fd = open(filename, O_RDWR, S_IRUSR|S_IWUSR); if (fd == -1) { perror("Unable to open input file"); goto done; } _initInfo(); // Now read in the 35 tracks of data from the DSK file and convert them to NIB if (preloadTracks) { uint8_t sectorData[256*16]; for (int phystrack=0; phystrack<35; phystrack++) { uint32_t bytesRead = read(fd, sectorData, 256*16); if (bytesRead != 256*16) { fprintf(stderr, "Failed to read DSK data; got %d bytes, wanted %d\n", bytesRead, 256); goto done; } uint8_t datatrack = quarterTrackMap[phystrack*4]; tracks[datatrack].trackData = (uint8_t *)calloc(NIBTRACKSIZE, 1); if (!tracks[datatrack].trackData) { fprintf(stderr, "Failed to malloc track data\n"); goto done; } tracks[datatrack].startingBlock = STARTBLOCK + 13*datatrack; tracks[datatrack].blockCount = 13; uint32_t sizeInBits = nibblizeTrack(tracks[datatrack].trackData, sectorData, subtype, phystrack); tracks[datatrack].bitCount = sizeInBits; // ... reality. } } retval = true; done: #ifndef LAZYFD if (preloadTracks && fd != -1) { close(fd); fd = -1; } #endif return retval; } bool Woz::readNibFile(const char *filename, bool preloadTracks) { autoFlushTrackData = !preloadTracks; imageType = T_NIB; if (fd != -1) close(fd); fd = open(filename, O_RDWR, S_IRUSR|S_IWUSR); if (fd == -1) { perror("Unable to open input file"); return false; } _initInfo(); // Now read in the 35 tracks of data from the nib file if (preloadTracks) { nibSector nibData[16]; for (int phystrack=0; phystrack<35; phystrack++) { uint32_t bytesRead = read(fd, nibData, NIBTRACKSIZE); if (bytesRead != NIBTRACKSIZE) { printf("Failed to read NIB data; got %d bytes, wanted %d\n", bytesRead, NIBTRACKSIZE); return false; } uint8_t datatrack = quarterTrackMap[phystrack * 4]; tracks[datatrack].trackData = (uint8_t *)calloc(NIBTRACKSIZE, 1); if (!tracks[datatrack].trackData) { printf("Failed to malloc track data\n"); return false; } memcpy(tracks[datatrack].trackData, nibData, NIBTRACKSIZE); tracks[datatrack].startingBlock = STARTBLOCK + 13*phystrack; tracks[datatrack].blockCount = 13; tracks[datatrack].bitCount = NIBTRACKSIZE*8; } } #ifndef LAZYFD if (preloadTracks && fd != -1) { close(fd); fd = -1; } #endif return true; } bool Woz::readWozFile(const char *filename, bool preloadTracks) { imageType = T_WOZ; autoFlushTrackData = !preloadTracks; if (fd != -1) close(fd); fd = open(filename, O_RDWR, S_IRUSR|S_IWUSR); if (fd == -1) { perror("Unable to open input file"); return false; } // Header uint32_t h; read32(fd, &h); if (h == 0x325A4F57 || h == 0x315A4F57) { if (verbose) { printf("WOZ%c disk image\n", (h & 0xFF000000)>>24); } } else { printf("Unknown disk image type; can't continue\n"); if (preloadTracks && fd != -1) close(fd); return false; } uint32_t tmp; if (!read32(fd, &tmp)) { printf("Read failure\n"); if (preloadTracks && fd != -1) close(fd); return false; } if (tmp != 0x0A0D0AFF) { printf("WOZ header failure; exiting\n"); if (preloadTracks && fd != -1) close(fd); return false; } uint32_t crc32; read32(fd, &crc32); // If CRC is set, then check it if (crc32) { // FIXME: check CRC if (verbose) { printf("Disk crc32 should be 0x%X\n", crc32); } } uint32_t fpos = 12; uint8_t haveData = 0; #define cINFO 1 #define cTMAP 2 #define cTRKS 4 while (1) { if (lseek(fd, fpos, SEEK_SET) == -1) { break; } uint32_t chunkType; if (!read32(fd, &chunkType)) { break; } uint32_t chunkDataSize; read32(fd, &chunkDataSize); if ((int32_t)chunkDataSize < 0) { printf("ERROR: data size < 0?\n"); exit(1); } bool isOk; switch (chunkType) { case 0x4F464E49: // 'INFO' if (verbose) { printf("Reading INFO chunk starting at byte 0x%llX\n", (unsigned long long)lseek(fd, 0, SEEK_CUR)); } isOk = parseInfoChunk(chunkDataSize); haveData |= cINFO; break; case 0x50414D54: // 'TMAP' if (verbose) { printf("Reading TMAP chunk starting at byte 0x%llX\n", (unsigned long long)lseek(fd, 0, SEEK_CUR)); } isOk = parseTMAPChunk(chunkDataSize); haveData |= cTMAP; break; case 0x534B5254: // 'TRKS' if (verbose) { printf("Reading TRKS chunk starting at byte 0x%llX\n", (unsigned long long) lseek(fd, 0, SEEK_CUR)); } isOk = parseTRKSChunk(chunkDataSize); haveData |= cTRKS; break; case 0x4154454D: // 'META' if (verbose) { printf("Reading META chunk starting at byte 0x%llX\n", (unsigned long long) lseek(fd, 0, SEEK_CUR)); } isOk = parseMetaChunk(chunkDataSize); break; default: printf("Unknown chunk type 0x%X\n", chunkType); if (preloadTracks && fd != -1) close(fd); return false; break; } if (!isOk) { printf("Chunk parsing [0x%X] failed; exiting\n", chunkType); if (preloadTracks && fd != -1) close(fd); return false; } fpos += chunkDataSize + 8; // 8 bytes for the ChunkID and the ChunkSize } if (haveData != 0x07) { printf("ERROR: missing one or more critical sections\n"); return false; } // For a Woz file, we need to read *every* quarter-track; and if we've // already got the target track's data, we don't need to re-read it. // And if we're not preloading the tracks, then we'll wind up loading // them on demand later. if (preloadTracks) { for (int i=0; i<160; i++) { if (!readWozDataTrack(i)) { printf("Failed to read Woz datatrack %d\n", i); if (fd != -1) { close(fd); fd = -1; } return false; } } } #ifndef LAZYFD if (preloadTracks && fd != -1) { fd = -1; close(fd); } #endif return true; } bool Woz::readFile(const char *filename, bool preloadTracks, uint8_t forceType) { if (forceType == T_AUTO) { // Try to determine type from the file extension const char *p = strrchr(filename, '.'); if (!p) { printf("Unable to determine file type of '%s'\n", filename); return false; } if (strcasecmp(p, ".woz") == 0) { forceType = T_WOZ; } else if (strcasecmp(p, ".dsk") == 0 || strcasecmp(p, ".do") == 0) { forceType = T_DSK; } else if (strcasecmp(p, ".po") == 0) { forceType = T_PO; } else if (strcasecmp(p, ".nib") == 0) { forceType = T_NIB; } else { printf("Unable to determine file type of '%s'\n", filename); return false; } } switch (forceType) { case T_WOZ: return readWozFile(filename, preloadTracks); case T_DSK: case T_PO: return readDskFile(filename, preloadTracks, forceType); case T_NIB: return readNibFile(filename, preloadTracks); default: printf("Unknown disk type; unable to read\n"); return false; } } bool Woz::parseTRKSChunk(uint32_t chunkSize) { if (di.version == 2) { for (int i=0; i<160; i++) { if (!read16(fd, &tracks[i].startingBlock)) return false; if (!read16(fd, &tracks[i].blockCount)) return false; if (!read32(fd, &tracks[i].bitCount)) return false; tracks[i].startingByte = 0; // v1-specific } return true; } // V1 parsing uint32_t ptr = 0; uint8_t trackNumber = 0; while (ptr < chunkSize) { tracks[trackNumber].startingByte = trackNumber * 6656 + 256; tracks[trackNumber].startingBlock = 0; // v2-specific tracks[trackNumber].blockCount = 13; lseek(fd, (trackNumber * 6656 + 256) + 6648, SEEK_SET); uint16_t numBits; if (!read16(fd, &numBits)) { return false; } if (verbose) { printf("Track %d: read %d bits\n", trackNumber, numBits); } if (numBits > 6656 * 8) { fprintf(stderr, "WARNING: track %d looks like it's too long (%d bits > 6656 bytes)?\n", trackNumber, numBits); } tracks[trackNumber].bitCount = numBits; ptr += 6656; trackNumber++; } return true; } bool Woz::parseTMAPChunk(uint32_t chunkSize) { if (chunkSize != 0xa0) { printf("TMAP chunk is the wrong size; aborting\n"); return false; } for (int i=0; i<40*4; i++) { if (!read8(fd, (uint8_t *)&quarterTrackMap[i])) return false; chunkSize--; } if (verbose){ printf("Read quarter-track map:\n"); for (int i=0; i<140; i+=4) { printf("%2d %3d => %3d %3d => %3d %3d => %3d %3d => %3d\n", i/4, i, quarterTrackMap[i], i+1, quarterTrackMap[i+1], i+2, quarterTrackMap[i+2], i+3, quarterTrackMap[i+3]); } } return true; } // return true if successful bool Woz::parseInfoChunk(uint32_t chunkSize) { if (chunkSize != 60) { fprintf(stderr, "INFO chunk size is not 60; aborting\n"); return false; } if (!read8(fd, &di.version)) return false; if (di.version > 2) { fprintf(stderr, "Incorrect version header; aborting\n"); return false; } if (!read8(fd, &di.diskType)) return false; if (di.diskType != 1) { fprintf(stderr, "Not a 5.25\" disk image; aborting\n"); return false; } if (!read8(fd, &di.writeProtected)) return false; if (!read8(fd, &di.synchronized)) return false; if (!read8(fd, &di.cleaned)) return false; di.creator[32] = 0; for (int i=0; i<32; i++) { if (!read8(fd, (uint8_t *)&di.creator[i])) return false; } if (di.version >= 2) { if (!read8(fd, &di.diskSides)) return false; if (!read8(fd, &di.bootSectorFormat)) return false; if (!read8(fd, &di.optimalBitTiming)) return false; if (!read16(fd, &di.compatHardware)) return false; if (!read16(fd, &di.requiredRam)) return false; if (!read16(fd, &di.largestTrack)) return false; } else { di.diskSides = 0; di.bootSectorFormat = 0; di.compatHardware = 0; di.requiredRam = 0; di.largestTrack = 13; // 13 * 512 bytes = 6656. All tracks are // padded to 6646 (yes, 6646, not 6656)bytes // in the v1 image. di.optimalBitTiming = 32; // "standard" disk bit timing for a 5.25" disk (4us per bit) } return true; } bool Woz::parseMetaChunk(uint32_t chunkSize) { metaData = (char *)calloc(chunkSize+1, 1); if (!metaData) return false; if (read(fd, metaData, chunkSize) != chunkSize) return false; metaData[chunkSize] = 0; return true; } bool Woz::readWozDataTrack(uint8_t datatrack) { // If it's already loaded then there's nothing to do here if (tracks[datatrack].trackData) return true; // If we have no open FD, then assume anything missing is supposed // to be missing if (fd == -1) { return true; } uint16_t bitsStartBlock = tracks[datatrack].startingBlock; // Allocate a new buffer for this track uint32_t count = tracks[datatrack].blockCount * 512; if (di.version == 1) count = (tracks[datatrack].bitCount / 8) + ((tracks[datatrack].bitCount % 8) ? 1 : 0); if (tracks[datatrack].trackData) { return true; // We've already read this track's data; don't re-read it } if (!count) { // This track has no data; do nothing, and we'll be successful at it return true; } tracks[datatrack].trackData = (uint8_t *)calloc(count, 1); if (!tracks[datatrack].trackData) { perror("Failed to alloc buf to read track magnetic data"); return false; } if (di.version == 1) { if (verbose) { printf("Reading datatrack %d starting at byte 0x%X\n", datatrack, tracks[datatrack].startingByte); } if (lseek(fd, tracks[datatrack].startingByte, SEEK_SET) == -1) { perror("Failed to seek to start of block"); return false; } } else { if (verbose) { printf("Reading datatrack %d starting at byte 0x%X\n", datatrack, bitsStartBlock*512); } if (lseek(fd, bitsStartBlock*512, SEEK_SET) == -1) { perror("Failed to seek to start of block"); return false; } } uint32_t didRead = read(fd, tracks[datatrack].trackData, count); if (didRead != count) { printf("Failed to read all track data for track [read %d, wanted %d]\n", didRead, count); return false; } return true; } #if 0 static void dumpNibSector(nibSector *ns) { printf("gap1: "); for (int i=0; i<48; i++) { printf("%.2X ", ns->gap1[i]); } printf("\nsectorProlog: "); for (int i=0; i<3; i++) { printf("%.2X ", ns->sectorProlog[i]); } printf("\n volume44: %.2X %.2X == %.2X\n", ns->volume44[0], ns->volume44[1], de44(ns->volume44)); printf(" sector44: %.2X %.2X == %.2X\n", ns->sector44[0], ns->sector44[1], de44(ns->sector44)); printf(" checksum44: %.2X %.2X == %.2X\n", ns->checksum44[0], ns->checksum44[1], de44(ns->checksum44)); printf("sectorEpilog: "); for (int i=0; i<3; i++) { printf("%.2X ", ns->sectorEpilog[i]); } printf("\n"); printf("gap2: "); for (int i=0; i<5; i++) { printf("%.2X ", ns->gap2[i]); } printf("\ndataProlog: "); for (int i=0; i<3; i++) { printf("%.2X ", ns->dataProlog[i]); } printf("\n data62:"); for (int i=0; i<342; i+=16) { printf("\n "); for (int j=0; (j<16) && (j+i < 342); j++) { printf("%.2X ", ns->data62[i+j]); } } printf("\n checksum: %.2X", ns->checksum); printf("\ndataEpilog: "); for (int i=0; i<3; i++) { printf("%.2X ", ns->dataEpilog[i]); } printf("\n\n"); } #endif // This writes to the given *physical* sector -- the caller is responsible for translating // to a logical sector number bool Woz::writeNibSectorDataToDataTrack(uint8_t dataTrack, uint8_t sector, uint8_t nibData[343]) { // Find the spot on the track that has the right sector if (!tracks[dataTrack].trackData) { // Load the cached track for this phys Nib track. if (!loadMissingTrackFromImage(dataTrack)) { fprintf(stderr, "Failed to read track %d\n", dataTrack); return false; } } // find the data header // write this nibblized data on top of whatever was there before // ensure the epilog is correct nibSector sectorData; memset(sectorData.gap1, 0xFF, sizeof(sectorData.gap1)); memset(sectorData.gap2, 0xFF, sizeof(sectorData.gap2)); // Allow two loops through the track data looking for the sector prolog uint32_t endCount = tracks[dataTrack].blockCount*512*2; if (di.version == 1) endCount = 2*6646; uint32_t ptr = 0; while (ptr < endCount) { sectorData.sectorProlog[0] = sectorData.sectorProlog[1]; sectorData.sectorProlog[1] = sectorData.sectorProlog[2]; sectorData.sectorProlog[2] = nextDiskByte(dataTrack); ptr++; if (sectorData.sectorProlog[0] == 0xd5 && sectorData.sectorProlog[1] == 0xaa && sectorData.sectorProlog[2] == 0x96) { // Found *a* sector header. See if it's ours. sectorData.volume44[0] = nextDiskByte(dataTrack); sectorData.volume44[1] = nextDiskByte(dataTrack); sectorData.track44[0] = nextDiskByte(dataTrack); sectorData.track44[1] = nextDiskByte(dataTrack); sectorData.sector44[0] = nextDiskByte(dataTrack); sectorData.sector44[1] = nextDiskByte(dataTrack); sectorData.checksum44[0] = nextDiskByte(dataTrack); sectorData.checksum44[1] = nextDiskByte(dataTrack); sectorData.sectorEpilog[0] = nextDiskByte(dataTrack); sectorData.sectorEpilog[1] = nextDiskByte(dataTrack); sectorData.sectorEpilog[2] = nextDiskByte(dataTrack); if (sectorData.sectorEpilog[0] == 0xde && sectorData.sectorEpilog[1] == 0xaa && sectorData.sectorEpilog[2] == 0xeb) { // Header is integral. See if it's our sector: uint8_t sectorNum = de44(sectorData.sector44); if (sectorNum != sector) { continue; } // It's our sector - find the data chunk and write it while (ptr < tracks[dataTrack].blockCount*512*2) { sectorData.dataProlog[0] = sectorData.dataProlog[1]; sectorData.dataProlog[1] = sectorData.dataProlog[2]; sectorData.dataProlog[2] = nextDiskByte(dataTrack); ptr++; if (sectorData.dataProlog[0] == 0xd5 && sectorData.dataProlog[1] == 0xaa && sectorData.dataProlog[2] == 0xad) { // That's the data prolog, so next comes our data payload: // 342 bytes of payload, 1 byte of checksum. for (int i=0; i<343; i++) { writeNextWozByte(dataTrack, nibData[i]); } writeNextWozByte(dataTrack, 0xDE); writeNextWozByte(dataTrack, 0xAA); writeNextWozByte(dataTrack, 0xEB); return true; } } } } } // If we get here, we failed to write it return false; } bool Woz::readNibSectorDataFromDataTrack(uint8_t dataTrack, uint8_t sector, nibSector *sectorData) { // Find the sector header for this sector and return the nibblized data uint32_t ptr = 0; if (!tracks[dataTrack].trackData) { // Load the cached track for this phys Nib track. if (!loadMissingTrackFromImage(dataTrack)) { fprintf(stderr, "Failed to read track %d\n", dataTrack); return false; } } memset(sectorData->gap1, 0xFF, sizeof(sectorData->gap1)); memset(sectorData->gap2, 0xFF, sizeof(sectorData->gap2)); // Allow two loops through the track data looking for the sector prolog uint32_t endCount = tracks[dataTrack].blockCount*512*2; if (di.version == 1) endCount = 2*6646; while (ptr < endCount) { sectorData->sectorProlog[0] = sectorData->sectorProlog[1]; sectorData->sectorProlog[1] = sectorData->sectorProlog[2]; sectorData->sectorProlog[2] = nextDiskByte(dataTrack); ptr++; if (sectorData->sectorProlog[0] == 0xd5 && sectorData->sectorProlog[1] == 0xaa && sectorData->sectorProlog[2] == 0x96) { // Found *a* sector header. See if it's ours. sectorData->volume44[0] = nextDiskByte(dataTrack); sectorData->volume44[1] = nextDiskByte(dataTrack); sectorData->track44[0] = nextDiskByte(dataTrack); sectorData->track44[1] = nextDiskByte(dataTrack); sectorData->sector44[0] = nextDiskByte(dataTrack); sectorData->sector44[1] = nextDiskByte(dataTrack); sectorData->checksum44[0] = nextDiskByte(dataTrack); sectorData->checksum44[1] = nextDiskByte(dataTrack); sectorData->sectorEpilog[0] = nextDiskByte(dataTrack); sectorData->sectorEpilog[1] = nextDiskByte(dataTrack); sectorData->sectorEpilog[2] = nextDiskByte(dataTrack); if (sectorData->sectorEpilog[0] == 0xde && sectorData->sectorEpilog[1] == 0xaa && sectorData->sectorEpilog[2] == 0xeb) { // Header is integral. See if it's our sector: uint8_t sectorNum = de44(sectorData->sector44); if (sectorNum != sector) { continue; } // It's our sector - find the data chunk and read it while (ptr < tracks[dataTrack].blockCount*512*2) { sectorData->dataProlog[0] = sectorData->dataProlog[1]; sectorData->dataProlog[1] = sectorData->dataProlog[2]; sectorData->dataProlog[2] = nextDiskByte(dataTrack); ptr++; if (sectorData->dataProlog[0] == 0xd5 && sectorData->dataProlog[1] == 0xaa && sectorData->dataProlog[2] == 0xad) { // Found the data; copy it in for (int i=0; i<342; i++) { sectorData->data62[i] = nextDiskByte(dataTrack); } sectorData->checksum = nextDiskByte(dataTrack); sectorData->dataEpilog[0] = nextDiskByte(dataTrack); sectorData->dataEpilog[1] = nextDiskByte(dataTrack); sectorData->dataEpilog[2] = nextDiskByte(dataTrack); if (sectorData->dataEpilog[0] != 0xde || sectorData->dataEpilog[1] != 0xaa || sectorData->dataEpilog[2] != 0xeb) { continue; } // Have an integral hunk of data, with epilog - return it return true; } } } } } return false; } bool Woz::writeInfoChunk(uint8_t version, int fdout) { if (!write8(fdout, version) || !write8(fdout, di.diskType) || !write8(fdout, di.writeProtected) || !write8(fdout, di.synchronized) || !write8(fdout, di.cleaned)) return false; for (int i=0; i<32; i++) { if (!write8(fdout, di.creator[i])) return false; } if (version >= 2) { // If we read a Wozv1, this will be set to 0. Set it to 1. if (di.diskSides == 0) di.diskSides = 1; if ( !write8(fdout, di.diskSides) || !write8(fdout, di.bootSectorFormat) || !write8(fdout, di.optimalBitTiming) || !write16(fdout, di.compatHardware) || !write16(fdout, di.requiredRam) || !write16(fdout, di.largestTrack)) return false; } // Padding for (int i=0; i<((version==1)?23:14); i++) { if (!write8(fdout, 0)) return false; } return true; } bool Woz::writeTMAPChunk(uint8_t version, int fdout) { for (int i=0; i<40*4; i++) { if (!write8(fdout, quarterTrackMap[i])) return false; } return true; } bool Woz::writeTRKSChunk(uint8_t version, int fdout) { if (version == 1) { fprintf(stderr, "V1 write is not implemented\n"); return false; } // Reconstruct all of the starting blocks/blockCounts for each // track. The bitCount should be correct. uint8_t numTracksPacked = 0; for (int i=0; i<160; i++) { // If we didn't preload, and the track isn't loaded, then load it now if (autoFlushTrackData && !tracks[i].trackData) { loadMissingTrackFromImage(i); } if (tracks[i].trackData && tracks[i].bitCount) { // For any tracks that have data, put it somewhere in the destination file tracks[i].startingBlock = STARTBLOCK + 13*(numTracksPacked++); // assume tracks[track].bitCount is correct, and recalculate the block size of this track uint32_t bytes = (tracks[i].bitCount / 8) + ((tracks[i].bitCount % 8) ? 1 : 0); uint32_t blocks = (bytes / 512) + ((bytes % 512) ? 1 : 0); tracks[i].blockCount = blocks; } else { tracks[i].startingBlock = 0; tracks[i].blockCount = 0; tracks[i].bitCount = 0; } if (!write16(fdout, tracks[i].startingBlock)) return false; if (!write16(fdout, tracks[i].blockCount)) return false; if (!write32(fdout, tracks[i].bitCount)) return false; } // All the track data for (int i=0; i<160; i++) { // If we didn't preload, and the track isn't loaded, then load it now if (autoFlushTrackData && !tracks[i].trackData) { loadMissingTrackFromImage(i); } if (tracks[i].startingBlock && tracks[i].blockCount) { if (lseek(fdout, tracks[i].startingBlock * 512, SEEK_SET) == -1) { fprintf(stderr, "Failed to seek before writing track\n"); return false; } // Technically, we only have this many bytes to write: // uint32_t writeSize = (tracks[i].bitCount / 8) + ((tracks[i].bitCount % 8) ? 1 : 0); // ... but in practice, the tracks are all padded to NIBTRACKSIZE bytes; // and we alloc'd a buffer of that size, too; so write the whole thing, // since it would have been calloc'd initially. ssize_t numWritten = write(fdout, tracks[i].trackData, NIBTRACKSIZE); if (numWritten != NIBTRACKSIZE) { fprintf(stderr, "Failed to write track [%ld]\n", numWritten); perror("error writing"); return false; } tracks[i].dirty = false; } } return true; } bool Woz::decodeWozTrackToNibFromDataTrack(uint8_t dataTrack, nibSector sectorData[16]) { for (int sector=0; sector<16; sector++) { if (!readNibSectorDataFromDataTrack(dataTrack, sector, (nibSector *)(§orData[sector]))) { fprintf(stderr, "failed to readNibSectorDataFromDataTrack for sector %d\n", sector); return false; } } return true; } // The caller is responsible for translating between the physical sector numbers and the logical // sector numbers. This only takes in to account the physical sector numbers (whatever is // represented in the sector's header). bool Woz::decodeWozTrackSector(uint8_t phystrack, uint8_t sector, uint8_t dataOut[256]) { uint8_t dataTrack = quarterTrackMap[phystrack*4]; nibSector nibData; if (!readNibSectorDataFromDataTrack(dataTrack, sector, &nibData)) { fprintf(stderr, "Failed to readNibSectorDataFromDataTrack for track %d sector %d\n", phystrack, sector); return false; } #if 0 printf("Track %d physector %d nib dump:\n", phystrack, sector); dumpNibSector(&nibData); #endif if (denibblizeSector(nibData, dataOut) != errorNone) { fprintf(stderr, "failed to denib sector\n"); return false; } return true; } // The caller is responsible for translating between the physical sector numbers and the logical // sector numbers. This only takes in to account the physical sector numbers (whatever is // represented in the sector's header). bool Woz::encodeWozTrackSector(uint8_t phystrack, uint8_t sector, uint8_t dataIn[256]) { uint8_t dataTrack = quarterTrackMap[phystrack*4]; uint8_t dataOut[343]; if (nibblizeSector(dataIn, dataOut) != errorNone) { fprintf(stderr, "Failed to nibblizeSector for track %d sector %d\n", phystrack, sector); return false; } if (!writeNibSectorDataToDataTrack(dataTrack, sector, dataOut)) { fprintf(stderr, "Failed to writeNibSectorDataToDataTrack for track %d sector %d\n", phystrack, sector); return false; } return true; } bool Woz::decodeWozTrackToDsk(uint8_t phystrack, uint8_t subtype, uint8_t sectorData[256*16]) { // Figure out which datatrack we need for the given physical track uint8_t dataTrack = quarterTrackMap[phystrack*4]; // First read it to a NIB; then convert the NIB to a DSK. nibSector nibData[16]; if (!decodeWozTrackToNibFromDataTrack(dataTrack, nibData)) { fprintf(stderr, "failed to decode Woz\n"); return false; } #if 0 for (int s=0; s<16; s++) { printf("Track read dumping nib sector %d:\n", s); dumpNibSector(&nibData[s]); } #endif if (denibblizeTrack((const uint8_t *)nibData, sectorData, subtype) != errorNone) { fprintf(stderr, "failed to denib track\n"); return false; } return true; } bool Woz::checksumWozDataTrack(uint8_t datatrack, uint32_t *retCRC) { if (!retCRC) return false; if (!tracks[datatrack].trackData) { *retCRC = 0; return false; } *retCRC = compute_crc_32(tracks[datatrack].trackData, tracks[datatrack].bitCount/8); return true; } void Woz::dumpInfo() { printf("WOZ image version %d\n", di.version); printf("Disk type: %s\n", di.diskType == 1 ? "5.25\"" : "3.5\""); printf("Write protected: %s\n", di.writeProtected ? "yes" : "no"); printf("Synchronized: %s\n", di.synchronized ? "yes" : "no"); printf("Cleaned: %s\n", di.cleaned ? "yes" : "no"); printf("Creator: %s\n", di.creator); printf("Disk sides: %d\n", di.diskSides); printf("Boot sector format: "); switch (di.bootSectorFormat) { case 0: default: printf("unknown\n"); break; case 1: printf("16 sector\n"); break; case 2: printf("13 sector\n"); break; case 3: printf("Both 16 and 13 sector\n"); break; } printf("Optimal bit timing: %d ns\n", di.optimalBitTiming * 125); printf("Hardware compatability flags: 0x%X\n", di.compatHardware); printf("Required RAM: %d K\n", di.requiredRam); printf("Largest track: %d bytes\n", di.largestTrack * 512); printf("\n"); if (metaData) { printf("Metadata:\n"); char *token, *string, *tofree; tofree = string = strdup(metaData); char *parts[25]; memset(parts, 0, sizeof(parts)); int idx = 0; while ((token = strsep(&string, "\n")) != NULL) { if (idx >= sizeof(parts)) { printf("ERROR: too many metadata keys\n"); return; } parts[idx++] = strdup(token); } free(tofree); for (int idx2=0; idx2= 0) { free(parts[idx]); } printf("\n"); } if (dumpflags & DUMP_QTMAP) { printf("Quarter-track map:\n"); for (int i=0; i<140; i+=4) { printf("%2d %3d => %3d %3d => %3d %3d => %3d %3d => %3d\n", i/4, i, quarterTrackMap[i], i+1, quarterTrackMap[i+1], i+2, quarterTrackMap[i+2], i+3, quarterTrackMap[i+3]); } } if (dumpflags & DUMP_QTCRC) { printf("Woz internal quarter-track CRCs:\n"); // Dump the CRC32 for each Woz quarter-track for (int i=0 ;i<160; i++) { uint32_t crc=0; checksumWozDataTrack(i, &crc); printf("Woz track %d CRC32: 0x%X\n", i, crc); } } if (dumpflags & DUMP_TRACK) { for (int i=0; i<40; i++) { printf("Track %d:\n", i); if (di.version == 1) { printf(" Starts at byte %d\n", tracks[i].startingByte); } else { printf(" Starts at block %d\n", tracks[i].startingBlock); } printf(" Number of blocks: %d\n", tracks[i].blockCount); printf(" Number of bits: %d\n", tracks[i].bitCount); if (tracks[i].bitCount && tracks[i].trackData) { if (dumpflags & DUMP_RAWTRACK) { // Raw track dump printf(" Raw track data:\n"); for (int k=0; k<(tracks[i].bitCount/8)+((tracks[i].bitCount%8)?1:0); k+=16) { printf(" 0x%.4X :", k); for (int j=0; j<16; j++) { if (k+j < (tracks[i].bitCount/8)+((tracks[i].bitCount%8)?1:0)) { printf(" %.2X", tracks[i].trackData[k+j]); } } printf("\n"); } } if (dumpflags & DUMP_SECTOR) { printf(" Sector dump:\n"); // Look at the sectors in numerical order // FIXME: 13-sector support nibSector sectorData; for (int sector=0; sector<16; sector++) { if (readNibSectorDataFromDataTrack(quarterTrackMap[i*4], sector, §orData)) { printf(" Volume ID: %d\n", de44(sectorData.volume44)); printf(" Track ID: %d\n", de44(sectorData.track44)); uint8_t sector = de44(sectorData.sector44); printf(" Sector: %d\n", sector); printf(" Cksum: %d\n", de44(sectorData.checksum44)); printf(" Sector Data:\n"); for (int k=0; k<342; k+=16) { printf(" 0x%.4X :", k); for (int j=0; j<16; j++) { if (k+j < 342) { printf(" %.2X", sectorData.data62[k+j]); } } printf("\n"); } } } } } if (dumpflags & DUMP_TOFILE) { // Dump each sector to a file for analysis uint8_t sectorData[256*16]; if (decodeWozTrackToDsk(i, T_DSK, sectorData)) { for (int j=0; j<16; j++) { char buf[25]; sprintf(buf, "t%ds%d", i, j); int fdout = open(buf, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); write(fdout, §orData[256*j], 256); close(fdout); } } else { fprintf(stderr, "Unable to read track %d\n", i); } } if (dumpflags & DUMP_ORDEREDSECTOR) { #define denib(a, b) ((((a) & ~0xAA) << 1) | ((b) & ~0xAA)) printf(" Track-ordered sector dump:\n"); // Look at the sectors found in order on the track trackPointer = 0; trackBitIdx = 0x80; trackBitCounter = 0; trackByteFromDataTrack = 255; uint16_t sectorsFound = 0; do { if (nextDiskByte(i) == 0xD5 && nextDiskByte(i) == 0xAA && nextDiskByte(i) == 0x96) { printf(" Volume ID: %d\n", denib(nextDiskByte(i), nextDiskByte(i))); printf(" Track ID: %d\n", denib(nextDiskByte(i), nextDiskByte(i))); uint8_t sector = denib(nextDiskByte(i), nextDiskByte(i)); printf(" Sector: %d\n", sector); sectorsFound |= (1 << sector); printf(" Cksum: %d\n", denib(nextDiskByte(i), nextDiskByte(i))); nextDiskByte(i); // skip epilog nextDiskByte(i); nextDiskByte(i); // look for data prolog d5 aa ad while (nextDiskByte(i) != 0xD5 && trackLoopCounter < 2) ; if (trackLoopCounter < 2) { // Hope that's it and skip two bytes nextDiskByte(i); nextDiskByte(i); // Dump the 6-and-2 data printf(" Sector Data:\n"); for (int k=0; k<342; k+=16) { printf(" 0x%.4X :", k); for (int j=0; j<16; j++) { if (k+j < 342) { printf(" %.2X", nextDiskByte(i)); } } printf("\n"); } } } } while (sectorsFound != 0xFFFF && trackLoopCounter < 2); } } } } bool Woz::isSynchronized() { return di.synchronized; } uint8_t Woz::dataTrackNumberForQuarterTrack(uint16_t qt) { return quarterTrackMap[qt]; } bool Woz::flush() { // *** FIXME - should flush() write the image out if it's dirty? return true; }