#include "globals.h" #include "flags.h" #include "sector.h" #include "imagereader/imagereader.h" #include "image.h" #include "proto.h" #include "logger.h" #include "layout.h" #include "lib/config.pb.h" #include #include #include static unsigned getModulationandSpeed(uint8_t flags, bool* fm) { switch (flags) { case 0: /* 500 kbps FM */ // clockRateKhz.setDefaultValue(250); *fm = true; return 500; break; case 1: /* 300 kbps FM */ *fm = true; return 300; break; case 2: /* 250 kbps FM */ *fm = true; return 250; break; case 3: /* 500 kbps MFM */ *fm = false; return 500; break; case 4: /* 300 kbps MFM */ *fm = false; return 300; break; case 5: /* 250 kbps MFM */ *fm = false; return 250; break; default: error( "IMD: don't understand IMD disks with this modulation and " "speed {}", flags); throw 0; } } struct TrackHeader { uint8_t ModeValue; uint8_t track; uint8_t Head; uint8_t numSectors; uint8_t SectorSize; }; static unsigned getSectorSize(uint8_t flags) { switch (flags) { case 0: return 128; break; case 1: return 256; break; case 2: return 512; break; case 3: return 1024; break; case 4: return 2048; break; case 5: return 4096; break; case 6: return 8192; break; default: error("not reachable"); throw 0; } } #define SEC_CYL_MAP_FLAG 0x80 #define SEC_HEAD_MAP_FLAG 0x40 #define HEAD_MASK 0x3F #define END_OF_FILE 0x1A class IMDImageReader : public ImageReader { public: IMDImageReader(const ImageReaderProto& config): ImageReader(config) {} // clang-format off /* * IMAGE FILE FORMAT * The overall layout of an ImageDisk .IMD image file is: * IMD v.vv: dd/mm/yyyy hh:mm:ss * Comment (ASCII only - unlimited size) * 1A byte - ASCII EOF character * - For each track on the disk: * 1 byte Mode value (0-5) see getModulationspeed for definition * 1 byte Cylinder (0-n) * 1 byte Head (0-1) * 1 byte number of sectors in track (1-n) * 1 byte sector size (0-6) see getsectorsize for definition * sector numbering map IMD start numbering sectors with 1. * sector cylinder map (optional) definied in high byte of head (since head is 0 or 1) * sector head map (optional) definied in high byte of head (since head is 0 or 1) * sector data records For each data record: * 1 byte Sector status * 0: Sector data unavailable - could not be read * 1: Normal data: (Sector Size) bytes follow * 2: Compressed: All bytes in sector have same value (xx) * 3: Normal data with "Deleted-Data address mark" * 4: Compressed with "Deleted-Data address mark" * 5: Normal data read with data error * 6: Compressed read with data error" * 7: Deleted data read with data error" * 8: Compressed, Deleted read with data error" * sector size of Sector data * */ // clang-format on std::unique_ptr readImage() { // Read File std::ifstream inputFile( _config.filename(), std::ios::in | std::ios::binary); if (!inputFile.is_open()) error("IMD: cannot open input file"); // define some variables bool fm = false; // define coding just to show in comment for setting // the right write parameters inputFile.seekg(0, inputFile.end); int inputFileSize = inputFile.tellg(); // determine filesize inputFile.seekg(0, inputFile.beg); Bytes data; data.writer() += inputFile; ByteReader br(data); std::unique_ptr image(new Image); TrackHeader header = {0, 0, 0, 0, 0}; TrackHeader previousheader = {0, 0, 0, 0, 0}; auto layout = config.mutable_layout(); unsigned n = 0; unsigned headerPtr = 0; unsigned Modulation_Speed = 0; unsigned sectorSize = 0; std::string sector_skew; int b; std::string comment; bool blnOptionalCylinderMap = false; bool blnOptionalHeadMap = false; int trackSectorSize = -1; // Read comment comment.clear(); while ((b = br.read_8()) != EOF && b != END_OF_FILE) { comment.push_back(b); n++; } headerPtr = n; // set pointer to after comment log("Comment in IMD file: {}", comment); for (;;) { if (headerPtr >= inputFileSize - 1) { break; } // first read header header.ModeValue = br.read_8(); headerPtr++; Modulation_Speed = getModulationandSpeed(header.ModeValue, &fm); header.track = br.read_8(); headerPtr++; header.Head = br.read_8(); headerPtr++; header.numSectors = br.read_8(); headerPtr++; header.SectorSize = br.read_8(); headerPtr++; sectorSize = getSectorSize(header.SectorSize); unsigned optionalsector_map[header.numSectors]; // The Sector Cylinder Map has one entry for each sector, and // contains the logical Cylinder ID for the corresponding sector in // the Sector Numbering Map. if (header.Head & SEC_CYL_MAP_FLAG) { // Read optional cylinder map for (b = 0; b < header.numSectors; b++) { optionalsector_map[b] = br.read_8(); headerPtr++; } blnOptionalCylinderMap = true; // set bool so we know there is // an optional cylinder map header.Head = header.Head ^ SEC_CYL_MAP_FLAG; // remove flag 10000001 ^ 10000000 = // 00000001 and 10000000 ^ 10000000 = // 00000000 } // Read optional sector head map // The Sector Head Map has one entry for each sector, and contains // the logical Head ID for the corresponding sector in the Sector // Numbering Map. unsigned optionalhead_map[header.numSectors]; if (header.Head & SEC_HEAD_MAP_FLAG) { // Read optional sector head map for (b = 0; b < header.numSectors; b++) { optionalhead_map[b] = br.read_8(); headerPtr++; } blnOptionalHeadMap = true; // set bool so we know there is an optional head map header.Head = header.Head ^ SEC_HEAD_MAP_FLAG; // remove flag 01000001 ^ 01000001 = // 00000001 and 01000000 ^ 0100000 = // 00000000 for writing sector head later } // read sector numbering map sector_skew.clear(); bool blnBase0 = false; // check what first start number of the // sector is. Fluxengine expects 1. for (b = 0; b < header.numSectors; b++) { uint8_t t; t = br.read_8(); if (t == 0x00) blnBase0 = true; if (blnBase0) { t = t + 1; } sector_skew.push_back(t); headerPtr++; } auto ibm = config.mutable_encoder()->mutable_ibm(); auto trackdata = ibm->add_trackdata(); auto layoutdata = layout->add_layoutdata(); trackdata->set_target_clock_period_us(1e3 / Modulation_Speed); trackdata->set_target_rotational_period_ms(200); if (trackSectorSize < 0) { trackSectorSize = sectorSize; // this is the first sector we've read, use it settings for // per-track data trackdata->set_track(header.track); trackdata->set_head(header.Head); trackdata->set_use_fm(fm); layoutdata->set_track(header.track); layoutdata->set_side(header.Head); layoutdata->set_sector_size(sectorSize); } else if (trackSectorSize != sectorSize) { error( "IMD: multiple sector sizes per track are " "currently unsupported"); } // read the sectors for (int s = 0; s < header.numSectors; s++) { Bytes sectordata; Bytes compressed(sectorSize); int SectorID; SectorID = sector_skew[s]; const auto& sector = image->put(header.track, header.Head, SectorID); // read the status of the sector unsigned int Status_Sector = br.read_8(); headerPtr++; switch (Status_Sector) { // clang-format off /* fluxengine knows of a few sector statussen but not all of the statussen in IMD. * // the statussen are in sector.h. Translation to fluxengine is as follows: * Statussen fluxengine | Status IMD *-------------------------------------------------------------------------------------------------------------------- * OK, | 1, 2 (Normal data: (Sector Size) of (compressed) bytes follow) * BAD_CHECKSUM, | 5, 6, 7, 8 * MISSING, sector not found | 0 (Sector data unavailable - could not be read) * DATA_MISSING, sector present but no data found | 3, 4 * CONFLICT, | * INTERNAL_ERROR | */ // clang-format on case 0: /* Sector data unavailable - could not be read */ sector->status = Sector::MISSING; break; case 1: /* Normal data: (Sector Size) bytes follow */ sectordata = br.read(sectorSize); headerPtr += sectorSize; sector->data.writer().append(sectordata); sector->status = Sector::OK; break; case 2: /* Compressed: All bytes in sector have same value (xx) */ compressed[0] = br.read_8(); headerPtr++; for (int k = 1; k < sectorSize; k++) { // fill data till sector is full br.seek(headerPtr); compressed[k] = br.read_8(); } sector->data.writer().append(compressed); sector->status = Sector::OK; break; case 3: /* Normal data with "Deleted-Data address mark" */ sector->status = Sector::DATA_MISSING; sectordata = br.read(sectorSize); headerPtr += sectorSize; sector->data.writer().append(sectordata); break; case 4: /* Compressed with "Deleted-Data address mark"*/ compressed[0] = br.read_8(); headerPtr++; for (int k = 1; k < sectorSize; k++) { // fill data till sector is full br.seek(headerPtr); compressed[k] = br.read_8(); } sector->data.writer().append(compressed); sector->status = Sector::DATA_MISSING; break; case 5: /* Normal data read with data error*/ sectordata = br.read(sectorSize); headerPtr += sectorSize; sector->status = Sector::BAD_CHECKSUM; sector->data.writer().append(sectordata); break; case 6: /* Compressed read with data error*/ compressed[0] = br.read_8(); headerPtr++; for (int k = 1; k < sectorSize; k++) { // fill data till sector is full br.seek(headerPtr); compressed[k] = br.read_8(); } sector->data.writer().append(compressed); sector->status = Sector::BAD_CHECKSUM; break; case 7: /* Deleted data read with data error*/ sectordata = br.read(sectorSize); headerPtr += sectorSize; sector->status = Sector::BAD_CHECKSUM; sector->data.writer().append(sectordata); break; case 8: /* Compressed, Deleted read with data error*/ compressed[0] = br.read_8(); headerPtr++; for (int k = 1; k < sectorSize; k++) { // fill data till sector is full br.seek(headerPtr); compressed[k] = br.read_8(); } sector->data.writer().append(compressed); sector->status = Sector::BAD_CHECKSUM; break; default: error( "IMD: Don't understand IMD files with sector " "status {}, track {}, sector {}", Status_Sector, header.track, s); } if (blnOptionalCylinderMap) // there was een optional cylinder // map. write it to the sector // The Sector Cylinder Map has one entry for each sector, and // contains the logical Cylinder ID for the corresponding sector // in the Sector Numbering Map. { sector->physicalTrack = Layout::remapTrackLogicalToPhysical(header.track); sector->logicalTrack = optionalsector_map[s]; blnOptionalCylinderMap = false; } else { sector->logicalTrack = header.track; sector->physicalTrack = Layout::remapTrackLogicalToPhysical(header.track); } if (blnOptionalHeadMap) // there was een optional head map. // write it to the sector // The Sector Head Map has one entry for each sector, and // contains the logical Head ID for the corresponding sector in // the Sector Numbering Map. { sector->physicalSide = header.Head; sector->logicalSide = optionalhead_map[s]; blnOptionalHeadMap = false; } else { sector->logicalSide = header.Head; sector->physicalSide = header.Head; } } } if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET) log("IMD: overriding configured format"); image->calculateSize(); const Geometry& geometry = image->getGeometry(); size_t headSize = ((header.numSectors) * (sectorSize)); size_t trackSize = (headSize * (header.Head + 1)); log("IMD: read {} tracks, {} heads; {}; {} kbps; {} sectors; " "sectorsize {}; {} kB total.", header.track + 1, header.Head + 1, fm ? "FM" : "MFM", Modulation_Speed, header.numSectors, sectorSize, (header.track + 1) * trackSize / 1024); layout->set_tracks(geometry.numTracks); layout->set_sides(geometry.numSides); return image; } }; std::unique_ptr ImageReader::createIMDImageReader( const ImageReaderProto& config) { return std::unique_ptr(new IMDImageReader(config)); } // vim: ts=4 sw=4 et