mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -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
|