mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #506 from davidgiven/logical
Rework to use logical track numbers throughout.
This commit is contained in:
16
README.md
16
README.md
@@ -37,11 +37,6 @@ FluxEngine features are available with the GreaseWeazle and it works out-of-the
|
||||
box. See the [dedicated GreaseWeazle documentation page](doc/greaseweazle.md)
|
||||
for more information.
|
||||
|
||||
**Important note.** On 2020-04-02 I changed the bytecode format (and firmware).
|
||||
Flux files will need to be upgraded with `fluxengine upgradefluxfile`. The new
|
||||
format should be more reliable and use way, way less bandwidth. Sorry for the
|
||||
inconvenience.
|
||||
|
||||
Where?
|
||||
------
|
||||
|
||||
@@ -66,14 +61,17 @@ following friendly articles:
|
||||
flux files and image files ∾ knowing what you're doing
|
||||
|
||||
- [Using GreaseWeazle hardware with the FluxEngine client
|
||||
software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to
|
||||
go for help
|
||||
software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to
|
||||
go for help
|
||||
|
||||
- [Configuring for your drive](doc/drives.md) ∾ but I don't have a 80 track
|
||||
drive! ∾ reading and writing 40 track disks ∾ Shugart and Apple II
|
||||
|
||||
- [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact
|
||||
science ∾ the sector map ∾ clock detection and the histogram
|
||||
science ∾ the sector map ∾ clock detection and the histogram
|
||||
|
||||
- [Checking your drive](doc/driveresponse.md) ∾ you can't do that with that ∾
|
||||
measuring your drive's ability to work with exotic formats
|
||||
measuring your drive's ability to work with exotic formats
|
||||
|
||||
Which?
|
||||
------
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
|
||||
std::set<unsigned> requiredSectors(const Location&) const override
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||
return sectors;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "encoders/encoders.h"
|
||||
#include "amiga.h"
|
||||
#include "crc.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "arch/amiga/amiga.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
@@ -12,152 +12,156 @@ static bool lastBit;
|
||||
|
||||
static int charToInt(char c)
|
||||
{
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
return 10 + tolower(c) - 'a';
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
return 10 + tolower(c) - 'a';
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
|
||||
{
|
||||
for (bool bit : src)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = bit;
|
||||
}
|
||||
for (bool bit : src)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = bit;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, uint64_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;
|
||||
}
|
||||
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 void write_bits(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
BitReader bitr(br);
|
||||
ByteReader br(bytes);
|
||||
BitReader bitr(br);
|
||||
|
||||
while (!bitr.eof())
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
bits[cursor++] = bitr.get();
|
||||
}
|
||||
while (!bitr.eof())
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
bits[cursor++] = bitr.get();
|
||||
}
|
||||
}
|
||||
|
||||
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<const Sector>& sector)
|
||||
static void write_sector(std::vector<bool>& bits,
|
||||
unsigned& cursor,
|
||||
const std::shared_ptr<const Sector>& sector)
|
||||
{
|
||||
if ((sector->data.size() != 512) && (sector->data.size() != 528))
|
||||
Error() << "unsupported sector size --- you must pick 512 or 528";
|
||||
if ((sector->data.size() != 512) && (sector->data.size() != 528))
|
||||
Error() << "unsupported sector size --- you must pick 512 or 528";
|
||||
|
||||
uint32_t checksum = 0;
|
||||
uint32_t checksum = 0;
|
||||
|
||||
auto write_interleaved_bytes = [&](const Bytes& bytes)
|
||||
{
|
||||
Bytes interleaved = amigaInterleave(bytes);
|
||||
Bytes mfm = encodeMfm(interleaved, lastBit);
|
||||
checksum ^= amigaChecksum(mfm);
|
||||
checksum &= 0x55555555;
|
||||
write_bits(bits, cursor, mfm);
|
||||
};
|
||||
auto write_interleaved_bytes = [&](const Bytes& bytes)
|
||||
{
|
||||
Bytes interleaved = amigaInterleave(bytes);
|
||||
Bytes mfm = encodeMfm(interleaved, lastBit);
|
||||
checksum ^= amigaChecksum(mfm);
|
||||
checksum &= 0x55555555;
|
||||
write_bits(bits, cursor, mfm);
|
||||
};
|
||||
|
||||
auto write_interleaved_word = [&](uint32_t word)
|
||||
{
|
||||
Bytes b(4);
|
||||
b.writer().write_be32(word);
|
||||
write_interleaved_bytes(b);
|
||||
};
|
||||
auto write_interleaved_word = [&](uint32_t word)
|
||||
{
|
||||
Bytes b(4);
|
||||
b.writer().write_be32(word);
|
||||
write_interleaved_bytes(b);
|
||||
};
|
||||
|
||||
write_bits(bits, cursor, 0xaaaa, 2*8);
|
||||
write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6*8);
|
||||
write_bits(bits, cursor, 0xaaaa, 2 * 8);
|
||||
write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6 * 8);
|
||||
|
||||
checksum = 0;
|
||||
Bytes header =
|
||||
{
|
||||
0xff, /* Amiga 1.0 format byte */
|
||||
(uint8_t) ((sector->logicalTrack<<1) | sector->logicalSide),
|
||||
(uint8_t) sector->logicalSector,
|
||||
(uint8_t) (AMIGA_SECTORS_PER_TRACK - sector->logicalSector)
|
||||
};
|
||||
write_interleaved_bytes(header);
|
||||
Bytes recoveryInfo(16);
|
||||
if (sector->data.size() == 528)
|
||||
recoveryInfo = sector->data.slice(512, 16);
|
||||
write_interleaved_bytes(recoveryInfo);
|
||||
write_interleaved_word(checksum);
|
||||
checksum = 0;
|
||||
Bytes header = {0xff, /* Amiga 1.0 format byte */
|
||||
(uint8_t)((sector->logicalTrack << 1) | sector->logicalSide),
|
||||
(uint8_t)sector->logicalSector,
|
||||
(uint8_t)(AMIGA_SECTORS_PER_TRACK - sector->logicalSector)};
|
||||
write_interleaved_bytes(header);
|
||||
Bytes recoveryInfo(16);
|
||||
if (sector->data.size() == 528)
|
||||
recoveryInfo = sector->data.slice(512, 16);
|
||||
write_interleaved_bytes(recoveryInfo);
|
||||
write_interleaved_word(checksum);
|
||||
|
||||
Bytes data = sector->data.slice(0, 512);
|
||||
write_interleaved_word(amigaChecksum(encodeMfm(amigaInterleave(data), lastBit)));
|
||||
write_interleaved_bytes(data);
|
||||
Bytes data = sector->data.slice(0, 512);
|
||||
write_interleaved_word(
|
||||
amigaChecksum(encodeMfm(amigaInterleave(data), lastBit)));
|
||||
write_interleaved_bytes(data);
|
||||
}
|
||||
|
||||
class AmigaEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
AmigaEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.amiga()) {}
|
||||
AmigaEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.amiga())
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
if ((physicalTrack >= 0) && (physicalTrack < AMIGA_TRACKS_PER_DISK))
|
||||
{
|
||||
for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
if ((location.logicalTrack >= 0) &&
|
||||
(location.logicalTrack < AMIGA_TRACKS_PER_DISK))
|
||||
{
|
||||
for (int sectorId = 0; sectorId < AMIGA_SECTORS_PER_TRACK;
|
||||
sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(
|
||||
location.logicalTrack, location.head, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
if ((physicalTrack < 0) || (physicalTrack >= AMIGA_TRACKS_PER_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
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;
|
||||
|
||||
fillBitmapTo(bits, cursor, _config.post_index_gap_ms() * 1000 / _config.clock_rate_us(), { true, false });
|
||||
lastBit = false;
|
||||
for (const auto& sector : sectors)
|
||||
write_sector(bits, cursor, sector);
|
||||
|
||||
for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
|
||||
{
|
||||
const auto& sectorData = image.get(physicalTrack, physicalSide, sectorId);
|
||||
if (sectorData)
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
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;
|
||||
}
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, _config.clock_rate_us() * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const AmigaEncoderProto& _config;
|
||||
const AmigaEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createAmigaEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new AmigaEncoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new AmigaEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "sector.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include <ctype.h>
|
||||
#include "bytes.h"
|
||||
|
||||
static int encode_data_gcr(uint8_t data) {
|
||||
switch(data)
|
||||
static int encode_data_gcr(uint8_t data)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: \
|
||||
return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -24,162 +26,178 @@ static int encode_data_gcr(uint8_t data) {
|
||||
class Apple2Encoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
Apple2Encoder(const EncoderProto& config):
|
||||
AbstractEncoder(config)
|
||||
{}
|
||||
Apple2Encoder(const EncoderProto& config): AbstractEncoder(config) {}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
constexpr auto numSectors = 16;
|
||||
if (physicalSide == 0)
|
||||
{
|
||||
int logicalTrack = physicalTrack / 2;
|
||||
unsigned numSectors = 16;
|
||||
for (int sectorId=0; sectorId<numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(logicalTrack, 0, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
constexpr auto numSectors = 16;
|
||||
if (location.head == 0)
|
||||
{
|
||||
unsigned numSectors = 16;
|
||||
for (int sectorId = 0; sectorId < numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(location.logicalTrack, 0, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
|
||||
return sectors;
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
std::unique_ptr<Fluxmap> encode(
|
||||
const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
if (physicalSide != 0)
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
double clockRateUs = 4.;
|
||||
|
||||
int logicalTrack = physicalTrack / 2;
|
||||
double clockRateUs = 4.;
|
||||
// apple2 drives spin at 300rpm. 300/minute in us = 200000.
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
|
||||
// apple2 drives spin at 300rpm. 300/minute in us = 200000.
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
for (const auto& sector : sectors)
|
||||
writeSector(bits, cursor, *sector);
|
||||
|
||||
for (const auto& sector : sectors) {
|
||||
if(sector) {
|
||||
writeSector(bits, cursor, *sector);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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:
|
||||
uint8_t volume_id = 254;
|
||||
|
||||
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
|
||||
* and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
|
||||
/* This is extremely inspired by the MESS implementation, written by Nathan
|
||||
* Woods and R. Belmont:
|
||||
* https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
|
||||
* as well as Understanding the Apple II (1983) Chapter 9
|
||||
* https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater
|
||||
*/
|
||||
|
||||
void writeSector(std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const
|
||||
void writeSector(
|
||||
std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const
|
||||
{
|
||||
if ((sector.status == Sector::OK) or (sector.status == Sector::BAD_CHECKSUM))
|
||||
{
|
||||
auto write_bit = [&](bool val) {
|
||||
if(cursor <= bits.size()) { bits[cursor] = val; }
|
||||
cursor++;
|
||||
};
|
||||
if ((sector.status == Sector::OK) or
|
||||
(sector.status == Sector::BAD_CHECKSUM))
|
||||
{
|
||||
auto write_bit = [&](bool val)
|
||||
{
|
||||
if (cursor <= bits.size())
|
||||
{
|
||||
bits[cursor] = val;
|
||||
}
|
||||
cursor++;
|
||||
};
|
||||
|
||||
auto write_bits = [&](uint32_t bits, int width) {
|
||||
for(int i=width; i--;) {
|
||||
write_bit(bits & (1u << i));
|
||||
}
|
||||
};
|
||||
auto write_bits = [&](uint32_t bits, int width)
|
||||
{
|
||||
for (int i = width; i--;)
|
||||
{
|
||||
write_bit(bits & (1u << i));
|
||||
}
|
||||
};
|
||||
|
||||
auto write_gcr44 = [&](uint8_t value) {
|
||||
write_bits((value << 7) | value | 0xaaaa, 16);
|
||||
};
|
||||
auto write_gcr44 = [&](uint8_t value)
|
||||
{
|
||||
write_bits((value << 7) | value | 0xaaaa, 16);
|
||||
};
|
||||
|
||||
auto write_gcr6 = [&](uint8_t value) {
|
||||
write_bits(encode_data_gcr(value), 8);
|
||||
};
|
||||
auto write_gcr6 = [&](uint8_t value)
|
||||
{
|
||||
write_bits(encode_data_gcr(value), 8);
|
||||
};
|
||||
|
||||
// The special "FF40" sequence is used to synchronize the receiving
|
||||
// shift register. It's written as "1111 1111 00"; FF indicates the
|
||||
// 8 consecutive 1-bits, while "40" indicates the total number of
|
||||
// microseconds.
|
||||
auto write_ff40 = [&](int n=1) {
|
||||
for(;n--;) {
|
||||
write_bits(0xff << 2, 10);
|
||||
}
|
||||
};
|
||||
// The special "FF40" sequence is used to synchronize the receiving
|
||||
// shift register. It's written as "1111 1111 00"; FF indicates the
|
||||
// 8 consecutive 1-bits, while "40" indicates the total number of
|
||||
// microseconds.
|
||||
auto write_ff40 = [&](int n = 1)
|
||||
{
|
||||
for (; n--;)
|
||||
{
|
||||
write_bits(0xff << 2, 10);
|
||||
}
|
||||
};
|
||||
|
||||
// There is data to encode to disk.
|
||||
if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
|
||||
Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector.data.size());
|
||||
// There is data to encode to disk.
|
||||
if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
|
||||
Error() << fmt::format(
|
||||
"unsupported sector size {} --- you must pick 256",
|
||||
sector.data.size());
|
||||
|
||||
// Write address syncing leader : A sequence of "FF40"s; 5 of them
|
||||
// are said to suffice to synchronize the decoder.
|
||||
// "FF40" indicates that the actual data written is "1111
|
||||
// 1111 00" i.e., 8 1s and a total of 40 microseconds
|
||||
//
|
||||
// In standard formatting, the first logical sector apparently gets
|
||||
// extra padding.
|
||||
write_ff40(sector.logicalSector == 0 ? 32 : 8);
|
||||
// Write address syncing leader : A sequence of "FF40"s; 5 of them
|
||||
// are said to suffice to synchronize the decoder.
|
||||
// "FF40" indicates that the actual data written is "1111
|
||||
// 1111 00" i.e., 8 1s and a total of 40 microseconds
|
||||
//
|
||||
// In standard formatting, the first logical sector apparently gets
|
||||
// extra padding.
|
||||
write_ff40(sector.logicalSector == 0 ? 32 : 8);
|
||||
|
||||
// Write address field: APPLE2_SECTOR_RECORD + sector identifier + DE AA EB
|
||||
write_bits(APPLE2_SECTOR_RECORD, 24);
|
||||
write_gcr44(volume_id);
|
||||
write_gcr44(sector.logicalTrack);
|
||||
write_gcr44(sector.logicalSector);
|
||||
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
|
||||
write_bits(0xDEAAEB, 24);
|
||||
// Write address field: APPLE2_SECTOR_RECORD + sector identifier +
|
||||
// DE AA EB
|
||||
write_bits(APPLE2_SECTOR_RECORD, 24);
|
||||
write_gcr44(volume_id);
|
||||
write_gcr44(sector.logicalTrack);
|
||||
write_gcr44(sector.logicalSector);
|
||||
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
|
||||
write_bits(0xDEAAEB, 24);
|
||||
|
||||
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector data + sum + DE AA EB (+ mystery bits cut off of the scan?)
|
||||
write_ff40(8);
|
||||
write_bits(APPLE2_DATA_RECORD, 24);
|
||||
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector
|
||||
// data + sum + DE AA EB (+ mystery bits cut off of the scan?)
|
||||
write_ff40(8);
|
||||
write_bits(APPLE2_DATA_RECORD, 24);
|
||||
|
||||
// Convert the sector data to GCR, append the checksum, and write it out
|
||||
constexpr auto TWOBIT_COUNT = 0x56; // Size of the 'twobit' area at the start of the GCR data
|
||||
uint8_t checksum = 0;
|
||||
for(int i=0; i<APPLE2_ENCODED_SECTOR_LENGTH; i++) {
|
||||
int value;
|
||||
if(i >= TWOBIT_COUNT) {
|
||||
value = sector.data[i - TWOBIT_COUNT] >> 2;
|
||||
} else {
|
||||
uint8_t tmp = sector.data[i];
|
||||
value = ((tmp & 1) << 1) | ((tmp & 2) >> 1);
|
||||
// Convert the sector data to GCR, append the checksum, and write it
|
||||
// out
|
||||
constexpr auto TWOBIT_COUNT =
|
||||
0x56; // Size of the 'twobit' area at the start of the GCR data
|
||||
uint8_t checksum = 0;
|
||||
for (int i = 0; i < APPLE2_ENCODED_SECTOR_LENGTH; i++)
|
||||
{
|
||||
int value;
|
||||
if (i >= TWOBIT_COUNT)
|
||||
{
|
||||
value = sector.data[i - TWOBIT_COUNT] >> 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t tmp = sector.data[i];
|
||||
value = ((tmp & 1) << 1) | ((tmp & 2) >> 1);
|
||||
|
||||
tmp = sector.data[i + TWOBIT_COUNT];
|
||||
value |= ((tmp & 1) << 3) | ((tmp & 2) << 1);
|
||||
tmp = sector.data[i + TWOBIT_COUNT];
|
||||
value |= ((tmp & 1) << 3) | ((tmp & 2) << 1);
|
||||
|
||||
if(i + 2*TWOBIT_COUNT < APPLE2_SECTOR_LENGTH) {
|
||||
tmp = sector.data[i + 2*TWOBIT_COUNT];
|
||||
value |= ((tmp & 1) << 5) | ((tmp & 2) << 3);
|
||||
}
|
||||
}
|
||||
checksum ^= value;
|
||||
// assert(checksum & ~0x3f == 0);
|
||||
write_gcr6(checksum);
|
||||
checksum = value;
|
||||
}
|
||||
if(sector.status == Sector::BAD_CHECKSUM) checksum ^= 0x3f;
|
||||
write_gcr6(checksum);
|
||||
write_bits(0xDEAAEB, 24);
|
||||
}
|
||||
if (i + 2 * TWOBIT_COUNT < APPLE2_SECTOR_LENGTH)
|
||||
{
|
||||
tmp = sector.data[i + 2 * TWOBIT_COUNT];
|
||||
value |= ((tmp & 1) << 5) | ((tmp & 2) << 3);
|
||||
}
|
||||
}
|
||||
checksum ^= value;
|
||||
// assert(checksum & ~0x3f == 0);
|
||||
write_gcr6(checksum);
|
||||
checksum = value;
|
||||
}
|
||||
if (sector.status == Sector::BAD_CHECKSUM)
|
||||
checksum ^= 0x3f;
|
||||
write_gcr6(checksum);
|
||||
write_bits(0xDEAAEB, 24);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new Apple2Encoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new Apple2Encoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,5 @@ message BrotherEncoderProto {
|
||||
optional string sector_skew = 5 [default = "05a3816b4927"];
|
||||
|
||||
optional BrotherFormat format = 6 [default = BROTHER240];
|
||||
optional int32 bias = 7 [default = 0];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,91 +3,95 @@
|
||||
#include "encoders/encoders.h"
|
||||
#include "brother.h"
|
||||
#include "crc.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "arch/brother/brother.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
|
||||
static int encode_header_gcr(uint16_t word)
|
||||
{
|
||||
switch (word)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: return gcr;
|
||||
#include "header_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
switch (word)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: \
|
||||
return gcr;
|
||||
#include "header_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int encode_data_gcr(uint8_t data)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
switch (data)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: \
|
||||
return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width)
|
||||
{
|
||||
cursor += width;
|
||||
for (int i=0; i<width; i++)
|
||||
{
|
||||
unsigned pos = cursor - i - 1;
|
||||
if (pos < bits.size())
|
||||
bits[pos] = data & 1;
|
||||
data >>= 1;
|
||||
}
|
||||
cursor += width;
|
||||
for (int i = 0; i < width; i++)
|
||||
{
|
||||
unsigned pos = cursor - i - 1;
|
||||
if (pos < bits.size())
|
||||
bits[pos] = data & 1;
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_sector_header(std::vector<bool>& bits, unsigned& cursor,
|
||||
int track, int sector)
|
||||
static void write_sector_header(
|
||||
std::vector<bool>& bits, unsigned& cursor, int track, int sector)
|
||||
{
|
||||
write_bits(bits, cursor, 0xffffffff, 31);
|
||||
write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32);
|
||||
write_bits(bits, cursor, encode_header_gcr(track), 16);
|
||||
write_bits(bits, cursor, encode_header_gcr(sector), 16);
|
||||
write_bits(bits, cursor, encode_header_gcr(0x2f), 16);
|
||||
write_bits(bits, cursor, 0xffffffff, 31);
|
||||
write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32);
|
||||
write_bits(bits, cursor, encode_header_gcr(track), 16);
|
||||
write_bits(bits, cursor, encode_header_gcr(sector), 16);
|
||||
write_bits(bits, cursor, encode_header_gcr(0x2f), 16);
|
||||
}
|
||||
|
||||
static void write_sector_data(std::vector<bool>& bits, unsigned& cursor, const Bytes& data)
|
||||
static void write_sector_data(
|
||||
std::vector<bool>& bits, unsigned& cursor, const Bytes& data)
|
||||
{
|
||||
write_bits(bits, cursor, 0xffffffff, 32);
|
||||
write_bits(bits, cursor, BROTHER_DATA_RECORD, 32);
|
||||
write_bits(bits, cursor, 0xffffffff, 32);
|
||||
write_bits(bits, cursor, BROTHER_DATA_RECORD, 32);
|
||||
|
||||
uint16_t fifo = 0;
|
||||
int width = 0;
|
||||
uint16_t fifo = 0;
|
||||
int width = 0;
|
||||
|
||||
if (data.size() != BROTHER_DATA_RECORD_PAYLOAD)
|
||||
Error() << "unsupported sector size";
|
||||
if (data.size() != BROTHER_DATA_RECORD_PAYLOAD)
|
||||
Error() << "unsupported sector size";
|
||||
|
||||
auto write_byte = [&](uint8_t byte)
|
||||
{
|
||||
fifo |= (byte << (8 - width));
|
||||
width += 8;
|
||||
auto write_byte = [&](uint8_t byte)
|
||||
{
|
||||
fifo |= (byte << (8 - width));
|
||||
width += 8;
|
||||
|
||||
while (width >= 5)
|
||||
{
|
||||
uint8_t quintet = fifo >> 11;
|
||||
fifo <<= 5;
|
||||
width -= 5;
|
||||
while (width >= 5)
|
||||
{
|
||||
uint8_t quintet = fifo >> 11;
|
||||
fifo <<= 5;
|
||||
width -= 5;
|
||||
|
||||
write_bits(bits, cursor, encode_data_gcr(quintet), 8);
|
||||
}
|
||||
};
|
||||
write_bits(bits, cursor, encode_data_gcr(quintet), 8);
|
||||
}
|
||||
};
|
||||
|
||||
for (uint8_t byte : data)
|
||||
write_byte(byte);
|
||||
for (uint8_t byte : data)
|
||||
write_byte(byte);
|
||||
|
||||
uint32_t realCrc = crcbrother(data);
|
||||
write_byte(realCrc>>16);
|
||||
write_byte(realCrc>>8);
|
||||
write_byte(realCrc);
|
||||
write_byte(0x58); /* magic */
|
||||
uint32_t realCrc = crcbrother(data);
|
||||
write_byte(realCrc >> 16);
|
||||
write_byte(realCrc >> 8);
|
||||
write_byte(realCrc);
|
||||
write_byte(0x58); /* magic */
|
||||
write_byte(0xd4);
|
||||
while (width != 0)
|
||||
write_byte(0);
|
||||
@@ -95,112 +99,98 @@ static void write_sector_data(std::vector<bool>& bits, unsigned& cursor, const B
|
||||
|
||||
static int charToInt(char c)
|
||||
{
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
return 10 + tolower(c) - 'a';
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
return 10 + tolower(c) - 'a';
|
||||
}
|
||||
|
||||
class BrotherEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
BrotherEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.brother())
|
||||
{}
|
||||
BrotherEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.brother())
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location,
|
||||
const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
if (physicalSide != 0)
|
||||
return sectors;
|
||||
physicalTrack -= _config.bias();
|
||||
switch (_config.format())
|
||||
{
|
||||
case BROTHER120:
|
||||
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|
||||
|| (physicalTrack & 1))
|
||||
return sectors;
|
||||
break;
|
||||
if (location.head != 0)
|
||||
return sectors;
|
||||
|
||||
case BROTHER240:
|
||||
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
|
||||
return sectors;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int sectorId=0; sectorId<BROTHER_SECTORS_PER_TRACK; sectorId++)
|
||||
switch (_config.format())
|
||||
{
|
||||
const auto& sector = image.get(physicalTrack, 0, sectorId);
|
||||
case BROTHER120:
|
||||
if (location.logicalTrack >= BROTHER_TRACKS_PER_120KB_DISK)
|
||||
return sectors;
|
||||
break;
|
||||
|
||||
case BROTHER240:
|
||||
if (location.logicalTrack >= BROTHER_TRACKS_PER_240KB_DISK)
|
||||
return sectors;
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string& skew = _config.sector_skew();
|
||||
for (int sectorCount = 0; sectorCount < BROTHER_SECTORS_PER_TRACK;
|
||||
sectorCount++)
|
||||
{
|
||||
int sectorId = charToInt(skew.at(sectorCount));
|
||||
const auto& sector = image.get(location.logicalTrack, 0, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
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>();
|
||||
break;
|
||||
std::unique_ptr<Fluxmap> encode(
|
||||
const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
case BROTHER240:
|
||||
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
break;
|
||||
}
|
||||
int sectorCount = 0;
|
||||
for (const auto& sectorData : sectors)
|
||||
{
|
||||
double headerMs = _config.post_index_gap_ms() +
|
||||
sectorCount * _config.sector_spacing_ms();
|
||||
unsigned headerCursor = headerMs * 1e3 / _config.clock_rate_us();
|
||||
double dataMs = headerMs + _config.post_header_spacing_ms();
|
||||
unsigned dataCursor = dataMs * 1e3 / _config.clock_rate_us();
|
||||
|
||||
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
|
||||
const std::string& skew = _config.sector_skew();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
fillBitmapTo(bits, cursor, headerCursor, {true, false});
|
||||
write_sector_header(
|
||||
bits, cursor, sectorData->logicalTrack, sectorData->logicalSector);
|
||||
fillBitmapTo(bits, cursor, dataCursor, {true, false});
|
||||
write_sector_data(bits, cursor, sectorData->data);
|
||||
|
||||
for (int sectorCount=0; sectorCount<BROTHER_SECTORS_PER_TRACK; sectorCount++)
|
||||
{
|
||||
int sectorId = charToInt(skew.at(sectorCount));
|
||||
double headerMs = _config.post_index_gap_ms() + sectorCount*_config.sector_spacing_ms();
|
||||
unsigned headerCursor = headerMs*1e3 / _config.clock_rate_us();
|
||||
double dataMs = headerMs + _config.post_header_spacing_ms();
|
||||
unsigned dataCursor = dataMs*1e3 / _config.clock_rate_us();
|
||||
sectorCount++;
|
||||
}
|
||||
|
||||
const auto& sectorData = image.get(physicalTrack, 0, sectorId);
|
||||
if (cursor >= bits.size())
|
||||
Error() << "track data overrun";
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
fillBitmapTo(bits, cursor, headerCursor, { true, false });
|
||||
write_sector_header(bits, cursor, sectorData->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, _config.clock_rate_us()*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, _config.clock_rate_us() * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const BrotherEncoderProto& _config;
|
||||
|
||||
const BrotherEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createBrotherEncoder(const EncoderProto& config)
|
||||
std::unique_ptr<AbstractEncoder> createBrotherEncoder(
|
||||
const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new BrotherEncoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new BrotherEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "c64.h"
|
||||
#include "crc.h"
|
||||
#include "sector.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "arch/c64/c64.pb.h"
|
||||
@@ -211,17 +211,16 @@ public:
|
||||
{}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
if (physicalSide == 0)
|
||||
if (location.head == 0)
|
||||
{
|
||||
int logicalTrack = physicalTrack / 2;
|
||||
unsigned numSectors = sectorsForTrack(logicalTrack);
|
||||
unsigned numSectors = sectorsForTrack(location.logicalTrack);
|
||||
for (int sectorId=0; sectorId<numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(logicalTrack, 0, sectorId);
|
||||
const auto& sector = image.get(location.logicalTrack, 0, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
@@ -230,7 +229,7 @@ public:
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
/* The format ID Character # 1 and # 2 are in the .d64 image only present
|
||||
@@ -240,9 +239,6 @@ public:
|
||||
* contains the BAM.
|
||||
*/
|
||||
|
||||
if (physicalSide != 0)
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
const auto& sectorData = image.get(C64_BAM_TRACK*2, 0, 0); //Read de BAM to get the DISK ID bytes
|
||||
if (sectorData)
|
||||
{
|
||||
@@ -254,8 +250,7 @@ public:
|
||||
else
|
||||
_formatByte1 = _formatByte2 = 0;
|
||||
|
||||
int logicalTrack = physicalTrack / 2;
|
||||
double clockRateUs = clockRateUsForTrack(logicalTrack) * _config.clock_compensation_factor();
|
||||
double clockRateUs = clockRateUsForTrack(location.logicalTrack) * _config.clock_compensation_factor();
|
||||
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
bw += decodeFmMfm(bits).slice(0, IBM_IDAM_LEN);
|
||||
|
||||
IbmDecoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, _sector->physicalCylinder, _sector->physicalHead);
|
||||
getTrackFormat(trackdata, _sector->physicalTrack, _sector->physicalHead);
|
||||
|
||||
_sector->logicalTrack = br.read_8();
|
||||
_sector->logicalSide = br.read_8();
|
||||
@@ -155,7 +155,7 @@ public:
|
||||
if (trackdata.ignore_side_byte())
|
||||
_sector->logicalSide = _sector->physicalHead;
|
||||
if (trackdata.ignore_track_byte())
|
||||
_sector->logicalTrack = _sector->physicalCylinder;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
|
||||
for (int sector : trackdata.ignore_sector())
|
||||
if (_sector->logicalSector == sector)
|
||||
@@ -203,10 +203,10 @@ public:
|
||||
_sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
|
||||
std::set<unsigned> requiredSectors(const Location& location) const override
|
||||
{
|
||||
IbmDecoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, cylinder, head);
|
||||
getTrackFormat(trackdata, location.logicalTrack, location.head);
|
||||
|
||||
std::set<unsigned> s;
|
||||
if (trackdata.has_sectors())
|
||||
@@ -227,12 +227,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head) const
|
||||
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, unsigned track, unsigned head) const
|
||||
{
|
||||
trackdata.Clear();
|
||||
for (const auto& f : _config.trackdata())
|
||||
{
|
||||
if (f.has_cylinder() && (f.cylinder() != cylinder))
|
||||
if (f.has_track() && (f.track() != track))
|
||||
continue;
|
||||
if (f.has_head() && (f.head() != head))
|
||||
continue;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "encoders/encoders.h"
|
||||
#include "ibm.h"
|
||||
#include "crc.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "arch/ibm/ibm.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
@@ -39,9 +39,9 @@
|
||||
* ^^^^^
|
||||
* When shifted out of phase, the special 0xa1 byte becomes an illegal
|
||||
* encoding (you can't do 10 00). So this can't be spoofed by user data.
|
||||
*
|
||||
*
|
||||
* shifted: 10 00 10 01 00 01 00 1
|
||||
*
|
||||
*
|
||||
* It's repeated three times.
|
||||
*/
|
||||
#define MFM_RECORD_SEPARATOR 0x4489
|
||||
@@ -59,255 +59,256 @@
|
||||
|
||||
static uint8_t decodeUint16(uint16_t raw)
|
||||
{
|
||||
Bytes b;
|
||||
ByteWriter bw(b);
|
||||
bw.write_be16(raw);
|
||||
return decodeFmMfm(b.toBits())[0];
|
||||
Bytes b;
|
||||
ByteWriter bw(b);
|
||||
bw.write_be16(raw);
|
||||
return decodeFmMfm(b.toBits())[0];
|
||||
}
|
||||
|
||||
class IbmEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
IbmEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.ibm())
|
||||
{}
|
||||
IbmEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.ibm())
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void 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 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 getTrackFormat(IbmEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
|
||||
{
|
||||
trackdata.Clear();
|
||||
for (const auto& f : _config.trackdata())
|
||||
{
|
||||
if (f.has_cylinder() && (f.cylinder() != cylinder))
|
||||
continue;
|
||||
if (f.has_head() && (f.head() != head))
|
||||
continue;
|
||||
void getTrackFormat(IbmEncoderProto::TrackdataProto& trackdata,
|
||||
unsigned track,
|
||||
unsigned head)
|
||||
{
|
||||
trackdata.Clear();
|
||||
for (const auto& f : _config.trackdata())
|
||||
{
|
||||
if (f.has_track() && (f.track() != track))
|
||||
continue;
|
||||
if (f.has_head() && (f.head() != head))
|
||||
continue;
|
||||
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static std::set<unsigned> getSectorIds(const IbmEncoderProto::TrackdataProto& trackdata)
|
||||
{
|
||||
std::set<unsigned> s;
|
||||
if (trackdata.has_sectors())
|
||||
{
|
||||
for (int sectorId : trackdata.sectors().sector())
|
||||
s.insert(sectorId);
|
||||
}
|
||||
else if (trackdata.has_sector_range())
|
||||
{
|
||||
int sectorId = trackdata.sector_range().min_sector();
|
||||
while (sectorId <= trackdata.sector_range().max_sector())
|
||||
{
|
||||
s.insert(sectorId);
|
||||
sectorId++;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
static std::set<unsigned> getSectorIds(
|
||||
const IbmEncoderProto::TrackdataProto& trackdata)
|
||||
{
|
||||
std::set<unsigned> s;
|
||||
if (trackdata.has_sectors())
|
||||
{
|
||||
for (int sectorId : trackdata.sectors().sector())
|
||||
s.insert(sectorId);
|
||||
}
|
||||
else if (trackdata.has_sector_range())
|
||||
{
|
||||
int sectorId = trackdata.sector_range().min_sector();
|
||||
while (sectorId <= trackdata.sector_range().max_sector())
|
||||
{
|
||||
s.insert(sectorId);
|
||||
sectorId++;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
IbmEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
IbmEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, location.logicalTrack, location.head);
|
||||
|
||||
int logicalSide = physicalSide ^ trackdata.swap_sides();
|
||||
for (int sectorId : getSectorIds(trackdata))
|
||||
int logicalSide = location.head ^ trackdata.swap_sides();
|
||||
for (int sectorId : getSectorIds(trackdata))
|
||||
{
|
||||
const auto& sector = image.get(physicalTrack, logicalSide, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
const auto& sector = image.get(location.logicalTrack, logicalSide, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
IbmEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
std::unique_ptr<Fluxmap> encode(
|
||||
const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
IbmEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, location.logicalTrack, location.head);
|
||||
|
||||
auto writeBytes = [&](const Bytes& bytes)
|
||||
{
|
||||
if (trackdata.use_fm())
|
||||
encodeFm(_bits, _cursor, bytes);
|
||||
else
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
};
|
||||
auto writeBytes = [&](const Bytes& bytes)
|
||||
{
|
||||
if (trackdata.use_fm())
|
||||
encodeFm(_bits, _cursor, bytes);
|
||||
else
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
};
|
||||
|
||||
auto writeFillerRawBytes = [&](int count, uint16_t byte)
|
||||
{
|
||||
for (int i=0; i<count; i++)
|
||||
writeRawBits(byte, 16);
|
||||
};
|
||||
auto writeFillerRawBytes = [&](int count, uint16_t byte)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
writeRawBits(byte, 16);
|
||||
};
|
||||
|
||||
auto writeFillerBytes = [&](int count, uint8_t byte)
|
||||
{
|
||||
Bytes b { byte };
|
||||
for (int i=0; i<count; i++)
|
||||
writeBytes(b);
|
||||
};
|
||||
auto writeFillerBytes = [&](int count, uint8_t byte)
|
||||
{
|
||||
Bytes b{byte};
|
||||
for (int i = 0; i < count; i++)
|
||||
writeBytes(b);
|
||||
};
|
||||
|
||||
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;
|
||||
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 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)
|
||||
{
|
||||
s >>= 1;
|
||||
sectorSize += 1;
|
||||
}
|
||||
}
|
||||
uint8_t sectorSize = 0;
|
||||
{
|
||||
int s = trackdata.sector_size() >> 7;
|
||||
while (s > 1)
|
||||
{
|
||||
s >>= 1;
|
||||
sectorSize += 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t gapFill = trackdata.gap_fill_byte();
|
||||
uint16_t gapFill = trackdata.gap_fill_byte();
|
||||
|
||||
writeFillerRawBytes(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);
|
||||
writeFillerRawBytes(trackdata.gap1(), gapFill);
|
||||
}
|
||||
writeFillerRawBytes(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);
|
||||
writeFillerRawBytes(trackdata.gap1(), gapFill);
|
||||
}
|
||||
|
||||
int logicalSide = physicalSide ^ trackdata.swap_sides();
|
||||
bool first = true;
|
||||
for (int sectorId : trackdata.sectors().sector())
|
||||
{
|
||||
if (!first)
|
||||
writeFillerRawBytes(trackdata.gap3(), gapFill);
|
||||
first = false;
|
||||
bool first = true;
|
||||
for (const auto& sectorData : sectors)
|
||||
{
|
||||
if (!first)
|
||||
writeFillerRawBytes(trackdata.gap3(), gapFill);
|
||||
first = false;
|
||||
|
||||
const auto& sectorData = image.get(physicalTrack, logicalSide, sectorId);
|
||||
if (!sectorData)
|
||||
continue;
|
||||
/* 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. */
|
||||
|
||||
/* 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);
|
||||
|
||||
{
|
||||
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);
|
||||
bw.write_8(sectorSize);
|
||||
uint16_t crc = crc16(CCITT_POLY, header);
|
||||
bw.write_be16(crc);
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
int conventionalHeaderStart = 0;
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_RECORD_SEPARATOR, 16);
|
||||
conventionalHeaderStart += 3;
|
||||
writeBytes(header.slice(conventionalHeaderStart));
|
||||
}
|
||||
|
||||
}
|
||||
writeRawBits(trackdata.idam_byte(), 16);
|
||||
conventionalHeaderStart += 1;
|
||||
writeFillerRawBytes(trackdata.gap2(), gapFill);
|
||||
|
||||
writeBytes(header.slice(conventionalHeaderStart));
|
||||
}
|
||||
{
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
|
||||
writeFillerRawBytes(trackdata.gap2(), gapFill);
|
||||
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 data;
|
||||
ByteWriter bw(data);
|
||||
Bytes truncatedData =
|
||||
sectorData->data.slice(0, trackdata.sector_size());
|
||||
bw += truncatedData;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
Bytes truncatedData = sectorData->data.slice(0, trackdata.sector_size());
|
||||
bw += truncatedData;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
writeBytes(data.slice(conventionalHeaderStart));
|
||||
}
|
||||
}
|
||||
|
||||
int conventionalHeaderStart = 0;
|
||||
if (!trackdata.use_fm())
|
||||
{
|
||||
for (int i=0; i<3; i++)
|
||||
writeRawBits(MFM_RECORD_SEPARATOR, 16);
|
||||
conventionalHeaderStart += 3;
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
while (_cursor < _bits.size())
|
||||
writeFillerRawBytes(1, gapFill);
|
||||
|
||||
}
|
||||
writeRawBits(trackdata.dam_byte(), 16);
|
||||
conventionalHeaderStart += 1;
|
||||
|
||||
writeBytes(data.slice(conventionalHeaderStart));
|
||||
}
|
||||
}
|
||||
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
while (_cursor < _bits.size())
|
||||
writeFillerRawBytes(1, gapFill);
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(_bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(_bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const IbmEncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
const IbmEncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createIbmEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new IbmEncoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new IbmEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
struct IbmIdam
|
||||
{
|
||||
uint8_t id;
|
||||
uint8_t cylinder;
|
||||
uint8_t track;
|
||||
uint8_t side;
|
||||
uint8_t sector;
|
||||
uint8_t sectorSize;
|
||||
|
||||
@@ -13,7 +13,7 @@ message IbmDecoderProto {
|
||||
optional int32 max_sector = 2 [(help) = "require these sectors to exist for a good read"];
|
||||
}
|
||||
|
||||
optional int32 cylinder = 7 [(help) = "if set, the format applies only to this track"];
|
||||
optional int32 track = 7 [(help) = "if set, the format applies only to this track"];
|
||||
optional int32 head = 8 [(help) = "if set, the format applies only to this head"];
|
||||
|
||||
optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
|
||||
@@ -42,7 +42,7 @@ message IbmEncoderProto {
|
||||
optional int32 max_sector = 2 [(help) = "write these sectors (in order) on each track"];
|
||||
}
|
||||
|
||||
optional int32 cylinder = 15 [(help) = "if set, the format applies only to this track"];
|
||||
optional int32 track = 15 [(help) = "if set, the format applies only to this track"];
|
||||
optional int32 head = 16 [(help) = "if set, the format applies only to this head"];
|
||||
|
||||
optional double track_length_ms = 1 [(help) = "length of track"];
|
||||
|
||||
@@ -144,7 +144,7 @@ public:
|
||||
auto header = toBytes(readRawBits(7*8)).slice(0, 7);
|
||||
|
||||
uint8_t encodedTrack = decode_data_gcr(header[0]);
|
||||
if (encodedTrack != (_sector->physicalCylinder & 0x3f))
|
||||
if (encodedTrack != (_sector->physicalTrack & 0x3f))
|
||||
return;
|
||||
|
||||
uint8_t encodedSector = decode_data_gcr(header[1]);
|
||||
@@ -155,7 +155,7 @@ public:
|
||||
if (encodedSector > 11)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = _sector->physicalCylinder;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = decode_side(encodedSide);
|
||||
_sector->logicalSector = encodedSector;
|
||||
uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
|
||||
@@ -183,16 +183,18 @@ public:
|
||||
_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12));
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
|
||||
std::set<unsigned> requiredSectors(const Location& location) const override
|
||||
{
|
||||
unsigned track = location.logicalTrack;
|
||||
|
||||
int count;
|
||||
if (cylinder < 16)
|
||||
if (track < 16)
|
||||
count = 12;
|
||||
else if (cylinder < 32)
|
||||
else if (track < 32)
|
||||
count = 11;
|
||||
else if (cylinder < 48)
|
||||
else if (track < 48)
|
||||
count = 10;
|
||||
else if (cylinder < 64)
|
||||
else if (track < 64)
|
||||
count = 9;
|
||||
else
|
||||
count = 8;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "encoders/encoders.h"
|
||||
#include "macintosh.h"
|
||||
#include "crc.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
@@ -14,44 +14,46 @@ static bool lastBit;
|
||||
|
||||
static double clockRateUsForTrack(unsigned track)
|
||||
{
|
||||
if (track < 16)
|
||||
return 2.623;
|
||||
if (track < 32)
|
||||
return 2.861;
|
||||
if (track < 48)
|
||||
return 3.148;
|
||||
if (track < 64)
|
||||
return 3.497;
|
||||
return 3.934;
|
||||
if (track < 16)
|
||||
return 2.623;
|
||||
if (track < 32)
|
||||
return 2.861;
|
||||
if (track < 48)
|
||||
return 3.148;
|
||||
if (track < 64)
|
||||
return 3.497;
|
||||
return 3.934;
|
||||
}
|
||||
|
||||
static unsigned sectorsForTrack(unsigned track)
|
||||
{
|
||||
if (track < 16)
|
||||
return 12;
|
||||
if (track < 32)
|
||||
return 11;
|
||||
if (track < 48)
|
||||
return 10;
|
||||
if (track < 64)
|
||||
return 9;
|
||||
return 8;
|
||||
if (track < 16)
|
||||
return 12;
|
||||
if (track < 32)
|
||||
return 11;
|
||||
if (track < 48)
|
||||
return 10;
|
||||
if (track < 64)
|
||||
return 9;
|
||||
return 8;
|
||||
}
|
||||
|
||||
static int encode_data_gcr(uint8_t gcr)
|
||||
{
|
||||
switch (gcr)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: \
|
||||
return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
|
||||
* and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
|
||||
/* This is extremely inspired by the MESS implementation, written by Nathan
|
||||
* Woods and R. Belmont:
|
||||
* https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
|
||||
*/
|
||||
static Bytes encode_crazy_data(const Bytes& input)
|
||||
{
|
||||
@@ -59,7 +61,7 @@ static Bytes encode_crazy_data(const Bytes& input)
|
||||
ByteWriter bw(output);
|
||||
ByteReader br(input);
|
||||
|
||||
uint8_t w1, w2, w3, w4;
|
||||
uint8_t w1, w2, w3, w4;
|
||||
|
||||
static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3;
|
||||
|
||||
@@ -67,92 +69,94 @@ static Bytes encode_crazy_data(const Bytes& input)
|
||||
uint8_t b2[LOOKUP_LEN + 1];
|
||||
uint8_t b3[LOOKUP_LEN + 1];
|
||||
|
||||
uint32_t c1 = 0;
|
||||
uint32_t c2 = 0;
|
||||
uint32_t c3 = 0;
|
||||
for (int j=0;; j++)
|
||||
{
|
||||
c1 = (c1 & 0xff) << 1;
|
||||
if (c1 & 0x0100)
|
||||
c1++;
|
||||
uint32_t c1 = 0;
|
||||
uint32_t c2 = 0;
|
||||
uint32_t c3 = 0;
|
||||
for (int j = 0;; j++)
|
||||
{
|
||||
c1 = (c1 & 0xff) << 1;
|
||||
if (c1 & 0x0100)
|
||||
c1++;
|
||||
|
||||
uint8_t val = br.read_8();
|
||||
c3 += val;
|
||||
if (c1 & 0x0100)
|
||||
{
|
||||
c3++;
|
||||
c1 &= 0xff;
|
||||
}
|
||||
b1[j] = (val ^ c1) & 0xff;
|
||||
uint8_t val = br.read_8();
|
||||
c3 += val;
|
||||
if (c1 & 0x0100)
|
||||
{
|
||||
c3++;
|
||||
c1 &= 0xff;
|
||||
}
|
||||
b1[j] = (val ^ c1) & 0xff;
|
||||
|
||||
val = br.read_8();
|
||||
c2 += val;
|
||||
if (c3 > 0xff)
|
||||
{
|
||||
c2++;
|
||||
c3 &= 0xff;
|
||||
}
|
||||
b2[j] = (val ^ c3) & 0xff;
|
||||
val = br.read_8();
|
||||
c2 += val;
|
||||
if (c3 > 0xff)
|
||||
{
|
||||
c2++;
|
||||
c3 &= 0xff;
|
||||
}
|
||||
b2[j] = (val ^ c3) & 0xff;
|
||||
|
||||
if (br.pos == 524)
|
||||
break;
|
||||
if (br.pos == 524)
|
||||
break;
|
||||
|
||||
val = br.read_8();
|
||||
c1 += val;
|
||||
if (c2 > 0xff)
|
||||
{
|
||||
c1++;
|
||||
c2 &= 0xff;
|
||||
}
|
||||
b3[j] = (val ^ c2) & 0xff;
|
||||
}
|
||||
uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2);
|
||||
b3[LOOKUP_LEN] = 0;
|
||||
val = br.read_8();
|
||||
c1 += val;
|
||||
if (c2 > 0xff)
|
||||
{
|
||||
c1++;
|
||||
c2 &= 0xff;
|
||||
}
|
||||
b3[j] = (val ^ c2) & 0xff;
|
||||
}
|
||||
uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2);
|
||||
b3[LOOKUP_LEN] = 0;
|
||||
|
||||
for (int i = 0; i <= LOOKUP_LEN; i++)
|
||||
{
|
||||
w1 = b1[i] & 0x3f;
|
||||
w2 = b2[i] & 0x3f;
|
||||
w3 = b3[i] & 0x3f;
|
||||
w4 = ((b1[i] & 0xc0) >> 2);
|
||||
w4 |= ((b2[i] & 0xc0) >> 4);
|
||||
w4 |= ((b3[i] & 0xc0) >> 6);
|
||||
for (int i = 0; i <= LOOKUP_LEN; i++)
|
||||
{
|
||||
w1 = b1[i] & 0x3f;
|
||||
w2 = b2[i] & 0x3f;
|
||||
w3 = b3[i] & 0x3f;
|
||||
w4 = ((b1[i] & 0xc0) >> 2);
|
||||
w4 |= ((b2[i] & 0xc0) >> 4);
|
||||
w4 |= ((b3[i] & 0xc0) >> 6);
|
||||
|
||||
bw.write_8(w4);
|
||||
bw.write_8(w1);
|
||||
bw.write_8(w2);
|
||||
bw.write_8(w4);
|
||||
bw.write_8(w1);
|
||||
bw.write_8(w2);
|
||||
|
||||
if (i != LOOKUP_LEN)
|
||||
bw.write_8(w3);
|
||||
}
|
||||
if (i != LOOKUP_LEN)
|
||||
bw.write_8(w3);
|
||||
}
|
||||
|
||||
bw.write_8(c4 & 0x3f);
|
||||
bw.write_8(c3 & 0x3f);
|
||||
bw.write_8(c2 & 0x3f);
|
||||
bw.write_8(c1 & 0x3f);
|
||||
bw.write_8(c4 & 0x3f);
|
||||
bw.write_8(c3 & 0x3f);
|
||||
bw.write_8(c2 & 0x3f);
|
||||
bw.write_8(c1 & 0x3f);
|
||||
|
||||
return output;
|
||||
return output;
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
|
||||
{
|
||||
for (bool bit : src)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
bits[cursor++] = bit;
|
||||
}
|
||||
for (bool bit : src)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
bits[cursor++] = bit;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
|
||||
{
|
||||
cursor += width;
|
||||
for (int i=0; i<width; i++)
|
||||
{
|
||||
unsigned pos = cursor - i - 1;
|
||||
if (pos < bits.size())
|
||||
bits[pos] = data & 1;
|
||||
data >>= 1;
|
||||
}
|
||||
cursor += width;
|
||||
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 encode_side(uint8_t track, uint8_t side)
|
||||
@@ -161,103 +165,116 @@ static uint8_t encode_side(uint8_t track, uint8_t side)
|
||||
* bit 5) and also whether we're above track 0x3f (in bit 0).
|
||||
*/
|
||||
|
||||
return (side ? 0x20 : 0x00) | ((track>0x3f) ? 0x01 : 0x00);
|
||||
return (side ? 0x20 : 0x00) | ((track > 0x3f) ? 0x01 : 0x00);
|
||||
}
|
||||
|
||||
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<const Sector>& sector)
|
||||
static void write_sector(std::vector<bool>& bits,
|
||||
unsigned& cursor,
|
||||
const std::shared_ptr<const Sector>& sector)
|
||||
{
|
||||
if ((sector->data.size() != 512) && (sector->data.size() != 524))
|
||||
Error() << "unsupported sector size --- you must pick 512 or 524";
|
||||
if ((sector->data.size() != 512) && (sector->data.size() != 524))
|
||||
Error() << "unsupported sector size --- you must pick 512 or 524";
|
||||
|
||||
write_bits(bits, cursor, 0xff, 1*8); /* pad byte */
|
||||
for (int i=0; i<7; i++)
|
||||
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
|
||||
write_bits(bits, cursor, MAC_SECTOR_RECORD, 3*8);
|
||||
write_bits(bits, cursor, 0xff, 1 * 8); /* pad byte */
|
||||
for (int i = 0; i < 7; i++)
|
||||
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */
|
||||
write_bits(bits, cursor, MAC_SECTOR_RECORD, 3 * 8);
|
||||
|
||||
uint8_t encodedTrack = sector->logicalTrack & 0x3f;
|
||||
uint8_t encodedSector = sector->logicalSector;
|
||||
uint8_t encodedSide = encode_side(sector->logicalTrack, sector->logicalSide);
|
||||
uint8_t formatByte = MAC_FORMAT_BYTE;
|
||||
uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
|
||||
uint8_t encodedSector = sector->logicalSector;
|
||||
uint8_t encodedSide =
|
||||
encode_side(sector->logicalTrack, sector->logicalSide);
|
||||
uint8_t formatByte = MAC_FORMAT_BYTE;
|
||||
uint8_t headerChecksum =
|
||||
(encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
|
||||
|
||||
write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1*8);
|
||||
write_bits(bits, cursor, encode_data_gcr(encodedSector), 1*8);
|
||||
write_bits(bits, cursor, encode_data_gcr(encodedSide), 1*8);
|
||||
write_bits(bits, cursor, encode_data_gcr(formatByte), 1*8);
|
||||
write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1*8);
|
||||
write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1 * 8);
|
||||
write_bits(bits, cursor, encode_data_gcr(encodedSector), 1 * 8);
|
||||
write_bits(bits, cursor, encode_data_gcr(encodedSide), 1 * 8);
|
||||
write_bits(bits, cursor, encode_data_gcr(formatByte), 1 * 8);
|
||||
write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1 * 8);
|
||||
|
||||
write_bits(bits, cursor, 0xdeaaff, 3*8);
|
||||
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
|
||||
write_bits(bits, cursor, MAC_DATA_RECORD, 3*8);
|
||||
write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1*8);
|
||||
write_bits(bits, cursor, 0xdeaaff, 3 * 8);
|
||||
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */
|
||||
write_bits(bits, cursor, MAC_DATA_RECORD, 3 * 8);
|
||||
write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1 * 8);
|
||||
|
||||
Bytes wireData;
|
||||
wireData.writer().append(sector->data.slice(512, 12)).append(sector->data.slice(0, 512));
|
||||
for (uint8_t b : encode_crazy_data(wireData))
|
||||
write_bits(bits, cursor, encode_data_gcr(b), 1*8);
|
||||
Bytes wireData;
|
||||
wireData.writer()
|
||||
.append(sector->data.slice(512, 12))
|
||||
.append(sector->data.slice(0, 512));
|
||||
for (uint8_t b : encode_crazy_data(wireData))
|
||||
write_bits(bits, cursor, encode_data_gcr(b), 1 * 8);
|
||||
|
||||
write_bits(bits, cursor, 0xdeaaff, 3*8);
|
||||
write_bits(bits, cursor, 0xdeaaff, 3 * 8);
|
||||
}
|
||||
|
||||
class MacintoshEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
MacintoshEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.macintosh())
|
||||
{}
|
||||
MacintoshEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.macintosh())
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
if ((physicalTrack >= 0) && (physicalTrack < MAC_TRACKS_PER_DISK))
|
||||
if ((location.logicalTrack >= 0) &&
|
||||
(location.logicalTrack < MAC_TRACKS_PER_DISK))
|
||||
{
|
||||
unsigned numSectors = sectorsForTrack(physicalTrack);
|
||||
for (int sectorId=0; sectorId<numSectors; sectorId++)
|
||||
unsigned numSectors = sectorsForTrack(location.logicalTrack);
|
||||
for (int sectorId = 0; sectorId < numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
|
||||
const auto& sector = image.get(
|
||||
location.logicalTrack, location.head, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
if ((physicalTrack < 0) || (physicalTrack >= MAC_TRACKS_PER_DISK))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
double clockRateUs = clockRateUsForTrack(location.logicalTrack) *
|
||||
_config.clock_compensation_factor();
|
||||
int bitsPerRevolution = 200000.0 / clockRateUs;
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
double clockRateUs = clockRateUsForTrack(physicalTrack) * _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;
|
||||
|
||||
fillBitmapTo(bits, cursor, _config.post_index_gap_us() / clockRateUs, { true, false });
|
||||
lastBit = false;
|
||||
for (const auto& sector : sectors)
|
||||
write_sector(bits, cursor, sector);
|
||||
|
||||
for (const auto& sector : sectors)
|
||||
write_sector(bits, cursor, sector);
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format(
|
||||
"track data overrun by {} bits", cursor - bits.size());
|
||||
fillBitmapTo(bits, cursor, bits.size(), {true, false});
|
||||
|
||||
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<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const MacintoshEncoderProto& _config;
|
||||
const MacintoshEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createMacintoshEncoder(const EncoderProto& config)
|
||||
std::unique_ptr<AbstractEncoder> createMacintoshEncoder(
|
||||
const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new MacintoshEncoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new MacintoshEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ public:
|
||||
return;
|
||||
if (_sector->logicalTrack > 76)
|
||||
return;
|
||||
if (_sector->logicalTrack != _sector->physicalCylinder)
|
||||
if (_sector->logicalTrack != _sector->physicalTrack)
|
||||
return;
|
||||
|
||||
br.read(10); /* OS data or padding */
|
||||
@@ -173,7 +173,7 @@ public:
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
|
||||
std::set<unsigned> requiredSectors(const Location& location) const override
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
|
||||
return sectors;
|
||||
|
||||
@@ -6,116 +6,123 @@
|
||||
#include "image.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
|
||||
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<const Sector>& sector)
|
||||
static void write_sector(std::vector<bool>& bits,
|
||||
unsigned& cursor,
|
||||
const std::shared_ptr<const Sector>& sector)
|
||||
{
|
||||
if ((sector->data.size() != 256) && (sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE))
|
||||
Error() << "unsupported sector size --- you must pick 256 or 275";
|
||||
if ((sector->data.size() != 256) &&
|
||||
(sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE))
|
||||
Error() << "unsupported sector size --- you must pick 256 or 275";
|
||||
|
||||
int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35;
|
||||
auto fullSector = std::make_shared<std::vector<uint8_t>>();
|
||||
fullSector->reserve(fullSectorSize);
|
||||
/* sector preamble */
|
||||
for (int i=0; i<40; i++)
|
||||
fullSector->push_back(0);
|
||||
Bytes sectorData;
|
||||
if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
|
||||
{
|
||||
if (sector->data[0] != 0xFF)
|
||||
Error() << "275 byte sector doesn't start with sync byte 0xFF. Corrupted sector";
|
||||
uint8_t wantChecksum = sector->data[1+2+266];
|
||||
uint8_t gotChecksum = micropolisChecksum(sector->data.slice(1, 2+266));
|
||||
if (wantChecksum != gotChecksum)
|
||||
std::cerr << "Warning: checksum incorrect. Sector: " << sector->logicalSector << std::endl;
|
||||
sectorData = sector->data;
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteWriter writer(sectorData);
|
||||
writer.write_8(0xff); /* Sync */
|
||||
writer.write_8(sector->logicalTrack);
|
||||
writer.write_8(sector->logicalSector);
|
||||
for (int i=0; i<10; i++)
|
||||
writer.write_8(0); /* Padding */
|
||||
writer += sector->data;
|
||||
writer.write_8(micropolisChecksum(sectorData.slice(1)));
|
||||
for (int i=0; i<5; i++)
|
||||
writer.write_8(0); /* 4 byte ECC and ECC not present flag */
|
||||
}
|
||||
for (uint8_t b : sectorData)
|
||||
fullSector->push_back(b);
|
||||
/* sector postamble */
|
||||
for (int i=0; i<40; i++)
|
||||
fullSector->push_back(0);
|
||||
/* filler */
|
||||
for (int i=0; i<35; i++)
|
||||
fullSector->push_back(0);
|
||||
int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35;
|
||||
auto fullSector = std::make_shared<std::vector<uint8_t>>();
|
||||
fullSector->reserve(fullSectorSize);
|
||||
/* sector preamble */
|
||||
for (int i = 0; i < 40; i++)
|
||||
fullSector->push_back(0);
|
||||
Bytes sectorData;
|
||||
if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
|
||||
{
|
||||
if (sector->data[0] != 0xFF)
|
||||
Error() << "275 byte sector doesn't start with sync byte 0xFF. "
|
||||
"Corrupted sector";
|
||||
uint8_t wantChecksum = sector->data[1 + 2 + 266];
|
||||
uint8_t gotChecksum =
|
||||
micropolisChecksum(sector->data.slice(1, 2 + 266));
|
||||
if (wantChecksum != gotChecksum)
|
||||
std::cerr << "Warning: checksum incorrect. Sector: "
|
||||
<< sector->logicalSector << std::endl;
|
||||
sectorData = sector->data;
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteWriter writer(sectorData);
|
||||
writer.write_8(0xff); /* Sync */
|
||||
writer.write_8(sector->logicalTrack);
|
||||
writer.write_8(sector->logicalSector);
|
||||
for (int i = 0; i < 10; i++)
|
||||
writer.write_8(0); /* Padding */
|
||||
writer += sector->data;
|
||||
writer.write_8(micropolisChecksum(sectorData.slice(1)));
|
||||
for (int i = 0; i < 5; i++)
|
||||
writer.write_8(0); /* 4 byte ECC and ECC not present flag */
|
||||
}
|
||||
for (uint8_t b : sectorData)
|
||||
fullSector->push_back(b);
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < 40; i++)
|
||||
fullSector->push_back(0);
|
||||
/* filler */
|
||||
for (int i = 0; i < 35; i++)
|
||||
fullSector->push_back(0);
|
||||
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length";
|
||||
bool lastBit = false;
|
||||
encodeMfm(bits, cursor, fullSector, lastBit);
|
||||
/* filler */
|
||||
for (int i=0; i<5; i++)
|
||||
{
|
||||
bits[cursor++] = 1;
|
||||
bits[cursor++] = 0;
|
||||
}
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length";
|
||||
bool lastBit = false;
|
||||
encodeMfm(bits, cursor, fullSector, lastBit);
|
||||
/* filler */
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
bits[cursor++] = 1;
|
||||
bits[cursor++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class MicropolisEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
MicropolisEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.micropolis())
|
||||
{}
|
||||
MicropolisEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.micropolis())
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
if ((physicalTrack >= 0) && (physicalTrack < 77))
|
||||
{
|
||||
for (int sectorId = 0; sectorId < 16; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
if ((location.logicalTrack >= 0) && (location.logicalTrack < 77))
|
||||
{
|
||||
for (int sectorId = 0; sectorId < 16; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(
|
||||
location.logicalTrack, location.head, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 2.00;
|
||||
std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 2.00;
|
||||
|
||||
if ((physicalTrack < 0) || (physicalTrack >= 77) || sectors.empty())
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
for (const auto& sectorData : sectors)
|
||||
write_sector(bits, cursor, sectorData);
|
||||
|
||||
for (const auto& sectorData : sectors)
|
||||
write_sector(bits, cursor, sectorData);
|
||||
if (cursor != bits.size())
|
||||
Error() << "track data mismatched length";
|
||||
|
||||
if (cursor != bits.size())
|
||||
Error() << "track data mismatched length";
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const MicropolisEncoderProto& _config;
|
||||
const MicropolisEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createMicropolisEncoder(const EncoderProto& config)
|
||||
std::unique_ptr<AbstractEncoder> createMicropolisEncoder(
|
||||
const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new MicropolisEncoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new MicropolisEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
gotChecksum += br.read_be16();
|
||||
uint16_t wantChecksum = br.read_be16();
|
||||
|
||||
_sector->logicalTrack = _sector->physicalCylinder;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = _sector->physicalHead;
|
||||
_sector->logicalSector = _currentSector;
|
||||
_sector->data = bytes.slice(0, SECTOR_SIZE).swab();
|
||||
|
||||
@@ -152,7 +152,7 @@ public:
|
||||
|
||||
_sector->logicalSide = _sector->physicalHead;
|
||||
_sector->logicalSector = _hardSectorId;
|
||||
_sector->logicalTrack = _sector->physicalCylinder;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
|
||||
if (headerSize == NORTHSTAR_HEADER_SIZE_DD) {
|
||||
br.read_8(); /* MFM second Sync char, usually 0xFB */
|
||||
@@ -164,7 +164,7 @@ public:
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
|
||||
std::set<unsigned> requiredSectors(const Location&) const override
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
return sectors;
|
||||
|
||||
@@ -12,155 +12,178 @@
|
||||
#define GAP_FILL_SIZE_DD 62
|
||||
#define PRE_HEADER_GAP_FILL_SIZE_DD 16
|
||||
|
||||
#define GAP1_FILL_BYTE (0x4F)
|
||||
#define GAP2_FILL_BYTE (0x4F)
|
||||
#define GAP1_FILL_BYTE (0x4F)
|
||||
#define GAP2_FILL_BYTE (0x4F)
|
||||
|
||||
#define TOTAL_SECTOR_BYTES ()
|
||||
|
||||
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<const Sector>& sector)
|
||||
static void write_sector(std::vector<bool>& bits,
|
||||
unsigned& cursor,
|
||||
const std::shared_ptr<const Sector>& sector)
|
||||
{
|
||||
int preambleSize = 0;
|
||||
int encodedSectorSize = 0;
|
||||
int gapFillSize = 0;
|
||||
int preHeaderGapFillSize = 0;
|
||||
int preambleSize = 0;
|
||||
int encodedSectorSize = 0;
|
||||
int gapFillSize = 0;
|
||||
int preHeaderGapFillSize = 0;
|
||||
|
||||
bool doubleDensity;
|
||||
bool doubleDensity;
|
||||
|
||||
switch (sector->data.size()) {
|
||||
case NORTHSTAR_PAYLOAD_SIZE_SD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + NORTHSTAR_ENCODED_SECTOR_SIZE_SD + GAP_FILL_SIZE_SD;
|
||||
gapFillSize = GAP_FILL_SIZE_SD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD;
|
||||
doubleDensity = false;
|
||||
break;
|
||||
case NORTHSTAR_PAYLOAD_SIZE_DD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + NORTHSTAR_ENCODED_SECTOR_SIZE_DD + GAP_FILL_SIZE_DD;
|
||||
gapFillSize = GAP_FILL_SIZE_DD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD;
|
||||
doubleDensity = true;
|
||||
break;
|
||||
default:
|
||||
Error() << "unsupported sector size --- you must pick 256 or 512";
|
||||
break;
|
||||
}
|
||||
switch (sector->data.size())
|
||||
{
|
||||
case NORTHSTAR_PAYLOAD_SIZE_SD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD +
|
||||
NORTHSTAR_ENCODED_SECTOR_SIZE_SD +
|
||||
GAP_FILL_SIZE_SD;
|
||||
gapFillSize = GAP_FILL_SIZE_SD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD;
|
||||
doubleDensity = false;
|
||||
break;
|
||||
case NORTHSTAR_PAYLOAD_SIZE_DD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD +
|
||||
NORTHSTAR_ENCODED_SECTOR_SIZE_DD +
|
||||
GAP_FILL_SIZE_DD;
|
||||
gapFillSize = GAP_FILL_SIZE_DD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD;
|
||||
doubleDensity = true;
|
||||
break;
|
||||
default:
|
||||
Error() << "unsupported sector size --- you must pick 256 or 512";
|
||||
break;
|
||||
}
|
||||
|
||||
int fullSectorSize = preambleSize + encodedSectorSize;
|
||||
auto fullSector = std::make_shared<std::vector<uint8_t>>();
|
||||
fullSector->reserve(fullSectorSize);
|
||||
int fullSectorSize = preambleSize + encodedSectorSize;
|
||||
auto fullSector = std::make_shared<std::vector<uint8_t>>();
|
||||
fullSector->reserve(fullSectorSize);
|
||||
|
||||
/* sector gap after index pulse */
|
||||
for (int i = 0; i < preHeaderGapFillSize; i++)
|
||||
fullSector->push_back(GAP1_FILL_BYTE);
|
||||
/* sector gap after index pulse */
|
||||
for (int i = 0; i < preHeaderGapFillSize; i++)
|
||||
fullSector->push_back(GAP1_FILL_BYTE);
|
||||
|
||||
/* sector preamble */
|
||||
for (int i = 0; i < preambleSize; i++)
|
||||
fullSector->push_back(0);
|
||||
/* sector preamble */
|
||||
for (int i = 0; i < preambleSize; i++)
|
||||
fullSector->push_back(0);
|
||||
|
||||
Bytes sectorData;
|
||||
if (sector->data.size() == encodedSectorSize)
|
||||
sectorData = sector->data;
|
||||
else {
|
||||
ByteWriter writer(sectorData);
|
||||
writer.write_8(0xFB); /* sync character */
|
||||
if (doubleDensity == true) {
|
||||
writer.write_8(0xFB); /* Double-density has two sync characters */
|
||||
}
|
||||
writer += sector->data;
|
||||
if (doubleDensity == true) {
|
||||
writer.write_8(northstarChecksum(sectorData.slice(2)));
|
||||
} else {
|
||||
writer.write_8(northstarChecksum(sectorData.slice(1)));
|
||||
}
|
||||
}
|
||||
for (uint8_t b : sectorData)
|
||||
fullSector->push_back(b);
|
||||
Bytes sectorData;
|
||||
if (sector->data.size() == encodedSectorSize)
|
||||
sectorData = sector->data;
|
||||
else
|
||||
{
|
||||
ByteWriter writer(sectorData);
|
||||
writer.write_8(0xFB); /* sync character */
|
||||
if (doubleDensity == true)
|
||||
{
|
||||
writer.write_8(0xFB); /* Double-density has two sync characters */
|
||||
}
|
||||
writer += sector->data;
|
||||
if (doubleDensity == true)
|
||||
{
|
||||
writer.write_8(northstarChecksum(sectorData.slice(2)));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.write_8(northstarChecksum(sectorData.slice(1)));
|
||||
}
|
||||
}
|
||||
for (uint8_t b : sectorData)
|
||||
fullSector->push_back(b);
|
||||
|
||||
if (sector->logicalSector != 9) {
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
if (sector->logicalSector != 9)
|
||||
{
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length (" << sector->data.size() << ") expected: " << fullSector->size() << " got " << fullSectorSize;
|
||||
} else {
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
}
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length (" << sector->data.size()
|
||||
<< ") expected: " << fullSector->size() << " got "
|
||||
<< fullSectorSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
}
|
||||
|
||||
bool lastBit = false;
|
||||
bool lastBit = false;
|
||||
|
||||
if (doubleDensity == true) {
|
||||
encodeMfm(bits, cursor, fullSector, lastBit);
|
||||
}
|
||||
else {
|
||||
encodeFm(bits, cursor, fullSector);
|
||||
}
|
||||
if (doubleDensity == true)
|
||||
{
|
||||
encodeMfm(bits, cursor, fullSector, lastBit);
|
||||
}
|
||||
else
|
||||
{
|
||||
encodeFm(bits, cursor, fullSector);
|
||||
}
|
||||
}
|
||||
|
||||
class NorthstarEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
NorthstarEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.northstar())
|
||||
{}
|
||||
NorthstarEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.northstar())
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
if ((physicalTrack >= 0) && (physicalTrack < 35))
|
||||
{
|
||||
for (int sectorId = 0; sectorId < 10; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
if ((location.logicalTrack >= 0) && (location.logicalTrack < 35))
|
||||
{
|
||||
for (int sectorId = 0; sectorId < 10; sectorId++)
|
||||
{
|
||||
const auto& sector = image.get(
|
||||
location.logicalTrack, location.head, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 4.00;
|
||||
std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 4.00;
|
||||
|
||||
if ((physicalTrack < 0) || (physicalTrack >= 35) || sectors.empty())
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
const auto& sector = *sectors.begin();
|
||||
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD)
|
||||
{
|
||||
bitsPerRevolution /= 2; // FM
|
||||
}
|
||||
else
|
||||
{
|
||||
clockRateUs /= 2.00;
|
||||
}
|
||||
|
||||
const auto& sector = *sectors.begin();
|
||||
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
|
||||
bitsPerRevolution /= 2; // FM
|
||||
} else {
|
||||
clockRateUs /= 2.00;
|
||||
}
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
for (const auto& sectorData : sectors)
|
||||
write_sector(bits, cursor, sectorData);
|
||||
|
||||
for (const auto& sectorData : sectors)
|
||||
write_sector(bits, cursor, sectorData);
|
||||
if (cursor > bits.size())
|
||||
Error() << "track data overrun";
|
||||
|
||||
if (cursor > bits.size())
|
||||
Error() << "track data overrun";
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const NorthstarEncoderProto& _config;
|
||||
const NorthstarEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createNorthstarEncoder(const EncoderProto& config)
|
||||
std::unique_ptr<AbstractEncoder> createNorthstarEncoder(
|
||||
const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new NorthstarEncoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new NorthstarEncoder(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "encoders/encoders.h"
|
||||
#include "tids990.h"
|
||||
#include "crc.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "arch/tids990/tids990.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
@@ -11,158 +11,157 @@
|
||||
|
||||
static int charToInt(char c)
|
||||
{
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
return 10 + tolower(c) - 'a';
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
return 10 + tolower(c) - 'a';
|
||||
}
|
||||
|
||||
static uint8_t decodeUint16(uint16_t raw)
|
||||
{
|
||||
Bytes b;
|
||||
ByteWriter bw(b);
|
||||
bw.write_be16(raw);
|
||||
return decodeFmMfm(b.toBits())[0];
|
||||
Bytes b;
|
||||
ByteWriter bw(b);
|
||||
bw.write_be16(raw);
|
||||
return decodeFmMfm(b.toBits())[0];
|
||||
}
|
||||
|
||||
class Tids990Encoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
Tids990Encoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.tids990())
|
||||
{}
|
||||
Tids990Encoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.tids990())
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void 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 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 writeBytes(const Bytes& bytes)
|
||||
{
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
}
|
||||
void writeBytes(const Bytes& bytes)
|
||||
{
|
||||
encodeMfm(_bits, _cursor, bytes, _lastBit);
|
||||
}
|
||||
|
||||
void writeBytes(int count, uint8_t byte)
|
||||
{
|
||||
Bytes bytes = { byte };
|
||||
for (int i=0; i<count; i++)
|
||||
writeBytes(bytes);
|
||||
}
|
||||
void writeBytes(int count, uint8_t byte)
|
||||
{
|
||||
Bytes bytes = {byte};
|
||||
for (int i = 0; i < count; i++)
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
for (char sectorChar : _config.sector_skew())
|
||||
for (char sectorChar : _config.sector_skew())
|
||||
{
|
||||
int sectorId = charToInt(sectorChar);
|
||||
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
int sectorId = charToInt(sectorChar);
|
||||
const auto& sector =
|
||||
image.get(location.logicalTrack, location.head, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
double clockRateUs = 1e3 / _config.clock_rate_khz() / 2.0;
|
||||
int bitsPerRevolution = (_config.track_length_ms() * 1000.0) / clockRateUs;
|
||||
_bits.resize(bitsPerRevolution);
|
||||
_cursor = 0;
|
||||
std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
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());
|
||||
uint8_t am1Unencoded = decodeUint16(_config.am1_byte());
|
||||
uint8_t am2Unencoded = decodeUint16(_config.am2_byte());
|
||||
|
||||
writeBytes(_config.gap1_bytes(), 0x55);
|
||||
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;
|
||||
bool first = true;
|
||||
for (const auto& sectorData : sectors)
|
||||
{
|
||||
if (!first)
|
||||
writeBytes(_config.gap3_bytes(), 0x55);
|
||||
first = false;
|
||||
|
||||
const auto& sectorData = image.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. */
|
||||
|
||||
/* 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);
|
||||
|
||||
{
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
writeRawBits(_config.am1_byte(), 16);
|
||||
writeBytes(header.slice(1));
|
||||
}
|
||||
writeBytes(_config.gap2_bytes(), 0x55);
|
||||
|
||||
writeBytes(_config.gap2_bytes(), 0x55);
|
||||
{
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
|
||||
{
|
||||
Bytes data;
|
||||
ByteWriter bw(data);
|
||||
writeBytes(12, 0x55);
|
||||
bw.write_8(am2Unencoded);
|
||||
|
||||
writeBytes(12, 0x55);
|
||||
bw.write_8(am2Unencoded);
|
||||
bw += sectorData->data;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
|
||||
bw += sectorData->data;
|
||||
uint16_t crc = crc16(CCITT_POLY, data);
|
||||
bw.write_be16(crc);
|
||||
writeRawBits(_config.am2_byte(), 16);
|
||||
writeBytes(data.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
writeRawBits(_config.am2_byte(), 16);
|
||||
writeBytes(data.slice(1));
|
||||
}
|
||||
}
|
||||
if (_cursor >= _bits.size())
|
||||
Error() << "track data overrun";
|
||||
while (_cursor < _bits.size())
|
||||
writeBytes(1, 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;
|
||||
}
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(_bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const Tids990EncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
const Tids990EncoderProto& _config;
|
||||
std::vector<bool> _bits;
|
||||
unsigned _cursor;
|
||||
bool _lastBit;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createTids990Encoder(const EncoderProto& config)
|
||||
std::unique_ptr<AbstractEncoder> createTids990Encoder(
|
||||
const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new Tids990Encoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new Tids990Encoder(config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "victor9k.h"
|
||||
#include "crc.h"
|
||||
#include "sector.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "arch/victor9k/victor9k.pb.h"
|
||||
@@ -14,77 +14,84 @@
|
||||
|
||||
static bool lastBit;
|
||||
|
||||
static void write_zero_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
|
||||
static void write_zero_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, unsigned count)
|
||||
{
|
||||
while (count--)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = 0;
|
||||
}
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_one_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
|
||||
static void write_one_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, unsigned count)
|
||||
{
|
||||
while (count--)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = 1;
|
||||
}
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
|
||||
{
|
||||
for (bool bit : src)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = bit;
|
||||
}
|
||||
for (bool bit : src)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = bit;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, uint64_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;
|
||||
}
|
||||
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 void write_bits(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
static void write_bits(
|
||||
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
BitReader bitr(br);
|
||||
ByteReader br(bytes);
|
||||
BitReader bitr(br);
|
||||
|
||||
while (!bitr.eof())
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
bits[cursor++] = bitr.get();
|
||||
}
|
||||
while (!bitr.eof())
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
bits[cursor++] = bitr.get();
|
||||
}
|
||||
}
|
||||
|
||||
static int encode_data_gcr(uint8_t data)
|
||||
{
|
||||
switch (data & 0x0f)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case data: \
|
||||
return gcr;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void write_byte(std::vector<bool>& bits, unsigned& cursor, uint8_t b)
|
||||
{
|
||||
write_bits(bits, cursor, encode_data_gcr(b>>4), 5);
|
||||
write_bits(bits, cursor, encode_data_gcr(b), 5);
|
||||
write_bits(bits, cursor, encode_data_gcr(b >> 4), 5);
|
||||
write_bits(bits, cursor, encode_data_gcr(b), 5);
|
||||
}
|
||||
|
||||
static void write_bytes(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
static void write_bytes(
|
||||
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
{
|
||||
for (uint8_t b : bytes)
|
||||
write_byte(bits, cursor, b);
|
||||
@@ -92,24 +99,27 @@ static void write_bytes(std::vector<bool>& bits, unsigned& cursor, const Bytes&
|
||||
|
||||
static void write_gap(std::vector<bool>& bits, unsigned& cursor, int length)
|
||||
{
|
||||
for (int i = 0; i < length/10; i++)
|
||||
for (int i = 0; i < length / 10; i++)
|
||||
write_byte(bits, cursor, '0');
|
||||
}
|
||||
|
||||
static void write_sector(std::vector<bool>& bits, unsigned& cursor,
|
||||
const Victor9kEncoderProto::TrackdataProto& trackdata,
|
||||
const Sector& sector)
|
||||
static void write_sector(std::vector<bool>& bits,
|
||||
unsigned& cursor,
|
||||
const Victor9kEncoderProto::TrackdataProto& trackdata,
|
||||
const Sector& sector)
|
||||
{
|
||||
write_one_bits(bits, cursor, trackdata.pre_header_sync_bits());
|
||||
write_bits(bits, cursor, VICTOR9K_SECTOR_RECORD, 10);
|
||||
|
||||
uint8_t encodedTrack = sector.logicalTrack | (sector.logicalSide<<7);
|
||||
uint8_t encodedTrack = sector.logicalTrack | (sector.logicalSide << 7);
|
||||
uint8_t encodedSector = sector.logicalSector;
|
||||
write_bytes(bits, cursor, Bytes {
|
||||
encodedTrack,
|
||||
encodedSector,
|
||||
(uint8_t)(encodedTrack + encodedSector),
|
||||
});
|
||||
write_bytes(bits,
|
||||
cursor,
|
||||
Bytes{
|
||||
encodedTrack,
|
||||
encodedSector,
|
||||
(uint8_t)(encodedTrack + encodedSector),
|
||||
});
|
||||
|
||||
write_gap(bits, cursor, trackdata.post_header_gap_bits());
|
||||
|
||||
@@ -127,82 +137,92 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor,
|
||||
class Victor9kEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
Victor9kEncoder(const EncoderProto& config):
|
||||
Victor9kEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.victor9k())
|
||||
{}
|
||||
_config(config.victor9k())
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void getTrackFormat(Victor9kEncoderProto::TrackdataProto& trackdata,
|
||||
unsigned track,
|
||||
unsigned head)
|
||||
{
|
||||
trackdata.Clear();
|
||||
for (const auto& f : _config.trackdata())
|
||||
{
|
||||
if (f.has_min_track() && (track < f.min_track()))
|
||||
continue;
|
||||
if (f.has_max_track() && (track > f.max_track()))
|
||||
continue;
|
||||
if (f.has_head() && (head != f.head()))
|
||||
continue;
|
||||
|
||||
void getTrackFormat(Victor9kEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
|
||||
{
|
||||
trackdata.Clear();
|
||||
for (const auto& f : _config.trackdata())
|
||||
{
|
||||
if (f.has_min_cylinder() && (cylinder < f.min_cylinder()))
|
||||
continue;
|
||||
if (f.has_max_cylinder() && (cylinder > f.max_cylinder()))
|
||||
continue;
|
||||
if (f.has_head() && (head != f.head()))
|
||||
continue;
|
||||
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
const Location& location, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
Victor9kEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
Victor9kEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, location.logicalTrack, location.head);
|
||||
|
||||
for (int i = 0; i < trackdata.sector_range().sector_count(); i++)
|
||||
{
|
||||
int sectorId = trackdata.sector_range().start_sector() + i;
|
||||
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
const auto& sector =
|
||||
image.get(location.logicalTrack, location.head, sectorId);
|
||||
if (sector)
|
||||
sectors.push_back(sector);
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
|
||||
std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) override
|
||||
{
|
||||
Victor9kEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
Victor9kEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, location.logicalTrack, location.head);
|
||||
|
||||
unsigned bitsPerRevolution = trackdata.original_data_rate_khz() * trackdata.original_period_ms();
|
||||
unsigned bitsPerRevolution =
|
||||
trackdata.original_data_rate_khz() * trackdata.original_period_ms();
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
double clockRateUs = 166666.0 / bitsPerRevolution;
|
||||
unsigned cursor = 0;
|
||||
|
||||
fillBitmapTo(bits, cursor, trackdata.post_index_gap_us() / clockRateUs, { true, false });
|
||||
fillBitmapTo(bits,
|
||||
cursor,
|
||||
trackdata.post_index_gap_us() / clockRateUs,
|
||||
{true, false});
|
||||
lastBit = false;
|
||||
|
||||
for (const auto& sector : sectors)
|
||||
write_sector(bits, cursor, trackdata, *sector);
|
||||
|
||||
if (cursor >= bits.size())
|
||||
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
|
||||
fillBitmapTo(bits, cursor, bits.size(), { true, false });
|
||||
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);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const Victor9kEncoderProto& _config;
|
||||
const Victor9kEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createVictor9kEncoder(const EncoderProto& config)
|
||||
std::unique_ptr<AbstractEncoder> createVictor9kEncoder(
|
||||
const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new Victor9kEncoder(config));
|
||||
return std::unique_ptr<AbstractEncoder>(new Victor9kEncoder(config));
|
||||
}
|
||||
|
||||
// vim: sw=4 ts=4 et
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ message Victor9kEncoderProto {
|
||||
optional int32 sector_count = 2 [(help) = "number of sectors on track"];
|
||||
}
|
||||
|
||||
optional int32 min_cylinder = 1 [(help) = "minimum cylinder this format applies to"];
|
||||
optional int32 max_cylinder = 2 [(help) = "maximum cylinder this format applies to"];
|
||||
optional int32 min_track = 1 [(help) = "minimum track this format applies to"];
|
||||
optional int32 max_track = 2 [(help) = "maximum track this format applies to"];
|
||||
optional int32 head = 3 [(help) = "which head this format applies to"];
|
||||
|
||||
optional double original_period_ms = 4 [(help) = "original rotational period of this cylinder"];
|
||||
optional double original_data_rate_khz = 5 [(help) = "original data rate of this cylinder"];
|
||||
optional double original_period_ms = 4 [(help) = "original rotational period of this track"];
|
||||
optional double original_data_rate_khz = 5 [(help) = "original data rate of this track"];
|
||||
optional double post_index_gap_us = 6 [(help) = "size of post-index gap"];
|
||||
optional int32 pre_header_sync_bits = 10 [(help) = "number of sync bits before the sector header"];
|
||||
optional int32 pre_data_sync_bits = 8 [(help) = "number of sync bits before the sector data"];
|
||||
|
||||
90
doc/drives.md
Normal file
90
doc/drives.md
Normal file
@@ -0,0 +1,90 @@
|
||||
Configuring for your drive
|
||||
==========================
|
||||
|
||||
By default, the FluxEngine client assumes you have a PC 80 track double sided
|
||||
high density drive, either 3.5" or 5.25", as these are the most common. This
|
||||
may not be the case, and you need to tell the FluxEngine client what kind of
|
||||
drive you have.
|
||||
|
||||
Forty track formats on an eighty track drive
|
||||
--------------------------------------------
|
||||
|
||||
Forty-track drives have the same geometry as eighty-track drives, but have a
|
||||
head that is twice as big, so halving the number of tracks on the disk.
|
||||
Examples of forty track drives include the Commodore 1541, IBM 360kB, or the
|
||||
Brother 120kB format (which uses a rare 3.5" single-sided forty-track drive).
|
||||
|
||||
When a forty-track disk is inserted into an eighty-track drive, then each head
|
||||
position will only see _half_ of each track. For reading this isn't a problem
|
||||
--- FluxEngine will actually read both halves and combine the results --- but
|
||||
writing is more problematic. Traditionally, if you wanted to write a
|
||||
forty-track disk in an eighty-track drive, you had to use a brand new disk; the
|
||||
drive would write to one half of the track, leaving the other half blank. If
|
||||
both halves contained data, then the wider head on a forty track drive would
|
||||
pick both up, producing a garbled result. This led to a very confusing
|
||||
situation where forty-track disks written on an eighty-track drive would read
|
||||
and write fine on an eight-track drive but wouldn't work at all on a
|
||||
forty-track drive.
|
||||
|
||||
FluxEngine is capable of both reading and writing forty-track formats on an
|
||||
eighty-track drive. It avoids the situation described above by writing one half
|
||||
of the track and then magnetically erasing the other half. This does produce a
|
||||
weaker signal on the disk, but in my testing the disks work just fine in
|
||||
forty-track drives.
|
||||
|
||||
Forty track formats on a forty track drive
|
||||
------------------------------------------
|
||||
|
||||
If you actually have a forty track drive, you need to tell FluxEngine. This is
|
||||
done by adding the special profile `40track_drive`:
|
||||
|
||||
```
|
||||
fluxengine write ibm360_525 40track_drive -i image.img -d drive:0
|
||||
```
|
||||
|
||||
It should then Just Work. This is supported by both FluxEngine and GreaseWeazle
|
||||
hardware.
|
||||
|
||||
Obviously you can't write an eighty-track format using a forty-track drive!
|
||||
|
||||
Apple II drives
|
||||
---------------
|
||||
|
||||
The Apple II had special drives which supported microstepping: when commanded
|
||||
to move the head, then instead of moving in single-track steps as is done in
|
||||
most other drives, the Apple II drive would move in quarter-track steps. This
|
||||
allowed much less precise head alignment, as small errors could be corrected in
|
||||
software. (The Brother word processor drives were similar.) The bus interface
|
||||
is different from normal PC drives.
|
||||
|
||||
The FluxEngine client supports these with the `apple2_drive` profile:
|
||||
|
||||
```
|
||||
fluxengine write apple2 apple2_drive -i image.img -d drive:0
|
||||
```
|
||||
|
||||
This is supported only by GreaseWeazle hardware.
|
||||
|
||||
Shugart drives
|
||||
--------------
|
||||
|
||||
PC drives have a standard interface which doesn't really have a name but is
|
||||
commonly referred to as 'the PC 34-pin interface'. There are a few other
|
||||
interfaces, most notably the Shugart standard. This is also 34 pin and is very
|
||||
similar to the PC interface but isn't quite electrically compatible. It
|
||||
supports up to four drives on a bus, unlike the PC interface's two drives, but
|
||||
the drives must be jumpered to configure them. This was mostly used by older
|
||||
3.5" drives, such as those on the Atari ST. [the How It Works
|
||||
page](technical.md) for the pinout.
|
||||
|
||||
The FluxEngine client supports these with the `shugart_drive` profile:
|
||||
|
||||
```
|
||||
fluxengine write atarist720 shugart_drive -i image.img -d drive:0
|
||||
```
|
||||
|
||||
(If you have a 40-track Shugart drive, use _both_ `shugart_drive` and
|
||||
`40track_drive`.)
|
||||
|
||||
This is supported only by GreaseWeazle hardware.
|
||||
|
||||
@@ -88,6 +88,54 @@ GreaseWeazle hardware interchangeably with FluxEngine hardware. See the
|
||||
[dedicated page](greaseweazle.md) for more information.
|
||||
|
||||
|
||||
## Drive interface pinouts
|
||||
|
||||
For reference, here are the FDC pinouts:
|
||||
|
||||
```ditaa
|
||||
:-E -s 0.75
|
||||
|
||||
+--+--+ +--+--+
|
||||
DISKCHG ---+34+33+ DISKCHG ---+34+33+
|
||||
+--+--+ +--+--+
|
||||
SIDE1 ---+32+31+ SIDE1 ---+32+31+
|
||||
+--+--+ +--+--+
|
||||
RDATA ---+30+29+ RDATA ---+30+29+
|
||||
+--+--+ +--+--+
|
||||
WPT ---+28+27+ WPT ---+28+27+
|
||||
+--+--+ +--+--+
|
||||
TRK00 ---+26+25+ TRK00 ---+26+25+
|
||||
+--+--+ +--+--+
|
||||
WGATE ---+24+23+ WGATE ---+24+23+
|
||||
+--+--+ +--+--+
|
||||
WDATA ---+22+21+ WDATA ---+22+21+
|
||||
+--+--+ +--+--+
|
||||
STEP ---+20+19+ STEP ---+20+19+
|
||||
+--+--+ +--+--+
|
||||
DIR/SIDE1 ---+18+17+ DIR/SIDE1 ---+18+17+
|
||||
+--+--+ +--+--+
|
||||
MOTEB ---+16+15+ MOTEB ---+16+15+
|
||||
+--+--+ +--+--+
|
||||
DRVSA ---+14+13+ DS3 ---+14+13+
|
||||
+--+--+ +--+--+
|
||||
DRVSB ---+12+11+ DS2 ---+12+11+
|
||||
+--+--+ +--+--+
|
||||
MOTEA ---+10+9 + DS1 ---+10+9 +
|
||||
+--+--+ +--+--+
|
||||
INDEX ---+8 +7 + INDEX ---+8 +7 +
|
||||
+--+--+ +--+--+
|
||||
n/c ---+6 +5 + DS4 ---+6 +5 +
|
||||
+--+--+ +--+--+
|
||||
n/c ---+4 +3 + INU ---+4 +3 +
|
||||
+--+--+ +--+--+
|
||||
REDWC ---+2 +1 + REDWC ---+2 +1 +
|
||||
+--+--+ +--+--+
|
||||
|
||||
PC interface Shugart interface
|
||||
|
||||
Odd numbered pins are always grounded
|
||||
```
|
||||
|
||||
### Some useful links
|
||||
|
||||
- [The floppy disk user's
|
||||
|
||||
15
doc/using.md
15
doc/using.md
@@ -66,12 +66,12 @@ Configurations can be specified either on the command line or in text files.
|
||||
Here are some sample invocations:
|
||||
|
||||
```
|
||||
# Read an PC disk, producing a disk image with the default name (ibm.img),
|
||||
# autodetecting all parameters
|
||||
$ fluxengine read ibm
|
||||
# Read an PC 1440kB disk, producing a disk image with the default name
|
||||
# (ibm.img)
|
||||
$ fluxengine read ibm1440
|
||||
|
||||
# Write a PC 1440kB disk to drive 1
|
||||
$ fluxengine write ibm -i image.img -d drive:1
|
||||
$ fluxengine write ibm1440 -i image.img -d drive:1
|
||||
|
||||
# Read a Eco1 CP/M disk, making a copy of the flux into a file
|
||||
$ fluxengine read eco1 --copy-flux-to copy.flux -o eco1.ldbs
|
||||
@@ -108,8 +108,9 @@ protobuf syntax](https://developers.google.com/protocol-buffers), which is
|
||||
hierarchical, type-safe, and easy to read.
|
||||
|
||||
The `ibm1440` string above is actually a reference to an internal configuration
|
||||
file containing all the settings for writing PC 1440kB disks. You can see all
|
||||
these settings by doing:
|
||||
file containing all the settings for writing PC 1440kB disks. You may specify
|
||||
as many profile names or textpb files as you wish; they are all merged left to
|
||||
right. You can see all these settings by doing:
|
||||
|
||||
```
|
||||
$ fluxengine write ibm1440 --config
|
||||
@@ -404,7 +405,7 @@ containing valuable historical data, and you want to read them.
|
||||
Typically I do this:
|
||||
|
||||
```
|
||||
$ fluxengine read brother -s drive:0 -o brother.img --copy-flux-to=brother.flux --decoder.write_csv_to=brother.csv
|
||||
$ fluxengine read brother240 -s drive:0 -o brother.img --copy-flux-to=brother.flux --decoder.write_csv_to=brother.csv
|
||||
```
|
||||
|
||||
This will read the disk in drive 0 and write out an information CSV file. It'll
|
||||
|
||||
@@ -11,7 +11,7 @@ import "lib/drive.proto";
|
||||
import "lib/mapper.proto";
|
||||
import "lib/common.proto";
|
||||
|
||||
// NEXT_TAG: 16
|
||||
// NEXT_TAG: 17
|
||||
message ConfigProto {
|
||||
optional string comment = 8;
|
||||
optional bool is_extension = 13;
|
||||
@@ -27,8 +27,9 @@ message ConfigProto {
|
||||
optional DecoderProto decoder = 4;
|
||||
optional UsbProto usb = 5;
|
||||
|
||||
optional RangeProto cylinders = 6;
|
||||
optional RangeProto tracks = 6;
|
||||
optional RangeProto heads = 7;
|
||||
optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ];
|
||||
|
||||
optional SectorMappingProto sector_mapping = 14;
|
||||
}
|
||||
|
||||
@@ -60,12 +60,11 @@ std::unique_ptr<AbstractDecoder> AbstractDecoder::create(const DecoderProto& con
|
||||
}
|
||||
|
||||
std::shared_ptr<const TrackDataFlux> AbstractDecoder::decodeToSectors(
|
||||
std::shared_ptr<const Fluxmap> fluxmap, unsigned physicalCylinder, unsigned physicalHead)
|
||||
std::shared_ptr<const Fluxmap> fluxmap, const Location& location)
|
||||
{
|
||||
_trackdata = std::make_shared<TrackDataFlux>();
|
||||
_trackdata->fluxmap = fluxmap;
|
||||
_trackdata->physicalCylinder = physicalCylinder;
|
||||
_trackdata->physicalHead = physicalHead;
|
||||
_trackdata->location = location;
|
||||
|
||||
FluxmapReader fmr(*fluxmap);
|
||||
_fmr = &fmr;
|
||||
@@ -73,8 +72,8 @@ std::shared_ptr<const TrackDataFlux> AbstractDecoder::decodeToSectors(
|
||||
auto newSector = [&] {
|
||||
_sector = std::make_shared<Sector>();
|
||||
_sector->status = Sector::MISSING;
|
||||
_sector->physicalCylinder = physicalCylinder;
|
||||
_sector->physicalHead = physicalHead;
|
||||
_sector->physicalTrack = location.physicalTrack;
|
||||
_sector->physicalHead = location.head;
|
||||
};
|
||||
|
||||
newSector();
|
||||
@@ -218,7 +217,7 @@ uint64_t AbstractDecoder::readRaw64()
|
||||
}
|
||||
|
||||
|
||||
std::set<unsigned> AbstractDecoder::requiredSectors(unsigned cylinder, unsigned head) const
|
||||
std::set<unsigned> AbstractDecoder::requiredSectors(const Location& location) const
|
||||
{
|
||||
static std::set<unsigned> set;
|
||||
return set;
|
||||
|
||||
@@ -18,23 +18,27 @@ extern void setDecoderManualClockRate(double clockrate_us);
|
||||
|
||||
extern Bytes decodeFmMfm(std::vector<bool>::const_iterator start,
|
||||
std::vector<bool>::const_iterator end);
|
||||
extern void encodeMfm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input, bool& lastBit);
|
||||
extern void encodeFm(std::vector<bool>& bits, unsigned& cursor, const Bytes& input);
|
||||
extern void encodeMfm(std::vector<bool>& bits,
|
||||
unsigned& cursor,
|
||||
const Bytes& input,
|
||||
bool& lastBit);
|
||||
extern void encodeFm(
|
||||
std::vector<bool>& bits, unsigned& cursor, const Bytes& input);
|
||||
extern Bytes encodeMfm(const Bytes& input, bool& lastBit);
|
||||
|
||||
static inline Bytes decodeFmMfm(const std::vector<bool> bits)
|
||||
{ return decodeFmMfm(bits.begin(), bits.end()); }
|
||||
{
|
||||
return decodeFmMfm(bits.begin(), bits.end());
|
||||
}
|
||||
|
||||
class AbstractDecoder
|
||||
{
|
||||
public:
|
||||
AbstractDecoder(const DecoderProto& config):
|
||||
_config(config)
|
||||
{}
|
||||
AbstractDecoder(const DecoderProto& config): _config(config) {}
|
||||
|
||||
virtual ~AbstractDecoder() {}
|
||||
|
||||
static std::unique_ptr<AbstractDecoder> create(const DecoderProto& config);
|
||||
static std::unique_ptr<AbstractDecoder> create(const DecoderProto& config);
|
||||
|
||||
public:
|
||||
enum RecordType
|
||||
@@ -45,47 +49,59 @@ public:
|
||||
};
|
||||
|
||||
public:
|
||||
std::shared_ptr<const TrackDataFlux> decodeToSectors(std::shared_ptr<const Fluxmap> fluxmap, unsigned cylinder, unsigned head);
|
||||
void pushRecord(const Fluxmap::Position& start, const Fluxmap::Position& end);
|
||||
std::shared_ptr<const TrackDataFlux> decodeToSectors(
|
||||
std::shared_ptr<const Fluxmap> fluxmap,
|
||||
const Location& location);
|
||||
|
||||
void resetFluxDecoder();
|
||||
void pushRecord(
|
||||
const Fluxmap::Position& start, const Fluxmap::Position& end);
|
||||
|
||||
void resetFluxDecoder();
|
||||
std::vector<bool> readRawBits(unsigned count);
|
||||
uint8_t readRaw8();
|
||||
uint16_t readRaw16();
|
||||
uint32_t readRaw20();
|
||||
uint32_t readRaw24();
|
||||
uint32_t readRaw32();
|
||||
uint64_t readRaw48();
|
||||
uint64_t readRaw64();
|
||||
uint8_t readRaw8();
|
||||
uint16_t readRaw16();
|
||||
uint32_t readRaw20();
|
||||
uint32_t readRaw24();
|
||||
uint32_t readRaw32();
|
||||
uint64_t readRaw48();
|
||||
uint64_t readRaw64();
|
||||
|
||||
Fluxmap::Position tell()
|
||||
{ return _fmr->tell(); }
|
||||
{
|
||||
return _fmr->tell();
|
||||
}
|
||||
|
||||
void seek(const Fluxmap::Position& pos)
|
||||
{ return _fmr->seek(pos); }
|
||||
{
|
||||
return _fmr->seek(pos);
|
||||
}
|
||||
|
||||
nanoseconds_t seekToPattern(const FluxMatcher& pattern);
|
||||
void seekToIndexMark();
|
||||
nanoseconds_t seekToPattern(const FluxMatcher& pattern);
|
||||
void seekToIndexMark();
|
||||
|
||||
bool eof() const
|
||||
{ return _fmr->eof(); }
|
||||
{
|
||||
return _fmr->eof();
|
||||
}
|
||||
|
||||
nanoseconds_t getFluxmapDuration() const
|
||||
{ return _fmr->getDuration(); }
|
||||
nanoseconds_t getFluxmapDuration() const
|
||||
{
|
||||
return _fmr->getDuration();
|
||||
}
|
||||
|
||||
virtual std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const;
|
||||
virtual std::set<unsigned> requiredSectors(const Location& location) const;
|
||||
|
||||
protected:
|
||||
virtual void beginTrack() {};
|
||||
virtual void beginTrack(){};
|
||||
virtual nanoseconds_t advanceToNextRecord() = 0;
|
||||
virtual void decodeSectorRecord() = 0;
|
||||
virtual void decodeDataRecord() {};
|
||||
virtual void decodeDataRecord(){};
|
||||
|
||||
const DecoderProto& _config;
|
||||
std::shared_ptr<TrackDataFlux> _trackdata;
|
||||
const DecoderProto& _config;
|
||||
std::shared_ptr<TrackDataFlux> _trackdata;
|
||||
std::shared_ptr<Sector> _sector;
|
||||
std::unique_ptr<FluxDecoder> _decoder;
|
||||
std::vector<bool> _recordBits;
|
||||
std::unique_ptr<FluxDecoder> _decoder;
|
||||
std::vector<bool> _recordBits;
|
||||
|
||||
private:
|
||||
FluxmapReader* _fmr = nullptr;
|
||||
|
||||
@@ -2,13 +2,33 @@ syntax = "proto2";
|
||||
|
||||
import "lib/common.proto";
|
||||
|
||||
message DriveProto {
|
||||
optional int32 drive = 1 [default = 0, (help) = "which drive to write to (0 or 1)"];
|
||||
optional IndexMode index_mode = 2 [default = INDEXMODE_DRIVE, (help) = "index pulse source"];
|
||||
optional int32 hard_sector_count = 3 [default = 0, (help) = "number of hard sectors on disk"];
|
||||
optional bool high_density = 4 [default = true, (help) = "set if this is a high density disk"];
|
||||
optional bool sync_with_index = 5 [default = false, (help) = "start reading at index mark"];
|
||||
optional double revolutions = 6 [default = 1.2, (help) = "number of revolutions to read"];
|
||||
// Next: 13
|
||||
message DriveProto
|
||||
{
|
||||
optional int32 drive = 1
|
||||
[ default = 0, (help) = "which drive to write to (0 or 1)" ];
|
||||
optional IndexMode index_mode = 2
|
||||
[ default = INDEXMODE_DRIVE, (help) = "index pulse source" ];
|
||||
optional int32 hard_sector_count = 3
|
||||
[ default = 0, (help) = "number of hard sectors on disk" ];
|
||||
optional bool high_density = 4
|
||||
[ default = true, (help) = "set if this is a high density disk" ];
|
||||
optional bool sync_with_index = 5
|
||||
[ default = false, (help) = "start reading at index mark" ];
|
||||
optional double revolutions = 6
|
||||
[ default = 1.2, (help) = "number of revolutions to read" ];
|
||||
|
||||
optional int32 tracks = 7
|
||||
[ default = 81, (help) = "Number of tracks supported by drive" ];
|
||||
optional int32 heads = 8
|
||||
[ default = 2, (help) = "Number of heads supported by drive" ];
|
||||
optional int32 head_bias = 10 [
|
||||
default = 0,
|
||||
(help) = "Bias to apply to the head position (in tracks)"
|
||||
];
|
||||
optional int32 head_width = 11
|
||||
[ default = 1, (help) = "Width of the head (in tracks)" ];
|
||||
optional int32 tpi = 9 [ default = 96, (help) = "TPI of drive" ];
|
||||
}
|
||||
|
||||
|
||||
// vim: ts=4 sw=4 et
|
||||
|
||||
@@ -15,45 +15,44 @@
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include "protocol.h"
|
||||
|
||||
std::unique_ptr<AbstractEncoder> AbstractEncoder::create(const EncoderProto& config)
|
||||
std::unique_ptr<AbstractEncoder> AbstractEncoder::create(
|
||||
const EncoderProto& config)
|
||||
{
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>> encoders =
|
||||
{
|
||||
{ EncoderProto::kAmiga, createAmigaEncoder },
|
||||
{ EncoderProto::kApple2, createApple2Encoder },
|
||||
{ EncoderProto::kBrother, createBrotherEncoder },
|
||||
{ EncoderProto::kC64, createCommodore64Encoder },
|
||||
{ EncoderProto::kIbm, createIbmEncoder },
|
||||
{ EncoderProto::kMacintosh, createMacintoshEncoder },
|
||||
{ EncoderProto::kMicropolis,createMicropolisEncoder },
|
||||
{ EncoderProto::kNorthstar, createNorthstarEncoder },
|
||||
{ EncoderProto::kTids990, createTids990Encoder },
|
||||
{ EncoderProto::kVictor9K, createVictor9kEncoder },
|
||||
};
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>>
|
||||
encoders = {
|
||||
{EncoderProto::kAmiga, createAmigaEncoder },
|
||||
{EncoderProto::kApple2, createApple2Encoder },
|
||||
{EncoderProto::kBrother, createBrotherEncoder },
|
||||
{EncoderProto::kC64, createCommodore64Encoder},
|
||||
{EncoderProto::kIbm, createIbmEncoder },
|
||||
{EncoderProto::kMacintosh, createMacintoshEncoder },
|
||||
{EncoderProto::kMicropolis, createMicropolisEncoder },
|
||||
{EncoderProto::kNorthstar, createNorthstarEncoder },
|
||||
{EncoderProto::kTids990, createTids990Encoder },
|
||||
{EncoderProto::kVictor9K, createVictor9kEncoder },
|
||||
};
|
||||
|
||||
auto encoder = encoders.find(config.format_case());
|
||||
if (encoder == encoders.end())
|
||||
Error() << "no encoder specified";
|
||||
auto encoder = encoders.find(config.format_case());
|
||||
if (encoder == encoders.end())
|
||||
Error() << "no encoder specified";
|
||||
|
||||
return (encoder->second)(config);
|
||||
return (encoder->second)(config);
|
||||
}
|
||||
|
||||
Fluxmap& Fluxmap::appendBits(const std::vector<bool>& bits, nanoseconds_t clock)
|
||||
{
|
||||
nanoseconds_t now = duration();
|
||||
for (unsigned i=0; i<bits.size(); i++)
|
||||
{
|
||||
now += clock;
|
||||
if (bits[i])
|
||||
{
|
||||
unsigned delta = (now - duration()) / NS_PER_TICK;
|
||||
nanoseconds_t now = duration();
|
||||
for (unsigned i = 0; i < bits.size(); i++)
|
||||
{
|
||||
now += clock;
|
||||
if (bits[i])
|
||||
{
|
||||
unsigned delta = (now - duration()) / NS_PER_TICK;
|
||||
appendInterval(delta);
|
||||
appendPulse();
|
||||
}
|
||||
}
|
||||
appendPulse();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef ENCODERS_H
|
||||
#define ENCODERS_H
|
||||
|
||||
class Fluxmap;
|
||||
class EncoderProto;
|
||||
class Fluxmap;
|
||||
class Image;
|
||||
class Location;
|
||||
class Sector;
|
||||
|
||||
class AbstractEncoder
|
||||
@@ -16,10 +17,9 @@ public:
|
||||
|
||||
public:
|
||||
virtual std::vector<std::shared_ptr<const Sector>> collectSectors(
|
||||
int physicalCylinder, int physicalHead, const Image& image) = 0;
|
||||
const Location& location, const Image& image) = 0;
|
||||
|
||||
virtual std::unique_ptr<Fluxmap> encode(int physicalCylinder,
|
||||
int physicalHead,
|
||||
virtual std::unique_ptr<Fluxmap> encode(const Location& location,
|
||||
const std::vector<std::shared_ptr<const Sector>>& sectors,
|
||||
const Image& image) = 0;
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ enum FluxFileVersion {
|
||||
}
|
||||
|
||||
message TrackFluxProto {
|
||||
optional int32 cylinder = 1;
|
||||
optional int32 track = 1;
|
||||
optional int32 head = 2;
|
||||
repeated bytes flux = 3;
|
||||
}
|
||||
|
||||
33
lib/flux.h
33
lib/flux.h
@@ -1,6 +1,9 @@
|
||||
#ifndef FLUX_H
|
||||
#define FLUX_H
|
||||
|
||||
#include "bytes.h"
|
||||
|
||||
class Fluxmap;
|
||||
class Sector;
|
||||
class Image;
|
||||
|
||||
@@ -12,10 +15,33 @@ struct Record
|
||||
Bytes rawData;
|
||||
};
|
||||
|
||||
struct Location
|
||||
{
|
||||
unsigned physicalTrack;
|
||||
unsigned logicalTrack;
|
||||
unsigned head;
|
||||
unsigned groupSize;
|
||||
|
||||
bool operator==(const Location& other) const
|
||||
{
|
||||
if (physicalTrack == other.physicalTrack)
|
||||
return true;
|
||||
return head == other.head;
|
||||
}
|
||||
|
||||
bool operator<(const Location& other) const
|
||||
{
|
||||
if (physicalTrack < other.physicalTrack)
|
||||
return true;
|
||||
if (physicalTrack == other.physicalTrack)
|
||||
return head < other.head;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct TrackDataFlux
|
||||
{
|
||||
unsigned physicalCylinder;
|
||||
unsigned physicalHead;
|
||||
Location location;
|
||||
std::shared_ptr<const Fluxmap> fluxmap;
|
||||
std::vector<std::shared_ptr<const Record>> records;
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
@@ -23,8 +49,7 @@ struct TrackDataFlux
|
||||
|
||||
struct TrackFlux
|
||||
{
|
||||
unsigned physicalCylinder;
|
||||
unsigned physicalHead;
|
||||
Location location;
|
||||
std::vector<std::shared_ptr<const TrackDataFlux>> trackDatas;
|
||||
std::set<std::shared_ptr<const Sector>> sectors;
|
||||
};
|
||||
|
||||
@@ -32,9 +32,10 @@ public:
|
||||
appendBytes((const uint8_t*) s.c_str(), s.size());
|
||||
}
|
||||
|
||||
Fluxmap(const Bytes bytes):
|
||||
_bytes(bytes)
|
||||
{}
|
||||
Fluxmap(const Bytes bytes)
|
||||
{
|
||||
appendBytes(bytes);
|
||||
}
|
||||
|
||||
nanoseconds_t duration() const { return _duration; }
|
||||
unsigned ticks() const { return _ticks; }
|
||||
|
||||
@@ -26,14 +26,14 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
unsigned totalTicks = fluxmap.ticks() + 2;
|
||||
unsigned channels = _config.index_markers() ? 2 : 1;
|
||||
|
||||
mkdir(_config.directory().c_str(), 0744);
|
||||
std::ofstream of(
|
||||
fmt::format("{}/c{:02d}.h{:01d}.au", _config.directory(), cylinder, head),
|
||||
fmt::format("{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
for (const auto& e : _data)
|
||||
{
|
||||
auto track = proto.add_track();
|
||||
track->set_cylinder(e.first.first);
|
||||
track->set_track(e.first.first);
|
||||
track->set_head(e.first.second);
|
||||
for (const auto& fluxBytes : e.second)
|
||||
track->add_flux(fluxBytes);
|
||||
@@ -51,9 +51,9 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
auto& vector = _data[std::make_pair(cylinder, head)];
|
||||
auto& vector = _data[std::make_pair(track, head)];
|
||||
vector.push_back(fluxmap.rawBytes());
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ public:
|
||||
_fileheader.file_id[2] = 'P';
|
||||
_fileheader.version = 0x18; /* Version 1.8 of the spec */
|
||||
_fileheader.type = _config.type_byte();
|
||||
_fileheader.start_track = strackno(config.cylinders().start(), config.heads().start());
|
||||
_fileheader.end_track = strackno(config.cylinders().end(), config.heads().end());
|
||||
_fileheader.start_track = strackno(config.tracks().start(), config.heads().start());
|
||||
_fileheader.end_track = strackno(config.tracks().end(), config.heads().end());
|
||||
_fileheader.flags = SCP_FLAG_INDEXED
|
||||
| SCP_FLAG_96TPI;
|
||||
_fileheader.cell_width = 0;
|
||||
@@ -80,16 +80,16 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
ByteWriter trackdataWriter(_trackdata);
|
||||
trackdataWriter.seekToEnd();
|
||||
int strack = strackno(cylinder, head);
|
||||
int strack = strackno(track, head);
|
||||
|
||||
if (strack >= std::size(_fileheader.track)) {
|
||||
std::cout << fmt::format("SCP: cannot write track {} head {}, "
|
||||
"there are not not enough Track Data Headers.\n",
|
||||
cylinder, head);
|
||||
track, head);
|
||||
return;
|
||||
}
|
||||
ScpTrack trackheader = {0};
|
||||
|
||||
@@ -20,11 +20,11 @@ public:
|
||||
{}
|
||||
|
||||
public:
|
||||
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
mkdir(_config.directory().c_str(), 0744);
|
||||
std::ofstream of(
|
||||
fmt::format("{}/c{:02d}.h{:01d}.vcd", _config.directory(), cylinder, head),
|
||||
fmt::format("{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
|
||||
@@ -14,10 +14,10 @@ struct CwfHeader
|
||||
uint8_t version; // version of this file
|
||||
uint8_t clock_rate; // clock rate used: 0, 1, 2 (2 = 28MHz)
|
||||
uint8_t drive_type; // type of drive
|
||||
uint8_t cylinders; // number of cylinders
|
||||
uint8_t tracks; // number of tracks
|
||||
uint8_t sides; // number of sides
|
||||
uint8_t index_mark; // nonzero if index marks are included
|
||||
uint8_t step; // cylinder stepping interval
|
||||
uint8_t step; // track stepping interval
|
||||
uint8_t filler[15]; // reserved for expansion
|
||||
uint8_t comment[100]; // brief comment
|
||||
};
|
||||
@@ -58,11 +58,11 @@ public:
|
||||
Error() << "unsupported clock rate";
|
||||
}
|
||||
|
||||
std::cout << fmt::format("CWF {}x{} = {} cylinders, {} sides\n",
|
||||
_header.cylinders, _header.step, _header.cylinders*_header.step, _header.sides);
|
||||
std::cout << fmt::format("CWF {}x{} = {} tracks, {} sides\n",
|
||||
_header.tracks, _header.step, _header.tracks*_header.step, _header.sides);
|
||||
std::cout << fmt::format("CWF sample clock rate: {} MHz\n", _clockRate / 1e6);
|
||||
|
||||
int tracks = _header.cylinders*_header.sides;
|
||||
int tracks = _header.tracks*_header.sides;
|
||||
for (int i=0; i<tracks; i++)
|
||||
{
|
||||
CwfTrack trackheader;
|
||||
|
||||
@@ -62,12 +62,12 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(int cylinder, int head) override
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override
|
||||
{
|
||||
for (const auto& track : _proto.track())
|
||||
for (const auto& trackFlux : _proto.track())
|
||||
{
|
||||
if ((track.cylinder() == cylinder) && (track.head() == head))
|
||||
return std::make_unique<Fl2FluxSourceIterator>(track);
|
||||
if ((trackFlux.track() == track) && (trackFlux.head() == head))
|
||||
return std::make_unique<Fl2FluxSourceIterator>(trackFlux);
|
||||
}
|
||||
|
||||
return std::make_unique<EmptyFluxSourceIterator>();
|
||||
|
||||
@@ -76,9 +76,9 @@ void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::stri
|
||||
class TrivialFluxSourceIterator : public FluxSourceIterator
|
||||
{
|
||||
public:
|
||||
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int cylinder, int head):
|
||||
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int track, int head):
|
||||
_fluxSource(fluxSource),
|
||||
_cylinder(cylinder),
|
||||
_track(track),
|
||||
_head(head)
|
||||
{}
|
||||
|
||||
@@ -89,20 +89,20 @@ public:
|
||||
|
||||
std::unique_ptr<const Fluxmap> next() override
|
||||
{
|
||||
auto fluxmap = _fluxSource->readSingleFlux(_cylinder, _head);
|
||||
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
|
||||
_fluxSource = nullptr;
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
TrivialFluxSource* _fluxSource;
|
||||
int _cylinder;
|
||||
int _track;
|
||||
int _head;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int cylinder, int head)
|
||||
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int track, int head)
|
||||
{
|
||||
return std::make_unique<TrivialFluxSourceIterator>(this, cylinder, head);
|
||||
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ private:
|
||||
{
|
||||
public:
|
||||
HardwareFluxSourceIterator(
|
||||
const HardwareFluxSource& fluxsource, int cylinder, int head):
|
||||
const HardwareFluxSource& fluxsource, int track, int head):
|
||||
_fluxsource(fluxsource),
|
||||
_cylinder(cylinder),
|
||||
_track(track),
|
||||
_head(head)
|
||||
{
|
||||
}
|
||||
@@ -30,7 +30,7 @@ private:
|
||||
std::unique_ptr<const Fluxmap> next()
|
||||
{
|
||||
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
|
||||
usbSeek(_cylinder);
|
||||
usbSeek(_track);
|
||||
|
||||
Bytes data = usbRead(_head,
|
||||
config.drive().sync_with_index(),
|
||||
@@ -43,7 +43,7 @@ private:
|
||||
|
||||
private:
|
||||
const HardwareFluxSource& _fluxsource;
|
||||
int _cylinder;
|
||||
int _track;
|
||||
int _head;
|
||||
};
|
||||
|
||||
@@ -76,10 +76,10 @@ public:
|
||||
~HardwareFluxSource() {}
|
||||
|
||||
public:
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(int cylinder, int head) override
|
||||
std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override
|
||||
{
|
||||
return std::make_unique<HardwareFluxSourceIterator>(
|
||||
*this, cylinder, head);
|
||||
*this, track, head);
|
||||
}
|
||||
|
||||
void recalibrate() override
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
|
||||
#if defined(_WIN32) || defined(__WIN32__)
|
||||
#include <direct.h>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "fmt/format.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "proto.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -32,11 +33,11 @@ public:
|
||||
data.writer() += inputFile;
|
||||
ByteReader br(data);
|
||||
|
||||
unsigned numCylinders = 39;
|
||||
unsigned numTracks = 39;
|
||||
unsigned numHeads = 1;
|
||||
unsigned numSectors = 0;
|
||||
|
||||
Logger() << fmt::format("D64: reading image with {} cylinders, {} heads", numCylinders, numHeads);
|
||||
Logger() << fmt::format("D64: reading image with {} tracks, {} heads", numTracks, numHeads);
|
||||
|
||||
uint32_t offset = 0;
|
||||
|
||||
@@ -55,7 +56,7 @@ public:
|
||||
for (int track = 0; track < 40; track++)
|
||||
{
|
||||
int numSectors = sectorsPerTrack(track);
|
||||
int physicalCylinder = track * 2;
|
||||
int physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
for (int head = 0; head < numHeads; head++)
|
||||
{
|
||||
for (int sectorId = 0; sectorId < numSectors; sectorId++)
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->physicalTrack = physicalTrack;
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data.writer().append(payload);
|
||||
@@ -79,7 +80,7 @@ public:
|
||||
// DATA_MISSING
|
||||
sector->status = Sector::DATA_MISSING;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->physicalTrack = physicalTrack;
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
sector->logicalSector = sectorId;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "image.h"
|
||||
#include "proto.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
@@ -62,21 +63,23 @@ public:
|
||||
Logger() << "D88: overriding configured format";
|
||||
|
||||
auto ibm = config.mutable_encoder()->mutable_ibm();
|
||||
int physicalStep = 1;
|
||||
int clockRate = 500;
|
||||
if (mediaFlag == 0x20)
|
||||
{
|
||||
Logger() << "D88: high density mode";
|
||||
if (!config.drive().has_drive())
|
||||
config.mutable_drive()->set_high_density(true);
|
||||
if (!config.has_tpi())
|
||||
config.set_tpi(96);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger() << "D88: single/double density mode";
|
||||
physicalStep = 2;
|
||||
clockRate = 300;
|
||||
if (!config.drive().has_drive())
|
||||
config.mutable_drive()->set_high_density(false);
|
||||
if (!config.has_tpi())
|
||||
config.set_tpi(48);
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
@@ -87,7 +90,7 @@ public:
|
||||
continue;
|
||||
|
||||
int currentTrackOffset = trackOffset;
|
||||
int currentTrackCylinder = -1;
|
||||
int currentTrackTrack = -1;
|
||||
int currentSectorsInTrack =
|
||||
0xffff; // don't know # of sectors until we read the first one
|
||||
int trackSectorSize = -1;
|
||||
@@ -105,7 +108,7 @@ public:
|
||||
inputFile.read(
|
||||
(char*)sectorHeader.begin(), sectorHeader.size());
|
||||
ByteReader sectorHeaderReader(sectorHeader);
|
||||
int cylinder = sectorHeaderReader.seek(0).read_8();
|
||||
int track = sectorHeaderReader.seek(0).read_8();
|
||||
int head = sectorHeaderReader.seek(1).read_8();
|
||||
int sectorId = sectorHeaderReader.seek(2).read_8();
|
||||
int sectorSize = 128 << sectorHeaderReader.seek(3).read_8();
|
||||
@@ -132,21 +135,21 @@ public:
|
||||
{
|
||||
Error() << "D88: mismatched number of sectors in track";
|
||||
}
|
||||
if (currentTrackCylinder < 0)
|
||||
if (currentTrackTrack < 0)
|
||||
{
|
||||
currentTrackCylinder = cylinder;
|
||||
currentTrackTrack = track;
|
||||
}
|
||||
else if (currentTrackCylinder != cylinder)
|
||||
else if (currentTrackTrack != track)
|
||||
{
|
||||
Error() << "D88: all sectors in a track must belong to the "
|
||||
"same cylinder";
|
||||
"same track";
|
||||
}
|
||||
if (trackSectorSize < 0)
|
||||
{
|
||||
trackSectorSize = sectorSize;
|
||||
// this is the first sector we've read, use it settings for
|
||||
// per-track data
|
||||
trackdata->set_cylinder(cylinder * physicalStep);
|
||||
trackdata->set_track(track);
|
||||
trackdata->set_head(head);
|
||||
trackdata->set_sector_size(sectorSize);
|
||||
trackdata->set_use_fm(fm);
|
||||
@@ -176,11 +179,10 @@ public:
|
||||
}
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
const auto& sector =
|
||||
image->put(cylinder * physicalStep, head, sectorId);
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = cylinder;
|
||||
sector->physicalCylinder = cylinder * physicalStep;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
@@ -188,7 +190,7 @@ public:
|
||||
sectors->add_sector(sectorId);
|
||||
}
|
||||
|
||||
if (physicalStep == 2)
|
||||
if (mediaFlag != 0x20)
|
||||
{
|
||||
auto trackdata = ibm->add_trackdata();
|
||||
trackdata->set_clock_rate_khz(clockRate);
|
||||
@@ -209,11 +211,11 @@ public:
|
||||
heads->set_end(geometry.numSides - 1);
|
||||
}
|
||||
|
||||
if (!config.has_cylinders())
|
||||
if (!config.has_tracks())
|
||||
{
|
||||
auto* cylinders = config.mutable_cylinders();
|
||||
cylinders->set_start(0);
|
||||
cylinders->set_end(geometry.numTracks - 1);
|
||||
auto* tracks = config.mutable_tracks();
|
||||
tracks->set_start(0);
|
||||
tracks->set_end(geometry.numTracks - 1);
|
||||
}
|
||||
|
||||
return image;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "proto.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
@@ -71,7 +72,6 @@ public:
|
||||
{
|
||||
if (inputFile.eof())
|
||||
break;
|
||||
int physicalCylinder = track;
|
||||
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
@@ -84,11 +84,10 @@ public:
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
|
||||
const auto& sector =
|
||||
image->put(physicalCylinder, side, sectorId);
|
||||
const auto& sector = image->put(track, side, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
sector->logicalSide = sector->physicalHead = side;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
@@ -110,7 +109,7 @@ public:
|
||||
case 0x00:
|
||||
Logger() << "DIM: automatically setting format to 1.2MB "
|
||||
"(1024 byte sectors)";
|
||||
config.mutable_cylinders()->set_end(76);
|
||||
config.mutable_tracks()->set_end(76);
|
||||
trackdata->set_track_length_ms(167);
|
||||
trackdata->set_sector_size(1024);
|
||||
for (int i = 0; i < 9; i++)
|
||||
@@ -156,11 +155,11 @@ public:
|
||||
heads->set_end(geometry.numSides - 1);
|
||||
}
|
||||
|
||||
if (!config.has_cylinders())
|
||||
if (!config.has_tracks())
|
||||
{
|
||||
auto* cylinders = config.mutable_cylinders();
|
||||
cylinders->set_start(0);
|
||||
cylinders->set_end(geometry.numTracks - 1);
|
||||
auto* tracks = config.mutable_tracks();
|
||||
tracks->set_start(0);
|
||||
tracks->set_end(geometry.numTracks - 1);
|
||||
}
|
||||
|
||||
return image;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
@@ -36,7 +37,7 @@ public:
|
||||
uint8_t encoding = br.read_8();
|
||||
uint8_t formatByte = br.read_8();
|
||||
|
||||
unsigned numCylinders = 80;
|
||||
unsigned numTracks = 80;
|
||||
unsigned numHeads = 2;
|
||||
unsigned numSectors = 0;
|
||||
bool mfm = false;
|
||||
@@ -66,8 +67,8 @@ public:
|
||||
}
|
||||
|
||||
Logger() << fmt::format(
|
||||
"DC42: reading image with {} cylinders, {} heads; {}; {}",
|
||||
numCylinders,
|
||||
"DC42: reading image with {} tracks, {} heads; {}; {}",
|
||||
numTracks,
|
||||
numHeads,
|
||||
mfm ? "MFM" : "GCR",
|
||||
label);
|
||||
@@ -92,7 +93,7 @@ public:
|
||||
uint32_t tagPtr = dataPtr + dataSize;
|
||||
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (int track = 0; track < numCylinders; track++)
|
||||
for (int track = 0; track < numTracks; track++)
|
||||
{
|
||||
int numSectors = sectorsPerTrack(track);
|
||||
for (int head = 0; head < numHeads; head++)
|
||||
@@ -109,7 +110,8 @@ public:
|
||||
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = track;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data.writer().append(payload).append(tag);
|
||||
@@ -117,7 +119,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
image->setGeometry({.numTracks = numCylinders,
|
||||
image->setGeometry({.numTracks = numTracks,
|
||||
.numSides = numHeads,
|
||||
.numSectors = 12,
|
||||
.sectorSize = 512 + 12,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "image.h"
|
||||
#include "proto.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
@@ -50,7 +51,6 @@ public:
|
||||
{
|
||||
if (inputFile.eof())
|
||||
break;
|
||||
int physicalCylinder = track;
|
||||
|
||||
for (int side = 0; side < sides; side++)
|
||||
{
|
||||
@@ -64,10 +64,10 @@ public:
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
|
||||
const auto& sector =
|
||||
image->put(physicalCylinder, side, sectorId);
|
||||
image->put(track, side, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
sector->logicalSide = sector->physicalHead = side;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
@@ -89,7 +89,7 @@ public:
|
||||
case 0x90:
|
||||
Logger() << "FDI: automatically setting format to 1.2MB "
|
||||
"(1024 byte sectors)";
|
||||
config.mutable_cylinders()->set_end(76);
|
||||
config.mutable_tracks()->set_end(76);
|
||||
trackdata->set_track_length_ms(167);
|
||||
trackdata->set_sector_size(1024);
|
||||
for (int i = 0; i < 9; i++)
|
||||
@@ -125,11 +125,11 @@ public:
|
||||
heads->set_end(geometry.numSides - 1);
|
||||
}
|
||||
|
||||
if (!config.has_cylinders())
|
||||
if (!config.has_tracks())
|
||||
{
|
||||
auto* cylinders = config.mutable_cylinders();
|
||||
cylinders->set_start(0);
|
||||
cylinders->set_end(geometry.numTracks - 1);
|
||||
auto* tracks = config.mutable_tracks();
|
||||
tracks->set_start(0);
|
||||
tracks->set_end(geometry.numTracks - 1);
|
||||
}
|
||||
|
||||
return image;
|
||||
|
||||
@@ -34,8 +34,6 @@ message ImgInputOutputProto {
|
||||
repeated TrackdataProto trackdata = 4 [(help) = "per-track format information (repeatable)"];
|
||||
optional int32 tracks = 5 [default=0, (help) = "number of tracks in image"];
|
||||
optional int32 sides = 6 [default=0, (help) = "number of sides in image"];
|
||||
optional int32 physical_offset = 7 [default=0, (help) = "logical:physical track offset"];
|
||||
optional int32 physical_step = 8 [default=1, (help) = "logical:physical track step"];
|
||||
optional Order order = 9 [default=CHS, (help) = "the order in which to emit tracks in the image"];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "image.h"
|
||||
#include "proto.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
@@ -100,12 +101,12 @@ public:
|
||||
1A byte - ASCII EOF character
|
||||
- For each track on the disk:
|
||||
1 byte Mode value see getModulationspeed for definition
|
||||
1 byte Cylinder
|
||||
1 byte Track
|
||||
1 byte Head
|
||||
1 byte number of sectors in track
|
||||
1 byte sector size see getsectorsize for definition
|
||||
sector numbering map
|
||||
sector cylinder map (optional) definied in high byte of head (since head is 0 or 1)
|
||||
sector track map (optional) definied in high byte of head (since head is 0 or 1)
|
||||
sector head map (optional) definied in high byte of head (since head is 0 or 1)
|
||||
sector data records
|
||||
<End of file>
|
||||
@@ -163,7 +164,7 @@ public:
|
||||
headerPtr++;
|
||||
sectorSize = getSectorSize(header.SectorSize);
|
||||
|
||||
//Read optional cylinder map To Do
|
||||
//Read optional track map To Do
|
||||
|
||||
//Read optional sector head map To Do
|
||||
|
||||
@@ -218,7 +219,8 @@ public:
|
||||
Error() << fmt::format("don't understand IMD disks with sector status {}", Status_Sector);
|
||||
}
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = header.track;
|
||||
sector->logicalTrack = header.track;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(header.track);
|
||||
sector->logicalSide = sector->physicalHead = header.Head;
|
||||
sector->logicalSector = (sector_map[s]);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "imginputoutpututils.h"
|
||||
#include "fmt/format.h"
|
||||
@@ -35,8 +36,6 @@ public:
|
||||
|
||||
if (inputFile.eof())
|
||||
break;
|
||||
int physicalCylinder = track * _config.img().physical_step() +
|
||||
_config.img().physical_offset();
|
||||
|
||||
ImgInputOutputProto::TrackdataProto trackdata;
|
||||
getTrackFormat(_config.img(), trackdata, track, side);
|
||||
@@ -46,11 +45,10 @@ public:
|
||||
Bytes data(trackdata.sector_size());
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
|
||||
const auto& sector =
|
||||
image->put(physicalCylinder, side, sectorId);
|
||||
const auto& sector = image->put(track, side, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
sector->logicalSide = sector->physicalHead = side;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "mapper.h"
|
||||
#include "logger.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/config.pb.h"
|
||||
@@ -123,8 +124,8 @@ public:
|
||||
const auto& sector =
|
||||
image->put(header.track, head, header.sector);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder =
|
||||
header.track;
|
||||
sector->logicalTrack = header.track;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(header.track);
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
sector->logicalSector = header.sector;
|
||||
sector->data = data;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "image.h"
|
||||
#include "proto.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
@@ -55,7 +56,7 @@ public:
|
||||
Logger() << "NFD: overriding configured format";
|
||||
|
||||
auto ibm = config.mutable_encoder()->mutable_ibm();
|
||||
config.mutable_cylinders()->set_end(0);
|
||||
config.mutable_tracks()->set_end(0);
|
||||
Logger() << "NFD: HD 1.2MB mode";
|
||||
if (!config.drive().has_drive())
|
||||
config.mutable_drive()->set_high_density(true);
|
||||
@@ -67,14 +68,14 @@ public:
|
||||
trackdata->set_clock_rate_khz(500);
|
||||
trackdata->set_track_length_ms(167);
|
||||
auto sectors = trackdata->mutable_sectors();
|
||||
int currentTrackCylinder = -1;
|
||||
int currentTrackTrack = -1;
|
||||
int currentTrackHead = -1;
|
||||
int trackSectorSize = -1;
|
||||
|
||||
for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++)
|
||||
{
|
||||
headerReader.seek(0x120 + track * 26 * 16 + sectorInTrack * 16);
|
||||
int cylinder = headerReader.read_8();
|
||||
int track = headerReader.read_8();
|
||||
int head = headerReader.read_8();
|
||||
int sectorId = headerReader.read_8();
|
||||
int sectorSize = 128 << headerReader.read_8();
|
||||
@@ -82,22 +83,22 @@ public:
|
||||
int ddam = headerReader.read_8();
|
||||
int status = headerReader.read_8();
|
||||
headerReader.skip(9); // skip ST0, ST1, ST2, PDA, reserved(5)
|
||||
if (cylinder == 0xFF)
|
||||
if (track == 0xFF)
|
||||
continue;
|
||||
if (ddam != 0)
|
||||
Error() << "NFD: nonzero ddam currently unsupported";
|
||||
if (status != 0)
|
||||
Error() << "NFD: nonzero fdd status codes are currently "
|
||||
"unsupported";
|
||||
if (currentTrackCylinder < 0)
|
||||
if (currentTrackTrack < 0)
|
||||
{
|
||||
currentTrackCylinder = cylinder;
|
||||
currentTrackTrack = track;
|
||||
currentTrackHead = head;
|
||||
}
|
||||
else if (currentTrackCylinder != cylinder)
|
||||
else if (currentTrackTrack != track)
|
||||
{
|
||||
Error() << "NFD: all sectors in a track must belong to the "
|
||||
"same cylinder";
|
||||
"same track";
|
||||
}
|
||||
else if (currentTrackHead != head)
|
||||
{
|
||||
@@ -109,7 +110,7 @@ public:
|
||||
trackSectorSize = sectorSize;
|
||||
// this is the first sector we've read, use it settings for
|
||||
// per-track data
|
||||
trackdata->set_cylinder(cylinder);
|
||||
trackdata->set_track(track);
|
||||
trackdata->set_head(head);
|
||||
trackdata->set_sector_size(sectorSize);
|
||||
trackdata->set_use_fm(!mfm);
|
||||
@@ -139,17 +140,17 @@ public:
|
||||
}
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*)data.begin(), data.size());
|
||||
const auto& sector = image->put(cylinder, head, sectorId);
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = cylinder;
|
||||
sector->physicalCylinder = cylinder;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
|
||||
sectors->add_sector(sectorId);
|
||||
if (config.cylinders().end() < cylinder)
|
||||
config.mutable_cylinders()->set_end(cylinder);
|
||||
if (config.tracks().end() < track)
|
||||
config.mutable_tracks()->set_end(track);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/imagereader/imagereader.pb.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
Logger() << fmt::format(
|
||||
"NSI: Autodetecting geometry based on file size: {}", fsize);
|
||||
|
||||
unsigned numCylinders = 35;
|
||||
unsigned numTracks = 35;
|
||||
unsigned numSectors = 10;
|
||||
unsigned numHeads = 2;
|
||||
unsigned sectorSize = 512;
|
||||
@@ -63,18 +64,18 @@ public:
|
||||
Logger() << fmt::format(
|
||||
"reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} "
|
||||
"kB total",
|
||||
numCylinders,
|
||||
numTracks,
|
||||
numHeads,
|
||||
numSectors,
|
||||
sectorSize,
|
||||
numCylinders * numHeads * trackSize / 1024);
|
||||
numTracks * numHeads * trackSize / 1024);
|
||||
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
unsigned sectorFileOffset;
|
||||
|
||||
for (unsigned head = 0; head < numHeads; head++)
|
||||
{
|
||||
for (unsigned track = 0; track < numCylinders; track++)
|
||||
for (unsigned track = 0; track < numTracks; track++)
|
||||
{
|
||||
for (unsigned sectorId = 0; sectorId < numSectors; sectorId++)
|
||||
{
|
||||
@@ -86,8 +87,8 @@ public:
|
||||
else
|
||||
{ /* Head 1 is from track 70-35 */
|
||||
sectorFileOffset =
|
||||
(trackSize * numCylinders) + /* Skip over side 0 */
|
||||
((numCylinders - track - 1) * trackSize) +
|
||||
(trackSize * numTracks) + /* Skip over side 0 */
|
||||
((numTracks - track - 1) * trackSize) +
|
||||
(sectorId * sectorSize); /* Sector offset from
|
||||
beginning of track. */
|
||||
}
|
||||
@@ -99,7 +100,8 @@ public:
|
||||
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = track;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalTrack = Mapper::remapTrackLogicalToPhysical(track);
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
@@ -107,7 +109,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
image->setGeometry({.numTracks = numCylinders,
|
||||
image->setGeometry({.numTracks = numTracks,
|
||||
.numSides = numHeads,
|
||||
.numSectors = numSectors,
|
||||
.sectorSize = sectorSize});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "image.h"
|
||||
#include "crc.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
@@ -111,7 +112,7 @@ public:
|
||||
if (sectorCount == 0xff)
|
||||
break;
|
||||
|
||||
uint8_t physicalCylinder = br.read_8();
|
||||
uint8_t physicalTrack = br.read_8();
|
||||
uint8_t physicalHead = br.read_8() & 1;
|
||||
br.skip(1); /* crc */
|
||||
|
||||
@@ -185,7 +186,7 @@ public:
|
||||
const auto& sector =
|
||||
image->put(logicalTrack, logicalSide, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->physicalTrack = physicalTrack;
|
||||
sector->physicalHead = physicalHead;
|
||||
sector->data = data.slice(0, sectorSize);
|
||||
totalSize += sectorSize;
|
||||
|
||||
@@ -97,7 +97,7 @@ void ImageWriter::writeCsv(const Image& image, const std::string& filename)
|
||||
for (const auto& sector : image)
|
||||
{
|
||||
f << fmt::format("{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
|
||||
sector->physicalCylinder,
|
||||
sector->physicalTrack,
|
||||
sector->physicalHead,
|
||||
sector->logicalTrack,
|
||||
sector->logicalSide,
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
Logger() << fmt::format("Writing {} cylinders, {} sides, {} sectors, {} ({} bytes/sector), {} kB total",
|
||||
Logger() << fmt::format("Writing {} tracks, {} sides, {} sectors, {} ({} bytes/sector), {} kB total",
|
||||
geometry.numTracks, geometry.numSides,
|
||||
geometry.numSectors, geometry.sectorSize == 256 ? "SD" : "DD", geometry.sectorSize,
|
||||
geometry.numTracks * geometry.numSides * geometry.numSectors * geometry.sectorSize / 1024);
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
Logger() << fmt::format("RAW: writing {} cylinders, {} sides",
|
||||
Logger() << fmt::format("RAW: writing {} tracks, {} sides",
|
||||
geometry.numTracks, geometry.numSides);
|
||||
|
||||
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);
|
||||
|
||||
@@ -62,14 +62,14 @@ std::string Logger::toString(const AnyLogMessage& message)
|
||||
/* Indicates that we're starting a write operation. */
|
||||
[&](const BeginWriteOperationLogMessage& m)
|
||||
{
|
||||
stream << fmt::format("{:2}.{}: ", m.cylinder, m.head);
|
||||
stream << fmt::format("{:2}.{}: ", m.track, m.head);
|
||||
indented = true;
|
||||
},
|
||||
|
||||
/* Indicates that we're starting a read operation. */
|
||||
[&](const BeginReadOperationLogMessage& m)
|
||||
{
|
||||
stream << fmt::format("{:2}.{}: ", m.cylinder, m.head);
|
||||
stream << fmt::format("{:2}.{}: ", m.track, m.head);
|
||||
indented = true;
|
||||
},
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ struct DiskReadLogMessage
|
||||
|
||||
struct BeginReadOperationLogMessage
|
||||
{
|
||||
unsigned cylinder;
|
||||
unsigned track;
|
||||
unsigned head;
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ struct EndReadOperationLogMessage
|
||||
|
||||
struct BeginWriteOperationLogMessage
|
||||
{
|
||||
unsigned cylinder;
|
||||
unsigned track;
|
||||
unsigned head;
|
||||
};
|
||||
|
||||
|
||||
180
lib/mapper.cc
180
lib/mapper.cc
@@ -3,89 +3,147 @@
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "logger.h"
|
||||
#include "proto.h"
|
||||
#include "mapper.h"
|
||||
#include "flux.h"
|
||||
#include "lib/mapper.pb.h"
|
||||
|
||||
typedef std::function<void(std::map<int, int>&, const SectorMappingProto::MappingProto&)> insertercb_t;
|
||||
typedef std::function<void(
|
||||
std::map<int, int>&, const SectorMappingProto::MappingProto&)>
|
||||
insertercb_t;
|
||||
|
||||
static void getTrackFormat(const SectorMappingProto& proto,
|
||||
SectorMappingProto::TrackdataProto& trackdata, unsigned track, unsigned side)
|
||||
SectorMappingProto::TrackdataProto& trackdata,
|
||||
unsigned track,
|
||||
unsigned side)
|
||||
{
|
||||
trackdata.Clear();
|
||||
for (const SectorMappingProto::TrackdataProto& f : proto.trackdata())
|
||||
{
|
||||
if (f.has_track() && f.has_up_to_track() && ((track < f.track()) || (track > f.up_to_track())))
|
||||
continue;
|
||||
if (f.has_track() && !f.has_up_to_track() && (track != f.track()))
|
||||
continue;
|
||||
if (f.has_side() && (f.side() != side))
|
||||
continue;
|
||||
trackdata.Clear();
|
||||
for (const SectorMappingProto::TrackdataProto& f : proto.trackdata())
|
||||
{
|
||||
if (f.has_track() && f.has_up_to_track() &&
|
||||
((track < f.track()) || (track > f.up_to_track())))
|
||||
continue;
|
||||
if (f.has_track() && !f.has_up_to_track() && (track != f.track()))
|
||||
continue;
|
||||
if (f.has_side() && (f.side() != side))
|
||||
continue;
|
||||
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<Image> remapImpl(const Image& source, const SectorMappingProto& mapping,
|
||||
insertercb_t inserter_cb)
|
||||
static std::unique_ptr<Image> remapImpl(const Image& source,
|
||||
const SectorMappingProto& mapping,
|
||||
insertercb_t inserter_cb)
|
||||
{
|
||||
typedef std::pair<int, int> tracksidekey_t;
|
||||
std::map<tracksidekey_t, std::map<int, int>> cache;
|
||||
typedef std::pair<int, int> tracksidekey_t;
|
||||
std::map<tracksidekey_t, std::map<int, int>> cache;
|
||||
|
||||
auto getTrackdata =
|
||||
[&](const tracksidekey_t& key) -> const std::map<int, int>& {
|
||||
auto it = cache.find(key);
|
||||
if (it != cache.end())
|
||||
return it->second;
|
||||
auto getTrackdata =
|
||||
[&](const tracksidekey_t& key) -> const std::map<int, int>&
|
||||
{
|
||||
auto it = cache.find(key);
|
||||
if (it != cache.end())
|
||||
return it->second;
|
||||
|
||||
SectorMappingProto::TrackdataProto trackdata;
|
||||
getTrackFormat(mapping, trackdata, key.first, key.second);
|
||||
SectorMappingProto::TrackdataProto trackdata;
|
||||
getTrackFormat(mapping, trackdata, key.first, key.second);
|
||||
|
||||
auto& map = cache[key];
|
||||
for (const auto mappingsit : trackdata.mapping())
|
||||
inserter_cb(map, mappingsit);
|
||||
auto& map = cache[key];
|
||||
for (const auto mappingsit : trackdata.mapping())
|
||||
inserter_cb(map, mappingsit);
|
||||
|
||||
return map;
|
||||
};
|
||||
return map;
|
||||
};
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> destSectors;
|
||||
for (const auto& sector : source)
|
||||
{
|
||||
tracksidekey_t key = { sector->logicalTrack, sector->logicalSide };
|
||||
const auto& trackdata = getTrackdata(key);
|
||||
if (trackdata.empty())
|
||||
destSectors.insert(sector);
|
||||
else
|
||||
{
|
||||
auto it = trackdata.find(sector->logicalSector);
|
||||
if (it == trackdata.end())
|
||||
Error() << fmt::format("mapping requested but mapping table has no entry for sector {}", sector->logicalSector);
|
||||
std::set<std::shared_ptr<const Sector>> destSectors;
|
||||
for (const auto& sector : source)
|
||||
{
|
||||
tracksidekey_t key = {sector->logicalTrack, sector->logicalSide};
|
||||
const auto& trackdata = getTrackdata(key);
|
||||
if (trackdata.empty())
|
||||
destSectors.insert(sector);
|
||||
else
|
||||
{
|
||||
auto it = trackdata.find(sector->logicalSector);
|
||||
if (it == trackdata.end())
|
||||
Error() << fmt::format(
|
||||
"mapping requested but mapping table has no entry for "
|
||||
"sector {}",
|
||||
sector->logicalSector);
|
||||
|
||||
auto newSector = std::make_shared<Sector>(*sector);
|
||||
newSector->logicalSector = it->second;
|
||||
destSectors.insert(newSector);
|
||||
}
|
||||
}
|
||||
auto newSector = std::make_shared<Sector>(*sector);
|
||||
newSector->logicalSector = it->second;
|
||||
destSectors.insert(newSector);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<Image>(destSectors);
|
||||
return std::make_unique<Image>(destSectors);
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Mapper::remapPhysicalToLogical(const Image& source, const SectorMappingProto& mapping)
|
||||
std::unique_ptr<const Image> Mapper::remapSectorsPhysicalToLogical(
|
||||
const Image& source, const SectorMappingProto& mapping)
|
||||
{
|
||||
Logger() << "remapping sectors from physical IDs to logical IDs";
|
||||
return remapImpl(source, mapping,
|
||||
[](auto& map, const auto& pair)
|
||||
{
|
||||
map.insert({ pair.physical(), pair.logical() });
|
||||
});
|
||||
Logger() << "remapping sectors from physical IDs to logical IDs";
|
||||
return remapImpl(source,
|
||||
mapping,
|
||||
[](auto& map, const auto& pair)
|
||||
{
|
||||
map.insert({pair.physical(), pair.logical()});
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Mapper::remapLogicalToPhysical(const Image& source, const SectorMappingProto& mapping)
|
||||
std::unique_ptr<const Image> Mapper::remapSectorsLogicalToPhysical(
|
||||
const Image& source, const SectorMappingProto& mapping)
|
||||
{
|
||||
Logger() << "remapping sectors from logical IDs to physical IDs";
|
||||
return remapImpl(source, mapping,
|
||||
[](auto& map, const auto& pair)
|
||||
{
|
||||
map.insert({ pair.logical(), pair.physical() });
|
||||
});
|
||||
Logger() << "remapping sectors from logical IDs to physical IDs";
|
||||
return remapImpl(source,
|
||||
mapping,
|
||||
[](auto& map, const auto& pair)
|
||||
{
|
||||
map.insert({pair.logical(), pair.physical()});
|
||||
});
|
||||
}
|
||||
|
||||
unsigned Mapper::remapTrackPhysicalToLogical(unsigned ptrack)
|
||||
{
|
||||
return (ptrack - config.drive().head_bias()) /
|
||||
config.drive().head_width();
|
||||
}
|
||||
|
||||
static unsigned getTrackStep()
|
||||
{
|
||||
unsigned track_step =
|
||||
(config.tpi() == 0) ? 1 : (config.drive().tpi() / config.tpi());
|
||||
|
||||
if (track_step == 0)
|
||||
Error()
|
||||
<< "this drive can't write this image, because the head is too big";
|
||||
return track_step;
|
||||
}
|
||||
|
||||
unsigned Mapper::remapTrackLogicalToPhysical(unsigned ltrack)
|
||||
{
|
||||
return config.drive().head_bias() + ltrack*getTrackStep();
|
||||
}
|
||||
|
||||
std::set<Location> Mapper::computeLocations()
|
||||
{
|
||||
std::set<Location> locations;
|
||||
|
||||
unsigned track_step = getTrackStep();
|
||||
for (unsigned logicalTrack : iterate(config.tracks()))
|
||||
{
|
||||
for (unsigned head : iterate(config.heads()))
|
||||
{
|
||||
unsigned physicalTrack = config.drive().head_bias() + logicalTrack * track_step;
|
||||
|
||||
locations.insert({.physicalTrack = physicalTrack,
|
||||
.logicalTrack = logicalTrack,
|
||||
.head = head,
|
||||
.groupSize = track_step});
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
14
lib/mapper.h
14
lib/mapper.h
@@ -2,13 +2,21 @@
|
||||
#define MAPPER_H
|
||||
|
||||
class SectorMappingProto;
|
||||
class DriveProto;
|
||||
class Location;
|
||||
|
||||
class Mapper
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<Image> remapPhysicalToLogical(const Image& source, const SectorMappingProto& mapping);
|
||||
static std::unique_ptr<Image> remapLogicalToPhysical(const Image& source, const SectorMappingProto& mapping);
|
||||
static std::unique_ptr<const Image> remapSectorsPhysicalToLogical(
|
||||
const Image& source, const SectorMappingProto& mapping);
|
||||
static std::unique_ptr<const Image> remapSectorsLogicalToPhysical(
|
||||
const Image& source, const SectorMappingProto& mapping);
|
||||
|
||||
static unsigned remapTrackPhysicalToLogical(unsigned track);
|
||||
static unsigned remapTrackLogicalToPhysical(unsigned track);
|
||||
|
||||
static std::set<Location> computeLocations();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
271
lib/reader.cc
271
lib/reader.cc
@@ -1,271 +0,0 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "usb/usb.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "fluxsink/fluxsink.h"
|
||||
#include "reader.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "sector.h"
|
||||
#include "bytes.h"
|
||||
#include "decoders/rawbits.h"
|
||||
#include "flux.h"
|
||||
#include "image.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "logger.h"
|
||||
#include "fmt/format.h"
|
||||
#include "proto.h"
|
||||
#include "utils.h"
|
||||
#include "mapper.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
static std::unique_ptr<FluxSink> outputFluxSink;
|
||||
|
||||
static std::shared_ptr<const Fluxmap> readFluxmap(FluxSourceIterator& fluxsourceIterator, unsigned cylinder, unsigned head)
|
||||
{
|
||||
Logger() << BeginReadOperationLogMessage { cylinder, head };
|
||||
auto fluxmap = fluxsourceIterator.next()->rescale(1.0/config.flux_source().rescale());
|
||||
Logger() << EndReadOperationLogMessage()
|
||||
<< fmt::format("{0:.0} ms in {1} bytes", fluxmap->duration()/1e6, fluxmap->bytes());
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
static bool conflictable(Sector::Status status)
|
||||
{
|
||||
return (status == Sector::OK) || (status == Sector::CONFLICT);
|
||||
}
|
||||
|
||||
static std::set<std::shared_ptr<const Sector>> collect_sectors(
|
||||
std::set<std::shared_ptr<const Sector>>& track_sectors, bool collapse_conflicts = true)
|
||||
{
|
||||
typedef std::tuple<unsigned, unsigned, unsigned> key_t;
|
||||
std::multimap<key_t, std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
for (const auto& sector : track_sectors)
|
||||
{
|
||||
key_t sectorid = {
|
||||
sector->logicalTrack, sector->logicalSide, sector->logicalSector};
|
||||
sectors.insert({sectorid, sector});
|
||||
}
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> sector_set;
|
||||
auto it = sectors.begin();
|
||||
while (it != sectors.end())
|
||||
{
|
||||
auto ub = sectors.upper_bound(it->first);
|
||||
auto new_sector = std::accumulate(it,
|
||||
ub,
|
||||
it->second,
|
||||
[&](auto left, auto& rightit) -> std::shared_ptr<const Sector>
|
||||
{
|
||||
auto& right = rightit.second;
|
||||
if ((left->status == Sector::OK) &&
|
||||
(right->status == Sector::OK) &&
|
||||
(left->data != right->data))
|
||||
{
|
||||
if (!collapse_conflicts)
|
||||
{
|
||||
auto s = std::make_shared<Sector>(*right);
|
||||
s->status = Sector::CONFLICT;
|
||||
sector_set.insert(s);
|
||||
}
|
||||
auto s = std::make_shared<Sector>(*left);
|
||||
s->status = Sector::CONFLICT;
|
||||
return s;
|
||||
}
|
||||
if (left->status == Sector::CONFLICT)
|
||||
return left;
|
||||
if (right->status == Sector::CONFLICT)
|
||||
return right;
|
||||
if (left->status == Sector::OK)
|
||||
return left;
|
||||
if (right->status == Sector::OK)
|
||||
return right;
|
||||
return left;
|
||||
});
|
||||
sector_set.insert(new_sector);
|
||||
it = ub;
|
||||
}
|
||||
return sector_set;
|
||||
}
|
||||
|
||||
std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder)
|
||||
{
|
||||
if (config.decoder().has_copy_flux_to())
|
||||
outputFluxSink = FluxSink::create(config.decoder().copy_flux_to());
|
||||
|
||||
auto diskflux = std::make_shared<DiskFlux>();
|
||||
bool failures = false;
|
||||
for (int cylinder : iterate(config.cylinders()))
|
||||
{
|
||||
for (int head : iterate(config.heads()))
|
||||
{
|
||||
testForEmergencyStop();
|
||||
|
||||
auto track = std::make_shared<TrackFlux>();
|
||||
track->physicalCylinder = cylinder;
|
||||
track->physicalHead = head;
|
||||
diskflux->tracks.push_back(track);
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> track_sectors;
|
||||
std::set<std::shared_ptr<const Record>> track_records;
|
||||
Fluxmap totalFlux;
|
||||
|
||||
auto fluxsourceIterator = fluxsource.readFlux(cylinder, head);
|
||||
int retriesRemaining = config.decoder().retries();
|
||||
while (fluxsourceIterator->hasNext())
|
||||
{
|
||||
auto fluxmap = readFluxmap(*fluxsourceIterator, cylinder, head);
|
||||
totalFlux.appendDesync().appendBytes(fluxmap->rawBytes());
|
||||
|
||||
auto trackdataflux =
|
||||
decoder.decodeToSectors(fluxmap, cylinder, head);
|
||||
track->trackDatas.push_back(trackdataflux);
|
||||
|
||||
track_sectors.insert(trackdataflux->sectors.begin(),
|
||||
trackdataflux->sectors.end());
|
||||
track_records.insert(trackdataflux->records.begin(),
|
||||
trackdataflux->records.end());
|
||||
|
||||
bool hasBadSectors = false;
|
||||
std::set<unsigned> required_sectors =
|
||||
decoder.requiredSectors(cylinder, head);
|
||||
std::set<std::shared_ptr<const Sector>> result_sectors;
|
||||
for (const auto& sector : collect_sectors(track_sectors))
|
||||
{
|
||||
result_sectors.insert(sector);
|
||||
required_sectors.erase(sector->logicalSector);
|
||||
|
||||
if (sector->status != Sector::OK)
|
||||
hasBadSectors = true;
|
||||
}
|
||||
for (unsigned logical_sector : required_sectors)
|
||||
{
|
||||
auto sector = std::make_shared<Sector>();
|
||||
sector->logicalSector = logical_sector;
|
||||
sector->status = Sector::MISSING;
|
||||
result_sectors.insert(sector);
|
||||
|
||||
hasBadSectors = true;
|
||||
}
|
||||
|
||||
track->sectors = collect_sectors(result_sectors);
|
||||
|
||||
/* track can't be modified below this point. */
|
||||
Logger() << TrackReadLogMessage { track };
|
||||
|
||||
if (hasBadSectors)
|
||||
failures = false;
|
||||
|
||||
if (!hasBadSectors)
|
||||
break;
|
||||
|
||||
if (!fluxsourceIterator->hasNext())
|
||||
break;
|
||||
if (fluxsource.isHardware())
|
||||
{
|
||||
retriesRemaining--;
|
||||
if (retriesRemaining == 0)
|
||||
{
|
||||
Logger() << fmt::format("giving up");
|
||||
break;
|
||||
}
|
||||
else
|
||||
Logger()
|
||||
<< fmt::format("retrying; {} retries remaining", retriesRemaining);
|
||||
}
|
||||
}
|
||||
|
||||
if (outputFluxSink)
|
||||
outputFluxSink->writeFlux(cylinder, head, totalFlux);
|
||||
|
||||
if (config.decoder().dump_records())
|
||||
{
|
||||
std::vector<std::shared_ptr<const Record>> sorted_records(track_records.begin(), track_records.end());
|
||||
std::sort(sorted_records.begin(), sorted_records.end(),
|
||||
[](const auto& o1, const auto& o2) {
|
||||
return o1->startTime < o2->startTime;
|
||||
});
|
||||
|
||||
std::cout << "\nRaw (undecoded) records follow:\n\n";
|
||||
for (const auto& record : sorted_records)
|
||||
{
|
||||
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
|
||||
record->startTime / 1000.0,
|
||||
record->clock / 1000.0);
|
||||
hexdump(std::cout, record->rawData);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.decoder().dump_sectors())
|
||||
{
|
||||
auto collected_sectors = collect_sectors(track_sectors, false);
|
||||
std::vector<std::shared_ptr<const Sector>> sorted_sectors(collected_sectors.begin(), collected_sectors.end());
|
||||
std::sort(sorted_sectors.begin(), sorted_sectors.end(),
|
||||
[](const auto& o1, const auto& o2) {
|
||||
return *o1 < *o2;
|
||||
});
|
||||
|
||||
std::cout << "\nDecoded sectors follow:\n\n";
|
||||
for (const auto& sector : sorted_sectors)
|
||||
{
|
||||
std::cout << fmt::format(
|
||||
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
|
||||
"status {}\n",
|
||||
sector->logicalTrack,
|
||||
sector->logicalSide,
|
||||
sector->logicalSector,
|
||||
sector->headerStartTime / 1000.0,
|
||||
sector->clock / 1000.0,
|
||||
Sector::statusToString(sector->status));
|
||||
hexdump(std::cout, sector->data);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures)
|
||||
Logger() << "Warning: some sectors could not be decoded.";
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> all_sectors;
|
||||
for (auto& track : diskflux->tracks)
|
||||
for (auto& sector : track->sectors)
|
||||
all_sectors.insert(sector);
|
||||
all_sectors = collect_sectors(all_sectors);
|
||||
diskflux->image = std::make_shared<Image>(all_sectors);
|
||||
|
||||
if (config.has_sector_mapping())
|
||||
diskflux->image = std::move(Mapper::remapPhysicalToLogical(*diskflux->image, config.sector_mapping()));
|
||||
|
||||
/* diskflux can't be modified below this point. */
|
||||
Logger() << DiskReadLogMessage { diskflux };
|
||||
return diskflux;
|
||||
}
|
||||
|
||||
void readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer)
|
||||
{
|
||||
auto diskflux = readDiskCommand(fluxsource, decoder);
|
||||
|
||||
writer.printMap(*diskflux->image);
|
||||
if (config.decoder().has_write_csv_to())
|
||||
writer.writeCsv(*diskflux->image, config.decoder().write_csv_to());
|
||||
writer.writeImage(*diskflux->image);
|
||||
}
|
||||
|
||||
void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
|
||||
{
|
||||
for (int cylinder : iterate(config.cylinders()))
|
||||
{
|
||||
for (int head : iterate(config.heads()))
|
||||
{
|
||||
testForEmergencyStop();
|
||||
auto fluxsourceIterator = fluxsource.readFlux(cylinder, head);
|
||||
auto fluxmap = readFluxmap(*fluxsourceIterator, cylinder, head);
|
||||
fluxsink.writeFlux(cylinder, head, *fluxmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
lib/reader.h
19
lib/reader.h
@@ -1,19 +0,0 @@
|
||||
#ifndef READER_H
|
||||
#define READER_H
|
||||
|
||||
class AbstractDecoder;
|
||||
class DiskFlux;
|
||||
class FluxSink;
|
||||
class FluxSource;
|
||||
class Fluxmap;
|
||||
class ImageWriter;
|
||||
class TrackDataFlux;
|
||||
|
||||
extern std::unique_ptr<TrackDataFlux> readAndDecodeTrack(
|
||||
FluxSource& source, AbstractDecoder& decoder, unsigned cylinder, unsigned head);
|
||||
|
||||
extern std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder);
|
||||
extern void readDiskCommand(FluxSource& source, AbstractDecoder& decoder, ImageWriter& writer);
|
||||
extern void rawReadDiskCommand(FluxSource& source, FluxSink& sink);
|
||||
|
||||
#endif
|
||||
503
lib/readerwriter.cc
Normal file
503
lib/readerwriter.cc
Normal file
@@ -0,0 +1,503 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "readerwriter.h"
|
||||
#include "protocol.h"
|
||||
#include "usb/usb.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "fluxsink/fluxsink.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "utils.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "proto.h"
|
||||
#include <optional>
|
||||
|
||||
enum ReadResult
|
||||
{
|
||||
GOOD_READ,
|
||||
BAD_AND_CAN_RETRY,
|
||||
BAD_AND_CAN_NOT_RETRY
|
||||
};
|
||||
|
||||
/* In order to allow rereads in file-based flux sources, we need to persist the
|
||||
* FluxSourceIterator (as that's where the state for which read to return is
|
||||
* held). This class handles that. */
|
||||
|
||||
class FluxSourceIteratorHolder
|
||||
{
|
||||
public:
|
||||
FluxSourceIteratorHolder(FluxSource& fluxSource): _fluxSource(fluxSource) {}
|
||||
|
||||
FluxSourceIterator& getIterator(unsigned physicalCylinder, unsigned head)
|
||||
{
|
||||
auto& it = _cache[std::make_pair(physicalCylinder, head)];
|
||||
if (!it)
|
||||
it = _fluxSource.readFlux(physicalCylinder, head);
|
||||
return *it;
|
||||
}
|
||||
|
||||
private:
|
||||
FluxSource& _fluxSource;
|
||||
std::map<std::pair<unsigned, unsigned>, std::unique_ptr<FluxSourceIterator>>
|
||||
_cache;
|
||||
};
|
||||
|
||||
/* Given a set of sectors, deduplicates them sensibly (e.g. if there is a good
|
||||
* and bad version of the same sector, the bad version is dropped). */
|
||||
|
||||
static std::set<std::shared_ptr<const Sector>> collectSectors(
|
||||
std::set<std::shared_ptr<const Sector>>& track_sectors,
|
||||
bool collapse_conflicts = true)
|
||||
{
|
||||
typedef std::tuple<unsigned, unsigned, unsigned> key_t;
|
||||
std::multimap<key_t, std::shared_ptr<const Sector>> sectors;
|
||||
|
||||
for (const auto& sector : track_sectors)
|
||||
{
|
||||
key_t sectorid = {
|
||||
sector->logicalTrack, sector->logicalSide, sector->logicalSector};
|
||||
sectors.insert({sectorid, sector});
|
||||
}
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> sector_set;
|
||||
auto it = sectors.begin();
|
||||
while (it != sectors.end())
|
||||
{
|
||||
auto ub = sectors.upper_bound(it->first);
|
||||
auto new_sector = std::accumulate(it,
|
||||
ub,
|
||||
it->second,
|
||||
[&](auto left, auto& rightit) -> std::shared_ptr<const Sector>
|
||||
{
|
||||
auto& right = rightit.second;
|
||||
if ((left->status == Sector::OK) &&
|
||||
(right->status == Sector::OK) &&
|
||||
(left->data != right->data))
|
||||
{
|
||||
if (!collapse_conflicts)
|
||||
{
|
||||
auto s = std::make_shared<Sector>(*right);
|
||||
s->status = Sector::CONFLICT;
|
||||
sector_set.insert(s);
|
||||
}
|
||||
auto s = std::make_shared<Sector>(*left);
|
||||
s->status = Sector::CONFLICT;
|
||||
return s;
|
||||
}
|
||||
if (left->status == Sector::CONFLICT)
|
||||
return left;
|
||||
if (right->status == Sector::CONFLICT)
|
||||
return right;
|
||||
if (left->status == Sector::OK)
|
||||
return left;
|
||||
if (right->status == Sector::OK)
|
||||
return right;
|
||||
return left;
|
||||
});
|
||||
sector_set.insert(new_sector);
|
||||
it = ub;
|
||||
}
|
||||
return sector_set;
|
||||
}
|
||||
|
||||
/* Returns true if the result contains bad sectors. */
|
||||
bool combineRecordAndSectors(
|
||||
TrackFlux& trackFlux, AbstractDecoder& decoder, const Location& location)
|
||||
{
|
||||
std::set<std::shared_ptr<const Sector>> track_sectors;
|
||||
|
||||
for (auto& trackdataflux : trackFlux.trackDatas)
|
||||
track_sectors.insert(
|
||||
trackdataflux->sectors.begin(), trackdataflux->sectors.end());
|
||||
|
||||
for (unsigned logical_sector : decoder.requiredSectors(trackFlux.location))
|
||||
{
|
||||
auto sector = std::make_shared<Sector>(location);
|
||||
sector->logicalSector = logical_sector;
|
||||
sector->status = Sector::MISSING;
|
||||
track_sectors.insert(sector);
|
||||
}
|
||||
|
||||
trackFlux.sectors = collectSectors(track_sectors);
|
||||
if (trackFlux.sectors.empty())
|
||||
return true;
|
||||
for (const auto& sector : trackFlux.sectors)
|
||||
if (sector->status != Sector::OK)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder,
|
||||
const Location& location,
|
||||
TrackFlux& trackFlux,
|
||||
AbstractDecoder& decoder)
|
||||
{
|
||||
ReadResult result = BAD_AND_CAN_NOT_RETRY;
|
||||
|
||||
for (unsigned offset = 0; offset < location.groupSize;
|
||||
offset += config.drive().head_width())
|
||||
{
|
||||
auto& fluxSourceIterator = fluxSourceIteratorHolder.getIterator(
|
||||
location.physicalTrack + offset, location.head);
|
||||
if (!fluxSourceIterator.hasNext())
|
||||
continue;
|
||||
|
||||
Logger() << BeginReadOperationLogMessage{
|
||||
location.physicalTrack + offset, location.head};
|
||||
std::shared_ptr<const Fluxmap> fluxmap =
|
||||
fluxSourceIterator.next()->rescale(
|
||||
1.0 / config.flux_source().rescale());
|
||||
Logger() << EndReadOperationLogMessage()
|
||||
<< fmt::format("{0:.0} ms in {1} bytes",
|
||||
fluxmap->duration() / 1e6,
|
||||
fluxmap->bytes());
|
||||
|
||||
auto trackdataflux = decoder.decodeToSectors(fluxmap, location);
|
||||
trackFlux.trackDatas.push_back(trackdataflux);
|
||||
if (!combineRecordAndSectors(trackFlux, decoder, location))
|
||||
return GOOD_READ;
|
||||
if (fluxSourceIterator.hasNext())
|
||||
result = BAD_AND_CAN_RETRY;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void writeTracks(FluxSink& fluxSink,
|
||||
std::function<std::unique_ptr<const Fluxmap>(const Location& location)>
|
||||
producer,
|
||||
std::function<bool(const Location& location)> verifier)
|
||||
{
|
||||
Logger() << fmt::format("Writing to: {}", (std::string)fluxSink);
|
||||
|
||||
for (const auto& location : Mapper::computeLocations())
|
||||
{
|
||||
testForEmergencyStop();
|
||||
|
||||
int retriesRemaining = config.decoder().retries();
|
||||
for (;;)
|
||||
{
|
||||
for (unsigned offset = 0; offset < location.groupSize;
|
||||
offset += config.drive().head_width())
|
||||
{
|
||||
unsigned physicalTrack = location.physicalTrack + offset;
|
||||
|
||||
Logger() << BeginWriteOperationLogMessage{
|
||||
physicalTrack, location.head};
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
auto fluxmap = producer(location);
|
||||
if (!fluxmap)
|
||||
goto erase;
|
||||
|
||||
auto scaled =
|
||||
fluxmap->rescale(config.flux_sink().rescale());
|
||||
/* Precompensation actually seems to make things worse, so
|
||||
* let's leave it disabled for now. */
|
||||
// fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS,
|
||||
// 2);
|
||||
fluxSink.writeFlux(physicalTrack, location.head, *scaled);
|
||||
Logger() << fmt::format("writing {0} ms in {1} bytes",
|
||||
int(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
}
|
||||
else
|
||||
{
|
||||
erase:
|
||||
/* Erase this track rather than writing. */
|
||||
|
||||
Fluxmap blank;
|
||||
fluxSink.writeFlux(physicalTrack, location.head, blank);
|
||||
Logger() << "erased";
|
||||
}
|
||||
|
||||
Logger() << EndWriteOperationLogMessage();
|
||||
}
|
||||
|
||||
if (verifier(location))
|
||||
break;
|
||||
|
||||
if (retriesRemaining == 0)
|
||||
Error() << "fatal error on write";
|
||||
|
||||
Logger() << fmt::format(
|
||||
"retrying; {} retries remaining", retriesRemaining);
|
||||
retriesRemaining--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool dontVerify(const Location&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeTracks(
|
||||
FluxSink& fluxSink, AbstractEncoder& encoder, const Image& image)
|
||||
{
|
||||
writeTracks(
|
||||
fluxSink,
|
||||
[&](const Location& location)
|
||||
{
|
||||
auto sectors = encoder.collectSectors(location, image);
|
||||
return encoder.encode(location, sectors, image);
|
||||
},
|
||||
dontVerify);
|
||||
}
|
||||
|
||||
void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSource& fluxSource,
|
||||
AbstractDecoder& decoder,
|
||||
const Image& image)
|
||||
{
|
||||
writeTracks(
|
||||
fluxSink,
|
||||
[&](const Location& location)
|
||||
{
|
||||
auto sectors = encoder.collectSectors(location, image);
|
||||
return encoder.encode(location, sectors, image);
|
||||
},
|
||||
[&](const Location& location)
|
||||
{
|
||||
auto trackFlux = std::make_shared<TrackFlux>();
|
||||
trackFlux->location = location;
|
||||
FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource);
|
||||
readGroup(fluxSourceIteratorHolder, location, *trackFlux, decoder);
|
||||
Logger() << TrackReadLogMessage{trackFlux};
|
||||
|
||||
auto wantedSectors = encoder.collectSectors(location, image);
|
||||
std::sort(wantedSectors.begin(),
|
||||
wantedSectors.end(),
|
||||
sectorPointerSortPredicate);
|
||||
|
||||
std::vector<std::shared_ptr<const Sector>> gotSectors(
|
||||
trackFlux->sectors.begin(), trackFlux->sectors.end());
|
||||
std::sort(gotSectors.begin(),
|
||||
gotSectors.end(),
|
||||
sectorPointerSortPredicate);
|
||||
|
||||
return std::equal(gotSectors.begin(),
|
||||
gotSectors.end(),
|
||||
wantedSectors.begin(),
|
||||
wantedSectors.end(),
|
||||
sectorPointerEqualsPredicate);
|
||||
});
|
||||
}
|
||||
|
||||
void writeDiskCommand(std::shared_ptr<const Image> image,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder,
|
||||
FluxSource* fluxSource)
|
||||
{
|
||||
if (config.has_sector_mapping())
|
||||
image = std::move(Mapper::remapSectorsLogicalToPhysical(
|
||||
*image, config.sector_mapping()));
|
||||
|
||||
if (fluxSource && decoder)
|
||||
writeTracksAndVerify(fluxSink, encoder, *fluxSource, *decoder, *image);
|
||||
else
|
||||
writeTracks(fluxSink, encoder, *image);
|
||||
}
|
||||
|
||||
void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink)
|
||||
{
|
||||
writeTracks(
|
||||
fluxSink,
|
||||
[&](const Location& location)
|
||||
{
|
||||
return fluxSource.readFlux(location.physicalTrack, location.head)
|
||||
->next();
|
||||
},
|
||||
dontVerify);
|
||||
}
|
||||
|
||||
std::shared_ptr<const DiskFlux> readDiskCommand(
|
||||
FluxSource& fluxSource, AbstractDecoder& decoder)
|
||||
{
|
||||
std::unique_ptr<FluxSink> outputFluxSink;
|
||||
if (config.decoder().has_copy_flux_to())
|
||||
outputFluxSink = FluxSink::create(config.decoder().copy_flux_to());
|
||||
|
||||
auto diskflux = std::make_shared<DiskFlux>();
|
||||
bool failures = false;
|
||||
|
||||
FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource);
|
||||
for (const auto& location : Mapper::computeLocations())
|
||||
{
|
||||
testForEmergencyStop();
|
||||
|
||||
auto trackFlux = std::make_shared<TrackFlux>();
|
||||
trackFlux->location = location;
|
||||
diskflux->tracks.push_back(trackFlux);
|
||||
|
||||
int retriesRemaining = config.decoder().retries();
|
||||
for (;;)
|
||||
{
|
||||
auto result = readGroup(
|
||||
fluxSourceIteratorHolder, location, *trackFlux, decoder);
|
||||
if (result == GOOD_READ)
|
||||
break;
|
||||
if (result == BAD_AND_CAN_NOT_RETRY)
|
||||
{
|
||||
failures = true;
|
||||
Logger() << fmt::format("no more data; giving up");
|
||||
break;
|
||||
}
|
||||
|
||||
if (retriesRemaining == 0)
|
||||
{
|
||||
failures = true;
|
||||
Logger() << fmt::format("giving up");
|
||||
break;
|
||||
}
|
||||
|
||||
Logger() << fmt::format(
|
||||
"retrying; {} retries remaining", retriesRemaining);
|
||||
retriesRemaining--;
|
||||
}
|
||||
|
||||
if (outputFluxSink)
|
||||
{
|
||||
for (const auto& data : trackFlux->trackDatas)
|
||||
outputFluxSink->writeFlux(
|
||||
location.physicalTrack, location.head, *data->fluxmap);
|
||||
}
|
||||
|
||||
if (config.decoder().dump_records())
|
||||
{
|
||||
std::vector<std::shared_ptr<const Record>> sorted_records;
|
||||
|
||||
for (const auto& data : trackFlux->trackDatas)
|
||||
sorted_records.insert(sorted_records.end(),
|
||||
data->records.begin(),
|
||||
data->records.end());
|
||||
|
||||
std::sort(sorted_records.begin(),
|
||||
sorted_records.end(),
|
||||
[](const auto& o1, const auto& o2)
|
||||
{
|
||||
return o1->startTime < o2->startTime;
|
||||
});
|
||||
|
||||
std::cout << "\nRaw (undecoded) records follow:\n\n";
|
||||
for (const auto& record : sorted_records)
|
||||
{
|
||||
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
|
||||
record->startTime / 1000.0,
|
||||
record->clock / 1000.0);
|
||||
hexdump(std::cout, record->rawData);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.decoder().dump_sectors())
|
||||
{
|
||||
auto collected_sectors = collectSectors(trackFlux->sectors, false);
|
||||
std::vector<std::shared_ptr<const Sector>> sorted_sectors(
|
||||
collected_sectors.begin(), collected_sectors.end());
|
||||
std::sort(sorted_sectors.begin(),
|
||||
sorted_sectors.end(),
|
||||
[](const auto& o1, const auto& o2)
|
||||
{
|
||||
return *o1 < *o2;
|
||||
});
|
||||
|
||||
std::cout << "\nDecoded sectors follow:\n\n";
|
||||
for (const auto& sector : sorted_sectors)
|
||||
{
|
||||
std::cout << fmt::format(
|
||||
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
|
||||
"status {}\n",
|
||||
sector->logicalTrack,
|
||||
sector->logicalSide,
|
||||
sector->logicalSector,
|
||||
sector->headerStartTime / 1000.0,
|
||||
sector->clock / 1000.0,
|
||||
Sector::statusToString(sector->status));
|
||||
hexdump(std::cout, sector->data);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/* track can't be modified below this point. */
|
||||
Logger() << TrackReadLogMessage{trackFlux};
|
||||
}
|
||||
|
||||
if (failures)
|
||||
Logger() << "Warning: some sectors could not be decoded.";
|
||||
|
||||
std::set<std::shared_ptr<const Sector>> all_sectors;
|
||||
for (auto& track : diskflux->tracks)
|
||||
for (auto& sector : track->sectors)
|
||||
all_sectors.insert(sector);
|
||||
all_sectors = collectSectors(all_sectors);
|
||||
diskflux->image = std::make_shared<Image>(all_sectors);
|
||||
|
||||
if (config.has_sector_mapping())
|
||||
diskflux->image = std::move(Mapper::remapSectorsPhysicalToLogical(
|
||||
*diskflux->image, config.sector_mapping()));
|
||||
|
||||
/* diskflux can't be modified below this point. */
|
||||
Logger() << DiskReadLogMessage{diskflux};
|
||||
return diskflux;
|
||||
}
|
||||
|
||||
void readDiskCommand(
|
||||
FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer)
|
||||
{
|
||||
auto diskflux = readDiskCommand(fluxsource, decoder);
|
||||
|
||||
writer.printMap(*diskflux->image);
|
||||
if (config.decoder().has_write_csv_to())
|
||||
writer.writeCsv(*diskflux->image, config.decoder().write_csv_to());
|
||||
writer.writeImage(*diskflux->image);
|
||||
}
|
||||
|
||||
void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
|
||||
{
|
||||
for (unsigned track : iterate(config.tracks()))
|
||||
{
|
||||
for (unsigned head : iterate(config.heads()))
|
||||
{
|
||||
testForEmergencyStop();
|
||||
auto fluxSourceIterator = fluxsource.readFlux(track, head);
|
||||
|
||||
Logger() << BeginReadOperationLogMessage{track, head};
|
||||
auto fluxmap = fluxSourceIterator->next()->rescale(
|
||||
1.0 / config.flux_source().rescale());
|
||||
Logger() << EndReadOperationLogMessage()
|
||||
<< fmt::format("{0:.0} ms in {1} bytes",
|
||||
fluxmap->duration() / 1e6,
|
||||
fluxmap->bytes());
|
||||
|
||||
fluxsink.writeFlux(track, head, *fluxmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
unsigned& cursor,
|
||||
unsigned terminateAt,
|
||||
const std::vector<bool>& pattern)
|
||||
{
|
||||
while (cursor < terminateAt)
|
||||
{
|
||||
for (bool b : pattern)
|
||||
{
|
||||
if (cursor < bitmap.size())
|
||||
bitmap[cursor++] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
#ifndef WRITER_H
|
||||
#define WRITER_H
|
||||
|
||||
class Fluxmap;
|
||||
class AbstractDecoder;
|
||||
class AbstractEncoder;
|
||||
class ImageReader;
|
||||
class FluxSource;
|
||||
class DiskFlux;
|
||||
class FluxSink;
|
||||
class FluxSource;
|
||||
class Fluxmap;
|
||||
class Image;
|
||||
class ImageReader;
|
||||
class ImageWriter;
|
||||
class Location;
|
||||
class TrackDataFlux;
|
||||
|
||||
extern void writeTracks(FluxSink& fluxSink,
|
||||
const std::function<std::unique_ptr<const Fluxmap>(int track, int side)>
|
||||
const std::function<std::unique_ptr<const Fluxmap>(const Location& location)>
|
||||
producer);
|
||||
|
||||
extern void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
@@ -26,4 +30,11 @@ extern void writeDiskCommand(std::shared_ptr<const Image> image,
|
||||
|
||||
extern void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink);
|
||||
|
||||
extern std::unique_ptr<TrackDataFlux> readAndDecodeTrack(
|
||||
FluxSource& source, AbstractDecoder& decoder, unsigned track, unsigned head);
|
||||
|
||||
extern std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder);
|
||||
extern void readDiskCommand(FluxSource& source, AbstractDecoder& decoder, ImageWriter& writer);
|
||||
extern void rawReadDiskCommand(FluxSource& source, FluxSink& sink);
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,15 @@
|
||||
#include "globals.h"
|
||||
#include "flux.h"
|
||||
#include "sector.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
Sector::Sector(const Location& location):
|
||||
physicalTrack(location.physicalTrack),
|
||||
physicalHead(location.head),
|
||||
logicalTrack(location.logicalTrack),
|
||||
logicalSide(location.head)
|
||||
{}
|
||||
|
||||
std::string Sector::statusToString(Status status)
|
||||
{
|
||||
switch (status)
|
||||
@@ -55,14 +63,14 @@ Sector::Status Sector::stringToStatus(const std::string& value)
|
||||
return Status::INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
bool sectorPointerSortPredicate(
|
||||
std::shared_ptr<const Sector>& lhs, std::shared_ptr<const Sector>& rhs)
|
||||
bool sectorPointerSortPredicate(const std::shared_ptr<const Sector>& lhs,
|
||||
const std::shared_ptr<const Sector>& rhs)
|
||||
{
|
||||
return *lhs < *rhs;
|
||||
}
|
||||
|
||||
bool sectorPointerEqualsPredicate(
|
||||
std::shared_ptr<const Sector>& lhs, std::shared_ptr<const Sector>& rhs)
|
||||
bool sectorPointerEqualsPredicate(const std::shared_ptr<const Sector>& lhs,
|
||||
const std::shared_ptr<const Sector>& rhs)
|
||||
{
|
||||
if (!lhs && !rhs)
|
||||
return true;
|
||||
|
||||
13
lib/sector.h
13
lib/sector.h
@@ -5,6 +5,7 @@
|
||||
#include "fluxmap.h"
|
||||
|
||||
class Record;
|
||||
class Location;
|
||||
|
||||
/*
|
||||
* Note that sectors here used zero-based numbering throughout (to make the
|
||||
@@ -35,7 +36,7 @@ public:
|
||||
nanoseconds_t headerEndTime = 0;
|
||||
nanoseconds_t dataStartTime = 0;
|
||||
nanoseconds_t dataEndTime = 0;
|
||||
unsigned physicalCylinder = 0;
|
||||
unsigned physicalTrack = 0;
|
||||
unsigned physicalHead = 0;
|
||||
unsigned logicalTrack = 0;
|
||||
unsigned logicalSide = 0;
|
||||
@@ -43,6 +44,10 @@ public:
|
||||
Bytes data;
|
||||
std::vector<std::shared_ptr<Record>> records;
|
||||
|
||||
Sector() {}
|
||||
|
||||
Sector(const Location& location);
|
||||
|
||||
std::tuple<int, int, int, Status> key() const
|
||||
{
|
||||
return std::make_tuple(
|
||||
@@ -66,8 +71,10 @@ public:
|
||||
};
|
||||
|
||||
extern bool sectorPointerSortPredicate(
|
||||
std::shared_ptr<const Sector>& lhs, std::shared_ptr<const Sector>& rhs);
|
||||
const std::shared_ptr<const Sector>& lhs,
|
||||
const std::shared_ptr<const Sector>& rhs);
|
||||
extern bool sectorPointerEqualsPredicate(
|
||||
std::shared_ptr<const Sector>& lhs, std::shared_ptr<const Sector>& rhs);
|
||||
const std::shared_ptr<const Sector>& lhs,
|
||||
const std::shared_ptr<const Sector>& rhs);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,7 +23,7 @@ static const char* gw_error(int e)
|
||||
case ACK_NO_BUS: return "No bus";
|
||||
case ACK_BAD_UNIT: return "Invalid unit";
|
||||
case ACK_BAD_PIN: return "Invalid pin";
|
||||
case ACK_BAD_CYLINDER: return "Invalid cylinder";
|
||||
case ACK_BAD_CYLINDER: return "Invalid track";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ message GreaseWeazleProto {
|
||||
BUSTYPE_INVALID = 0;
|
||||
IBMPC = 1;
|
||||
SHUGART = 2;
|
||||
APPLE2 = 3;
|
||||
};
|
||||
|
||||
optional string port = 1
|
||||
|
||||
194
lib/writer.cc
194
lib/writer.cc
@@ -1,194 +0,0 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "writer.h"
|
||||
#include "protocol.h"
|
||||
#include "usb/usb.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "fluxsink/fluxsink.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "fmt/format.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "logger.h"
|
||||
#include "mapper.h"
|
||||
#include "utils.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "proto.h"
|
||||
|
||||
void writeTracks(FluxSink& fluxSink,
|
||||
const std::function<std::unique_ptr<const Fluxmap>(int track, int side)> producer)
|
||||
{
|
||||
for (unsigned cylinder : iterate(config.cylinders()))
|
||||
{
|
||||
for (unsigned head : iterate(config.heads()))
|
||||
{
|
||||
testForEmergencyStop();
|
||||
Logger() << BeginWriteOperationLogMessage{cylinder, head};
|
||||
|
||||
auto fluxmap = producer(cylinder, head);
|
||||
if (!fluxmap)
|
||||
{
|
||||
/* Erase this track rather than writing. */
|
||||
|
||||
fluxmap.reset(new Fluxmap());
|
||||
fluxSink.writeFlux(cylinder, head, *fluxmap);
|
||||
Logger() << "erased";
|
||||
}
|
||||
else
|
||||
{
|
||||
auto scaled = fluxmap->rescale(config.flux_sink().rescale());
|
||||
/* Precompensation actually seems to make things worse, so let's
|
||||
* leave it disabled for now. */
|
||||
// fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2);
|
||||
fluxSink.writeFlux(cylinder, head, *scaled);
|
||||
Logger() << fmt::format("{0} ms in {1} bytes",
|
||||
int(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
}
|
||||
Logger() << EndWriteOperationLogMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSource& fluxSource,
|
||||
AbstractDecoder& decoder,
|
||||
const Image& image)
|
||||
{
|
||||
Logger() << fmt::format("Writing to: {}", (std::string)fluxSink);
|
||||
|
||||
for (unsigned cylinder : iterate(config.cylinders()))
|
||||
{
|
||||
for (unsigned head : iterate(config.heads()))
|
||||
{
|
||||
testForEmergencyStop();
|
||||
|
||||
auto sectors = encoder.collectSectors(cylinder, head, image);
|
||||
std::unique_ptr<Fluxmap> fluxmap =
|
||||
encoder.encode(cylinder, head, sectors, image);
|
||||
if (!fluxmap)
|
||||
{
|
||||
/* Erase this track rather than writing. */
|
||||
|
||||
Logger() << BeginWriteOperationLogMessage{cylinder, head};
|
||||
fluxmap.reset(new Fluxmap());
|
||||
fluxSink.writeFlux(cylinder, head, *fluxmap);
|
||||
Logger() << EndWriteOperationLogMessage()
|
||||
<< fmt::format("erased");
|
||||
}
|
||||
else
|
||||
{
|
||||
fluxmap->rescale(config.flux_sink().rescale());
|
||||
std::sort(
|
||||
sectors.begin(), sectors.end(), sectorPointerSortPredicate);
|
||||
|
||||
for (int retry = 0;; retry++)
|
||||
{
|
||||
/* Precompensation actually seems to make things worse, so
|
||||
* let's leave it disabled for now. */
|
||||
// fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS,
|
||||
// 2);
|
||||
Logger() << BeginWriteOperationLogMessage{cylinder, head};
|
||||
fluxSink.writeFlux(cylinder, head, *fluxmap);
|
||||
Logger() << EndWriteOperationLogMessage()
|
||||
<< fmt::format("writing {0} ms in {1} bytes",
|
||||
int(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
|
||||
Logger() << BeginReadOperationLogMessage{cylinder, head};
|
||||
std::shared_ptr<const Fluxmap> writtenFluxmap = fluxSource.readFlux(cylinder, head)->next();
|
||||
Logger() << EndReadOperationLogMessage()
|
||||
<< fmt::format("verifying {0} ms in {1} bytes",
|
||||
int(writtenFluxmap->duration() / 1e6),
|
||||
writtenFluxmap->bytes());
|
||||
|
||||
const auto trackdata =
|
||||
decoder.decodeToSectors(writtenFluxmap, cylinder, head);
|
||||
|
||||
std::vector<std::shared_ptr<const Sector>> gotSectors(
|
||||
trackdata->sectors.begin(), trackdata->sectors.end());
|
||||
gotSectors.erase(std::remove_if(gotSectors.begin(),
|
||||
gotSectors.end(),
|
||||
[](const auto& s)
|
||||
{
|
||||
return s->status != Sector::OK;
|
||||
}),
|
||||
gotSectors.end());
|
||||
std::sort(gotSectors.begin(),
|
||||
gotSectors.end(),
|
||||
sectorPointerSortPredicate);
|
||||
gotSectors.erase(std::unique(gotSectors.begin(),
|
||||
gotSectors.end(),
|
||||
sectorPointerEqualsPredicate),
|
||||
gotSectors.end());
|
||||
|
||||
if (std::equal(gotSectors.begin(),
|
||||
gotSectors.end(),
|
||||
sectors.begin(),
|
||||
sectors.end(),
|
||||
sectorPointerEqualsPredicate))
|
||||
break;
|
||||
|
||||
if (retry == config.decoder().retries())
|
||||
Error() << "Write failed; uncorrectable error during "
|
||||
"write.";
|
||||
|
||||
Logger() << "retrying";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
unsigned& cursor,
|
||||
unsigned terminateAt,
|
||||
const std::vector<bool>& pattern)
|
||||
{
|
||||
while (cursor < terminateAt)
|
||||
{
|
||||
for (bool b : pattern)
|
||||
{
|
||||
if (cursor < bitmap.size())
|
||||
bitmap[cursor++] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeDiskCommand(
|
||||
std::shared_ptr<const Image> image,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder,
|
||||
FluxSource* fluxSource)
|
||||
{
|
||||
if (config.has_sector_mapping())
|
||||
image = std::move(Mapper::remapLogicalToPhysical(*image, config.sector_mapping()));
|
||||
|
||||
if (fluxSource && decoder)
|
||||
writeTracksAndVerify(fluxSink, encoder, *fluxSource, *decoder, *image);
|
||||
else
|
||||
writeTracks(fluxSink,
|
||||
[&](int physicalTrack, int physicalSide) -> std::unique_ptr<Fluxmap>
|
||||
{
|
||||
const auto& sectors =
|
||||
encoder.collectSectors(physicalTrack, physicalSide, *image);
|
||||
if (sectors.empty())
|
||||
return std::make_unique<Fluxmap>();
|
||||
return encoder.encode(
|
||||
physicalTrack, physicalSide, sectors, *image);
|
||||
});
|
||||
}
|
||||
|
||||
void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink)
|
||||
{
|
||||
writeTracks(fluxSink,
|
||||
[&](int track, int side) -> std::unique_ptr<const Fluxmap>
|
||||
{
|
||||
return fluxSource.readFlux(track, side)->next();
|
||||
});
|
||||
}
|
||||
10
mkninja.sh
10
mkninja.sh
@@ -477,7 +477,7 @@ buildlibrary libbackend.a \
|
||||
lib/logger.cc \
|
||||
lib/mapper.cc \
|
||||
lib/proto.cc \
|
||||
lib/reader.cc \
|
||||
lib/readerwriter.cc \
|
||||
lib/sector.cc \
|
||||
lib/usb/fluxengineusb.cc \
|
||||
lib/usb/greaseweazle.cc \
|
||||
@@ -486,16 +486,17 @@ buildlibrary libbackend.a \
|
||||
lib/usb/usb.cc \
|
||||
lib/usb/usbfinder.cc \
|
||||
lib/utils.cc \
|
||||
lib/writer.cc \
|
||||
|
||||
FORMATS="\
|
||||
40track_drive \
|
||||
acornadfs \
|
||||
acorndfs \
|
||||
agat840 \
|
||||
aeslanier \
|
||||
agat840 \
|
||||
amiga \
|
||||
ampro \
|
||||
apple2 \
|
||||
apple2_drive \
|
||||
appledos \
|
||||
atarist360 \
|
||||
atarist370 \
|
||||
@@ -535,10 +536,11 @@ FORMATS="\
|
||||
northstar87 \
|
||||
prodos \
|
||||
rx50 \
|
||||
shugart_drive \
|
||||
tids990 \
|
||||
vgi \
|
||||
victor9k_ss \
|
||||
victor9k_ds \
|
||||
victor9k_ss \
|
||||
zilogmcz \
|
||||
"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "bitmap.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "protocol.h"
|
||||
#include "proto.h"
|
||||
#include "fluxsink/fluxsink.h"
|
||||
@@ -24,9 +24,9 @@ static StringFlag destFlux(
|
||||
FluxSink::updateConfigForFilename(config.mutable_flux_sink(), value);
|
||||
});
|
||||
|
||||
static IntFlag destCylinder(
|
||||
static IntFlag destTrack(
|
||||
{ "--cylinder", "-c" },
|
||||
"cylinder to write to",
|
||||
"track to write to",
|
||||
0);
|
||||
|
||||
static IntFlag destHead(
|
||||
@@ -213,7 +213,7 @@ int mainAnalyseDriveResponse(int argc, const char* argv[])
|
||||
usbSetDrive(config.drive().drive(),
|
||||
config.drive().high_density(),
|
||||
config.drive().index_mode());
|
||||
usbSeek(destCylinder);
|
||||
usbSeek(destTrack);
|
||||
|
||||
std::cout << "Measuring rotational speed...\n";
|
||||
nanoseconds_t period = usbGetRotationalPeriod(0);
|
||||
|
||||
@@ -82,9 +82,9 @@ void visualiseSectorsToFile(const Image& image, const std::string& filename)
|
||||
? (panel_centre + side*panel_size)
|
||||
: panel_centre);
|
||||
|
||||
for (int physicalCylinder = 0; physicalCylinder < TRACKS; physicalCylinder++)
|
||||
for (int physicalTrack = 0; physicalTrack < TRACKS; physicalTrack++)
|
||||
{
|
||||
double visibleDistance = (TRACKS * 0.5) + (TRACKS - physicalCylinder);
|
||||
double visibleDistance = (TRACKS * 0.5) + (TRACKS - physicalTrack);
|
||||
double radius = (disk_radius*visibleDistance)/(TRACKS * 1.5);
|
||||
painter.noFill();
|
||||
painter.lineColor(0x88, 0x88, 0x88);
|
||||
@@ -96,7 +96,7 @@ void visualiseSectorsToFile(const Image& image, const std::string& filename)
|
||||
{
|
||||
for (const auto& sector : image)
|
||||
{
|
||||
if ((sector->physicalHead == side) && (sector->physicalCylinder == physicalCylinder)
|
||||
if ((sector->physicalHead == side) && (sector->physicalTrack == physicalTrack)
|
||||
&& (sector->logicalSector == alignWithSector))
|
||||
{
|
||||
offset = sector->headerStartTime;
|
||||
@@ -124,7 +124,7 @@ void visualiseSectorsToFile(const Image& image, const std::string& filename)
|
||||
/* Sadly, Images aren't indexable by physical track. */
|
||||
for (const auto& sector : image)
|
||||
{
|
||||
if ((sector->physicalHead == side) && (sector->physicalCylinder == physicalCylinder))
|
||||
if ((sector->physicalHead == side) && (sector->physicalTrack == physicalTrack))
|
||||
{
|
||||
painter.lineColor(0xff, 0x00, 0x00);
|
||||
if (sector->status == Sector::OK)
|
||||
@@ -192,7 +192,7 @@ static void readRow(const std::vector<std::string>& row, Image& image)
|
||||
int logicalSector = std::stoi(row[4]);
|
||||
|
||||
const auto& sector = image.put(logicalTrack, logicalSide, logicalSector);
|
||||
sector->physicalCylinder = std::stoi(row[0]);
|
||||
sector->physicalTrack = std::stoi(row[0]);
|
||||
sector->physicalHead = std::stoi(row[1]);
|
||||
sector->logicalTrack = logicalTrack;
|
||||
sector->logicalSide = logicalSide;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "reader.h"
|
||||
#include "readerwriter.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "decoders/fluxdecoder.h"
|
||||
@@ -23,9 +23,9 @@ static StringFlag sourceFlux(
|
||||
FluxSource::updateConfigForFilename(config.mutable_flux_source(), value);
|
||||
});
|
||||
|
||||
static IntFlag cylinderFlag(
|
||||
static IntFlag trackFlag(
|
||||
{ "--cylinder", "-c" },
|
||||
"Cylinder to read.",
|
||||
"Track to read.",
|
||||
0);
|
||||
|
||||
static IntFlag headFlag(
|
||||
@@ -205,7 +205,7 @@ int mainInspect(int argc, const char* argv[])
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, {});
|
||||
|
||||
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));
|
||||
const auto fluxmap = fluxSource->readFlux(cylinderFlag, headFlag)->next();
|
||||
const auto fluxmap = fluxSource->readFlux(trackFlag, headFlag)->next();
|
||||
|
||||
std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n",
|
||||
fluxmap->bytes(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "reader.h"
|
||||
#include "readerwriter.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "macintosh/macintosh.h"
|
||||
@@ -36,13 +36,13 @@ static StringFlag destFlux(
|
||||
FluxSink::updateConfigForFilename(config.mutable_flux_sink(), value);
|
||||
});
|
||||
|
||||
static StringFlag srcCylinders(
|
||||
static StringFlag srcTracks(
|
||||
{ "--cylinders", "-c" },
|
||||
"cylinders to read from",
|
||||
"tracks to read from",
|
||||
"",
|
||||
[](const auto& value)
|
||||
{
|
||||
setRange(config.mutable_cylinders(), value);
|
||||
setRange(config.mutable_tracks(), value);
|
||||
});
|
||||
|
||||
static StringFlag srcHeads(
|
||||
@@ -56,7 +56,7 @@ static StringFlag srcHeads(
|
||||
|
||||
int mainRawRead(int argc, const char* argv[])
|
||||
{
|
||||
setRange(config.mutable_cylinders(), "0-79");
|
||||
setRange(config.mutable_tracks(), "0-79");
|
||||
setRange(config.mutable_heads(), "0-1");
|
||||
|
||||
if (argc == 1)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "reader.h"
|
||||
#include "readerwriter.h"
|
||||
#include "fluxmap.h"
|
||||
#include "writer.h"
|
||||
#include "sector.h"
|
||||
#include "proto.h"
|
||||
#include "lib/fluxsource/fluxsource.h"
|
||||
@@ -32,13 +31,13 @@ static StringFlag destFlux(
|
||||
FluxSink::updateConfigForFilename(config.mutable_flux_sink(), value);
|
||||
});
|
||||
|
||||
static StringFlag destCylinders(
|
||||
static StringFlag destTracks(
|
||||
{ "--cylinders", "-c" },
|
||||
"cylinders to write to",
|
||||
"tracks to write to",
|
||||
"",
|
||||
[](const auto& value)
|
||||
{
|
||||
setRange(config.mutable_cylinders(), value);
|
||||
setRange(config.mutable_tracks(), value);
|
||||
});
|
||||
|
||||
static StringFlag destHeads(
|
||||
@@ -60,7 +59,7 @@ static ActionFlag eraseFlag(
|
||||
|
||||
int mainRawWrite(int argc, const char* argv[])
|
||||
{
|
||||
setRange(config.mutable_cylinders(), "0-79");
|
||||
setRange(config.mutable_tracks(), "0-79");
|
||||
setRange(config.mutable_heads(), "0-1");
|
||||
|
||||
if (argc == 1)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "reader.h"
|
||||
#include "readerwriter.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "macintosh/macintosh.h"
|
||||
@@ -45,13 +45,13 @@ static StringFlag copyFluxTo(
|
||||
FluxSink::updateConfigForFilename(config.mutable_decoder()->mutable_copy_flux_to(), value);
|
||||
});
|
||||
|
||||
static StringFlag srcCylinders(
|
||||
static StringFlag srcTracks(
|
||||
{ "--cylinders", "-c" },
|
||||
"cylinders to read from",
|
||||
"tracks to read from",
|
||||
"",
|
||||
[](const auto& value)
|
||||
{
|
||||
setRange(config.mutable_cylinders(), value);
|
||||
setRange(config.mutable_tracks(), value);
|
||||
});
|
||||
|
||||
static StringFlag srcHeads(
|
||||
|
||||
@@ -16,9 +16,9 @@ static StringFlag sourceFlux(
|
||||
FluxSource::updateConfigForFilename(config.mutable_flux_source(), value);
|
||||
});
|
||||
|
||||
static IntFlag cylinder(
|
||||
static IntFlag track(
|
||||
{ "--cylinder", "-c" },
|
||||
"cylinder to seek to",
|
||||
"track to seek to",
|
||||
0);
|
||||
|
||||
extern const std::map<std::string, std::string> readables;
|
||||
@@ -31,6 +31,6 @@ int mainSeek(int argc, const char* argv[])
|
||||
Error() << "this only makes sense with a real disk drive";
|
||||
|
||||
usbSetDrive(config.drive().drive(), false, config.drive().index_mode());
|
||||
usbSeek(cylinder);
|
||||
usbSeek(track);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "writer.h"
|
||||
#include "readerwriter.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
@@ -38,13 +38,13 @@ static StringFlag destFlux(
|
||||
FluxSource::updateConfigForFilename(config.mutable_flux_source(), value);
|
||||
});
|
||||
|
||||
static StringFlag destCylinders(
|
||||
static StringFlag destTracks(
|
||||
{ "--cylinders", "-c" },
|
||||
"cylinders to write to",
|
||||
"tracks to write to",
|
||||
"",
|
||||
[](const auto& value)
|
||||
{
|
||||
setRange(config.mutable_cylinders(), value);
|
||||
setRange(config.mutable_tracks(), value);
|
||||
});
|
||||
|
||||
static StringFlag destHeads(
|
||||
|
||||
10
src/formats/40track_drive.textpb
Normal file
10
src/formats/40track_drive.textpb
Normal file
@@ -0,0 +1,10 @@
|
||||
comment: 'Adjust configuration for a 40-track drive'
|
||||
is_extension: true
|
||||
|
||||
drive {
|
||||
tracks: 40
|
||||
head_width: 1
|
||||
tpi: 48
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ decoder {
|
||||
aeslanier {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 76
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ decoder {
|
||||
agat {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ decoder {
|
||||
amiga {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ decoder {
|
||||
ibm {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -28,10 +28,9 @@ encoder {
|
||||
apple2 {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
step: 2
|
||||
end: 39
|
||||
}
|
||||
|
||||
heads {
|
||||
@@ -39,3 +38,5 @@ heads {
|
||||
end: 0
|
||||
}
|
||||
|
||||
tpi: 48
|
||||
|
||||
|
||||
17
src/formats/apple2_drive.textpb
Normal file
17
src/formats/apple2_drive.textpb
Normal file
@@ -0,0 +1,17 @@
|
||||
comment: 'Adjust configuration for a 40-track Apple II drive'
|
||||
is_extension: true
|
||||
|
||||
usb {
|
||||
greaseweazle {
|
||||
bus_type: APPLE2
|
||||
}
|
||||
}
|
||||
|
||||
drive {
|
||||
tracks: 160
|
||||
heads: 1
|
||||
head_width: 4
|
||||
tpi: 192
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 81
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 81
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 81
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 81
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ image_reader {
|
||||
img {
|
||||
tracks: 39
|
||||
sides: 1
|
||||
physical_step: 2
|
||||
trackdata {
|
||||
sector_size: 256
|
||||
sector_range {
|
||||
@@ -21,7 +20,6 @@ image_writer {
|
||||
img {
|
||||
tracks: 39
|
||||
sides: 1
|
||||
physical_step: 2
|
||||
trackdata {
|
||||
sector_size: 256
|
||||
sector_range {
|
||||
@@ -39,13 +37,16 @@ encoder {
|
||||
}
|
||||
|
||||
decoder {
|
||||
retries: 1
|
||||
brother {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
drive {
|
||||
head_bias: 1
|
||||
}
|
||||
|
||||
tracks {
|
||||
start: 0
|
||||
end: 77
|
||||
end: 38
|
||||
}
|
||||
|
||||
heads {
|
||||
@@ -53,3 +54,4 @@ heads {
|
||||
end: 0
|
||||
}
|
||||
|
||||
tpi: 48
|
||||
|
||||
@@ -38,7 +38,11 @@ decoder {
|
||||
brother {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
drive {
|
||||
head_bias: 1
|
||||
}
|
||||
|
||||
tracks {
|
||||
start: 0
|
||||
end: 77
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ decoder {
|
||||
c64 {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
end: 39
|
||||
}
|
||||
|
||||
heads {
|
||||
@@ -28,3 +28,5 @@ heads {
|
||||
end: 0
|
||||
}
|
||||
|
||||
tpi: 48
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ decoder {
|
||||
}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
tracks {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user