mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			479 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			479 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "lib/core/globals.h"
 | |
| #include "lib/config/flags.h"
 | |
| #include "lib/data/sector.h"
 | |
| #include "lib/imagereader/imagereader.h"
 | |
| #include "lib/data/image.h"
 | |
| #include "lib/config/proto.h"
 | |
| #include "lib/core/logger.h"
 | |
| #include "lib/data/layout.h"
 | |
| #include "lib/config/config.pb.h"
 | |
| #include <algorithm>
 | |
| #include <iostream>
 | |
| #include <fstream>
 | |
| 
 | |
| 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
 | |
|      * <End of file>
 | |
|      */
 | |
|     // clang-format on
 | |
|     std::unique_ptr<Image> readImage() override
 | |
|     {
 | |
|         // 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> image(new Image);
 | |
|         TrackHeader header = {0, 0, 0, 0, 0};
 | |
|         TrackHeader previousheader = {0, 0, 0, 0, 0};
 | |
| 
 | |
|         auto layout = _extraConfig.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 = _extraConfig.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 (_extraConfig.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> ImageReader::createIMDImageReader(
 | |
|     const ImageReaderProto& config)
 | |
| {
 | |
|     return std::unique_ptr<ImageReader>(new IMDImageReader(config));
 | |
| }
 | |
| 
 | |
| // vim: ts=4 sw=4 et
 |