Files
fluxengine/lib/imagewriter/imdimagewriter.cc
David Given 2bda78fb40 Distinguish between filesystem track ordering and image track ordering
(although currently only the filesystem ordering is used).
2024-11-29 22:07:58 +01:00

535 lines
20 KiB
C++

#include "lib/core/globals.h"
#include "lib/config/flags.h"
#include "lib/data/sector.h"
#include "lib/imagewriter/imagewriter.h"
#include "lib/data/image.h"
#include "lib/config/config.pb.h"
#include "lib/data/layout.h"
#include "lib/core/logger.h"
#include <algorithm>
#include <iostream>
#include <fstream>
#include <chrono>
#include <ctime>
/*
* Where to get the type of encoding FM or MFM? Now solved with options in
* proto config
*/
static const char LABEL[] = "IMD archive by fluxengine on"; // 22 karakters
static uint8_t getModulationandSpeed(
int flags, ImdOutputProto::RecordingMode mode)
{
if (flags == 0)
{
error(
"Can't write IMD files with this speed {}, and modulation {}. Did "
"you read a real disk?",
flags,
false);
}
else
{
flags = 1000000.0 / flags;
}
if ((flags > 950) and
(flags < 1050)) // HD disk 5% discrepency is ok 1000*5% = 50 1 us
{
/* 500 kbps */
if (mode == ImdOutputProto::RECMODE_FM)
{
return 0;
}
else
{
return 3;
}
}
else if ((flags > 1475) and (flags < 1575)) // SD disk
{
/* 300 kbps*/
if (mode == ImdOutputProto::RECMODE_FM)
{
return 1;
}
else
{
return 4;
}
}
else if ((flags > 1900) and (flags < 2100)) // DD disk
{
/* 250 kbps */
if (mode == ImdOutputProto::RECMODE_FM)
{
return 2;
}
else
{
return 5;
}
}
else
{
error(
"IMD: Can't write IMD files with this speed {}, and modulation {}. "
"Try another format.",
flags,
false);
}
}
struct TrackHeader
{
uint8_t ModeValue;
uint8_t track;
uint8_t Head;
uint8_t numSectors;
uint8_t SectorSize;
};
static uint8_t setSectorSize(int flags)
{
switch (flags)
{
case 128:
return 0;
case 256:
return 1;
case 512:
return 2;
case 1024:
return 3;
case 2048:
return 4;
case 4096:
return 5;
case 8192:
return 6;
}
error(
"IMD: Sector size {} not in standard range (128, 256, 512, 1024, 2048, "
"4096, 8192).",
flags);
}
#define SEC_CYL_MAP_FLAG 0x80
#define SEC_HEAD_MAP_FLAG 0x40
#define HEAD_MASK 0x3F
#define END_OF_FILE 0x1A
// 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
class ImdImageWriter : public ImageWriter
{
public:
ImdImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
void writeImage(const Image& image) override
{
const Geometry& geometry = image.getGeometry();
unsigned numHeads;
unsigned numSectors;
unsigned numBytes;
std::ofstream outputFile(
_config.filename(), std::ios::out | std::ios::binary);
if (!outputFile.is_open())
error("IMD: cannot open output file");
unsigned numSectorsinTrack = 0;
numHeads = geometry.numSides;
numSectors = geometry.numSectors;
numBytes = geometry.sectorSize;
Bytes imagenew;
ByteWriter bw(imagenew);
ImdOutputProto::DataRate dataRate = _config.imd().data_rate();
if (dataRate == ImdOutputProto::RATE_GUESS)
{
dataRate = (geometry.numSectors > 10) ? ImdOutputProto::RATE_HD
: ImdOutputProto::RATE_DD;
if (geometry.sectorSize <= 256)
dataRate = ImdOutputProto::RATE_SD;
log("IMD: guessing data rate as {}",
ImdOutputProto::DataRate_Name(dataRate));
}
ImdOutputProto::RecordingMode recordingMode =
_config.imd().recording_mode();
if (recordingMode == ImdOutputProto::RECMODE_GUESS)
{
recordingMode = ImdOutputProto::RECMODE_MFM;
log("IMD: guessing recording mode as {}",
ImdOutputProto::RecordingMode_Name(recordingMode));
}
// Give the user a option to give a comment in the IMD file for archive
// purposes.
auto start = std::chrono::system_clock::now();
std::time_t time = std::chrono::system_clock::to_time_t(start);
std::string comment = _config.imd().comment();
if (comment.size() == 0)
{
comment = LABEL;
comment.append(" date: ");
comment.append(std::ctime(&time));
}
else
{
comment.insert(0, "IMD ");
}
bw.seek(0);
bw.append(comment);
bw.write_8(END_OF_FILE);
std::string sector_skew;
sector_skew.clear();
unsigned Status_Sector = 1;
bool blnOptionalCylinderMap = false;
bool blnOptionalHeadMap = false;
/* Write the actual sector data. */
for (int track = 0; track < geometry.numTracks; track++)
{
for (int head = 0; head < numHeads; head++)
{
unsigned sectorIdBase = 1; // IMD starts sector numbering with
// 1;
unsigned sectorId = 0;
TrackHeader header = {0,
0,
0,
0,
0}; // define something to hold the header values
const auto& sector = image.get(track, head, sectorId + 1);
if (!sector)
{ // sector 0 doesnt exist exit with error
// this track, head has no sectors
Status_Sector = 0;
log("IMD: sector {} not found on track {}, head {}\n",
sectorId + 1,
track,
head);
break;
}
else
{
/* Get the header information */
numBytes =
sector->data.size(); // number of bytes can change per
// sector per track
header.track = track;
header.Head = head;
header.SectorSize = setSectorSize(numBytes);
sector_skew.clear();
numSectorsinTrack = 0;
nanoseconds_t RATE = 0;
if (sector->clock > 0)
{
RATE = 1000000.0 / sector->clock;
}
else
{
switch (dataRate)
{
case ImdOutputProto::RATE_HD:
RATE = 1000;
break;
case ImdOutputProto::RATE_SD:
RATE = 1500;
break;
case ImdOutputProto::RATE_DD:
RATE = 2000;
break;
case ImdOutputProto::RATE_GUESS:
break;
}
}
header.ModeValue =
getModulationandSpeed(RATE, recordingMode);
}
// determine number of sectors in track
for (int sectorId = 0; sectorId < numSectors; sectorId++)
{
const auto& sector = image.get(track, head, sectorId + 1);
if (!sector)
{
break;
}
else
{
numSectorsinTrack++;
}
}
// determine sector skew and if there are optional cylindermaps
// or headermaps
for (int sectorId = 0; sectorId < numSectorsinTrack; sectorId++)
{
const auto& sector = image.get(track, head, sectorId + 1);
if (!sector)
{
break;
}
else
{
sector_skew.push_back(
(sectorId + sectorIdBase) +
'0'); // fill sectorskew start with 1
if ((sector->physicalTrack) !=
(sector->logicalTrack)) // different physicaltrack
// fromn logicaltrack
{
blnOptionalCylinderMap = true;
}
if (sector->logicalSide !=
sector->physicalSide) // different physicalside
// fromn logicalside
{
blnOptionalHeadMap = true;
}
}
}
bw.write_8(header.ModeValue); // 1 byte ModeValue
bw.write_8(track); // 1 byte Cylinder
// are there optional cylinder or head maps?
if (blnOptionalCylinderMap)
{
header.Head = header.Head ^
SEC_CYL_MAP_FLAG; // if head was 0 (00000000)
// it becomes (10000000)
}
if (blnOptionalHeadMap)
{
header.Head = header.Head ^
SEC_HEAD_MAP_FLAG; // if head was 1 (00000001)
// it becomes (01000001)
}
bw.write_8(head); // 1 byte Head
bw.write_8(
numSectorsinTrack); // 1 byte number of sectors in track
bw.write_8(header.SectorSize); // 1 byte sector size
for (int sectorId = 0; sectorId < numSectorsinTrack; sectorId++)
{
bw.write_8(
(sectorId + sectorIdBase)); // sector numbering map
}
// Write optional cylinder map
// 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 (blnOptionalCylinderMap)
{
// determine how the optional cylinder map looks like
// write the corresponding logical ID
for (int sectorId = 0; sectorId < numSectorsinTrack;
sectorId++)
{
// const auto& sector = sectors.get(track, head,
// sectorId);
bw.write_8(sector->logicalTrack); // 1 byte logical
// track
}
}
// Write 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.
if (blnOptionalHeadMap)
{
// determine how the optional head map looks like
// write the corresponding logical ID
for (int sectorId = 0; sectorId < numSectorsinTrack;
sectorId++)
{
// const auto& sector = sectors.get(track, head,
// sectorId);
bw.write_8(sector->logicalSide); // 1 byte logical side
}
}
// Now read data and write to file
for (int sectorId = 0; sectorId < numSectorsinTrack; sectorId++)
{
// clang-format off
/* 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
// read sector
const auto& sector = image.get(track, head, sectorId + 1);
bool blnCompressable =
false; // Consists the sector of 1 value? if yes then
// compresses IMD this to 1 value
Bytes sectordata(numBytes); // define the sectordata with
// the size of the sectorsize
Bytes compressed(
1); // reserve 1 byte for comressed sectordata
uint8_t byte; // value read
uint8_t
byte_previous; // previous value read (to determine if
// all bytes are equel in this sector)
if (!sector)
{
Status_Sector = 0;
break;
}
else
{
ByteReader br(sector->data); // read the sector data
int i;
// determine if all bytes are the same -> compress and
// sector status = 2
for (i = 0; i < numBytes; i++)
{
byte = br.read_8();
if (i == 0)
{
byte_previous = byte;
}
if (byte_previous == byte)
{
blnCompressable = true;
}
else
{
blnCompressable = false;
break;
}
}
switch (sector->status)
{
// 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 Sector::MISSING: /* Sector data unavailable -
could not be read */
Status_Sector = 0;
break;
case Sector::OK: /* Normal data: (Sector Size) bytes
follow */
if (blnCompressable) // data is compressable
{
Status_Sector = 2;
}
else
{
Status_Sector = 1;
}
break;
case Sector::DATA_MISSING:
Status_Sector =
3; // we misuse normal data with
// deleted-data addres mark for this
// missing data option
break;
// IMD recognizes all of these cases. but fluxengine
// doesnt support them. case 2: /* Compressed: All
// bytes in sector have same value (xx) */ case 3:
// /* Normal data with "Deleted-Data address mark"
// */ case 4: /* Compressed with "Deleted-Data
// address mark"*/
case Sector::BAD_CHECKSUM:
// case 5: /* Normal data read with data error -
// could not be read*/
Status_Sector = 5;
break;
// case 6: /* Compressed read with data error -
// could not be read */ case 7: /* Deleted data
// read with data error - could not be read */
// case 8: /* Compressed, Deleted read with data
// error - could not be read */
default:
error(
"IMD: Don't understand IMD files with "
"sector status {}",
Status_Sector);
}
bw.write_8(Status_Sector); // 1 byte status sector
if (blnCompressable)
{
bw.write_8(byte);
blnCompressable = false;
}
else
{
bw.append(sector->data);
}
numSectors = numSectorsinTrack;
}
blnOptionalCylinderMap = false;
blnOptionalHeadMap = false;
}
}
}
imagenew.writeTo(outputFile);
log("IMD: Written {} tracks, {} heads, {} sectors, {} bytes per "
"sector, {} kB total",
geometry.numTracks,
numHeads,
numSectors,
numBytes,
outputFile.tellp() / 1024);
}
};
std::unique_ptr<ImageWriter> ImageWriter::createImdImageWriter(
const ImageWriterProto& config)
{
return std::unique_ptr<ImageWriter>(new ImdImageWriter(config));
}