mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge from master.
This commit is contained in:
@@ -5,18 +5,6 @@
|
||||
#define AESLANIER_SECTOR_LENGTH 256
|
||||
#define AESLANIER_RECORD_SIZE (AESLANIER_SECTOR_LENGTH + 5)
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class AesLanierDecoderProto;
|
||||
|
||||
class AesLanierDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
AesLanierDecoder(const AesLanierDecoderProto&) {}
|
||||
virtual ~AesLanierDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createAesLanierDecoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -24,42 +24,57 @@ static Bytes reverse_bits(const Bytes& input)
|
||||
return output;
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType AesLanierDecoder::advanceToNextRecord()
|
||||
class AesLanierDecoder : public AbstractDecoder
|
||||
{
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
|
||||
if (_fmr->eof() || !_sector->clock)
|
||||
return UNKNOWN_RECORD;
|
||||
return SECTOR_RECORD;
|
||||
public:
|
||||
AesLanierDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
|
||||
if (_fmr->eof() || !_sector->clock)
|
||||
return UNKNOWN_RECORD;
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
/* Skip ID mark. */
|
||||
|
||||
readRawBits(16);
|
||||
|
||||
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
|
||||
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
|
||||
const auto& reversed = reverse_bits(bytes);
|
||||
|
||||
_sector->logicalTrack = reversed[1];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalSector = reversed[2];
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createAesLanierDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new AesLanierDecoder(config));
|
||||
}
|
||||
|
||||
void AesLanierDecoder::decodeSectorRecord()
|
||||
{
|
||||
/* Skip ID mark. */
|
||||
|
||||
readRawBits(16);
|
||||
|
||||
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
|
||||
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
|
||||
const auto& reversed = reverse_bits(bytes);
|
||||
|
||||
_sector->logicalTrack = reversed[1];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalSector = reversed[2];
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -9,37 +9,8 @@
|
||||
#define AMIGA_SECTORS_PER_TRACK 11
|
||||
#define AMIGA_RECORD_SIZE 0x21f
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class SectorSet;
|
||||
class AmigaDecoderProto;
|
||||
class AmigaEncoderProto;
|
||||
|
||||
class AmigaDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
AmigaDecoder(const AmigaDecoderProto&) {}
|
||||
virtual ~AmigaDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
|
||||
std::set<unsigned> requiredSectors(FluxTrackProto& track) const;
|
||||
};
|
||||
|
||||
class AmigaEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
AmigaEncoder(const AmigaEncoderProto& config):
|
||||
_config(config) {}
|
||||
virtual ~AmigaEncoder() {}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
|
||||
private:
|
||||
const AmigaEncoderProto& _config;
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createAmigaDecoder(const DecoderProto& config);
|
||||
extern std::unique_ptr<AbstractEncoder> createAmigaEncoder(const EncoderProto& config);
|
||||
|
||||
extern uint32_t amigaChecksum(const Bytes& bytes);
|
||||
extern Bytes amigaInterleave(const Bytes& input);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "amiga.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -21,47 +22,64 @@
|
||||
|
||||
static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD);
|
||||
|
||||
AbstractDecoder::RecordType AmigaDecoder::advanceToNextRecord()
|
||||
class AmigaDecoder : public AbstractDecoder
|
||||
{
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
|
||||
if (_fmr->eof() || !_sector->clock)
|
||||
return UNKNOWN_RECORD;
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
void AmigaDecoder::decodeSectorRecord()
|
||||
{
|
||||
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);
|
||||
|
||||
const uint8_t* ptr = bytes.begin() + 3;
|
||||
|
||||
Bytes header = amigaDeinterleave(ptr, 4);
|
||||
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);
|
||||
|
||||
_sector->logicalTrack = header[1] >> 1;
|
||||
_sector->logicalSide = header[1] & 1;
|
||||
_sector->logicalSector = header[2];
|
||||
|
||||
uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(6, 40));
|
||||
if (gotheaderchecksum != wantedheaderchecksum)
|
||||
return;
|
||||
|
||||
uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(62, 1024));
|
||||
|
||||
_sector->data.clear();
|
||||
_sector->data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
|
||||
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> AmigaDecoder::requiredSectors(Track& track) const
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||
return sectors;
|
||||
public:
|
||||
AmigaDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config),
|
||||
_config(config.amiga())
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
|
||||
if (_fmr->eof() || !_sector->clock)
|
||||
return UNKNOWN_RECORD;
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
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);
|
||||
|
||||
const uint8_t* ptr = bytes.begin() + 3;
|
||||
|
||||
Bytes header = amigaDeinterleave(ptr, 4);
|
||||
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);
|
||||
|
||||
_sector->logicalTrack = header[1] >> 1;
|
||||
_sector->logicalSide = header[1] & 1;
|
||||
_sector->logicalSector = header[2];
|
||||
|
||||
uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(6, 40));
|
||||
if (gotheaderchecksum != wantedheaderchecksum)
|
||||
return;
|
||||
|
||||
uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
|
||||
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(62, 1024));
|
||||
|
||||
_sector->data.clear();
|
||||
_sector->data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
|
||||
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(Track& track) const
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||
return sectors;
|
||||
}
|
||||
|
||||
private:
|
||||
const AmigaDecoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createAmigaDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new AmigaDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "sectorset.h"
|
||||
#include "writer.h"
|
||||
#include "arch/amiga/amiga.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
|
||||
static bool lastBit;
|
||||
|
||||
@@ -96,31 +97,48 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector
|
||||
write_interleaved_bytes(data);
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> AmigaEncoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
class AmigaEncoder : public AbstractEncoder
|
||||
{
|
||||
if ((physicalTrack < 0) || (physicalTrack >= AMIGA_TRACKS_PER_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
public:
|
||||
AmigaEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.amiga()) {}
|
||||
|
||||
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
fillBitmapTo(bits, cursor, _config.post_index_gap_ms() * 1000 / _config.clock_rate_us(), { true, false });
|
||||
lastBit = false;
|
||||
|
||||
for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
if ((physicalTrack < 0) || (physicalTrack >= AMIGA_TRACKS_PER_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
fillBitmapTo(bits, cursor, bits.size(), { true, false });
|
||||
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, _config.clock_rate_us()*1e3);
|
||||
return fluxmap;
|
||||
fillBitmapTo(bits, cursor, _config.post_index_gap_ms() * 1000 / _config.clock_rate_us(), { true, false });
|
||||
lastBit = false;
|
||||
|
||||
for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
fillBitmapTo(bits, cursor, bits.size(), { true, false });
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, _config.clock_rate_us()*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const AmigaEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createAmigaEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new AmigaEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,21 +7,7 @@
|
||||
#define APPLE2_SECTOR_LENGTH 256
|
||||
#define APPLE2_ENCODED_SECTOR_LENGTH 342
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class Apple2DecoderProto;
|
||||
|
||||
class Apple2Decoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
Apple2Decoder(const Apple2DecoderProto&) {}
|
||||
virtual ~Apple2Decoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
};
|
||||
|
||||
extern std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -60,53 +60,68 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status)
|
||||
return output;
|
||||
}
|
||||
|
||||
uint8_t combine(uint16_t word)
|
||||
static uint8_t combine(uint16_t word)
|
||||
{
|
||||
return word & (word >> 7);
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType Apple2Decoder::advanceToNextRecord()
|
||||
class Apple2Decoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
public:
|
||||
Apple2Decoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
/* Skip ID (as we know it's a APPLE2_SECTOR_RECORD). */
|
||||
readRawBits(24);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
auto header = toBytes(readRawBits(8*8)).slice(0, 8);
|
||||
ByteReader br(header);
|
||||
|
||||
uint8_t volume = combine(br.read_be16());
|
||||
_sector->logicalTrack = combine(br.read_be16());
|
||||
_sector->logicalSector = combine(br.read_be16());
|
||||
uint8_t checksum = combine(br.read_be16());
|
||||
if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector))
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
{
|
||||
/* Check ID. */
|
||||
|
||||
Bytes bytes = toBytes(readRawBits(3*8)).slice(0, 3);
|
||||
if (bytes.reader().read_be24() != APPLE2_DATA_RECORD)
|
||||
return;
|
||||
|
||||
/* Read and decode data. */
|
||||
|
||||
unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
|
||||
bytes = toBytes(readRawBits(recordLength*8)).slice(0, recordLength);
|
||||
|
||||
_sector->status = Sector::BAD_CHECKSUM;
|
||||
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new Apple2Decoder(config));
|
||||
}
|
||||
|
||||
void Apple2Decoder::decodeSectorRecord()
|
||||
{
|
||||
/* Skip ID (as we know it's a APPLE2_SECTOR_RECORD). */
|
||||
readRawBits(24);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
auto header = toBytes(readRawBits(8*8)).slice(0, 8);
|
||||
ByteReader br(header);
|
||||
|
||||
uint8_t volume = combine(br.read_be16());
|
||||
_sector->logicalTrack = combine(br.read_be16());
|
||||
_sector->logicalSector = combine(br.read_be16());
|
||||
uint8_t checksum = combine(br.read_be16());
|
||||
if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector))
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void Apple2Decoder::decodeDataRecord()
|
||||
{
|
||||
/* Check ID. */
|
||||
|
||||
Bytes bytes = toBytes(readRawBits(3*8)).slice(0, 3);
|
||||
if (bytes.reader().read_be24() != APPLE2_DATA_RECORD)
|
||||
return;
|
||||
|
||||
/* Read and decode data. */
|
||||
|
||||
unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
|
||||
bytes = toBytes(readRawBits(recordLength*8)).slice(0, recordLength);
|
||||
|
||||
_sector->status = Sector::BAD_CHECKSUM;
|
||||
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
|
||||
}
|
||||
|
||||
@@ -13,37 +13,7 @@
|
||||
#define BROTHER_TRACKS_PER_120KB_DISK 39
|
||||
#define BROTHER_SECTORS_PER_TRACK 12
|
||||
|
||||
class Sector;
|
||||
class SectorSet;
|
||||
class Fluxmap;
|
||||
class BrotherDecoderProto;
|
||||
class BrotherEncoderProto;
|
||||
|
||||
class BrotherDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
BrotherDecoder(const BrotherDecoderProto& config) {}
|
||||
virtual ~BrotherDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
};
|
||||
|
||||
class BrotherEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
BrotherEncoder(const BrotherEncoderProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
|
||||
virtual ~BrotherEncoder() {}
|
||||
|
||||
private:
|
||||
const BrotherEncoderProto& _config;
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createBrotherDecoder(const DecoderProto& config);
|
||||
extern std::unique_ptr<AbstractEncoder> createBrotherEncoder(const EncoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -54,56 +54,71 @@ static int decode_header_gcr(uint16_t word)
|
||||
return -1;
|
||||
};
|
||||
|
||||
AbstractDecoder::RecordType BrotherDecoder::advanceToNextRecord()
|
||||
class BrotherDecoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
public:
|
||||
BrotherDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
void BrotherDecoder::decodeSectorRecord()
|
||||
{
|
||||
readRawBits(32);
|
||||
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());
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
void BrotherDecoder::decodeDataRecord()
|
||||
{
|
||||
readRawBits(32);
|
||||
|
||||
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)
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
uint32_t nibble = decode_data_gcr(b);
|
||||
bitw.push(nibble, 5);
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
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;
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
readRawBits(32);
|
||||
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());
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
{
|
||||
readRawBits(32);
|
||||
|
||||
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();
|
||||
|
||||
_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<AbstractDecoder> createBrotherDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new BrotherDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "sectorset.h"
|
||||
#include "writer.h"
|
||||
#include "arch/brother/brother.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
|
||||
FlagGroup brotherEncoderFlags;
|
||||
|
||||
@@ -127,58 +128,78 @@ static int charToInt(char c)
|
||||
return 10 + tolower(c) - 'a';
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> BrotherEncoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
class BrotherEncoder : public AbstractEncoder
|
||||
{
|
||||
int logicalTrack;
|
||||
if (physicalSide != 0)
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
physicalTrack -= _config.bias();
|
||||
switch (_config.format())
|
||||
{
|
||||
case BROTHER120:
|
||||
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|
||||
|| (physicalTrack & 1))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
logicalTrack = physicalTrack/2;
|
||||
break;
|
||||
public:
|
||||
BrotherEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.brother())
|
||||
{}
|
||||
|
||||
case BROTHER240:
|
||||
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
logicalTrack = physicalTrack;
|
||||
break;
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
int logicalTrack;
|
||||
if (physicalSide != 0)
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
physicalTrack -= _config.bias();
|
||||
switch (_config.format())
|
||||
{
|
||||
case BROTHER120:
|
||||
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|
||||
|| (physicalTrack & 1))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
logicalTrack = physicalTrack/2;
|
||||
break;
|
||||
|
||||
case BROTHER240:
|
||||
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
logicalTrack = physicalTrack;
|
||||
break;
|
||||
}
|
||||
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
const std::string& skew = sectorSkew.get();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
for (int sectorCount=0; sectorCount<BROTHER_SECTORS_PER_TRACK; sectorCount++)
|
||||
{
|
||||
int sectorId = charToInt(skew.at(sectorCount));
|
||||
double headerMs = postIndexGapMs + sectorCount*sectorSpacingMs;
|
||||
unsigned headerCursor = headerMs*1e3 / clockRateUs;
|
||||
double dataMs = headerMs + postHeaderSpacingMs;
|
||||
unsigned dataCursor = dataMs*1e3 / clockRateUs;
|
||||
|
||||
const auto& sectorData = allSectors.get(logicalTrack, 0, sectorId);
|
||||
|
||||
fillBitmapTo(bits, cursor, headerCursor, { true, false });
|
||||
write_sector_header(bits, cursor, logicalTrack, sectorId);
|
||||
fillBitmapTo(bits, cursor, dataCursor, { true, false });
|
||||
write_sector_data(bits, cursor, sectorData->data);
|
||||
}
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
fillBitmapTo(bits, cursor, bits.size(), { true, false });
|
||||
|
||||
// The pre-index gap is not normally reported.
|
||||
// std::cerr << "pre-index gap " << 200.0 - (double)cursor*clockRateUs/1e3 << std::endl;
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
const std::string& skew = sectorSkew.get();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
private:
|
||||
const BrotherEncoderProto& _config;
|
||||
|
||||
for (int sectorCount=0; sectorCount<BROTHER_SECTORS_PER_TRACK; sectorCount++)
|
||||
{
|
||||
int sectorId = charToInt(skew.at(sectorCount));
|
||||
double headerMs = postIndexGapMs + sectorCount*sectorSpacingMs;
|
||||
unsigned headerCursor = headerMs*1e3 / clockRateUs;
|
||||
double dataMs = headerMs + postHeaderSpacingMs;
|
||||
unsigned dataCursor = dataMs*1e3 / clockRateUs;
|
||||
};
|
||||
|
||||
const auto& sectorData = allSectors.get(logicalTrack, 0, sectorId);
|
||||
|
||||
fillBitmapTo(bits, cursor, headerCursor, { true, false });
|
||||
write_sector_header(bits, cursor, logicalTrack, sectorId);
|
||||
fillBitmapTo(bits, cursor, dataCursor, { true, false });
|
||||
write_sector_data(bits, cursor, sectorData->data);
|
||||
}
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
fillBitmapTo(bits, cursor, bits.size(), { true, false });
|
||||
|
||||
// The pre-index gap is not normally reported.
|
||||
// std::cerr << "pre-index gap " << 200.0 - (double)cursor*clockRateUs/1e3 << std::endl;
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
std::unique_ptr<AbstractEncoder> createBrotherEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new BrotherEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -27,42 +27,7 @@
|
||||
#define C64_TRACKS_PER_DISK 40
|
||||
#define C64_BAM_TRACK 17
|
||||
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class Commodore64DecoderProto;
|
||||
class Commodore64EncoderProto;
|
||||
|
||||
class Commodore64Decoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
Commodore64Decoder(const Commodore64DecoderProto&) {}
|
||||
virtual ~Commodore64Decoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
};
|
||||
|
||||
class Commodore64Encoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
Commodore64Encoder(const Commodore64EncoderProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
|
||||
virtual ~Commodore64Encoder() {}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
|
||||
private:
|
||||
void writeSector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector) const;
|
||||
|
||||
private:
|
||||
const Commodore64EncoderProto& _config;
|
||||
uint8_t _formatByte1;
|
||||
uint8_t _formatByte2;
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createCommodore64Decoder(const DecoderProto& config);
|
||||
extern std::unique_ptr<AbstractEncoder> createCommodore64Encoder(const EncoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -52,41 +52,56 @@ static Bytes decode(const std::vector<bool>& bits)
|
||||
return output;
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType Commodore64Decoder::advanceToNextRecord()
|
||||
class Commodore64Decoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
public:
|
||||
Commodore64Decoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
readRawBits(20);
|
||||
|
||||
const auto& bits = readRawBits(5*10);
|
||||
const auto& bytes = decode(bits).slice(0, 5);
|
||||
|
||||
uint8_t checksum = bytes[0];
|
||||
_sector->logicalSector = bytes[1];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalTrack = bytes[2] - 1;
|
||||
if (checksum == xorBytes(bytes.slice(1, 4)))
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
{
|
||||
readRawBits(20);
|
||||
|
||||
const auto& bits = readRawBits(259*10);
|
||||
const auto& bytes = decode(bits).slice(0, 259);
|
||||
|
||||
_sector->data = bytes.slice(0, C64_SECTOR_LENGTH);
|
||||
uint8_t gotChecksum = xorBytes(_sector->data);
|
||||
uint8_t wantChecksum = bytes[256];
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createCommodore64Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new Commodore64Decoder(config));
|
||||
}
|
||||
|
||||
void Commodore64Decoder::decodeSectorRecord()
|
||||
{
|
||||
readRawBits(20);
|
||||
|
||||
const auto& bits = readRawBits(5*10);
|
||||
const auto& bytes = decode(bits).slice(0, 5);
|
||||
|
||||
uint8_t checksum = bytes[0];
|
||||
_sector->logicalSector = bytes[1];
|
||||
_sector->logicalSide = 0;
|
||||
_sector->logicalTrack = bytes[2] - 1;
|
||||
if (checksum == xorBytes(bytes.slice(1, 4)))
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void Commodore64Decoder::decodeDataRecord()
|
||||
{
|
||||
readRawBits(20);
|
||||
|
||||
const auto& bits = readRawBits(259*10);
|
||||
const auto& bytes = decode(bits).slice(0, 259);
|
||||
|
||||
_sector->data = bytes.slice(0, C64_SECTOR_LENGTH);
|
||||
uint8_t gotChecksum = xorBytes(_sector->data);
|
||||
uint8_t wantChecksum = bytes[256];
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "writer.h"
|
||||
#include "fmt/format.h"
|
||||
#include "arch/c64/c64.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include <ctype.h>
|
||||
#include "bytes.h"
|
||||
|
||||
@@ -202,147 +203,167 @@ static std::vector<bool> encode_data(uint8_t input)
|
||||
return output;
|
||||
}
|
||||
|
||||
void Commodore64Encoder::writeSector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector) const
|
||||
class Commodore64Encoder : public AbstractEncoder
|
||||
{
|
||||
/* Source: http://www.unusedino.de/ec64/technical/formats/g64.html
|
||||
* 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR)
|
||||
* 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes)
|
||||
* 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read)
|
||||
* 4. Data sync FF FF FF FF FF (40 'on' bits, not GCR)
|
||||
* 5. Data block 55...4A (325 GCR bytes)
|
||||
* 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read)
|
||||
* 1. Header sync (SYNC for the next sector)
|
||||
*/
|
||||
if ((sector->status == Sector::OK) or (sector->status == Sector::BAD_CHECKSUM))
|
||||
public:
|
||||
Commodore64Encoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.c64())
|
||||
{}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
// There is data to encode to disk.
|
||||
if ((sector->data.size() != C64_SECTOR_LENGTH))
|
||||
Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector->data.size());
|
||||
/* The format ID Character # 1 and # 2 are in the .d64 image only present
|
||||
* in track 18 sector zero which contains the BAM info in byte 162 and 163.
|
||||
* it is written in every header of every sector and track. headers are not
|
||||
* stored in a d64 disk image so we have to get it from track 18 which
|
||||
* contains the BAM.
|
||||
*/
|
||||
|
||||
// 1. Write header Sync (not GCR)
|
||||
for (int i=0; i<6; i++)
|
||||
write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
|
||||
|
||||
// 2. Write Header info 10 GCR bytes
|
||||
/*
|
||||
* The 10 byte header info (#2) is GCR encoded and must be decoded to
|
||||
* it's normal 8 bytes to be understood. Once decoded, its breakdown is
|
||||
* as follows:
|
||||
*
|
||||
* Byte $00 - header block ID ($08)
|
||||
* 01 - header block checksum 16 (EOR of $02-$05)
|
||||
* 02 - Sector
|
||||
* 03 - Track
|
||||
* 04 - Format ID byte #2
|
||||
* 05 - Format ID byte #1
|
||||
* 06-07 - $0F ("off" bytes)
|
||||
*/
|
||||
uint8_t encodedTrack = ((sector->logicalTrack) + 1); // C64 track numbering starts with 1. Fluxengine with 0.
|
||||
uint8_t encodedSector = sector->logicalSector;
|
||||
// uint8_t formatByte1 = C64_FORMAT_ID_BYTE1;
|
||||
// uint8_t formatByte2 = C64_FORMAT_ID_BYTE2;
|
||||
uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ _formatByte1 ^ _formatByte2);
|
||||
write_bits(bits, cursor, encode_data(C64_HEADER_BLOCK_ID));
|
||||
write_bits(bits, cursor, encode_data(headerChecksum));
|
||||
write_bits(bits, cursor, encode_data(encodedSector));
|
||||
write_bits(bits, cursor, encode_data(encodedTrack));
|
||||
write_bits(bits, cursor, encode_data(_formatByte2));
|
||||
write_bits(bits, cursor, encode_data(_formatByte1));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
|
||||
// 3. Write header GAP not GCR
|
||||
for (int i=0; i<9; i++)
|
||||
write_bits(bits, cursor, C64_HEADER_GAP, 1*8); /* header gap */
|
||||
|
||||
// 4. Write Data sync not GCR
|
||||
for (int i=0; i<6; i++)
|
||||
write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
|
||||
|
||||
// 5. Write data block 325 GCR bytes
|
||||
/*
|
||||
* The 325 byte data block (#5) is GCR encoded and must be decoded to its
|
||||
* normal 260 bytes to be understood. The data block is made up of the following:
|
||||
*
|
||||
* Byte $00 - data block ID ($07)
|
||||
* 01-100 - 256 bytes data
|
||||
* 101 - data block checksum (EOR of $01-100)
|
||||
* 102-103 - $00 ("off" bytes, to make the sector size a multiple of 5)
|
||||
*/
|
||||
|
||||
write_bits(bits, cursor, encode_data(C64_DATA_BLOCK_ID));
|
||||
uint8_t dataChecksum = xorBytes(sector->data);
|
||||
ByteReader br(sector->data);
|
||||
int i = 0;
|
||||
for (i = 0; i < C64_SECTOR_LENGTH; i++)
|
||||
{
|
||||
uint8_t val = br.read_8();
|
||||
write_bits(bits, cursor, encode_data(val));
|
||||
}
|
||||
write_bits(bits, cursor, encode_data(dataChecksum));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
|
||||
//6. Write inter-sector gap 9 - 12 bytes nor gcr
|
||||
for (int i=0; i<9; i++)
|
||||
write_bits(bits, cursor, C64_INTER_SECTOR_GAP, 1*8); /* sync */
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> Commodore64Encoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
/* The format ID Character # 1 and # 2 are in the .d64 image only present
|
||||
* in track 18 sector zero which contains the BAM info in byte 162 and 163.
|
||||
* it is written in every header of every sector and track. headers are not
|
||||
* stored in a d64 disk image so we have to get it from track 18 which
|
||||
* contains the BAM.
|
||||
*/
|
||||
|
||||
const auto& sectorData = allSectors.get(C64_BAM_TRACK*2, 0, 0); //Read de BAM to get the DISK ID bytes
|
||||
if (sectorData)
|
||||
{
|
||||
ByteReader br(sectorData->data);
|
||||
br.seek(162); //goto position of the first Disk ID Byte
|
||||
_formatByte1 = br.read_8();
|
||||
_formatByte2 = br.read_8();
|
||||
}
|
||||
else
|
||||
_formatByte1 = _formatByte2 = 0;
|
||||
|
||||
int logicalTrack = physicalTrack / 2;
|
||||
double clockRateUs = clockRateUsForTrack(logicalTrack) * _config.clock_compensation_factor();
|
||||
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
fillBitmapTo(bits, cursor, _config.post_index_gap_us() / clockRateUs, { true, false });
|
||||
lastBit = false;
|
||||
|
||||
unsigned numSectors = sectorsForTrack(logicalTrack);
|
||||
unsigned writtenSectors = 0;
|
||||
for (int sectorId=0; sectorId<numSectors; sectorId++)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
const auto& sectorData = allSectors.get(C64_BAM_TRACK*2, 0, 0); //Read de BAM to get the DISK ID bytes
|
||||
if (sectorData)
|
||||
{
|
||||
writeSector(bits, cursor, sectorData);
|
||||
writtenSectors++;
|
||||
ByteReader br(sectorData->data);
|
||||
br.seek(162); //goto position of the first Disk ID Byte
|
||||
_formatByte1 = br.read_8();
|
||||
_formatByte2 = br.read_8();
|
||||
}
|
||||
else
|
||||
_formatByte1 = _formatByte2 = 0;
|
||||
|
||||
int logicalTrack = physicalTrack / 2;
|
||||
double clockRateUs = clockRateUsForTrack(logicalTrack) * _config.clock_compensation_factor();
|
||||
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
fillBitmapTo(bits, cursor, _config.post_index_gap_us() / clockRateUs, { true, false });
|
||||
lastBit = false;
|
||||
|
||||
unsigned numSectors = sectorsForTrack(logicalTrack);
|
||||
unsigned writtenSectors = 0;
|
||||
for (int sectorId=0; sectorId<numSectors; sectorId++)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
if (sectorData)
|
||||
{
|
||||
writeSector(bits, cursor, sectorData);
|
||||
writtenSectors++;
|
||||
}
|
||||
}
|
||||
if (writtenSectors == 0)
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format("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, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
void writeSector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector) const
|
||||
{
|
||||
/* Source: http://www.unusedino.de/ec64/technical/formats/g64.html
|
||||
* 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR)
|
||||
* 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes)
|
||||
* 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read)
|
||||
* 4. Data sync FF FF FF FF FF (40 'on' bits, not GCR)
|
||||
* 5. Data block 55...4A (325 GCR bytes)
|
||||
* 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read)
|
||||
* 1. Header sync (SYNC for the next sector)
|
||||
*/
|
||||
if ((sector->status == Sector::OK) or (sector->status == Sector::BAD_CHECKSUM))
|
||||
{
|
||||
// There is data to encode to disk.
|
||||
if ((sector->data.size() != C64_SECTOR_LENGTH))
|
||||
Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector->data.size());
|
||||
|
||||
// 1. Write header Sync (not GCR)
|
||||
for (int i=0; i<6; i++)
|
||||
write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
|
||||
|
||||
// 2. Write Header info 10 GCR bytes
|
||||
/*
|
||||
* The 10 byte header info (#2) is GCR encoded and must be decoded to
|
||||
* it's normal 8 bytes to be understood. Once decoded, its breakdown is
|
||||
* as follows:
|
||||
*
|
||||
* Byte $00 - header block ID ($08)
|
||||
* 01 - header block checksum 16 (EOR of $02-$05)
|
||||
* 02 - Sector
|
||||
* 03 - Track
|
||||
* 04 - Format ID byte #2
|
||||
* 05 - Format ID byte #1
|
||||
* 06-07 - $0F ("off" bytes)
|
||||
*/
|
||||
uint8_t encodedTrack = ((sector->logicalTrack) + 1); // C64 track numbering starts with 1. Fluxengine with 0.
|
||||
uint8_t encodedSector = sector->logicalSector;
|
||||
// uint8_t formatByte1 = C64_FORMAT_ID_BYTE1;
|
||||
// uint8_t formatByte2 = C64_FORMAT_ID_BYTE2;
|
||||
uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ _formatByte1 ^ _formatByte2);
|
||||
write_bits(bits, cursor, encode_data(C64_HEADER_BLOCK_ID));
|
||||
write_bits(bits, cursor, encode_data(headerChecksum));
|
||||
write_bits(bits, cursor, encode_data(encodedSector));
|
||||
write_bits(bits, cursor, encode_data(encodedTrack));
|
||||
write_bits(bits, cursor, encode_data(_formatByte2));
|
||||
write_bits(bits, cursor, encode_data(_formatByte1));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
|
||||
// 3. Write header GAP not GCR
|
||||
for (int i=0; i<9; i++)
|
||||
write_bits(bits, cursor, C64_HEADER_GAP, 1*8); /* header gap */
|
||||
|
||||
// 4. Write Data sync not GCR
|
||||
for (int i=0; i<6; i++)
|
||||
write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
|
||||
|
||||
// 5. Write data block 325 GCR bytes
|
||||
/*
|
||||
* The 325 byte data block (#5) is GCR encoded and must be decoded to its
|
||||
* normal 260 bytes to be understood. The data block is made up of the following:
|
||||
*
|
||||
* Byte $00 - data block ID ($07)
|
||||
* 01-100 - 256 bytes data
|
||||
* 101 - data block checksum (EOR of $01-100)
|
||||
* 102-103 - $00 ("off" bytes, to make the sector size a multiple of 5)
|
||||
*/
|
||||
|
||||
write_bits(bits, cursor, encode_data(C64_DATA_BLOCK_ID));
|
||||
uint8_t dataChecksum = xorBytes(sector->data);
|
||||
ByteReader br(sector->data);
|
||||
int i = 0;
|
||||
for (i = 0; i < C64_SECTOR_LENGTH; i++)
|
||||
{
|
||||
uint8_t val = br.read_8();
|
||||
write_bits(bits, cursor, encode_data(val));
|
||||
}
|
||||
write_bits(bits, cursor, encode_data(dataChecksum));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
write_bits(bits, cursor, encode_data(C64_PADDING));
|
||||
|
||||
//6. Write inter-sector gap 9 - 12 bytes nor gcr
|
||||
for (int i=0; i<9; i++)
|
||||
write_bits(bits, cursor, C64_INTER_SECTOR_GAP, 1*8); /* sync */
|
||||
|
||||
}
|
||||
}
|
||||
if (writtenSectors == 0)
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
private:
|
||||
const Commodore64EncoderProto& _config;
|
||||
uint8_t _formatByte1;
|
||||
uint8_t _formatByte2;
|
||||
};
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format("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, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
std::unique_ptr<AbstractEncoder> createCommodore64Encoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new Commodore64Encoder(config));
|
||||
}
|
||||
|
||||
// vim: sw=4 ts=4 et
|
||||
|
||||
@@ -52,49 +52,63 @@ static Bytes decode(const std::vector<bool>& bits)
|
||||
return output;
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType DurangoF85Decoder::advanceToNextRecord()
|
||||
class DurangoF85Decoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
public:
|
||||
DurangoF85Decoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
/* Skip sync bits and ID byte. */
|
||||
|
||||
readRawBits(24);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
const auto& bytes = decode(readRawBits(6*10));
|
||||
|
||||
_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 */
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
{
|
||||
/* Skip sync bits ID byte. */
|
||||
|
||||
readRawBits(24);
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createDurangoF85Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new DurangoF85Decoder(config));
|
||||
}
|
||||
|
||||
void DurangoF85Decoder::decodeSectorRecord()
|
||||
{
|
||||
/* Skip sync bits and ID byte. */
|
||||
|
||||
readRawBits(24);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
const auto& bytes = decode(readRawBits(6*10));
|
||||
|
||||
_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 */
|
||||
}
|
||||
|
||||
void DurangoF85Decoder::decodeDataRecord()
|
||||
{
|
||||
/* Skip sync bits ID byte. */
|
||||
|
||||
readRawBits(24);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -5,19 +5,6 @@
|
||||
#define F85_DATA_RECORD 0xffffcb /* 1111 1111 1111 1111 1100 1101 */
|
||||
#define F85_SECTOR_LENGTH 512
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class F85DecoderProto;
|
||||
|
||||
class DurangoF85Decoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
DurangoF85Decoder(const F85DecoderProto&) {}
|
||||
virtual ~DurangoF85Decoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createDurangoF85Decoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -99,37 +99,52 @@ static uint16_t checksum(const Bytes& bytes)
|
||||
return (crchi << 8) | crclo;
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType Fb100Decoder::advanceToNextRecord()
|
||||
class Fb100Decoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_ID_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_ID_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
public:
|
||||
Fb100Decoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_ID_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_ID_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
_sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createFb100Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new Fb100Decoder(config));
|
||||
}
|
||||
|
||||
void Fb100Decoder::decodeSectorRecord()
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
_sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
@@ -5,20 +5,7 @@
|
||||
#define FB100_ID_SIZE 17
|
||||
#define FB100_PAYLOAD_SIZE 0x500
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class Track;
|
||||
class Fb100DecoderProto;
|
||||
|
||||
class Fb100Decoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
Fb100Decoder(const Fb100DecoderProto&) {}
|
||||
virtual ~Fb100Decoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createFb100Decoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -91,76 +91,96 @@ const FluxMatchers ANY_RECORD_PATTERN(
|
||||
}
|
||||
);
|
||||
|
||||
std::set<unsigned> IbmDecoder::requiredSectors(Track& track) const
|
||||
class IbmDecoder : public AbstractDecoder
|
||||
{
|
||||
return iterate(_config.required_sectors());
|
||||
public:
|
||||
IbmDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config),
|
||||
_config(config.ibm())
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
|
||||
/* If this is the MFM prefix byte, the the decoder is going to expect three
|
||||
* extra bytes on the front of the header. */
|
||||
_currentHeaderLength = (matcher == &MFM_PATTERN) ? 3 : 0;
|
||||
|
||||
Fluxmap::Position here = tell();
|
||||
if (_currentHeaderLength > 0)
|
||||
readRawBits(_currentHeaderLength*16);
|
||||
auto idbits = readRawBits(16);
|
||||
const Bytes idbytes = decodeFmMfm(idbits);
|
||||
uint8_t id = idbytes.slice(0, 1)[0];
|
||||
seek(here);
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case IBM_IDAM:
|
||||
return RecordType::SECTOR_RECORD;
|
||||
|
||||
case IBM_DAM1:
|
||||
case IBM_DAM2:
|
||||
case IBM_TRS80DAM1:
|
||||
case IBM_TRS80DAM2:
|
||||
return RecordType::DATA_RECORD;
|
||||
}
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
unsigned recordSize = _currentHeaderLength + IBM_IDAM_LEN;
|
||||
auto bits = readRawBits(recordSize*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, recordSize);
|
||||
|
||||
ByteReader br(bytes);
|
||||
br.seek(_currentHeaderLength);
|
||||
br.read_8(); /* skip ID byte */
|
||||
_sector->logicalTrack = br.read_8();
|
||||
_sector->logicalSide = br.read_8();
|
||||
_sector->logicalSector = br.read_8() - _config.sector_id_base();
|
||||
_currentSectorSize = 1 << (br.read_8() + 7);
|
||||
uint16_t wantCrc = br.read_be16();
|
||||
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, _currentHeaderLength + 5));
|
||||
if (wantCrc == gotCrc)
|
||||
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
|
||||
|
||||
if (_config.ignore_side_byte())
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
{
|
||||
unsigned recordLength = _currentHeaderLength + _currentSectorSize + 3;
|
||||
auto bits = readRawBits(recordLength*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, recordLength);
|
||||
|
||||
ByteReader br(bytes);
|
||||
br.seek(_currentHeaderLength);
|
||||
br.read_8(); /* skip ID byte */
|
||||
|
||||
_sector->data = br.read(_currentSectorSize);
|
||||
uint16_t wantCrc = br.read_be16();
|
||||
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, recordLength-2));
|
||||
_sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(Track& track) const
|
||||
{
|
||||
return iterate(_config.required_sectors());
|
||||
}
|
||||
|
||||
private:
|
||||
const IbmDecoderProto& _config;
|
||||
unsigned _currentSectorSize;
|
||||
unsigned _currentHeaderLength;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createIbmDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new IbmDecoder(config));
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType IbmDecoder::advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
|
||||
/* If this is the MFM prefix byte, the the decoder is going to expect three
|
||||
* extra bytes on the front of the header. */
|
||||
_currentHeaderLength = (matcher == &MFM_PATTERN) ? 3 : 0;
|
||||
|
||||
Fluxmap::Position here = tell();
|
||||
if (_currentHeaderLength > 0)
|
||||
readRawBits(_currentHeaderLength*16);
|
||||
auto idbits = readRawBits(16);
|
||||
const Bytes idbytes = decodeFmMfm(idbits);
|
||||
uint8_t id = idbytes.slice(0, 1)[0];
|
||||
seek(here);
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case IBM_IDAM:
|
||||
return RecordType::SECTOR_RECORD;
|
||||
|
||||
case IBM_DAM1:
|
||||
case IBM_DAM2:
|
||||
case IBM_TRS80DAM1:
|
||||
case IBM_TRS80DAM2:
|
||||
return RecordType::DATA_RECORD;
|
||||
}
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void IbmDecoder::decodeSectorRecord()
|
||||
{
|
||||
unsigned recordSize = _currentHeaderLength + IBM_IDAM_LEN;
|
||||
auto bits = readRawBits(recordSize*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, recordSize);
|
||||
|
||||
ByteReader br(bytes);
|
||||
br.seek(_currentHeaderLength);
|
||||
br.read_8(); /* skip ID byte */
|
||||
_sector->logicalTrack = br.read_8();
|
||||
_sector->logicalSide = br.read_8();
|
||||
_sector->logicalSector = br.read_8() - _config.sector_id_base();
|
||||
_currentSectorSize = 1 << (br.read_8() + 7);
|
||||
uint16_t wantCrc = br.read_be16();
|
||||
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, _currentHeaderLength + 5));
|
||||
if (wantCrc == gotCrc)
|
||||
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
|
||||
|
||||
if (_config.ignore_side_byte())
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
}
|
||||
|
||||
void IbmDecoder::decodeDataRecord()
|
||||
{
|
||||
unsigned recordLength = _currentHeaderLength + _currentSectorSize + 3;
|
||||
auto bits = readRawBits(recordLength*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, recordLength);
|
||||
|
||||
ByteReader br(bytes);
|
||||
br.seek(_currentHeaderLength);
|
||||
br.read_8(); /* skip ID byte */
|
||||
|
||||
_sector->data = br.read(_currentSectorSize);
|
||||
uint16_t wantCrc = br.read_be16();
|
||||
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, recordLength-2));
|
||||
_sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "sectorset.h"
|
||||
#include "writer.h"
|
||||
#include "arch/ibm/ibm.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -64,19 +65,6 @@ static int charToInt(char c)
|
||||
return 10 + tolower(c) - 'a';
|
||||
}
|
||||
|
||||
void IbmEncoder::writeRawBits(uint32_t data, int width)
|
||||
{
|
||||
_cursor += width;
|
||||
_lastBit = data & 1;
|
||||
for (int i=0; i<width; i++)
|
||||
{
|
||||
unsigned pos = _cursor - i - 1;
|
||||
if (pos < _bits.size())
|
||||
_bits[pos] = data & 1;
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t decodeUint16(uint16_t raw)
|
||||
{
|
||||
Bytes b;
|
||||
@@ -85,174 +73,209 @@ static uint8_t decodeUint16(uint16_t raw)
|
||||
return decodeFmMfm(b.toBits())[0];
|
||||
}
|
||||
|
||||
void IbmEncoder::getTrackFormat(IbmEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
|
||||
class IbmEncoder : public AbstractEncoder
|
||||
{
|
||||
trackdata.Clear();
|
||||
for (const auto& f : _config.trackdata())
|
||||
public:
|
||||
IbmEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.ibm())
|
||||
{}
|
||||
|
||||
private:
|
||||
void writeRawBits(uint32_t data, int width)
|
||||
{
|
||||
if (f.has_cylinder() && (f.cylinder() != cylinder))
|
||||
continue;
|
||||
if (f.has_head() && (f.head() != head))
|
||||
continue;
|
||||
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> IbmEncoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
IbmEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
|
||||
auto writeBytes = [&](const Bytes& bytes)
|
||||
{
|
||||
if (trackdata.use_fm())
|
||||
encodeFm(_bits, _cursor, bytes);
|
||||
else
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
};
|
||||
|
||||
auto writeFillerBytes = [&](int count, uint8_t byte)
|
||||
{
|
||||
Bytes bytes = { byte };
|
||||
for (int i=0; i<count; i++)
|
||||
writeBytes(bytes);
|
||||
};
|
||||
|
||||
if (trackdata.swap_sides())
|
||||
physicalSide = 1 - physicalSide;
|
||||
double clockRateUs = 1e3 / trackdata.clock_rate_khz();
|
||||
if (!trackdata.use_fm())
|
||||
clockRateUs /= 2.0;
|
||||
int bitsPerRevolution = (trackdata.track_length_ms() * 1000.0) / clockRateUs;
|
||||
_bits.resize(bitsPerRevolution);
|
||||
_cursor = 0;
|
||||
|
||||
uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte());
|
||||
uint8_t damUnencoded = decodeUint16(trackdata.dam_byte());
|
||||
|
||||
uint8_t sectorSize = 0;
|
||||
{
|
||||
int s = trackdata.sector_size() >> 7;
|
||||
while (s > 1)
|
||||
_cursor += width;
|
||||
_lastBit = data & 1;
|
||||
for (int i=0; i<width; i++)
|
||||
{
|
||||
s >>= 1;
|
||||
sectorSize += 1;
|
||||
unsigned pos = _cursor - i - 1;
|
||||
if (pos < _bits.size())
|
||||
_bits[pos] = data & 1;
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t gapFill = trackdata.use_fm() ? 0x00 : 0x4e;
|
||||
|
||||
writeFillerBytes(trackdata.gap0(), gapFill);
|
||||
if (trackdata.emit_iam())
|
||||
void getTrackFormat(IbmEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
|
||||
{
|
||||
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
|
||||
trackdata.Clear();
|
||||
for (const auto& f : _config.trackdata())
|
||||
{
|
||||
if (f.has_cylinder() && (f.cylinder() != cylinder))
|
||||
continue;
|
||||
if (f.has_head() && (f.head() != head))
|
||||
continue;
|
||||
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
IbmEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
|
||||
auto writeBytes = [&](const Bytes& bytes)
|
||||
{
|
||||
if (trackdata.use_fm())
|
||||
encodeFm(_bits, _cursor, bytes);
|
||||
else
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
};
|
||||
|
||||
auto writeFillerBytes = [&](int count, uint8_t byte)
|
||||
{
|
||||
Bytes bytes = { byte };
|
||||
for (int i=0; i<count; i++)
|
||||
writeBytes(bytes);
|
||||
};
|
||||
|
||||
if (trackdata.swap_sides())
|
||||
physicalSide = 1 - physicalSide;
|
||||
double clockRateUs = 1e3 / trackdata.clock_rate_khz();
|
||||
if (!trackdata.use_fm())
|
||||
clockRateUs /= 2.0;
|
||||
int bitsPerRevolution = (trackdata.track_length_ms() * 1000.0) / clockRateUs;
|
||||
_bits.resize(bitsPerRevolution);
|
||||
_cursor = 0;
|
||||
|
||||
uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte());
|
||||
uint8_t damUnencoded = decodeUint16(trackdata.dam_byte());
|
||||
|
||||
uint8_t sectorSize = 0;
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_IAM_SEPARATOR, 16);
|
||||
int s = trackdata.sector_size() >> 7;
|
||||
while (s > 1)
|
||||
{
|
||||
s >>= 1;
|
||||
sectorSize += 1;
|
||||
}
|
||||
}
|
||||
writeRawBits(trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
|
||||
writeFillerBytes(trackdata.gap1(), gapFill);
|
||||
|
||||
uint8_t gapFill = trackdata.use_fm() ? 0x00 : 0x4e;
|
||||
|
||||
writeFillerBytes(trackdata.gap0(), gapFill);
|
||||
if (trackdata.emit_iam())
|
||||
{
|
||||
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_IAM_SEPARATOR, 16);
|
||||
}
|
||||
writeRawBits(trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
|
||||
writeFillerBytes(trackdata.gap1(), gapFill);
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
for (char sectorChar : trackdata.sector_skew())
|
||||
{
|
||||
int sectorId = charToInt(sectorChar);
|
||||
if (!first)
|
||||
writeFillerBytes(trackdata.gap3(), gapFill);
|
||||
first = false;
|
||||
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
if (!sectorData)
|
||||
{
|
||||
/* If there are any missing sectors, this is an empty track. */
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
}
|
||||
|
||||
/* Writing the sector and data records are fantastically annoying.
|
||||
* The CRC is calculated from the *very start* of the record, and
|
||||
* include the malformed marker bytes. Our encoder doesn't know
|
||||
* about this, of course, with the result that we have to construct
|
||||
* the unencoded header, calculate the checksum, and then use the
|
||||
* same logic to emit the bytes which require special encoding
|
||||
* before encoding the rest of the header normally. */
|
||||
|
||||
{
|
||||
Bytes header;
|
||||
ByteWriter bw(header);
|
||||
|
||||
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
|
||||
}
|
||||
bw.write_8(idamUnencoded);
|
||||
bw.write_8(sectorData->logicalTrack);
|
||||
bw.write_8(sectorData->logicalSide);
|
||||
bw.write_8(sectorData->logicalSector + trackdata.start_sector_id());
|
||||
bw.write_8(sectorSize);
|
||||
uint16_t crc = crc16(CCITT_POLY, header);
|
||||
bw.write_be16(crc);
|
||||
|
||||
int conventionalHeaderStart = 0;
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_RECORD_SEPARATOR, 16);
|
||||
conventionalHeaderStart += 3;
|
||||
|
||||
}
|
||||
writeRawBits(trackdata.idam_byte(), 16);
|
||||
conventionalHeaderStart += 1;
|
||||
|
||||
writeBytes(header.slice(conventionalHeaderStart));
|
||||
}
|
||||
|
||||
writeFillerBytes(trackdata.gap2(), gapFill);
|
||||
|
||||
{
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
|
||||
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
|
||||
}
|
||||
bw.write_8(damUnencoded);
|
||||
|
||||
Bytes truncatedData = sectorData->data.slice(0, trackdata.sector_size());
|
||||
bw += truncatedData;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
|
||||
int conventionalHeaderStart = 0;
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_RECORD_SEPARATOR, 16);
|
||||
conventionalHeaderStart += 3;
|
||||
|
||||
}
|
||||
writeRawBits(trackdata.dam_byte(), 16);
|
||||
conventionalHeaderStart += 1;
|
||||
|
||||
writeBytes(data.slice(conventionalHeaderStart));
|
||||
}
|
||||
}
|
||||
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
while (_cursor < _bits.size())
|
||||
writeFillerBytes(1, gapFill);
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(_bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
for (char sectorChar : trackdata.sector_skew())
|
||||
{
|
||||
int sectorId = charToInt(sectorChar);
|
||||
if (!first)
|
||||
writeFillerBytes(trackdata.gap3(), gapFill);
|
||||
first = false;
|
||||
private:
|
||||
const IbmEncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
};
|
||||
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
if (!sectorData)
|
||||
{
|
||||
/* If there are any missing sectors, this is an empty track. */
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
}
|
||||
|
||||
/* Writing the sector and data records are fantastically annoying.
|
||||
* The CRC is calculated from the *very start* of the record, and
|
||||
* include the malformed marker bytes. Our encoder doesn't know
|
||||
* about this, of course, with the result that we have to construct
|
||||
* the unencoded header, calculate the checksum, and then use the
|
||||
* same logic to emit the bytes which require special encoding
|
||||
* before encoding the rest of the header normally. */
|
||||
|
||||
{
|
||||
Bytes header;
|
||||
ByteWriter bw(header);
|
||||
|
||||
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
|
||||
}
|
||||
bw.write_8(idamUnencoded);
|
||||
bw.write_8(sectorData->logicalTrack);
|
||||
bw.write_8(sectorData->logicalSide);
|
||||
bw.write_8(sectorData->logicalSector + trackdata.start_sector_id());
|
||||
bw.write_8(sectorSize);
|
||||
uint16_t crc = crc16(CCITT_POLY, header);
|
||||
bw.write_be16(crc);
|
||||
|
||||
int conventionalHeaderStart = 0;
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_RECORD_SEPARATOR, 16);
|
||||
conventionalHeaderStart += 3;
|
||||
|
||||
}
|
||||
writeRawBits(trackdata.idam_byte(), 16);
|
||||
conventionalHeaderStart += 1;
|
||||
|
||||
writeBytes(header.slice(conventionalHeaderStart));
|
||||
}
|
||||
|
||||
writeFillerBytes(trackdata.gap2(), gapFill);
|
||||
|
||||
{
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
|
||||
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
|
||||
}
|
||||
bw.write_8(damUnencoded);
|
||||
|
||||
Bytes truncatedData = sectorData->data.slice(0, trackdata.sector_size());
|
||||
bw += truncatedData;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
|
||||
int conventionalHeaderStart = 0;
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_RECORD_SEPARATOR, 16);
|
||||
conventionalHeaderStart += 3;
|
||||
|
||||
}
|
||||
writeRawBits(trackdata.dam_byte(), 16);
|
||||
conventionalHeaderStart += 1;
|
||||
|
||||
writeBytes(data.slice(conventionalHeaderStart));
|
||||
}
|
||||
}
|
||||
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
while (_cursor < _bits.size())
|
||||
writeFillerBytes(1, gapFill);
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(_bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
std::unique_ptr<AbstractEncoder> createIbmEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new IbmEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#ifndef IBM_H
|
||||
#define IBM_H
|
||||
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "arch/ibm/ibm.pb.h"
|
||||
|
||||
/* IBM format (i.e. ordinary PC floppies). */
|
||||
|
||||
#define IBM_MFM_SYNC 0xA1 /* sync byte for MFM */
|
||||
@@ -30,48 +26,12 @@ struct IbmIdam
|
||||
uint8_t crc[2];
|
||||
};
|
||||
|
||||
class IbmDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
IbmDecoder(const IbmDecoderProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
class AbstractEncoder;
|
||||
class AbstractDecoder;
|
||||
class DecoderProto;
|
||||
class EncoderProto;
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
|
||||
std::set<unsigned> requiredSectors(Track& track) const;
|
||||
|
||||
private:
|
||||
const IbmDecoderProto& _config;
|
||||
unsigned _currentSectorSize;
|
||||
unsigned _currentHeaderLength;
|
||||
};
|
||||
|
||||
class IbmEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
IbmEncoder(const IbmEncoderProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
|
||||
virtual ~IbmEncoder() {}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
|
||||
private:
|
||||
void writeRawBits(uint32_t data, int width);
|
||||
void writeSync();
|
||||
|
||||
void getTrackFormat(IbmEncoderProto::TrackdataProto& format, unsigned track, unsigned side);
|
||||
|
||||
private:
|
||||
const IbmEncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createIbmDecoder(const DecoderProto& config);
|
||||
extern std::unique_ptr<AbstractEncoder> createIbmEncoder(const EncoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -124,85 +124,97 @@ uint8_t decode_side(uint8_t side)
|
||||
return !!(side & 0x20);
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord()
|
||||
class MacintoshDecoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return DATA_RECORD;
|
||||
return UNKNOWN_RECORD;
|
||||
public:
|
||||
MacintoshDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return DATA_RECORD;
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
/* Skip ID (as we know it's a MAC_SECTOR_RECORD). */
|
||||
readRawBits(24);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
auto header = toBytes(readRawBits(7*8)).slice(0, 7);
|
||||
|
||||
uint8_t encodedTrack = decode_data_gcr(header[0]);
|
||||
if (encodedTrack != (_track->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]);
|
||||
|
||||
if (encodedSector > 11)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = _track->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()
|
||||
{
|
||||
auto id = toBytes(readRawBits(24)).reader().read_be24();
|
||||
if (id != MAC_DATA_RECORD)
|
||||
return;
|
||||
|
||||
/* Read data. */
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(Track& track) const
|
||||
{
|
||||
int count;
|
||||
if (track.physicalTrack < 16)
|
||||
count = 12;
|
||||
else if (track.physicalTrack < 32)
|
||||
count = 11;
|
||||
else if (track.physicalTrack < 48)
|
||||
count = 10;
|
||||
else if (track.physicalTrack < 64)
|
||||
count = 9;
|
||||
else
|
||||
count = 8;
|
||||
|
||||
std::set<unsigned> sectors;
|
||||
while (count--)
|
||||
sectors.insert(count);
|
||||
return sectors;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createMacintoshDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new MacintoshDecoder(config));
|
||||
}
|
||||
|
||||
void MacintoshDecoder::decodeSectorRecord()
|
||||
{
|
||||
/* Skip ID (as we know it's a MAC_SECTOR_RECORD). */
|
||||
readRawBits(24);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
auto header = toBytes(readRawBits(7*8)).slice(0, 7);
|
||||
|
||||
uint8_t encodedTrack = decode_data_gcr(header[0]);
|
||||
if (encodedTrack != (_track->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]);
|
||||
|
||||
if (encodedSector > 11)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = _track->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 MacintoshDecoder::decodeDataRecord()
|
||||
{
|
||||
auto id = toBytes(readRawBits(24)).reader().read_be24();
|
||||
if (id != MAC_DATA_RECORD)
|
||||
return;
|
||||
|
||||
/* Read data. */
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
std::set<unsigned> MacintoshDecoder::requiredSectors(Track& track) const
|
||||
{
|
||||
int count;
|
||||
if (track.physicalTrack < 16)
|
||||
count = 12;
|
||||
else if (track.physicalTrack < 32)
|
||||
count = 11;
|
||||
else if (track.physicalTrack < 48)
|
||||
count = 10;
|
||||
else if (track.physicalTrack < 64)
|
||||
count = 9;
|
||||
else
|
||||
count = 8;
|
||||
|
||||
std::set<unsigned> sectors;
|
||||
while (count--)
|
||||
sectors.insert(count);
|
||||
return sectors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,20 +7,10 @@
|
||||
#include "sectorset.h"
|
||||
#include "writer.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include "arch/macintosh/macintosh.pb.h"
|
||||
#include <ctype.h>
|
||||
|
||||
FlagGroup macintoshEncoderFlags;
|
||||
|
||||
static DoubleFlag postIndexGapUs(
|
||||
{ "--post-index-gap-us" },
|
||||
"Post-index gap before first sector header (microseconds).",
|
||||
0);
|
||||
|
||||
static DoubleFlag clockCompensation(
|
||||
{ "--clock-compensation-factor" },
|
||||
"Scale the output clock by this much.",
|
||||
1.0);
|
||||
|
||||
static bool lastBit;
|
||||
|
||||
static double clockRateUsForTrack(unsigned track)
|
||||
@@ -210,33 +200,50 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector
|
||||
write_bits(bits, cursor, 0xdeaaff, 3*8);
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> MacintoshEncoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
class MacintoshEncoder : public AbstractEncoder
|
||||
{
|
||||
if ((physicalTrack < 0) || (physicalTrack >= MAC_TRACKS_PER_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
public:
|
||||
MacintoshEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.macintosh())
|
||||
{}
|
||||
|
||||
double clockRateUs = clockRateUsForTrack(physicalTrack) * clockCompensation;
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
fillBitmapTo(bits, cursor, postIndexGapUs / clockRateUs, { true, false });
|
||||
lastBit = false;
|
||||
|
||||
unsigned numSectors = sectorsForTrack(physicalTrack);
|
||||
for (int sectorId=0; sectorId<numSectors; sectorId++)
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
if ((physicalTrack < 0) || (physicalTrack >= MAC_TRACKS_PER_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
|
||||
fillBitmapTo(bits, cursor, bits.size(), { true, false });
|
||||
double clockRateUs = clockRateUsForTrack(physicalTrack) * _config.clock_compensation_factor();
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
fillBitmapTo(bits, cursor, _config.post_index_gap_us() / clockRateUs, { true, false });
|
||||
lastBit = false;
|
||||
|
||||
unsigned numSectors = sectorsForTrack(physicalTrack);
|
||||
for (int sectorId=0; sectorId<numSectors; sectorId++)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format("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, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const MacintoshEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createMacintoshEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new MacintoshEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#ifndef MACINTOSH_H
|
||||
#define MACINTOSH_H
|
||||
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
|
||||
#define MAC_SECTOR_RECORD 0xd5aa96 /* 1101 0101 1010 1010 1001 0110 */
|
||||
#define MAC_DATA_RECORD 0xd5aaad /* 1101 0101 1010 1010 1010 1101 */
|
||||
|
||||
@@ -13,36 +10,13 @@
|
||||
|
||||
#define MAC_TRACKS_PER_DISK 80
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class MacintoshDecoderProto;
|
||||
class MacintoshEncoderProto;
|
||||
|
||||
class MacintoshDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
MacintoshDecoder(const MacintoshDecoderProto&) {}
|
||||
virtual ~MacintoshDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
|
||||
std::set<unsigned> requiredSectors(Track& track) const;
|
||||
};
|
||||
|
||||
class MacintoshEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
MacintoshEncoder(const MacintoshEncoderProto&) {}
|
||||
virtual ~MacintoshEncoder() {}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
};
|
||||
|
||||
extern FlagGroup macintoshEncoderFlags;
|
||||
class AbstractEncoder;
|
||||
class AbstractDecoder;
|
||||
class DecoderProto;
|
||||
class EncoderProto;
|
||||
|
||||
extern std::unique_ptr<AbstractDecoder> createMacintoshDecoder(const DecoderProto& config);
|
||||
extern std::unique_ptr<AbstractEncoder> createMacintoshEncoder(const EncoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message MacintoshDecoderProto {}
|
||||
message MacintoshEncoderProto {}
|
||||
import "lib/common.proto";
|
||||
|
||||
message MacintoshDecoderProto {}
|
||||
|
||||
message MacintoshEncoderProto {
|
||||
optional double post_index_gap_us = 1 [default = 0.0,
|
||||
(help) = "post-index gap before first sector header (microseconds)."];
|
||||
|
||||
optional double clock_compensation_factor = 2 [default = 1.0,
|
||||
(help) = "scale the output clock by this much."];
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,6 @@
|
||||
/* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. */
|
||||
static const FluxPattern SECTOR_SYNC_PATTERN(32, 0xaaaa5555);
|
||||
|
||||
AbstractDecoder::RecordType MicropolisDecoder::advanceToNextRecord()
|
||||
{
|
||||
_fmr->seekToIndexMark();
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_SYNC_PATTERN) {
|
||||
readRawBits(16);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
/* Adds all bytes, with carry. */
|
||||
static uint8_t checksum(const Bytes& bytes) {
|
||||
ByteReader br(bytes);
|
||||
@@ -36,26 +24,52 @@ static uint8_t checksum(const Bytes& bytes) {
|
||||
return sum & 0xFF;
|
||||
}
|
||||
|
||||
void MicropolisDecoder::decodeSectorRecord()
|
||||
class MicropolisDecoder : public AbstractDecoder
|
||||
{
|
||||
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
|
||||
ByteReader br(bytes);
|
||||
public:
|
||||
MicropolisDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
br.read_8(); /* sync */
|
||||
_sector->logicalTrack = br.read_8();
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = br.read_8();
|
||||
if (_sector->logicalSector > 15)
|
||||
return;
|
||||
if (_sector->logicalTrack > 77)
|
||||
return;
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
_fmr->seekToIndexMark();
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_SYNC_PATTERN) {
|
||||
readRawBits(16);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
br.read(10); /* OS data or padding */
|
||||
_sector->data = br.read(256);
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
uint8_t gotChecksum = checksum(bytes.slice(1, 2+266));
|
||||
br.read(5); /* 4 byte ECC and ECC-present flag */
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
|
||||
ByteReader br(bytes);
|
||||
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
br.read_8(); /* sync */
|
||||
_sector->logicalTrack = br.read_8();
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = br.read_8();
|
||||
if (_sector->logicalSector > 15)
|
||||
return;
|
||||
if (_sector->logicalTrack > 77)
|
||||
return;
|
||||
|
||||
br.read(10); /* OS data or padding */
|
||||
_sector->data = br.read(256);
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
uint8_t gotChecksum = checksum(bytes.slice(1, 2+266));
|
||||
br.read(5); /* 4 byte ECC and ECC-present flag */
|
||||
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createMicropolisDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new MicropolisDecoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,6 @@
|
||||
|
||||
#define MICROPOLIS_ENCODED_SECTOR_SIZE (1+2+266+6)
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class MicropolisDecoderProto;
|
||||
|
||||
class MicropolisDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
MicropolisDecoder(const MicropolisDecoderProto&) {}
|
||||
virtual ~MicropolisDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createMicropolisDecoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,53 +23,73 @@ const int SECTOR_SIZE = 256;
|
||||
*/
|
||||
const FluxPattern ID_PATTERN(32, 0xaaaaffaf);
|
||||
|
||||
void MxDecoder::beginTrack()
|
||||
class MxDecoder : public AbstractDecoder
|
||||
{
|
||||
_currentSector = -1;
|
||||
_clock = 0;
|
||||
public:
|
||||
MxDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
void beginTrack()
|
||||
{
|
||||
_currentSector = -1;
|
||||
_clock = 0;
|
||||
}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
if (_currentSector == -1)
|
||||
{
|
||||
/* First sector in the track: look for the sync marker. */
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher);
|
||||
readRawBits(32); /* skip the ID mark */
|
||||
_logicalTrack = decodeFmMfm(readRawBits(32)).slice(0, 32).reader().read_be16();
|
||||
}
|
||||
else if (_currentSector == 10)
|
||||
{
|
||||
/* That was the last sector on the disk. */
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise we assume the clock from the first sector is still valid.
|
||||
* The decoder framwork will automatically stop when we hit the end of
|
||||
* the track. */
|
||||
_sector->clock = _clock;
|
||||
}
|
||||
|
||||
_currentSector++;
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
auto bits = readRawBits((SECTOR_SIZE+2)*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2).swab();
|
||||
|
||||
uint16_t gotChecksum = 0;
|
||||
ByteReader br(bytes);
|
||||
for (int i=0; i<(SECTOR_SIZE/2); i++)
|
||||
gotChecksum += br.read_le16();
|
||||
uint16_t wantChecksum = br.read_le16();
|
||||
|
||||
_sector->logicalTrack = _logicalTrack;
|
||||
_sector->logicalSide = _track->physicalSide;
|
||||
_sector->logicalSector = _currentSector;
|
||||
_sector->data = bytes.slice(0, SECTOR_SIZE);
|
||||
_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
private:
|
||||
nanoseconds_t _clock;
|
||||
int _currentSector;
|
||||
int _logicalTrack;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createMxDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new MxDecoder(config));
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType MxDecoder::advanceToNextRecord()
|
||||
{
|
||||
if (_currentSector == -1)
|
||||
{
|
||||
/* First sector in the track: look for the sync marker. */
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher);
|
||||
readRawBits(32); /* skip the ID mark */
|
||||
_logicalTrack = decodeFmMfm(readRawBits(32)).slice(0, 32).reader().read_be16();
|
||||
}
|
||||
else if (_currentSector == 10)
|
||||
{
|
||||
/* That was the last sector on the disk. */
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise we assume the clock from the first sector is still valid.
|
||||
* The decoder framwork will automatically stop when we hit the end of
|
||||
* the track. */
|
||||
_sector->clock = _clock;
|
||||
}
|
||||
|
||||
_currentSector++;
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
void MxDecoder::decodeSectorRecord()
|
||||
{
|
||||
auto bits = readRawBits((SECTOR_SIZE+2)*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2).swab();
|
||||
|
||||
uint16_t gotChecksum = 0;
|
||||
ByteReader br(bytes);
|
||||
for (int i=0; i<(SECTOR_SIZE/2); i++)
|
||||
gotChecksum += br.read_le16();
|
||||
uint16_t wantChecksum = br.read_le16();
|
||||
|
||||
_sector->logicalTrack = _logicalTrack;
|
||||
_sector->logicalSide = _track->physicalSide;
|
||||
_sector->logicalSector = _currentSector;
|
||||
_sector->data = bytes.slice(0, SECTOR_SIZE);
|
||||
_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
18
arch/mx/mx.h
18
arch/mx/mx.h
@@ -3,22 +3,6 @@
|
||||
|
||||
#include "decoders/decoders.h"
|
||||
|
||||
class MxDecoderProto;
|
||||
|
||||
class MxDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
MxDecoder(const MxDecoderProto&) {}
|
||||
virtual ~MxDecoder() {}
|
||||
|
||||
void beginTrack();
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
|
||||
private:
|
||||
nanoseconds_t _clock;
|
||||
int _currentSector;
|
||||
int _logicalTrack;
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createMxDecoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "sector.h"
|
||||
#include "northstar.h"
|
||||
#include "bytes.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
/*
|
||||
@@ -48,73 +49,6 @@ const FluxMatchers ANY_SECTOR_PATTERN(
|
||||
}
|
||||
);
|
||||
|
||||
/* Search for FM or MFM sector record */
|
||||
AbstractDecoder::RecordType NorthstarDecoder::advanceToNextRecord()
|
||||
{
|
||||
nanoseconds_t now = _fmr->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) {
|
||||
_fmr->seekToIndexMark();
|
||||
now = _fmr->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 > (_fmr->getDuration() - 21e6)) {
|
||||
_fmr->seekToIndexMark();
|
||||
return(UNKNOWN_RECORD);
|
||||
}
|
||||
|
||||
int msSinceIndex = std::round(now / 1e6);
|
||||
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
_sector->clock = _fmr->seekToPattern(ANY_SECTOR_PATTERN, matcher);
|
||||
|
||||
int sectorFoundTimeRaw = std::round((_fmr->tell().ns()) / 1e6);
|
||||
int sectorFoundTime;
|
||||
|
||||
/* 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;
|
||||
|
||||
// std::cout << fmt::format(
|
||||
// "Sector ID {}: hole at {}ms, sector start at {}ms",
|
||||
// _hardSectorId, msSinceIndex, sectorFoundTimeRaw) << std::endl;
|
||||
|
||||
if (matcher == &MFM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_MFM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
if (matcher == &FM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_FM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
/* Checksum is initially 0.
|
||||
* For each data byte, XOR with the current checksum.
|
||||
* Rotate checksum left, carrying bit 7 to bit 0.
|
||||
@@ -131,45 +65,132 @@ uint8_t northstarChecksum(const Bytes& bytes) {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void NorthstarDecoder::decodeSectorRecord()
|
||||
class NorthstarDecoder : public AbstractDecoder
|
||||
{
|
||||
unsigned recordSize, payloadSize, headerSize;
|
||||
public:
|
||||
NorthstarDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config),
|
||||
_config(config.northstar())
|
||||
{}
|
||||
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
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;
|
||||
/* Search for FM or MFM sector record */
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
nanoseconds_t now = _fmr->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) {
|
||||
_fmr->seekToIndexMark();
|
||||
now = _fmr->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 > (_fmr->getDuration() - 21e6)) {
|
||||
_fmr->seekToIndexMark();
|
||||
return(UNKNOWN_RECORD);
|
||||
}
|
||||
|
||||
int msSinceIndex = std::round(now / 1e6);
|
||||
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
_sector->clock = _fmr->seekToPattern(ANY_SECTOR_PATTERN, matcher);
|
||||
|
||||
int sectorFoundTimeRaw = std::round((_fmr->tell().ns()) / 1e6);
|
||||
int sectorFoundTime;
|
||||
|
||||
/* 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;
|
||||
|
||||
// std::cout << fmt::format(
|
||||
// "Sector ID {}: hole at {}ms, sector start at {}ms",
|
||||
// _hardSectorId, msSinceIndex, sectorFoundTimeRaw) << std::endl;
|
||||
|
||||
if (matcher == &MFM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_MFM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
if (matcher == &FM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_FM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
auto rawbits = readRawBits(recordSize * 16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
|
||||
ByteReader br(bytes);
|
||||
uint8_t sync_char;
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
unsigned recordSize, payloadSize, headerSize;
|
||||
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _hardSectorId;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
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;
|
||||
}
|
||||
|
||||
sync_char = br.read_8(); /* Sync char: 0xFB */
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
sync_char = br.read_8();/* MFM second Sync char, usually 0xFB */
|
||||
auto rawbits = readRawBits(recordSize * 16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
|
||||
ByteReader br(bytes);
|
||||
uint8_t sync_char;
|
||||
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _hardSectorId;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
|
||||
sync_char = br.read_8(); /* Sync char: 0xFB */
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
sync_char = 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, payloadSize));
|
||||
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
_sector->data = br.read(payloadSize);
|
||||
std::set<unsigned> requiredSectors(Track& track) const
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
return sectors;
|
||||
}
|
||||
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize, payloadSize));
|
||||
private:
|
||||
const NorthstarDecoderProto& _config;
|
||||
uint8_t _sectorType = SECTOR_TYPE_MFM;
|
||||
uint8_t _hardSectorId;
|
||||
};
|
||||
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
std::unique_ptr<AbstractDecoder> createNorthstarDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new NorthstarDecoder(config));
|
||||
}
|
||||
|
||||
std::set<unsigned> NorthstarDecoder::requiredSectors(Track& track) const
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
return sectors;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include "globals.h"
|
||||
#include "northstar.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "bytes.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
|
||||
#define GAP_FILL_SIZE_SD 30
|
||||
#define PRE_HEADER_GAP_FILL_SIZE_SD 9
|
||||
@@ -95,36 +100,53 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> NorthstarEncoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
class NorthstarEncoder : public AbstractEncoder
|
||||
{
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 4.00;
|
||||
public:
|
||||
NorthstarEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.northstar())
|
||||
{}
|
||||
|
||||
if ((physicalTrack < 0) || (physicalTrack >= 35))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
const auto& sector = allSectors.get(physicalTrack, physicalSide, 0);
|
||||
|
||||
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
|
||||
bitsPerRevolution /= 2; // FM
|
||||
} else {
|
||||
clockRateUs /= 2.00;
|
||||
}
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
for (int sectorId = 0; sectorId < 10; sectorId++)
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 4.00;
|
||||
|
||||
if ((physicalTrack < 0) || (physicalTrack >= 35))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
const auto& sector = allSectors.get(physicalTrack, physicalSide, 0);
|
||||
|
||||
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
|
||||
bitsPerRevolution /= 2; // FM
|
||||
} else {
|
||||
clockRateUs /= 2.00;
|
||||
}
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
for (int sectorId = 0; sectorId < 10; sectorId++)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
|
||||
if (cursor > bits.size())
|
||||
Error() << "track data overrun";
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
if (cursor > bits.size())
|
||||
Error() << "track data overrun";
|
||||
private:
|
||||
const NorthstarEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
std::unique_ptr<AbstractEncoder> createNorthstarEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new NorthstarEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
|
||||
#define NORTHSTAR_PREAMBLE_SIZE_SD (16)
|
||||
#define NORTHSTAR_PREAMBLE_SIZE_DD (32)
|
||||
#define NORTHSTAR_HEADER_SIZE_SD (1)
|
||||
@@ -28,45 +25,14 @@
|
||||
#define SECTOR_TYPE_MFM (0)
|
||||
#define SECTOR_TYPE_FM (1)
|
||||
|
||||
class NorthstarEncoderProto;
|
||||
class NorthstarDecoderProto;
|
||||
class AbstractDecoder;
|
||||
class AbstractEncoder;
|
||||
class EncoderProto;
|
||||
class DecoderProto;
|
||||
|
||||
class NorthstarDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
NorthstarDecoder(const NorthstarDecoderProto& config):
|
||||
_config(config)
|
||||
{
|
||||
_sectorType = SECTOR_TYPE_MFM;
|
||||
}
|
||||
|
||||
virtual ~NorthstarDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
std::set<unsigned> requiredSectors(Track& track) const;
|
||||
|
||||
private:
|
||||
const NorthstarDecoderProto& _config;
|
||||
uint8_t _sectorType;
|
||||
uint8_t _hardSectorId;
|
||||
};
|
||||
|
||||
class NorthstarEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
NorthstarEncoder(const NorthstarEncoderProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
|
||||
virtual ~NorthstarEncoder() {}
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
|
||||
private:
|
||||
const NorthstarEncoderProto& _config;
|
||||
};
|
||||
|
||||
extern FlagGroup northstarEncoderFlags;
|
||||
extern uint8_t northstarChecksum(const Bytes& bytes);
|
||||
|
||||
extern std::unique_ptr<AbstractDecoder> createNorthstarDecoder(const DecoderProto& config);
|
||||
extern std::unique_ptr<AbstractEncoder> createNorthstarEncoder(const EncoderProto& config);
|
||||
|
||||
#endif /* NORTHSTAR */
|
||||
|
||||
@@ -40,48 +40,61 @@ const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245);
|
||||
|
||||
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
|
||||
|
||||
AbstractDecoder::RecordType Tids990Decoder::advanceToNextRecord()
|
||||
class Tids990Decoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void Tids990Decoder::decodeSectorRecord()
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE);
|
||||
|
||||
ByteReader br(bytes);
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3));
|
||||
|
||||
br.seek(2);
|
||||
_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 */
|
||||
}
|
||||
|
||||
void Tids990Decoder::decodeDataRecord()
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
|
||||
|
||||
ByteReader br(bytes);
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3));
|
||||
|
||||
br.seek(2);
|
||||
_sector->data = br.read(TIDS990_PAYLOAD_SIZE);
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
public:
|
||||
Tids990Decoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return RecordType::SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return RecordType::DATA_RECORD;
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE);
|
||||
|
||||
ByteReader br(bytes);
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3));
|
||||
|
||||
br.seek(2);
|
||||
_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 */
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
{
|
||||
auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16);
|
||||
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
|
||||
|
||||
ByteReader br(bytes);
|
||||
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3));
|
||||
|
||||
br.seek(2);
|
||||
_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<AbstractDecoder> createTids990Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new Tids990Decoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "sectorset.h"
|
||||
#include "writer.h"
|
||||
#include "arch/tids990/tids990.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
static int charToInt(char c)
|
||||
@@ -16,31 +17,6 @@ static int charToInt(char c)
|
||||
return 10 + tolower(c) - 'a';
|
||||
}
|
||||
|
||||
void Tids990Encoder::writeRawBits(uint32_t data, int width)
|
||||
{
|
||||
_cursor += width;
|
||||
_lastBit = data & 1;
|
||||
for (int i=0; i<width; i++)
|
||||
{
|
||||
unsigned pos = _cursor - i - 1;
|
||||
if (pos < _bits.size())
|
||||
_bits[pos] = data & 1;
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Tids990Encoder::writeBytes(const Bytes& bytes)
|
||||
{
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
}
|
||||
|
||||
void Tids990Encoder::writeBytes(int count, uint8_t byte)
|
||||
{
|
||||
Bytes bytes = { byte };
|
||||
for (int i=0; i<count; i++)
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
static uint8_t decodeUint16(uint16_t raw)
|
||||
{
|
||||
Bytes b;
|
||||
@@ -49,82 +25,129 @@ static uint8_t decodeUint16(uint16_t raw)
|
||||
return decodeFmMfm(b.toBits())[0];
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> Tids990Encoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
class Tids990Encoder : public AbstractEncoder
|
||||
{
|
||||
double clockRateUs = 1e3 / _config.clock_rate_khz() / 2.0;
|
||||
int bitsPerRevolution = (_config.track_length_ms() * 1000.0) / clockRateUs;
|
||||
_bits.resize(bitsPerRevolution);
|
||||
_cursor = 0;
|
||||
public:
|
||||
Tids990Encoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.tids990())
|
||||
{}
|
||||
|
||||
uint8_t am1Unencoded = decodeUint16(_config.am1_byte());
|
||||
uint8_t am2Unencoded = decodeUint16(_config.am2_byte());
|
||||
|
||||
writeBytes(_config.gap1_bytes(), 0x55);
|
||||
|
||||
bool first = true;
|
||||
for (char sectorChar : _config.sector_skew())
|
||||
private:
|
||||
void writeRawBits(uint32_t data, int width)
|
||||
{
|
||||
int sectorId = charToInt(sectorChar);
|
||||
if (!first)
|
||||
writeBytes(_config.gap3_bytes(), 0x55);
|
||||
first = false;
|
||||
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
if (!sectorData)
|
||||
Error() << fmt::format("format tried to find sector {} which wasn't in the input file", sectorId);
|
||||
|
||||
/* Writing the sector and data records are fantastically annoying.
|
||||
* The CRC is calculated from the *very start* of the record, and
|
||||
* include the malformed marker bytes. Our encoder doesn't know
|
||||
* about this, of course, with the result that we have to construct
|
||||
* the unencoded header, calculate the checksum, and then use the
|
||||
* same logic to emit the bytes which require special encoding
|
||||
* before encoding the rest of the header normally. */
|
||||
|
||||
_cursor += width;
|
||||
_lastBit = data & 1;
|
||||
for (int i=0; i<width; i++)
|
||||
{
|
||||
Bytes header;
|
||||
ByteWriter bw(header);
|
||||
unsigned pos = _cursor - i - 1;
|
||||
if (pos < _bits.size())
|
||||
_bits[pos] = data & 1;
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
writeBytes(12, 0x55);
|
||||
bw.write_8(am1Unencoded);
|
||||
bw.write_8(sectorData->logicalSide << 3);
|
||||
bw.write_8(sectorData->logicalTrack);
|
||||
bw.write_8(_config.sector_count());
|
||||
bw.write_8(sectorData->logicalSector);
|
||||
bw.write_be16(sectorData->data.size());
|
||||
uint16_t crc = crc16(CCITT_POLY, header);
|
||||
bw.write_be16(crc);
|
||||
void writeBytes(const Bytes& bytes)
|
||||
{
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
}
|
||||
|
||||
writeRawBits(_config.am1_byte(), 16);
|
||||
writeBytes(header.slice(1));
|
||||
void writeBytes(int count, uint8_t byte)
|
||||
{
|
||||
Bytes bytes = { byte };
|
||||
for (int i=0; i<count; i++)
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
double clockRateUs = 1e3 / _config.clock_rate_khz() / 2.0;
|
||||
int bitsPerRevolution = (_config.track_length_ms() * 1000.0) / clockRateUs;
|
||||
_bits.resize(bitsPerRevolution);
|
||||
_cursor = 0;
|
||||
|
||||
uint8_t am1Unencoded = decodeUint16(_config.am1_byte());
|
||||
uint8_t am2Unencoded = decodeUint16(_config.am2_byte());
|
||||
|
||||
writeBytes(_config.gap1_bytes(), 0x55);
|
||||
|
||||
bool first = true;
|
||||
for (char sectorChar : _config.sector_skew())
|
||||
{
|
||||
int sectorId = charToInt(sectorChar);
|
||||
if (!first)
|
||||
writeBytes(_config.gap3_bytes(), 0x55);
|
||||
first = false;
|
||||
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
if (!sectorData)
|
||||
Error() << fmt::format("format tried to find sector {} which wasn't in the input file", sectorId);
|
||||
|
||||
/* Writing the sector and data records are fantastically annoying.
|
||||
* The CRC is calculated from the *very start* of the record, and
|
||||
* include the malformed marker bytes. Our encoder doesn't know
|
||||
* about this, of course, with the result that we have to construct
|
||||
* the unencoded header, calculate the checksum, and then use the
|
||||
* same logic to emit the bytes which require special encoding
|
||||
* before encoding the rest of the header normally. */
|
||||
|
||||
{
|
||||
Bytes header;
|
||||
ByteWriter bw(header);
|
||||
|
||||
writeBytes(12, 0x55);
|
||||
bw.write_8(am1Unencoded);
|
||||
bw.write_8(sectorData->logicalSide << 3);
|
||||
bw.write_8(sectorData->logicalTrack);
|
||||
bw.write_8(_config.sector_count());
|
||||
bw.write_8(sectorData->logicalSector);
|
||||
bw.write_be16(sectorData->data.size());
|
||||
uint16_t crc = crc16(CCITT_POLY, header);
|
||||
bw.write_be16(crc);
|
||||
|
||||
writeRawBits(_config.am1_byte(), 16);
|
||||
writeBytes(header.slice(1));
|
||||
}
|
||||
|
||||
writeBytes(_config.gap2_bytes(), 0x55);
|
||||
|
||||
{
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
|
||||
writeBytes(12, 0x55);
|
||||
bw.write_8(am2Unencoded);
|
||||
|
||||
bw += sectorData->data;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
|
||||
writeRawBits(_config.am2_byte(), 16);
|
||||
writeBytes(data.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
writeBytes(_config.gap2_bytes(), 0x55);
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
while (_cursor < _bits.size())
|
||||
writeBytes(1, 0x55);
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(_bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
{
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
private:
|
||||
const Tids990EncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
};
|
||||
|
||||
writeBytes(12, 0x55);
|
||||
bw.write_8(am2Unencoded);
|
||||
|
||||
bw += sectorData->data;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
|
||||
writeRawBits(_config.am2_byte(), 16);
|
||||
writeBytes(data.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
while (_cursor < _bits.size())
|
||||
writeBytes(1, 0x55);
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(_bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
std::unique_ptr<AbstractEncoder> createTids990Encoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new Tids990Encoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,49 +5,12 @@
|
||||
#define TIDS990_SECTOR_RECORD_SIZE 10 /* bytes */
|
||||
#define TIDS990_DATA_RECORD_SIZE (TIDS990_PAYLOAD_SIZE + 4) /* bytes */
|
||||
|
||||
class Sector;
|
||||
class SectorSet;
|
||||
class Fluxmap;
|
||||
class Track;
|
||||
class Tids990DecoderProto;
|
||||
class Tids990EncoderProto;
|
||||
class AbstractEncoder;
|
||||
class AbstractDecoder;
|
||||
class DecoderProto;
|
||||
class EncoderProto;
|
||||
|
||||
class Tids990Decoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
Tids990Decoder(const Tids990DecoderProto&) {}
|
||||
virtual ~Tids990Decoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
};
|
||||
|
||||
class Tids990Encoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
Tids990Encoder(const Tids990EncoderProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
virtual ~Tids990Encoder() {}
|
||||
|
||||
private:
|
||||
void writeRawBits(uint32_t data, int width);
|
||||
void writeBytes(const Bytes& bytes);
|
||||
void writeBytes(int count, uint8_t value);
|
||||
void writeSync();
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
|
||||
private:
|
||||
const Tids990EncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
};
|
||||
|
||||
extern FlagGroup tids990EncoderFlags;
|
||||
extern std::unique_ptr<AbstractDecoder> createTids990Decoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -54,58 +54,73 @@ static Bytes decode(const std::vector<bool>& bits)
|
||||
return output;
|
||||
}
|
||||
|
||||
AbstractDecoder::RecordType Victor9kDecoder::advanceToNextRecord()
|
||||
class Victor9kDecoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return DATA_RECORD;
|
||||
return UNKNOWN_RECORD;
|
||||
public:
|
||||
Victor9kDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_RECORD_PATTERN)
|
||||
return SECTOR_RECORD;
|
||||
if (matcher == &DATA_RECORD_PATTERN)
|
||||
return DATA_RECORD;
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
/* Skip the sync marker bit. */
|
||||
readRawBits(23);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
auto bytes = decode(readRawBits(4*10)).slice(0, 4);
|
||||
|
||||
uint8_t rawTrack = bytes[1];
|
||||
_sector->logicalSector = bytes[2];
|
||||
uint8_t gotChecksum = bytes[3];
|
||||
|
||||
_sector->logicalTrack = rawTrack & 0x7f;
|
||||
_sector->logicalSide = rawTrack >> 7;
|
||||
uint8_t wantChecksum = bytes[1] + bytes[2];
|
||||
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1))
|
||||
return;
|
||||
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
{
|
||||
/* Skip the sync marker bit. */
|
||||
readRawBits(23);
|
||||
|
||||
/* Read data. */
|
||||
|
||||
auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+5)*10))
|
||||
.slice(0, VICTOR9K_SECTOR_LENGTH+5);
|
||||
ByteReader br(bytes);
|
||||
|
||||
/* Check that this is actually a data record. */
|
||||
|
||||
if (br.read_8() != 8)
|
||||
return;
|
||||
|
||||
_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<AbstractDecoder> createVictor9kDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new Victor9kDecoder(config));
|
||||
}
|
||||
|
||||
void Victor9kDecoder::decodeSectorRecord()
|
||||
{
|
||||
/* Skip the sync marker bit. */
|
||||
readRawBits(23);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
auto bytes = decode(readRawBits(4*10)).slice(0, 4);
|
||||
|
||||
uint8_t rawTrack = bytes[1];
|
||||
_sector->logicalSector = bytes[2];
|
||||
uint8_t gotChecksum = bytes[3];
|
||||
|
||||
_sector->logicalTrack = rawTrack & 0x7f;
|
||||
_sector->logicalSide = rawTrack >> 7;
|
||||
uint8_t wantChecksum = bytes[1] + bytes[2];
|
||||
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1))
|
||||
return;
|
||||
|
||||
if (wantChecksum == gotChecksum)
|
||||
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
|
||||
}
|
||||
|
||||
void Victor9kDecoder::decodeDataRecord()
|
||||
{
|
||||
/* Skip the sync marker bit. */
|
||||
readRawBits(23);
|
||||
|
||||
/* Read data. */
|
||||
|
||||
auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+5)*10))
|
||||
.slice(0, VICTOR9K_SECTOR_LENGTH+5);
|
||||
ByteReader br(bytes);
|
||||
|
||||
/* Check that this is actually a data record. */
|
||||
|
||||
if (br.read_8() != 8)
|
||||
return;
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
@@ -6,19 +6,6 @@
|
||||
|
||||
#define VICTOR9K_SECTOR_LENGTH 512
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class Victor9kDecoderProto;
|
||||
|
||||
class Victor9kDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
Victor9kDecoder(const Victor9kDecoderProto&) {}
|
||||
virtual ~Victor9kDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
void decodeDataRecord();
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createVictor9kDecoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,35 +14,49 @@
|
||||
|
||||
static const FluxPattern SECTOR_START_PATTERN(16, 0xaaab);
|
||||
|
||||
AbstractDecoder::RecordType ZilogMczDecoder::advanceToNextRecord()
|
||||
class ZilogMczDecoder : public AbstractDecoder
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_fmr->seekToIndexMark();
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_START_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_START_PATTERN)
|
||||
return SECTOR_RECORD;
|
||||
return UNKNOWN_RECORD;
|
||||
public:
|
||||
ZilogMczDecoder(const DecoderProto& config):
|
||||
AbstractDecoder(config)
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_fmr->seekToIndexMark();
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_START_PATTERN, matcher);
|
||||
if (matcher == &SECTOR_START_PATTERN)
|
||||
return SECTOR_RECORD;
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
readRawBits(14);
|
||||
|
||||
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->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;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractDecoder> createZilogMczDecoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractDecoder>(new ZilogMczDecoder(config));
|
||||
}
|
||||
|
||||
void ZilogMczDecoder::decodeSectorRecord()
|
||||
{
|
||||
readRawBits(14);
|
||||
|
||||
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->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;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
#ifndef ZILOGMCZ_H
|
||||
#define ZILOGMCZ_H
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
class ZilogMczDecoderProto;
|
||||
|
||||
class ZilogMczDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
ZilogMczDecoder(const ZilogMczDecoderProto&) {}
|
||||
virtual ~ZilogMczDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
};
|
||||
extern std::unique_ptr<AbstractDecoder> createZilogMczDecoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
#include "encoders/encoders.h"
|
||||
//#include "arch/aeslanier/aeslanier.h"
|
||||
#include "arch/amiga/amiga.h"
|
||||
//#include "arch/apple2/apple2.h"
|
||||
//#include "arch/brother/brother.h"
|
||||
//#include "arch/c64/c64.h"
|
||||
//#include "arch/f85/f85.h"
|
||||
//#include "arch/ibm/ibm.h"
|
||||
//#include "arch/macintosh/macintosh.h"
|
||||
//#include "arch/micropolis/micropolis.h"
|
||||
//#include "arch/mx/mx.h"
|
||||
//#include "arch/northstar/northstar.h"
|
||||
//#include "arch/tids990/tids990.h"
|
||||
//#include "arch/victor9k/victor9k.h"
|
||||
//#include "arch/zilogmcz/zilogmcz.h"
|
||||
#include "arch/apple2/apple2.h"
|
||||
#include "arch/brother/brother.h"
|
||||
#include "arch/c64/c64.h"
|
||||
#include "arch/f85/f85.h"
|
||||
#include "arch/fb100/fb100.h"
|
||||
#include "arch/ibm/ibm.h"
|
||||
#include "arch/macintosh/macintosh.h"
|
||||
#include "arch/micropolis/micropolis.h"
|
||||
#include "arch/mx/mx.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include "arch/tids990/tids990.h"
|
||||
#include "arch/victor9k/victor9k.h"
|
||||
#include "arch/zilogmcz/zilogmcz.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "record.h"
|
||||
#include "protocol.h"
|
||||
@@ -33,7 +34,21 @@ std::unique_ptr<AbstractDecoder> AbstractDecoder::create(const DecoderProto& con
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<AbstractDecoder>(const DecoderProto&)>> decoders =
|
||||
{
|
||||
{ DecoderProto::kAmiga, constructor },
|
||||
{ DecoderProto::kAeslanier, createAesLanierDecoder },
|
||||
{ DecoderProto::kAmiga, createAmigaDecoder },
|
||||
{ DecoderProto::kApple2, createApple2Decoder },
|
||||
{ DecoderProto::kBrother, createBrotherDecoder },
|
||||
{ DecoderProto::kC64, createCommodore64Decoder },
|
||||
{ DecoderProto::kF85, createDurangoF85Decoder },
|
||||
{ DecoderProto::kFb100, createFb100Decoder },
|
||||
{ DecoderProto::kIbm, createIbmDecoder },
|
||||
{ DecoderProto::kMacintosh, createMacintoshDecoder },
|
||||
{ DecoderProto::kMicropolis, createMicropolisDecoder },
|
||||
{ DecoderProto::kMx, createMxDecoder },
|
||||
{ DecoderProto::kNorthstar, createNorthstarDecoder },
|
||||
{ DecoderProto::kTids990, createTids990Decoder },
|
||||
{ DecoderProto::kVictor9K, createVictor9kDecoder },
|
||||
{ DecoderProto::kZilogmcz, createZilogMczDecoder },
|
||||
};
|
||||
|
||||
auto decoder = decoders.find(config.format_case());
|
||||
|
||||
@@ -33,6 +33,9 @@ static inline Bytes decodeFmMfm(const std::vector<bool> bits)
|
||||
class AbstractDecoder
|
||||
{
|
||||
public:
|
||||
AbstractDecoder() {} // REMOVE ME
|
||||
AbstractDecoder(const DecoderProto& config) {}
|
||||
|
||||
virtual ~AbstractDecoder() {}
|
||||
|
||||
static std::unique_ptr<AbstractDecoder> create(const DecoderProto& config);
|
||||
|
||||
@@ -14,33 +14,24 @@
|
||||
|
||||
std::unique_ptr<AbstractEncoder> AbstractEncoder::create(const EncoderProto& config)
|
||||
{
|
||||
switch (config.format_case())
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>> encoders =
|
||||
{
|
||||
case EncoderProto::kAmiga:
|
||||
return std::unique_ptr<AbstractEncoder>(new AmigaEncoder(config.amiga()));
|
||||
{ EncoderProto::kAmiga, createAmigaEncoder },
|
||||
{ EncoderProto::kBrother, createBrotherEncoder },
|
||||
{ EncoderProto::kC64, createCommodore64Encoder },
|
||||
{ EncoderProto::kIbm, createIbmEncoder },
|
||||
{ EncoderProto::kMacintosh, createMacintoshEncoder },
|
||||
{ EncoderProto::kNorthstar, createNorthstarEncoder },
|
||||
};
|
||||
|
||||
case EncoderProto::kIbm:
|
||||
return std::unique_ptr<AbstractEncoder>(new IbmEncoder(config.ibm()));
|
||||
auto encoder = encoders.find(config.format_case());
|
||||
if (encoder == encoders.end())
|
||||
Error() << "no encoder specified";
|
||||
|
||||
case EncoderProto::kBrother:
|
||||
return std::unique_ptr<AbstractEncoder>(new BrotherEncoder(config.brother()));
|
||||
|
||||
case EncoderProto::kMacintosh:
|
||||
return std::unique_ptr<AbstractEncoder>(new MacintoshEncoder(config.macintosh()));
|
||||
|
||||
case EncoderProto::kC64:
|
||||
return std::unique_ptr<AbstractEncoder>(new Commodore64Encoder(config.c64()));
|
||||
|
||||
case EncoderProto::kNorthstar:
|
||||
return std::unique_ptr<AbstractEncoder>(new NorthstarEncoder(config.northstar()));
|
||||
|
||||
default:
|
||||
Error() << "no input disk format specified";
|
||||
}
|
||||
return std::unique_ptr<AbstractEncoder>();
|
||||
return (encoder->second)(config);
|
||||
}
|
||||
|
||||
|
||||
Fluxmap& Fluxmap::appendBits(const std::vector<bool>& bits, nanoseconds_t clock)
|
||||
{
|
||||
nanoseconds_t now = duration();
|
||||
|
||||
@@ -9,7 +9,7 @@ class EncoderProto;
|
||||
class AbstractEncoder
|
||||
{
|
||||
public:
|
||||
virtual ~AbstractEncoder() {}
|
||||
AbstractEncoder(const EncoderProto& config) {}
|
||||
|
||||
static std::unique_ptr<AbstractEncoder> create(const EncoderProto& config);
|
||||
|
||||
|
||||
@@ -24,10 +24,16 @@ public:
|
||||
unsigned autoSides;
|
||||
unsigned autoSectors;
|
||||
unsigned autoBytes;
|
||||
bool mixedDensity = false;
|
||||
sectors.calculateSize(autoTracks, autoSides, autoSectors, autoBytes);
|
||||
|
||||
size_t trackSize = autoSectors * autoBytes;
|
||||
|
||||
if (autoTracks * trackSize == 0) {
|
||||
std::cout << "No sectors in output; skipping .nsi image file generation." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << fmt::format("Writing {} cylinders, {} heads, {} sectors, {} ({} bytes/sector), {} kB total",
|
||||
autoTracks, autoSides,
|
||||
autoSectors, autoBytes == 256 ? "SD" : "DD", autoBytes,
|
||||
@@ -56,7 +62,25 @@ public:
|
||||
(sectorId * autoBytes); /* Sector offset from beginning of track. */
|
||||
}
|
||||
outputFile.seekp(sectorFileOffset, std::ios::beg);
|
||||
sector->data.slice(0, autoBytes).writeTo(outputFile);
|
||||
if ((autoBytes == 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) {
|
||||
std::cout << "Warning: Disk contains mixed single/double-density sectors." << std::endl;
|
||||
}
|
||||
mixedDensity = true;
|
||||
sector->data.slice(0, 256).writeTo(outputFile);
|
||||
outputFile.write(fill, sizeof(fill));
|
||||
} else {
|
||||
sector->data.slice(0, autoBytes).writeTo(outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user