mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #677 from davidgiven/errors
Clean up error handling.
This commit is contained in:
@@ -11,56 +11,54 @@
|
||||
|
||||
static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR);
|
||||
|
||||
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */
|
||||
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine
|
||||
* with it. */
|
||||
|
||||
class AesLanierDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
AesLanierDecoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
AesLanierDecoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(SECTOR_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(SECTOR_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
/* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */
|
||||
{
|
||||
/* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */
|
||||
|
||||
readRawBits(16);
|
||||
readRawBits(16);
|
||||
|
||||
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
|
||||
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
|
||||
const auto& reversed = bytes.reverseBits();
|
||||
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE * 16);
|
||||
const auto& bytes =
|
||||
decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
|
||||
const auto& reversed = bytes.reverseBits();
|
||||
|
||||
_sector->logicalTrack = reversed[1];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalSector = reversed[2];
|
||||
_sector->logicalTrack = reversed[1];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalSector = reversed[2];
|
||||
|
||||
/* Check header 'checksum' (which seems far too simple to mean much). */
|
||||
/* Check header 'checksum' (which seems far too simple to mean much). */
|
||||
|
||||
{
|
||||
uint8_t wanted = reversed[3];
|
||||
uint8_t got = reversed[1] + reversed[2];
|
||||
if (wanted != got)
|
||||
return;
|
||||
}
|
||||
{
|
||||
uint8_t wanted = reversed[3];
|
||||
uint8_t got = reversed[1] + reversed[2];
|
||||
if (wanted != got)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check data checksum, which also includes the header and is
|
||||
* significantly better. */
|
||||
/* Check data checksum, which also includes the header and is
|
||||
* significantly better. */
|
||||
|
||||
_sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH);
|
||||
uint16_t wanted = reversed.reader().seek(0x101).read_le16();
|
||||
uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data);
|
||||
_sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH);
|
||||
uint16_t wanted = reversed.reader().seek(0x101).read_le16();
|
||||
uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data);
|
||||
_sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createAesLanierDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new AesLanierDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new AesLanierDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,15 +8,13 @@ uint8_t agatChecksum(const Bytes& bytes)
|
||||
{
|
||||
uint16_t checksum = 0;
|
||||
|
||||
for (uint8_t b : bytes)
|
||||
{
|
||||
if (checksum > 0xff)
|
||||
checksum = (checksum + 1) & 0xff;
|
||||
for (uint8_t b : bytes)
|
||||
{
|
||||
if (checksum > 0xff)
|
||||
checksum = (checksum + 1) & 0xff;
|
||||
|
||||
checksum += b;
|
||||
}
|
||||
checksum += b;
|
||||
}
|
||||
|
||||
return checksum & 0xff;
|
||||
return checksum & 0xff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
#include "fmt/format.h"
|
||||
#include <string.h>
|
||||
|
||||
// clang-format off
|
||||
/*
|
||||
* data: X X X X X X X X X - - X - X - X - X X - X - X - = 0xff956a
|
||||
* flux: 01 01 01 01 01 01 01 01 01 00 10 01 00 01 00 01 00 01 01 00 01 00 01 00 = 0x555549111444
|
||||
*
|
||||
* data: X X X X X X X X - X X - X - X - X - - X - X - X = 0xff6a95
|
||||
* flux: 01 01 01 01 01 01 01 01 00 01 01 00 01 00 01 00 01 00 10 01 00 01 00 01 = 0x555514444911
|
||||
*
|
||||
*
|
||||
* Each pattern is prefixed with this one:
|
||||
*
|
||||
* data: - - - X - - X - = 0x12
|
||||
@@ -30,65 +31,59 @@
|
||||
* 0100010010010010 = MFM encoded
|
||||
* 1000100100100100 = with trailing zero
|
||||
* - - - X - - X - = effective bitstream = 0x12
|
||||
*
|
||||
*/
|
||||
// clang-format on
|
||||
|
||||
static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID);
|
||||
static const FluxPattern DATA_PATTERN(64, DATA_ID);
|
||||
|
||||
static const FluxMatchers ALL_PATTERNS = {
|
||||
&SECTOR_PATTERN,
|
||||
&DATA_PATTERN
|
||||
};
|
||||
static const FluxMatchers ALL_PATTERNS = {&SECTOR_PATTERN, &DATA_PATTERN};
|
||||
|
||||
class AgatDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
AgatDecoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
AgatDecoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(ALL_PATTERNS);
|
||||
}
|
||||
{
|
||||
return seekToPattern(ALL_PATTERNS);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
if (readRaw64() != SECTOR_ID)
|
||||
return;
|
||||
{
|
||||
if (readRaw64() != SECTOR_ID)
|
||||
return;
|
||||
|
||||
auto bytes = decodeFmMfm(readRawBits(64)).slice(0, 4);
|
||||
if (bytes[3] != 0x5a)
|
||||
return;
|
||||
auto bytes = decodeFmMfm(readRawBits(64)).slice(0, 4);
|
||||
if (bytes[3] != 0x5a)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = bytes[1] >> 1;
|
||||
_sector->logicalSector = bytes[2];
|
||||
_sector->logicalSide = bytes[1] & 1;
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
_sector->logicalTrack = bytes[1] >> 1;
|
||||
_sector->logicalSector = bytes[2];
|
||||
_sector->logicalSide = bytes[1] & 1;
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
if (readRaw64() != DATA_ID)
|
||||
return;
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
if (readRaw64() != DATA_ID)
|
||||
return;
|
||||
|
||||
Bytes bytes = decodeFmMfm(readRawBits((AGAT_SECTOR_SIZE+2)*16)).slice(0, AGAT_SECTOR_SIZE+2);
|
||||
Bytes bytes = decodeFmMfm(readRawBits((AGAT_SECTOR_SIZE + 2) * 16))
|
||||
.slice(0, AGAT_SECTOR_SIZE + 2);
|
||||
|
||||
if (bytes[AGAT_SECTOR_SIZE+1] != 0x5a)
|
||||
return;
|
||||
if (bytes[AGAT_SECTOR_SIZE + 1] != 0x5a)
|
||||
return;
|
||||
|
||||
_sector->data = bytes.slice(0, AGAT_SECTOR_SIZE);
|
||||
uint8_t wantChecksum = bytes[AGAT_SECTOR_SIZE];
|
||||
uint8_t gotChecksum = agatChecksum(_sector->data);
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->data = bytes.slice(0, AGAT_SECTOR_SIZE);
|
||||
uint8_t wantChecksum = bytes[AGAT_SECTOR_SIZE];
|
||||
uint8_t gotChecksum = agatChecksum(_sector->data);
|
||||
_sector->status =
|
||||
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new AgatDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new AgatDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
}
|
||||
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
error("track data overrun");
|
||||
fillBitmapTo(_bits, _cursor, _bits.size(), {true, false});
|
||||
|
||||
auto fluxmap = std::make_unique<Fluxmap>();
|
||||
|
||||
@@ -18,61 +18,61 @@ uint32_t amigaChecksum(const Bytes& bytes)
|
||||
|
||||
static uint8_t everyother(uint16_t x)
|
||||
{
|
||||
/* aabb ccdd eeff gghh */
|
||||
x &= 0x6666; /* 0ab0 0cd0 0ef0 0gh0 */
|
||||
x >>= 1; /* 00ab 00cd 00ef 00gh */
|
||||
x |= x << 2; /* abab cdcd efef ghgh */
|
||||
x &= 0x3c3c; /* 00ab cd00 00ef gh00 */
|
||||
x >>= 2; /* 0000 abcd 0000 efgh */
|
||||
x |= x >> 4; /* 0000 abcd abcd efgh */
|
||||
return x;
|
||||
/* aabb ccdd eeff gghh */
|
||||
x &= 0x6666; /* 0ab0 0cd0 0ef0 0gh0 */
|
||||
x >>= 1; /* 00ab 00cd 00ef 00gh */
|
||||
x |= x << 2; /* abab cdcd efef ghgh */
|
||||
x &= 0x3c3c; /* 00ab cd00 00ef gh00 */
|
||||
x >>= 2; /* 0000 abcd 0000 efgh */
|
||||
x |= x >> 4; /* 0000 abcd abcd efgh */
|
||||
return x;
|
||||
}
|
||||
|
||||
Bytes amigaInterleave(const Bytes& input)
|
||||
{
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
|
||||
/* Write all odd bits. (Numbering starts at 0...) */
|
||||
/* Write all odd bits. (Numbering starts at 0...) */
|
||||
|
||||
{
|
||||
ByteReader br(input);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint16_t x = br.read_be16();
|
||||
x &= 0xaaaa; /* a0b0 c0d0 e0f0 g0h0 */
|
||||
x |= x >> 1; /* aabb ccdd eeff gghh */
|
||||
x = everyother(x); /* 0000 0000 abcd efgh */
|
||||
bw.write_8(x);
|
||||
}
|
||||
}
|
||||
{
|
||||
ByteReader br(input);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint16_t x = br.read_be16();
|
||||
x &= 0xaaaa; /* a0b0 c0d0 e0f0 g0h0 */
|
||||
x |= x >> 1; /* aabb ccdd eeff gghh */
|
||||
x = everyother(x); /* 0000 0000 abcd efgh */
|
||||
bw.write_8(x);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write all even bits. */
|
||||
/* Write all even bits. */
|
||||
|
||||
{
|
||||
ByteReader br(input);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint16_t x = br.read_be16();
|
||||
x &= 0x5555; /* 0a0b 0c0d 0e0f 0g0h */
|
||||
x |= x << 1; /* aabb ccdd eeff gghh */
|
||||
x = everyother(x); /* 0000 0000 abcd efgh */
|
||||
bw.write_8(x);
|
||||
}
|
||||
}
|
||||
{
|
||||
ByteReader br(input);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint16_t x = br.read_be16();
|
||||
x &= 0x5555; /* 0a0b 0c0d 0e0f 0g0h */
|
||||
x |= x << 1; /* aabb ccdd eeff gghh */
|
||||
x = everyother(x); /* 0000 0000 abcd efgh */
|
||||
bw.write_8(x);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
return output;
|
||||
}
|
||||
|
||||
Bytes amigaDeinterleave(const uint8_t*& input, size_t len)
|
||||
{
|
||||
assert(!(len & 1));
|
||||
const uint8_t* odds = &input[0];
|
||||
const uint8_t* evens = &input[len/2];
|
||||
const uint8_t* evens = &input[len / 2];
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
|
||||
for (size_t i=0; i<len/2; i++)
|
||||
for (size_t i = 0; i < len / 2; i++)
|
||||
{
|
||||
uint8_t o = *odds++;
|
||||
uint8_t e = *evens++;
|
||||
@@ -81,11 +81,15 @@ Bytes amigaDeinterleave(const uint8_t*& input, size_t len)
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
|
||||
*/
|
||||
uint16_t result =
|
||||
(((e * 0x0101010101010101ULL & 0x8040201008040201ULL)
|
||||
* 0x0102040810204081ULL >> 49) & 0x5555) |
|
||||
(((o * 0x0101010101010101ULL & 0x8040201008040201ULL)
|
||||
* 0x0102040810204081ULL >> 48) & 0xAAAA);
|
||||
|
||||
(((e * 0x0101010101010101ULL & 0x8040201008040201ULL) *
|
||||
0x0102040810204081ULL >>
|
||||
49) &
|
||||
0x5555) |
|
||||
(((o * 0x0101010101010101ULL & 0x8040201008040201ULL) *
|
||||
0x0102040810204081ULL >>
|
||||
48) &
|
||||
0xAAAA);
|
||||
|
||||
bw.write_be16(result);
|
||||
}
|
||||
|
||||
@@ -95,6 +99,6 @@ Bytes amigaDeinterleave(const uint8_t*& input, size_t len)
|
||||
|
||||
Bytes amigaDeinterleave(const Bytes& input)
|
||||
{
|
||||
const uint8_t* ptr = input.cbegin();
|
||||
return amigaDeinterleave(ptr, input.size());
|
||||
const uint8_t* ptr = input.cbegin();
|
||||
return amigaDeinterleave(ptr, input.size());
|
||||
}
|
||||
|
||||
@@ -11,70 +11,74 @@
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
/*
|
||||
/*
|
||||
* Amiga disks use MFM but it's not quite the same as IBM MFM. They only use
|
||||
* a single type of record with a different marker byte.
|
||||
*
|
||||
*
|
||||
* See the big comment in the IBM MFM decoder for the gruesome details of how
|
||||
* MFM works.
|
||||
*/
|
||||
|
||||
|
||||
static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD);
|
||||
|
||||
class AmigaDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
AmigaDecoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.amiga())
|
||||
{}
|
||||
AmigaDecoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.amiga())
|
||||
{
|
||||
}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(SECTOR_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(SECTOR_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
if (readRaw48() != AMIGA_SECTOR_RECORD)
|
||||
return;
|
||||
|
||||
const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16);
|
||||
if (rawbits.size() < (AMIGA_RECORD_SIZE*16))
|
||||
return;
|
||||
const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2);
|
||||
const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE);
|
||||
{
|
||||
if (readRaw48() != AMIGA_SECTOR_RECORD)
|
||||
return;
|
||||
|
||||
const uint8_t* ptr = bytes.begin();
|
||||
const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE * 16);
|
||||
if (rawbits.size() < (AMIGA_RECORD_SIZE * 16))
|
||||
return;
|
||||
const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE * 2);
|
||||
const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE);
|
||||
|
||||
Bytes header = amigaDeinterleave(ptr, 4);
|
||||
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);
|
||||
const uint8_t* ptr = bytes.begin();
|
||||
|
||||
_sector->logicalTrack = header[1] >> 1;
|
||||
_sector->logicalSide = header[1] & 1;
|
||||
_sector->logicalSector = header[2];
|
||||
Bytes header = amigaDeinterleave(ptr, 4);
|
||||
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);
|
||||
|
||||
uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(0, 40));
|
||||
if (gotheaderchecksum != wantedheaderchecksum)
|
||||
return;
|
||||
_sector->logicalTrack = header[1] >> 1;
|
||||
_sector->logicalSide = header[1] & 1;
|
||||
_sector->logicalSector = header[2];
|
||||
|
||||
uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(56, 1024));
|
||||
uint32_t wantedheaderchecksum =
|
||||
amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(0, 40));
|
||||
if (gotheaderchecksum != wantedheaderchecksum)
|
||||
return;
|
||||
|
||||
Bytes data;
|
||||
data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
|
||||
_sector->data = data;
|
||||
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
uint32_t wanteddatachecksum =
|
||||
amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(56, 1024));
|
||||
|
||||
Bytes data;
|
||||
data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
|
||||
_sector->data = data;
|
||||
_sector->status = (gotdatachecksum == wanteddatachecksum)
|
||||
? Sector::OK
|
||||
: Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
private:
|
||||
const AmigaDecoderProto& _config;
|
||||
nanoseconds_t _clock;
|
||||
const AmigaDecoderProto& _config;
|
||||
nanoseconds_t _clock;
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createAmigaDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new AmigaDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new AmigaDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ static void write_sector(std::vector<bool>& bits,
|
||||
const std::shared_ptr<const Sector>& sector)
|
||||
{
|
||||
if ((sector->data.size() != 512) && (sector->data.size() != 528))
|
||||
Error() << "unsupported sector size --- you must pick 512 or 528";
|
||||
error("unsupported sector size --- you must pick 512 or 528");
|
||||
|
||||
uint32_t checksum = 0;
|
||||
|
||||
@@ -114,7 +114,8 @@ public:
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
/* Number of bits for one nominal revolution of a real 200ms Amiga disk. */
|
||||
/* Number of bits for one nominal revolution of a real 200ms Amiga disk.
|
||||
*/
|
||||
int bitsPerRevolution = 200e3 / _config.clock_rate_us();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
@@ -129,13 +130,12 @@ public:
|
||||
write_sector(bits, cursor, sector);
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
error("track data overrun");
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
auto fluxmap = std::make_unique<Fluxmap>();
|
||||
fluxmap->appendBits(bits,
|
||||
calculatePhysicalClockPeriod(
|
||||
_config.clock_rate_us() * 1e3, 200e6));
|
||||
calculatePhysicalClockPeriod(_config.clock_rate_us() * 1e3, 200e6));
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ public:
|
||||
writeSector(bits, cursor, *sector);
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format(
|
||||
"track data overrun by {} bits", cursor - bits.size());
|
||||
error("track data overrun by {} bits", cursor - bits.size());
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
@@ -118,8 +117,7 @@ private:
|
||||
|
||||
// There is data to encode to disk.
|
||||
if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
|
||||
Error() << fmt::format(
|
||||
"unsupported sector size {} --- you must pick 256",
|
||||
error("unsupported sector size {} --- you must pick 256",
|
||||
sector.data.size());
|
||||
|
||||
// Write address syncing leader : A sequence of "FF40"s; 5 of them
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
const FluxPattern SECTOR_RECORD_PATTERN(32, BROTHER_SECTOR_RECORD);
|
||||
const FluxPattern DATA_RECORD_PATTERN(32, BROTHER_DATA_RECORD);
|
||||
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
|
||||
const FluxMatchers ANY_RECORD_PATTERN(
|
||||
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
|
||||
|
||||
static std::vector<uint8_t> outputbuffer;
|
||||
|
||||
@@ -32,88 +33,89 @@ static int decode_data_gcr(uint8_t gcr)
|
||||
{
|
||||
switch (gcr)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: \
|
||||
return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int decode_header_gcr(uint16_t word)
|
||||
{
|
||||
switch (word)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: return data;
|
||||
#include "header_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
switch (word)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: \
|
||||
return data;
|
||||
#include "header_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
class BrotherDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
BrotherDecoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
BrotherDecoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
if (readRaw32() != BROTHER_SECTOR_RECORD)
|
||||
return;
|
||||
{
|
||||
if (readRaw32() != BROTHER_SECTOR_RECORD)
|
||||
return;
|
||||
|
||||
const auto& rawbits = readRawBits(32);
|
||||
const auto& bytes = toBytes(rawbits).slice(0, 4);
|
||||
const auto& rawbits = readRawBits(32);
|
||||
const auto& bytes = toBytes(rawbits).slice(0, 4);
|
||||
|
||||
ByteReader br(bytes);
|
||||
_sector->logicalTrack = decode_header_gcr(br.read_be16());
|
||||
_sector->logicalSector = decode_header_gcr(br.read_be16());
|
||||
ByteReader br(bytes);
|
||||
_sector->logicalTrack = decode_header_gcr(br.read_be16());
|
||||
_sector->logicalSector = decode_header_gcr(br.read_be16());
|
||||
|
||||
/* Sanity check the values read; there's no header checksum and
|
||||
* occasionally we get garbage due to bit errors. */
|
||||
if (_sector->logicalSector > 11)
|
||||
return;
|
||||
if (_sector->logicalTrack > 79)
|
||||
return;
|
||||
/* Sanity check the values read; there's no header checksum and
|
||||
* occasionally we get garbage due to bit errors. */
|
||||
if (_sector->logicalSector > 11)
|
||||
return;
|
||||
if (_sector->logicalTrack > 79)
|
||||
return;
|
||||
|
||||
_sector->status = Sector::DATA_MISSING;
|
||||
}
|
||||
|
||||
_sector->status = Sector::DATA_MISSING;
|
||||
}
|
||||
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
if (readRaw32() != BROTHER_DATA_RECORD)
|
||||
return;
|
||||
{
|
||||
if (readRaw32() != BROTHER_DATA_RECORD)
|
||||
return;
|
||||
|
||||
const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE*8);
|
||||
const auto& rawbytes = toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE);
|
||||
const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE * 8);
|
||||
const auto& rawbytes =
|
||||
toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE);
|
||||
|
||||
Bytes bytes;
|
||||
ByteWriter bw(bytes);
|
||||
BitWriter bitw(bw);
|
||||
for (uint8_t b : rawbytes)
|
||||
{
|
||||
uint32_t nibble = decode_data_gcr(b);
|
||||
bitw.push(nibble, 5);
|
||||
}
|
||||
bitw.flush();
|
||||
Bytes bytes;
|
||||
ByteWriter bw(bytes);
|
||||
BitWriter bitw(bw);
|
||||
for (uint8_t b : rawbytes)
|
||||
{
|
||||
uint32_t nibble = decode_data_gcr(b);
|
||||
bitw.push(nibble, 5);
|
||||
}
|
||||
bitw.flush();
|
||||
|
||||
_sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD);
|
||||
uint32_t realCrc = crcbrother(_sector->data);
|
||||
uint32_t wantCrc = bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24();
|
||||
_sector->status = (realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD);
|
||||
uint32_t realCrc = crcbrother(_sector->data);
|
||||
uint32_t wantCrc =
|
||||
bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24();
|
||||
_sector->status =
|
||||
(realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createBrotherDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new BrotherDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new BrotherDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ static void write_sector_data(
|
||||
int width = 0;
|
||||
|
||||
if (data.size() != BROTHER_DATA_RECORD_PAYLOAD)
|
||||
Error() << "unsupported sector size";
|
||||
error("unsupported sector size");
|
||||
|
||||
auto write_byte = [&](uint8_t byte)
|
||||
{
|
||||
@@ -107,8 +107,7 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(
|
||||
std::shared_ptr<const TrackInfo>& trackInfo,
|
||||
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
@@ -116,8 +115,8 @@ public:
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
int sectorCount = 0;
|
||||
for (const auto& sectorData : sectors)
|
||||
int sectorCount = 0;
|
||||
for (const auto& sectorData : sectors)
|
||||
{
|
||||
double headerMs = _config.post_index_gap_ms() +
|
||||
sectorCount * _config.sector_spacing_ms();
|
||||
@@ -126,16 +125,18 @@ public:
|
||||
unsigned dataCursor = dataMs * 1e3 / _config.clock_rate_us();
|
||||
|
||||
fillBitmapTo(bits, cursor, headerCursor, {true, false});
|
||||
write_sector_header(
|
||||
bits, cursor, sectorData->logicalTrack, sectorData->logicalSector);
|
||||
write_sector_header(bits,
|
||||
cursor,
|
||||
sectorData->logicalTrack,
|
||||
sectorData->logicalSector);
|
||||
fillBitmapTo(bits, cursor, dataCursor, {true, false});
|
||||
write_sector_data(bits, cursor, sectorData->data);
|
||||
|
||||
sectorCount++;
|
||||
sectorCount++;
|
||||
}
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
error("track data overrun");
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
@@ -147,8 +148,7 @@ private:
|
||||
const BrotherEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> createBrotherEncoder(
|
||||
const EncoderProto& config)
|
||||
std::unique_ptr<Encoder> createBrotherEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Encoder>(new BrotherEncoder(config));
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
#include "c64.h"
|
||||
|
||||
/*
|
||||
* Track Sectors/track # Sectors Storage in Bytes Clock rate
|
||||
* ----- ------------- --------- ---------------- ----------
|
||||
* 1-17 21 357 7820 3.25
|
||||
* 18-24 19 133 7170 3.5
|
||||
* 25-30 18 108 6300 3.75
|
||||
* 31-40(*) 17 85 6020 4
|
||||
* ---
|
||||
* 683 (for a 35 track image)
|
||||
*
|
||||
* The clock rate is normalised for a 200ms drive.
|
||||
*/
|
||||
* Track Sectors/track # Sectors Storage in Bytes Clock rate
|
||||
* ----- ------------- --------- ---------------- ----------
|
||||
* 1-17 21 357 7820 3.25
|
||||
* 18-24 19 133 7170 3.5
|
||||
* 25-30 18 108 6300 3.75
|
||||
* 31-40(*) 17 85 6020 4
|
||||
* ---
|
||||
* 683 (for a 35 track image)
|
||||
*
|
||||
* The clock rate is normalised for a 200ms drive.
|
||||
*/
|
||||
|
||||
nanoseconds_t clockPeriodForC64Track(unsigned track)
|
||||
{
|
||||
|
||||
@@ -96,8 +96,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createCommodore64Decoder(
|
||||
const DecoderProto& config)
|
||||
std::unique_ptr<Decoder> createCommodore64Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new Commodore64Decoder(config));
|
||||
}
|
||||
|
||||
@@ -214,8 +214,7 @@ public:
|
||||
writeSector(bits, cursor, sector);
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format(
|
||||
"track data overrun by {} bits", cursor - bits.size());
|
||||
error("track data overrun by {} bits", cursor - bits.size());
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
@@ -243,8 +242,7 @@ private:
|
||||
{
|
||||
// There is data to encode to disk.
|
||||
if ((sector->data.size() != C64_SECTOR_LENGTH))
|
||||
Error() << fmt::format(
|
||||
"unsupported sector size {} --- you must pick 256",
|
||||
error("unsupported sector size {} --- you must pick 256",
|
||||
sector->data.size());
|
||||
|
||||
// 1. Write header Sync (not GCR)
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
|
||||
const FluxPattern SECTOR_RECORD_PATTERN(24, F85_SECTOR_RECORD);
|
||||
const FluxPattern DATA_RECORD_PATTERN(24, F85_DATA_RECORD);
|
||||
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
|
||||
const FluxMatchers ANY_RECORD_PATTERN(
|
||||
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
|
||||
|
||||
static int decode_data_gcr(uint8_t gcr)
|
||||
{
|
||||
switch (gcr)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: \
|
||||
return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -37,11 +39,11 @@ static Bytes decode(const std::vector<bool>& bits)
|
||||
while (ii != bits.end())
|
||||
{
|
||||
uint8_t inputfifo = 0;
|
||||
for (size_t i=0; i<5; i++)
|
||||
for (size_t i = 0; i < 5; i++)
|
||||
{
|
||||
if (ii == bits.end())
|
||||
break;
|
||||
inputfifo = (inputfifo<<1) | *ii++;
|
||||
inputfifo = (inputfifo << 1) | *ii++;
|
||||
}
|
||||
|
||||
bitw.push(decode_data_gcr(inputfifo), 4);
|
||||
@@ -54,56 +56,55 @@ static Bytes decode(const std::vector<bool>& bits)
|
||||
class DurangoF85Decoder : public Decoder
|
||||
{
|
||||
public:
|
||||
DurangoF85Decoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
DurangoF85Decoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
/* Skip sync bits and ID byte. */
|
||||
{
|
||||
/* Skip sync bits and ID byte. */
|
||||
|
||||
if (readRaw24() != F85_SECTOR_RECORD)
|
||||
return;
|
||||
if (readRaw24() != F85_SECTOR_RECORD)
|
||||
return;
|
||||
|
||||
/* Read header. */
|
||||
/* Read header. */
|
||||
|
||||
const auto& bytes = decode(readRawBits(6*10));
|
||||
const auto& bytes = decode(readRawBits(6 * 10));
|
||||
|
||||
_sector->logicalSector = bytes[2];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalTrack = bytes[0];
|
||||
_sector->logicalSector = bytes[2];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalTrack = bytes[0];
|
||||
|
||||
uint16_t wantChecksum = bytes.reader().seek(4).read_be16();
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4));
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
uint16_t wantChecksum = bytes.reader().seek(4).read_be16();
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4));
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status =
|
||||
Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
/* Skip sync bits ID byte. */
|
||||
{
|
||||
/* Skip sync bits ID byte. */
|
||||
|
||||
if (readRaw24() != F85_DATA_RECORD)
|
||||
return;
|
||||
if (readRaw24() != F85_DATA_RECORD)
|
||||
return;
|
||||
|
||||
const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH+3)*10))
|
||||
.slice(0, F85_SECTOR_LENGTH+3);
|
||||
ByteReader br(bytes);
|
||||
const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH + 3) * 10))
|
||||
.slice(0, F85_SECTOR_LENGTH + 3);
|
||||
ByteReader br(bytes);
|
||||
|
||||
_sector->data = br.read(F85_SECTOR_LENGTH);
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data);
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->data = br.read(F85_SECTOR_LENGTH);
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data);
|
||||
_sector->status =
|
||||
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createDurangoF85Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new DurangoF85Decoder(config));
|
||||
return std::unique_ptr<Decoder>(new DurangoF85Decoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
const FluxPattern SECTOR_ID_PATTERN(16, 0xabaa);
|
||||
|
||||
/*
|
||||
/*
|
||||
* Reverse engineered from a dump of the floppy drive's ROM. I have no idea how
|
||||
* it works.
|
||||
*
|
||||
*
|
||||
* LF8BA:
|
||||
* clra
|
||||
* staa X00B0
|
||||
@@ -100,45 +100,43 @@ static uint16_t checksum(const Bytes& bytes)
|
||||
class Fb100Decoder : public Decoder
|
||||
{
|
||||
public:
|
||||
Fb100Decoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
Fb100Decoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(SECTOR_ID_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(SECTOR_ID_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
auto rawbits = readRawBits(FB100_RECORD_SIZE*16);
|
||||
{
|
||||
auto rawbits = readRawBits(FB100_RECORD_SIZE * 16);
|
||||
|
||||
const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE);
|
||||
ByteReader br(bytes);
|
||||
br.seek(1);
|
||||
const Bytes id = br.read(FB100_ID_SIZE);
|
||||
uint16_t wantIdCrc = br.read_be16();
|
||||
uint16_t gotIdCrc = checksum(id);
|
||||
const Bytes payload = br.read(FB100_PAYLOAD_SIZE);
|
||||
uint16_t wantPayloadCrc = br.read_be16();
|
||||
uint16_t gotPayloadCrc = checksum(payload);
|
||||
const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE);
|
||||
ByteReader br(bytes);
|
||||
br.seek(1);
|
||||
const Bytes id = br.read(FB100_ID_SIZE);
|
||||
uint16_t wantIdCrc = br.read_be16();
|
||||
uint16_t gotIdCrc = checksum(id);
|
||||
const Bytes payload = br.read(FB100_PAYLOAD_SIZE);
|
||||
uint16_t wantPayloadCrc = br.read_be16();
|
||||
uint16_t gotPayloadCrc = checksum(payload);
|
||||
|
||||
if (wantIdCrc != gotIdCrc)
|
||||
return;
|
||||
if (wantIdCrc != gotIdCrc)
|
||||
return;
|
||||
|
||||
uint8_t abssector = id[2];
|
||||
_sector->logicalTrack = abssector >> 1;
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalSector = abssector & 1;
|
||||
_sector->data.writer().append(id.slice(5, 12)).append(payload);
|
||||
uint8_t abssector = id[2];
|
||||
_sector->logicalTrack = abssector >> 1;
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalSector = abssector & 1;
|
||||
_sector->data.writer().append(id.slice(5, 12)).append(payload);
|
||||
|
||||
_sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->status = (wantPayloadCrc == gotPayloadCrc)
|
||||
? Sector::OK
|
||||
: Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createFb100Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new Fb100Decoder(config));
|
||||
return std::unique_ptr<Decoder>(new Fb100Decoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -112,10 +112,11 @@ public:
|
||||
const Image& image) override
|
||||
{
|
||||
IbmEncoderProto::TrackdataProto trackdata;
|
||||
getEncoderTrackData(trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
|
||||
getEncoderTrackData(
|
||||
trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
|
||||
|
||||
auto trackLayout =
|
||||
Layout::getLayoutOfTrack(trackInfo->logicalTrack, trackInfo->logicalSide);
|
||||
auto trackLayout = Layout::getLayoutOfTrack(
|
||||
trackInfo->logicalTrack, trackInfo->logicalSide);
|
||||
|
||||
auto writeBytes = [&](const Bytes& bytes)
|
||||
{
|
||||
@@ -257,7 +258,7 @@ public:
|
||||
}
|
||||
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
error("track data overrun");
|
||||
while (_cursor < _bits.size())
|
||||
writeFillerRawBytes(1, gapFill);
|
||||
|
||||
|
||||
@@ -12,22 +12,25 @@
|
||||
|
||||
const FluxPattern SECTOR_RECORD_PATTERN(24, MAC_SECTOR_RECORD);
|
||||
const FluxPattern DATA_RECORD_PATTERN(24, MAC_DATA_RECORD);
|
||||
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
|
||||
const FluxMatchers ANY_RECORD_PATTERN(
|
||||
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
|
||||
|
||||
static int decode_data_gcr(uint8_t gcr)
|
||||
{
|
||||
switch (gcr)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: \
|
||||
return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
|
||||
* and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
|
||||
/* This is extremely inspired by the MESS implementation, written by Nathan
|
||||
* Woods and R. Belmont:
|
||||
* https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
|
||||
*/
|
||||
static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status)
|
||||
{
|
||||
@@ -41,7 +44,7 @@ static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status)
|
||||
uint8_t b2[LOOKUP_LEN + 1];
|
||||
uint8_t b3[LOOKUP_LEN + 1];
|
||||
|
||||
for (int i=0; i<=LOOKUP_LEN; i++)
|
||||
for (int i = 0; i <= LOOKUP_LEN; i++)
|
||||
{
|
||||
uint8_t w4 = br.read_8();
|
||||
uint8_t w1 = br.read_8();
|
||||
@@ -125,67 +128,68 @@ uint8_t decode_side(uint8_t side)
|
||||
class MacintoshDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
MacintoshDecoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
MacintoshDecoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
if (readRaw24() != MAC_SECTOR_RECORD)
|
||||
return;
|
||||
{
|
||||
if (readRaw24() != MAC_SECTOR_RECORD)
|
||||
return;
|
||||
|
||||
/* Read header. */
|
||||
/* Read header. */
|
||||
|
||||
auto header = toBytes(readRawBits(7*8)).slice(0, 7);
|
||||
|
||||
uint8_t encodedTrack = decode_data_gcr(header[0]);
|
||||
if (encodedTrack != (_sector->physicalTrack & 0x3f))
|
||||
return;
|
||||
|
||||
uint8_t encodedSector = decode_data_gcr(header[1]);
|
||||
uint8_t encodedSide = decode_data_gcr(header[2]);
|
||||
uint8_t formatByte = decode_data_gcr(header[3]);
|
||||
uint8_t wantedsum = decode_data_gcr(header[4]);
|
||||
auto header = toBytes(readRawBits(7 * 8)).slice(0, 7);
|
||||
|
||||
if (encodedSector > 11)
|
||||
return;
|
||||
uint8_t encodedTrack = decode_data_gcr(header[0]);
|
||||
if (encodedTrack != (_sector->physicalTrack & 0x3f))
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = decode_side(encodedSide);
|
||||
_sector->logicalSector = encodedSector;
|
||||
uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
|
||||
if (wantedsum == gotsum)
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
uint8_t encodedSector = decode_data_gcr(header[1]);
|
||||
uint8_t encodedSide = decode_data_gcr(header[2]);
|
||||
uint8_t formatByte = decode_data_gcr(header[3]);
|
||||
uint8_t wantedsum = decode_data_gcr(header[4]);
|
||||
|
||||
if (encodedSector > 11)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = decode_side(encodedSide);
|
||||
_sector->logicalSector = encodedSector;
|
||||
uint8_t gotsum =
|
||||
(encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
|
||||
if (wantedsum == gotsum)
|
||||
_sector->status =
|
||||
Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
if (readRaw24() != MAC_DATA_RECORD)
|
||||
return;
|
||||
{
|
||||
if (readRaw24() != MAC_DATA_RECORD)
|
||||
return;
|
||||
|
||||
/* Read data. */
|
||||
/* Read data. */
|
||||
|
||||
readRawBits(8); /* skip spare byte */
|
||||
auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH*8))
|
||||
.slice(0, MAC_ENCODED_SECTOR_LENGTH);
|
||||
readRawBits(8); /* skip spare byte */
|
||||
auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH * 8))
|
||||
.slice(0, MAC_ENCODED_SECTOR_LENGTH);
|
||||
|
||||
for (unsigned i=0; i<inputbuffer.size(); i++)
|
||||
inputbuffer[i] = decode_data_gcr(inputbuffer[i]);
|
||||
|
||||
_sector->status = Sector::BAD_CHECKSUM;
|
||||
Bytes userData = decode_crazy_data(inputbuffer, _sector->status);
|
||||
_sector->data.clear();
|
||||
_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12));
|
||||
}
|
||||
for (unsigned i = 0; i < inputbuffer.size(); i++)
|
||||
inputbuffer[i] = decode_data_gcr(inputbuffer[i]);
|
||||
|
||||
_sector->status = Sector::BAD_CHECKSUM;
|
||||
Bytes userData = decode_crazy_data(inputbuffer, _sector->status);
|
||||
_sector->data.clear();
|
||||
_sector->data.writer()
|
||||
.append(userData.slice(12, 512))
|
||||
.append(userData.slice(0, 12));
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createMacintoshDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new MacintoshDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new MacintoshDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ static void write_sector(std::vector<bool>& bits,
|
||||
const std::shared_ptr<const Sector>& sector)
|
||||
{
|
||||
if ((sector->data.size() != 512) && (sector->data.size() != 524))
|
||||
Error() << "unsupported sector size --- you must pick 512 or 524";
|
||||
error("unsupported sector size --- you must pick 512 or 524");
|
||||
|
||||
write_bits(bits, cursor, 0xff, 1 * 8); /* pad byte */
|
||||
for (int i = 0; i < 7; i++)
|
||||
@@ -239,13 +239,12 @@ public:
|
||||
write_sector(bits, cursor, sector);
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format(
|
||||
"track data overrun by {} bits", cursor - bits.size());
|
||||
error("track data overrun by {} bits", cursor - bits.size());
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits,
|
||||
calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6));
|
||||
fluxmap->appendBits(
|
||||
bits, calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6));
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
@@ -253,8 +252,7 @@ private:
|
||||
const MacintoshEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> createMacintoshEncoder(
|
||||
const EncoderProto& config)
|
||||
std::unique_ptr<Encoder> createMacintoshEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Encoder>(new MacintoshEncoder(config));
|
||||
}
|
||||
|
||||
@@ -20,17 +20,20 @@ static const FluxPattern SECTOR_SYNC_PATTERN(64, 0xAAAAAAAAAAAA5555LL);
|
||||
static const FluxPattern SECTOR_ADVANCE_PATTERN(64, 0xAAAAAAAAAAAAAAAALL);
|
||||
|
||||
/* Standard Micropolis checksum. Adds all bytes, with carry. */
|
||||
uint8_t micropolisChecksum(const Bytes& bytes) {
|
||||
ByteReader br(bytes);
|
||||
uint16_t sum = 0;
|
||||
while (!br.eof()) {
|
||||
if (sum > 0xFF) {
|
||||
sum -= 0x100 - 1;
|
||||
}
|
||||
sum += br.read_8();
|
||||
}
|
||||
/* The last carry is ignored */
|
||||
return sum & 0xFF;
|
||||
uint8_t micropolisChecksum(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
uint16_t sum = 0;
|
||||
while (!br.eof())
|
||||
{
|
||||
if (sum > 0xFF)
|
||||
{
|
||||
sum -= 0x100 - 1;
|
||||
}
|
||||
sum += br.read_8();
|
||||
}
|
||||
/* The last carry is ignored */
|
||||
return sum & 0xFF;
|
||||
}
|
||||
|
||||
/* Vector MZOS does not use the standard Micropolis checksum.
|
||||
@@ -41,145 +44,164 @@ uint8_t micropolisChecksum(const Bytes& bytes) {
|
||||
* Unlike the Micropolis checksum, this does not cover the 12-byte
|
||||
* header (track, sector, 10 OS-specific bytes.)
|
||||
*/
|
||||
uint8_t mzosChecksum(const Bytes& bytes) {
|
||||
ByteReader br(bytes);
|
||||
uint8_t checksum = 0;
|
||||
uint8_t databyte;
|
||||
uint8_t mzosChecksum(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
uint8_t checksum = 0;
|
||||
uint8_t databyte;
|
||||
|
||||
while (!br.eof()) {
|
||||
databyte = br.read_8();
|
||||
checksum ^= ((databyte << 1) | (databyte >> 7));
|
||||
}
|
||||
while (!br.eof())
|
||||
{
|
||||
databyte = br.read_8();
|
||||
checksum ^= ((databyte << 1) | (databyte >> 7));
|
||||
}
|
||||
|
||||
return checksum;
|
||||
return checksum;
|
||||
}
|
||||
|
||||
class MicropolisDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
MicropolisDecoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.micropolis())
|
||||
{
|
||||
_checksumType = _config.checksum_type();
|
||||
}
|
||||
MicropolisDecoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.micropolis())
|
||||
{
|
||||
_checksumType = _config.checksum_type();
|
||||
}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
nanoseconds_t now = tell().ns();
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
nanoseconds_t now = tell().ns();
|
||||
|
||||
/* For all but the first sector, seek to the next sector pulse.
|
||||
* The first sector does not contain the sector pulse in the fluxmap.
|
||||
*/
|
||||
if (now != 0) {
|
||||
seekToIndexMark();
|
||||
now = tell().ns();
|
||||
}
|
||||
/* For all but the first sector, seek to the next sector pulse.
|
||||
* The first sector does not contain the sector pulse in the fluxmap.
|
||||
*/
|
||||
if (now != 0)
|
||||
{
|
||||
seekToIndexMark();
|
||||
now = tell().ns();
|
||||
}
|
||||
|
||||
/* Discard a possible partial sector at the end of the track.
|
||||
* This partial sector could be mistaken for a conflicted sector, if
|
||||
* whatever data read happens to match the checksum of 0, which is
|
||||
* rare, but has been observed on some disks.
|
||||
*/
|
||||
if (now > (getFluxmapDuration() - 12.5e6)) {
|
||||
seekToIndexMark();
|
||||
return 0;
|
||||
}
|
||||
/* Discard a possible partial sector at the end of the track.
|
||||
* This partial sector could be mistaken for a conflicted sector, if
|
||||
* whatever data read happens to match the checksum of 0, which is
|
||||
* rare, but has been observed on some disks.
|
||||
*/
|
||||
if (now > (getFluxmapDuration() - 12.5e6))
|
||||
{
|
||||
seekToIndexMark();
|
||||
return 0;
|
||||
}
|
||||
|
||||
nanoseconds_t clock = seekToPattern(SECTOR_SYNC_PATTERN);
|
||||
nanoseconds_t clock = seekToPattern(SECTOR_SYNC_PATTERN);
|
||||
|
||||
auto syncDelta = tell().ns() - now;
|
||||
/* Due to the weak nature of the Micropolis SYNC patern,
|
||||
* it's possible to detect a false SYNC during the gap
|
||||
* between the sector pulse and the write gate. If the SYNC
|
||||
* is detected less than 100uS after the sector pulse, search
|
||||
* for another valid SYNC.
|
||||
*
|
||||
* Reference: Vector Micropolis Disk Controller Board Technical
|
||||
* Information Manual, pp. 1-16.
|
||||
*/
|
||||
if ((syncDelta > 0) && (syncDelta < 100e3)) {
|
||||
seekToPattern(SECTOR_ADVANCE_PATTERN);
|
||||
clock = seekToPattern(SECTOR_SYNC_PATTERN);
|
||||
}
|
||||
auto syncDelta = tell().ns() - now;
|
||||
/* Due to the weak nature of the Micropolis SYNC patern,
|
||||
* it's possible to detect a false SYNC during the gap
|
||||
* between the sector pulse and the write gate. If the SYNC
|
||||
* is detected less than 100uS after the sector pulse, search
|
||||
* for another valid SYNC.
|
||||
*
|
||||
* Reference: Vector Micropolis Disk Controller Board Technical
|
||||
* Information Manual, pp. 1-16.
|
||||
*/
|
||||
if ((syncDelta > 0) && (syncDelta < 100e3))
|
||||
{
|
||||
seekToPattern(SECTOR_ADVANCE_PATTERN);
|
||||
clock = seekToPattern(SECTOR_SYNC_PATTERN);
|
||||
}
|
||||
|
||||
_sector->headerStartTime = tell().ns();
|
||||
_sector->headerStartTime = tell().ns();
|
||||
|
||||
/* seekToPattern() can skip past the index hole, if this happens
|
||||
* too close to the end of the Fluxmap, discard the sector.
|
||||
*/
|
||||
if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6)) {
|
||||
return 0;
|
||||
}
|
||||
/* seekToPattern() can skip past the index hole, if this happens
|
||||
* too close to the end of the Fluxmap, discard the sector.
|
||||
*/
|
||||
if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return clock;
|
||||
}
|
||||
return clock;
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
readRawBits(48);
|
||||
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
|
||||
ByteReader br(bytes);
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
readRawBits(48);
|
||||
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE * 16);
|
||||
auto bytes =
|
||||
decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
|
||||
ByteReader br(bytes);
|
||||
|
||||
int syncByte = br.read_8(); /* sync */
|
||||
if (syncByte != 0xFF)
|
||||
return;
|
||||
int syncByte = br.read_8(); /* sync */
|
||||
if (syncByte != 0xFF)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = br.read_8();
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = br.read_8();
|
||||
if (_sector->logicalSector > 15)
|
||||
return;
|
||||
if (_sector->logicalTrack > 76)
|
||||
return;
|
||||
if (_sector->logicalTrack != _sector->physicalTrack)
|
||||
return;
|
||||
_sector->logicalTrack = br.read_8();
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = br.read_8();
|
||||
if (_sector->logicalSector > 15)
|
||||
return;
|
||||
if (_sector->logicalTrack > 76)
|
||||
return;
|
||||
if (_sector->logicalTrack != _sector->physicalTrack)
|
||||
return;
|
||||
|
||||
br.read(10); /* OS data or padding */
|
||||
auto data = br.read(MICROPOLIS_PAYLOAD_SIZE);
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
br.read(10); /* OS data or padding */
|
||||
auto data = br.read(MICROPOLIS_PAYLOAD_SIZE);
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
|
||||
/* If not specified, automatically determine the checksum type.
|
||||
* Once the checksum type is determined, it will be used for the
|
||||
* entire disk.
|
||||
*/
|
||||
if (_checksumType == MicropolisDecoderProto::AUTO) {
|
||||
/* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS checksums */
|
||||
if (wantChecksum == micropolisChecksum(bytes.slice(1, 2+266))) {
|
||||
_checksumType = MicropolisDecoderProto::MICROPOLIS;
|
||||
} else if (wantChecksum == mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE))) {
|
||||
_checksumType = MicropolisDecoderProto::MZOS;
|
||||
std::cout << "Note: MZOS checksum detected." << std::endl;
|
||||
}
|
||||
}
|
||||
/* If not specified, automatically determine the checksum type.
|
||||
* Once the checksum type is determined, it will be used for the
|
||||
* entire disk.
|
||||
*/
|
||||
if (_checksumType == MicropolisDecoderProto::AUTO)
|
||||
{
|
||||
/* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS
|
||||
* checksums */
|
||||
if (wantChecksum == micropolisChecksum(bytes.slice(1, 2 + 266)))
|
||||
{
|
||||
_checksumType = MicropolisDecoderProto::MICROPOLIS;
|
||||
}
|
||||
else if (wantChecksum ==
|
||||
mzosChecksum(bytes.slice(
|
||||
MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE)))
|
||||
{
|
||||
_checksumType = MicropolisDecoderProto::MZOS;
|
||||
std::cout << "Note: MZOS checksum detected." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t gotChecksum;
|
||||
uint8_t gotChecksum;
|
||||
|
||||
if (_checksumType == MicropolisDecoderProto::MZOS) {
|
||||
gotChecksum = mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE));
|
||||
} else {
|
||||
gotChecksum = micropolisChecksum(bytes.slice(1, 2+266));
|
||||
}
|
||||
if (_checksumType == MicropolisDecoderProto::MZOS)
|
||||
{
|
||||
gotChecksum = mzosChecksum(
|
||||
bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE));
|
||||
}
|
||||
else
|
||||
{
|
||||
gotChecksum = micropolisChecksum(bytes.slice(1, 2 + 266));
|
||||
}
|
||||
|
||||
br.read(5); /* 4 byte ECC and ECC-present flag */
|
||||
br.read(5); /* 4 byte ECC and ECC-present flag */
|
||||
|
||||
if (_config.sector_output_size() == MICROPOLIS_PAYLOAD_SIZE)
|
||||
_sector->data = data;
|
||||
else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
|
||||
_sector->data = bytes;
|
||||
else
|
||||
Error() << "Sector output size may only be 256 or 275";
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
if (_config.sector_output_size() == MICROPOLIS_PAYLOAD_SIZE)
|
||||
_sector->data = data;
|
||||
else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
|
||||
_sector->data = bytes;
|
||||
else
|
||||
error("Sector output size may only be 256 or 275");
|
||||
_sector->status =
|
||||
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
private:
|
||||
const MicropolisDecoderProto& _config;
|
||||
MicropolisDecoderProto_ChecksumType _checksumType; /* -1 = auto, 1 = Micropolis, 2=MZOS */
|
||||
const MicropolisDecoderProto& _config;
|
||||
MicropolisDecoderProto_ChecksumType
|
||||
_checksumType; /* -1 = auto, 1 = Micropolis, 2=MZOS */
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createMicropolisDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new MicropolisDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new MicropolisDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ static void write_sector(std::vector<bool>& bits,
|
||||
{
|
||||
if ((sector->data.size() != 256) &&
|
||||
(sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE))
|
||||
Error() << "unsupported sector size --- you must pick 256 or 275";
|
||||
error("unsupported sector size --- you must pick 256 or 275");
|
||||
|
||||
int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35;
|
||||
auto fullSector = std::make_shared<std::vector<uint8_t>>();
|
||||
@@ -24,8 +24,9 @@ static void write_sector(std::vector<bool>& bits,
|
||||
if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
|
||||
{
|
||||
if (sector->data[0] != 0xFF)
|
||||
Error() << "275 byte sector doesn't start with sync byte 0xFF. "
|
||||
"Corrupted sector";
|
||||
error(
|
||||
"275 byte sector doesn't start with sync byte 0xFF. "
|
||||
"Corrupted sector");
|
||||
uint8_t wantChecksum = sector->data[1 + 2 + 266];
|
||||
uint8_t gotChecksum =
|
||||
micropolisChecksum(sector->data.slice(1, 2 + 266));
|
||||
@@ -57,7 +58,7 @@ static void write_sector(std::vector<bool>& bits,
|
||||
fullSector->push_back(0);
|
||||
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length";
|
||||
error("sector mismatched length");
|
||||
bool lastBit = false;
|
||||
encodeMfm(bits, cursor, fullSector, lastBit);
|
||||
/* filler */
|
||||
@@ -91,12 +92,11 @@ public:
|
||||
write_sector(bits, cursor, sectorData);
|
||||
|
||||
if (cursor != bits.size())
|
||||
Error() << "track data mismatched length";
|
||||
error("track data mismatched length");
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits,
|
||||
calculatePhysicalClockPeriod(
|
||||
_config.clock_period_us() * 1e3,
|
||||
calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3,
|
||||
_config.rotational_period_ms() * 1e6));
|
||||
return fluxmap;
|
||||
}
|
||||
@@ -105,8 +105,7 @@ private:
|
||||
const MicropolisEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> createMicropolisEncoder(
|
||||
const EncoderProto& config)
|
||||
std::unique_ptr<Encoder> createMicropolisEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Encoder>(new MicropolisEncoder(config));
|
||||
}
|
||||
|
||||
@@ -26,52 +26,51 @@ const FluxPattern ID_PATTERN(32, 0xaaaaffaf);
|
||||
class MxDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
MxDecoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
MxDecoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
void beginTrack() override
|
||||
{
|
||||
_clock = _sector->clock = seekToPattern(ID_PATTERN);
|
||||
_currentSector = 0;
|
||||
}
|
||||
{
|
||||
_clock = _sector->clock = seekToPattern(ID_PATTERN);
|
||||
_currentSector = 0;
|
||||
}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
if (_currentSector == 11)
|
||||
{
|
||||
/* That was the last sector on the disk. */
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return _clock;
|
||||
}
|
||||
{
|
||||
if (_currentSector == 11)
|
||||
{
|
||||
/* That was the last sector on the disk. */
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return _clock;
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
/* Skip the ID pattern and track word, which is only present on the
|
||||
* first sector. We don't trust the track word because some driver
|
||||
* don't write it correctly. */
|
||||
{
|
||||
/* Skip the ID pattern and track word, which is only present on the
|
||||
* first sector. We don't trust the track word because some driver
|
||||
* don't write it correctly. */
|
||||
|
||||
if (_currentSector == 0)
|
||||
readRawBits(64);
|
||||
if (_currentSector == 0)
|
||||
readRawBits(64);
|
||||
|
||||
auto bits = readRawBits((SECTOR_SIZE+2)*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2);
|
||||
auto bits = readRawBits((SECTOR_SIZE + 2) * 16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE + 2);
|
||||
|
||||
uint16_t gotChecksum = 0;
|
||||
ByteReader br(bytes);
|
||||
for (int i=0; i<(SECTOR_SIZE/2); i++)
|
||||
gotChecksum += br.read_be16();
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
uint16_t gotChecksum = 0;
|
||||
ByteReader br(bytes);
|
||||
for (int i = 0; i < (SECTOR_SIZE / 2); i++)
|
||||
gotChecksum += br.read_be16();
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _currentSector;
|
||||
_sector->data = bytes.slice(0, SECTOR_SIZE).swab();
|
||||
_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
_currentSector++;
|
||||
}
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _currentSector;
|
||||
_sector->data = bytes.slice(0, SECTOR_SIZE).swab();
|
||||
_sector->status =
|
||||
(gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
_currentSector++;
|
||||
}
|
||||
|
||||
private:
|
||||
nanoseconds_t _clock;
|
||||
@@ -80,7 +79,5 @@ private:
|
||||
|
||||
std::unique_ptr<Decoder> createMxDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new MxDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new MxDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "fmt/format.h"
|
||||
|
||||
#define MFM_ID 0xaaaaaaaaaaaa5545LL
|
||||
#define FM_ID 0xaaaaaaaaaaaaffefLL
|
||||
#define FM_ID 0xaaaaaaaaaaaaffefLL
|
||||
/*
|
||||
* MFM sectors have 32 bytes of 00's followed by two sync characters,
|
||||
* specified in the North Star MDS manual as 0xFBFB.
|
||||
@@ -44,133 +44,143 @@ static const FluxPattern MFM_PATTERN(64, MFM_ID);
|
||||
*/
|
||||
static const FluxPattern FM_PATTERN(64, FM_ID);
|
||||
|
||||
const FluxMatchers ANY_SECTOR_PATTERN(
|
||||
{
|
||||
&MFM_PATTERN,
|
||||
&FM_PATTERN,
|
||||
}
|
||||
);
|
||||
const FluxMatchers ANY_SECTOR_PATTERN({
|
||||
&MFM_PATTERN,
|
||||
&FM_PATTERN,
|
||||
});
|
||||
|
||||
/* Checksum is initially 0.
|
||||
* For each data byte, XOR with the current checksum.
|
||||
* Rotate checksum left, carrying bit 7 to bit 0.
|
||||
*/
|
||||
uint8_t northstarChecksum(const Bytes& bytes) {
|
||||
ByteReader br(bytes);
|
||||
uint8_t checksum = 0;
|
||||
uint8_t northstarChecksum(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
uint8_t checksum = 0;
|
||||
|
||||
while (!br.eof()) {
|
||||
checksum ^= br.read_8();
|
||||
checksum = ((checksum << 1) | ((checksum >> 7)));
|
||||
}
|
||||
while (!br.eof())
|
||||
{
|
||||
checksum ^= br.read_8();
|
||||
checksum = ((checksum << 1) | ((checksum >> 7)));
|
||||
}
|
||||
|
||||
return checksum;
|
||||
return checksum;
|
||||
}
|
||||
|
||||
class NorthstarDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
NorthstarDecoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.northstar())
|
||||
{}
|
||||
NorthstarDecoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.northstar())
|
||||
{
|
||||
}
|
||||
|
||||
/* Search for FM or MFM sector record */
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
nanoseconds_t now = tell().ns();
|
||||
/* Search for FM or MFM sector record */
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
nanoseconds_t now = tell().ns();
|
||||
|
||||
/* For all but the first sector, seek to the next sector pulse.
|
||||
* The first sector does not contain the sector pulse in the fluxmap.
|
||||
*/
|
||||
if (now != 0) {
|
||||
seekToIndexMark();
|
||||
now = tell().ns();
|
||||
}
|
||||
/* For all but the first sector, seek to the next sector pulse.
|
||||
* The first sector does not contain the sector pulse in the fluxmap.
|
||||
*/
|
||||
if (now != 0)
|
||||
{
|
||||
seekToIndexMark();
|
||||
now = tell().ns();
|
||||
}
|
||||
|
||||
/* Discard a possible partial sector at the end of the track.
|
||||
* This partial sector could be mistaken for a conflicted sector, if
|
||||
* whatever data read happens to match the checksum of 0, which is
|
||||
* rare, but has been observed on some disks.
|
||||
*/
|
||||
if (now > (getFluxmapDuration() - 21e6)) {
|
||||
seekToIndexMark();
|
||||
return 0;
|
||||
}
|
||||
/* Discard a possible partial sector at the end of the track.
|
||||
* This partial sector could be mistaken for a conflicted sector, if
|
||||
* whatever data read happens to match the checksum of 0, which is
|
||||
* rare, but has been observed on some disks.
|
||||
*/
|
||||
if (now > (getFluxmapDuration() - 21e6))
|
||||
{
|
||||
seekToIndexMark();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msSinceIndex = std::round(now / 1e6);
|
||||
int msSinceIndex = std::round(now / 1e6);
|
||||
|
||||
/* Note that the seekToPattern ignores the sector pulses, so if
|
||||
* a sector is not found for some reason, the seek will advance
|
||||
* past one or more sector pulses. For this reason, calculate
|
||||
* _hardSectorId after the sector header is found.
|
||||
*/
|
||||
nanoseconds_t clock = seekToPattern(ANY_SECTOR_PATTERN);
|
||||
_sector->headerStartTime = tell().ns();
|
||||
/* Note that the seekToPattern ignores the sector pulses, so if
|
||||
* a sector is not found for some reason, the seek will advance
|
||||
* past one or more sector pulses. For this reason, calculate
|
||||
* _hardSectorId after the sector header is found.
|
||||
*/
|
||||
nanoseconds_t clock = seekToPattern(ANY_SECTOR_PATTERN);
|
||||
_sector->headerStartTime = tell().ns();
|
||||
|
||||
/* Discard a possible partial sector. */
|
||||
if (_sector->headerStartTime > (getFluxmapDuration() - 21e6)) {
|
||||
return 0;
|
||||
}
|
||||
/* Discard a possible partial sector. */
|
||||
if (_sector->headerStartTime > (getFluxmapDuration() - 21e6))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sectorFoundTimeRaw = std::round(_sector->headerStartTime / 1e6);
|
||||
int sectorFoundTime;
|
||||
int sectorFoundTimeRaw = std::round(_sector->headerStartTime / 1e6);
|
||||
int sectorFoundTime;
|
||||
|
||||
/* Round time to the nearest 20ms */
|
||||
if ((sectorFoundTimeRaw % 20) < 10) {
|
||||
sectorFoundTime = (sectorFoundTimeRaw / 20) * 20;
|
||||
}
|
||||
else {
|
||||
sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20;
|
||||
}
|
||||
/* Round time to the nearest 20ms */
|
||||
if ((sectorFoundTimeRaw % 20) < 10)
|
||||
{
|
||||
sectorFoundTime = (sectorFoundTimeRaw / 20) * 20;
|
||||
}
|
||||
else
|
||||
{
|
||||
sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20;
|
||||
}
|
||||
|
||||
/* Calculate the sector ID based on time since the index */
|
||||
_hardSectorId = (sectorFoundTime / 20) % 10;
|
||||
/* Calculate the sector ID based on time since the index */
|
||||
_hardSectorId = (sectorFoundTime / 20) % 10;
|
||||
|
||||
return clock;
|
||||
}
|
||||
return clock;
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
uint64_t id = toBytes(readRawBits(64)).reader().read_be64();
|
||||
unsigned recordSize, payloadSize, headerSize;
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
uint64_t id = toBytes(readRawBits(64)).reader().read_be64();
|
||||
unsigned recordSize, payloadSize, headerSize;
|
||||
|
||||
if (id == MFM_ID) {
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_DD;
|
||||
}
|
||||
else {
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_SD;
|
||||
}
|
||||
if (id == MFM_ID)
|
||||
{
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_DD;
|
||||
}
|
||||
else
|
||||
{
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_SD;
|
||||
}
|
||||
|
||||
auto rawbits = readRawBits(recordSize * 16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
|
||||
ByteReader br(bytes);
|
||||
auto rawbits = readRawBits(recordSize * 16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
|
||||
ByteReader br(bytes);
|
||||
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _hardSectorId;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _hardSectorId;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
|
||||
if (headerSize == NORTHSTAR_HEADER_SIZE_DD) {
|
||||
br.read_8(); /* MFM second Sync char, usually 0xFB */
|
||||
}
|
||||
if (headerSize == NORTHSTAR_HEADER_SIZE_DD)
|
||||
{
|
||||
br.read_8(); /* MFM second Sync char, usually 0xFB */
|
||||
}
|
||||
|
||||
_sector->data = br.read(payloadSize);
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize - 1, payloadSize));
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->data = br.read(payloadSize);
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
uint8_t gotChecksum =
|
||||
northstarChecksum(bytes.slice(headerSize - 1, payloadSize));
|
||||
_sector->status =
|
||||
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
private:
|
||||
const NorthstarDecoderProto& _config;
|
||||
uint8_t _hardSectorId;
|
||||
const NorthstarDecoderProto& _config;
|
||||
uint8_t _hardSectorId;
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createNorthstarDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new NorthstarDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new NorthstarDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ static void write_sector(std::vector<bool>& bits,
|
||||
doubleDensity = true;
|
||||
break;
|
||||
default:
|
||||
Error() << "unsupported sector size --- you must pick 256 or 512";
|
||||
error("unsupported sector size --- you must pick 256 or 512");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -96,9 +96,10 @@ static void write_sector(std::vector<bool>& bits,
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length (" << sector->data.size()
|
||||
<< ") expected: " << fullSector->size() << " got "
|
||||
<< fullSectorSize;
|
||||
error("sector mismatched length ({}); expected {}, got {}",
|
||||
sector->data.size(),
|
||||
fullSector->size(),
|
||||
fullSectorSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -148,7 +149,7 @@ public:
|
||||
write_sector(bits, cursor, sectorData);
|
||||
|
||||
if (cursor > bits.size())
|
||||
Error() << "track data overrun";
|
||||
error("track data overrun");
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits,
|
||||
@@ -161,8 +162,7 @@ private:
|
||||
const NorthstarEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> createNorthstarEncoder(
|
||||
const EncoderProto& config)
|
||||
std::unique_ptr<Encoder> createNorthstarEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Encoder>(new NorthstarEncoder(config));
|
||||
}
|
||||
|
||||
@@ -29,28 +29,23 @@ static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL);
|
||||
class RolandD20Decoder : public Decoder
|
||||
{
|
||||
public:
|
||||
RolandD20Decoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
RolandD20Decoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(SECTOR_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(SECTOR_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
auto rawbits = readRawBits(256);
|
||||
const auto& bytes = decodeFmMfm(rawbits);
|
||||
fmt::print("{} ", _sector->clock);
|
||||
hexdump(std::cout, bytes);
|
||||
}
|
||||
{
|
||||
auto rawbits = readRawBits(256);
|
||||
const auto& bytes = decodeFmMfm(rawbits);
|
||||
fmt::print("{} ", _sector->clock);
|
||||
hexdump(std::cout, bytes);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new RolandD20Decoder(config));
|
||||
return std::unique_ptr<Decoder>(new RolandD20Decoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -38,61 +38,63 @@ const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244);
|
||||
const uint16_t DATA_ID = 0x550b;
|
||||
const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245);
|
||||
|
||||
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
|
||||
const FluxMatchers ANY_RECORD_PATTERN(
|
||||
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
|
||||
|
||||
class Tids990Decoder : public Decoder
|
||||
{
|
||||
public:
|
||||
Tids990Decoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
Tids990Decoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE);
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE * 16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE);
|
||||
|
||||
ByteReader br(bytes);
|
||||
if (br.read_be16() != SECTOR_ID)
|
||||
return;
|
||||
ByteReader br(bytes);
|
||||
if (br.read_be16() != SECTOR_ID)
|
||||
return;
|
||||
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3));
|
||||
uint16_t gotChecksum =
|
||||
crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE - 3));
|
||||
|
||||
_sector->logicalSide = br.read_8() >> 3;
|
||||
_sector->logicalTrack = br.read_8();
|
||||
br.read_8(); /* number of sectors per track */
|
||||
_sector->logicalSector = br.read_8();
|
||||
br.read_be16(); /* sector size */
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
_sector->logicalSide = br.read_8() >> 3;
|
||||
_sector->logicalTrack = br.read_8();
|
||||
br.read_8(); /* number of sectors per track */
|
||||
_sector->logicalSector = br.read_8();
|
||||
br.read_be16(); /* sector size */
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
|
||||
}
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status =
|
||||
Sector::DATA_MISSING; /* correct but unintuitive */
|
||||
}
|
||||
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE * 16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
|
||||
|
||||
ByteReader br(bytes);
|
||||
if (br.read_be16() != DATA_ID)
|
||||
return;
|
||||
ByteReader br(bytes);
|
||||
if (br.read_be16() != DATA_ID)
|
||||
return;
|
||||
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3));
|
||||
uint16_t gotChecksum =
|
||||
crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE - 3));
|
||||
|
||||
_sector->data = br.read(TIDS990_PAYLOAD_SIZE);
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->data = br.read(TIDS990_PAYLOAD_SIZE);
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
_sector->status =
|
||||
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createTids990Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new Tids990Decoder(config));
|
||||
return std::unique_ptr<Decoder>(new Tids990Decoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -127,14 +127,14 @@ public:
|
||||
}
|
||||
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
error("track data overrun");
|
||||
while (_cursor < _bits.size())
|
||||
writeBytes(1, 0x55);
|
||||
|
||||
auto fluxmap = std::make_unique<Fluxmap>();
|
||||
fluxmap->appendBits(_bits,
|
||||
calculatePhysicalClockPeriod(clockRateUs * 1e3,
|
||||
_config.rotational_period_ms() * 1e6));
|
||||
calculatePhysicalClockPeriod(
|
||||
clockRateUs * 1e3, _config.rotational_period_ms() * 1e6));
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
@@ -145,8 +145,7 @@ private:
|
||||
bool _lastBit;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> createTids990Encoder(
|
||||
const EncoderProto& config)
|
||||
std::unique_ptr<Encoder> createTids990Encoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Encoder>(new Tids990Encoder(config));
|
||||
}
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
|
||||
const FluxPattern SECTOR_RECORD_PATTERN(32, VICTOR9K_SECTOR_RECORD);
|
||||
const FluxPattern DATA_RECORD_PATTERN(32, VICTOR9K_DATA_RECORD);
|
||||
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
|
||||
const FluxMatchers ANY_RECORD_PATTERN(
|
||||
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
|
||||
|
||||
static int decode_data_gcr(uint8_t gcr)
|
||||
{
|
||||
switch (gcr)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: \
|
||||
return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -37,11 +39,11 @@ static Bytes decode(const std::vector<bool>& bits)
|
||||
while (ii != bits.end())
|
||||
{
|
||||
uint8_t inputfifo = 0;
|
||||
for (size_t i=0; i<5; i++)
|
||||
for (size_t i = 0; i < 5; i++)
|
||||
{
|
||||
if (ii == bits.end())
|
||||
break;
|
||||
inputfifo = (inputfifo<<1) | *ii++;
|
||||
inputfifo = (inputfifo << 1) | *ii++;
|
||||
}
|
||||
|
||||
uint8_t decoded = decode_data_gcr(inputfifo);
|
||||
@@ -55,63 +57,62 @@ static Bytes decode(const std::vector<bool>& bits)
|
||||
class Victor9kDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
Victor9kDecoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
Victor9kDecoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
{
|
||||
return seekToPattern(ANY_RECORD_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
/* Check the ID. */
|
||||
{
|
||||
/* Check the ID. */
|
||||
|
||||
if (readRaw32() != VICTOR9K_SECTOR_RECORD)
|
||||
return;
|
||||
if (readRaw32() != VICTOR9K_SECTOR_RECORD)
|
||||
return;
|
||||
|
||||
/* Read header. */
|
||||
/* Read header. */
|
||||
|
||||
auto bytes = decode(readRawBits(3*10)).slice(0, 3);
|
||||
auto bytes = decode(readRawBits(3 * 10)).slice(0, 3);
|
||||
|
||||
uint8_t rawTrack = bytes[0];
|
||||
_sector->logicalSector = bytes[1];
|
||||
uint8_t gotChecksum = bytes[2];
|
||||
uint8_t rawTrack = bytes[0];
|
||||
_sector->logicalSector = bytes[1];
|
||||
uint8_t gotChecksum = bytes[2];
|
||||
|
||||
_sector->logicalTrack = rawTrack & 0x7f;
|
||||
_sector->logicalSide = rawTrack >> 7;
|
||||
uint8_t wantChecksum = bytes[0] + bytes[1];
|
||||
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1))
|
||||
return;
|
||||
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
_sector->logicalTrack = rawTrack & 0x7f;
|
||||
_sector->logicalSide = rawTrack >> 7;
|
||||
uint8_t wantChecksum = bytes[0] + bytes[1];
|
||||
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) ||
|
||||
(_sector->logicalSide > 1))
|
||||
return;
|
||||
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status =
|
||||
Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
/* Check the ID. */
|
||||
{
|
||||
/* Check the ID. */
|
||||
|
||||
if (readRaw32() != VICTOR9K_DATA_RECORD)
|
||||
return;
|
||||
if (readRaw32() != VICTOR9K_DATA_RECORD)
|
||||
return;
|
||||
|
||||
/* Read data. */
|
||||
/* Read data. */
|
||||
|
||||
auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+4)*10))
|
||||
.slice(0, VICTOR9K_SECTOR_LENGTH+4);
|
||||
ByteReader br(bytes);
|
||||
auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH + 4) * 10))
|
||||
.slice(0, VICTOR9K_SECTOR_LENGTH + 4);
|
||||
ByteReader br(bytes);
|
||||
|
||||
_sector->data = br.read(VICTOR9K_SECTOR_LENGTH);
|
||||
uint16_t gotChecksum = sumBytes(_sector->data);
|
||||
uint16_t wantChecksum = br.read_le16();
|
||||
_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->data = br.read(VICTOR9K_SECTOR_LENGTH);
|
||||
uint16_t gotChecksum = sumBytes(_sector->data);
|
||||
uint16_t wantChecksum = br.read_le16();
|
||||
_sector->status =
|
||||
(gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createVictor9kDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new Victor9kDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new Victor9kDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -169,14 +169,15 @@ public:
|
||||
const Image& image) override
|
||||
{
|
||||
Victor9kEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
|
||||
getTrackFormat(
|
||||
trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
|
||||
|
||||
unsigned bitsPerRevolution = (trackdata.rotational_period_ms() * 1e3) /
|
||||
trackdata.clock_period_us();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
nanoseconds_t clockPeriod = calculatePhysicalClockPeriod(
|
||||
trackdata.clock_period_us() * 1e3,
|
||||
trackdata.rotational_period_ms() * 1e6);
|
||||
nanoseconds_t clockPeriod =
|
||||
calculatePhysicalClockPeriod(trackdata.clock_period_us() * 1e3,
|
||||
trackdata.rotational_period_ms() * 1e6);
|
||||
unsigned cursor = 0;
|
||||
|
||||
fillBitmapTo(bits,
|
||||
@@ -189,8 +190,7 @@ public:
|
||||
write_sector(bits, cursor, trackdata, *sector);
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format(
|
||||
"track data overrun by {} bits", cursor - bits.size());
|
||||
error("track data overrun by {} bits", cursor - bits.size());
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
@@ -202,8 +202,7 @@ private:
|
||||
const Victor9kEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<Encoder> createVictor9kEncoder(
|
||||
const EncoderProto& config)
|
||||
std::unique_ptr<Encoder> createVictor9kEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Encoder>(new Victor9kEncoder(config));
|
||||
}
|
||||
|
||||
@@ -16,42 +16,40 @@ static const FluxPattern SECTOR_START_PATTERN(16, 0xaaab);
|
||||
class ZilogMczDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
ZilogMczDecoder(const DecoderProto& config):
|
||||
Decoder(config)
|
||||
{}
|
||||
ZilogMczDecoder(const DecoderProto& config): Decoder(config) {}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
seekToIndexMark();
|
||||
return seekToPattern(SECTOR_START_PATTERN);
|
||||
}
|
||||
{
|
||||
seekToIndexMark();
|
||||
return seekToPattern(SECTOR_START_PATTERN);
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
readRawBits(14);
|
||||
{
|
||||
readRawBits(14);
|
||||
|
||||
auto rawbits = readRawBits(140*16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, 140);
|
||||
ByteReader br(bytes);
|
||||
auto rawbits = readRawBits(140 * 16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, 140);
|
||||
ByteReader br(bytes);
|
||||
|
||||
_sector->logicalSector = br.read_8() & 0x1f;
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalTrack = br.read_8() & 0x7f;
|
||||
if (_sector->logicalSector > 31)
|
||||
return;
|
||||
if (_sector->logicalTrack > 80)
|
||||
return;
|
||||
_sector->logicalSector = br.read_8() & 0x1f;
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalTrack = br.read_8() & 0x7f;
|
||||
if (_sector->logicalSector > 31)
|
||||
return;
|
||||
if (_sector->logicalTrack > 80)
|
||||
return;
|
||||
|
||||
_sector->data = br.read(132);
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134));
|
||||
_sector->data = br.read(132);
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134));
|
||||
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
_sector->status =
|
||||
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createZilogMczDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new ZilogMczDecoder(config));
|
||||
return std::unique_ptr<Decoder>(new ZilogMczDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fmt/format.h"
|
||||
#include "dep/agg/include/agg2d.h"
|
||||
#include "dep/stb/stb_image_write.h"
|
||||
#include "utils.h"
|
||||
@@ -13,30 +12,31 @@ Bitmap::Bitmap(const std::string filename, unsigned width, unsigned height):
|
||||
width(width),
|
||||
height(height),
|
||||
initialised(true)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
Agg2D& Bitmap::painter()
|
||||
{
|
||||
if (!_painter)
|
||||
{
|
||||
_bitmap.resize(width * height * 4, 255);
|
||||
_painter.reset(new Agg2D());
|
||||
_painter->attach(&_bitmap[0], width, height, width*4);
|
||||
}
|
||||
return *_painter;
|
||||
if (!_painter)
|
||||
{
|
||||
_bitmap.resize(width * height * 4, 255);
|
||||
_painter.reset(new Agg2D());
|
||||
_painter->attach(&_bitmap[0], width, height, width * 4);
|
||||
}
|
||||
return *_painter;
|
||||
}
|
||||
|
||||
void Bitmap::save()
|
||||
{
|
||||
if (endsWith(filename, ".png"))
|
||||
stbi_write_png(filename.c_str(), width, height, 4, &_bitmap[0], width*4);
|
||||
else if (endsWith(filename, ".bmp"))
|
||||
stbi_write_bmp(filename.c_str(), width, height, 4, &_bitmap[0]);
|
||||
else if (endsWith(filename, ".tga"))
|
||||
stbi_write_tga(filename.c_str(), width, height, 4, &_bitmap[0]);
|
||||
else if (endsWith(filename, ".jpg"))
|
||||
stbi_write_jpg(filename.c_str(), width, height, 4, &_bitmap[0], 80);
|
||||
else
|
||||
Error() << "don't know how to write that image format";
|
||||
if (endsWith(filename, ".png"))
|
||||
stbi_write_png(
|
||||
filename.c_str(), width, height, 4, &_bitmap[0], width * 4);
|
||||
else if (endsWith(filename, ".bmp"))
|
||||
stbi_write_bmp(filename.c_str(), width, height, 4, &_bitmap[0]);
|
||||
else if (endsWith(filename, ".tga"))
|
||||
stbi_write_tga(filename.c_str(), width, height, 4, &_bitmap[0]);
|
||||
else if (endsWith(filename, ".jpg"))
|
||||
stbi_write_jpg(filename.c_str(), width, height, 4, &_bitmap[0], 80);
|
||||
else
|
||||
error("don't know how to write that image format");
|
||||
}
|
||||
|
||||
|
||||
190
lib/bytes.cc
190
lib/bytes.cc
@@ -1,6 +1,5 @@
|
||||
#include "globals.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -9,69 +8,70 @@ static std::shared_ptr<std::vector<uint8_t>> createVector(unsigned size)
|
||||
return std::make_shared<std::vector<uint8_t>>(size);
|
||||
}
|
||||
|
||||
static std::shared_ptr<std::vector<uint8_t>> createVector(const uint8_t* ptr, unsigned size)
|
||||
static std::shared_ptr<std::vector<uint8_t>> createVector(
|
||||
const uint8_t* ptr, unsigned size)
|
||||
{
|
||||
auto vector = std::make_shared<std::vector<uint8_t>>(size);
|
||||
std::uninitialized_copy(ptr, ptr+size, vector->begin());
|
||||
std::uninitialized_copy(ptr, ptr + size, vector->begin());
|
||||
return vector;
|
||||
}
|
||||
|
||||
static std::shared_ptr<std::vector<uint8_t>> createVector(std::initializer_list<uint8_t> data)
|
||||
static std::shared_ptr<std::vector<uint8_t>> createVector(
|
||||
std::initializer_list<uint8_t> data)
|
||||
{
|
||||
auto vector = std::make_shared<std::vector<uint8_t>>(data.size());
|
||||
std::uninitialized_copy(data.begin(), data.end(), vector->begin());
|
||||
return vector;
|
||||
}
|
||||
|
||||
Bytes::Bytes():
|
||||
_data(createVector(0)),
|
||||
_low(0),
|
||||
_high(0)
|
||||
{}
|
||||
Bytes::Bytes(): _data(createVector(0)), _low(0), _high(0) {}
|
||||
|
||||
Bytes::Bytes(unsigned size):
|
||||
_data(createVector(size)),
|
||||
_low(0),
|
||||
_high(size)
|
||||
{}
|
||||
Bytes::Bytes(unsigned size): _data(createVector(size)), _low(0), _high(size) {}
|
||||
|
||||
Bytes::Bytes(const uint8_t* ptr, size_t len):
|
||||
_data(createVector(ptr, len)),
|
||||
_low(0),
|
||||
_high(len)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
Bytes::Bytes(const std::string& s):
|
||||
_data(createVector((const uint8_t*)&s[0], s.size())),
|
||||
_low(0),
|
||||
_high(s.size())
|
||||
{}
|
||||
_data(createVector((const uint8_t*)&s[0], s.size())),
|
||||
_low(0),
|
||||
_high(s.size())
|
||||
{
|
||||
}
|
||||
|
||||
Bytes::Bytes(const char* s):
|
||||
_data(createVector((const uint8_t*)s, strlen(s))),
|
||||
_low(0),
|
||||
_high(strlen(s))
|
||||
{}
|
||||
_data(createVector((const uint8_t*)s, strlen(s))),
|
||||
_low(0),
|
||||
_high(strlen(s))
|
||||
{
|
||||
}
|
||||
|
||||
Bytes::Bytes(std::initializer_list<uint8_t> data):
|
||||
_data(createVector(data)),
|
||||
_low(0),
|
||||
_high(data.size())
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
Bytes::Bytes(std::shared_ptr<std::vector<uint8_t>> data):
|
||||
_data(data),
|
||||
_low(0),
|
||||
_high(data->size())
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
Bytes::Bytes(std::shared_ptr<std::vector<uint8_t>> data, unsigned start, unsigned end):
|
||||
Bytes::Bytes(
|
||||
std::shared_ptr<std::vector<uint8_t>> data, unsigned start, unsigned end):
|
||||
_data(data),
|
||||
_low(start),
|
||||
_high(end)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
Bytes* Bytes::operator = (const Bytes& other)
|
||||
Bytes* Bytes::operator=(const Bytes& other)
|
||||
{
|
||||
_data = other._data;
|
||||
_low = other._low;
|
||||
@@ -102,7 +102,7 @@ void Bytes::adjustBounds(unsigned pos)
|
||||
checkWritable();
|
||||
if (pos >= _high)
|
||||
{
|
||||
_high = pos+1;
|
||||
_high = pos + 1;
|
||||
_data->resize(_high);
|
||||
}
|
||||
}
|
||||
@@ -119,14 +119,14 @@ Bytes& Bytes::resize(unsigned size)
|
||||
return *this;
|
||||
}
|
||||
|
||||
const uint8_t& Bytes::operator [] (unsigned pos) const
|
||||
const uint8_t& Bytes::operator[](unsigned pos) const
|
||||
{
|
||||
pos += _low;
|
||||
boundsCheck(pos);
|
||||
return (*_data)[pos];
|
||||
}
|
||||
|
||||
uint8_t& Bytes::operator [] (unsigned pos)
|
||||
uint8_t& Bytes::operator[](unsigned pos)
|
||||
{
|
||||
checkWritable();
|
||||
pos += _low;
|
||||
@@ -136,15 +136,15 @@ uint8_t& Bytes::operator [] (unsigned pos)
|
||||
|
||||
Bytes Bytes::readFromFile(const std::string& filename)
|
||||
{
|
||||
Bytes bytes;
|
||||
ByteWriter bw(bytes);
|
||||
Bytes bytes;
|
||||
ByteWriter bw(bytes);
|
||||
|
||||
std::ifstream f(filename);
|
||||
if (!f)
|
||||
Error() << fmt::format("cannot open '{}': {}", filename, strerror(errno));
|
||||
bw += f;
|
||||
std::ifstream f(filename);
|
||||
if (!f)
|
||||
error("cannot open '{}': {}", filename, strerror(errno));
|
||||
bw += f;
|
||||
|
||||
return bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Bytes Bytes::slice(unsigned start, unsigned len) const
|
||||
@@ -160,7 +160,8 @@ Bytes Bytes::slice(unsigned start, unsigned len) const
|
||||
{
|
||||
/* Can't share the buffer, as we need to zero-pad the end. */
|
||||
Bytes b(len);
|
||||
std::uninitialized_copy(_data->cbegin()+start, _data->cbegin()+_high, b._data->begin());
|
||||
std::uninitialized_copy(
|
||||
_data->cbegin() + start, _data->cbegin() + _high, b._data->begin());
|
||||
return b;
|
||||
}
|
||||
else
|
||||
@@ -173,42 +174,42 @@ Bytes Bytes::slice(unsigned start, unsigned len) const
|
||||
|
||||
Bytes Bytes::slice(unsigned start) const
|
||||
{
|
||||
int len = 0;
|
||||
if (start < size())
|
||||
len = size() - start;
|
||||
return slice(start, len);
|
||||
int len = 0;
|
||||
if (start < size())
|
||||
len = size() - start;
|
||||
return slice(start, len);
|
||||
}
|
||||
|
||||
std::vector<Bytes> Bytes::split(uint8_t separator) const
|
||||
{
|
||||
std::vector<Bytes> vector;
|
||||
std::vector<Bytes> vector;
|
||||
|
||||
int lastEnd = 0;
|
||||
for (int i=0; i<size(); i++)
|
||||
{
|
||||
if ((*this)[i] == separator)
|
||||
{
|
||||
vector.push_back(this->slice(lastEnd, i-lastEnd));
|
||||
lastEnd = i + 1;
|
||||
}
|
||||
}
|
||||
vector.push_back(this->slice(lastEnd));
|
||||
int lastEnd = 0;
|
||||
for (int i = 0; i < size(); i++)
|
||||
{
|
||||
if ((*this)[i] == separator)
|
||||
{
|
||||
vector.push_back(this->slice(lastEnd, i - lastEnd));
|
||||
lastEnd = i + 1;
|
||||
}
|
||||
}
|
||||
vector.push_back(this->slice(lastEnd));
|
||||
|
||||
return vector;
|
||||
return vector;
|
||||
}
|
||||
|
||||
std::vector<bool> Bytes::toBits() const
|
||||
{
|
||||
std::vector<bool> bits;
|
||||
for (uint8_t byte : *this)
|
||||
{
|
||||
for (int i=0; i<8; i++)
|
||||
{
|
||||
bits.push_back(byte & 0x80);
|
||||
byte <<= 1;
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
std::vector<bool> bits;
|
||||
for (uint8_t byte : *this)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
bits.push_back(byte & 0x80);
|
||||
byte <<= 1;
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
Bytes Bytes::reverseBits() const
|
||||
@@ -221,7 +222,7 @@ Bytes Bytes::reverseBits() const
|
||||
return output;
|
||||
}
|
||||
|
||||
Bytes Bytes::operator + (const Bytes& other)
|
||||
Bytes Bytes::operator+(const Bytes& other)
|
||||
{
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
@@ -230,7 +231,7 @@ Bytes Bytes::operator + (const Bytes& other)
|
||||
return output;
|
||||
}
|
||||
|
||||
Bytes Bytes::operator * (size_t count)
|
||||
Bytes Bytes::operator*(size_t count)
|
||||
{
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
@@ -239,15 +240,13 @@ Bytes Bytes::operator * (size_t count)
|
||||
return output;
|
||||
}
|
||||
|
||||
uint8_t toByte(
|
||||
std::vector<bool>::const_iterator start,
|
||||
uint8_t toByte(std::vector<bool>::const_iterator start,
|
||||
std::vector<bool>::const_iterator end)
|
||||
{
|
||||
return toBytes(start, end)[0];
|
||||
}
|
||||
|
||||
Bytes toBytes(
|
||||
std::vector<bool>::const_iterator start,
|
||||
Bytes toBytes(std::vector<bool>::const_iterator start,
|
||||
std::vector<bool>::const_iterator end)
|
||||
{
|
||||
Bytes bytes;
|
||||
@@ -283,7 +282,7 @@ Bytes Bytes::compress() const
|
||||
uLongf destsize = compressBound(size());
|
||||
Bytes dest(destsize);
|
||||
if (::compress(dest.begin(), &destsize, cbegin(), size()) != Z_OK)
|
||||
Error() << "error compressing data";
|
||||
error("error compressing data");
|
||||
dest.resize(destsize);
|
||||
return dest;
|
||||
}
|
||||
@@ -292,12 +291,12 @@ Bytes Bytes::decompress() const
|
||||
{
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
Bytes outputBuffer(1024*1024);
|
||||
Bytes outputBuffer(1024 * 1024);
|
||||
|
||||
z_stream stream = {};
|
||||
inflateInit(&stream);
|
||||
stream.avail_in = size();
|
||||
stream.next_in = (uint8_t*) cbegin();
|
||||
stream.next_in = (uint8_t*)cbegin();
|
||||
|
||||
int ret;
|
||||
do
|
||||
@@ -308,12 +307,11 @@ Bytes Bytes::decompress() const
|
||||
if (ret == Z_BUF_ERROR)
|
||||
ret = Z_OK;
|
||||
if ((ret != Z_OK) && (ret != Z_STREAM_END))
|
||||
Error() << fmt::format(
|
||||
"failed to decompress data: {}", stream.msg ? stream.msg : "(unknown error)");
|
||||
|
||||
error("failed to decompress data: {}",
|
||||
stream.msg ? stream.msg : "(unknown error)");
|
||||
|
||||
bw += outputBuffer.slice(0, outputBuffer.size() - stream.avail_out);
|
||||
}
|
||||
while (ret != Z_STREAM_END);
|
||||
} while (ret != Z_STREAM_END);
|
||||
inflateEnd(&stream);
|
||||
|
||||
return output;
|
||||
@@ -323,15 +321,15 @@ void Bytes::writeToFile(const std::string& filename) const
|
||||
{
|
||||
std::ofstream f(filename, std::ios::out | std::ios::binary);
|
||||
if (!f.is_open())
|
||||
Error() << fmt::format("cannot open output file '{}'", filename);
|
||||
error("cannot open output file '{}'", filename);
|
||||
|
||||
f.write((const char*) cbegin(), size());
|
||||
f.write((const char*)cbegin(), size());
|
||||
f.close();
|
||||
}
|
||||
|
||||
void Bytes::writeTo(std::ostream& stream) const
|
||||
{
|
||||
stream.write((const char*) cbegin(), size());
|
||||
stream.write((const char*)cbegin(), size());
|
||||
}
|
||||
|
||||
ByteReader Bytes::reader() const
|
||||
@@ -346,19 +344,19 @@ ByteWriter Bytes::writer()
|
||||
|
||||
uint64_t ByteReader::read_be48()
|
||||
{
|
||||
return ((uint64_t)read_be16() << 32) | read_be32();
|
||||
return ((uint64_t)read_be16() << 32) | read_be32();
|
||||
}
|
||||
|
||||
uint64_t ByteReader::read_be64()
|
||||
{
|
||||
return ((uint64_t)read_be32() << 32) | read_be32();
|
||||
return ((uint64_t)read_be32() << 32) | read_be32();
|
||||
}
|
||||
|
||||
ByteWriter& ByteWriter::operator +=(std::istream& stream)
|
||||
ByteWriter& ByteWriter::operator+=(std::istream& stream)
|
||||
{
|
||||
Bytes buffer(4096);
|
||||
|
||||
while (stream.read((char*) buffer.begin(), buffer.size()))
|
||||
while (stream.read((char*)buffer.begin(), buffer.size()))
|
||||
this->append(buffer);
|
||||
this->append(buffer.slice(0, stream.gcount()));
|
||||
return *this;
|
||||
@@ -366,18 +364,18 @@ ByteWriter& ByteWriter::operator +=(std::istream& stream)
|
||||
|
||||
void BitWriter::push(uint32_t bits, size_t size)
|
||||
{
|
||||
bits <<= 32-size;
|
||||
bits <<= 32 - size;
|
||||
|
||||
while (size-- != 0)
|
||||
{
|
||||
_fifo = (_fifo<<1) | (bits >> 31);
|
||||
_fifo = (_fifo << 1) | (bits >> 31);
|
||||
_bitcount++;
|
||||
bits <<= 1;
|
||||
if (_bitcount == 8)
|
||||
{
|
||||
_bw.write_8(_fifo);
|
||||
_bitcount = 0;
|
||||
_fifo = 0;
|
||||
_fifo = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,18 +391,18 @@ void BitWriter::flush()
|
||||
|
||||
bool BitReader::eof()
|
||||
{
|
||||
return (_bitcount == 0) && _br.eof();
|
||||
return (_bitcount == 0) && _br.eof();
|
||||
}
|
||||
|
||||
bool BitReader::get()
|
||||
{
|
||||
if (_bitcount == 0)
|
||||
_fifo = _br.read_8();
|
||||
if (_bitcount == 0)
|
||||
_fifo = _br.read_8();
|
||||
|
||||
bool bit = _fifo & 0x80;
|
||||
_fifo <<= 1;
|
||||
_bitcount = (_bitcount+1) & 7;
|
||||
return bit;
|
||||
bool bit = _fifo & 0x80;
|
||||
_fifo <<= 1;
|
||||
_bitcount = (_bitcount + 1) & 7;
|
||||
return bit;
|
||||
}
|
||||
|
||||
std::vector<bool> reverseBits(const std::vector<bool>& bits)
|
||||
|
||||
131
lib/crc.cc
131
lib/crc.cc
@@ -3,108 +3,107 @@
|
||||
#include "crc.h"
|
||||
|
||||
template <class T>
|
||||
T reflect(T bin, unsigned width = sizeof(T)*8)
|
||||
T reflect(T bin, unsigned width = sizeof(T) * 8)
|
||||
{
|
||||
T bout = 0;
|
||||
while (width--)
|
||||
{
|
||||
bout <<= 1;
|
||||
bout |= (bin & 1);
|
||||
bin >>= 1;
|
||||
}
|
||||
return bout;
|
||||
T bout = 0;
|
||||
while (width--)
|
||||
{
|
||||
bout <<= 1;
|
||||
bout |= (bin & 1);
|
||||
bin >>= 1;
|
||||
}
|
||||
return bout;
|
||||
}
|
||||
|
||||
uint64_t generic_crc(const struct crcspec& spec, const Bytes& bytes)
|
||||
{
|
||||
uint64_t crc = spec.init;
|
||||
uint64_t top = 1LL << (spec.width-1);
|
||||
uint64_t mask = (top<<1) - 1;
|
||||
uint64_t crc = spec.init;
|
||||
uint64_t top = 1LL << (spec.width - 1);
|
||||
uint64_t mask = (top << 1) - 1;
|
||||
|
||||
for (uint8_t b : bytes)
|
||||
{
|
||||
if (spec.refin)
|
||||
b = reflect(b);
|
||||
for (uint8_t b : bytes)
|
||||
{
|
||||
if (spec.refin)
|
||||
b = reflect(b);
|
||||
|
||||
for (uint8_t i = 0x80; i != 0; i >>= 1)
|
||||
{
|
||||
uint64_t bit = crc & top;
|
||||
crc <<= 1;
|
||||
if (b & i)
|
||||
bit ^= top;
|
||||
if (bit)
|
||||
crc ^= spec.poly;
|
||||
}
|
||||
}
|
||||
for (uint8_t i = 0x80; i != 0; i >>= 1)
|
||||
{
|
||||
uint64_t bit = crc & top;
|
||||
crc <<= 1;
|
||||
if (b & i)
|
||||
bit ^= top;
|
||||
if (bit)
|
||||
crc ^= spec.poly;
|
||||
}
|
||||
}
|
||||
|
||||
if (spec.refout)
|
||||
crc = reflect(crc, spec.width);
|
||||
crc ^= spec.xorout;
|
||||
return crc & mask;
|
||||
if (spec.refout)
|
||||
crc = reflect(crc, spec.width);
|
||||
crc ^= spec.xorout;
|
||||
return crc & mask;
|
||||
}
|
||||
|
||||
uint16_t sumBytes(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
ByteReader br(bytes);
|
||||
|
||||
uint16_t i = 0;
|
||||
while (!br.eof())
|
||||
i += br.read_8();
|
||||
return i;
|
||||
uint16_t i = 0;
|
||||
while (!br.eof())
|
||||
i += br.read_8();
|
||||
return i;
|
||||
}
|
||||
|
||||
uint8_t xorBytes(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
ByteReader br(bytes);
|
||||
|
||||
uint8_t i = 0;
|
||||
while (!br.eof())
|
||||
i ^= br.read_8();
|
||||
return i;
|
||||
uint8_t i = 0;
|
||||
while (!br.eof())
|
||||
i ^= br.read_8();
|
||||
return i;
|
||||
}
|
||||
|
||||
uint16_t crc16(uint16_t poly, uint16_t crc, const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
ByteReader br(bytes);
|
||||
|
||||
while (!br.eof())
|
||||
{
|
||||
crc ^= br.read_8() << 8;
|
||||
for (int i=0; i<8; i++)
|
||||
crc = (crc & 0x8000) ? ((crc<<1)^poly) : (crc<<1);
|
||||
}
|
||||
while (!br.eof())
|
||||
{
|
||||
crc ^= br.read_8() << 8;
|
||||
for (int i = 0; i < 8; i++)
|
||||
crc = (crc & 0x8000) ? ((crc << 1) ^ poly) : (crc << 1);
|
||||
}
|
||||
|
||||
return crc;
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crc16ref(uint16_t poly, uint16_t crc, const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
ByteReader br(bytes);
|
||||
|
||||
while (!br.eof())
|
||||
{
|
||||
crc ^= br.read_8();
|
||||
for (int i=0; i<8; i++)
|
||||
crc = (crc & 0x0001) ? ((crc>>1)^poly) : (crc>>1);
|
||||
}
|
||||
while (!br.eof())
|
||||
{
|
||||
crc ^= br.read_8();
|
||||
for (int i = 0; i < 8; i++)
|
||||
crc = (crc & 0x0001) ? ((crc >> 1) ^ poly) : (crc >> 1);
|
||||
}
|
||||
|
||||
return crc;
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* Thanks to user202729 on StackOverflow for miraculously reverse engineering
|
||||
* this. */
|
||||
uint32_t crcbrother(const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
ByteReader br(bytes);
|
||||
|
||||
uint32_t crc = br.read_8();
|
||||
while (!br.eof())
|
||||
{
|
||||
for (int i=0; i<8; i++)
|
||||
crc = (crc & 0x800000) ? ((crc<<1)^BROTHER_POLY) : (crc<<1);
|
||||
crc ^= br.read_8();
|
||||
}
|
||||
uint32_t crc = br.read_8();
|
||||
while (!br.eof())
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
crc = (crc & 0x800000) ? ((crc << 1) ^ BROTHER_POLY) : (crc << 1);
|
||||
crc ^= br.read_8();
|
||||
}
|
||||
|
||||
return crc & 0xFFFFFF;
|
||||
return crc & 0xFFFFFF;
|
||||
}
|
||||
|
||||
|
||||
127
lib/csvreader.cc
127
lib/csvreader.cc
@@ -4,78 +4,77 @@
|
||||
|
||||
std::vector<std::string> CsvReader::readLine()
|
||||
{
|
||||
enum state_t
|
||||
{
|
||||
UNQUOTED,
|
||||
QUOTED,
|
||||
QUOTEDQUOTE
|
||||
};
|
||||
enum state_t
|
||||
{
|
||||
UNQUOTED,
|
||||
QUOTED,
|
||||
QUOTEDQUOTE
|
||||
};
|
||||
|
||||
std::vector<std::string> results;
|
||||
std::string row;
|
||||
std::getline(_istream, row);
|
||||
if (_istream.bad() || _istream.fail())
|
||||
return results;
|
||||
std::vector<std::string> results;
|
||||
std::string row;
|
||||
std::getline(_istream, row);
|
||||
if (_istream.bad() || _istream.fail())
|
||||
return results;
|
||||
|
||||
results.push_back("");
|
||||
state_t state = UNQUOTED;
|
||||
size_t index = 0;
|
||||
for (char c : row)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case UNQUOTED:
|
||||
switch (c)
|
||||
{
|
||||
case ',':
|
||||
results.push_back("");
|
||||
index++;
|
||||
break;
|
||||
results.push_back("");
|
||||
state_t state = UNQUOTED;
|
||||
size_t index = 0;
|
||||
for (char c : row)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case UNQUOTED:
|
||||
switch (c)
|
||||
{
|
||||
case ',':
|
||||
results.push_back("");
|
||||
index++;
|
||||
break;
|
||||
|
||||
case '"':
|
||||
state = QUOTED;
|
||||
break;
|
||||
case '"':
|
||||
state = QUOTED;
|
||||
break;
|
||||
|
||||
default:
|
||||
results[index].push_back(c);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
results[index].push_back(c);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case QUOTED:
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
state = QUOTEDQUOTE;
|
||||
break;
|
||||
case QUOTED:
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
state = QUOTEDQUOTE;
|
||||
break;
|
||||
|
||||
default:
|
||||
results[index].push_back(c);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
results[index].push_back(c);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case QUOTEDQUOTE:
|
||||
switch (c)
|
||||
{
|
||||
case ',':
|
||||
results.push_back("");
|
||||
index++;
|
||||
state = UNQUOTED;
|
||||
break;
|
||||
case QUOTEDQUOTE:
|
||||
switch (c)
|
||||
{
|
||||
case ',':
|
||||
results.push_back("");
|
||||
index++;
|
||||
state = UNQUOTED;
|
||||
break;
|
||||
|
||||
case '"':
|
||||
results[index].push_back('"');
|
||||
state = QUOTED;
|
||||
break;
|
||||
case '"':
|
||||
results[index].push_back('"');
|
||||
state = QUOTED;
|
||||
break;
|
||||
|
||||
default:
|
||||
state = UNQUOTED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
state = UNQUOTED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "image.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include <numeric>
|
||||
|
||||
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
|
||||
@@ -59,7 +58,7 @@ std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
|
||||
|
||||
auto decoder = decoders.find(config.format_case());
|
||||
if (decoder == decoders.end())
|
||||
Error() << "no decoder specified";
|
||||
error("no decoder specified");
|
||||
|
||||
return (decoder->second)(config);
|
||||
}
|
||||
|
||||
@@ -12,81 +12,82 @@
|
||||
* than my code.
|
||||
*/
|
||||
|
||||
FluxDecoder::FluxDecoder(FluxmapReader* fmr, nanoseconds_t bitcell,
|
||||
const DecoderProto& config):
|
||||
_fmr(fmr),
|
||||
_pll_phase(config.pll_phase()),
|
||||
_pll_adjust(config.pll_adjust()),
|
||||
_flux_scale(config.flux_scale()),
|
||||
_clock(bitcell),
|
||||
_clock_centre(bitcell),
|
||||
_clock_min(bitcell * (1.0 - _pll_adjust)),
|
||||
_clock_max(bitcell * (1.0 + _pll_adjust)),
|
||||
_flux(0),
|
||||
_leading_zeroes(fmr->tell().zeroes)
|
||||
{}
|
||||
|
||||
FluxDecoder::FluxDecoder(
|
||||
FluxmapReader* fmr, nanoseconds_t bitcell, const DecoderProto& config):
|
||||
_fmr(fmr),
|
||||
_pll_phase(config.pll_phase()),
|
||||
_pll_adjust(config.pll_adjust()),
|
||||
_flux_scale(config.flux_scale()),
|
||||
_clock(bitcell),
|
||||
_clock_centre(bitcell),
|
||||
_clock_min(bitcell * (1.0 - _pll_adjust)),
|
||||
_clock_max(bitcell * (1.0 + _pll_adjust)),
|
||||
_flux(0),
|
||||
_leading_zeroes(fmr->tell().zeroes)
|
||||
{
|
||||
}
|
||||
|
||||
bool FluxDecoder::readBit()
|
||||
{
|
||||
if (_leading_zeroes > 0)
|
||||
{
|
||||
_leading_zeroes--;
|
||||
return false;
|
||||
}
|
||||
else if (_leading_zeroes == 0)
|
||||
{
|
||||
_leading_zeroes--;
|
||||
return true;
|
||||
}
|
||||
if (_leading_zeroes > 0)
|
||||
{
|
||||
_leading_zeroes--;
|
||||
return false;
|
||||
}
|
||||
else if (_leading_zeroes == 0)
|
||||
{
|
||||
_leading_zeroes--;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (!_fmr->eof() && (_flux < (_clock/2)))
|
||||
{
|
||||
_flux += nextFlux() * _flux_scale;;
|
||||
_clocked_zeroes = 0;
|
||||
}
|
||||
while (!_fmr->eof() && (_flux < (_clock / 2)))
|
||||
{
|
||||
_flux += nextFlux() * _flux_scale;
|
||||
;
|
||||
_clocked_zeroes = 0;
|
||||
}
|
||||
|
||||
_flux -= _clock;
|
||||
if (_flux >= (_clock/2))
|
||||
{
|
||||
_clocked_zeroes++;
|
||||
_goodbits++;
|
||||
return false;
|
||||
}
|
||||
_flux -= _clock;
|
||||
if (_flux >= (_clock / 2))
|
||||
{
|
||||
_clocked_zeroes++;
|
||||
_goodbits++;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* PLL adjustment: change the clock frequency according to the phase
|
||||
* mismatch */
|
||||
if (_clocked_zeroes <= 3)
|
||||
{
|
||||
/* In sync: adjust base clock */
|
||||
|
||||
_clock += _flux * _pll_adjust;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Out of sync: adjust the base clock back towards the centre */
|
||||
/* PLL adjustment: change the clock frequency according to the phase
|
||||
* mismatch */
|
||||
if (_clocked_zeroes <= 3)
|
||||
{
|
||||
/* In sync: adjust base clock */
|
||||
|
||||
_clock += (_clock_centre - _clock) * _pll_adjust;
|
||||
_clock += _flux * _pll_adjust;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Out of sync: adjust the base clock back towards the centre */
|
||||
|
||||
/* We require 256 good bits before reporting another sync loss event. */
|
||||
_clock += (_clock_centre - _clock) * _pll_adjust;
|
||||
|
||||
if (_goodbits >= 256)
|
||||
_sync_lost = true;
|
||||
_goodbits = 0;
|
||||
}
|
||||
/* We require 256 good bits before reporting another sync loss event. */
|
||||
|
||||
/* Clamp the clock's adjustment range. */
|
||||
if (_goodbits >= 256)
|
||||
_sync_lost = true;
|
||||
_goodbits = 0;
|
||||
}
|
||||
|
||||
_clock = std::min(std::max(_clock_min, _clock), _clock_max);
|
||||
/* Clamp the clock's adjustment range. */
|
||||
|
||||
/* I'm not sure what this does, but the original comment is:
|
||||
_clock = std::min(std::max(_clock_min, _clock), _clock_max);
|
||||
|
||||
/* I'm not sure what this does, but the original comment is:
|
||||
* Authentic PLL: Do not snap the timing window to each flux transition
|
||||
*/
|
||||
*/
|
||||
|
||||
_flux = _flux * (1.0 - _pll_phase);
|
||||
|
||||
_goodbits++;
|
||||
return true;
|
||||
_goodbits++;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<bool> FluxDecoder::readBits(unsigned count)
|
||||
@@ -113,5 +114,5 @@ std::vector<bool> FluxDecoder::readBits(const Fluxmap::Position& until)
|
||||
|
||||
nanoseconds_t FluxDecoder::nextFlux()
|
||||
{
|
||||
return _fmr->readInterval(_clock_centre) * NS_PER_TICK;
|
||||
return _fmr->readInterval(_clock_centre) * NS_PER_TICK;
|
||||
}
|
||||
|
||||
@@ -4,18 +4,17 @@
|
||||
#include "flags.h"
|
||||
#include "protocol.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include <numeric>
|
||||
#include <math.h>
|
||||
#include <strings.h>
|
||||
|
||||
FluxmapReader::FluxmapReader(const Fluxmap& fluxmap):
|
||||
_fluxmap(fluxmap),
|
||||
_bytes(fluxmap.ptr()),
|
||||
_size(fluxmap.bytes()),
|
||||
_config(config.decoder())
|
||||
_fluxmap(fluxmap),
|
||||
_bytes(fluxmap.ptr()),
|
||||
_size(fluxmap.bytes()),
|
||||
_config(config.decoder())
|
||||
{
|
||||
rewind();
|
||||
rewind();
|
||||
}
|
||||
|
||||
void FluxmapReader::getNextEvent(int& event, unsigned& ticks)
|
||||
@@ -25,23 +24,23 @@ void FluxmapReader::getNextEvent(int& event, unsigned& ticks)
|
||||
while (!eof())
|
||||
{
|
||||
uint8_t b = _bytes[_pos.bytes++];
|
||||
ticks += b & 0x3f;
|
||||
if (!b || (b & (F_BIT_PULSE|F_BIT_INDEX)))
|
||||
ticks += b & 0x3f;
|
||||
if (!b || (b & (F_BIT_PULSE | F_BIT_INDEX)))
|
||||
{
|
||||
_pos.ticks += ticks;
|
||||
event = b & 0xc0;
|
||||
event = b & 0xc0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_pos.ticks += ticks;
|
||||
event = F_EOF;
|
||||
event = F_EOF;
|
||||
}
|
||||
|
||||
void FluxmapReader::skipToEvent(int event)
|
||||
{
|
||||
unsigned ticks;
|
||||
findEvent(event, ticks);
|
||||
unsigned ticks;
|
||||
findEvent(event, ticks);
|
||||
}
|
||||
|
||||
bool FluxmapReader::findEvent(int event, unsigned& ticks)
|
||||
@@ -51,29 +50,30 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks)
|
||||
for (;;)
|
||||
{
|
||||
unsigned thisTicks;
|
||||
int thisEvent;
|
||||
getNextEvent(thisEvent, thisTicks);
|
||||
ticks += thisTicks;
|
||||
int thisEvent;
|
||||
getNextEvent(thisEvent, thisTicks);
|
||||
ticks += thisTicks;
|
||||
|
||||
if (thisEvent == F_EOF)
|
||||
return false;
|
||||
if (thisEvent == F_EOF)
|
||||
return false;
|
||||
|
||||
if (eof())
|
||||
return false;
|
||||
if ((event == thisEvent) || (event & thisEvent))
|
||||
return true;
|
||||
if ((event == thisEvent) || (event & thisEvent))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned FluxmapReader::readInterval(nanoseconds_t clock)
|
||||
{
|
||||
unsigned thresholdTicks = (clock * _config.pulse_debounce_threshold()) / NS_PER_TICK;
|
||||
unsigned thresholdTicks =
|
||||
(clock * _config.pulse_debounce_threshold()) / NS_PER_TICK;
|
||||
unsigned ticks = 0;
|
||||
|
||||
while (ticks <= thresholdTicks)
|
||||
{
|
||||
unsigned thisTicks;
|
||||
if (!findEvent(F_BIT_PULSE, thisTicks))
|
||||
if (!findEvent(F_BIT_PULSE, thisTicks))
|
||||
break;
|
||||
ticks += thisTicks;
|
||||
}
|
||||
@@ -93,14 +93,13 @@ static int findLowestSetBit(uint64_t value)
|
||||
return bit;
|
||||
}
|
||||
|
||||
FluxPattern::FluxPattern(unsigned bits, uint64_t pattern):
|
||||
_bits(bits)
|
||||
FluxPattern::FluxPattern(unsigned bits, uint64_t pattern): _bits(bits)
|
||||
{
|
||||
const uint64_t TOPBIT = 1ULL << 63;
|
||||
|
||||
assert(pattern != 0);
|
||||
|
||||
unsigned lowbit = findLowestSetBit(pattern)-1;
|
||||
unsigned lowbit = findLowestSetBit(pattern) - 1;
|
||||
|
||||
pattern <<= 64 - bits;
|
||||
_highzeroes = 0;
|
||||
@@ -118,8 +117,7 @@ FluxPattern::FluxPattern(unsigned bits, uint64_t pattern):
|
||||
{
|
||||
pattern <<= 1;
|
||||
interval++;
|
||||
}
|
||||
while (!(pattern & TOPBIT));
|
||||
} while (!(pattern & TOPBIT));
|
||||
_intervals.push_back(interval);
|
||||
_length += interval;
|
||||
}
|
||||
@@ -134,7 +132,7 @@ FluxPattern::FluxPattern(unsigned bits, uint64_t pattern):
|
||||
|
||||
bool FluxPattern::matches(const unsigned* end, FluxMatch& match) const
|
||||
{
|
||||
const double clockDecodeThreshold = config.decoder().bit_error_threshold();
|
||||
const double clockDecodeThreshold = config.decoder().bit_error_threshold();
|
||||
const unsigned* start = end - _intervals.size();
|
||||
unsigned candidatelength = std::accumulate(start, end - _lowzero, 0);
|
||||
if (!candidatelength)
|
||||
@@ -142,7 +140,7 @@ bool FluxPattern::matches(const unsigned* end, FluxMatch& match) const
|
||||
match.clock = (double)candidatelength / (double)_length;
|
||||
|
||||
unsigned exactIntervals = _intervals.size() - _lowzero;
|
||||
for (unsigned i=0; i<exactIntervals; i++)
|
||||
for (unsigned i = 0; i < exactIntervals; i++)
|
||||
{
|
||||
double ii = match.clock * (double)_intervals[i];
|
||||
double ci = (double)start[i];
|
||||
@@ -166,7 +164,8 @@ bool FluxPattern::matches(const unsigned* end, FluxMatch& match) const
|
||||
return true;
|
||||
}
|
||||
|
||||
FluxMatchers::FluxMatchers(const std::initializer_list<const FluxMatcher*> matchers):
|
||||
FluxMatchers::FluxMatchers(
|
||||
const std::initializer_list<const FluxMatcher*> matchers):
|
||||
_matchers(matchers)
|
||||
{
|
||||
_intervals = 0;
|
||||
@@ -195,7 +194,7 @@ void FluxmapReader::seek(nanoseconds_t ns)
|
||||
|
||||
while (!eof() && (_pos.ticks < ticks))
|
||||
{
|
||||
int e;
|
||||
int e;
|
||||
unsigned t;
|
||||
getNextEvent(e, t);
|
||||
}
|
||||
@@ -204,7 +203,7 @@ void FluxmapReader::seek(nanoseconds_t ns)
|
||||
|
||||
void FluxmapReader::seekToByte(unsigned b)
|
||||
{
|
||||
if (b < _pos.bytes)
|
||||
if (b < _pos.bytes)
|
||||
{
|
||||
_pos.ticks = 0;
|
||||
_pos.bytes = 0;
|
||||
@@ -212,7 +211,7 @@ void FluxmapReader::seekToByte(unsigned b)
|
||||
|
||||
while (!eof() && (_pos.bytes < b))
|
||||
{
|
||||
int e;
|
||||
int e;
|
||||
unsigned t;
|
||||
getNextEvent(e, t);
|
||||
}
|
||||
@@ -225,13 +224,14 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern)
|
||||
return seekToPattern(pattern, unused);
|
||||
}
|
||||
|
||||
nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const FluxMatcher*& matching)
|
||||
nanoseconds_t FluxmapReader::seekToPattern(
|
||||
const FluxMatcher& pattern, const FluxMatcher*& matching)
|
||||
{
|
||||
unsigned intervalCount = pattern.intervals();
|
||||
std::vector<unsigned> candidates(intervalCount+1);
|
||||
std::vector<Fluxmap::Position> positions(intervalCount+1);
|
||||
std::vector<unsigned> candidates(intervalCount + 1);
|
||||
std::vector<Fluxmap::Position> positions(intervalCount + 1);
|
||||
|
||||
for (unsigned i=0; i<=intervalCount; i++)
|
||||
for (unsigned i = 0; i <= intervalCount; i++)
|
||||
{
|
||||
positions[i] = tell();
|
||||
candidates[i] = 0;
|
||||
@@ -242,22 +242,21 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
|
||||
FluxMatch match;
|
||||
if (pattern.matches(&*candidates.end(), match))
|
||||
{
|
||||
seek(positions[intervalCount-match.intervals]);
|
||||
seek(positions[intervalCount - match.intervals]);
|
||||
_pos.zeroes = match.zeroes;
|
||||
matching = match.matcher;
|
||||
nanoseconds_t detectedClock = match.clock * NS_PER_TICK;
|
||||
if (detectedClock > (_config.minimum_clock_us()*1000))
|
||||
if (detectedClock > (_config.minimum_clock_us() * 1000))
|
||||
return match.clock * NS_PER_TICK;
|
||||
}
|
||||
|
||||
for (unsigned i=0; i<intervalCount; i++)
|
||||
for (unsigned i = 0; i < intervalCount; i++)
|
||||
{
|
||||
positions[i] = positions[i+1];
|
||||
candidates[i] = candidates[i+1];
|
||||
positions[i] = positions[i + 1];
|
||||
candidates[i] = candidates[i + 1];
|
||||
}
|
||||
findEvent(F_BIT_PULSE, candidates[intervalCount]);
|
||||
findEvent(F_BIT_PULSE, candidates[intervalCount]);
|
||||
positions[intervalCount] = tell();
|
||||
|
||||
}
|
||||
|
||||
matching = NULL;
|
||||
@@ -269,4 +268,3 @@ void FluxmapReader::seekToIndexMark()
|
||||
skipToEvent(F_BIT_INDEX);
|
||||
_pos.zeroes = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
#include "decoders/decoders.h"
|
||||
|
||||
Bytes decodeFmMfm(
|
||||
std::vector<bool>::const_iterator ii, std::vector<bool>::const_iterator end)
|
||||
std::vector<bool>::const_iterator ii, std::vector<bool>::const_iterator end)
|
||||
{
|
||||
/*
|
||||
* FM is dumb as rocks, consisting on regular clock pulses with data pulses in
|
||||
* the gaps. 0x00 is:
|
||||
*
|
||||
/*
|
||||
* FM is dumb as rocks, consisting on regular clock pulses with data pulses
|
||||
* in the gaps. 0x00 is:
|
||||
*
|
||||
* X-X-X-X-X-X-X-X-
|
||||
*
|
||||
*
|
||||
* 0xff is:
|
||||
*
|
||||
*
|
||||
* XXXXXXXXXXXXXXXX
|
||||
*
|
||||
* So we just need to extract all the odd bits.
|
||||
*
|
||||
*
|
||||
* MFM and M2FM are slightly more complicated, where the first bit of each
|
||||
* pair can be either 0 or 1... but the second bit is always the data bit,
|
||||
* and at this point we simply don't care what the first bit is, so
|
||||
@@ -33,7 +33,7 @@ Bytes decodeFmMfm(
|
||||
ii++; /* skip clock bit */
|
||||
if (ii == end)
|
||||
break;
|
||||
fifo = (fifo<<1) | *ii++;
|
||||
fifo = (fifo << 1) | *ii++;
|
||||
|
||||
bitcount++;
|
||||
if (bitcount == 8)
|
||||
@@ -45,7 +45,7 @@ Bytes decodeFmMfm(
|
||||
|
||||
if (bitcount != 0)
|
||||
{
|
||||
fifo <<= 8-bitcount;
|
||||
fifo <<= 8 - bitcount;
|
||||
bw.write_8(fifo);
|
||||
}
|
||||
|
||||
@@ -54,42 +54,45 @@ Bytes decodeFmMfm(
|
||||
|
||||
void encodeFm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input)
|
||||
{
|
||||
if (bits.size() == 0)
|
||||
return;
|
||||
unsigned len = bits.size()-1;
|
||||
if (bits.size() == 0)
|
||||
return;
|
||||
unsigned len = bits.size() - 1;
|
||||
|
||||
for (uint8_t b : input)
|
||||
{
|
||||
for (int i=0; i<8; i++)
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
bool bit = b & 0x80;
|
||||
b <<= 1;
|
||||
|
||||
if (cursor >= len)
|
||||
return;
|
||||
|
||||
|
||||
bits[cursor++] = true;
|
||||
bits[cursor++] = bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input, bool& lastBit)
|
||||
void encodeMfm(std::vector<bool>& bits,
|
||||
unsigned& cursor,
|
||||
const Bytes& input,
|
||||
bool& lastBit)
|
||||
{
|
||||
if (bits.size() == 0)
|
||||
return;
|
||||
unsigned len = bits.size()-1;
|
||||
if (bits.size() == 0)
|
||||
return;
|
||||
unsigned len = bits.size() - 1;
|
||||
|
||||
for (uint8_t b : input)
|
||||
{
|
||||
for (int i=0; i<8; i++)
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
bool bit = b & 0x80;
|
||||
b <<= 1;
|
||||
|
||||
if (cursor >= len)
|
||||
return;
|
||||
|
||||
|
||||
bits[cursor++] = !lastBit && !bit;
|
||||
bits[cursor++] = bit;
|
||||
lastBit = bit;
|
||||
@@ -99,22 +102,21 @@ void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input, bo
|
||||
|
||||
Bytes encodeMfm(const Bytes& input, bool& lastBit)
|
||||
{
|
||||
ByteReader br(input);
|
||||
BitReader bitr(br);
|
||||
Bytes b;
|
||||
ByteWriter bw(b);
|
||||
BitWriter bitw(bw);
|
||||
ByteReader br(input);
|
||||
BitReader bitr(br);
|
||||
Bytes b;
|
||||
ByteWriter bw(b);
|
||||
BitWriter bitw(bw);
|
||||
|
||||
while (!bitr.eof())
|
||||
{
|
||||
uint8_t bit = bitr.get();
|
||||
|
||||
bitw.push(!lastBit && !bit);
|
||||
bitw.push(bit);
|
||||
lastBit = bit;
|
||||
while (!bitr.eof())
|
||||
{
|
||||
uint8_t bit = bitr.get();
|
||||
|
||||
bitw.push(!lastBit && !bit);
|
||||
bitw.push(bit);
|
||||
lastBit = bit;
|
||||
}
|
||||
|
||||
bitw.flush();
|
||||
return b;
|
||||
bitw.flush();
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,13 @@
|
||||
#include "lib/image.h"
|
||||
#include "protocol.h"
|
||||
|
||||
std::unique_ptr<Encoder> Encoder::create(
|
||||
const EncoderProto& config)
|
||||
std::unique_ptr<Encoder> Encoder::create(const EncoderProto& config)
|
||||
{
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<Encoder>(const EncoderProto&)>>
|
||||
encoders = {
|
||||
{EncoderProto::kAmiga, createAmigaEncoder },
|
||||
{EncoderProto::kAgat, createAgatEncoder },
|
||||
{EncoderProto::kAgat, createAgatEncoder },
|
||||
{EncoderProto::kApple2, createApple2Encoder },
|
||||
{EncoderProto::kBrother, createBrotherEncoder },
|
||||
{EncoderProto::kC64, createCommodore64Encoder},
|
||||
@@ -40,7 +39,7 @@ std::unique_ptr<Encoder> Encoder::create(
|
||||
|
||||
auto encoder = encoders.find(config.format_case());
|
||||
if (encoder == encoders.end())
|
||||
Error() << "no encoder specified";
|
||||
error("no encoder specified");
|
||||
|
||||
return (encoder->second)(config);
|
||||
}
|
||||
@@ -51,15 +50,18 @@ nanoseconds_t Encoder::calculatePhysicalClockPeriod(
|
||||
nanoseconds_t currentRotationalPeriod =
|
||||
config.drive().rotational_period_ms() * 1e6;
|
||||
if (currentRotationalPeriod == 0)
|
||||
Error() << "you must set --drive.rotational_period_ms as it can't be "
|
||||
"autodetected";
|
||||
error(
|
||||
"you must set --drive.rotational_period_ms as it can't be "
|
||||
"autodetected");
|
||||
|
||||
return targetClockPeriod *
|
||||
(currentRotationalPeriod / targetRotationalPeriod);
|
||||
}
|
||||
|
||||
std::shared_ptr<const Sector> Encoder::getSector(
|
||||
std::shared_ptr<const TrackInfo>& trackInfo, const Image& image, unsigned sectorId)
|
||||
std::shared_ptr<const TrackInfo>& trackInfo,
|
||||
const Image& image,
|
||||
unsigned sectorId)
|
||||
{
|
||||
return image.get(trackInfo->logicalTrack, trackInfo->logicalSide, sectorId);
|
||||
}
|
||||
@@ -73,7 +75,7 @@ std::vector<std::shared_ptr<const Sector>> Encoder::collectSectors(
|
||||
{
|
||||
const auto& sector = getSector(trackLayout, image, sectorId);
|
||||
if (!sector)
|
||||
Error() << fmt::format("sector {}.{}.{} is missing from the image",
|
||||
error("sector {}.{}.{} is missing from the image",
|
||||
trackLayout->logicalTrack,
|
||||
trackLayout->logicalSide,
|
||||
sectorId);
|
||||
|
||||
21
lib/fl2.cc
21
lib/fl2.cc
@@ -1,7 +1,6 @@
|
||||
#include "globals.h"
|
||||
#include "proto.h"
|
||||
#include "fluxmap.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/fl2.pb.h"
|
||||
#include <fstream>
|
||||
|
||||
@@ -27,7 +26,7 @@ static void upgradeFluxFile(FluxFileProto& proto)
|
||||
proto.set_version(FluxFileVersion::VERSION_2);
|
||||
}
|
||||
if (proto.version() > FluxFileVersion::VERSION_2)
|
||||
Error() << fmt::format(
|
||||
error(
|
||||
"this is a version {} flux file, but this build of the client can "
|
||||
"only handle up to version {} --- please upgrade",
|
||||
proto.version(),
|
||||
@@ -38,19 +37,19 @@ FluxFileProto loadFl2File(const std::string filename)
|
||||
{
|
||||
std::ifstream ifs(filename, std::ios::in | std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
Error() << fmt::format(
|
||||
"cannot open input file '{}': {}", filename, strerror(errno));
|
||||
error("cannot open input file '{}': {}", filename, strerror(errno));
|
||||
|
||||
char buffer[16];
|
||||
ifs.read(buffer, sizeof(buffer));
|
||||
ifs.read(buffer, sizeof(buffer));
|
||||
if (strncmp(buffer, "SQLite format 3", 16) == 0)
|
||||
Error() << "this flux file is too old; please use the "
|
||||
"upgrade-flux-file tool to upgrade it";
|
||||
error(
|
||||
"this flux file is too old; please use the upgrade-flux-file tool "
|
||||
"to upgrade it");
|
||||
|
||||
FluxFileProto proto;
|
||||
ifs.seekg(0);
|
||||
ifs.seekg(0);
|
||||
if (!proto.ParseFromIstream(&ifs))
|
||||
Error() << fmt::format("unable to read input file '{}'", filename);
|
||||
error("unable to read input file '{}'", filename);
|
||||
upgradeFluxFile(proto);
|
||||
return proto;
|
||||
}
|
||||
@@ -62,8 +61,8 @@ void saveFl2File(const std::string filename, FluxFileProto& proto)
|
||||
|
||||
std::ofstream of(filename, std::ios::out | std::ios::binary);
|
||||
if (!proto.SerializeToOstream(&of))
|
||||
Error() << fmt::format("unable to write output file '{}'", filename);
|
||||
error("unable to write output file '{}'", filename);
|
||||
of.close();
|
||||
if (of.fail())
|
||||
Error() << "FL2 write I/O error: " << strerror(errno);
|
||||
error("FL2 write I/O error: {}", strerror(errno));
|
||||
}
|
||||
|
||||
53
lib/flags.cc
53
lib/flags.cc
@@ -3,7 +3,6 @@
|
||||
#include "proto.h"
|
||||
#include "utils.h"
|
||||
#include "logger.h"
|
||||
#include "fmt/format.h"
|
||||
#include <google/protobuf/text_format.h>
|
||||
#include <regex>
|
||||
#include <fstream>
|
||||
@@ -44,17 +43,13 @@ void FlagGroup::addFlag(Flag* flag)
|
||||
void FlagGroup::applyOption(const OptionProto& option)
|
||||
{
|
||||
if (option.config().option_size() > 0)
|
||||
Error() << fmt::format(
|
||||
"option '{}' has an option inside it, which isn't "
|
||||
"allowed",
|
||||
error("option '{}' has an option inside it, which isn't allowed",
|
||||
option.name());
|
||||
if (option.config().option_group_size() > 0)
|
||||
Error() << fmt::format(
|
||||
"option '{}' has an option group inside it, which isn't "
|
||||
"allowed",
|
||||
error("option '{}' has an option group inside it, which isn't allowed",
|
||||
option.name());
|
||||
|
||||
Logger() << fmt::format("OPTION: {}",
|
||||
log("OPTION: {}",
|
||||
option.has_message() ? option.message() : option.comment());
|
||||
|
||||
config.MergeFrom(option.config());
|
||||
@@ -112,7 +107,7 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
|
||||
for (const auto& name : flag->names())
|
||||
{
|
||||
if (flags_by_name.find(name) != flags_by_name.end())
|
||||
Error() << "two flags use the name '" << name << "'";
|
||||
error("two flags use the name '{}'", name);
|
||||
flags_by_name[name] = flag;
|
||||
}
|
||||
|
||||
@@ -203,7 +198,7 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
|
||||
options.insert(path);
|
||||
}
|
||||
else
|
||||
Error() << "unrecognised flag; try --help";
|
||||
error("unrecognised flag; try --help");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -235,20 +230,19 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
|
||||
FlagGroup::applyOption(*defaultOption);
|
||||
}
|
||||
|
||||
/* Next, any standalone options. */
|
||||
/* Next, any standalone options. */
|
||||
|
||||
for (auto& option : config.option())
|
||||
{
|
||||
if (options.find(option.name()) != options.end())
|
||||
{
|
||||
FlagGroup::applyOption(option);
|
||||
options.erase(option.name());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& option : config.option())
|
||||
{
|
||||
if (options.find(option.name()) != options.end())
|
||||
{
|
||||
FlagGroup::applyOption(option);
|
||||
options.erase(option.name());
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.empty())
|
||||
Error() << fmt::format(
|
||||
"--{} is not a known flag or format option; try --help",
|
||||
error("--{} is not a known flag or format option; try --help",
|
||||
*options.begin());
|
||||
|
||||
/* Now apply any value overrides (in order). */
|
||||
@@ -268,8 +262,8 @@ void FlagGroup::parseFlags(int argc,
|
||||
{
|
||||
auto filenames = parseFlagsWithFilenames(argc, argv, callback);
|
||||
if (!filenames.empty())
|
||||
Error() << "non-option parameter " << *filenames.begin()
|
||||
<< " seen (try --help)";
|
||||
error(
|
||||
"non-option parameter '{}' seen (try --help)", *filenames.begin());
|
||||
}
|
||||
|
||||
void FlagGroup::parseFlagsWithConfigFiles(int argc,
|
||||
@@ -290,20 +284,19 @@ ConfigProto FlagGroup::parseSingleConfigFile(const std::string& filename,
|
||||
{
|
||||
const auto& it = configFiles.find(filename);
|
||||
if (it != configFiles.end())
|
||||
return *it->second;
|
||||
return *it->second;
|
||||
else
|
||||
{
|
||||
std::ifstream f(filename, std::ios::out);
|
||||
if (f.fail())
|
||||
Error() << fmt::format(
|
||||
"Cannot open '{}': {}", filename, strerror(errno));
|
||||
error("Cannot open '{}': {}", filename, strerror(errno));
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
|
||||
ConfigProto config;
|
||||
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
|
||||
Error() << "couldn't load external config proto";
|
||||
error("couldn't load external config proto");
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -326,7 +319,7 @@ Flag::Flag(const std::vector<std::string>& names, const std::string helptext):
|
||||
_helptext(helptext)
|
||||
{
|
||||
if (!currentFlagGroup)
|
||||
Error() << "no flag group defined for " << *names.begin();
|
||||
error("no flag group defined for {}", *names.begin());
|
||||
_group.addFlag(this);
|
||||
}
|
||||
|
||||
@@ -337,7 +330,7 @@ void BoolFlag::set(const std::string& value)
|
||||
else if ((value == "false") || (value == "n"))
|
||||
_value = false;
|
||||
else
|
||||
Error() << "can't parse '" << value << "'; try 'true' or 'false'";
|
||||
error("can't parse '{}'; try 'true' or 'false'", value);
|
||||
_callback(_value);
|
||||
_isSet = true;
|
||||
}
|
||||
|
||||
@@ -8,217 +8,217 @@
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "lib/logger.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fluxmap.h"
|
||||
#include "a2r.h"
|
||||
#include <fstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
uint32_t ticks_to_a2r(uint32_t ticks)
|
||||
{
|
||||
return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
|
||||
}
|
||||
|
||||
bool singlesided(void)
|
||||
{
|
||||
return config.heads().start() == config.heads().end();
|
||||
}
|
||||
|
||||
class A2RFluxSink : public FluxSink
|
||||
{
|
||||
public:
|
||||
A2RFluxSink(const A2RFluxSinkProto& lconfig):
|
||||
_config(lconfig),
|
||||
_bytes{},
|
||||
_writer{_bytes.writer()}
|
||||
uint32_t ticks_to_a2r(uint32_t ticks)
|
||||
{
|
||||
|
||||
Logger() << fmt::format(
|
||||
"A2R: writing A2R {} file containing {} tracks\n",
|
||||
singlesided() ? "single sided" : "double sided",
|
||||
config.tracks().end() - config.tracks().start() + 1);
|
||||
|
||||
time_t now{std::time(nullptr)};
|
||||
auto t = gmtime(&now);
|
||||
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
|
||||
return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
|
||||
}
|
||||
|
||||
~A2RFluxSink()
|
||||
bool singlesided(void)
|
||||
{
|
||||
writeHeader();
|
||||
writeInfo();
|
||||
writeStream();
|
||||
writeMeta();
|
||||
|
||||
Logger() << "A2R: writing output file...\n";
|
||||
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
_bytes.writeTo(of);
|
||||
of.close();
|
||||
return config.heads().start() == config.heads().end();
|
||||
}
|
||||
|
||||
private:
|
||||
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
|
||||
class A2RFluxSink : public FluxSink
|
||||
{
|
||||
_writer.write_le32(chunk_id);
|
||||
_writer.write_le32(data.size());
|
||||
_writer += data;
|
||||
}
|
||||
|
||||
void writeHeader()
|
||||
{
|
||||
static const uint8_t a2r2_fileheader[] = {'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
|
||||
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
|
||||
}
|
||||
|
||||
void writeInfo()
|
||||
{
|
||||
Bytes info;
|
||||
auto writer = info.writer();
|
||||
writer.write_8(A2R_INFO_CHUNK_VERSION);
|
||||
auto version_str_padded = fmt::format("{: <32}", "Fluxengine");
|
||||
assert(version_str_padded.size() == 32);
|
||||
writer.append(version_str_padded);
|
||||
writer.write_8(singlesided() ? A2R_DISK_525 : A2R_DISK_35);
|
||||
writer.write_8(1); // write protected
|
||||
writer.write_8(1); // synchronized
|
||||
writeChunkAndData(A2R_CHUNK_INFO, info);
|
||||
}
|
||||
|
||||
void writeMeta()
|
||||
{
|
||||
Bytes meta;
|
||||
auto writer = meta.writer();
|
||||
for (auto& i : _metadata)
|
||||
public:
|
||||
A2RFluxSink(const A2RFluxSinkProto& lconfig):
|
||||
_config(lconfig),
|
||||
_bytes{},
|
||||
_writer{_bytes.writer()}
|
||||
{
|
||||
writer.append(i.first);
|
||||
writer.write_8('\t');
|
||||
writer.append(i.second);
|
||||
writer.write_8('\n');
|
||||
}
|
||||
writeChunkAndData(A2R_CHUNK_META, meta);
|
||||
}
|
||||
|
||||
void writeStream()
|
||||
{
|
||||
// A STRM always ends with a 255, even though this could ALSO indicate
|
||||
// the first byte of a multi-byte sequence
|
||||
_strmWriter.write_8(255);
|
||||
log("A2R: writing A2R {} file containing {} tracks\n",
|
||||
singlesided() ? "single sided" : "double sided",
|
||||
config.tracks().end() - config.tracks().start() + 1);
|
||||
|
||||
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
|
||||
}
|
||||
|
||||
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
if (!fluxmap.bytes())
|
||||
{
|
||||
return;
|
||||
time_t now{std::time(nullptr)};
|
||||
auto t = gmtime(&now);
|
||||
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
|
||||
}
|
||||
|
||||
// Writing from an image (as opposed to from a floppy) will contain
|
||||
// exactly one revolution and no index events.
|
||||
auto is_image = [](auto& fluxmap)
|
||||
~A2RFluxSink()
|
||||
{
|
||||
writeHeader();
|
||||
writeInfo();
|
||||
writeStream();
|
||||
writeMeta();
|
||||
|
||||
log("A2R: writing output file...\n");
|
||||
std::ofstream of(
|
||||
_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
error("cannot open output file");
|
||||
_bytes.writeTo(of);
|
||||
of.close();
|
||||
}
|
||||
|
||||
private:
|
||||
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
|
||||
{
|
||||
_writer.write_le32(chunk_id);
|
||||
_writer.write_le32(data.size());
|
||||
_writer += data;
|
||||
}
|
||||
|
||||
void writeHeader()
|
||||
{
|
||||
static const uint8_t a2r2_fileheader[] = {
|
||||
'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
|
||||
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
|
||||
}
|
||||
|
||||
void writeInfo()
|
||||
{
|
||||
Bytes info;
|
||||
auto writer = info.writer();
|
||||
writer.write_8(A2R_INFO_CHUNK_VERSION);
|
||||
auto version_str_padded = fmt::format("{: <32}", "Fluxengine");
|
||||
assert(version_str_padded.size() == 32);
|
||||
writer.append(version_str_padded);
|
||||
writer.write_8(singlesided() ? A2R_DISK_525 : A2R_DISK_35);
|
||||
writer.write_8(1); // write protected
|
||||
writer.write_8(1); // synchronized
|
||||
writeChunkAndData(A2R_CHUNK_INFO, info);
|
||||
}
|
||||
|
||||
void writeMeta()
|
||||
{
|
||||
Bytes meta;
|
||||
auto writer = meta.writer();
|
||||
for (auto& i : _metadata)
|
||||
{
|
||||
writer.append(i.first);
|
||||
writer.write_8('\t');
|
||||
writer.append(i.second);
|
||||
writer.write_8('\n');
|
||||
}
|
||||
writeChunkAndData(A2R_CHUNK_META, meta);
|
||||
}
|
||||
|
||||
void writeStream()
|
||||
{
|
||||
// A STRM always ends with a 255, even though this could ALSO
|
||||
// indicate the first byte of a multi-byte sequence
|
||||
_strmWriter.write_8(255);
|
||||
|
||||
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
|
||||
}
|
||||
|
||||
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
if (!fluxmap.bytes())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Writing from an image (as opposed to from a floppy) will contain
|
||||
// exactly one revolution and no index events.
|
||||
auto is_image = [](auto& fluxmap)
|
||||
{
|
||||
FluxmapReader fmr(fluxmap);
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
// but maybe there is no index, if we're writing from an image
|
||||
// to an a2r
|
||||
return fmr.eof();
|
||||
};
|
||||
|
||||
// Write the flux data into its own Bytes
|
||||
Bytes trackBytes;
|
||||
auto trackWriter = trackBytes.writer();
|
||||
|
||||
auto write_one_flux = [&](unsigned ticks)
|
||||
{
|
||||
auto value = ticks_to_a2r(ticks);
|
||||
while (value > 254)
|
||||
{
|
||||
trackWriter.write_8(255);
|
||||
value -= 255;
|
||||
}
|
||||
trackWriter.write_8(value);
|
||||
};
|
||||
|
||||
int revolution = 0;
|
||||
uint32_t loopPoint = 0;
|
||||
uint32_t totalTicks = 0;
|
||||
FluxmapReader fmr(fluxmap);
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
// but maybe there is no index, if we're writing from an image to an
|
||||
// a2r
|
||||
return fmr.eof();
|
||||
};
|
||||
|
||||
// Write the flux data into its own Bytes
|
||||
Bytes trackBytes;
|
||||
auto trackWriter = trackBytes.writer();
|
||||
|
||||
auto write_one_flux = [&](unsigned ticks)
|
||||
{
|
||||
auto value = ticks_to_a2r(ticks);
|
||||
while (value > 254)
|
||||
auto write_flux = [&](unsigned maxTicks = ~0u)
|
||||
{
|
||||
trackWriter.write_8(255);
|
||||
value -= 255;
|
||||
}
|
||||
trackWriter.write_8(value);
|
||||
};
|
||||
unsigned ticksSinceLastPulse = 0;
|
||||
|
||||
int revolution = 0;
|
||||
uint32_t loopPoint = 0;
|
||||
uint32_t totalTicks = 0;
|
||||
FluxmapReader fmr(fluxmap);
|
||||
while (!fmr.eof() && totalTicks < maxTicks)
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
|
||||
auto write_flux = [&](unsigned maxTicks = ~0u)
|
||||
{
|
||||
unsigned ticksSinceLastPulse = 0;
|
||||
ticksSinceLastPulse += ticks;
|
||||
totalTicks += ticks;
|
||||
|
||||
while (!fmr.eof() && totalTicks < maxTicks)
|
||||
if (event & F_BIT_PULSE)
|
||||
{
|
||||
write_one_flux(ticksSinceLastPulse);
|
||||
ticksSinceLastPulse = 0;
|
||||
}
|
||||
|
||||
if (event & F_BIT_INDEX && revolution == 0)
|
||||
{
|
||||
loopPoint = totalTicks;
|
||||
revolution += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (is_image(fluxmap))
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
|
||||
ticksSinceLastPulse += ticks;
|
||||
totalTicks += ticks;
|
||||
|
||||
if (event & F_BIT_PULSE)
|
||||
{
|
||||
write_one_flux(ticksSinceLastPulse);
|
||||
ticksSinceLastPulse = 0;
|
||||
}
|
||||
|
||||
if (event & F_BIT_INDEX && revolution == 0)
|
||||
{
|
||||
loopPoint = totalTicks;
|
||||
revolution += 1;
|
||||
}
|
||||
// A timing stream with no index represents exactly one
|
||||
// revolution with no index. However, a2r nominally contains 450
|
||||
// degress of rotation, 250ms at 300rpm.
|
||||
write_flux();
|
||||
loopPoint = totalTicks;
|
||||
fmr.rewind();
|
||||
revolution += 1;
|
||||
write_flux(totalTicks * 5 / 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have an index, so this is real from a floppy and should be
|
||||
// "one revolution plus a bit"
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
write_flux();
|
||||
}
|
||||
};
|
||||
|
||||
if (is_image(fluxmap))
|
||||
{
|
||||
// A timing stream with no index represents exactly one
|
||||
// revolution with no index. However, a2r nominally contains 450
|
||||
// degress of rotation, 250ms at 300rpm.
|
||||
write_flux();
|
||||
loopPoint = totalTicks;
|
||||
fmr.rewind();
|
||||
revolution += 1;
|
||||
write_flux(totalTicks * 5 / 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have an index, so this is real from a floppy and should be
|
||||
// "one revolution plus a bit"
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
write_flux();
|
||||
uint32_t chunk_size = 10 + trackBytes.size();
|
||||
|
||||
_strmWriter.write_8(cylinder);
|
||||
_strmWriter.write_8(A2R_TIMING);
|
||||
_strmWriter.write_le32(trackBytes.size());
|
||||
_strmWriter.write_le32(ticks_to_a2r(loopPoint));
|
||||
_strmWriter += trackBytes;
|
||||
}
|
||||
|
||||
uint32_t chunk_size = 10 + trackBytes.size();
|
||||
operator std::string() const override
|
||||
{
|
||||
return fmt::format("a2r({})", _config.filename());
|
||||
}
|
||||
|
||||
_strmWriter.write_8(cylinder);
|
||||
_strmWriter.write_8(A2R_TIMING);
|
||||
_strmWriter.write_le32(trackBytes.size());
|
||||
_strmWriter.write_le32(ticks_to_a2r(loopPoint));
|
||||
_strmWriter += trackBytes;
|
||||
}
|
||||
|
||||
operator std::string() const override
|
||||
{
|
||||
return fmt::format("a2r({})", _config.filename());
|
||||
}
|
||||
|
||||
private:
|
||||
const A2RFluxSinkProto& _config;
|
||||
Bytes _bytes;
|
||||
ByteWriter _writer;
|
||||
Bytes _strmBytes;
|
||||
ByteWriter _strmWriter{_strmBytes.writer()};
|
||||
std::map<std::string, std::string> _metadata;
|
||||
};
|
||||
private:
|
||||
const A2RFluxSinkProto& _config;
|
||||
Bytes _bytes;
|
||||
ByteWriter _writer;
|
||||
Bytes _strmBytes;
|
||||
ByteWriter _strmWriter{_strmBytes.writer()};
|
||||
std::map<std::string, std::string> _metadata;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createA2RFluxSink(
|
||||
@@ -226,4 +226,3 @@ std::unique_ptr<FluxSink> FluxSink::createA2RFluxSink(
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new A2RFluxSink(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -15,87 +14,84 @@
|
||||
class AuFluxSink : public FluxSink
|
||||
{
|
||||
public:
|
||||
AuFluxSink(const AuFluxSinkProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
AuFluxSink(const AuFluxSinkProto& config): _config(config) {}
|
||||
|
||||
~AuFluxSink()
|
||||
{
|
||||
std::cerr << "Warning: do not play these files, or you will break your speakers"
|
||||
" and/or ears!\n";
|
||||
}
|
||||
~AuFluxSink()
|
||||
{
|
||||
std::cerr << "Warning: do not play these files, or you will break your "
|
||||
"speakers and/or ears!\n";
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
unsigned totalTicks = fluxmap.ticks() + 2;
|
||||
unsigned channels = _config.index_markers() ? 2 : 1;
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
unsigned totalTicks = fluxmap.ticks() + 2;
|
||||
unsigned channels = _config.index_markers() ? 2 : 1;
|
||||
|
||||
mkdir(_config.directory().c_str(), 0744);
|
||||
std::ofstream of(
|
||||
fmt::format("{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
mkdir(_config.directory().c_str(), 0744);
|
||||
std::ofstream of(
|
||||
fmt::format(
|
||||
"{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
error("cannot open output file");
|
||||
|
||||
/* Write header */
|
||||
/* Write header */
|
||||
|
||||
{
|
||||
Bytes header;
|
||||
header.resize(24);
|
||||
ByteWriter bw(header);
|
||||
{
|
||||
Bytes header;
|
||||
header.resize(24);
|
||||
ByteWriter bw(header);
|
||||
|
||||
bw.write_be32(0x2e736e64);
|
||||
bw.write_be32(24);
|
||||
bw.write_be32(totalTicks * channels);
|
||||
bw.write_be32(2); /* 8-bit PCM */
|
||||
bw.write_be32(TICK_FREQUENCY);
|
||||
bw.write_be32(channels); /* channels */
|
||||
bw.write_be32(0x2e736e64);
|
||||
bw.write_be32(24);
|
||||
bw.write_be32(totalTicks * channels);
|
||||
bw.write_be32(2); /* 8-bit PCM */
|
||||
bw.write_be32(TICK_FREQUENCY);
|
||||
bw.write_be32(channels); /* channels */
|
||||
|
||||
of.write((const char*) header.cbegin(), header.size());
|
||||
}
|
||||
of.write((const char*)header.cbegin(), header.size());
|
||||
}
|
||||
|
||||
/* Write data */
|
||||
/* Write data */
|
||||
|
||||
{
|
||||
Bytes data;
|
||||
data.resize(totalTicks * channels);
|
||||
memset(data.begin(), 0x80, data.size());
|
||||
{
|
||||
Bytes data;
|
||||
data.resize(totalTicks * channels);
|
||||
memset(data.begin(), 0x80, data.size());
|
||||
|
||||
FluxmapReader fmr(fluxmap);
|
||||
unsigned timestamp = 0;
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
if (fmr.eof())
|
||||
break;
|
||||
timestamp += ticks;
|
||||
FluxmapReader fmr(fluxmap);
|
||||
unsigned timestamp = 0;
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
if (fmr.eof())
|
||||
break;
|
||||
timestamp += ticks;
|
||||
|
||||
if (event & F_BIT_PULSE)
|
||||
data[timestamp*channels + 0] = 0x7f;
|
||||
if (_config.index_markers() && (event & F_BIT_INDEX))
|
||||
data[timestamp*channels + 1] = 0x7f;
|
||||
}
|
||||
if (event & F_BIT_PULSE)
|
||||
data[timestamp * channels + 0] = 0x7f;
|
||||
if (_config.index_markers() && (event & F_BIT_INDEX))
|
||||
data[timestamp * channels + 1] = 0x7f;
|
||||
}
|
||||
|
||||
of.write((const char*) data.cbegin(), data.size());
|
||||
}
|
||||
of.write((const char*)data.cbegin(), data.size());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
operator std::string () const override
|
||||
{
|
||||
return fmt::format("au({})", _config.directory());
|
||||
}
|
||||
operator std::string() const override
|
||||
{
|
||||
return fmt::format("au({})", _config.directory());
|
||||
}
|
||||
|
||||
private:
|
||||
const AuFluxSinkProto& _config;
|
||||
const AuFluxSinkProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createAuFluxSink(const AuFluxSinkProto& config)
|
||||
std::unique_ptr<FluxSink> FluxSink::createAuFluxSink(
|
||||
const AuFluxSinkProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new AuFluxSink(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/fl2.pb.h"
|
||||
#include "fl2.h"
|
||||
#include <fstream>
|
||||
@@ -27,7 +26,7 @@ public:
|
||||
{
|
||||
std::ofstream of(filename);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
error("cannot open output file");
|
||||
of.close();
|
||||
std::filesystem::remove(filename);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "lib/config.pb.h"
|
||||
#include "proto.h"
|
||||
#include "utils.h"
|
||||
#include "fmt/format.h"
|
||||
#include <regex>
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
|
||||
@@ -30,7 +29,7 @@ std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
|
||||
return createFl2FluxSink(config.fl2());
|
||||
|
||||
default:
|
||||
Error() << "bad output disk config";
|
||||
error("bad output disk config");
|
||||
return std::unique_ptr<FluxSink>();
|
||||
}
|
||||
}
|
||||
@@ -89,5 +88,5 @@ void FluxSink::updateConfigForFilename(
|
||||
}
|
||||
}
|
||||
|
||||
Error() << fmt::format("unrecognised flux filename '{}'", filename);
|
||||
error("unrecognised flux filename '{}'", filename);
|
||||
}
|
||||
|
||||
@@ -7,54 +7,50 @@
|
||||
#include "fluxsink/fluxsink.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "lib/readerwriter.cc"
|
||||
#include "fmt/format.h"
|
||||
|
||||
class HardwareFluxSink : public FluxSink
|
||||
{
|
||||
public:
|
||||
HardwareFluxSink(const HardwareFluxSinkProto& conf):
|
||||
_config(conf)
|
||||
HardwareFluxSink(const HardwareFluxSinkProto& conf): _config(conf)
|
||||
{
|
||||
nanoseconds_t oneRevolution;
|
||||
measureDiskRotation(oneRevolution, _hardSectorThreshold);
|
||||
nanoseconds_t oneRevolution;
|
||||
measureDiskRotation(oneRevolution, _hardSectorThreshold);
|
||||
}
|
||||
|
||||
~HardwareFluxSink()
|
||||
{
|
||||
}
|
||||
~HardwareFluxSink() {}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int side, const Fluxmap& fluxmap) override
|
||||
{
|
||||
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
|
||||
#if 0
|
||||
usbSetDrive(config.drive().drive(),
|
||||
config.drive().high_density(),
|
||||
config.drive().index_mode());
|
||||
#if 0
|
||||
if (fluxSourceSinkFortyTrack)
|
||||
{
|
||||
if (track & 1)
|
||||
Error() << "cannot write to odd physical tracks in 40-track mode";
|
||||
error("cannot write to odd physical tracks in 40-track mode");
|
||||
usbSeek(track / 2);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
usbSeek(track);
|
||||
#endif
|
||||
usbSeek(track);
|
||||
|
||||
return usbWrite(side, fluxmap.rawBytes(), _hardSectorThreshold);
|
||||
}
|
||||
|
||||
operator std::string () const
|
||||
{
|
||||
return fmt::format("drive {}", config.drive().drive());
|
||||
}
|
||||
operator std::string() const
|
||||
{
|
||||
return fmt::format("drive {}", config.drive().drive());
|
||||
}
|
||||
|
||||
private:
|
||||
const HardwareFluxSinkProto& _config;
|
||||
nanoseconds_t _hardSectorThreshold;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink(const HardwareFluxSinkProto& config)
|
||||
std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink(
|
||||
const HardwareFluxSinkProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new HardwareFluxSink(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxmap.h"
|
||||
#include "layout.h"
|
||||
#include "scp.h"
|
||||
#include "lib/logger.h"
|
||||
#include <fstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -58,15 +58,14 @@ public:
|
||||
if (config.tpi() != 48)
|
||||
_fileheader.flags |= SCP_FLAG_96TPI;
|
||||
_fileheader.cell_width = 0;
|
||||
if ((minSide == 0) && (maxSide == 0))
|
||||
_fileheader.heads = 1;
|
||||
else if ((minSide == 1) && (maxSide == 1))
|
||||
_fileheader.heads = 2;
|
||||
else
|
||||
_fileheader.heads = 0;
|
||||
if ((minSide == 0) && (maxSide == 0))
|
||||
_fileheader.heads = 1;
|
||||
else if ((minSide == 1) && (maxSide == 1))
|
||||
_fileheader.heads = 2;
|
||||
else
|
||||
_fileheader.heads = 0;
|
||||
|
||||
std::cout << fmt::format(
|
||||
"SCP: writing 96 tpi {} file containing {} tracks\n",
|
||||
log("SCP: writing 96 tpi {} file containing {} tracks",
|
||||
(minSide == maxSide) ? "single sided" : "double sided",
|
||||
_fileheader.end_track - _fileheader.start_track + 1);
|
||||
}
|
||||
@@ -80,10 +79,10 @@ public:
|
||||
appendChecksum(checksum, _trackdata);
|
||||
write_le32(_fileheader.checksum, checksum);
|
||||
|
||||
std::cout << "SCP: writing output file...\n";
|
||||
log("SCP: writing output file");
|
||||
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
error("cannot open output file");
|
||||
of.write((const char*)&_fileheader, sizeof(_fileheader));
|
||||
_trackdata.writeTo(of);
|
||||
of.close();
|
||||
@@ -98,9 +97,8 @@ public:
|
||||
|
||||
if (strack >= std::size(_fileheader.track))
|
||||
{
|
||||
std::cout << fmt::format(
|
||||
"SCP: cannot write track {} head {}, "
|
||||
"there are not not enough Track Data Headers.\n",
|
||||
log("SCP: cannot write track {} head {}, there are not not enough "
|
||||
"Track Data Headers.",
|
||||
track,
|
||||
head);
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -15,67 +14,67 @@
|
||||
class VcdFluxSink : public FluxSink
|
||||
{
|
||||
public:
|
||||
VcdFluxSink(const VcdFluxSinkProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
VcdFluxSink(const VcdFluxSinkProto& config): _config(config) {}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
mkdir(_config.directory().c_str(), 0744);
|
||||
std::ofstream of(
|
||||
fmt::format("{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
mkdir(_config.directory().c_str(), 0744);
|
||||
std::ofstream of(
|
||||
fmt::format(
|
||||
"{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
error("cannot open output file");
|
||||
|
||||
of << "$timescale 1ns $end\n"
|
||||
<< "$var wire 1 i index $end\n"
|
||||
<< "$var wire 1 p pulse $end\n"
|
||||
<< "$upscope $end\n"
|
||||
<< "$enddefinitions $end\n"
|
||||
<< "$dumpvars 0i 0p $end\n";
|
||||
of << "$timescale 1ns $end\n"
|
||||
<< "$var wire 1 i index $end\n"
|
||||
<< "$var wire 1 p pulse $end\n"
|
||||
<< "$upscope $end\n"
|
||||
<< "$enddefinitions $end\n"
|
||||
<< "$dumpvars 0i 0p $end\n";
|
||||
|
||||
FluxmapReader fmr(fluxmap);
|
||||
unsigned timestamp = 0;
|
||||
unsigned lasttimestamp = 0;
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
if (fmr.eof())
|
||||
break;
|
||||
FluxmapReader fmr(fluxmap);
|
||||
unsigned timestamp = 0;
|
||||
unsigned lasttimestamp = 0;
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
if (fmr.eof())
|
||||
break;
|
||||
|
||||
unsigned newtimestamp = timestamp + ticks;
|
||||
if (newtimestamp != lasttimestamp)
|
||||
{
|
||||
of << fmt::format("\n#{} 0i 0p\n", (uint64_t)((lasttimestamp+1) * NS_PER_TICK));
|
||||
timestamp = newtimestamp;
|
||||
of << fmt::format("#{} ", (uint64_t)(timestamp * NS_PER_TICK));
|
||||
}
|
||||
unsigned newtimestamp = timestamp + ticks;
|
||||
if (newtimestamp != lasttimestamp)
|
||||
{
|
||||
of << fmt::format("\n#{} 0i 0p\n",
|
||||
(uint64_t)((lasttimestamp + 1) * NS_PER_TICK));
|
||||
timestamp = newtimestamp;
|
||||
of << fmt::format("#{} ", (uint64_t)(timestamp * NS_PER_TICK));
|
||||
}
|
||||
|
||||
if (event & F_BIT_PULSE)
|
||||
of << "1p ";
|
||||
if (event & F_BIT_INDEX)
|
||||
of << "1i ";
|
||||
if (event & F_BIT_PULSE)
|
||||
of << "1p ";
|
||||
if (event & F_BIT_INDEX)
|
||||
of << "1i ";
|
||||
|
||||
lasttimestamp = timestamp;
|
||||
}
|
||||
of << "\n";
|
||||
}
|
||||
lasttimestamp = timestamp;
|
||||
}
|
||||
of << "\n";
|
||||
}
|
||||
|
||||
operator std::string () const
|
||||
{
|
||||
return fmt::format("vcd({})", _config.directory());
|
||||
}
|
||||
operator std::string() const
|
||||
{
|
||||
return fmt::format("vcd({})", _config.directory());
|
||||
}
|
||||
|
||||
private:
|
||||
const VcdFluxSinkProto& _config;
|
||||
const VcdFluxSinkProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createVcdFluxSink(const VcdFluxSinkProto& config)
|
||||
std::unique_ptr<FluxSink> FluxSink::createVcdFluxSink(
|
||||
const VcdFluxSinkProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new VcdFluxSink(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
|
||||
struct A2Rv2Flux
|
||||
@@ -121,7 +120,7 @@ public:
|
||||
}
|
||||
|
||||
default:
|
||||
Error() << "unsupported A2R version";
|
||||
error("unsupported A2R version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +140,7 @@ public:
|
||||
}
|
||||
|
||||
default:
|
||||
Error() << "unsupported A2R version";
|
||||
error("unsupported A2R version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +163,7 @@ private:
|
||||
offset += br.read_le32() + 8;
|
||||
}
|
||||
|
||||
Error() << "A2R file missing chunk";
|
||||
error("A2R file missing chunk");
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
|
||||
struct CwfHeader
|
||||
@@ -14,7 +13,7 @@ struct CwfHeader
|
||||
uint8_t version; // version of this file
|
||||
uint8_t clock_rate; // clock rate used: 0, 1, 2 (2 = 28MHz)
|
||||
uint8_t drive_type; // type of drive
|
||||
uint8_t tracks; // number of tracks
|
||||
uint8_t tracks; // number of tracks
|
||||
uint8_t sides; // number of sides
|
||||
uint8_t index_mark; // nonzero if index marks are included
|
||||
uint8_t step; // track stepping interval
|
||||
@@ -24,7 +23,7 @@ struct CwfHeader
|
||||
|
||||
struct CwfTrack
|
||||
{
|
||||
uint8_t track; // sequential
|
||||
uint8_t track; // sequential
|
||||
uint8_t side;
|
||||
uint8_t unused[2];
|
||||
uint8_t length[4]; // little-endian
|
||||
@@ -33,109 +32,120 @@ struct CwfTrack
|
||||
class CwfFluxSource : public TrivialFluxSource
|
||||
{
|
||||
public:
|
||||
CwfFluxSource(const CwfFluxSourceProto& config):
|
||||
_config(config)
|
||||
CwfFluxSource(const CwfFluxSourceProto& config): _config(config)
|
||||
{
|
||||
_if.open(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!_if.is_open())
|
||||
Error() << fmt::format("cannot open input file '{}': {}",
|
||||
_config.filename(), strerror(errno));
|
||||
_if.open(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!_if.is_open())
|
||||
error("cannot open input file '{}': {}",
|
||||
_config.filename(),
|
||||
strerror(errno));
|
||||
|
||||
_if.read((char*) &_header, sizeof(_header));
|
||||
check_for_error();
|
||||
_if.read((char*)&_header, sizeof(_header));
|
||||
check_for_error();
|
||||
|
||||
if ((_header.file_id[0] != 'C')
|
||||
|| (_header.file_id[1] != 'W')
|
||||
|| (_header.file_id[2] != 'S')
|
||||
|| (_header.file_id[3] != 'F'))
|
||||
Error() << "input not a CWF file";
|
||||
if ((_header.file_id[0] != 'C') || (_header.file_id[1] != 'W') ||
|
||||
(_header.file_id[2] != 'S') || (_header.file_id[3] != 'F'))
|
||||
error("input not a CWF file");
|
||||
|
||||
switch (_header.clock_rate)
|
||||
{
|
||||
case 1: _clockRate = 14161000.0; break;
|
||||
case 2: _clockRate = 28322000.0; break;
|
||||
default:
|
||||
Error() << "unsupported clock rate";
|
||||
}
|
||||
switch (_header.clock_rate)
|
||||
{
|
||||
case 1:
|
||||
_clockRate = 14161000.0;
|
||||
break;
|
||||
case 2:
|
||||
_clockRate = 28322000.0;
|
||||
break;
|
||||
default:
|
||||
error("unsupported clock rate");
|
||||
}
|
||||
|
||||
std::cout << fmt::format("CWF {}x{} = {} tracks, {} sides\n",
|
||||
_header.tracks, _header.step, _header.tracks*_header.step, _header.sides);
|
||||
std::cout << fmt::format("CWF sample clock rate: {} MHz\n", _clockRate / 1e6);
|
||||
std::cout << fmt::format("CWF {}x{} = {} tracks, {} sides\n",
|
||||
_header.tracks,
|
||||
_header.step,
|
||||
_header.tracks * _header.step,
|
||||
_header.sides);
|
||||
std::cout << fmt::format(
|
||||
"CWF sample clock rate: {} MHz\n", _clockRate / 1e6);
|
||||
|
||||
int tracks = _header.tracks*_header.sides;
|
||||
for (int i=0; i<tracks; i++)
|
||||
{
|
||||
CwfTrack trackheader;
|
||||
_if.read((char*) &trackheader, sizeof(trackheader));
|
||||
check_for_error();
|
||||
int tracks = _header.tracks * _header.sides;
|
||||
for (int i = 0; i < tracks; i++)
|
||||
{
|
||||
CwfTrack trackheader;
|
||||
_if.read((char*)&trackheader, sizeof(trackheader));
|
||||
check_for_error();
|
||||
|
||||
uint32_t length = Bytes(trackheader.length, 4).reader().read_le32() - sizeof(CwfTrack);
|
||||
unsigned track_number = trackheader.track * _header.step;
|
||||
uint32_t length =
|
||||
Bytes(trackheader.length, 4).reader().read_le32() -
|
||||
sizeof(CwfTrack);
|
||||
unsigned track_number = trackheader.track * _header.step;
|
||||
|
||||
off_t pos = _if.tellg();
|
||||
_trackOffsets[std::make_pair(track_number, trackheader.side)] = std::make_pair(pos, length);
|
||||
_if.seekg(pos + length);
|
||||
}
|
||||
}
|
||||
off_t pos = _if.tellg();
|
||||
_trackOffsets[std::make_pair(track_number, trackheader.side)] =
|
||||
std::make_pair(pos, length);
|
||||
_if.seekg(pos + length);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side)
|
||||
{
|
||||
const auto& p = _trackOffsets.find(std::make_pair(track, side));
|
||||
if (p == _trackOffsets.end())
|
||||
return std::make_unique<const Fluxmap>();
|
||||
const auto& p = _trackOffsets.find(std::make_pair(track, side));
|
||||
if (p == _trackOffsets.end())
|
||||
return std::make_unique<const Fluxmap>();
|
||||
|
||||
off_t pos = p->second.first;;
|
||||
size_t length = p->second.second;
|
||||
_if.seekg(pos);
|
||||
off_t pos = p->second.first;
|
||||
;
|
||||
size_t length = p->second.second;
|
||||
_if.seekg(pos);
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
uint32_t pending = 0;
|
||||
bool oldindex = true;
|
||||
for (size_t cursor = 0; cursor < length; cursor++)
|
||||
{
|
||||
uint32_t b = _if.get();
|
||||
bool index = !!(b & 0x80);
|
||||
b &= 0x7f;
|
||||
if (b == 0x7f)
|
||||
{
|
||||
pending += 0x7f;
|
||||
continue;
|
||||
}
|
||||
b += pending;
|
||||
pending = 0;
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
uint32_t pending = 0;
|
||||
bool oldindex = true;
|
||||
for (size_t cursor = 0; cursor < length; cursor++)
|
||||
{
|
||||
uint32_t b = _if.get();
|
||||
bool index = !!(b & 0x80);
|
||||
b &= 0x7f;
|
||||
if (b == 0x7f)
|
||||
{
|
||||
pending += 0x7f;
|
||||
continue;
|
||||
}
|
||||
b += pending;
|
||||
pending = 0;
|
||||
|
||||
double interval_us = b * (1e6/_clockRate);
|
||||
fluxmap->appendInterval(interval_us / US_PER_TICK);
|
||||
fluxmap->appendPulse();
|
||||
double interval_us = b * (1e6 / _clockRate);
|
||||
fluxmap->appendInterval(interval_us / US_PER_TICK);
|
||||
fluxmap->appendPulse();
|
||||
|
||||
if (index && !oldindex)
|
||||
fluxmap->appendIndex();
|
||||
oldindex = index;
|
||||
}
|
||||
check_for_error();
|
||||
if (index && !oldindex)
|
||||
fluxmap->appendIndex();
|
||||
oldindex = index;
|
||||
}
|
||||
check_for_error();
|
||||
|
||||
return fluxmap;
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
|
||||
private:
|
||||
void check_for_error()
|
||||
{
|
||||
if (_if.fail())
|
||||
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
|
||||
}
|
||||
void check_for_error()
|
||||
{
|
||||
if (_if.fail())
|
||||
error("SCP read I/O error: {}", strerror(errno));
|
||||
}
|
||||
|
||||
private:
|
||||
const CwfFluxSourceProto& _config;
|
||||
std::ifstream _if;
|
||||
CwfHeader _header;
|
||||
nanoseconds_t _clockRate;
|
||||
std::map<std::pair<int, int>, std::pair<off_t, size_t>> _trackOffsets;
|
||||
std::ifstream _if;
|
||||
CwfHeader _header;
|
||||
nanoseconds_t _clockRate;
|
||||
std::map<std::pair<int, int>, std::pair<off_t, size_t>> _trackOffsets;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createCwfFluxSource(const CwfFluxSourceProto& config)
|
||||
std::unique_ptr<FluxSource> FluxSource::createCwfFluxSource(
|
||||
const CwfFluxSourceProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSource>(new CwfFluxSource(config));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "fluxmap.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
class EraseFluxSource : public TrivialFluxSource
|
||||
{
|
||||
@@ -13,16 +12,14 @@ public:
|
||||
public:
|
||||
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
|
||||
{
|
||||
return std::unique_ptr<const Fluxmap>();
|
||||
return std::unique_ptr<const Fluxmap>();
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createEraseFluxSource(const EraseFluxSourceProto& config)
|
||||
std::unique_ptr<FluxSource> FluxSource::createEraseFluxSource(
|
||||
const EraseFluxSourceProto& config)
|
||||
{
|
||||
return std::make_unique<EraseFluxSource>(config);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "proto.h"
|
||||
#include "fl2.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxmap.h"
|
||||
#include <fstream>
|
||||
|
||||
@@ -57,7 +56,7 @@ private:
|
||||
void check_for_error(std::ifstream& ifs)
|
||||
{
|
||||
if (ifs.fail())
|
||||
Error() << fmt::format("FL2 read I/O error: {}", strerror(errno));
|
||||
error("FL2 read I/O error: {}", strerror(errno));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "lib/config.pb.h"
|
||||
#include "proto.h"
|
||||
#include "utils.h"
|
||||
#include "fmt/format.h"
|
||||
#include <regex>
|
||||
|
||||
static bool ends_with(const std::string& value, const std::string& ending)
|
||||
@@ -47,7 +46,7 @@ std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
|
||||
return createFlxFluxSource(config.flx());
|
||||
|
||||
default:
|
||||
Error() << "bad input disk configuration";
|
||||
error("bad input disk configuration");
|
||||
return std::unique_ptr<FluxSource>();
|
||||
}
|
||||
}
|
||||
@@ -123,7 +122,7 @@ void FluxSource::updateConfigForFilename(
|
||||
}
|
||||
}
|
||||
|
||||
Error() << fmt::format("unrecognised flux filename '{}'", filename);
|
||||
error("unrecognised flux filename '{}'", filename);
|
||||
}
|
||||
|
||||
class TrivialFluxSourceIterator : public FluxSourceIterator
|
||||
|
||||
@@ -79,7 +79,7 @@ class EmptyFluxSourceIterator : public FluxSourceIterator
|
||||
|
||||
std::unique_ptr<const Fluxmap> next() override
|
||||
{
|
||||
Error() << "no flux to read";
|
||||
error("no flux to read");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "kryoflux.h"
|
||||
#include "protocol.h"
|
||||
#include "lib/fluxsource/flx.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes)
|
||||
{
|
||||
@@ -14,7 +13,7 @@ std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes)
|
||||
for (;;)
|
||||
{
|
||||
if (br.eof())
|
||||
Error() << fmt::format("malformed FLX stream");
|
||||
error("malformed FLX stream");
|
||||
uint8_t b = br.read_8();
|
||||
if (b == 0)
|
||||
break;
|
||||
@@ -36,7 +35,7 @@ std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes)
|
||||
default:
|
||||
{
|
||||
if (b < 32)
|
||||
Error() << fmt::format("unknown FLX opcode 0x{:2x}", b);
|
||||
error("unknown FLX opcode 0x{:2x}", b);
|
||||
nanoseconds_t interval = b * FLX_TICK_NS;
|
||||
fluxmap->appendInterval(interval / NS_PER_TICK);
|
||||
fluxmap->appendPulse();
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "lib/readerwriter.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
class HardwareFluxSource : public FluxSource
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "fluxmap.h"
|
||||
#include "kryoflux.h"
|
||||
#include "protocol.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
@@ -17,17 +16,20 @@ static bool has_suffix(const std::string& haystack, const std::string& needle)
|
||||
{
|
||||
if (needle.length() > haystack.length())
|
||||
return false;
|
||||
|
||||
return haystack.compare(haystack.length() - needle.length(), needle.length(), needle) == 0;
|
||||
|
||||
return haystack.compare(haystack.length() - needle.length(),
|
||||
needle.length(),
|
||||
needle) == 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> readStream(const std::string& dir, unsigned track, unsigned side)
|
||||
std::unique_ptr<Fluxmap> readStream(
|
||||
const std::string& dir, unsigned track, unsigned side)
|
||||
{
|
||||
std::string suffix = fmt::format("{:02}.{}.raw", track, side);
|
||||
|
||||
DIR* dirp = opendir(dir.c_str());
|
||||
if (!dirp)
|
||||
Error() << fmt::format("cannot access path '{}'", dir);
|
||||
error("cannot access path '{}'", dir);
|
||||
|
||||
std::string filename;
|
||||
for (;;)
|
||||
@@ -35,18 +37,18 @@ std::unique_ptr<Fluxmap> readStream(const std::string& dir, unsigned track, unsi
|
||||
struct dirent* de = readdir(dirp);
|
||||
if (!de)
|
||||
break;
|
||||
|
||||
|
||||
if (has_suffix(de->d_name, suffix))
|
||||
{
|
||||
if (!filename.empty())
|
||||
Error() << fmt::format("data is ambiguous --- multiple files end in {}", suffix);
|
||||
error("data is ambiguous --- multiple files end in {}", suffix);
|
||||
filename = fmt::format("{}/{}", dir, de->d_name);
|
||||
}
|
||||
}
|
||||
closedir(dirp);
|
||||
|
||||
if (filename.empty())
|
||||
Error() << fmt::format("failed to find track {} side {} in {}", track, side, dir);
|
||||
error("failed to find track {} side {} in {}", track, side, dir);
|
||||
|
||||
return readStream(filename);
|
||||
}
|
||||
@@ -55,7 +57,7 @@ std::unique_ptr<Fluxmap> readStream(const std::string& filename)
|
||||
{
|
||||
std::ifstream f(filename, std::ios::in | std::ios::binary);
|
||||
if (!f.is_open())
|
||||
Error() << fmt::format("cannot open input file '{}'", filename);
|
||||
error("cannot open input file '{}'", filename);
|
||||
|
||||
Bytes bytes;
|
||||
ByteWriter bw(bytes);
|
||||
@@ -69,7 +71,7 @@ std::unique_ptr<Fluxmap> readStream(const Bytes& bytes)
|
||||
ByteReader br(bytes);
|
||||
|
||||
/* Pass 1: scan the stream looking for index marks. */
|
||||
|
||||
|
||||
std::set<uint32_t> indexmarks;
|
||||
br.seek(0);
|
||||
while (!br.eof())
|
||||
@@ -119,7 +121,8 @@ std::unique_ptr<Fluxmap> readStream(const Bytes& bytes)
|
||||
}
|
||||
else if (b == 0x0b)
|
||||
{
|
||||
/* Ovl16: the next block is 0x10000 sclks longer than normal. */
|
||||
/* Ovl16: the next block is 0x10000 sclks longer than
|
||||
* normal. */
|
||||
len = 0;
|
||||
}
|
||||
else if (b == 0x0c)
|
||||
@@ -133,8 +136,9 @@ std::unique_ptr<Fluxmap> readStream(const Bytes& bytes)
|
||||
len = 0;
|
||||
}
|
||||
else
|
||||
Error() << fmt::format(
|
||||
"unknown stream block byte 0x{:02x} at 0x{:08x}", b, (uint64_t)br.pos-1);
|
||||
error("unknown stream block byte 0x{:02x} at 0x{:08x}",
|
||||
b,
|
||||
(uint64_t)br.pos - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +201,7 @@ finished_pass_1:
|
||||
if ((b >= 0x00) && (b <= 0x07))
|
||||
{
|
||||
/* Flux2: double byte value */
|
||||
b = (b<<8) | br.read_8();
|
||||
b = (b << 8) | br.read_8();
|
||||
writeFlux(extrasclks + b);
|
||||
extrasclks = 0;
|
||||
}
|
||||
@@ -217,7 +221,8 @@ finished_pass_1:
|
||||
}
|
||||
else if (b == 0x0b)
|
||||
{
|
||||
/* Ovl16: the next block is 0x10000 sclks longer than normal. */
|
||||
/* Ovl16: the next block is 0x10000 sclks longer than
|
||||
* normal. */
|
||||
extrasclks += 0x10000;
|
||||
}
|
||||
else if (b == 0x0c)
|
||||
@@ -234,14 +239,15 @@ finished_pass_1:
|
||||
extrasclks = 0;
|
||||
}
|
||||
else
|
||||
Error() << fmt::format(
|
||||
"unknown stream block byte 0x{:02x} at 0x{:08x}", b, (uint64_t)br.pos-1);
|
||||
error("unknown stream block byte 0x{:02x} at 0x{:08x}",
|
||||
b,
|
||||
(uint64_t)br.pos - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finished_pass_2:
|
||||
if (!br.eof())
|
||||
Error() << "I/O error reading stream";
|
||||
error("I/O error reading stream");
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ class KryofluxFluxSource : public TrivialFluxSource
|
||||
public:
|
||||
KryofluxFluxSource(const KryofluxFluxSourceProto& config):
|
||||
_path(config.directory())
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
|
||||
@@ -23,7 +24,8 @@ private:
|
||||
const std::string _path;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createKryofluxFluxSource(const KryofluxFluxSourceProto& config)
|
||||
std::unique_ptr<FluxSource> FluxSource::createKryofluxFluxSource(
|
||||
const KryofluxFluxSourceProto& config)
|
||||
{
|
||||
return std::make_unique<KryofluxFluxSource>(config);
|
||||
}
|
||||
|
||||
@@ -4,47 +4,44 @@
|
||||
#include "lib/fluxsource/fluxsource.h"
|
||||
#include "lib/fluxmap.h"
|
||||
#include "lib/layout.h"
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
|
||||
class MemoryFluxSourceIterator : public FluxSourceIterator
|
||||
{
|
||||
public:
|
||||
MemoryFluxSourceIterator(const TrackFlux& track):
|
||||
_track(track)
|
||||
{}
|
||||
MemoryFluxSourceIterator(const TrackFlux& track): _track(track) {}
|
||||
|
||||
bool hasNext() const override
|
||||
{
|
||||
return _count < _track.trackDatas.size();
|
||||
}
|
||||
bool hasNext() const override
|
||||
{
|
||||
return _count < _track.trackDatas.size();
|
||||
}
|
||||
|
||||
std::unique_ptr<const Fluxmap> next() override
|
||||
{
|
||||
auto bytes = _track.trackDatas[_count]->fluxmap->rawBytes();
|
||||
_count++;
|
||||
return std::make_unique<Fluxmap>(bytes);
|
||||
}
|
||||
std::unique_ptr<const Fluxmap> next() override
|
||||
{
|
||||
auto bytes = _track.trackDatas[_count]->fluxmap->rawBytes();
|
||||
_count++;
|
||||
return std::make_unique<Fluxmap>(bytes);
|
||||
}
|
||||
|
||||
private:
|
||||
const TrackFlux& _track;
|
||||
int _count = 0;
|
||||
const TrackFlux& _track;
|
||||
int _count = 0;
|
||||
};
|
||||
|
||||
class MemoryFluxSource : public FluxSource
|
||||
{
|
||||
public:
|
||||
MemoryFluxSource(const DiskFlux& flux): _flux(flux)
|
||||
{
|
||||
}
|
||||
MemoryFluxSource(const DiskFlux& flux): _flux(flux) {}
|
||||
|
||||
public:
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(int physicalTrack, int physicalSide) override
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(
|
||||
int physicalTrack, int physicalSide) override
|
||||
{
|
||||
for (const auto& trackFlux : _flux.tracks)
|
||||
{
|
||||
if ((trackFlux->trackInfo->physicalTrack == physicalTrack) && (trackFlux->trackInfo->physicalSide == physicalSide))
|
||||
return std::make_unique<MemoryFluxSourceIterator>(*trackFlux);
|
||||
if ((trackFlux->trackInfo->physicalTrack == physicalTrack) &&
|
||||
(trackFlux->trackInfo->physicalSide == physicalSide))
|
||||
return std::make_unique<MemoryFluxSourceIterator>(*trackFlux);
|
||||
}
|
||||
|
||||
return std::make_unique<EmptyFluxSourceIterator>();
|
||||
@@ -53,11 +50,11 @@ public:
|
||||
void recalibrate() {}
|
||||
|
||||
private:
|
||||
const DiskFlux& _flux;
|
||||
const DiskFlux& _flux;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createMemoryFluxSource(const DiskFlux& flux)
|
||||
std::unique_ptr<FluxSource> FluxSource::createMemoryFluxSource(
|
||||
const DiskFlux& flux)
|
||||
{
|
||||
return std::make_unique<MemoryFluxSource>(flux);
|
||||
return std::make_unique<MemoryFluxSource>(flux);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "scp.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/logger.h"
|
||||
#include <fstream>
|
||||
|
||||
static int trackno(int strack)
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
{
|
||||
_if.open(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!_if.is_open())
|
||||
Error() << fmt::format("cannot open input file '{}': {}",
|
||||
error("cannot open input file '{}': {}",
|
||||
_config.filename(),
|
||||
strerror(errno));
|
||||
|
||||
@@ -40,9 +40,9 @@ public:
|
||||
|
||||
if ((_header.file_id[0] != 'S') || (_header.file_id[1] != 'C') ||
|
||||
(_header.file_id[2] != 'P'))
|
||||
Error() << "input not a SCP file";
|
||||
error("input not a SCP file");
|
||||
|
||||
int tpi = (_header.flags & SCP_FLAG_96TPI) ? 96 : 48;
|
||||
int tpi = (_header.flags & SCP_FLAG_96TPI) ? 96 : 48;
|
||||
::config.set_tpi(tpi);
|
||||
|
||||
_resolution = 25 * (_header.resolution + 1);
|
||||
@@ -50,14 +50,14 @@ public:
|
||||
int endSide = (_header.heads == 1) ? 0 : 1;
|
||||
|
||||
if ((_header.cell_width != 0) && (_header.cell_width != 16))
|
||||
Error() << "currently only 16-bit cells in SCP files are supported";
|
||||
error("currently only 16-bit cells in SCP files are supported");
|
||||
|
||||
std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n",
|
||||
log("SCP tracks {}-{}, heads {}-{}",
|
||||
trackno(_header.start_track),
|
||||
trackno(_header.end_track),
|
||||
startSide,
|
||||
endSide);
|
||||
std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution);
|
||||
log("SCP sample resolution: {} ns", _resolution);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
if ((trackheader.track_id[0] != 'T') ||
|
||||
(trackheader.track_id[1] != 'R') ||
|
||||
(trackheader.track_id[2] != 'K'))
|
||||
Error() << "corrupt SCP file";
|
||||
error("corrupt SCP file");
|
||||
|
||||
std::vector<ScpTrackRevolution> revs(_header.revolutions);
|
||||
for (int revolution = 0; revolution < _header.revolutions; revolution++)
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
void check_for_error()
|
||||
{
|
||||
if (_if.fail())
|
||||
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
|
||||
error("SCP read I/O error: {}", strerror(errno));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -2,40 +2,39 @@
|
||||
#include "fluxmap.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
class TestPatternFluxSource : public TrivialFluxSource
|
||||
{
|
||||
public:
|
||||
TestPatternFluxSource(const TestPatternFluxSourceProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
~TestPatternFluxSource() {}
|
||||
|
||||
public:
|
||||
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
|
||||
{
|
||||
auto fluxmap = std::make_unique<Fluxmap>();
|
||||
auto fluxmap = std::make_unique<Fluxmap>();
|
||||
|
||||
while (fluxmap->duration() < (_config.sequence_length_us()*1000000.0))
|
||||
{
|
||||
fluxmap->appendInterval(_config.interval_us());
|
||||
fluxmap->appendPulse();
|
||||
}
|
||||
while (fluxmap->duration() < (_config.sequence_length_us() * 1000000.0))
|
||||
{
|
||||
fluxmap->appendInterval(_config.interval_us());
|
||||
fluxmap->appendPulse();
|
||||
}
|
||||
|
||||
return fluxmap;
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
|
||||
private:
|
||||
const TestPatternFluxSourceProto& _config;
|
||||
const TestPatternFluxSourceProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createTestPatternFluxSource(const TestPatternFluxSourceProto& config)
|
||||
std::unique_ptr<FluxSource> FluxSource::createTestPatternFluxSource(
|
||||
const TestPatternFluxSourceProto& config)
|
||||
{
|
||||
return std::make_unique<TestPatternFluxSource>(config);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
double getCurrentTime(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
return double(tv.tv_sec) + tv.tv_usec/1000000.0;
|
||||
return double(tv.tv_sec) + tv.tv_usec / 1000000.0;
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <climits>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#if defined(_WIN32) || defined(__WIN32__)
|
||||
#include <direct.h>
|
||||
@@ -41,29 +42,12 @@ struct ErrorException
|
||||
void print() const;
|
||||
};
|
||||
|
||||
class Error
|
||||
template <typename... Args>
|
||||
[[ noreturn ]]
|
||||
inline void error(fmt::string_view fstr, const Args&... args)
|
||||
{
|
||||
public:
|
||||
Error()
|
||||
{
|
||||
_stream << "Error: ";
|
||||
}
|
||||
|
||||
[[ noreturn ]] ~Error() noexcept(false)
|
||||
{
|
||||
throw ErrorException { _stream.str() };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Error& operator<<(T&& t)
|
||||
{
|
||||
_stream << t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::stringstream _stream;
|
||||
};
|
||||
throw ErrorException { fmt::format(fstr, args...) };
|
||||
}
|
||||
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
@@ -1,44 +1,41 @@
|
||||
#include "globals.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
void hexdump(std::ostream& stream, const Bytes& buffer)
|
||||
{
|
||||
size_t pos = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < buffer.size())
|
||||
{
|
||||
stream << fmt::format("{:05x} : ", pos);
|
||||
for (int i=0; i<16; i++)
|
||||
{
|
||||
if ((pos+i) < buffer.size())
|
||||
stream << fmt::format("{:02x} ", buffer[pos+i]);
|
||||
else
|
||||
stream << "-- ";
|
||||
}
|
||||
stream << " : ";
|
||||
for (int i=0; i<16; i++)
|
||||
{
|
||||
if ((pos+i) >= buffer.size())
|
||||
break;
|
||||
while (pos < buffer.size())
|
||||
{
|
||||
stream << fmt::format("{:05x} : ", pos);
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if ((pos + i) < buffer.size())
|
||||
stream << fmt::format("{:02x} ", buffer[pos + i]);
|
||||
else
|
||||
stream << "-- ";
|
||||
}
|
||||
stream << " : ";
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if ((pos + i) >= buffer.size())
|
||||
break;
|
||||
|
||||
uint8_t c = buffer[pos+i];
|
||||
if ((c >= 32) && (c <= 126))
|
||||
stream << (char)c;
|
||||
else
|
||||
stream << '.';
|
||||
}
|
||||
stream << std::endl;
|
||||
uint8_t c = buffer[pos + i];
|
||||
if ((c >= 32) && (c <= 126))
|
||||
stream << (char)c;
|
||||
else
|
||||
stream << '.';
|
||||
}
|
||||
stream << std::endl;
|
||||
|
||||
pos += 16;
|
||||
}
|
||||
pos += 16;
|
||||
}
|
||||
}
|
||||
|
||||
void hexdumpForSrp16(std::ostream& stream, const Bytes& buffer)
|
||||
{
|
||||
for (uint8_t byte : buffer)
|
||||
stream << fmt::format("{:02x}", byte);
|
||||
stream << std::endl;
|
||||
for (uint8_t byte : buffer)
|
||||
stream << fmt::format("{:02x}", byte);
|
||||
stream << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "fmt/format.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "proto.h"
|
||||
@@ -20,7 +19,7 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
inputFile.seekg(0, inputFile.beg);
|
||||
uint32_t begin = inputFile.tellg();
|
||||
@@ -36,7 +35,7 @@ public:
|
||||
unsigned numHeads = 1;
|
||||
unsigned numSectors = 0;
|
||||
|
||||
Logger() << fmt::format("D64: reading image with {} tracks, {} heads", numTracks, numHeads);
|
||||
log("D64: reading image with {} tracks, {} heads", numTracks, numHeads);
|
||||
|
||||
uint32_t offset = 0;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "proto.h"
|
||||
#include "logger.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -24,7 +23,7 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
Bytes header(0x24); // read first entry of track table as well
|
||||
inputFile.read((char*)header.begin(), header.size());
|
||||
@@ -35,7 +34,7 @@ public:
|
||||
std::string diskName = header.slice(0, 0x16);
|
||||
|
||||
if (diskName[0])
|
||||
Logger() << fmt::format("D88: disk name: {}", diskName);
|
||||
log("D88: disk name: {}", diskName);
|
||||
|
||||
ByteReader headerReader(header);
|
||||
|
||||
@@ -47,7 +46,7 @@ public:
|
||||
int diskSize = headerReader.seek(0x1c).read_le32();
|
||||
|
||||
if (diskSize > fileSize)
|
||||
Logger() << "D88: found multiple disk images. Only using first";
|
||||
log("D88: found multiple disk images. Only using first");
|
||||
|
||||
int trackTableEnd = headerReader.seek(0x20).read_le32();
|
||||
int trackTableSize = trackTableEnd - 0x20;
|
||||
@@ -59,25 +58,25 @@ public:
|
||||
|
||||
if (config.encoder().format_case() !=
|
||||
EncoderProto::FormatCase::FORMAT_NOT_SET)
|
||||
Logger() << "D88: overriding configured format";
|
||||
log("D88: overriding configured format");
|
||||
|
||||
auto ibm = config.mutable_encoder()->mutable_ibm();
|
||||
int clockRate = 500;
|
||||
if (mediaFlag == 0x20)
|
||||
{
|
||||
Logger() << "D88: forcing high density mode";
|
||||
config.mutable_drive()->set_high_density(true);
|
||||
config.set_tpi(96);
|
||||
log("D88: forcing high density mode");
|
||||
config.mutable_drive()->set_high_density(true);
|
||||
config.set_tpi(96);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger() << "D88: forcing single/double density mode";
|
||||
log("D88: forcing single/double density mode");
|
||||
clockRate = 300;
|
||||
config.mutable_drive()->set_high_density(false);
|
||||
config.set_tpi(48);
|
||||
config.mutable_drive()->set_high_density(false);
|
||||
config.set_tpi(48);
|
||||
}
|
||||
|
||||
auto layout = config.mutable_layout();
|
||||
auto layout = config.mutable_layout();
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (int track = 0; track < trackTableSize / 4; track++)
|
||||
{
|
||||
@@ -96,7 +95,7 @@ public:
|
||||
trackdata->set_target_clock_period_us(1e3 / clockRate);
|
||||
trackdata->set_target_rotational_period_ms(167);
|
||||
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto physical = layoutdata->mutable_physical();
|
||||
|
||||
for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack;
|
||||
@@ -116,26 +115,27 @@ public:
|
||||
int fddStatusCode = sectorHeaderReader.seek(8).read_8();
|
||||
int rpm = sectorHeaderReader.seek(13).read_8();
|
||||
int dataLength = sectorHeaderReader.seek(14).read_le16();
|
||||
if (dataLength < sectorSize) {
|
||||
if (dataLength < sectorSize)
|
||||
{
|
||||
dataLength = sectorSize;
|
||||
}
|
||||
// D88 provides much more sector information that is currently
|
||||
// ignored
|
||||
if (ddam != 0)
|
||||
Error() << "D88: nonzero ddam currently unsupported";
|
||||
error("D88: nonzero ddam currently unsupported");
|
||||
if (rpm != 0)
|
||||
Error()
|
||||
<< "D88: 1.44MB 300rpm formats currently unsupported";
|
||||
error("D88: 1.44MB 300rpm formats currently unsupported");
|
||||
if (fddStatusCode != 0)
|
||||
Error() << "D88: nonzero fdd status codes are currently "
|
||||
"unsupported";
|
||||
error(
|
||||
"D88: nonzero fdd status codes are currently "
|
||||
"unsupported");
|
||||
if (currentSectorsInTrack == 0xffff)
|
||||
{
|
||||
currentSectorsInTrack = sectorsInTrack;
|
||||
}
|
||||
else if (currentSectorsInTrack != sectorsInTrack)
|
||||
{
|
||||
Error() << "D88: mismatched number of sectors in track";
|
||||
error("D88: mismatched number of sectors in track");
|
||||
}
|
||||
if (currentTrackTrack < 0)
|
||||
{
|
||||
@@ -143,8 +143,9 @@ public:
|
||||
}
|
||||
else if (currentTrackTrack != track)
|
||||
{
|
||||
Error() << "D88: all sectors in a track must belong to the "
|
||||
"same track";
|
||||
error(
|
||||
"D88: all sectors in a track must belong to the same "
|
||||
"track");
|
||||
}
|
||||
if (trackSectorSize < 0)
|
||||
{
|
||||
@@ -166,14 +167,17 @@ public:
|
||||
trackdata->set_dam_byte(0xf56f);
|
||||
}
|
||||
// create timings to approximately match N88-BASIC
|
||||
if (clockRate == 300) {
|
||||
if (clockRate == 300)
|
||||
{
|
||||
if (sectorSize <= 256)
|
||||
{
|
||||
trackdata->set_gap0(0x1b);
|
||||
trackdata->set_gap2(0x14);
|
||||
trackdata->set_gap3(0x1b);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sectorSize <= 128)
|
||||
{
|
||||
trackdata->set_gap0(0x1b);
|
||||
@@ -189,12 +193,13 @@ public:
|
||||
}
|
||||
else if (trackSectorSize != sectorSize)
|
||||
{
|
||||
Error() << "D88: multiple sector sizes per track are "
|
||||
"currently unsupported";
|
||||
error(
|
||||
"D88: multiple sector sizes per track are currently "
|
||||
"unsupported");
|
||||
}
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
inputFile.seekg(dataLength-sectorSize, std::ios_base::cur);
|
||||
inputFile.seekg(dataLength - sectorSize, std::ios_base::cur);
|
||||
physical->add_sector(sectorId);
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
@@ -211,12 +216,12 @@ public:
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
Logger() << fmt::format("D88: read {} tracks, {} sides",
|
||||
log("D88: read {} tracks, {} sides",
|
||||
geometry.numTracks,
|
||||
geometry.numSides);
|
||||
|
||||
layout->set_tracks(geometry.numTracks);
|
||||
layout->set_sides(geometry.numSides);
|
||||
layout->set_tracks(geometry.numTracks);
|
||||
layout->set_sides(geometry.numSides);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "logger.h"
|
||||
#include "proto.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -24,12 +23,12 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
Bytes header(256);
|
||||
inputFile.read((char*)header.begin(), header.size());
|
||||
if (header.slice(0xAB, 13) != Bytes("DIFC HEADER "))
|
||||
Error() << "DIM: could not find DIM header, is this a DIM file?";
|
||||
error("DIM: could not find DIM header, is this a DIM file?");
|
||||
|
||||
// the DIM header technically has a bit field for sectors present,
|
||||
// however it is currently ignored by this reader
|
||||
@@ -61,7 +60,7 @@ public:
|
||||
sectorSize = 512;
|
||||
break;
|
||||
default:
|
||||
Error() << "DIM: unsupported media byte";
|
||||
error("DIM: unsupported media byte");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ public:
|
||||
trackCount++;
|
||||
}
|
||||
|
||||
auto layout = config.mutable_layout();
|
||||
auto layout = config.mutable_layout();
|
||||
if (config.encoder().format_case() ==
|
||||
EncoderProto::FormatCase::FORMAT_NOT_SET)
|
||||
{
|
||||
@@ -100,36 +99,36 @@ public:
|
||||
auto trackdata = ibm->add_trackdata();
|
||||
trackdata->set_target_clock_period_us(2);
|
||||
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto physical = layoutdata->mutable_physical();
|
||||
switch (mediaByte)
|
||||
{
|
||||
case 0x00:
|
||||
Logger() << "DIM: automatically setting format to 1.2MB "
|
||||
"(1024 byte sectors)";
|
||||
log("DIM: automatically setting format to 1.2MB "
|
||||
"(1024 byte sectors)");
|
||||
trackdata->set_target_rotational_period_ms(167);
|
||||
layoutdata->set_sector_size(1024);
|
||||
for (int i = 0; i < 9; i++)
|
||||
physical->add_sector(i);
|
||||
break;
|
||||
case 0x02:
|
||||
Logger() << "DIM: automatically setting format to 1.2MB "
|
||||
"(512 byte sectors)";
|
||||
log("DIM: automatically setting format to 1.2MB "
|
||||
"(512 byte sectors)");
|
||||
trackdata->set_target_rotational_period_ms(167);
|
||||
layoutdata->set_sector_size(512);
|
||||
for (int i = 0; i < 15; i++)
|
||||
physical->add_sector(i);
|
||||
break;
|
||||
case 0x03:
|
||||
Logger() << "DIM: automatically setting format to 1.44MB";
|
||||
log("DIM: automatically setting format to 1.44MB");
|
||||
trackdata->set_target_rotational_period_ms(200);
|
||||
layoutdata->set_sector_size(512);
|
||||
for (int i = 0; i < 18; i++)
|
||||
physical->add_sector(i);
|
||||
break;
|
||||
default:
|
||||
Error() << fmt::format(
|
||||
"DIM: unknown media byte 0x%02x, could not determine "
|
||||
error(
|
||||
"DIM: unknown media byte 0x{:02x}, could not determine "
|
||||
"write profile automatically",
|
||||
mediaByte);
|
||||
break;
|
||||
@@ -140,13 +139,13 @@ public:
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
Logger() << fmt::format("DIM: read {} tracks, {} sides, {} kB total",
|
||||
log("DIM: read {} tracks, {} sides, {} kB total",
|
||||
geometry.numTracks,
|
||||
geometry.numSides,
|
||||
((int)inputFile.tellg() - 256) / 1024);
|
||||
|
||||
layout->set_tracks(geometry.numTracks);
|
||||
layout->set_sides(geometry.numSides);
|
||||
layout->set_tracks(geometry.numTracks);
|
||||
layout->set_sides(geometry.numSides);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -20,7 +19,7 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
Bytes data;
|
||||
data.writer() += inputFile;
|
||||
@@ -61,12 +60,10 @@ public:
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << fmt::format(
|
||||
"don't understand DiskCopy disks of type {}", encoding);
|
||||
error("don't understand DiskCopy disks of type {}", encoding);
|
||||
}
|
||||
|
||||
Logger() << fmt::format(
|
||||
"DC42: reading image with {} tracks, {} heads; {}; {}",
|
||||
log("DC42: reading image with {} tracks, {} heads; {}; {}",
|
||||
numTracks,
|
||||
numHeads,
|
||||
mfm ? "MFM" : "GCR",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "proto.h"
|
||||
#include "logger.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -24,13 +23,13 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
Bytes header(32);
|
||||
inputFile.read((char*)header.begin(), header.size());
|
||||
ByteReader headerReader(header);
|
||||
if (headerReader.seek(0).read_le32() != 0)
|
||||
Error() << "FDI: could not find FDI header, is this a FDI file?";
|
||||
error("FDI: could not find FDI header, is this a FDI file?");
|
||||
|
||||
// we currently don't use fddType but it could be used to automatically
|
||||
// select profile parameters in the future
|
||||
@@ -62,8 +61,7 @@ public:
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
|
||||
const auto& sector =
|
||||
image->put(track, side, sectorId);
|
||||
const auto& sector = image->put(track, side, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->data = data;
|
||||
}
|
||||
@@ -72,7 +70,7 @@ public:
|
||||
trackCount++;
|
||||
}
|
||||
|
||||
auto layout = config.mutable_layout();
|
||||
auto layout = config.mutable_layout();
|
||||
if (config.encoder().format_case() ==
|
||||
EncoderProto::FormatCase::FORMAT_NOT_SET)
|
||||
{
|
||||
@@ -80,13 +78,13 @@ public:
|
||||
auto trackdata = ibm->add_trackdata();
|
||||
trackdata->set_target_clock_period_us(2);
|
||||
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto physical = layoutdata->mutable_physical();
|
||||
switch (fddType)
|
||||
{
|
||||
case 0x90:
|
||||
Logger() << "FDI: automatically setting format to 1.2MB "
|
||||
"(1024 byte sectors)";
|
||||
log("FDI: automatically setting format to 1.2MB (1024 byte "
|
||||
"sectors)");
|
||||
trackdata->set_target_rotational_period_ms(167);
|
||||
layoutdata->set_sector_size(1024);
|
||||
for (int i = 0; i < 9; i++)
|
||||
@@ -94,7 +92,7 @@ public:
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
Logger() << "FDI: automatically setting format to 1.44MB";
|
||||
log("FDI: automatically setting format to 1.44MB");
|
||||
trackdata->set_target_rotational_period_ms(200);
|
||||
layoutdata->set_sector_size(512);
|
||||
for (int i = 0; i < 18; i++)
|
||||
@@ -102,7 +100,7 @@ public:
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << fmt::format(
|
||||
error(
|
||||
"FDI: unknown fdd type 0x{:2x}, could not determine "
|
||||
"write profile automatically",
|
||||
fddType);
|
||||
@@ -112,13 +110,13 @@ public:
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
Logger() << fmt::format("FDI: read {} tracks, {} sides, {} kB total",
|
||||
log("FDI: read {} tracks, {} sides, {} kB total",
|
||||
geometry.numTracks,
|
||||
geometry.numSides,
|
||||
((int)inputFile.tellg() - headerSize) / 1024);
|
||||
|
||||
layout->set_tracks(geometry.numTracks);
|
||||
layout->set_sides(geometry.numSides);
|
||||
layout->set_tracks(geometry.numTracks);
|
||||
layout->set_sides(geometry.numSides);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "utils.h"
|
||||
#include "fmt/format.h"
|
||||
#include "proto.h"
|
||||
#include "image.h"
|
||||
#include "lib/layout.h"
|
||||
@@ -50,7 +49,7 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
|
||||
return ImageReader::createTd0ImageReader(config);
|
||||
|
||||
default:
|
||||
Error() << "bad input file config";
|
||||
error("bad input file config");
|
||||
return std::unique_ptr<ImageReader>();
|
||||
}
|
||||
}
|
||||
@@ -91,7 +90,7 @@ void ImageReader::updateConfigForFilename(
|
||||
}
|
||||
}
|
||||
|
||||
Error() << fmt::format("unrecognised image filename '{}'", filename);
|
||||
error("unrecognised image filename '{}'", filename);
|
||||
}
|
||||
|
||||
ImageReader::ImageReader(const ImageReaderProto& config): _config(config) {}
|
||||
@@ -103,7 +102,7 @@ std::unique_ptr<Image> ImageReader::readMappedImage()
|
||||
if (!_config.filesystem_sector_order())
|
||||
return rawImage;
|
||||
|
||||
Logger() << "READER: converting from filesystem sector order to disk order";
|
||||
log("READER: converting from filesystem sector order to disk order");
|
||||
std::set<std::shared_ptr<const Sector>> sectors;
|
||||
for (const auto& e : *rawImage)
|
||||
{
|
||||
|
||||
@@ -7,51 +7,53 @@
|
||||
#include "logger.h"
|
||||
#include "layout.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
static unsigned getModulationandSpeed(uint8_t flags, bool *fm)
|
||||
static unsigned getModulationandSpeed(uint8_t flags, bool* fm)
|
||||
{
|
||||
switch (flags)
|
||||
{
|
||||
case 0: /* 500 kbps FM */
|
||||
//clockRateKhz.setDefaultValue(250);
|
||||
*fm = true;
|
||||
return 500;
|
||||
break;
|
||||
{
|
||||
case 0: /* 500 kbps FM */
|
||||
// clockRateKhz.setDefaultValue(250);
|
||||
*fm = true;
|
||||
return 500;
|
||||
break;
|
||||
|
||||
case 1: /* 300 kbps FM */
|
||||
*fm = true;
|
||||
return 300;
|
||||
|
||||
break;
|
||||
case 1: /* 300 kbps FM */
|
||||
*fm = true;
|
||||
return 300;
|
||||
|
||||
case 2: /* 250 kbps FM */
|
||||
*fm = true;
|
||||
return 250;
|
||||
break;
|
||||
break;
|
||||
|
||||
case 3: /* 500 kbps MFM */
|
||||
*fm = false;
|
||||
return 500;
|
||||
break;
|
||||
case 2: /* 250 kbps FM */
|
||||
*fm = true;
|
||||
return 250;
|
||||
break;
|
||||
|
||||
case 4: /* 300 kbps MFM */
|
||||
*fm = false;
|
||||
return 300;
|
||||
break;
|
||||
case 3: /* 500 kbps MFM */
|
||||
*fm = false;
|
||||
return 500;
|
||||
break;
|
||||
|
||||
case 5: /* 250 kbps MFM */
|
||||
*fm = false;
|
||||
return 250;
|
||||
break;
|
||||
case 4: /* 300 kbps MFM */
|
||||
*fm = false;
|
||||
return 300;
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << fmt::format("IMD: don't understand IMD disks with this modulation and speed {}", flags);
|
||||
throw 0;
|
||||
}
|
||||
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
|
||||
@@ -67,73 +69,86 @@ 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;
|
||||
}
|
||||
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_CYL_MAP_FLAG 0x80
|
||||
#define SEC_HEAD_MAP_FLAG 0x40
|
||||
#define HEAD_MASK 0x3F
|
||||
#define END_OF_FILE 0x1A
|
||||
|
||||
#define HEAD_MASK 0x3F
|
||||
#define END_OF_FILE 0x1A
|
||||
|
||||
class IMDImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
IMDImageReader(const ImageReaderProto& config): ImageReader(config) {}
|
||||
|
||||
std::unique_ptr<Image> readImage()
|
||||
// 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 see getModulationspeed for definition
|
||||
1 byte Track
|
||||
1 byte Head
|
||||
1 byte number of sectors in track
|
||||
1 byte sector size see getsectorsize for definition
|
||||
sector numbering map
|
||||
sector track 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
|
||||
<End of file>
|
||||
*/
|
||||
* 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()
|
||||
{
|
||||
//Read File
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
// 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
|
||||
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
|
||||
int inputFileSize = inputFile.tellg(); // determine filesize
|
||||
inputFile.seekg(0, inputFile.beg);
|
||||
Bytes data;
|
||||
data.writer() += inputFile;
|
||||
@@ -148,32 +163,30 @@ public:
|
||||
unsigned headerPtr = 0;
|
||||
unsigned Modulation_Speed = 0;
|
||||
unsigned sectorSize = 0;
|
||||
std::string sector_skew;
|
||||
std::string sector_skew;
|
||||
|
||||
int b;
|
||||
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
|
||||
Logger() << "Comment in IMD file:"
|
||||
<< fmt::format("{}",
|
||||
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)
|
||||
if (headerPtr >= inputFileSize - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
//first read header
|
||||
// first read header
|
||||
header.ModeValue = br.read_8();
|
||||
headerPtr++;
|
||||
Modulation_Speed = getModulationandSpeed(header.ModeValue, &fm);
|
||||
@@ -187,237 +200,272 @@ public:
|
||||
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++;
|
||||
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 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);
|
||||
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
|
||||
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
|
||||
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)
|
||||
{
|
||||
/*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 |
|
||||
*/
|
||||
case 0: /* Sector data unavailable - could not be read */
|
||||
// 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;
|
||||
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 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 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 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 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 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 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 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;
|
||||
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() << fmt::format("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;
|
||||
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)
|
||||
Logger() << "IMD: overriding configured format";
|
||||
|
||||
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));
|
||||
size_t headSize = ((header.numSectors) * (sectorSize));
|
||||
size_t trackSize = (headSize * (header.Head + 1));
|
||||
|
||||
|
||||
Logger() << "IMD: read "
|
||||
<< fmt::format("{} 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);
|
||||
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;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "lib/layout.pb.h"
|
||||
#include "lib/proto.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -23,12 +22,13 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
auto layout = config.layout();
|
||||
if (!layout.tracks() || !layout.sides())
|
||||
Error() << "IMG: bad configuration; did you remember to set the "
|
||||
"tracks, sides and trackdata fields in the layout?";
|
||||
error(
|
||||
"IMG: bad configuration; did you remember to set the "
|
||||
"tracks, sides and trackdata fields in the layout?");
|
||||
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (const auto& p : Layout::getTrackOrdering())
|
||||
@@ -53,11 +53,11 @@ public:
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
Logger() << fmt::format("IMG: read {} tracks, {} sides, {} kB total from {}",
|
||||
log("IMG: read {} tracks, {} sides, {} kB total from {}",
|
||||
geometry.numTracks,
|
||||
geometry.numSides,
|
||||
inputFile.tellg() / 1024,
|
||||
_config.filename());
|
||||
_config.filename());
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -82,7 +81,7 @@ static unsigned getSectorSize(uint8_t flags)
|
||||
return 512;
|
||||
}
|
||||
}
|
||||
Error() << "not reachable";
|
||||
error("not reachable");
|
||||
}
|
||||
|
||||
class Jv3ImageReader : public ImageReader
|
||||
@@ -95,7 +94,7 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
inputFile.seekg(0, std::ios::end);
|
||||
unsigned inputFileSize = inputFile.tellg();
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "proto.h"
|
||||
#include "logger.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -24,18 +23,18 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
Bytes fileId(14); // read first entry of track table as well
|
||||
inputFile.read((char*)fileId.begin(), fileId.size());
|
||||
|
||||
if (fileId == Bytes("T98FDDIMAGE.R1"))
|
||||
{
|
||||
Error() << "NFD: r1 images are not currently supported";
|
||||
error("NFD: r1 images are not currently supported");
|
||||
}
|
||||
if (fileId != Bytes("T98FDDIMAGE.R0"))
|
||||
{
|
||||
Error() << "NFD: could not find NFD header";
|
||||
error("NFD: could not find NFD header");
|
||||
}
|
||||
|
||||
Bytes header(0x10a10);
|
||||
@@ -47,17 +46,17 @@ public:
|
||||
char heads = headerReader.seek(0x115).read_8();
|
||||
if (heads != 2)
|
||||
{
|
||||
Error() << "NFD: unsupported number of heads";
|
||||
error("NFD: unsupported number of heads");
|
||||
}
|
||||
|
||||
if (config.encoder().format_case() !=
|
||||
EncoderProto::FormatCase::FORMAT_NOT_SET)
|
||||
Logger() << "NFD: overriding configured format";
|
||||
log("NFD: overriding configured format");
|
||||
|
||||
auto ibm = config.mutable_encoder()->mutable_ibm();
|
||||
auto layout = config.mutable_layout();
|
||||
Logger() << "NFD: HD 1.2MB mode";
|
||||
Logger() << "NFD: forcing hign density mode";
|
||||
auto layout = config.mutable_layout();
|
||||
log("NFD: HD 1.2MB mode");
|
||||
log("NFD: forcing hign density mode");
|
||||
config.mutable_drive()->set_high_density(true);
|
||||
config.set_tpi(96);
|
||||
|
||||
@@ -68,7 +67,7 @@ public:
|
||||
trackdata->set_target_clock_period_us(2);
|
||||
trackdata->set_target_rotational_period_ms(167);
|
||||
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto layoutdata = layout->add_layoutdata();
|
||||
auto physical = layoutdata->mutable_physical();
|
||||
int currentTrackTrack = -1;
|
||||
int currentTrackHead = -1;
|
||||
@@ -88,10 +87,11 @@ public:
|
||||
if (track == 0xFF)
|
||||
continue;
|
||||
if (ddam != 0)
|
||||
Error() << "NFD: nonzero ddam currently unsupported";
|
||||
error("NFD: nonzero ddam currently unsupported");
|
||||
if (status != 0)
|
||||
Error() << "NFD: nonzero fdd status codes are currently "
|
||||
"unsupported";
|
||||
error(
|
||||
"NFD: nonzero fdd status codes are currently "
|
||||
"unsupported");
|
||||
if (currentTrackTrack < 0)
|
||||
{
|
||||
currentTrackTrack = track;
|
||||
@@ -99,13 +99,15 @@ public:
|
||||
}
|
||||
else if (currentTrackTrack != track)
|
||||
{
|
||||
Error() << "NFD: all sectors in a track must belong to the "
|
||||
"same track";
|
||||
error(
|
||||
"NFD: all sectors in a track must belong to the same "
|
||||
"track");
|
||||
}
|
||||
else if (currentTrackHead != head)
|
||||
{
|
||||
Error() << "NFD: all sectors in a track must belong to the "
|
||||
"same head";
|
||||
error(
|
||||
"NFD: all sectors in a track must belong to the same "
|
||||
"head");
|
||||
}
|
||||
if (trackSectorSize < 0)
|
||||
{
|
||||
@@ -114,8 +116,8 @@ public:
|
||||
// per-track data
|
||||
trackdata->set_track(track);
|
||||
trackdata->set_head(head);
|
||||
layoutdata->set_track(track);
|
||||
layoutdata->set_side(head);
|
||||
layoutdata->set_track(track);
|
||||
layoutdata->set_side(head);
|
||||
layoutdata->set_sector_size(sectorSize);
|
||||
trackdata->set_use_fm(!mfm);
|
||||
if (!mfm)
|
||||
@@ -139,8 +141,9 @@ public:
|
||||
}
|
||||
else if (trackSectorSize != sectorSize)
|
||||
{
|
||||
Error() << "NFD: multiple sector sizes per track are "
|
||||
"currently unsupported";
|
||||
error(
|
||||
"NFD: multiple sector sizes per track are currently "
|
||||
"unsupported");
|
||||
}
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
@@ -153,7 +156,7 @@ public:
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
Logger() << fmt::format("NFD: read {} tracks, {} sides",
|
||||
log("NFD: read {} tracks, {} sides",
|
||||
geometry.numTracks,
|
||||
geometry.numSides);
|
||||
return image;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "logger.h"
|
||||
#include "lib/imagereader/imagereader.pb.h"
|
||||
#include <algorithm>
|
||||
@@ -22,15 +21,14 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
const auto begin = inputFile.tellg();
|
||||
inputFile.seekg(0, std::ios::end);
|
||||
const auto end = inputFile.tellg();
|
||||
const auto fsize = (end - begin);
|
||||
|
||||
Logger() << fmt::format(
|
||||
"NSI: Autodetecting geometry based on file size: {}", fsize);
|
||||
log("NSI: Autodetecting geometry based on file size: {}", fsize);
|
||||
|
||||
unsigned numTracks = 35;
|
||||
unsigned numSectors = 10;
|
||||
@@ -55,13 +53,12 @@ public:
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << "NSI: unknown file size";
|
||||
error("NSI: unknown file size");
|
||||
}
|
||||
|
||||
size_t trackSize = numSectors * sectorSize;
|
||||
|
||||
Logger() << fmt::format(
|
||||
"reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} "
|
||||
log("reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} "
|
||||
"kB total",
|
||||
numTracks,
|
||||
numHeads,
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
#include "image.h"
|
||||
#include "crc.h"
|
||||
#include "logger.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -54,7 +52,7 @@ public:
|
||||
std::ifstream inputFile(
|
||||
_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
error("cannot open input file");
|
||||
|
||||
Bytes input;
|
||||
input.writer() += inputFile;
|
||||
@@ -71,10 +69,11 @@ public:
|
||||
|
||||
uint16_t gotCrc = crc16(0xa097, 0, input.slice(0, 10));
|
||||
if (gotCrc != headerCrc)
|
||||
Error() << "TD0: header checksum mismatch";
|
||||
error("TD0: header checksum mismatch");
|
||||
if (signature != 0x5444)
|
||||
Error() << "TD0: unsupported file type (only uncompressed files "
|
||||
"are supported for now)";
|
||||
error(
|
||||
"TD0: unsupported file type (only uncompressed files "
|
||||
"are supported for now)");
|
||||
|
||||
std::string comment = "(no comment)";
|
||||
if (stepping & 0x80)
|
||||
@@ -98,8 +97,7 @@ public:
|
||||
comment.erase(nl.base(), comment.end());
|
||||
}
|
||||
|
||||
Logger() << fmt::format(
|
||||
"TD0: TeleDisk {}.{}: {}", version / 10, version % 10, comment);
|
||||
log("TD0: TeleDisk {}.{}: {}", version / 10, version % 10, comment);
|
||||
|
||||
unsigned totalSize = 0;
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
@@ -194,8 +192,7 @@ public:
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
Logger() << fmt::format(
|
||||
"TD0: found {} tracks, {} sides, {} sectors, {} bytes per sector, "
|
||||
log("TD0: found {} tracks, {} sides, {} sectors, {} bytes per sector, "
|
||||
"{} kB total",
|
||||
geometry.numTracks,
|
||||
geometry.numSides,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "image.h"
|
||||
#include "ldbs.h"
|
||||
#include "logger.h"
|
||||
@@ -25,21 +24,20 @@ static int sectors_per_track(int track)
|
||||
class D64ImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
D64ImageWriter(const ImageWriterProto& config):
|
||||
ImageWriter(config)
|
||||
{}
|
||||
D64ImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
|
||||
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
Logger() << "D64: writing triangular image";
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
log("D64: writing triangular image");
|
||||
|
||||
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
Error() << "cannot open output file";
|
||||
std::ofstream outputFile(
|
||||
_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
error("cannot open output file");
|
||||
|
||||
uint32_t offset = 0;
|
||||
for (int track = 0; track < 40; track++)
|
||||
{
|
||||
for (int track = 0; track < 40; track++)
|
||||
{
|
||||
int sectorCount = sectors_per_track(track);
|
||||
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
|
||||
{
|
||||
@@ -47,17 +45,18 @@ public:
|
||||
if (sector)
|
||||
{
|
||||
outputFile.seekp(offset);
|
||||
outputFile.write((const char*) sector->data.cbegin(), sector->data.size());
|
||||
outputFile.write((const char*)sector->data.cbegin(),
|
||||
sector->data.size());
|
||||
}
|
||||
|
||||
offset += 256;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createD64ImageWriter(const ImageWriterProto& config)
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createD64ImageWriter(
|
||||
const ImageWriterProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new D64ImageWriter(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "image.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include "logger.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -13,112 +12,129 @@
|
||||
|
||||
static int countl_zero(uint32_t value)
|
||||
{
|
||||
int count = 0;
|
||||
while (!(value & 0x80000000))
|
||||
{
|
||||
value <<= 1;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
int count = 0;
|
||||
while (!(value & 0x80000000))
|
||||
{
|
||||
value <<= 1;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
class D88ImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
D88ImageWriter(const ImageWriterProto& config):
|
||||
ImageWriter(config)
|
||||
{}
|
||||
D88ImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
|
||||
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry geometry = image.getGeometry();
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry geometry = image.getGeometry();
|
||||
|
||||
int tracks = geometry.numTracks;
|
||||
int sides = geometry.numSides;
|
||||
int tracks = geometry.numTracks;
|
||||
int sides = geometry.numSides;
|
||||
|
||||
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
Error() << "cannot open output file";
|
||||
std::ofstream outputFile(
|
||||
_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
error("cannot open output file");
|
||||
|
||||
Bytes header;
|
||||
ByteWriter headerWriter(header);
|
||||
for(int i = 0; i < 26; i++) {
|
||||
headerWriter.write_8(0x0); // image name + reserved bytes
|
||||
}
|
||||
headerWriter.write_8(0x00); // not write protected
|
||||
if (geometry.numTracks > 42) {
|
||||
headerWriter.write_8(0x20); // 2HD
|
||||
} else {
|
||||
headerWriter.write_8(0x00); // 2D
|
||||
}
|
||||
headerWriter.write_le32(0); // disk size (will be overridden at the end of writing)
|
||||
for (int i = 0; i < 164; i++) {
|
||||
headerWriter.write_le32(0); // track pointer (will be overridden in track loop)
|
||||
}
|
||||
header.writeTo(outputFile);
|
||||
Bytes header;
|
||||
ByteWriter headerWriter(header);
|
||||
for (int i = 0; i < 26; i++)
|
||||
{
|
||||
headerWriter.write_8(0x0); // image name + reserved bytes
|
||||
}
|
||||
headerWriter.write_8(0x00); // not write protected
|
||||
if (geometry.numTracks > 42)
|
||||
{
|
||||
headerWriter.write_8(0x20); // 2HD
|
||||
}
|
||||
else
|
||||
{
|
||||
headerWriter.write_8(0x00); // 2D
|
||||
}
|
||||
headerWriter.write_le32(
|
||||
0); // disk size (will be overridden at the end of writing)
|
||||
for (int i = 0; i < 164; i++)
|
||||
{
|
||||
headerWriter.write_le32(
|
||||
0); // track pointer (will be overridden in track loop)
|
||||
}
|
||||
header.writeTo(outputFile);
|
||||
|
||||
uint32_t trackOffset = 688;
|
||||
uint32_t trackOffset = 688;
|
||||
|
||||
for (int track = 0; track < geometry.numTracks * geometry.numSides; track++)
|
||||
{
|
||||
headerWriter.seek(0x20 + 4 * track);
|
||||
headerWriter.write_le32(trackOffset);
|
||||
int side = track & 1;
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
for (int sectorId = geometry.firstSector; sectorId <= geometry.numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track >> 1, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
std::sort(begin(sectors),
|
||||
end(sectors),
|
||||
[](std::shared_ptr<const Sector> a, std::shared_ptr<const Sector> b) { return a->position < b->position; });
|
||||
for (auto& sector : sectors)
|
||||
{
|
||||
Bytes sectorBytes;
|
||||
ByteWriter sectorWriter(sectorBytes);
|
||||
sectorWriter.write_8(sector->logicalTrack);
|
||||
sectorWriter.write_8(sector->logicalSide);
|
||||
sectorWriter.write_8(sector->logicalSector);
|
||||
sectorWriter.write_8(24 - countl_zero(uint32_t(sector->data.size())));
|
||||
sectorWriter.write_le16(sectors.size());
|
||||
sectorWriter.write_8(0x00); // always write mfm
|
||||
sectorWriter.write_8(0x00); // always write not deleted data
|
||||
if (sector->status == Sector::Status::BAD_CHECKSUM) {
|
||||
sectorWriter.write_8(0xB0);
|
||||
} else {
|
||||
sectorWriter.write_8(0x00);
|
||||
}
|
||||
sectorWriter.write_8(0x00); // reserved
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_le16(sector->data.size());
|
||||
sectorBytes.writeTo(outputFile);
|
||||
sector->data.writeTo(outputFile);
|
||||
trackOffset += sectorBytes.size();
|
||||
trackOffset += sector->data.size();
|
||||
}
|
||||
}
|
||||
for (int track = 0; track < geometry.numTracks * geometry.numSides;
|
||||
track++)
|
||||
{
|
||||
headerWriter.seek(0x20 + 4 * track);
|
||||
headerWriter.write_le32(trackOffset);
|
||||
int side = track & 1;
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
for (int sectorId = geometry.firstSector;
|
||||
sectorId <= geometry.numSectors;
|
||||
sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track >> 1, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
std::sort(begin(sectors),
|
||||
end(sectors),
|
||||
[](std::shared_ptr<const Sector> a,
|
||||
std::shared_ptr<const Sector> b)
|
||||
{
|
||||
return a->position < b->position;
|
||||
});
|
||||
for (auto& sector : sectors)
|
||||
{
|
||||
Bytes sectorBytes;
|
||||
ByteWriter sectorWriter(sectorBytes);
|
||||
sectorWriter.write_8(sector->logicalTrack);
|
||||
sectorWriter.write_8(sector->logicalSide);
|
||||
sectorWriter.write_8(sector->logicalSector);
|
||||
sectorWriter.write_8(
|
||||
24 - countl_zero(uint32_t(sector->data.size())));
|
||||
sectorWriter.write_le16(sectors.size());
|
||||
sectorWriter.write_8(0x00); // always write mfm
|
||||
sectorWriter.write_8(0x00); // always write not deleted data
|
||||
if (sector->status == Sector::Status::BAD_CHECKSUM)
|
||||
{
|
||||
sectorWriter.write_8(0xB0);
|
||||
}
|
||||
else
|
||||
{
|
||||
sectorWriter.write_8(0x00);
|
||||
}
|
||||
sectorWriter.write_8(0x00); // reserved
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_8(0x00);
|
||||
sectorWriter.write_le16(sector->data.size());
|
||||
sectorBytes.writeTo(outputFile);
|
||||
sector->data.writeTo(outputFile);
|
||||
trackOffset += sectorBytes.size();
|
||||
trackOffset += sector->data.size();
|
||||
}
|
||||
}
|
||||
|
||||
headerWriter.seek(0x1c);
|
||||
headerWriter.write_le32(outputFile.tellp());
|
||||
outputFile.seekp(0);
|
||||
header.writeTo(outputFile);
|
||||
|
||||
Logger() << fmt::format("D88: wrote {} tracks, {} sides, {} kB total",
|
||||
tracks, sides,
|
||||
outputFile.tellp() / 1024);
|
||||
}
|
||||
headerWriter.seek(0x1c);
|
||||
headerWriter.write_le32(outputFile.tellp());
|
||||
outputFile.seekp(0);
|
||||
header.writeTo(outputFile);
|
||||
|
||||
log("D88: wrote {} tracks, {} sides, {} kB total",
|
||||
tracks,
|
||||
sides,
|
||||
outputFile.tellp() / 1024);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createD88ImageWriter(
|
||||
const ImageWriterProto& config)
|
||||
const ImageWriterProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new D88ImageWriter(config));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ldbs.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
@@ -13,162 +12,166 @@
|
||||
|
||||
static const char LABEL[] = "FluxEngine image";
|
||||
|
||||
static void write_and_update_checksum(ByteWriter& bw, uint32_t& checksum, const Bytes& data)
|
||||
static void write_and_update_checksum(
|
||||
ByteWriter& bw, uint32_t& checksum, const Bytes& data)
|
||||
{
|
||||
ByteReader br(data);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint32_t i = br.read_be16();
|
||||
checksum += i;
|
||||
checksum = (checksum >> 1) | (checksum << 31);
|
||||
bw.write_be16(i);
|
||||
}
|
||||
ByteReader br(data);
|
||||
while (!br.eof())
|
||||
{
|
||||
uint32_t i = br.read_be16();
|
||||
checksum += i;
|
||||
checksum = (checksum >> 1) | (checksum << 31);
|
||||
bw.write_be16(i);
|
||||
}
|
||||
}
|
||||
|
||||
class DiskCopyImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
DiskCopyImageWriter(const ImageWriterProto& config):
|
||||
ImageWriter(config)
|
||||
{}
|
||||
DiskCopyImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
|
||||
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
|
||||
bool mfm = false;
|
||||
bool mfm = false;
|
||||
|
||||
switch (geometry.sectorSize)
|
||||
{
|
||||
case 524:
|
||||
/* GCR disk */
|
||||
break;
|
||||
switch (geometry.sectorSize)
|
||||
{
|
||||
case 524:
|
||||
/* GCR disk */
|
||||
break;
|
||||
|
||||
case 512:
|
||||
/* MFM disk */
|
||||
mfm = true;
|
||||
break;
|
||||
case 512:
|
||||
/* MFM disk */
|
||||
mfm = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << "this image is not compatible with the DiskCopy 4.2 format";
|
||||
}
|
||||
default:
|
||||
error(
|
||||
"this image is not compatible with the DiskCopy 4.2 "
|
||||
"format");
|
||||
}
|
||||
|
||||
Logger() << "DC42: writing DiskCopy 4.2 image"
|
||||
<< fmt::format("DC42: {} tracks, {} sides, {} sectors, {} bytes per sector; {}",
|
||||
geometry.numTracks, geometry.numSides, geometry.numSectors, geometry.sectorSize,
|
||||
mfm ? "MFM" : "GCR");
|
||||
log("DC42: writing DiskCopy 4.2 image");
|
||||
log("DC42: {} tracks, {} sides, {} sectors, {} bytes per sector; {}",
|
||||
geometry.numTracks,
|
||||
geometry.numSides,
|
||||
geometry.numSectors,
|
||||
geometry.sectorSize,
|
||||
mfm ? "MFM" : "GCR");
|
||||
|
||||
auto sectors_per_track = [&](int track) -> int
|
||||
{
|
||||
if (mfm)
|
||||
return geometry.numSectors;
|
||||
auto sectors_per_track = [&](int track) -> int
|
||||
{
|
||||
if (mfm)
|
||||
return geometry.numSectors;
|
||||
|
||||
if (track < 16)
|
||||
return 12;
|
||||
if (track < 32)
|
||||
return 11;
|
||||
if (track < 48)
|
||||
return 10;
|
||||
if (track < 64)
|
||||
return 9;
|
||||
return 8;
|
||||
};
|
||||
if (track < 16)
|
||||
return 12;
|
||||
if (track < 32)
|
||||
return 11;
|
||||
if (track < 48)
|
||||
return 10;
|
||||
if (track < 64)
|
||||
return 9;
|
||||
return 8;
|
||||
};
|
||||
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
|
||||
/* Write the actual sectr data. */
|
||||
/* Write the actual sectr data. */
|
||||
|
||||
uint32_t dataChecksum = 0;
|
||||
uint32_t tagChecksum = 0;
|
||||
uint32_t offset = 0x54;
|
||||
uint32_t sectorDataStart = offset;
|
||||
for (int track = 0; track < geometry.numTracks; track++)
|
||||
{
|
||||
for (int side = 0; side < geometry.numSides; side++)
|
||||
{
|
||||
int sectorCount = sectors_per_track(track);
|
||||
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
bw.seek(offset);
|
||||
write_and_update_checksum(bw, dataChecksum, sector->data.slice(0, 512));
|
||||
}
|
||||
offset += 512;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t sectorDataEnd = offset;
|
||||
if (!mfm)
|
||||
{
|
||||
for (int track = 0; track < geometry.numTracks; track++)
|
||||
{
|
||||
for (int side = 0; side < geometry.numSides; side++)
|
||||
{
|
||||
int sectorCount = sectors_per_track(track);
|
||||
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
bw.seek(offset);
|
||||
write_and_update_checksum(bw, tagChecksum, sector->data.slice(512, 12));
|
||||
}
|
||||
offset += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t tagDataEnd = offset;
|
||||
uint32_t dataChecksum = 0;
|
||||
uint32_t tagChecksum = 0;
|
||||
uint32_t offset = 0x54;
|
||||
uint32_t sectorDataStart = offset;
|
||||
for (int track = 0; track < geometry.numTracks; track++)
|
||||
{
|
||||
for (int side = 0; side < geometry.numSides; side++)
|
||||
{
|
||||
int sectorCount = sectors_per_track(track);
|
||||
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
bw.seek(offset);
|
||||
write_and_update_checksum(
|
||||
bw, dataChecksum, sector->data.slice(0, 512));
|
||||
}
|
||||
offset += 512;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t sectorDataEnd = offset;
|
||||
if (!mfm)
|
||||
{
|
||||
for (int track = 0; track < geometry.numTracks; track++)
|
||||
{
|
||||
for (int side = 0; side < geometry.numSides; side++)
|
||||
{
|
||||
int sectorCount = sectors_per_track(track);
|
||||
for (int sectorId = 0; sectorId < sectorCount; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
bw.seek(offset);
|
||||
write_and_update_checksum(
|
||||
bw, tagChecksum, sector->data.slice(512, 12));
|
||||
}
|
||||
offset += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t tagDataEnd = offset;
|
||||
|
||||
/* Write the header. */
|
||||
/* Write the header. */
|
||||
|
||||
uint8_t encoding;
|
||||
uint8_t format;
|
||||
if (mfm)
|
||||
{
|
||||
format = 0x22;
|
||||
if (geometry.numSectors == 18)
|
||||
encoding = 3;
|
||||
else
|
||||
encoding = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (geometry.numSides == 2)
|
||||
{
|
||||
encoding = 1;
|
||||
format = 0x22;
|
||||
}
|
||||
else
|
||||
{
|
||||
encoding = 0;
|
||||
format = 0x02;
|
||||
}
|
||||
}
|
||||
uint8_t encoding;
|
||||
uint8_t format;
|
||||
if (mfm)
|
||||
{
|
||||
format = 0x22;
|
||||
if (geometry.numSectors == 18)
|
||||
encoding = 3;
|
||||
else
|
||||
encoding = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (geometry.numSides == 2)
|
||||
{
|
||||
encoding = 1;
|
||||
format = 0x22;
|
||||
}
|
||||
else
|
||||
{
|
||||
encoding = 0;
|
||||
format = 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
bw.seek(0);
|
||||
bw.write_8(sizeof(LABEL));
|
||||
bw.append(LABEL);
|
||||
bw.seek(0x40);
|
||||
bw.write_be32(sectorDataEnd - sectorDataStart); /* data size */
|
||||
bw.write_be32(tagDataEnd - sectorDataEnd); /* tag size */
|
||||
bw.write_be32(dataChecksum); /* data checksum */
|
||||
bw.write_be32(tagChecksum); /* tag checksum */
|
||||
bw.write_8(encoding); /* encoding */
|
||||
bw.write_8(format); /* format byte */
|
||||
bw.write_be16(0x0100); /* magic number */
|
||||
bw.seek(0);
|
||||
bw.write_8(sizeof(LABEL));
|
||||
bw.append(LABEL);
|
||||
bw.seek(0x40);
|
||||
bw.write_be32(sectorDataEnd - sectorDataStart); /* data size */
|
||||
bw.write_be32(tagDataEnd - sectorDataEnd); /* tag size */
|
||||
bw.write_be32(dataChecksum); /* data checksum */
|
||||
bw.write_be32(tagChecksum); /* tag checksum */
|
||||
bw.write_8(encoding); /* encoding */
|
||||
bw.write_8(format); /* format byte */
|
||||
bw.write_be16(0x0100); /* magic number */
|
||||
|
||||
data.writeToFile(_config.filename());
|
||||
data.writeToFile(_config.filename());
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createDiskCopyImageWriter(
|
||||
const ImageWriterProto& config)
|
||||
const ImageWriterProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new DiskCopyImageWriter(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "proto.h"
|
||||
#include "lib/layout.h"
|
||||
#include "lib/logger.h"
|
||||
#include "fmt/format.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
@@ -41,7 +40,7 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config)
|
||||
return ImageWriter::createImdImageWriter(config);
|
||||
|
||||
default:
|
||||
Error() << "bad output image config";
|
||||
error("bad output image config");
|
||||
return std::unique_ptr<ImageWriter>();
|
||||
}
|
||||
}
|
||||
@@ -79,7 +78,7 @@ void ImageWriter::updateConfigForFilename(
|
||||
}
|
||||
}
|
||||
|
||||
Error() << fmt::format("unrecognised image filename '{}'", filename);
|
||||
error("unrecognised image filename '{}'", filename);
|
||||
}
|
||||
|
||||
ImageWriter::ImageWriter(const ImageWriterProto& config): _config(config) {}
|
||||
@@ -88,7 +87,7 @@ void ImageWriter::writeCsv(const Image& image, const std::string& filename)
|
||||
{
|
||||
std::ofstream f(filename, std::ios::out);
|
||||
if (!f.is_open())
|
||||
Error() << "cannot open CSV report file";
|
||||
error("cannot open CSV report file");
|
||||
|
||||
f << "\"Physical track\","
|
||||
"\"Physical side\","
|
||||
@@ -207,8 +206,7 @@ void ImageWriter::writeMappedImage(const Image& image)
|
||||
{
|
||||
if (_config.filesystem_sector_order())
|
||||
{
|
||||
Logger()
|
||||
<< "WRITER: converting from disk sector order to filesystem order";
|
||||
log("WRITER: converting from disk sector order to filesystem order");
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> sectors;
|
||||
for (const auto& e : image)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "image.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include "logger.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -14,426 +13,520 @@
|
||||
#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)
|
||||
* 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() << fmt::format("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 == 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() << fmt::format("IMD: Can't write IMD files with this speed {}, and modulation {}. Try another format.", flags, false);
|
||||
}
|
||||
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;
|
||||
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() << fmt::format("IMD: Sector size {} not in standard range (128, 256, 512, 1024, 2048, 4096, 8192).", 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_CYL_MAP_FLAG 0x80
|
||||
#define SEC_HEAD_MAP_FLAG 0x40
|
||||
#define HEAD_MASK 0x3F
|
||||
#define END_OF_FILE 0x1A
|
||||
|
||||
/*
|
||||
*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>
|
||||
*/
|
||||
#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)
|
||||
{}
|
||||
ImdImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
|
||||
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
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;
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
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;
|
||||
numHeads = geometry.numSides;
|
||||
numSectors = geometry.numSectors;
|
||||
numBytes = geometry.sectorSize;
|
||||
|
||||
Bytes imagenew;
|
||||
ByteWriter bw(imagenew);
|
||||
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;
|
||||
Logger() << fmt::format("IMD: guessing data rate as {}", ImdOutputProto::DataRate_Name(dataRate));
|
||||
}
|
||||
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;
|
||||
Logger() << fmt::format("IMD: guessing recording mode as {}", ImdOutputProto::RecordingMode_Name(recordingMode));
|
||||
}
|
||||
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);
|
||||
|
||||
//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);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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 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;
|
||||
Logger() << fmt::format("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;
|
||||
}
|
||||
}
|
||||
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 */
|
||||
|
||||
//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++)
|
||||
{
|
||||
/* 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
|
||||
*/
|
||||
//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)
|
||||
{
|
||||
/*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 |
|
||||
*/
|
||||
case Sector::MISSING: /* Sector data unavailable - could not be read */
|
||||
Status_Sector = 0;
|
||||
break;
|
||||
|
||||
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 */
|
||||
|
||||
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() << fmt::format("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);
|
||||
Logger() << fmt::format("IMD: Written {} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total",
|
||||
geometry.numTracks, numHeads,
|
||||
numSectors, numBytes,
|
||||
outputFile.tellp() / 1024);
|
||||
}
|
||||
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)
|
||||
const ImageWriterProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new ImdImageWriter(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "lib/layout.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include "logger.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -29,7 +28,7 @@ public:
|
||||
std::ofstream outputFile(
|
||||
_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
Error() << "cannot open output file";
|
||||
error("cannot open output file");
|
||||
|
||||
for (const auto& p : Layout::getTrackOrdering(tracks, sides))
|
||||
{
|
||||
@@ -41,17 +40,18 @@ public:
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
sector->data.slice(0, trackLayout->sectorSize).writeTo(outputFile);
|
||||
sector->data.slice(0, trackLayout->sectorSize)
|
||||
.writeTo(outputFile);
|
||||
else
|
||||
outputFile.seekp(trackLayout->sectorSize, std::ios::cur);
|
||||
}
|
||||
}
|
||||
|
||||
Logger() << fmt::format("IMG: wrote {} tracks, {} sides, {} kB total to {}",
|
||||
log("IMG: wrote {} tracks, {} sides, {} kB total to {}",
|
||||
tracks,
|
||||
sides,
|
||||
outputFile.tellp() / 1024,
|
||||
_config.filename());
|
||||
_config.filename());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ldbs.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
@@ -14,106 +13,125 @@
|
||||
class LDBSImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
LDBSImageWriter(const ImageWriterProto& config):
|
||||
ImageWriter(config)
|
||||
{}
|
||||
LDBSImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
|
||||
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
LDBS ldbs;
|
||||
|
||||
const Geometry geometry = image.getGeometry();
|
||||
const Geometry geometry = image.getGeometry();
|
||||
|
||||
Logger() << fmt::format("LDBS: writing {} tracks, {} sides, {} sectors, {} bytes per sector",
|
||||
geometry.numTracks, geometry.numSides, geometry.numSectors,
|
||||
geometry.sectorSize);
|
||||
log("LDBS: writing {} tracks, {} sides, {} sectors, {} bytes per "
|
||||
"sector",
|
||||
geometry.numTracks,
|
||||
geometry.numSides,
|
||||
geometry.numSectors,
|
||||
geometry.sectorSize);
|
||||
|
||||
Bytes trackDirectory;
|
||||
ByteWriter trackDirectoryWriter(trackDirectory);
|
||||
int trackDirectorySize = 0;
|
||||
trackDirectoryWriter.write_le16(0);
|
||||
|
||||
LDBSOutputProto::DataRate dataRate = _config.ldbs().data_rate();
|
||||
if (dataRate == LDBSOutputProto::RATE_GUESS)
|
||||
{
|
||||
dataRate = (geometry.numSectors > 10) ? LDBSOutputProto::RATE_HD : LDBSOutputProto::RATE_DD;
|
||||
if (geometry.sectorSize <= 256)
|
||||
dataRate = LDBSOutputProto::RATE_SD;
|
||||
Logger() << fmt::format("LDBS: guessing data rate as {}", LDBSOutputProto::DataRate_Name(dataRate));
|
||||
}
|
||||
LDBSOutputProto::DataRate dataRate = _config.ldbs().data_rate();
|
||||
if (dataRate == LDBSOutputProto::RATE_GUESS)
|
||||
{
|
||||
dataRate = (geometry.numSectors > 10) ? LDBSOutputProto::RATE_HD
|
||||
: LDBSOutputProto::RATE_DD;
|
||||
if (geometry.sectorSize <= 256)
|
||||
dataRate = LDBSOutputProto::RATE_SD;
|
||||
log("LDBS: guessing data rate as {}",
|
||||
LDBSOutputProto::DataRate_Name(dataRate));
|
||||
}
|
||||
|
||||
LDBSOutputProto::RecordingMode recordingMode = _config.ldbs().recording_mode();
|
||||
if (recordingMode == LDBSOutputProto::RECMODE_GUESS)
|
||||
{
|
||||
recordingMode = LDBSOutputProto::RECMODE_MFM;
|
||||
Logger() << fmt::format("LDBS: guessing recording mode as {}", LDBSOutputProto::RecordingMode_Name(recordingMode));
|
||||
}
|
||||
LDBSOutputProto::RecordingMode recordingMode =
|
||||
_config.ldbs().recording_mode();
|
||||
if (recordingMode == LDBSOutputProto::RECMODE_GUESS)
|
||||
{
|
||||
recordingMode = LDBSOutputProto::RECMODE_MFM;
|
||||
log("LDBS: guessing recording mode as {}",
|
||||
LDBSOutputProto::RecordingMode_Name(recordingMode));
|
||||
}
|
||||
|
||||
for (int track = 0; track < geometry.numTracks; track++)
|
||||
{
|
||||
for (int side = 0; side < geometry.numSides; side++)
|
||||
{
|
||||
for (int track = 0; track < geometry.numTracks; track++)
|
||||
{
|
||||
for (int side = 0; side < geometry.numSides; side++)
|
||||
{
|
||||
Bytes trackHeader;
|
||||
ByteWriter trackHeaderWriter(trackHeader);
|
||||
|
||||
int actualSectors = 0;
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors;
|
||||
sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
actualSectors++;
|
||||
}
|
||||
|
||||
trackHeaderWriter.write_le16(0x000C); /* offset of sector sideers */
|
||||
trackHeaderWriter.write_le16(0x0012); /* length of each sector descriptor */
|
||||
trackHeaderWriter.write_le16(
|
||||
0x000C); /* offset of sector sideers */
|
||||
trackHeaderWriter.write_le16(
|
||||
0x0012); /* length of each sector descriptor */
|
||||
trackHeaderWriter.write_le16(actualSectors);
|
||||
trackHeaderWriter.write_8(dataRate);
|
||||
trackHeaderWriter.write_8(recordingMode);
|
||||
trackHeaderWriter.write_8(0); /* format gap length */
|
||||
trackHeaderWriter.write_8(0); /* filler byte */
|
||||
trackHeaderWriter.write_8(0); /* format gap length */
|
||||
trackHeaderWriter.write_8(0); /* filler byte */
|
||||
trackHeaderWriter.write_le16(0); /* approximate track length */
|
||||
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
uint32_t sectorLabel = (('S') << 24) | ((track & 0xff) << 16) | (side << 8) | sectorId;
|
||||
uint32_t sectorAddress = ldbs.put(sector->data, sectorLabel);
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors;
|
||||
sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
uint32_t sectorLabel = (('S') << 24) |
|
||||
((track & 0xff) << 16) |
|
||||
(side << 8) | sectorId;
|
||||
uint32_t sectorAddress =
|
||||
ldbs.put(sector->data, sectorLabel);
|
||||
|
||||
trackHeaderWriter.write_8(track);
|
||||
trackHeaderWriter.write_8(side);
|
||||
trackHeaderWriter.write_8(sectorId);
|
||||
trackHeaderWriter.write_8(0); /* power-of-two size */
|
||||
trackHeaderWriter.write_8((sector->status == Sector::OK) ? 0x00 : 0x20); /* 8272 status 1 */
|
||||
trackHeaderWriter.write_8(
|
||||
(sector->status == Sector::OK)
|
||||
? 0x00
|
||||
: 0x20); /* 8272 status 1 */
|
||||
trackHeaderWriter.write_8(0); /* 8272 status 2 */
|
||||
trackHeaderWriter.write_8(1); /* number of copies */
|
||||
trackHeaderWriter.write_8(0); /* filler byte */
|
||||
trackHeaderWriter.write_le32(sectorAddress);
|
||||
trackHeaderWriter.write_le16(0); /* trailing bytes */
|
||||
trackHeaderWriter.write_le16(0); /* approximate offset */
|
||||
trackHeaderWriter.write_le16(
|
||||
0); /* approximate offset */
|
||||
trackHeaderWriter.write_le16(sector->data.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t trackLabel = (('T') << 24) | ((track & 0xff) << 16) | ((track >> 8) << 8) | side;
|
||||
uint32_t trackLabel = (('T') << 24) | ((track & 0xff) << 16) |
|
||||
((track >> 8) << 8) | side;
|
||||
uint32_t trackHeaderAddress = ldbs.put(trackHeader, trackLabel);
|
||||
trackDirectoryWriter.write_be32(trackLabel);
|
||||
trackDirectoryWriter.write_le32(trackHeaderAddress);
|
||||
trackDirectorySize++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trackDirectoryWriter.seek(0);
|
||||
trackDirectoryWriter.write_le16(trackDirectorySize);
|
||||
|
||||
uint32_t trackDirectoryAddress = ldbs.put(trackDirectory, LDBS_TRACK_BLOCK);
|
||||
uint32_t trackDirectoryAddress =
|
||||
ldbs.put(trackDirectory, LDBS_TRACK_BLOCK);
|
||||
Bytes data = ldbs.write(trackDirectoryAddress);
|
||||
data.writeToFile(_config.filename());
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createLDBSImageWriter(const ImageWriterProto& config)
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createLDBSImageWriter(
|
||||
const ImageWriterProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new LDBSImageWriter(config));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
@@ -15,76 +14,101 @@
|
||||
class NsiImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
NsiImageWriter(const ImageWriterProto& config):
|
||||
ImageWriter(config)
|
||||
{}
|
||||
NsiImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
|
||||
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
bool mixedDensity = false;
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
bool mixedDensity = false;
|
||||
|
||||
size_t trackSize = geometry.numSectors * geometry.sectorSize;
|
||||
size_t trackSize = geometry.numSectors * geometry.sectorSize;
|
||||
|
||||
if (geometry.numTracks * trackSize == 0) {
|
||||
Logger() << "No sectors in output; skipping .nsi image file generation.";
|
||||
return;
|
||||
}
|
||||
if (geometry.numTracks * trackSize == 0)
|
||||
{
|
||||
log("No sectors in output; skipping .nsi image file generation.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger() << fmt::format("Writing {} tracks, {} sides, {} sectors, {} ({} bytes/sector), {} kB total",
|
||||
geometry.numTracks, geometry.numSides,
|
||||
geometry.numSectors, geometry.sectorSize == 256 ? "SD" : "DD", geometry.sectorSize,
|
||||
geometry.numTracks * geometry.numSides * geometry.numSectors * geometry.sectorSize / 1024);
|
||||
log("Writing {} tracks, {} sides, {} sectors, {} ({} bytes/sector), {} "
|
||||
"kB total",
|
||||
geometry.numTracks,
|
||||
geometry.numSides,
|
||||
geometry.numSectors,
|
||||
geometry.sectorSize == 256 ? "SD" : "DD",
|
||||
geometry.sectorSize,
|
||||
geometry.numTracks * geometry.numSides * geometry.numSectors *
|
||||
geometry.sectorSize / 1024);
|
||||
|
||||
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
Error() << "cannot open output file";
|
||||
std::ofstream outputFile(
|
||||
_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
error("cannot open output file");
|
||||
|
||||
unsigned sectorFileOffset;
|
||||
for (int track = 0; track < geometry.numTracks * geometry.numSides; track++)
|
||||
{
|
||||
int side = (track < geometry.numTracks) ? 0 : 1;
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track % geometry.numTracks, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
if (side == 0) { /* Side 0 is from track 0-34 */
|
||||
sectorFileOffset = track * trackSize + sectorId * geometry.sectorSize;
|
||||
}
|
||||
else { /* Side 1 is from track 70-35 */
|
||||
sectorFileOffset = (geometry.sectorSize * geometry.numSectors * geometry.numTracks) + /* Skip over side 0 */
|
||||
((geometry.numTracks - 1) - (track % geometry.numTracks)) * (geometry.sectorSize * geometry.numSectors) +
|
||||
(sectorId * geometry.sectorSize); /* Sector offset from beginning of track. */
|
||||
}
|
||||
outputFile.seekp(sectorFileOffset, std::ios::beg);
|
||||
if ((geometry.sectorSize == 512) && (sector->data.size() == 256)) {
|
||||
/* North Star DOS provided an upgrade path for disks formatted as single-
|
||||
* density to hold double-density data without reformatting. In this
|
||||
* case, the four directory blocks will be single-density but other areas
|
||||
* of the disk are double-density. This cannot be accurately represented
|
||||
* using a .nsi file, so in these cases, we pad the sector to 512-bytes,
|
||||
* filling with spaces.
|
||||
*/
|
||||
char fill[256];
|
||||
memset(fill, ' ', sizeof(fill));
|
||||
if (mixedDensity == false) {
|
||||
Logger() << "Warning: Disk contains mixed single/double-density sectors.";
|
||||
}
|
||||
mixedDensity = true;
|
||||
sector->data.slice(0, 256).writeTo(outputFile);
|
||||
outputFile.write(fill, sizeof(fill));
|
||||
} else {
|
||||
sector->data.slice(0, geometry.sectorSize).writeTo(outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unsigned sectorFileOffset;
|
||||
for (int track = 0; track < geometry.numTracks * geometry.numSides;
|
||||
track++)
|
||||
{
|
||||
int side = (track < geometry.numTracks) ? 0 : 1;
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector =
|
||||
image.get(track % geometry.numTracks, side, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
if (side == 0)
|
||||
{ /* Side 0 is from track 0-34 */
|
||||
sectorFileOffset =
|
||||
track * trackSize + sectorId * geometry.sectorSize;
|
||||
}
|
||||
else
|
||||
{ /* Side 1 is from track 70-35 */
|
||||
sectorFileOffset =
|
||||
(geometry.sectorSize * geometry.numSectors *
|
||||
geometry.numTracks) + /* Skip over side 0 */
|
||||
((geometry.numTracks - 1) -
|
||||
(track % geometry.numTracks)) *
|
||||
(geometry.sectorSize * geometry.numSectors) +
|
||||
(sectorId *
|
||||
geometry.sectorSize); /* Sector offset from
|
||||
beginning of track. */
|
||||
}
|
||||
outputFile.seekp(sectorFileOffset, std::ios::beg);
|
||||
if ((geometry.sectorSize == 512) &&
|
||||
(sector->data.size() == 256))
|
||||
{
|
||||
/* North Star DOS provided an upgrade path for disks
|
||||
* formatted as single- density to hold double-density
|
||||
* data without reformatting. In this case, the four
|
||||
* directory blocks will be single-density but other
|
||||
* areas of the disk are double-density. This cannot be
|
||||
* accurately represented using a .nsi file, so in these
|
||||
* cases, we pad the sector to 512-bytes, filling with
|
||||
* spaces.
|
||||
*/
|
||||
char fill[256];
|
||||
memset(fill, ' ', sizeof(fill));
|
||||
if (mixedDensity == false)
|
||||
{
|
||||
log("Warning: Disk contains mixed "
|
||||
"single/double-density sectors.");
|
||||
}
|
||||
mixedDensity = true;
|
||||
sector->data.slice(0, 256).writeTo(outputFile);
|
||||
outputFile.write(fill, sizeof(fill));
|
||||
}
|
||||
else
|
||||
{
|
||||
sector->data.slice(0, geometry.sectorSize)
|
||||
.writeTo(outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createNsiImageWriter(
|
||||
const ImageWriterProto& config)
|
||||
const ImageWriterProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new NsiImageWriter(config));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
@@ -15,60 +14,65 @@
|
||||
class RawImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
RawImageWriter(const ImageWriterProto& config):
|
||||
ImageWriter(config)
|
||||
{}
|
||||
RawImageWriter(const ImageWriterProto& config): ImageWriter(config) {}
|
||||
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
void writeImage(const Image& image)
|
||||
{
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
|
||||
size_t trackSize = geometry.numSectors * geometry.sectorSize;
|
||||
size_t trackSize = geometry.numSectors * geometry.sectorSize;
|
||||
|
||||
if (geometry.numTracks * trackSize == 0) {
|
||||
Logger() << "RAW: no sectors in output; skipping image file generation.";
|
||||
return;
|
||||
}
|
||||
if (geometry.numTracks * trackSize == 0)
|
||||
{
|
||||
log("RAW: no sectors in output; skipping image file generation.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger() << fmt::format("RAW: writing {} tracks, {} sides",
|
||||
geometry.numTracks, geometry.numSides);
|
||||
log("RAW: writing {} tracks, {} sides",
|
||||
geometry.numTracks,
|
||||
geometry.numSides);
|
||||
|
||||
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
Error() << "RAW: cannot open output file";
|
||||
std::ofstream outputFile(
|
||||
_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
error("RAW: cannot open output file");
|
||||
|
||||
unsigned sectorFileOffset;
|
||||
for (int track = 0; track < geometry.numTracks * geometry.numSides; track++)
|
||||
{
|
||||
int side = (track < geometry.numTracks) ? 0 : 1;
|
||||
unsigned sectorFileOffset;
|
||||
for (int track = 0; track < geometry.numTracks * geometry.numSides;
|
||||
track++)
|
||||
{
|
||||
int side = (track < geometry.numTracks) ? 0 : 1;
|
||||
|
||||
std::vector<std::shared_ptr<Record>> records;
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(track % geometry.numTracks, side, sectorId);
|
||||
if (sector)
|
||||
records.insert(records.end(), sector->records.begin(), sector->records.end());
|
||||
}
|
||||
|
||||
std::sort(records.begin(), records.end(),
|
||||
[&](std::shared_ptr<Record> left, std::shared_ptr<Record> right) {
|
||||
return left->startTime < right->startTime;
|
||||
});
|
||||
std::vector<std::shared_ptr<Record>> records;
|
||||
for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector =
|
||||
image.get(track % geometry.numTracks, side, sectorId);
|
||||
if (sector)
|
||||
records.insert(records.end(),
|
||||
sector->records.begin(),
|
||||
sector->records.end());
|
||||
}
|
||||
|
||||
for (const auto& record : records)
|
||||
{
|
||||
record->rawData.writeTo(outputFile);
|
||||
Bytes(3).writeTo(outputFile);
|
||||
}
|
||||
Bytes(1).writeTo(outputFile);
|
||||
}
|
||||
}
|
||||
std::sort(records.begin(),
|
||||
records.end(),
|
||||
[&](std::shared_ptr<Record> left, std::shared_ptr<Record> right)
|
||||
{
|
||||
return left->startTime < right->startTime;
|
||||
});
|
||||
|
||||
for (const auto& record : records)
|
||||
{
|
||||
record->rawData.writeTo(outputFile);
|
||||
Bytes(3).writeTo(outputFile);
|
||||
}
|
||||
Bytes(1).writeTo(outputFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createRawImageWriter(
|
||||
const ImageWriterProto& config)
|
||||
const ImageWriterProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new RawImageWriter(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "lib/layout.h"
|
||||
#include "lib/proto.h"
|
||||
#include "lib/environment.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
static unsigned getTrackStep()
|
||||
{
|
||||
@@ -10,8 +9,7 @@ static unsigned getTrackStep()
|
||||
(config.tpi() == 0) ? 1 : (config.drive().tpi() / config.tpi());
|
||||
|
||||
if (track_step == 0)
|
||||
Error()
|
||||
<< "this drive can't write this image, because the head is too big";
|
||||
error("this drive can't write this image, because the head is too big");
|
||||
return track_step;
|
||||
}
|
||||
|
||||
@@ -108,7 +106,7 @@ std::vector<std::pair<int, int>> Layout::getTrackOrdering(
|
||||
}
|
||||
|
||||
default:
|
||||
Error() << "LAYOUT: invalid track ordering";
|
||||
error("LAYOUT: invalid track ordering");
|
||||
}
|
||||
|
||||
return ordering;
|
||||
@@ -122,8 +120,9 @@ std::vector<unsigned> Layout::expandSectorList(
|
||||
if (sectorsProto.has_count())
|
||||
{
|
||||
if (sectorsProto.sector_size() != 0)
|
||||
Error() << "LAYOUT: if you use a sector count, you can't use an "
|
||||
"explicit sector list";
|
||||
error(
|
||||
"LAYOUT: if you use a sector count, you can't use an "
|
||||
"explicit sector list");
|
||||
|
||||
std::set<unsigned> sectorset;
|
||||
int id = sectorsProto.start_sector();
|
||||
@@ -150,7 +149,7 @@ std::vector<unsigned> Layout::expandSectorList(
|
||||
sectors.push_back(sectorId);
|
||||
}
|
||||
else
|
||||
Error() << "LAYOUT: no sectors in sector definition!";
|
||||
error("LAYOUT: no sectors in sector definition!");
|
||||
|
||||
return sectors;
|
||||
}
|
||||
@@ -194,8 +193,9 @@ std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
|
||||
trackInfo->filesystemSectorOrder =
|
||||
expandSectorList(layoutdata.filesystem());
|
||||
if (trackInfo->filesystemSectorOrder.size() != trackInfo->numSectors)
|
||||
Error() << "filesystem sector order list doesn't contain the right "
|
||||
"number of sectors";
|
||||
error(
|
||||
"filesystem sector order list doesn't contain the right "
|
||||
"number of sectors");
|
||||
}
|
||||
else
|
||||
trackInfo->filesystemSectorOrder = trackInfo->naturalSectorOrder;
|
||||
|
||||
16
lib/ldbs.cc
16
lib/ldbs.cc
@@ -2,10 +2,8 @@
|
||||
#include <string.h>
|
||||
#include "bytes.h"
|
||||
#include "ldbs.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
LDBS::LDBS()
|
||||
{}
|
||||
LDBS::LDBS() {}
|
||||
|
||||
uint32_t LDBS::put(const Bytes& data, uint32_t type)
|
||||
{
|
||||
@@ -23,9 +21,9 @@ uint32_t LDBS::read(const Bytes& data)
|
||||
ByteReader br(data);
|
||||
|
||||
br.seek(0);
|
||||
if ((br.read_be32() != LDBS_FILE_MAGIC)
|
||||
|| (br.read_be32() != LDBS_FILE_TYPE))
|
||||
Error() << "not a valid LDBS file";
|
||||
if ((br.read_be32() != LDBS_FILE_MAGIC) ||
|
||||
(br.read_be32() != LDBS_FILE_TYPE))
|
||||
error("not a valid LDBS file");
|
||||
|
||||
uint32_t address = br.read_le32();
|
||||
br.skip(4);
|
||||
@@ -35,7 +33,7 @@ uint32_t LDBS::read(const Bytes& data)
|
||||
{
|
||||
br.seek(address);
|
||||
if (br.read_be32() != LDBS_BLOCK_MAGIC)
|
||||
Error() << fmt::format("invalid block at address 0x{:x}", address);
|
||||
error("invalid block at address 0x{:x}", address);
|
||||
|
||||
Block& block = blocks[address];
|
||||
block.type = br.read_be32();
|
||||
@@ -46,7 +44,7 @@ uint32_t LDBS::read(const Bytes& data)
|
||||
|
||||
block.data.writer().append(br.read(size));
|
||||
}
|
||||
|
||||
|
||||
top = data.size();
|
||||
return trackDirectory;
|
||||
}
|
||||
@@ -76,6 +74,6 @@ const Bytes LDBS::write(uint32_t trackDirectory)
|
||||
bw.write_le32(previous);
|
||||
bw.write_le32(0);
|
||||
bw.write_le32(trackDirectory);
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
#include "fluxmap.h"
|
||||
#include "sector.h"
|
||||
#include "flux.h"
|
||||
#include "fmt/format.h"
|
||||
#include "logger.h"
|
||||
|
||||
static bool indented = false;
|
||||
static std::function<void(std::shared_ptr<const AnyLogMessage>)> loggerImpl =
|
||||
Logger::textLogger;
|
||||
|
||||
Logger& Logger::operator<<(std::shared_ptr<const AnyLogMessage> message)
|
||||
static std::function<void(std::shared_ptr<const AnyLogMessage>)> loggerImpl =
|
||||
[](auto message)
|
||||
{
|
||||
std::cout << Logger::toString(*message) << std::flush;
|
||||
};
|
||||
|
||||
void log(std::shared_ptr<const AnyLogMessage> message)
|
||||
{
|
||||
loggerImpl(message);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Logger::setLogger(
|
||||
@@ -22,11 +24,6 @@ void Logger::setLogger(
|
||||
loggerImpl = cb;
|
||||
}
|
||||
|
||||
void Logger::textLogger(std::shared_ptr<const AnyLogMessage> message)
|
||||
{
|
||||
std::cout << toString(*message) << std::flush;
|
||||
}
|
||||
|
||||
std::string Logger::toString(const AnyLogMessage& message)
|
||||
{
|
||||
std::stringstream stream;
|
||||
|
||||
28
lib/logger.h
28
lib/logger.h
@@ -91,22 +91,26 @@ typedef std::variant<std::string,
|
||||
OperationProgressLogMessage>
|
||||
AnyLogMessage;
|
||||
|
||||
class Logger
|
||||
template <class T>
|
||||
inline void log(const T& message)
|
||||
{
|
||||
public:
|
||||
Logger& operator<<(std::shared_ptr<const AnyLogMessage> message);
|
||||
log(std::make_shared<const AnyLogMessage>(message));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Logger& operator<<(const T& message)
|
||||
{
|
||||
return *this << std::make_shared<const AnyLogMessage>(message);
|
||||
}
|
||||
extern void log(std::shared_ptr<const AnyLogMessage> message);
|
||||
|
||||
static void setLogger(
|
||||
template <typename... Args>
|
||||
inline void log(fmt::string_view fstr, const Args&... args)
|
||||
{
|
||||
log(fmt::format(fstr, args...));
|
||||
}
|
||||
|
||||
namespace Logger
|
||||
{
|
||||
extern void setLogger(
|
||||
std::function<void(std::shared_ptr<const AnyLogMessage>)> cb);
|
||||
|
||||
static std::string toString(const AnyLogMessage&);
|
||||
static void textLogger(std::shared_ptr<const AnyLogMessage>);
|
||||
};
|
||||
extern std::string toString(const AnyLogMessage&);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
35
lib/proto.cc
35
lib/proto.cc
@@ -1,7 +1,6 @@
|
||||
#include "globals.h"
|
||||
#include "proto.h"
|
||||
#include "lib/common.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <regex>
|
||||
|
||||
ConfigProto config = []()
|
||||
@@ -17,7 +16,7 @@ static double toDouble(const std::string& value)
|
||||
size_t idx;
|
||||
double d = std::stod(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
error("invalid number '{}'", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -26,7 +25,7 @@ static int64_t toInt64(const std::string& value)
|
||||
size_t idx;
|
||||
int64_t d = std::stoll(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
error("invalid number '{}'", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -35,7 +34,7 @@ static uint64_t toUint64(const std::string& value)
|
||||
size_t idx;
|
||||
uint64_t d = std::stoull(value, &idx);
|
||||
if (value[idx] != '\0')
|
||||
Error() << fmt::format("invalid number '{}'", value);
|
||||
error("invalid number '{}'", value);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -46,7 +45,7 @@ void setRange(RangeProto* range, const std::string& data)
|
||||
|
||||
std::smatch dmatch;
|
||||
if (!std::regex_match(data, dmatch, DATA_REGEX))
|
||||
Error() << "invalid range '" << data << "'";
|
||||
error("invalid range '{}'", data);
|
||||
|
||||
int start = std::stoi(dmatch[1]);
|
||||
range->set_start(start);
|
||||
@@ -76,11 +75,9 @@ ProtoField resolveProtoPath(
|
||||
{
|
||||
const auto* field = descriptor->FindFieldByName(item);
|
||||
if (!field)
|
||||
Error() << fmt::format(
|
||||
"no such config field '{}' in '{}'", item, path);
|
||||
error("no such config field '{}' in '{}'", item, path);
|
||||
if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
|
||||
Error() << fmt::format(
|
||||
"config field '{}' in '{}' is not a message", item, path);
|
||||
error("config field '{}' in '{}' is not a message", item, path);
|
||||
|
||||
const auto* reflection = message->GetReflection();
|
||||
switch (field->label())
|
||||
@@ -98,7 +95,7 @@ ProtoField resolveProtoPath(
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << "bad proto label " << field->label();
|
||||
error("bad proto label {}", field->label());
|
||||
}
|
||||
|
||||
descriptor = message->GetDescriptor();
|
||||
@@ -106,8 +103,7 @@ ProtoField resolveProtoPath(
|
||||
|
||||
const auto* field = descriptor->FindFieldByName(trailing);
|
||||
if (!field)
|
||||
Error() << fmt::format(
|
||||
"no such config field '{}' in '{}'", trailing, path);
|
||||
error("no such config field '{}' in '{}'", trailing, path);
|
||||
|
||||
return std::make_pair(message, field);
|
||||
}
|
||||
@@ -161,7 +157,7 @@ void setProtoFieldFromString(ProtoField& protoField, const std::string& value)
|
||||
|
||||
const auto& it = boolvalues.find(value);
|
||||
if (it == boolvalues.end())
|
||||
Error() << "invalid boolean value";
|
||||
error("invalid boolean value");
|
||||
reflection->SetBool(message, field, it->second);
|
||||
break;
|
||||
}
|
||||
@@ -171,7 +167,7 @@ void setProtoFieldFromString(ProtoField& protoField, const std::string& value)
|
||||
const auto* enumfield = field->enum_type();
|
||||
const auto* enumvalue = enumfield->FindValueByName(value);
|
||||
if (!enumvalue)
|
||||
Error() << fmt::format("unrecognised enum value '{}'", value);
|
||||
error("unrecognised enum value '{}'", value);
|
||||
|
||||
reflection->SetEnum(message, field, enumvalue);
|
||||
break;
|
||||
@@ -192,7 +188,7 @@ void setProtoFieldFromString(ProtoField& protoField, const std::string& value)
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
Error() << "can't set this config value type";
|
||||
error("can't set this config value type");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,9 +245,8 @@ findAllProtoFields(google::protobuf::Message* message)
|
||||
|
||||
ConfigProto parseConfigBytes(const std::string_view& data)
|
||||
{
|
||||
ConfigProto proto;
|
||||
if (!proto.ParseFromArray(data.begin(), data.size()))
|
||||
Error() << "invalid internal config data";
|
||||
return proto;
|
||||
ConfigProto proto;
|
||||
if (!proto.ParseFromArray(data.begin(), data.size()))
|
||||
error("invalid internal config data");
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "fluxsink/fluxsink.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
@@ -59,7 +58,7 @@ private:
|
||||
void measureDiskRotation(
|
||||
nanoseconds_t& oneRevolution, nanoseconds_t& hardSectorThreshold)
|
||||
{
|
||||
Logger() << BeginSpeedOperationLogMessage();
|
||||
log(BeginSpeedOperationLogMessage());
|
||||
|
||||
int retries = 5;
|
||||
usbSetDrive(config.drive().drive(),
|
||||
@@ -74,8 +73,7 @@ void measureDiskRotation(
|
||||
|
||||
if (oneRevolution == 0)
|
||||
{
|
||||
Logger() << BeginOperationLogMessage{
|
||||
"Measuring drive rotational speed"};
|
||||
log(BeginOperationLogMessage{"Measuring drive rotational speed"});
|
||||
do
|
||||
{
|
||||
oneRevolution =
|
||||
@@ -87,13 +85,13 @@ void measureDiskRotation(
|
||||
retries--;
|
||||
} while ((oneRevolution == 0) && (retries > 0));
|
||||
config.mutable_drive()->set_rotational_period_ms(oneRevolution / 1e6);
|
||||
Logger() << EndOperationLogMessage{};
|
||||
log(EndOperationLogMessage{});
|
||||
}
|
||||
|
||||
if (oneRevolution == 0)
|
||||
Error() << "Failed\nIs a disk in the drive?";
|
||||
error("Failed\nIs a disk in the drive?");
|
||||
|
||||
Logger() << EndSpeedOperationLogMessage{oneRevolution};
|
||||
log(EndSpeedOperationLogMessage{oneRevolution});
|
||||
}
|
||||
|
||||
/* Given a set of sectors, deduplicates them sensibly (e.g. if there is a good
|
||||
@@ -224,15 +222,15 @@ ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder,
|
||||
if (!fluxSourceIterator.hasNext())
|
||||
continue;
|
||||
|
||||
Logger() << BeginReadOperationLogMessage{
|
||||
trackInfo->physicalTrack + offset, trackInfo->physicalSide};
|
||||
log(BeginReadOperationLogMessage{
|
||||
trackInfo->physicalTrack + offset, trackInfo->physicalSide});
|
||||
std::shared_ptr<const Fluxmap> fluxmap = fluxSourceIterator.next();
|
||||
// ->rescale(
|
||||
// 1.0 / config.flux_source().rescale());
|
||||
Logger() << EndReadOperationLogMessage()
|
||||
<< fmt::format("{0} ms in {1} bytes",
|
||||
(int)(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
log(EndReadOperationLogMessage());
|
||||
log("{0} ms in {1} bytes",
|
||||
(int)(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
|
||||
auto trackdataflux = decoder.decodeToSectors(fluxmap, trackInfo);
|
||||
trackFlux.trackDatas.push_back(trackdataflux);
|
||||
@@ -256,13 +254,13 @@ void writeTracks(FluxSink& fluxSink,
|
||||
std::function<bool(std::shared_ptr<const TrackInfo>& trackInfo)> verifier,
|
||||
std::vector<std::shared_ptr<const TrackInfo>>& trackInfos)
|
||||
{
|
||||
Logger() << BeginOperationLogMessage{"Encoding and writing to disk"};
|
||||
log(BeginOperationLogMessage{"Encoding and writing to disk"});
|
||||
|
||||
int index = 0;
|
||||
for (auto& trackInfo : trackInfos)
|
||||
{
|
||||
Logger() << OperationProgressLogMessage{
|
||||
index * 100 / (unsigned)trackInfos.size()};
|
||||
log(OperationProgressLogMessage{
|
||||
index * 100 / (unsigned)trackInfos.size()});
|
||||
index++;
|
||||
|
||||
testForEmergencyStop();
|
||||
@@ -275,8 +273,8 @@ void writeTracks(FluxSink& fluxSink,
|
||||
{
|
||||
unsigned physicalTrack = trackInfo->physicalTrack + offset;
|
||||
|
||||
Logger() << BeginWriteOperationLogMessage{
|
||||
physicalTrack, trackInfo->physicalSide};
|
||||
log(BeginWriteOperationLogMessage{
|
||||
physicalTrack, trackInfo->physicalSide});
|
||||
|
||||
if (offset == config.drive().group_offset())
|
||||
{
|
||||
@@ -286,7 +284,7 @@ void writeTracks(FluxSink& fluxSink,
|
||||
|
||||
fluxSink.writeFlux(
|
||||
physicalTrack, trackInfo->physicalSide, *fluxmap);
|
||||
Logger() << fmt::format("writing {0} ms in {1} bytes",
|
||||
log("writing {0} ms in {1} bytes",
|
||||
int(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
}
|
||||
@@ -298,25 +296,24 @@ void writeTracks(FluxSink& fluxSink,
|
||||
Fluxmap blank;
|
||||
fluxSink.writeFlux(
|
||||
physicalTrack, trackInfo->physicalSide, blank);
|
||||
Logger() << "erased";
|
||||
log("erased");
|
||||
}
|
||||
|
||||
Logger() << EndWriteOperationLogMessage();
|
||||
log(EndWriteOperationLogMessage());
|
||||
}
|
||||
|
||||
if (verifier(trackInfo))
|
||||
break;
|
||||
|
||||
if (retriesRemaining == 0)
|
||||
Error() << "fatal error on write";
|
||||
error("fatal error on write");
|
||||
|
||||
Logger() << fmt::format(
|
||||
"retrying; {} retries remaining", retriesRemaining);
|
||||
log("retrying; {} retries remaining", retriesRemaining);
|
||||
retriesRemaining--;
|
||||
}
|
||||
}
|
||||
|
||||
Logger() << EndOperationLogMessage{"Write complete"};
|
||||
log(EndOperationLogMessage{"Write complete"});
|
||||
}
|
||||
|
||||
void writeTracks(FluxSink& fluxSink,
|
||||
@@ -359,12 +356,12 @@ void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource);
|
||||
auto result = readGroup(
|
||||
fluxSourceIteratorHolder, trackInfo, *trackFlux, decoder);
|
||||
Logger() << TrackReadLogMessage{trackFlux};
|
||||
log(TrackReadLogMessage{trackFlux});
|
||||
|
||||
if (result != GOOD_READ)
|
||||
{
|
||||
adjustTrackOnError(fluxSource, trackInfo->physicalTrack);
|
||||
Logger() << "bad read";
|
||||
log("bad read");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -383,12 +380,12 @@ void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
sector->logicalSector);
|
||||
if (!s)
|
||||
{
|
||||
Logger() << "spurious sector on verify";
|
||||
log("spurious sector on verify");
|
||||
return false;
|
||||
}
|
||||
if (s->data != sector->data.slice(0, s->data.size()))
|
||||
{
|
||||
Logger() << "data mismatch on verify";
|
||||
log("data mismatch on verify");
|
||||
return false;
|
||||
}
|
||||
wanted.erase(sector->logicalTrack,
|
||||
@@ -397,7 +394,7 @@ void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
}
|
||||
if (!wanted.empty())
|
||||
{
|
||||
Logger() << "missing sector on verify";
|
||||
log("missing sector on verify");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -464,21 +461,20 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
|
||||
break;
|
||||
if (result == BAD_AND_CAN_NOT_RETRY)
|
||||
{
|
||||
Logger() << fmt::format("no more data; giving up");
|
||||
log("no more data; giving up");
|
||||
break;
|
||||
}
|
||||
|
||||
if (retriesRemaining == 0)
|
||||
{
|
||||
Logger() << fmt::format("giving up");
|
||||
log("giving up");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fluxSource.isHardware())
|
||||
{
|
||||
adjustTrackOnError(fluxSource, trackInfo->physicalTrack);
|
||||
Logger() << fmt::format(
|
||||
"retrying; {} retries remaining", retriesRemaining);
|
||||
log("retrying; {} retries remaining", retriesRemaining);
|
||||
retriesRemaining--;
|
||||
}
|
||||
}
|
||||
@@ -495,13 +491,13 @@ std::shared_ptr<const DiskFlux> readDiskCommand(
|
||||
|
||||
auto diskflux = std::make_shared<DiskFlux>();
|
||||
|
||||
Logger() << BeginOperationLogMessage{"Reading and decoding disk"};
|
||||
log(BeginOperationLogMessage{"Reading and decoding disk"});
|
||||
auto locations = Layout::computeLocations();
|
||||
unsigned index = 0;
|
||||
for (auto& trackInfo : locations)
|
||||
{
|
||||
Logger() << OperationProgressLogMessage{
|
||||
index * 100 / (unsigned)locations.size()};
|
||||
log(OperationProgressLogMessage{
|
||||
index * 100 / (unsigned)locations.size()});
|
||||
index++;
|
||||
|
||||
testForEmergencyStop();
|
||||
@@ -574,7 +570,7 @@ std::shared_ptr<const DiskFlux> readDiskCommand(
|
||||
}
|
||||
|
||||
/* track can't be modified below this point. */
|
||||
Logger() << TrackReadLogMessage{trackFlux};
|
||||
log(TrackReadLogMessage{trackFlux});
|
||||
}
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> all_sectors;
|
||||
@@ -585,8 +581,8 @@ std::shared_ptr<const DiskFlux> readDiskCommand(
|
||||
diskflux->image = std::make_shared<Image>(all_sectors);
|
||||
|
||||
/* diskflux can't be modified below this point. */
|
||||
Logger() << DiskReadLogMessage{diskflux};
|
||||
Logger() << EndOperationLogMessage{"Read complete"};
|
||||
log(DiskReadLogMessage{diskflux});
|
||||
log(EndOperationLogMessage{"Read complete"});
|
||||
return diskflux;
|
||||
}
|
||||
|
||||
@@ -603,33 +599,32 @@ void readDiskCommand(
|
||||
|
||||
void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
|
||||
{
|
||||
Logger() << BeginOperationLogMessage{"Performing raw read of disk"};
|
||||
log(BeginOperationLogMessage{"Performing raw read of disk"});
|
||||
|
||||
auto locations = Layout::computeLocations();
|
||||
unsigned index = 0;
|
||||
for (auto& trackInfo : locations)
|
||||
{
|
||||
Logger() << OperationProgressLogMessage{
|
||||
index * 100 / (int)locations.size()};
|
||||
log(OperationProgressLogMessage{index * 100 / (int)locations.size()});
|
||||
index++;
|
||||
|
||||
testForEmergencyStop();
|
||||
auto fluxSourceIterator = fluxsource.readFlux(
|
||||
trackInfo->physicalTrack, trackInfo->physicalSide);
|
||||
|
||||
Logger() << BeginReadOperationLogMessage{
|
||||
trackInfo->physicalTrack, trackInfo->physicalSide};
|
||||
log(BeginReadOperationLogMessage{
|
||||
trackInfo->physicalTrack, trackInfo->physicalSide});
|
||||
auto fluxmap = fluxSourceIterator->next();
|
||||
Logger() << EndReadOperationLogMessage()
|
||||
<< fmt::format("{0} ms in {1} bytes",
|
||||
(int)(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
log(EndReadOperationLogMessage());
|
||||
log("{0} ms in {1} bytes",
|
||||
(int)(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
|
||||
fluxsink.writeFlux(
|
||||
trackInfo->physicalTrack, trackInfo->physicalSide, *fluxmap);
|
||||
}
|
||||
|
||||
Logger() << EndOperationLogMessage{"Raw read complete"};
|
||||
log(EndOperationLogMessage{"Raw read complete"});
|
||||
}
|
||||
|
||||
void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
#include "flux.h"
|
||||
#include "sector.h"
|
||||
#include "layout.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
Sector::Sector(const LogicalLocation& location):
|
||||
LogicalLocation(location),
|
||||
physicalTrack(Layout::remapTrackLogicalToPhysical(location.logicalTrack)),
|
||||
physicalSide(Layout::remapSideLogicalToPhysical(location.logicalSide))
|
||||
{}
|
||||
LogicalLocation(location),
|
||||
physicalTrack(Layout::remapTrackLogicalToPhysical(location.logicalTrack)),
|
||||
physicalSide(Layout::remapSideLogicalToPhysical(location.logicalSide))
|
||||
{
|
||||
}
|
||||
|
||||
Sector::Sector(std::shared_ptr<const TrackInfo>& layout, unsigned sectorId):
|
||||
LogicalLocation({ layout->logicalTrack, layout->logicalSide, sectorId }),
|
||||
LogicalLocation({layout->logicalTrack, layout->logicalSide, sectorId}),
|
||||
physicalTrack(layout->physicalTrack),
|
||||
physicalSide(layout->physicalSide)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
std::string Sector::statusToString(Status status)
|
||||
{
|
||||
|
||||
@@ -5,332 +5,352 @@
|
||||
#include "bytes.h"
|
||||
#include "libusbp_config.h"
|
||||
#include "libusbp.hpp"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#define MAX_TRANSFER (32*1024)
|
||||
#define MAX_TRANSFER (32 * 1024)
|
||||
|
||||
/* Hacky: the board always operates in little-endian mode. */
|
||||
static uint16_t read_short_from_usb(uint16_t usb)
|
||||
{
|
||||
uint8_t* p = (uint8_t*)&usb;
|
||||
return p[0] | (p[1] << 8);
|
||||
uint8_t* p = (uint8_t*)&usb;
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
class FluxEngineUsb : public USB
|
||||
{
|
||||
private:
|
||||
uint8_t _buffer[FRAME_SIZE];
|
||||
uint8_t _buffer[FRAME_SIZE];
|
||||
|
||||
void usb_cmd_send(void* ptr, size_t len)
|
||||
{
|
||||
size_t rlen;
|
||||
_handle.write_pipe(FLUXENGINE_CMD_OUT_EP, ptr, len, &rlen);
|
||||
}
|
||||
void usb_cmd_send(void* ptr, size_t len)
|
||||
{
|
||||
size_t rlen;
|
||||
_handle.write_pipe(FLUXENGINE_CMD_OUT_EP, ptr, len, &rlen);
|
||||
}
|
||||
|
||||
void usb_cmd_recv(void* ptr, size_t len)
|
||||
{
|
||||
size_t rlen;
|
||||
_handle.read_pipe(FLUXENGINE_CMD_IN_EP, ptr, len, &rlen);
|
||||
}
|
||||
void usb_cmd_recv(void* ptr, size_t len)
|
||||
{
|
||||
size_t rlen;
|
||||
_handle.read_pipe(FLUXENGINE_CMD_IN_EP, ptr, len, &rlen);
|
||||
}
|
||||
|
||||
void usb_data_send(const Bytes& bytes)
|
||||
{
|
||||
size_t ptr = 0;
|
||||
while (ptr < bytes.size())
|
||||
{
|
||||
size_t rlen = bytes.size() - ptr;
|
||||
if (rlen > MAX_TRANSFER)
|
||||
rlen = MAX_TRANSFER;
|
||||
_handle.write_pipe(FLUXENGINE_DATA_OUT_EP, bytes.cbegin() + ptr, rlen, &rlen);
|
||||
ptr += rlen;
|
||||
}
|
||||
}
|
||||
void usb_data_send(const Bytes& bytes)
|
||||
{
|
||||
size_t ptr = 0;
|
||||
while (ptr < bytes.size())
|
||||
{
|
||||
size_t rlen = bytes.size() - ptr;
|
||||
if (rlen > MAX_TRANSFER)
|
||||
rlen = MAX_TRANSFER;
|
||||
_handle.write_pipe(
|
||||
FLUXENGINE_DATA_OUT_EP, bytes.cbegin() + ptr, rlen, &rlen);
|
||||
ptr += rlen;
|
||||
}
|
||||
}
|
||||
|
||||
void usb_data_recv(Bytes& bytes)
|
||||
{
|
||||
size_t ptr = 0;
|
||||
while (ptr < bytes.size())
|
||||
{
|
||||
size_t rlen = bytes.size() - ptr;
|
||||
if (rlen > MAX_TRANSFER)
|
||||
rlen = MAX_TRANSFER;
|
||||
_handle.read_pipe(FLUXENGINE_DATA_IN_EP, bytes.begin() + ptr, rlen, &rlen);
|
||||
ptr += rlen;
|
||||
if (rlen < MAX_TRANSFER)
|
||||
break;
|
||||
}
|
||||
|
||||
bytes.resize(ptr);
|
||||
}
|
||||
void usb_data_recv(Bytes& bytes)
|
||||
{
|
||||
size_t ptr = 0;
|
||||
while (ptr < bytes.size())
|
||||
{
|
||||
size_t rlen = bytes.size() - ptr;
|
||||
if (rlen > MAX_TRANSFER)
|
||||
rlen = MAX_TRANSFER;
|
||||
_handle.read_pipe(
|
||||
FLUXENGINE_DATA_IN_EP, bytes.begin() + ptr, rlen, &rlen);
|
||||
ptr += rlen;
|
||||
if (rlen < MAX_TRANSFER)
|
||||
break;
|
||||
}
|
||||
|
||||
bytes.resize(ptr);
|
||||
}
|
||||
|
||||
public:
|
||||
FluxEngineUsb(libusbp::device& device):
|
||||
_device(device),
|
||||
_interface(_device, 0, false),
|
||||
_handle(_interface)
|
||||
{
|
||||
int version = getVersion();
|
||||
if (version != FLUXENGINE_PROTOCOL_VERSION)
|
||||
Error() << "your FluxEngine firmware is at version " << version
|
||||
<< " but the client is for version " << FLUXENGINE_PROTOCOL_VERSION
|
||||
<< "; please upgrade";
|
||||
}
|
||||
FluxEngineUsb(libusbp::device& device):
|
||||
_device(device),
|
||||
_interface(_device, 0, false),
|
||||
_handle(_interface)
|
||||
{
|
||||
int version = getVersion();
|
||||
if (version != FLUXENGINE_PROTOCOL_VERSION)
|
||||
error(
|
||||
"your FluxEngine firmware is at version {} but the client is "
|
||||
"for version {}; please upgrade",
|
||||
version,
|
||||
FLUXENGINE_PROTOCOL_VERSION);
|
||||
}
|
||||
|
||||
private:
|
||||
libusbp::device _device;
|
||||
libusbp::generic_interface _interface;
|
||||
libusbp::generic_handle _handle;
|
||||
libusbp::device _device;
|
||||
libusbp::generic_interface _interface;
|
||||
libusbp::generic_handle _handle;
|
||||
|
||||
private:
|
||||
void bad_reply(void)
|
||||
{
|
||||
struct error_frame* f = (struct error_frame*) _buffer;
|
||||
if (f->f.type != F_FRAME_ERROR)
|
||||
Error() << fmt::format("bad USB reply 0x{:2x}", f->f.type);
|
||||
switch (f->error)
|
||||
{
|
||||
case F_ERROR_BAD_COMMAND:
|
||||
Error() << "device did not understand command";
|
||||
void bad_reply(void)
|
||||
{
|
||||
struct error_frame* f = (struct error_frame*)_buffer;
|
||||
if (f->f.type != F_FRAME_ERROR)
|
||||
error("bad USB reply 0x{:2x}", f->f.type);
|
||||
switch (f->error)
|
||||
{
|
||||
case F_ERROR_BAD_COMMAND:
|
||||
error("device did not understand command");
|
||||
|
||||
case F_ERROR_UNDERRUN:
|
||||
Error() << "USB underrun (not enough bandwidth)";
|
||||
|
||||
default:
|
||||
Error() << fmt::format("unknown device error {}", f->error);
|
||||
}
|
||||
}
|
||||
case F_ERROR_UNDERRUN:
|
||||
error("USB underrun (not enough bandwidth)");
|
||||
|
||||
template <typename T>
|
||||
T* await_reply(int desired)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
usb_cmd_recv(_buffer, sizeof(_buffer));
|
||||
struct any_frame* r = (struct any_frame*) _buffer;
|
||||
if (r->f.type == F_FRAME_DEBUG)
|
||||
{
|
||||
std::cout << "dev: " << ((struct debug_frame*)r)->payload << std::endl;
|
||||
continue;
|
||||
}
|
||||
if (r->f.type != desired)
|
||||
bad_reply();
|
||||
return (T*) r;
|
||||
}
|
||||
}
|
||||
default:
|
||||
error("unknown device error {}", f->error);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* await_reply(int desired)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
usb_cmd_recv(_buffer, sizeof(_buffer));
|
||||
struct any_frame* r = (struct any_frame*)_buffer;
|
||||
if (r->f.type == F_FRAME_DEBUG)
|
||||
{
|
||||
std::cout << "dev: " << ((struct debug_frame*)r)->payload
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
if (r->f.type != desired)
|
||||
bad_reply();
|
||||
return (T*)r;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
int getVersion()
|
||||
{
|
||||
struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} };
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY);
|
||||
return r->version;
|
||||
}
|
||||
int getVersion()
|
||||
{
|
||||
struct any_frame f = {
|
||||
.f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)}
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY);
|
||||
return r->version;
|
||||
}
|
||||
|
||||
void seek(int track)
|
||||
{
|
||||
struct seek_frame f = {
|
||||
.f = { .type = F_FRAME_SEEK_CMD, .size = sizeof(f) },
|
||||
.track = (uint8_t) track
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
await_reply<struct any_frame>(F_FRAME_SEEK_REPLY);
|
||||
}
|
||||
void seek(int track)
|
||||
{
|
||||
struct seek_frame f = {
|
||||
.f = {.type = F_FRAME_SEEK_CMD, .size = sizeof(f)},
|
||||
.track = (uint8_t)track
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
await_reply<struct any_frame>(F_FRAME_SEEK_REPLY);
|
||||
}
|
||||
|
||||
void recalibrate()
|
||||
{
|
||||
struct any_frame f = {
|
||||
.f = { .type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f) },
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY);
|
||||
}
|
||||
void recalibrate()
|
||||
{
|
||||
struct any_frame f = {
|
||||
.f = {.type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f)},
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY);
|
||||
}
|
||||
|
||||
nanoseconds_t getRotationalPeriod(int hardSectorCount)
|
||||
{
|
||||
struct measurespeed_frame f = {
|
||||
.f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)},
|
||||
.hard_sector_count = (uint8_t) hardSectorCount,
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
nanoseconds_t getRotationalPeriod(int hardSectorCount)
|
||||
{
|
||||
struct measurespeed_frame f = {
|
||||
.f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)},
|
||||
.hard_sector_count = (uint8_t)hardSectorCount,
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
|
||||
auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY);
|
||||
return r->period_ms * 1000000;
|
||||
}
|
||||
auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY);
|
||||
return r->period_ms * 1000000;
|
||||
}
|
||||
|
||||
void testBulkWrite()
|
||||
{
|
||||
struct any_frame f = { .f = {.type = F_FRAME_BULK_WRITE_TEST_CMD, .size = sizeof(f)} };
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
void testBulkWrite()
|
||||
{
|
||||
struct any_frame f = {
|
||||
.f = {.type = F_FRAME_BULK_WRITE_TEST_CMD, .size = sizeof(f)}
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
|
||||
/* These must match the device. */
|
||||
const int XSIZE = 64;
|
||||
const int YSIZE = 256;
|
||||
const int ZSIZE = 64;
|
||||
/* These must match the device. */
|
||||
const int XSIZE = 64;
|
||||
const int YSIZE = 256;
|
||||
const int ZSIZE = 64;
|
||||
|
||||
std::cout << "Reading data: " << std::flush;
|
||||
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
|
||||
double start_time = getCurrentTime();
|
||||
usb_data_recv(bulk_buffer);
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
std::cout << "Reading data: " << std::flush;
|
||||
Bytes bulk_buffer(XSIZE * YSIZE * ZSIZE);
|
||||
double start_time = getCurrentTime();
|
||||
usb_data_recv(bulk_buffer);
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
|
||||
std::cout << "transferred "
|
||||
<< bulk_buffer.size()
|
||||
<< " bytes from device -> PC in "
|
||||
<< int(elapsed_time * 1000.0)
|
||||
<< " ms ("
|
||||
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
|
||||
<< " kB/s)"
|
||||
<< std::endl;
|
||||
std::cout << "transferred " << bulk_buffer.size()
|
||||
<< " bytes from device -> PC in "
|
||||
<< int(elapsed_time * 1000.0) << " ms ("
|
||||
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
|
||||
<< " kB/s)" << std::endl;
|
||||
|
||||
for (int x=0; x<XSIZE; x++)
|
||||
for (int y=0; y<YSIZE; y++)
|
||||
for (int z=0; z<ZSIZE; z++)
|
||||
{
|
||||
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
|
||||
if (bulk_buffer[offset] != uint8_t(x+y+z))
|
||||
Error() << "data transfer corrupted at 0x"
|
||||
<< std::hex << offset << std::dec
|
||||
<< " "
|
||||
<< x << '.' << y << '.' << z << '.';
|
||||
}
|
||||
for (int x = 0; x < XSIZE; x++)
|
||||
for (int y = 0; y < YSIZE; y++)
|
||||
for (int z = 0; z < ZSIZE; z++)
|
||||
{
|
||||
int offset = x * XSIZE * YSIZE + y * ZSIZE + z;
|
||||
if (bulk_buffer[offset] != uint8_t(x + y + z))
|
||||
error("data transfer corrupted at 0x{:x} {}.{}.{}",
|
||||
offset,
|
||||
x,
|
||||
y,
|
||||
z);
|
||||
}
|
||||
|
||||
await_reply<struct any_frame>(F_FRAME_BULK_WRITE_TEST_REPLY);
|
||||
}
|
||||
await_reply<struct any_frame>(F_FRAME_BULK_WRITE_TEST_REPLY);
|
||||
}
|
||||
|
||||
void testBulkRead()
|
||||
{
|
||||
struct any_frame f = { .f = {.type = F_FRAME_BULK_READ_TEST_CMD, .size = sizeof(f)} };
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
void testBulkRead()
|
||||
{
|
||||
struct any_frame f = {
|
||||
.f = {.type = F_FRAME_BULK_READ_TEST_CMD, .size = sizeof(f)}
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
|
||||
/* These must match the device. */
|
||||
const int XSIZE = 64;
|
||||
const int YSIZE = 256;
|
||||
const int ZSIZE = 64;
|
||||
/* These must match the device. */
|
||||
const int XSIZE = 64;
|
||||
const int YSIZE = 256;
|
||||
const int ZSIZE = 64;
|
||||
|
||||
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
|
||||
for (int x=0; x<XSIZE; x++)
|
||||
for (int y=0; y<YSIZE; y++)
|
||||
for (int z=0; z<ZSIZE; z++)
|
||||
{
|
||||
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
|
||||
bulk_buffer[offset] = uint8_t(x+y+z);
|
||||
}
|
||||
Bytes bulk_buffer(XSIZE * YSIZE * ZSIZE);
|
||||
for (int x = 0; x < XSIZE; x++)
|
||||
for (int y = 0; y < YSIZE; y++)
|
||||
for (int z = 0; z < ZSIZE; z++)
|
||||
{
|
||||
int offset = x * XSIZE * YSIZE + y * ZSIZE + z;
|
||||
bulk_buffer[offset] = uint8_t(x + y + z);
|
||||
}
|
||||
|
||||
std::cout << "Writing data: " << std::flush;
|
||||
double start_time = getCurrentTime();
|
||||
usb_data_send(bulk_buffer);
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
std::cout << "Writing data: " << std::flush;
|
||||
double start_time = getCurrentTime();
|
||||
usb_data_send(bulk_buffer);
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
|
||||
std::cout << "transferred "
|
||||
<< bulk_buffer.size()
|
||||
<< " bytes from PC -> device in "
|
||||
<< int(elapsed_time * 1000.0)
|
||||
<< " ms ("
|
||||
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
|
||||
<< " kB/s)"
|
||||
<< std::endl;
|
||||
std::cout << "transferred " << bulk_buffer.size()
|
||||
<< " bytes from PC -> device in "
|
||||
<< int(elapsed_time * 1000.0) << " ms ("
|
||||
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
|
||||
<< " kB/s)" << std::endl;
|
||||
|
||||
await_reply<struct any_frame>(F_FRAME_BULK_READ_TEST_REPLY);
|
||||
}
|
||||
await_reply<struct any_frame>(F_FRAME_BULK_READ_TEST_REPLY);
|
||||
}
|
||||
|
||||
Bytes read(int side, bool synced, nanoseconds_t readTime,
|
||||
nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
struct read_frame f = {
|
||||
.f = { .type = F_FRAME_READ_CMD, .size = sizeof(f) },
|
||||
.side = (uint8_t) side,
|
||||
.synced = (uint8_t) synced,
|
||||
};
|
||||
f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
||||
uint16_t milliseconds = readTime / 1e6;
|
||||
((uint8_t*)&f.milliseconds)[0] = milliseconds;
|
||||
((uint8_t*)&f.milliseconds)[1] = milliseconds >> 8;
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
Bytes read(int side,
|
||||
bool synced,
|
||||
nanoseconds_t readTime,
|
||||
nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
struct read_frame f = {
|
||||
.f = {.type = F_FRAME_READ_CMD, .size = sizeof(f)},
|
||||
.side = (uint8_t)side,
|
||||
.synced = (uint8_t)synced,
|
||||
};
|
||||
f.hardsec_threshold_ms =
|
||||
(hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
||||
uint16_t milliseconds = readTime / 1e6;
|
||||
((uint8_t*)&f.milliseconds)[0] = milliseconds;
|
||||
((uint8_t*)&f.milliseconds)[1] = milliseconds >> 8;
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
|
||||
auto fluxmap = std::unique_ptr<Fluxmap>(new Fluxmap);
|
||||
auto fluxmap = std::unique_ptr<Fluxmap>(new Fluxmap);
|
||||
|
||||
Bytes buffer(1024*1024);
|
||||
usb_data_recv(buffer);
|
||||
Bytes buffer(1024 * 1024);
|
||||
usb_data_recv(buffer);
|
||||
|
||||
await_reply<struct any_frame>(F_FRAME_READ_REPLY);
|
||||
return buffer;
|
||||
}
|
||||
await_reply<struct any_frame>(F_FRAME_READ_REPLY);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void write(int side, const Bytes& bytes, nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
unsigned safelen = bytes.size() & ~(FRAME_SIZE-1);
|
||||
Bytes safeBytes = bytes.slice(0, safelen);
|
||||
void write(int side, const Bytes& bytes, nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
unsigned safelen = bytes.size() & ~(FRAME_SIZE - 1);
|
||||
Bytes safeBytes = bytes.slice(0, safelen);
|
||||
|
||||
struct write_frame f = {
|
||||
.f = { .type = F_FRAME_WRITE_CMD, .size = sizeof(f) },
|
||||
.side = (uint8_t) side,
|
||||
};
|
||||
f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
||||
((uint8_t*)&f.bytes_to_write)[0] = safelen;
|
||||
((uint8_t*)&f.bytes_to_write)[1] = safelen >> 8;
|
||||
((uint8_t*)&f.bytes_to_write)[2] = safelen >> 16;
|
||||
((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24;
|
||||
struct write_frame f = {
|
||||
.f = {.type = F_FRAME_WRITE_CMD, .size = sizeof(f)},
|
||||
.side = (uint8_t)side,
|
||||
};
|
||||
f.hardsec_threshold_ms =
|
||||
(hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
||||
((uint8_t*)&f.bytes_to_write)[0] = safelen;
|
||||
((uint8_t*)&f.bytes_to_write)[1] = safelen >> 8;
|
||||
((uint8_t*)&f.bytes_to_write)[2] = safelen >> 16;
|
||||
((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24;
|
||||
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
usb_data_send(safeBytes);
|
||||
|
||||
await_reply<struct any_frame>(F_FRAME_WRITE_REPLY);
|
||||
}
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
usb_data_send(safeBytes);
|
||||
|
||||
void erase(int side, nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
struct erase_frame f = {
|
||||
.f = { .type = F_FRAME_ERASE_CMD, .size = sizeof(f) },
|
||||
.side = (uint8_t) side,
|
||||
};
|
||||
f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
await_reply<struct any_frame>(F_FRAME_WRITE_REPLY);
|
||||
}
|
||||
|
||||
await_reply<struct any_frame>(F_FRAME_ERASE_REPLY);
|
||||
}
|
||||
void erase(int side, nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
struct erase_frame f = {
|
||||
.f = {.type = F_FRAME_ERASE_CMD, .size = sizeof(f)},
|
||||
.side = (uint8_t)side,
|
||||
};
|
||||
f.hardsec_threshold_ms =
|
||||
(hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
|
||||
void setDrive(int drive, bool high_density, int index_mode)
|
||||
{
|
||||
struct set_drive_frame f = {
|
||||
.f = { .type = F_FRAME_SET_DRIVE_CMD, .size = sizeof(f) },
|
||||
.drive = (uint8_t) drive,
|
||||
.high_density = high_density,
|
||||
.index_mode = (uint8_t) index_mode
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
await_reply<struct any_frame>(F_FRAME_SET_DRIVE_REPLY);
|
||||
}
|
||||
await_reply<struct any_frame>(F_FRAME_ERASE_REPLY);
|
||||
}
|
||||
|
||||
void measureVoltages(struct voltages_frame* voltages)
|
||||
{
|
||||
struct any_frame f = {
|
||||
{ .type = F_FRAME_MEASURE_VOLTAGES_CMD, .size = sizeof(f) },
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
void setDrive(int drive, bool high_density, int index_mode)
|
||||
{
|
||||
struct set_drive_frame f = {
|
||||
.f = {.type = F_FRAME_SET_DRIVE_CMD, .size = sizeof(f)},
|
||||
.drive = (uint8_t)drive,
|
||||
.high_density = high_density,
|
||||
.index_mode = (uint8_t)index_mode
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
await_reply<struct any_frame>(F_FRAME_SET_DRIVE_REPLY);
|
||||
}
|
||||
|
||||
auto convert_voltages_from_usb = [&](const struct voltages& vin, struct voltages& vout)
|
||||
{
|
||||
vout.logic0_mv = read_short_from_usb(vin.logic0_mv);
|
||||
vout.logic1_mv = read_short_from_usb(vin.logic1_mv);
|
||||
};
|
||||
void measureVoltages(struct voltages_frame* voltages)
|
||||
{
|
||||
struct any_frame f = {
|
||||
{.type = F_FRAME_MEASURE_VOLTAGES_CMD, .size = sizeof(f)},
|
||||
};
|
||||
usb_cmd_send(&f, f.f.size);
|
||||
|
||||
struct voltages_frame* r = await_reply<struct voltages_frame>(F_FRAME_MEASURE_VOLTAGES_REPLY);
|
||||
convert_voltages_from_usb(r->input_both_off, voltages->input_both_off);
|
||||
convert_voltages_from_usb(r->input_drive_0_selected, voltages->input_drive_0_selected);
|
||||
convert_voltages_from_usb(r->input_drive_1_selected, voltages->input_drive_1_selected);
|
||||
convert_voltages_from_usb(r->input_drive_0_running, voltages->input_drive_0_running);
|
||||
convert_voltages_from_usb(r->input_drive_1_running, voltages->input_drive_1_running);
|
||||
convert_voltages_from_usb(r->output_both_off, voltages->output_both_off);
|
||||
convert_voltages_from_usb(r->output_drive_0_selected, voltages->output_drive_0_selected);
|
||||
convert_voltages_from_usb(r->output_drive_1_selected, voltages->output_drive_1_selected);
|
||||
convert_voltages_from_usb(r->output_drive_0_running, voltages->output_drive_0_running);
|
||||
convert_voltages_from_usb(r->output_drive_1_running, voltages->output_drive_1_running);
|
||||
}
|
||||
auto convert_voltages_from_usb =
|
||||
[&](const struct voltages& vin, struct voltages& vout)
|
||||
{
|
||||
vout.logic0_mv = read_short_from_usb(vin.logic0_mv);
|
||||
vout.logic1_mv = read_short_from_usb(vin.logic1_mv);
|
||||
};
|
||||
|
||||
struct voltages_frame* r =
|
||||
await_reply<struct voltages_frame>(F_FRAME_MEASURE_VOLTAGES_REPLY);
|
||||
convert_voltages_from_usb(r->input_both_off, voltages->input_both_off);
|
||||
convert_voltages_from_usb(
|
||||
r->input_drive_0_selected, voltages->input_drive_0_selected);
|
||||
convert_voltages_from_usb(
|
||||
r->input_drive_1_selected, voltages->input_drive_1_selected);
|
||||
convert_voltages_from_usb(
|
||||
r->input_drive_0_running, voltages->input_drive_0_running);
|
||||
convert_voltages_from_usb(
|
||||
r->input_drive_1_running, voltages->input_drive_1_running);
|
||||
convert_voltages_from_usb(
|
||||
r->output_both_off, voltages->output_both_off);
|
||||
convert_voltages_from_usb(
|
||||
r->output_drive_0_selected, voltages->output_drive_0_selected);
|
||||
convert_voltages_from_usb(
|
||||
r->output_drive_1_selected, voltages->output_drive_1_selected);
|
||||
convert_voltages_from_usb(
|
||||
r->output_drive_0_running, voltages->output_drive_0_running);
|
||||
convert_voltages_from_usb(
|
||||
r->output_drive_1_running, voltages->output_drive_1_running);
|
||||
}
|
||||
};
|
||||
|
||||
USB* createFluxengineUsb(libusbp::device& device)
|
||||
{
|
||||
return new FluxEngineUsb(device);
|
||||
return new FluxEngineUsb(device);
|
||||
}
|
||||
|
||||
@@ -2,155 +2,153 @@
|
||||
#include "usb.h"
|
||||
#include "protocol.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include "greaseweazle.h"
|
||||
|
||||
Bytes fluxEngineToGreaseWeazle(const Bytes& fldata, nanoseconds_t clock)
|
||||
{
|
||||
Bytes gwdata;
|
||||
ByteWriter bw(gwdata);
|
||||
ByteReader br(fldata);
|
||||
uint32_t ticks_fl = 0;
|
||||
uint32_t ticks_gw = 0;
|
||||
Bytes gwdata;
|
||||
ByteWriter bw(gwdata);
|
||||
ByteReader br(fldata);
|
||||
uint32_t ticks_fl = 0;
|
||||
uint32_t ticks_gw = 0;
|
||||
|
||||
auto write_28 = [&](uint32_t val) {
|
||||
bw.write_8(1 | (val<<1) & 0xff);
|
||||
bw.write_8(1 | (val>>6) & 0xff);
|
||||
bw.write_8(1 | (val>>13) & 0xff);
|
||||
bw.write_8(1 | (val>>20) & 0xff);
|
||||
};
|
||||
auto write_28 = [&](uint32_t val)
|
||||
{
|
||||
bw.write_8(1 | (val << 1) & 0xff);
|
||||
bw.write_8(1 | (val >> 6) & 0xff);
|
||||
bw.write_8(1 | (val >> 13) & 0xff);
|
||||
bw.write_8(1 | (val >> 20) & 0xff);
|
||||
};
|
||||
|
||||
while (!br.eof())
|
||||
{
|
||||
uint8_t b = br.read_8();
|
||||
ticks_fl += b & 0x3f;
|
||||
if (b & F_BIT_PULSE)
|
||||
{
|
||||
uint32_t newticks_gw = ticks_fl * NS_PER_TICK / clock;
|
||||
uint32_t delta = newticks_gw - ticks_gw;
|
||||
if (delta < 250)
|
||||
bw.write_8(delta);
|
||||
else
|
||||
{
|
||||
int high = (delta-250) / 255;
|
||||
if (high < 5)
|
||||
{
|
||||
bw.write_8(250 + high);
|
||||
bw.write_8(1 + (delta-250) % 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
bw.write_8(255);
|
||||
bw.write_8(FLUXOP_SPACE);
|
||||
write_28(delta - 249);
|
||||
bw.write_8(249);
|
||||
}
|
||||
}
|
||||
ticks_gw = newticks_gw;
|
||||
}
|
||||
}
|
||||
bw.write_8(0); /* end of stream */
|
||||
return gwdata;
|
||||
while (!br.eof())
|
||||
{
|
||||
uint8_t b = br.read_8();
|
||||
ticks_fl += b & 0x3f;
|
||||
if (b & F_BIT_PULSE)
|
||||
{
|
||||
uint32_t newticks_gw = ticks_fl * NS_PER_TICK / clock;
|
||||
uint32_t delta = newticks_gw - ticks_gw;
|
||||
if (delta < 250)
|
||||
bw.write_8(delta);
|
||||
else
|
||||
{
|
||||
int high = (delta - 250) / 255;
|
||||
if (high < 5)
|
||||
{
|
||||
bw.write_8(250 + high);
|
||||
bw.write_8(1 + (delta - 250) % 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
bw.write_8(255);
|
||||
bw.write_8(FLUXOP_SPACE);
|
||||
write_28(delta - 249);
|
||||
bw.write_8(249);
|
||||
}
|
||||
}
|
||||
ticks_gw = newticks_gw;
|
||||
}
|
||||
}
|
||||
bw.write_8(0); /* end of stream */
|
||||
return gwdata;
|
||||
}
|
||||
|
||||
Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock)
|
||||
{
|
||||
Bytes fldata;
|
||||
ByteReader br(gwdata);
|
||||
ByteWriter bw(fldata);
|
||||
Bytes fldata;
|
||||
ByteReader br(gwdata);
|
||||
ByteWriter bw(fldata);
|
||||
|
||||
auto read_28 = [&]() {
|
||||
return ((br.read_8() & 0xfe) >> 1)
|
||||
| ((br.read_8() & 0xfe) << 6)
|
||||
| ((br.read_8() & 0xfe) << 13)
|
||||
| ((br.read_8() & 0xfe) << 20);
|
||||
auto read_28 = [&]()
|
||||
{
|
||||
return ((br.read_8() & 0xfe) >> 1) | ((br.read_8() & 0xfe) << 6) |
|
||||
((br.read_8() & 0xfe) << 13) | ((br.read_8() & 0xfe) << 20);
|
||||
};
|
||||
|
||||
uint32_t ticks_gw = 0;
|
||||
uint32_t lastevent_fl = 0;
|
||||
uint32_t index_gw = ~0;
|
||||
uint32_t ticks_gw = 0;
|
||||
uint32_t lastevent_fl = 0;
|
||||
uint32_t index_gw = ~0;
|
||||
|
||||
while (!br.eof())
|
||||
{
|
||||
uint8_t b = br.read_8();
|
||||
if (!b)
|
||||
break;
|
||||
while (!br.eof())
|
||||
{
|
||||
uint8_t b = br.read_8();
|
||||
if (!b)
|
||||
break;
|
||||
|
||||
uint8_t event = 0;
|
||||
if (b == 255)
|
||||
{
|
||||
switch (br.read_8())
|
||||
{
|
||||
case FLUXOP_INDEX:
|
||||
index_gw = ticks_gw + read_28();
|
||||
break;
|
||||
uint8_t event = 0;
|
||||
if (b == 255)
|
||||
{
|
||||
switch (br.read_8())
|
||||
{
|
||||
case FLUXOP_INDEX:
|
||||
index_gw = ticks_gw + read_28();
|
||||
break;
|
||||
|
||||
case FLUXOP_SPACE:
|
||||
ticks_gw += read_28();
|
||||
break;
|
||||
case FLUXOP_SPACE:
|
||||
ticks_gw += read_28();
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << "bad opcode in GreaseWeazle stream";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b < 250)
|
||||
ticks_gw += b;
|
||||
else
|
||||
{
|
||||
int delta = 250 + (b-250)*255 + br.read_8() - 1;
|
||||
ticks_gw += delta;
|
||||
}
|
||||
event = F_BIT_PULSE;
|
||||
}
|
||||
default:
|
||||
error("bad opcode in GreaseWeazle stream");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b < 250)
|
||||
ticks_gw += b;
|
||||
else
|
||||
{
|
||||
int delta = 250 + (b - 250) * 255 + br.read_8() - 1;
|
||||
ticks_gw += delta;
|
||||
}
|
||||
event = F_BIT_PULSE;
|
||||
}
|
||||
|
||||
if (event)
|
||||
{
|
||||
uint32_t index_fl = round((index_gw * clock) / NS_PER_TICK);
|
||||
uint32_t ticks_fl = round((ticks_gw * clock) / NS_PER_TICK);
|
||||
if (index_gw != ~0)
|
||||
{
|
||||
if (index_fl < ticks_fl)
|
||||
{
|
||||
uint32_t delta_fl = index_fl - lastevent_fl;
|
||||
while (delta_fl > 0x3f)
|
||||
{
|
||||
bw.write_8(0x3f);
|
||||
delta_fl -= 0x3f;
|
||||
}
|
||||
bw.write_8(delta_fl | F_BIT_INDEX);
|
||||
lastevent_fl = index_fl;
|
||||
index_gw = ~0;
|
||||
}
|
||||
else if (index_fl == ticks_fl)
|
||||
event |= F_BIT_INDEX;
|
||||
}
|
||||
if (event)
|
||||
{
|
||||
uint32_t index_fl = round((index_gw * clock) / NS_PER_TICK);
|
||||
uint32_t ticks_fl = round((ticks_gw * clock) / NS_PER_TICK);
|
||||
if (index_gw != ~0)
|
||||
{
|
||||
if (index_fl < ticks_fl)
|
||||
{
|
||||
uint32_t delta_fl = index_fl - lastevent_fl;
|
||||
while (delta_fl > 0x3f)
|
||||
{
|
||||
bw.write_8(0x3f);
|
||||
delta_fl -= 0x3f;
|
||||
}
|
||||
bw.write_8(delta_fl | F_BIT_INDEX);
|
||||
lastevent_fl = index_fl;
|
||||
index_gw = ~0;
|
||||
}
|
||||
else if (index_fl == ticks_fl)
|
||||
event |= F_BIT_INDEX;
|
||||
}
|
||||
|
||||
uint32_t delta_fl = ticks_fl - lastevent_fl;
|
||||
while (delta_fl > 0x3f)
|
||||
{
|
||||
bw.write_8(0x3f);
|
||||
delta_fl -= 0x3f;
|
||||
}
|
||||
bw.write_8(delta_fl | event);
|
||||
lastevent_fl = ticks_fl;
|
||||
}
|
||||
}
|
||||
uint32_t delta_fl = ticks_fl - lastevent_fl;
|
||||
while (delta_fl > 0x3f)
|
||||
{
|
||||
bw.write_8(0x3f);
|
||||
delta_fl -= 0x3f;
|
||||
}
|
||||
bw.write_8(delta_fl | event);
|
||||
lastevent_fl = ticks_fl;
|
||||
}
|
||||
}
|
||||
|
||||
return fldata;
|
||||
return fldata;
|
||||
}
|
||||
|
||||
/* Left-truncates at the first index mark, so the resulting data as aligned at
|
||||
* the index. */
|
||||
Bytes stripPartialRotation(const Bytes& fldata)
|
||||
{
|
||||
for (unsigned i=0; i<fldata.size(); i++)
|
||||
{
|
||||
uint8_t b = fldata[i];
|
||||
if (b & F_BIT_INDEX)
|
||||
return fldata.slice(i);
|
||||
}
|
||||
return fldata;
|
||||
for (unsigned i = 0; i < fldata.size(); i++)
|
||||
{
|
||||
uint8_t b = fldata[i];
|
||||
if (b & F_BIT_INDEX)
|
||||
return fldata.slice(i);
|
||||
}
|
||||
return fldata;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "protocol.h"
|
||||
#include "fluxmap.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/usb/usb.pb.h"
|
||||
#include "greaseweazle.h"
|
||||
#include "serial.h"
|
||||
@@ -12,25 +11,38 @@ static const char* gw_error(int e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case ACK_OKAY: return "OK";
|
||||
case ACK_BAD_COMMAND: return "Bad command";
|
||||
case ACK_NO_INDEX: return "No index";
|
||||
case ACK_NO_TRK0: return "No track 0";
|
||||
case ACK_FLUX_OVERFLOW: return "Overflow";
|
||||
case ACK_FLUX_UNDERFLOW: return "Underflow";
|
||||
case ACK_WRPROT: return "Write protected";
|
||||
case ACK_NO_UNIT: return "No unit";
|
||||
case ACK_NO_BUS: return "No bus";
|
||||
case ACK_BAD_UNIT: return "Invalid unit";
|
||||
case ACK_BAD_PIN: return "Invalid pin";
|
||||
case ACK_BAD_CYLINDER: return "Invalid track";
|
||||
default: return "Unknown error";
|
||||
case ACK_OKAY:
|
||||
return "OK";
|
||||
case ACK_BAD_COMMAND:
|
||||
return "Bad command";
|
||||
case ACK_NO_INDEX:
|
||||
return "No index";
|
||||
case ACK_NO_TRK0:
|
||||
return "No track 0";
|
||||
case ACK_FLUX_OVERFLOW:
|
||||
return "Overflow";
|
||||
case ACK_FLUX_UNDERFLOW:
|
||||
return "Underflow";
|
||||
case ACK_WRPROT:
|
||||
return "Write protected";
|
||||
case ACK_NO_UNIT:
|
||||
return "No unit";
|
||||
case ACK_NO_BUS:
|
||||
return "No bus";
|
||||
case ACK_BAD_UNIT:
|
||||
return "Invalid unit";
|
||||
case ACK_BAD_PIN:
|
||||
return "Invalid pin";
|
||||
case ACK_BAD_CYLINDER:
|
||||
return "Invalid track";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t ss_rand_next(uint32_t x)
|
||||
{
|
||||
return (x&1) ? (x>>1) ^ 0x80000062 : x>>1;
|
||||
return (x & 1) ? (x >> 1) ^ 0x80000062 : x >> 1;
|
||||
}
|
||||
|
||||
class GreaseWeazleUsb : public USB
|
||||
@@ -41,10 +53,8 @@ private:
|
||||
uint8_t buffer[4];
|
||||
_serial->read(buffer, sizeof(buffer));
|
||||
|
||||
return ((buffer[0] & 0xfe) >> 1)
|
||||
| ((buffer[1] & 0xfe) << 6)
|
||||
| ((buffer[2] & 0xfe) << 13)
|
||||
| ((buffer[3] & 0xfe) << 20);
|
||||
return ((buffer[0] & 0xfe) >> 1) | ((buffer[1] & 0xfe) << 6) |
|
||||
((buffer[2] & 0xfe) << 13) | ((buffer[3] & 0xfe) << 20);
|
||||
}
|
||||
|
||||
void do_command(const Bytes& command)
|
||||
@@ -55,16 +65,20 @@ private:
|
||||
_serial->read(buffer, sizeof(buffer));
|
||||
|
||||
if (buffer[0] != command[0])
|
||||
Error() << fmt::format("command returned garbage (0x{:x} != 0x{:x} with status 0x{:x})",
|
||||
buffer[0], command[0], buffer[1]);
|
||||
error(
|
||||
"command returned garbage (0x{:x} != 0x{:x} with status "
|
||||
"0x{:x})",
|
||||
buffer[0],
|
||||
command[0],
|
||||
buffer[1]);
|
||||
if (buffer[1])
|
||||
Error() << fmt::format("GreaseWeazle error: {}", gw_error(buffer[1]));
|
||||
error("GreaseWeazle error: {}", gw_error(buffer[1]));
|
||||
}
|
||||
|
||||
public:
|
||||
GreaseWeazleUsb(const std::string& port, const GreaseWeazleProto& config):
|
||||
_serial(SerialPort::openSerialPort(port)),
|
||||
_config(config)
|
||||
_serial(SerialPort::openSerialPort(port)),
|
||||
_config(config)
|
||||
{
|
||||
int version = getVersion();
|
||||
if (version >= 29)
|
||||
@@ -75,18 +89,21 @@ public:
|
||||
_version = V22;
|
||||
else
|
||||
{
|
||||
Error() << "only GreaseWeazle firmware versions 22 and 24 or above are currently "
|
||||
<< "supported, but you have version " << version << ". Please file a bug.";
|
||||
error(
|
||||
"only GreaseWeazle firmware versions 22 and 24 or above are "
|
||||
"currently "
|
||||
"supported, but you have version {}. Please file a bug.",
|
||||
version);
|
||||
}
|
||||
|
||||
/* Configure the hardware. */
|
||||
|
||||
do_command({ CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type() });
|
||||
do_command({CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type()});
|
||||
}
|
||||
|
||||
int getVersion()
|
||||
{
|
||||
do_command({ CMD_GET_INFO, 3, GETINFO_FIRMWARE });
|
||||
do_command({CMD_GET_INFO, 3, GETINFO_FIRMWARE});
|
||||
|
||||
Bytes response = _serial->readBytes(32);
|
||||
ByteReader br(response);
|
||||
@@ -103,16 +120,16 @@ public:
|
||||
{
|
||||
seek(0);
|
||||
}
|
||||
|
||||
|
||||
void seek(int track)
|
||||
{
|
||||
do_command({ CMD_SEEK, 3, (uint8_t)track });
|
||||
do_command({CMD_SEEK, 3, (uint8_t)track});
|
||||
}
|
||||
|
||||
|
||||
nanoseconds_t getRotationalPeriod(int hardSectorCount)
|
||||
{
|
||||
if (hardSectorCount != 0)
|
||||
Error() << "hard sectors are currently unsupported on the GreaseWeazel";
|
||||
error("hard sectors are currently unsupported on the GreaseWeazel");
|
||||
|
||||
/* The GreaseWeazle doesn't have a command to fetch the period directly,
|
||||
* so we have to do a flux read. */
|
||||
@@ -120,7 +137,7 @@ public:
|
||||
switch (_version)
|
||||
{
|
||||
case V22:
|
||||
do_command({ CMD_READ_FLUX, 2 });
|
||||
do_command({CMD_READ_FLUX, 2});
|
||||
break;
|
||||
|
||||
case V24:
|
||||
@@ -130,8 +147,8 @@ public:
|
||||
cmd.writer()
|
||||
.write_8(CMD_READ_FLUX)
|
||||
.write_8(cmd.size())
|
||||
.write_le32(0) //ticks default value (guessed)
|
||||
.write_le16(2);//revolutions
|
||||
.write_le32(0) // ticks default value (guessed)
|
||||
.write_le16(2); // revolutions
|
||||
do_command(cmd);
|
||||
}
|
||||
}
|
||||
@@ -164,7 +181,7 @@ public:
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << "bad opcode in GreaseWeazle stream";
|
||||
error("bad opcode in GreaseWeazle stream");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -173,24 +190,26 @@ public:
|
||||
ticks_gw += b;
|
||||
else
|
||||
{
|
||||
int delta = 250 + (b-250)*255 + _serial->readByte() - 1;
|
||||
int delta = 250 + (b - 250) * 255 + _serial->readByte() - 1;
|
||||
ticks_gw += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (secondindex == ~0)
|
||||
Error() << "unable to determine disk rotational period (is a disk in the drive?)";
|
||||
do_command({ CMD_GET_FLUX_STATUS, 2 });
|
||||
error(
|
||||
"unable to determine disk rotational period (is a disk in the "
|
||||
"drive?)");
|
||||
do_command({CMD_GET_FLUX_STATUS, 2});
|
||||
|
||||
_revolutions = (nanoseconds_t)(secondindex - firstindex) * _clock;
|
||||
return _revolutions;
|
||||
}
|
||||
|
||||
|
||||
void testBulkWrite()
|
||||
{
|
||||
std::cout << "Writing data: " << std::flush;
|
||||
const int LEN = 10*1024*1024;
|
||||
const int LEN = 10 * 1024 * 1024;
|
||||
Bytes cmd;
|
||||
switch (_version)
|
||||
{
|
||||
@@ -220,24 +239,27 @@ public:
|
||||
|
||||
Bytes junk(LEN);
|
||||
uint32_t seed = 0;
|
||||
for (int i=0; i<LEN; i++)
|
||||
for (int i = 0; i < LEN; i++)
|
||||
{
|
||||
junk[i] = seed;
|
||||
seed = ss_rand_next(seed);
|
||||
}
|
||||
double start_time = getCurrentTime();
|
||||
double start_time = getCurrentTime();
|
||||
_serial->write(junk);
|
||||
_serial->readBytes(1);
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
|
||||
std::cout << fmt::format("transferred {} bytes from PC -> device in {} ms ({} kb/s)\n",
|
||||
LEN, int(elapsed_time * 1000.0), int((LEN / 1024.0) / elapsed_time));
|
||||
std::cout << fmt::format(
|
||||
"transferred {} bytes from PC -> device in {} ms ({} kb/s)\n",
|
||||
LEN,
|
||||
int(elapsed_time * 1000.0),
|
||||
int((LEN / 1024.0) / elapsed_time));
|
||||
}
|
||||
|
||||
|
||||
void testBulkRead()
|
||||
{
|
||||
std::cout << "Reading data: " << std::flush;
|
||||
const int LEN = 10*1024*1024;
|
||||
const int LEN = 10 * 1024 * 1024;
|
||||
Bytes cmd;
|
||||
switch (_version)
|
||||
{
|
||||
@@ -265,26 +287,32 @@ public:
|
||||
}
|
||||
do_command(cmd);
|
||||
|
||||
double start_time = getCurrentTime();
|
||||
double start_time = getCurrentTime();
|
||||
_serial->readBytes(LEN);
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
double elapsed_time = getCurrentTime() - start_time;
|
||||
|
||||
std::cout << fmt::format("transferred {} bytes from device -> PC in {} ms ({} kb/s)\n",
|
||||
LEN, int(elapsed_time * 1000.0), int((LEN / 1024.0) / elapsed_time));
|
||||
std::cout << fmt::format(
|
||||
"transferred {} bytes from device -> PC in {} ms ({} kb/s)\n",
|
||||
LEN,
|
||||
int(elapsed_time * 1000.0),
|
||||
int((LEN / 1024.0) / elapsed_time));
|
||||
}
|
||||
|
||||
Bytes read(int side, bool synced, nanoseconds_t readTime, nanoseconds_t hardSectorThreshold)
|
||||
Bytes read(int side,
|
||||
bool synced,
|
||||
nanoseconds_t readTime,
|
||||
nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
if (hardSectorThreshold != 0)
|
||||
Error() << "hard sectors are currently unsupported on the GreaseWeazel";
|
||||
error("hard sectors are currently unsupported on the GreaseWeazel");
|
||||
|
||||
do_command({ CMD_HEAD, 3, (uint8_t)side });
|
||||
do_command({CMD_HEAD, 3, (uint8_t)side});
|
||||
|
||||
switch (_version)
|
||||
{
|
||||
case V22:
|
||||
{
|
||||
int revolutions = (readTime+_revolutions-1) / _revolutions;
|
||||
int revolutions = (readTime + _revolutions - 1) / _revolutions;
|
||||
Bytes cmd(4);
|
||||
cmd.writer()
|
||||
.write_8(CMD_READ_FLUX)
|
||||
@@ -301,13 +329,14 @@ public:
|
||||
cmd.writer()
|
||||
.write_8(CMD_READ_FLUX)
|
||||
.write_8(cmd.size())
|
||||
.write_le32((readTime + (synced ? _revolutions : 0)) / _clock)
|
||||
.write_le32(
|
||||
(readTime + (synced ? _revolutions : 0)) / _clock)
|
||||
.write_le16(0);
|
||||
do_command(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
Bytes buffer;
|
||||
Bytes buffer;
|
||||
ByteWriter bw(buffer);
|
||||
for (;;)
|
||||
{
|
||||
@@ -317,7 +346,7 @@ public:
|
||||
bw.write_8(b);
|
||||
}
|
||||
|
||||
do_command({ CMD_GET_FLUX_STATUS, 2 });
|
||||
do_command({CMD_GET_FLUX_STATUS, 2});
|
||||
|
||||
Bytes fldata = greaseWeazleToFluxEngine(buffer, _clock);
|
||||
if (synced)
|
||||
@@ -328,32 +357,32 @@ public:
|
||||
void write(int side, const Bytes& fldata, nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
if (hardSectorThreshold != 0)
|
||||
Error() << "hard sectors are currently unsupported on the GreaseWeazel";
|
||||
error("hard sectors are currently unsupported on the GreaseWeazel");
|
||||
|
||||
do_command({ CMD_HEAD, 3, (uint8_t)side });
|
||||
do_command({CMD_HEAD, 3, (uint8_t)side});
|
||||
switch (_version)
|
||||
{
|
||||
case V22:
|
||||
do_command({ CMD_WRITE_FLUX, 3, 1 });
|
||||
do_command({CMD_WRITE_FLUX, 3, 1});
|
||||
break;
|
||||
|
||||
case V24:
|
||||
case V29:
|
||||
do_command({ CMD_WRITE_FLUX, 4, 1, 1 });
|
||||
do_command({CMD_WRITE_FLUX, 4, 1, 1});
|
||||
break;
|
||||
}
|
||||
_serial->write(fluxEngineToGreaseWeazle(fldata, _clock));
|
||||
_serial->readByte(); /* synchronise */
|
||||
|
||||
do_command({ CMD_GET_FLUX_STATUS, 2 });
|
||||
do_command({CMD_GET_FLUX_STATUS, 2});
|
||||
}
|
||||
|
||||
void erase(int side, nanoseconds_t hardSectorThreshold)
|
||||
{
|
||||
if (hardSectorThreshold != 0)
|
||||
Error() << "hard sectors are currently unsupported on the GreaseWeazel";
|
||||
error("hard sectors are currently unsupported on the GreaseWeazel");
|
||||
|
||||
do_command({ CMD_HEAD, 3, (uint8_t)side });
|
||||
do_command({CMD_HEAD, 3, (uint8_t)side});
|
||||
|
||||
Bytes cmd(6);
|
||||
ByteWriter bw(cmd);
|
||||
@@ -363,18 +392,20 @@ public:
|
||||
do_command(cmd);
|
||||
_serial->readByte(); /* synchronise */
|
||||
|
||||
do_command({ CMD_GET_FLUX_STATUS, 2 });
|
||||
do_command({CMD_GET_FLUX_STATUS, 2});
|
||||
}
|
||||
|
||||
|
||||
void setDrive(int drive, bool high_density, int index_mode)
|
||||
{
|
||||
do_command({ CMD_SELECT, 3, (uint8_t)drive });
|
||||
do_command({ CMD_MOTOR, 4, (uint8_t)drive, 1 });
|
||||
do_command({ CMD_SET_PIN, 4, 2, (uint8_t)(high_density ? 1 : 0) });
|
||||
do_command({CMD_SELECT, 3, (uint8_t)drive});
|
||||
do_command({CMD_MOTOR, 4, (uint8_t)drive, 1});
|
||||
do_command({CMD_SET_PIN, 4, 2, (uint8_t)(high_density ? 1 : 0)});
|
||||
}
|
||||
|
||||
void measureVoltages(struct voltages_frame* voltages)
|
||||
{ Error() << "unsupported operation on the GreaseWeazle"; }
|
||||
{
|
||||
error("unsupported operation on the GreaseWeazle");
|
||||
}
|
||||
|
||||
private:
|
||||
enum
|
||||
@@ -383,7 +414,7 @@ private:
|
||||
V24,
|
||||
V29
|
||||
};
|
||||
|
||||
|
||||
std::unique_ptr<SerialPort> _serial;
|
||||
const GreaseWeazleProto& _config;
|
||||
int _version;
|
||||
@@ -391,7 +422,8 @@ private:
|
||||
nanoseconds_t _revolutions;
|
||||
};
|
||||
|
||||
USB* createGreaseWeazleUsb(const std::string& port, const GreaseWeazleProto& config)
|
||||
USB* createGreaseWeazleUsb(
|
||||
const std::string& port, const GreaseWeazleProto& config)
|
||||
{
|
||||
return new GreaseWeazleUsb(port, config);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "protocol.h"
|
||||
#include "fluxmap.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include "serial.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
@@ -11,230 +10,226 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
#if defined __WIN32__
|
||||
#include <windows.h>
|
||||
#include <windows.h>
|
||||
|
||||
class SerialPortImpl : public SerialPort
|
||||
{
|
||||
public:
|
||||
SerialPortImpl(const std::string& name)
|
||||
{
|
||||
std::string dos_name = "\\\\.\\" + name;
|
||||
_handle = CreateFileA(
|
||||
dos_name.c_str(),
|
||||
/* dwDesiredAccess= */ GENERIC_READ|GENERIC_WRITE,
|
||||
/* dwShareMode= */ 0,
|
||||
/* lpSecurityAttribues= */ nullptr,
|
||||
/* dwCreationDisposition= */ OPEN_EXISTING,
|
||||
/* dwFlagsAndAttributes= */ FILE_ATTRIBUTE_NORMAL,
|
||||
/* hTemplateFile= */ nullptr);
|
||||
if (_handle == INVALID_HANDLE_VALUE)
|
||||
Error() << fmt::format("cannot open serial port '{}': {}",
|
||||
name, get_last_error_string());
|
||||
|
||||
DCB dcb =
|
||||
{
|
||||
.DCBlength = sizeof(DCB),
|
||||
.BaudRate = CBR_9600,
|
||||
.fBinary = true,
|
||||
.ByteSize = 8,
|
||||
.Parity = NOPARITY,
|
||||
.StopBits = ONESTOPBIT
|
||||
};
|
||||
SetCommState(_handle, &dcb);
|
||||
class SerialPortImpl : public SerialPort
|
||||
{
|
||||
public:
|
||||
SerialPortImpl(const std::string& name)
|
||||
{
|
||||
std::string dos_name = "\\\\.\\" + name;
|
||||
_handle = CreateFileA(dos_name.c_str(),
|
||||
/* dwDesiredAccess= */ GENERIC_READ | GENERIC_WRITE,
|
||||
/* dwShareMode= */ 0,
|
||||
/* lpSecurityAttribues= */ nullptr,
|
||||
/* dwCreationDisposition= */ OPEN_EXISTING,
|
||||
/* dwFlagsAndAttributes= */ FILE_ATTRIBUTE_NORMAL,
|
||||
/* hTemplateFile= */ nullptr);
|
||||
if (_handle == INVALID_HANDLE_VALUE)
|
||||
error("cannot open serial port '{}': {}",
|
||||
name,
|
||||
get_last_error_string());
|
||||
|
||||
COMMTIMEOUTS commtimeouts = {0};
|
||||
commtimeouts.ReadIntervalTimeout = 100;
|
||||
SetCommTimeouts(_handle, &commtimeouts);
|
||||
|
||||
if (!EscapeCommFunction(_handle, CLRDTR))
|
||||
Error() << fmt::format("Couldn't clear DTR: {}",
|
||||
get_last_error_string());
|
||||
Sleep(200);
|
||||
if (!EscapeCommFunction(_handle, SETDTR))
|
||||
Error() << fmt::format("Couldn't set DTR: {}",
|
||||
get_last_error_string());
|
||||
DCB dcb = {.DCBlength = sizeof(DCB),
|
||||
.BaudRate = CBR_9600,
|
||||
.fBinary = true,
|
||||
.ByteSize = 8,
|
||||
.Parity = NOPARITY,
|
||||
.StopBits = ONESTOPBIT};
|
||||
SetCommState(_handle, &dcb);
|
||||
|
||||
PurgeComm(_handle, PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_TXCLEAR);
|
||||
}
|
||||
COMMTIMEOUTS commtimeouts = {0};
|
||||
commtimeouts.ReadIntervalTimeout = 100;
|
||||
SetCommTimeouts(_handle, &commtimeouts);
|
||||
|
||||
~SerialPortImpl() override
|
||||
{
|
||||
CloseHandle(_handle);
|
||||
}
|
||||
if (!EscapeCommFunction(_handle, CLRDTR))
|
||||
error("Couldn't clear DTR: {}", get_last_error_string());
|
||||
Sleep(200);
|
||||
if (!EscapeCommFunction(_handle, SETDTR))
|
||||
error("Couldn't set DTR: {}", get_last_error_string());
|
||||
|
||||
public:
|
||||
ssize_t readImpl(uint8_t* buffer, size_t len) override
|
||||
{
|
||||
DWORD rlen;
|
||||
bool r = ReadFile(
|
||||
/* hFile= */ _handle,
|
||||
/* lpBuffer= */ buffer,
|
||||
/* nNumberOfBytesToRead= */ len,
|
||||
/* lpNumberOfBytesRead= */ &rlen,
|
||||
/* lpOverlapped= */ nullptr);
|
||||
if (!r)
|
||||
Error() << fmt::format("serial read I/O error: {}", get_last_error_string());
|
||||
return rlen;
|
||||
}
|
||||
PurgeComm(_handle,
|
||||
PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR);
|
||||
}
|
||||
|
||||
ssize_t write(const uint8_t* buffer, size_t len) override
|
||||
{
|
||||
DWORD wlen;
|
||||
/* Windows gets unhappy if we try to transfer too much... */
|
||||
len = std::min(4096U, len);
|
||||
bool r = WriteFile(
|
||||
/* hFile= */ _handle,
|
||||
/* lpBuffer= */ buffer,
|
||||
/* nNumberOfBytesToWrite= */ len,
|
||||
/* lpNumberOfBytesWritten= */ &wlen,
|
||||
/* lpOverlapped= */ nullptr);
|
||||
if (!r)
|
||||
Error() << fmt::format("serial write I/O error: {}", get_last_error_string());
|
||||
return wlen;
|
||||
}
|
||||
~SerialPortImpl() override
|
||||
{
|
||||
CloseHandle(_handle);
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string get_last_error_string()
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (error == 0)
|
||||
return "OK";
|
||||
public:
|
||||
ssize_t readImpl(uint8_t* buffer, size_t len) override
|
||||
{
|
||||
DWORD rlen;
|
||||
bool r = ReadFile(
|
||||
/* hFile= */ _handle,
|
||||
/* lpBuffer= */ buffer,
|
||||
/* nNumberOfBytesToRead= */ len,
|
||||
/* lpNumberOfBytesRead= */ &rlen,
|
||||
/* lpOverlapped= */ nullptr);
|
||||
if (!r)
|
||||
error("serial read I/O error: {}", get_last_error_string());
|
||||
return rlen;
|
||||
}
|
||||
|
||||
LPSTR buffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
/* dwFlags= */ FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
/* lpSource= */ nullptr,
|
||||
/* dwMessageId= */ error,
|
||||
/* dwLanguageId= */ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
/* lpBuffer= */ (LPSTR) &buffer,
|
||||
/* nSize= */ 0,
|
||||
/* Arguments= */ nullptr);
|
||||
|
||||
std::string message(buffer, size);
|
||||
LocalFree(buffer);
|
||||
return message;
|
||||
}
|
||||
ssize_t write(const uint8_t* buffer, size_t len) override
|
||||
{
|
||||
DWORD wlen;
|
||||
/* Windows gets unhappy if we try to transfer too much... */
|
||||
len = std::min(4096U, len);
|
||||
bool r = WriteFile(
|
||||
/* hFile= */ _handle,
|
||||
/* lpBuffer= */ buffer,
|
||||
/* nNumberOfBytesToWrite= */ len,
|
||||
/* lpNumberOfBytesWritten= */ &wlen,
|
||||
/* lpOverlapped= */ nullptr);
|
||||
if (!r)
|
||||
error("serial write I/O error: {}", get_last_error_string());
|
||||
return wlen;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE _handle;
|
||||
};
|
||||
private:
|
||||
static std::string get_last_error_string()
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (error == 0)
|
||||
return "OK";
|
||||
|
||||
LPSTR buffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
/* dwFlags= */ FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
/* lpSource= */ nullptr,
|
||||
/* dwMessageId= */ error,
|
||||
/* dwLanguageId= */ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
/* lpBuffer= */ (LPSTR)&buffer,
|
||||
/* nSize= */ 0,
|
||||
/* Arguments= */ nullptr);
|
||||
|
||||
std::string message(buffer, size);
|
||||
LocalFree(buffer);
|
||||
return message;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE _handle;
|
||||
};
|
||||
|
||||
#else
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
class SerialPortImpl : public SerialPort
|
||||
{
|
||||
public:
|
||||
SerialPortImpl(const std::string& path)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
if (path.find("/dev/tty.") != std::string::npos)
|
||||
std::cerr << "Warning: you probably want to be using a /dev/cu.* device\n";
|
||||
#endif
|
||||
|
||||
_fd = open(path.c_str(), O_RDWR);
|
||||
if (_fd == -1)
|
||||
Error() << fmt::format("cannot open serial port '{}': {}",
|
||||
path, strerror(errno));
|
||||
|
||||
struct termios t;
|
||||
tcgetattr(_fd, &t);
|
||||
t.c_iflag = 0;
|
||||
t.c_oflag = 0;
|
||||
t.c_cflag = CREAD;
|
||||
t.c_lflag = 0;
|
||||
t.c_cc[VMIN] = 1;
|
||||
cfsetspeed(&t, 9600);
|
||||
tcsetattr(_fd, TCSANOW, &t);
|
||||
|
||||
/* Toggle DTR to reset the device. */
|
||||
|
||||
int flag = TIOCM_DTR;
|
||||
if (ioctl(_fd, TIOCMBIC, &flag) == -1)
|
||||
Error() << fmt::format("cannot clear DTR on serial port: {}", strerror(errno));
|
||||
usleep(200000);
|
||||
if (ioctl(_fd, TIOCMBIS, &flag) == -1)
|
||||
Error() << fmt::format("cannot set DTR on serial port: {}", strerror(errno));
|
||||
|
||||
/* Flush pending input from a generic greaseweazel device */
|
||||
tcsetattr(_fd, TCSAFLUSH, &t);
|
||||
}
|
||||
|
||||
~SerialPortImpl() override
|
||||
{
|
||||
close(_fd);
|
||||
}
|
||||
|
||||
public:
|
||||
ssize_t readImpl(uint8_t* buffer, size_t len) override
|
||||
{
|
||||
ssize_t rlen = ::read(_fd, buffer, len);
|
||||
if (rlen == 0)
|
||||
Error() << "serial read returned no data (device removed?)";
|
||||
if (rlen == -1)
|
||||
Error() << fmt::format("serial read I/O error: {}", strerror(errno));
|
||||
return rlen;
|
||||
}
|
||||
|
||||
ssize_t write(const uint8_t* buffer, size_t len) override
|
||||
{
|
||||
ssize_t wlen = ::write(_fd, buffer, len);
|
||||
if (wlen == -1)
|
||||
Error() << fmt::format("serial write I/O error: {}", strerror(errno));
|
||||
return wlen;
|
||||
}
|
||||
private:
|
||||
int _fd;
|
||||
};
|
||||
class SerialPortImpl : public SerialPort
|
||||
{
|
||||
public:
|
||||
SerialPortImpl(const std::string& path)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
if (path.find("/dev/tty.") != std::string::npos)
|
||||
std::cerr << "Warning: you probably want to be using a /dev/cu.* "
|
||||
"device\n";
|
||||
#endif
|
||||
|
||||
SerialPort::~SerialPort()
|
||||
{}
|
||||
_fd = open(path.c_str(), O_RDWR);
|
||||
if (_fd == -1)
|
||||
error("cannot open serial port '{}': {}", path, strerror(errno));
|
||||
|
||||
struct termios t;
|
||||
tcgetattr(_fd, &t);
|
||||
t.c_iflag = 0;
|
||||
t.c_oflag = 0;
|
||||
t.c_cflag = CREAD;
|
||||
t.c_lflag = 0;
|
||||
t.c_cc[VMIN] = 1;
|
||||
cfsetspeed(&t, 9600);
|
||||
tcsetattr(_fd, TCSANOW, &t);
|
||||
|
||||
/* Toggle DTR to reset the device. */
|
||||
|
||||
int flag = TIOCM_DTR;
|
||||
if (ioctl(_fd, TIOCMBIC, &flag) == -1)
|
||||
error("cannot clear DTR on serial port: {}", strerror(errno));
|
||||
usleep(200000);
|
||||
if (ioctl(_fd, TIOCMBIS, &flag) == -1)
|
||||
error("cannot set DTR on serial port: {}", strerror(errno));
|
||||
|
||||
/* Flush pending input from a generic greaseweazel device */
|
||||
tcsetattr(_fd, TCSAFLUSH, &t);
|
||||
}
|
||||
|
||||
~SerialPortImpl() override
|
||||
{
|
||||
close(_fd);
|
||||
}
|
||||
|
||||
public:
|
||||
ssize_t readImpl(uint8_t* buffer, size_t len) override
|
||||
{
|
||||
ssize_t rlen = ::read(_fd, buffer, len);
|
||||
if (rlen == 0)
|
||||
error("serial read returned no data (device removed?)");
|
||||
if (rlen == -1)
|
||||
error("serial read I/O error: {}", strerror(errno));
|
||||
return rlen;
|
||||
}
|
||||
|
||||
ssize_t write(const uint8_t* buffer, size_t len) override
|
||||
{
|
||||
ssize_t wlen = ::write(_fd, buffer, len);
|
||||
if (wlen == -1)
|
||||
error("serial write I/O error: {}", strerror(errno));
|
||||
return wlen;
|
||||
}
|
||||
|
||||
private:
|
||||
int _fd;
|
||||
};
|
||||
#endif
|
||||
|
||||
SerialPort::~SerialPort() {}
|
||||
|
||||
void SerialPort::read(uint8_t* buffer, size_t len)
|
||||
{
|
||||
while (len != 0)
|
||||
{
|
||||
//std::cout << "want " << len << " " << std::flush;
|
||||
size_t rlen = this->readImpl(buffer, len);
|
||||
//std::cout << "got " << rlen << "\n" << std::flush;
|
||||
buffer += rlen;
|
||||
len -= rlen;
|
||||
}
|
||||
while (len != 0)
|
||||
{
|
||||
// std::cout << "want " << len << " " << std::flush;
|
||||
size_t rlen = this->readImpl(buffer, len);
|
||||
// std::cout << "got " << rlen << "\n" << std::flush;
|
||||
buffer += rlen;
|
||||
len -= rlen;
|
||||
}
|
||||
}
|
||||
|
||||
void SerialPort::read(Bytes& bytes)
|
||||
{
|
||||
this->read(bytes.begin(), bytes.size());
|
||||
this->read(bytes.begin(), bytes.size());
|
||||
}
|
||||
|
||||
Bytes SerialPort::readBytes(size_t len)
|
||||
{
|
||||
Bytes b(len);
|
||||
this->read(b);
|
||||
return b;
|
||||
Bytes b(len);
|
||||
this->read(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
uint8_t SerialPort::readByte()
|
||||
{
|
||||
uint8_t b;
|
||||
this->read(&b, 1);
|
||||
return b;
|
||||
uint8_t b;
|
||||
this->read(&b, 1);
|
||||
return b;
|
||||
}
|
||||
|
||||
void SerialPort::write(const Bytes& bytes)
|
||||
{
|
||||
int ptr = 0;
|
||||
while (ptr < bytes.size())
|
||||
{
|
||||
ssize_t wlen = this->write(bytes.cbegin() + ptr, bytes.size() - ptr);
|
||||
ptr += wlen;
|
||||
}
|
||||
int ptr = 0;
|
||||
while (ptr < bytes.size())
|
||||
{
|
||||
ssize_t wlen = this->write(bytes.cbegin() + ptr, bytes.size() - ptr);
|
||||
ptr += wlen;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<SerialPort> SerialPort::openSerialPort(const std::string& path)
|
||||
{
|
||||
return std::make_unique<SerialPortImpl>(path);
|
||||
return std::make_unique<SerialPortImpl>(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "usbfinder.h"
|
||||
#include "logger.h"
|
||||
#include "greaseweazle.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
static USB* usb = NULL;
|
||||
|
||||
@@ -20,8 +19,9 @@ static std::shared_ptr<CandidateDevice> selectDevice()
|
||||
{
|
||||
auto candidates = findUsbDevices();
|
||||
if (candidates.size() == 0)
|
||||
Error() << "no devices found (is one plugged in? Do you have the "
|
||||
"appropriate permissions?";
|
||||
error(
|
||||
"no devices found (is one plugged in? Do you have the "
|
||||
"appropriate permissions?");
|
||||
|
||||
if (config.usb().has_serial())
|
||||
{
|
||||
@@ -31,8 +31,9 @@ static std::shared_ptr<CandidateDevice> selectDevice()
|
||||
if (c->serial == wantedSerial)
|
||||
return c;
|
||||
}
|
||||
Error() << "serial number not found (try without one to list or "
|
||||
"autodetect devices)";
|
||||
error(
|
||||
"serial number not found (try without one to list or "
|
||||
"autodetect devices)");
|
||||
}
|
||||
|
||||
if (candidates.size() == 1)
|
||||
@@ -66,8 +67,7 @@ USB* get_usb_impl()
|
||||
config.usb().greaseweazle().has_port())
|
||||
{
|
||||
const auto& conf = config.usb().greaseweazle();
|
||||
Logger() << fmt::format(
|
||||
"Using GreaseWeazle on serial port {}", conf.port());
|
||||
log("Using GreaseWeazle on serial port {}", conf.port());
|
||||
return createGreaseWeazleUsb(conf.port(), conf);
|
||||
}
|
||||
|
||||
@@ -77,18 +77,18 @@ USB* get_usb_impl()
|
||||
switch (candidate->id)
|
||||
{
|
||||
case FLUXENGINE_ID:
|
||||
Logger() << fmt::format(
|
||||
"Using FluxEngine {}", candidate->serial);
|
||||
log("Using FluxEngine {}", candidate->serial);
|
||||
return createFluxengineUsb(candidate->device);
|
||||
|
||||
case GREASEWEAZLE_ID:
|
||||
Logger() << fmt::format("Using GreaseWeazle {} on {}",
|
||||
log("Using GreaseWeazle {} on {}",
|
||||
candidate->serial,
|
||||
candidate->serialPort);
|
||||
return createGreaseWeazleUsb(
|
||||
candidate->serialPort, config.usb().greaseweazle());
|
||||
|
||||
default: Error() << "internal";
|
||||
default:
|
||||
error("internal");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
#include "flags.h"
|
||||
#include "usb.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include "usbfinder.h"
|
||||
#include "greaseweazle.h"
|
||||
#include "protocol.h"
|
||||
#include "libusbp.hpp"
|
||||
|
||||
static const std::set<uint32_t> VALID_DEVICES = { GREASEWEAZLE_ID, FLUXENGINE_ID };
|
||||
static const std::set<uint32_t> VALID_DEVICES = {
|
||||
GREASEWEAZLE_ID, FLUXENGINE_ID};
|
||||
|
||||
static const std::string get_serial_number(const libusbp::device& device)
|
||||
{
|
||||
@@ -42,10 +42,10 @@ std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
|
||||
{
|
||||
libusbp::serial_port port(candidate->device);
|
||||
candidate->serialPort = port.get_name();
|
||||
candidate->type = DEVICE_GREASEWEAZLE;
|
||||
candidate->type = DEVICE_GREASEWEAZLE;
|
||||
}
|
||||
else if (id == FLUXENGINE_ID)
|
||||
candidate->type = DEVICE_FLUXENGINE;
|
||||
else if (id == FLUXENGINE_ID)
|
||||
candidate->type = DEVICE_FLUXENGINE;
|
||||
|
||||
candidates.push_back(std::move(candidate));
|
||||
}
|
||||
@@ -56,16 +56,15 @@ std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
|
||||
|
||||
std::string getDeviceName(DeviceType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DEVICE_GREASEWEAZLE:
|
||||
return "Greaseweazle";
|
||||
switch (type)
|
||||
{
|
||||
case DEVICE_GREASEWEAZLE:
|
||||
return "Greaseweazle";
|
||||
|
||||
case DEVICE_FLUXENGINE:
|
||||
return "FluxEngine";
|
||||
case DEVICE_FLUXENGINE:
|
||||
return "FluxEngine";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "globals.h"
|
||||
#include "utils.h"
|
||||
#include "lib/bytes.h"
|
||||
#include <fmt/format.h>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/utils.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
class AcornDfsFilesystem;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "lib/proto.h"
|
||||
#include "lib/layout.h"
|
||||
#include "lib/logger.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "adflib.h"
|
||||
#include "adf_blk.h"
|
||||
@@ -420,7 +419,7 @@ private:
|
||||
|
||||
static void onAdfWarning(char* message)
|
||||
{
|
||||
Logger() << message;
|
||||
log(message);
|
||||
}
|
||||
|
||||
static void onAdfError(char* message)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user