mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge from master.
This commit is contained in:
2
Makefile
2
Makefile
@@ -52,7 +52,7 @@ CFLAGS += -Ilib -Idep/fmt -Iarch
|
||||
export OBJDIR = .obj
|
||||
|
||||
all: .obj/build.ninja
|
||||
@ninja -f .obj/build.ninja
|
||||
@ninja -f .obj/build.ninja -k 0
|
||||
@if command -v cscope > /dev/null; then cscope -bRq; fi
|
||||
|
||||
clean:
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/data.pb.h"
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -30,7 +29,7 @@ public:
|
||||
_config(config.amiga())
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
RecordType advanceToNextRecord() override
|
||||
{
|
||||
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
|
||||
if (_fmr->eof() || !_sector->clock)
|
||||
@@ -38,7 +37,7 @@ public:
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16);
|
||||
if (rawbits.size() < (AMIGA_RECORD_SIZE*16))
|
||||
|
||||
@@ -144,7 +144,7 @@ public:
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image)
|
||||
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
int logicalTrack;
|
||||
if (physicalSide != 0)
|
||||
|
||||
@@ -231,7 +231,7 @@ public:
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image)
|
||||
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
/* The format ID Character # 1 and # 2 are in the .d64 image only present
|
||||
* in track 18 sector zero which contains the BAM info in byte 162 and 163.
|
||||
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
_config(config.ibm())
|
||||
{}
|
||||
|
||||
RecordType advanceToNextRecord()
|
||||
RecordType advanceToNextRecord() override
|
||||
{
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
|
||||
@@ -108,6 +108,7 @@ public:
|
||||
_currentHeaderLength = (matcher == &MFM_PATTERN) ? 3 : 0;
|
||||
|
||||
Fluxmap::Position here = tell();
|
||||
resetFluxDecoder();
|
||||
if (_currentHeaderLength > 0)
|
||||
readRawBits(_currentHeaderLength*16);
|
||||
auto idbits = readRawBits(16);
|
||||
@@ -129,7 +130,7 @@ public:
|
||||
return RecordType::UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
unsigned recordSize = _currentHeaderLength + IBM_IDAM_LEN;
|
||||
auto bits = readRawBits(recordSize*16);
|
||||
@@ -155,7 +156,7 @@ public:
|
||||
_sector->logicalTrack = _sector->physicalCylinder;
|
||||
}
|
||||
|
||||
void decodeDataRecord()
|
||||
void decodeDataRecord() override
|
||||
{
|
||||
unsigned recordLength = _currentHeaderLength + _currentSectorSize + 3;
|
||||
auto bits = readRawBits(recordLength*16);
|
||||
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
{}
|
||||
|
||||
/* Search for FM or MFM sector record */
|
||||
RecordType advanceToNextRecord()
|
||||
RecordType advanceToNextRecord() override
|
||||
{
|
||||
nanoseconds_t now = _fmr->tell().ns();
|
||||
|
||||
@@ -140,7 +140,7 @@ public:
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
void decodeSectorRecord()
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
unsigned recordSize, payloadSize, headerSize;
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
void decodeSectorRecord()
|
||||
{
|
||||
/* Skip the sync marker bit. */
|
||||
readRawBits(23);
|
||||
readRawBits(22);
|
||||
|
||||
/* Read header. */
|
||||
|
||||
@@ -96,7 +96,7 @@ public:
|
||||
void decodeDataRecord()
|
||||
{
|
||||
/* Skip the sync marker bit. */
|
||||
readRawBits(23);
|
||||
readRawBits(22);
|
||||
|
||||
/* Read data. */
|
||||
|
||||
|
||||
200
arch/victor9k/encoder.cc
Normal file
200
arch/victor9k/encoder.cc
Normal file
@@ -0,0 +1,200 @@
|
||||
#include "globals.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "victor9k.h"
|
||||
#include "crc.h"
|
||||
#include "sector.h"
|
||||
#include "writer.h"
|
||||
#include "image.h"
|
||||
#include "fmt/format.h"
|
||||
#include "arch/victor9k/victor9k.pb.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include <ctype.h>
|
||||
#include "bytes.h"
|
||||
|
||||
static bool lastBit;
|
||||
|
||||
static void write_zero_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
|
||||
{
|
||||
while (count--)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_one_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
|
||||
{
|
||||
while (count--)
|
||||
{
|
||||
if (cursor < bits.size())
|
||||
lastBit = bits[cursor++] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
{
|
||||
ByteReader br(bytes);
|
||||
BitReader bitr(br);
|
||||
|
||||
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
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void write_bytes(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
|
||||
{
|
||||
for (uint8_t b : bytes)
|
||||
{
|
||||
write_bits(bits, cursor, encode_data_gcr(b>>4), 5);
|
||||
write_bits(bits, cursor, encode_data_gcr(b), 5);
|
||||
}
|
||||
}
|
||||
|
||||
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 encodedSector = sector.logicalSector;
|
||||
write_bytes(bits, cursor, Bytes {
|
||||
encodedTrack,
|
||||
encodedSector,
|
||||
(uint8_t)(encodedTrack + encodedSector),
|
||||
});
|
||||
|
||||
|
||||
write_zero_bits(bits, cursor, trackdata.post_header_gap_bits());
|
||||
write_one_bits(bits, cursor, trackdata.pre_data_sync_bits());
|
||||
write_bits(bits, cursor, VICTOR9K_DATA_RECORD, 10);
|
||||
|
||||
write_bytes(bits, cursor, sector.data);
|
||||
|
||||
Bytes checksum(2);
|
||||
checksum.writer().write_le16(sumBytes(sector.data));
|
||||
write_bytes(bits, cursor, checksum);
|
||||
|
||||
write_zero_bits(bits, cursor, trackdata.post_data_gap_bits());
|
||||
}
|
||||
|
||||
class Victor9kEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
Victor9kEncoder(const EncoderProto& config):
|
||||
AbstractEncoder(config),
|
||||
_config(config.victor9k())
|
||||
{}
|
||||
|
||||
private:
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
|
||||
{
|
||||
std::vector<std::shared_ptr<Sector>> sectors;
|
||||
|
||||
Victor9kEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
|
||||
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
|
||||
{
|
||||
Victor9kEncoderProto::TrackdataProto trackdata;
|
||||
getTrackFormat(trackdata, physicalTrack, physicalSide);
|
||||
|
||||
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 });
|
||||
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 });
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs*1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
private:
|
||||
const Victor9kEncoderProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractEncoder> createVictor9kEncoder(const EncoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<AbstractEncoder>(new Victor9kEncoder(config));
|
||||
}
|
||||
|
||||
// vim: sw=4 ts=4 et
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
#ifndef VICTOR9K_H
|
||||
#define VICTOR9K_H
|
||||
|
||||
#define VICTOR9K_SECTOR_RECORD 0xfffffeab
|
||||
#define VICTOR9K_DATA_RECORD 0xfffffea4
|
||||
class AbstractEncoder;
|
||||
class AbstractDecoder;
|
||||
class EncoderProto;
|
||||
class DecoderProto;
|
||||
|
||||
/* ... 1101 0101 0111
|
||||
* ^^ ^^^^ ^^^^ ten bit IO byte */
|
||||
#define VICTOR9K_SECTOR_RECORD 0xfffffd57
|
||||
|
||||
/* ... 1101 0100 1001
|
||||
* ^^ ^^^^ ^^^^ ten bit IO byte */
|
||||
#define VICTOR9K_DATA_RECORD 0xfffffd49
|
||||
|
||||
#define VICTOR9K_SECTOR_LENGTH 512
|
||||
|
||||
extern std::unique_ptr<AbstractDecoder> createVictor9kDecoder(const DecoderProto& config);
|
||||
extern std::unique_ptr<AbstractEncoder> createVictor9kEncoder(const EncoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
syntax = "proto2";
|
||||
|
||||
import "lib/common.proto";
|
||||
|
||||
message Victor9kDecoderProto {}
|
||||
|
||||
// NEXT: 12
|
||||
message Victor9kEncoderProto {
|
||||
message TrackdataProto {
|
||||
message SectorRangeProto {
|
||||
optional int32 start_sector = 1 [(help) = "first sector ID on track"];
|
||||
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 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 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"];
|
||||
optional int32 post_data_gap_bits = 9 [(help) = "size of gap between data and the next header"];
|
||||
optional int32 post_header_gap_bits = 11 [(help) = "size of gap between header and the data"];
|
||||
|
||||
optional SectorRangeProto sector_range = 7 [(help) = "write these sectors on each track"];
|
||||
}
|
||||
|
||||
repeated TrackdataProto trackdata = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,10 @@ and there's too many configuration options to usefully list. Use `fluxengine
|
||||
write` to list all formats, and try `fluxengine write ibm1440 --config` to see
|
||||
a sample configuration.
|
||||
|
||||
Some image formats, such as DIM, specify the image format, For these you can
|
||||
specify the `ibm` format and FluxEngine will automatically determine the
|
||||
correct format to use.
|
||||
|
||||
Mixed-format disks
|
||||
------------------
|
||||
|
||||
|
||||
@@ -6,9 +6,28 @@ which used a disk format very reminiscent of the Commodore format; not a
|
||||
coincidence, as Chuck Peddle designed them both. They're 80-track, 512-byte
|
||||
sector GCR disks, with a variable-speed drive and a varying number of sectors
|
||||
per track --- from 19 to 12. Disks can be double-sided, meaning that they can
|
||||
store 1224kB per disk, which was almost unheard of back then.
|
||||
store 1224kB per disk, which was almost unheard of back then. Because the way
|
||||
that the tracks on head 1 are offset from head 0 (this happens with all disks),
|
||||
the speed zone allocation on head 1 differ from head 0...
|
||||
|
||||
FluxEngine reads these.
|
||||
| Zone | Head 0 tracks | Head 1 tracks | Sectors | Original period (ms) |
|
||||
|:----:|:-------------:|:-------------:|:-------:|:--------------------:|
|
||||
| 0 | 0-3 | | 19 | 237.9 |
|
||||
| 1 | 4-15 | 0-7 | 18 | 224.5 |
|
||||
| 2 | 16-26 | 8-18 | 17 | 212.2 |
|
||||
| 3 | 27-37 | 19-29 | 16 | 199.9 |
|
||||
| 4 | 38-48 | 30-40 | 15 | 187.6 |
|
||||
| 5 | 49-59 | 41-51 | 14 | 175.3 |
|
||||
| 6 | 60-70 | 52-62 | 13 | 163.0 |
|
||||
| 7 | 71-79 | 63-74 | 12 | 149.6 |
|
||||
| 8 | | 75-79 | 11 | 144.0 |
|
||||
|
||||
(The Original Period column is the original rotation rate. When used in
|
||||
FluxEngine, the disk always spins at 360 rpm, which corresponds to a rotational
|
||||
period of 166 ms.)
|
||||
|
||||
FluxEngine can read and write the single-sided variant of these. (Double-sided
|
||||
will be trivial to do, it's just not done yet.)
|
||||
|
||||
Reading discs
|
||||
-------------
|
||||
@@ -16,18 +35,25 @@ Reading discs
|
||||
Just do:
|
||||
|
||||
```
|
||||
fluxengine read victor9k
|
||||
fluxengine read victor9k-ss
|
||||
```
|
||||
|
||||
You should end up with an `victor9k.img` which is 774656 bytes long.
|
||||
if you want the double-sided variety, use `--heads 0-1`.
|
||||
You should end up with an `victor9k.img` which is 627200 bytes long.
|
||||
|
||||
**Big warning!** The image may not work in an emulator. Victor disk images are
|
||||
complicated due to the way the tracks are different sizes and the odd sector
|
||||
size. FluxEngine chooses to store them in a simple 512 x 19 x 1 x 80 layout,
|
||||
with holes where missing sectors should be. This was easiest. If anyone can
|
||||
suggest a better way, please [get in
|
||||
touch](https://github.com/davidgiven/fluxengine/issues/new).
|
||||
**Big warning!** The image is triangular, where each track occupies a different
|
||||
amount of space. Victor disk images are complicated due to the way the tracks
|
||||
are different sizes and the odd sector size.
|
||||
|
||||
Writing discs
|
||||
-------------
|
||||
|
||||
Just do:
|
||||
|
||||
```
|
||||
fluxengine read victor9k-ss -i victor9k.img
|
||||
```
|
||||
|
||||
**Big warning!** This uses the same triangular disk image that reading uses.
|
||||
|
||||
|
||||
Useful references
|
||||
|
||||
24
doc/using.md
24
doc/using.md
@@ -233,7 +233,7 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or
|
||||
FluxEngine also supports a number of file system image formats. When using the
|
||||
`-i` or `-o` options (for input and output), you can use any of these strings:
|
||||
|
||||
- `<filename.adf>`, `<filename.d81>`, `<filename.img>`, `<filename.st>`
|
||||
- `<filename.adf>`, `<filename.d81>`, `<filename.img>`, `<filename.st>`, `<filename.xdf>`
|
||||
|
||||
Read from or write to a simple headerless image file (all these formats are
|
||||
the same). This will probably want configuration via the
|
||||
@@ -256,6 +256,28 @@ FluxEngine also supports a number of file system image formats. When using the
|
||||
|
||||
Read from a JV3 image file, commonly used by TRS-80 emulators. **Read
|
||||
only.**
|
||||
|
||||
- `<filename.dim>`
|
||||
|
||||
Read from a [DIM image file](https://www.pc98.org/project/doc/dim.html),
|
||||
commonly used by X68000 emulators. Supports automatically configuring
|
||||
the encoder. **Read Only.**
|
||||
|
||||
- `<filename.fdi>`
|
||||
|
||||
Read from a [FDI image file](https://www.pc98.org/project/doc/hdi.html),
|
||||
commonly used by PC-98 emulators. **Read Only.**
|
||||
|
||||
- `<filename.d88>`
|
||||
|
||||
Read from a [D88 image file](https://www.pc98.org/project/doc/d88.html),
|
||||
commonly used by various Japanese PC emulators, including the NEC PC-88. **Read Only.**
|
||||
|
||||
FluxEngine is currently limited to reading only the first floppy image in a
|
||||
D88 file.
|
||||
|
||||
The D88 reader should be used with the `ibm` profile and will override
|
||||
most encoding parameters on a track-by-track basis.
|
||||
|
||||
- `<filename.ldbs>`
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
syntax = "proto2";
|
||||
|
||||
// Images
|
||||
|
||||
enum SectorStatus {
|
||||
UNKNOWN = 0;
|
||||
OK = 1;
|
||||
BAD_CHECKSUM = 2;
|
||||
MISSING = 3;
|
||||
DATA_MISSING = 4;
|
||||
CONFLICT = 5;
|
||||
INTERNAL_ERROR = 6;
|
||||
}
|
||||
|
||||
message SectorProto {
|
||||
optional int32 logical_sector = 1;
|
||||
optional bytes data = 2;
|
||||
optional SectorStatus status = 3;
|
||||
|
||||
optional uint64 clock = 4;
|
||||
optional uint64 header_starttime_ns = 5;
|
||||
optional uint64 header_endtime_ns = 6;
|
||||
optional uint64 data_starttime_ns = 7;
|
||||
optional uint64 data_endtime_ns = 8;
|
||||
optional int32 physical_cylinder = 9;
|
||||
optional int32 physical_head = 10;
|
||||
optional int32 logical_track = 11;
|
||||
optional int32 logical_side = 12;
|
||||
}
|
||||
|
||||
message TrackProto {
|
||||
map<int32, SectorProto> sectors = 1;
|
||||
optional int32 logical_track = 2;
|
||||
optional int32 logical_side = 3;
|
||||
}
|
||||
|
||||
message ImageProto {
|
||||
map<int32, TrackProto> tracks = 1;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/data.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <numeric>
|
||||
|
||||
@@ -78,18 +77,20 @@ std::unique_ptr<TrackDataFlux> AbstractDecoder::decodeToSectors(
|
||||
_sector->physicalHead = physicalHead;
|
||||
|
||||
Fluxmap::Position recordStart = fmr.tell();
|
||||
_decoder.reset(new FluxDecoder(&fmr, _sector->clock, _config));
|
||||
RecordType r = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
return std::move(_trackdata);
|
||||
if ((r == UNKNOWN_RECORD) || (r == DATA_RECORD))
|
||||
{
|
||||
fmr.findEvent(F_BIT_PULSE);
|
||||
fmr.skipToEvent(F_BIT_PULSE);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Read the sector record. */
|
||||
|
||||
recordStart = fmr.tell();
|
||||
resetFluxDecoder();
|
||||
decodeSectorRecord();
|
||||
Fluxmap::Position recordEnd = fmr.tell();
|
||||
pushRecord(recordStart, recordEnd);
|
||||
@@ -104,12 +105,15 @@ std::unique_ptr<TrackDataFlux> AbstractDecoder::decodeToSectors(
|
||||
r = advanceToNextRecord();
|
||||
if (r != UNKNOWN_RECORD)
|
||||
break;
|
||||
if (fmr.findEvent(F_BIT_PULSE) == 0)
|
||||
if (fmr.eof())
|
||||
break;
|
||||
}
|
||||
recordStart = fmr.tell();
|
||||
if (r == DATA_RECORD)
|
||||
{
|
||||
resetFluxDecoder();
|
||||
decodeDataRecord();
|
||||
}
|
||||
recordEnd = fmr.tell();
|
||||
pushRecord(recordStart, recordEnd);
|
||||
}
|
||||
@@ -133,13 +137,19 @@ void AbstractDecoder::pushRecord(const Fluxmap::Position& start, const Fluxmap::
|
||||
record->clock = _sector->clock;
|
||||
|
||||
_fmr->seek(start);
|
||||
record->rawData = toBytes(_fmr->readRawBits(end, _sector->clock));
|
||||
FluxDecoder decoder(_fmr, _sector->clock, _config);
|
||||
record->rawData = toBytes(decoder.readBits(end));
|
||||
_fmr->seek(here);
|
||||
}
|
||||
|
||||
void AbstractDecoder::resetFluxDecoder()
|
||||
{
|
||||
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
|
||||
}
|
||||
|
||||
std::vector<bool> AbstractDecoder::readRawBits(unsigned count)
|
||||
{
|
||||
return _fmr->readRawBits(count, _sector->clock);
|
||||
return _decoder->readBits(count);
|
||||
}
|
||||
|
||||
std::set<unsigned> AbstractDecoder::requiredSectors(unsigned cylinder, unsigned head) const
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "bytes.h"
|
||||
#include "sector.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "decoders/fluxdecoder.h"
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
@@ -47,8 +48,8 @@ public:
|
||||
std::unique_ptr<TrackDataFlux> decodeToSectors(std::shared_ptr<const Fluxmap> fluxmap, unsigned cylinder, unsigned head);
|
||||
void pushRecord(const Fluxmap::Position& start, const Fluxmap::Position& end);
|
||||
|
||||
void resetFluxDecoder();
|
||||
std::vector<bool> readRawBits(unsigned count);
|
||||
//{ return _fmr->readRawBits(count, _sector->clock); }
|
||||
|
||||
Fluxmap::Position tell()
|
||||
{ return _fmr->tell(); }
|
||||
@@ -68,6 +69,7 @@ protected:
|
||||
FluxmapReader* _fmr = nullptr;
|
||||
std::unique_ptr<TrackDataFlux> _trackdata;
|
||||
std::shared_ptr<Sector> _sector;
|
||||
std::unique_ptr<FluxDecoder> _decoder;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,16 +18,19 @@ import "arch/zilogmcz/zilogmcz.proto";
|
||||
import "lib/fluxsink/fluxsink.proto";
|
||||
import "lib/common.proto";
|
||||
|
||||
//NEXT: 27
|
||||
message DecoderProto {
|
||||
optional double pulse_debounce_threshold = 1 [default = 0.30,
|
||||
(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"];
|
||||
optional double bit_error_threshold = 2 [default = 0.40,
|
||||
(help) = "amount of error to tolerate in pulse timing, in fractions of a clock"];
|
||||
optional double clock_interval_bias = 3 [default = -0.02,
|
||||
(help) = "adjust intervals between pulses by this many clocks before decoding"];
|
||||
optional double minimum_clock_us = 4 [default = 0.75,
|
||||
(help) = "refuse to detect clocks shorter than this, to avoid false positives"];
|
||||
|
||||
optional double pll_adjust = 25 [default = 0.04];
|
||||
optional double pll_phase = 26 [default = 0.60];
|
||||
optional double flux_scale = 27 [default = 1.0];
|
||||
|
||||
oneof format {
|
||||
IbmDecoderProto ibm = 5;
|
||||
BrotherDecoderProto brother = 6;
|
||||
|
||||
117
lib/decoders/fluxdecoder.cc
Normal file
117
lib/decoders/fluxdecoder.cc
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "decoders/fluxdecoder.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
|
||||
/* This is a port of the samdisk code:
|
||||
*
|
||||
* https://github.com/simonowen/samdisk/blob/master/src/FluxDecoder.cpp
|
||||
*
|
||||
* I'm not actually terribly sure how it works, but it does, and much better
|
||||
* than my code.
|
||||
*/
|
||||
|
||||
FluxDecoder::FluxDecoder(FluxmapReader* fmr, nanoseconds_t bitcell,
|
||||
const DecoderProto& config):
|
||||
_fmr(fmr),
|
||||
_pll_phase(config.pll_phase()),
|
||||
_pll_adjust(config.pll_adjust()),
|
||||
_flux_scale(config.flux_scale()),
|
||||
_clock(bitcell),
|
||||
_clock_centre(bitcell),
|
||||
_clock_min(bitcell * (1.0 - _pll_adjust)),
|
||||
_clock_max(bitcell * (1.0 + _pll_adjust)),
|
||||
_flux(0),
|
||||
_leading_zeroes(fmr->tell().zeroes)
|
||||
{}
|
||||
|
||||
|
||||
bool FluxDecoder::readBit()
|
||||
{
|
||||
if (_leading_zeroes > 0)
|
||||
{
|
||||
_leading_zeroes--;
|
||||
return false;
|
||||
}
|
||||
else if (_leading_zeroes == 0)
|
||||
{
|
||||
_leading_zeroes--;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (!_fmr->eof() && (_flux < (_clock/2)))
|
||||
{
|
||||
_flux += nextFlux() * _flux_scale;;
|
||||
_clocked_zeroes = 0;
|
||||
}
|
||||
|
||||
_flux -= _clock;
|
||||
if (_flux >= (_clock/2))
|
||||
{
|
||||
_clocked_zeroes++;
|
||||
_goodbits++;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* PLL adjustment: change the clock frequency according to the phase
|
||||
* mismatch */
|
||||
if (_clocked_zeroes <= 3)
|
||||
{
|
||||
/* In sync: adjust base clock */
|
||||
|
||||
_clock += _flux * _pll_adjust;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Out of sync: adjust the base clock back towards the centre */
|
||||
|
||||
_clock += (_clock_centre - _clock) * _pll_adjust;
|
||||
|
||||
/* We require 256 good bits before reporting another sync loss event. */
|
||||
|
||||
if (_goodbits >= 256)
|
||||
_sync_lost = true;
|
||||
_goodbits = 0;
|
||||
}
|
||||
|
||||
/* Clamp the clock's adjustment range. */
|
||||
|
||||
_clock = std::min(std::max(_clock_min, _clock), _clock_max);
|
||||
|
||||
/* I'm not sure what this does, but the original comment is:
|
||||
* Authentic PLL: Do not snap the timing window to each flux transition
|
||||
*/
|
||||
|
||||
_flux = _flux * (1.0 - _pll_phase);
|
||||
|
||||
_goodbits++;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<bool> FluxDecoder::readBits(unsigned count)
|
||||
{
|
||||
std::vector<bool> result;
|
||||
while (!_fmr->eof() && count--)
|
||||
{
|
||||
bool b = readBit();
|
||||
result.push_back(b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<bool> FluxDecoder::readBits(const Fluxmap::Position& until)
|
||||
{
|
||||
std::vector<bool> result;
|
||||
while (!_fmr->eof() && (_fmr->tell().bytes < until.bytes))
|
||||
{
|
||||
bool b = readBit();
|
||||
result.push_back(b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
nanoseconds_t FluxDecoder::nextFlux()
|
||||
{
|
||||
return _fmr->readInterval(_clock_centre) * NS_PER_TICK;
|
||||
}
|
||||
37
lib/decoders/fluxdecoder.h
Normal file
37
lib/decoders/fluxdecoder.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef FLUXDECODER_H
|
||||
#define FLUXDECODER_H
|
||||
|
||||
class FluxmapReader;
|
||||
|
||||
class FluxDecoder
|
||||
{
|
||||
public:
|
||||
FluxDecoder(FluxmapReader* fmr, nanoseconds_t bitcell,
|
||||
const DecoderProto& config);
|
||||
|
||||
bool readBit();
|
||||
std::vector<bool> readBits(unsigned count);
|
||||
std::vector<bool> readBits(const Fluxmap::Position& until);
|
||||
|
||||
private:
|
||||
nanoseconds_t nextFlux();
|
||||
|
||||
private:
|
||||
FluxmapReader* _fmr;
|
||||
double _pll_phase;
|
||||
double _pll_adjust;
|
||||
double _flux_scale;
|
||||
nanoseconds_t _clock = 0;
|
||||
nanoseconds_t _clock_centre;
|
||||
nanoseconds_t _clock_min;
|
||||
nanoseconds_t _clock_max;
|
||||
nanoseconds_t _flux = 0;
|
||||
unsigned _clocked_zeroes = 0;
|
||||
unsigned _goodbits = 0;
|
||||
bool _index = false;
|
||||
bool _sync_lost = false;
|
||||
int _leading_zeroes;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,7 +18,7 @@ FluxmapReader::FluxmapReader(const Fluxmap& fluxmap):
|
||||
rewind();
|
||||
}
|
||||
|
||||
uint8_t FluxmapReader::getNextEvent(unsigned& ticks)
|
||||
void FluxmapReader::getNextEvent(int& event, unsigned& ticks)
|
||||
{
|
||||
ticks = 0;
|
||||
|
||||
@@ -26,30 +26,42 @@ uint8_t FluxmapReader::getNextEvent(unsigned& ticks)
|
||||
{
|
||||
uint8_t b = _bytes[_pos.bytes++];
|
||||
ticks += b & 0x3f;
|
||||
if (b & (F_BIT_PULSE|F_BIT_INDEX))
|
||||
if (!b || (b & (F_BIT_PULSE|F_BIT_INDEX)))
|
||||
{
|
||||
_pos.ticks += ticks;
|
||||
return b;
|
||||
event = b & 0xc0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_pos.ticks += ticks;
|
||||
return 0;
|
||||
event = F_EOF;
|
||||
}
|
||||
|
||||
unsigned FluxmapReader::findEvent(uint8_t target)
|
||||
void FluxmapReader::skipToEvent(int event)
|
||||
{
|
||||
unsigned ticks = 0;
|
||||
unsigned ticks;
|
||||
findEvent(event, ticks);
|
||||
}
|
||||
|
||||
bool FluxmapReader::findEvent(int event, unsigned& ticks)
|
||||
{
|
||||
ticks = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
unsigned thisTicks;
|
||||
uint8_t bits = getNextEvent(thisTicks);
|
||||
ticks += thisTicks;
|
||||
int thisEvent;
|
||||
getNextEvent(thisEvent, thisTicks);
|
||||
ticks += thisTicks;
|
||||
|
||||
if (thisEvent == F_EOF)
|
||||
return false;
|
||||
|
||||
if (eof())
|
||||
return 0;
|
||||
if (bits & target)
|
||||
return ticks;
|
||||
return false;
|
||||
if ((event == thisEvent) || (event & thisEvent))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,10 +70,10 @@ unsigned FluxmapReader::readInterval(nanoseconds_t clock)
|
||||
unsigned thresholdTicks = (clock * _config.pulse_debounce_threshold()) / NS_PER_TICK;
|
||||
unsigned ticks = 0;
|
||||
|
||||
while (ticks < thresholdTicks)
|
||||
while (ticks <= thresholdTicks)
|
||||
{
|
||||
unsigned thisTicks = findEvent(F_BIT_PULSE);
|
||||
if (!thisTicks)
|
||||
unsigned thisTicks;
|
||||
if (!findEvent(F_BIT_PULSE, thisTicks))
|
||||
break;
|
||||
ticks += thisTicks;
|
||||
}
|
||||
@@ -183,8 +195,9 @@ void FluxmapReader::seek(nanoseconds_t ns)
|
||||
|
||||
while (!eof() && (_pos.ticks < ticks))
|
||||
{
|
||||
int e;
|
||||
unsigned t;
|
||||
getNextEvent(t);
|
||||
getNextEvent(e, t);
|
||||
}
|
||||
_pos.zeroes = 0;
|
||||
}
|
||||
@@ -225,7 +238,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
|
||||
positions[i] = positions[i+1];
|
||||
candidates[i] = candidates[i+1];
|
||||
}
|
||||
candidates[intervalCount] = findEvent(F_BIT_PULSE);
|
||||
findEvent(F_BIT_PULSE, candidates[intervalCount]);
|
||||
positions[intervalCount] = tell();
|
||||
|
||||
}
|
||||
@@ -236,44 +249,7 @@ nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern, const Flu
|
||||
|
||||
void FluxmapReader::seekToIndexMark()
|
||||
{
|
||||
findEvent(F_BIT_INDEX);
|
||||
skipToEvent(F_BIT_INDEX);
|
||||
_pos.zeroes = 0;
|
||||
}
|
||||
|
||||
bool FluxmapReader::readRawBit(nanoseconds_t clockPeriod)
|
||||
{
|
||||
assert(clockPeriod != 0);
|
||||
|
||||
if (_pos.zeroes)
|
||||
{
|
||||
_pos.zeroes--;
|
||||
return false;
|
||||
}
|
||||
|
||||
nanoseconds_t interval = readInterval(clockPeriod)*NS_PER_TICK;
|
||||
double clocks = (double)interval / clockPeriod + _config.clock_interval_bias();
|
||||
|
||||
if (clocks < 1.0)
|
||||
clocks = 1.0;
|
||||
_pos.zeroes = (int)round(clocks) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<bool> FluxmapReader::readRawBits(unsigned count, nanoseconds_t clockPeriod)
|
||||
{
|
||||
std::vector<bool> result;
|
||||
while (!eof() && count--)
|
||||
{
|
||||
bool b = readRawBit(clockPeriod);
|
||||
result.push_back(b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<bool> FluxmapReader::readRawBits(const Fluxmap::Position& until, nanoseconds_t clockPeriod)
|
||||
{
|
||||
std::vector<bool> result;
|
||||
while (!eof() && (_pos.bytes < until.bytes))
|
||||
result.push_back(readRawBit(clockPeriod));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -93,8 +93,9 @@ public:
|
||||
return (_fluxmap.duration());
|
||||
}
|
||||
|
||||
uint8_t getNextEvent(unsigned& ticks);
|
||||
unsigned findEvent(uint8_t bits);
|
||||
void getNextEvent(int& event, unsigned& ticks);
|
||||
void skipToEvent(int event);
|
||||
bool findEvent(int event, unsigned& ticks);
|
||||
unsigned readInterval(nanoseconds_t clock); /* with debounce support */
|
||||
|
||||
/* Important! You can only reliably seek to 1 bits. */
|
||||
@@ -104,10 +105,6 @@ public:
|
||||
nanoseconds_t seekToPattern(const FluxMatcher& pattern);
|
||||
nanoseconds_t seekToPattern(const FluxMatcher& pattern, const FluxMatcher*& matching);
|
||||
|
||||
bool readRawBit(nanoseconds_t clockPeriod);
|
||||
std::vector<bool> readRawBits(unsigned count, nanoseconds_t clockPeriod);
|
||||
std::vector<bool> readRawBits(const Fluxmap::Position& until, nanoseconds_t clockPeriod);
|
||||
|
||||
private:
|
||||
const Fluxmap& _fluxmap;
|
||||
const uint8_t* _bytes;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "arch/micropolis/micropolis.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include "arch/tids990/tids990.h"
|
||||
#include "arch/victor9k/victor9k.h"
|
||||
#include "lib/encoders/encoders.pb.h"
|
||||
#include "protocol.h"
|
||||
|
||||
@@ -26,6 +27,7 @@ std::unique_ptr<AbstractEncoder> AbstractEncoder::create(const EncoderProto& con
|
||||
{ EncoderProto::kMicropolis,createMicropolisEncoder },
|
||||
{ EncoderProto::kNorthstar, createNorthstarEncoder },
|
||||
{ EncoderProto::kTids990, createTids990Encoder },
|
||||
{ EncoderProto::kVictor9K, createVictor9kEncoder },
|
||||
};
|
||||
|
||||
auto encoder = encoders.find(config.format_case());
|
||||
|
||||
@@ -8,6 +8,7 @@ import "arch/macintosh/macintosh.proto";
|
||||
import "arch/micropolis/micropolis.proto";
|
||||
import "arch/northstar/northstar.proto";
|
||||
import "arch/tids990/tids990.proto";
|
||||
import "arch/victor9k/victor9k.proto";
|
||||
//import "lib/common.proto";
|
||||
|
||||
message EncoderProto {
|
||||
@@ -20,5 +21,6 @@ message EncoderProto {
|
||||
Commodore64EncoderProto c64 = 8;
|
||||
NorthstarEncoderProto northstar = 9;
|
||||
MicropolisEncoderProto micropolis = 10;
|
||||
Victor9kEncoderProto victor9k = 11;
|
||||
}
|
||||
}
|
||||
|
||||
17
lib/fl2.proto
Normal file
17
lib/fl2.proto
Normal file
@@ -0,0 +1,17 @@
|
||||
syntax = "proto2";
|
||||
|
||||
enum FluxFileVersion {
|
||||
VERSION_1 = 1;
|
||||
}
|
||||
|
||||
message TrackFluxProto {
|
||||
optional int32 cylinder = 1;
|
||||
optional int32 head = 2;
|
||||
optional bytes flux = 3;
|
||||
}
|
||||
|
||||
message FluxFileProto {
|
||||
optional FluxFileVersion version = 1;
|
||||
repeated TrackFluxProto track = 2;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ Fluxmap& Fluxmap::appendIndex()
|
||||
return *this;
|
||||
}
|
||||
|
||||
Fluxmap& Fluxmap::appendDesync()
|
||||
{
|
||||
appendByte(F_DESYNC);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Fluxmap::precompensate(int threshold_ticks, int amount_ticks)
|
||||
{
|
||||
uint8_t junk = 0xff;
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
Fluxmap& appendInterval(uint32_t ticks);
|
||||
Fluxmap& appendPulse();
|
||||
Fluxmap& appendIndex();
|
||||
Fluxmap& appendDesync();
|
||||
|
||||
Fluxmap& appendBytes(const Bytes& bytes);
|
||||
Fluxmap& appendBytes(const uint8_t* ptr, size_t len);
|
||||
|
||||
@@ -68,14 +68,15 @@ public:
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
uint8_t bits = fmr.getNextEvent(ticks);
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
if (fmr.eof())
|
||||
break;
|
||||
timestamp += ticks;
|
||||
|
||||
if (bits & F_BIT_PULSE)
|
||||
if (event & F_BIT_PULSE)
|
||||
data[timestamp*channels + 0] = 0x7f;
|
||||
if (_config.index_markers() && (bits & F_BIT_INDEX))
|
||||
if (_config.index_markers() && (event & F_BIT_INDEX))
|
||||
data[timestamp*channels + 1] = 0x7f;
|
||||
}
|
||||
|
||||
|
||||
69
lib/fluxsink/fl2fluxsink.cc
Normal file
69
lib/fluxsink/fl2fluxsink.cc
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "sql.h"
|
||||
#include "bytes.h"
|
||||
#include "protocol.h"
|
||||
#include "fluxsink/fluxsink.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/fl2.pb.h"
|
||||
#include <fstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
class Fl2FluxSink : public FluxSink
|
||||
{
|
||||
public:
|
||||
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
|
||||
_config(lconfig),
|
||||
_of(lconfig.filename(), std::ios::out | std::ios::binary)
|
||||
{
|
||||
if (!_of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
}
|
||||
|
||||
~Fl2FluxSink()
|
||||
{
|
||||
FluxFileProto proto;
|
||||
proto.set_version(FluxFileVersion::VERSION_1);
|
||||
for (const auto& e : _data)
|
||||
{
|
||||
auto track = proto.add_track();
|
||||
track->set_cylinder(e.first.first);
|
||||
track->set_head(e.first.second);
|
||||
track->set_flux(e.second);
|
||||
}
|
||||
|
||||
if (!proto.SerializeToOstream(&_of))
|
||||
Error() << "unable to write output file";
|
||||
_of.close();
|
||||
if (_of.fail())
|
||||
Error() << "FL2 write I/O error: " << strerror(errno);
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int cylinder, int head, Fluxmap& fluxmap)
|
||||
{
|
||||
_data[std::make_pair(cylinder, head)] = fluxmap.rawBytes();
|
||||
}
|
||||
|
||||
operator std::string () const
|
||||
{
|
||||
return fmt::format("fl2({})", _config.filename());
|
||||
}
|
||||
|
||||
private:
|
||||
const Fl2FluxSinkProto& _config;
|
||||
std::ofstream _of;
|
||||
std::map<std::pair<unsigned, unsigned>, Bytes> _data;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const Fl2FluxSinkProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
|
||||
case FluxSinkProto::kScp:
|
||||
return createScpFluxSink(config.scp());
|
||||
|
||||
case FluxSinkProto::kFl2:
|
||||
return createFl2FluxSink(config.fl2());
|
||||
|
||||
default:
|
||||
Error() << "bad output disk config";
|
||||
return std::unique_ptr<FluxSink>();
|
||||
@@ -38,6 +41,7 @@ void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string&
|
||||
{
|
||||
{ std::regex("^(.*\\.flux)$"), [&](const auto& s) { proto->set_fluxfile(s); }},
|
||||
{ std::regex("^(.*\\.scp)$"), [&](const auto& s) { proto->mutable_scp()->set_filename(s); }},
|
||||
{ std::regex("^(.*\\.fl2)$"), [&](const auto& s) { proto->mutable_fl2()->set_filename(s); }},
|
||||
{ std::regex("^vcd:(.*)$"), [&](const auto& s) { proto->mutable_vcd()->set_directory(s); }},
|
||||
{ std::regex("^au:(.*)$"), [&](const auto& s) { proto->mutable_au()->set_directory(s); }},
|
||||
{ std::regex("^drive:(.*)"), [&](const auto& s) { proto->mutable_drive()->set_drive(std::stoi(s)); }},
|
||||
|
||||
@@ -10,6 +10,7 @@ class HardwareFluxSinkProto;
|
||||
class AuFluxSinkProto;
|
||||
class VcdFluxSinkProto;
|
||||
class ScpFluxSinkProto;
|
||||
class Fl2FluxSinkProto;
|
||||
|
||||
class FluxSink
|
||||
{
|
||||
@@ -21,6 +22,7 @@ public:
|
||||
static std::unique_ptr<FluxSink> createAuFluxSink(const AuFluxSinkProto& config);
|
||||
static std::unique_ptr<FluxSink> createVcdFluxSink(const VcdFluxSinkProto& config);
|
||||
static std::unique_ptr<FluxSink> createScpFluxSink(const ScpFluxSinkProto& config);
|
||||
static std::unique_ptr<FluxSink> createFl2FluxSink(const Fl2FluxSinkProto& config);
|
||||
|
||||
static std::unique_ptr<FluxSink> create(const FluxSinkProto& config);
|
||||
static void updateConfigForFilename(FluxSinkProto* proto, const std::string& filename);
|
||||
|
||||
@@ -24,6 +24,10 @@ message ScpFluxSinkProto {
|
||||
optional int32 type_byte = 4 [default = 0xff, (help) = "set the SCP disk type byte"];
|
||||
}
|
||||
|
||||
message Fl2FluxSinkProto {
|
||||
optional string filename = 1 [default = "flux.fl2", (help) = ".fl2 file to write to"];
|
||||
}
|
||||
|
||||
message FluxSinkProto {
|
||||
oneof dest {
|
||||
string fluxfile = 1 [(help) = "name of destination flux file"];
|
||||
@@ -31,6 +35,7 @@ message FluxSinkProto {
|
||||
AuFluxSinkProto au = 3;
|
||||
VcdFluxSinkProto vcd = 4;
|
||||
ScpFluxSinkProto scp = 5;
|
||||
Fl2FluxSinkProto fl2 = 6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ public:
|
||||
_fileheader.file_id[2] = 'P';
|
||||
_fileheader.version = 0x18; /* Version 1.8 of the spec */
|
||||
_fileheader.type = _config.type_byte();
|
||||
_fileheader.revolutions = 5;
|
||||
_fileheader.start_track = strackno(config.cylinders().start(), config.heads().start());
|
||||
_fileheader.end_track = strackno(config.cylinders().end(), config.heads().end());
|
||||
_fileheader.flags = (_config.align_with_index() ? SCP_FLAG_INDEXED : 0)
|
||||
@@ -55,7 +54,7 @@ public:
|
||||
_fileheader.cell_width = 0;
|
||||
_fileheader.heads = singlesided;
|
||||
|
||||
std::cout << fmt::format("Writing 96 tpi {} SCP file containing {} SCP tracks\n",
|
||||
std::cout << fmt::format("SCP: writing 96 tpi {} file containing {} tracks\n",
|
||||
singlesided ? "single sided" : "double sided",
|
||||
_fileheader.end_track - _fileheader.start_track + 1
|
||||
);
|
||||
@@ -71,7 +70,7 @@ public:
|
||||
appendChecksum(checksum, _trackdata);
|
||||
write_le32(_fileheader.checksum, checksum);
|
||||
|
||||
std::cout << "Writing output file...\n";
|
||||
std::cout << "SCP: writing output file...\n";
|
||||
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
Error() << "cannot open output file";
|
||||
@@ -98,7 +97,7 @@ public:
|
||||
ByteWriter fluxdataWriter(fluxdata);
|
||||
|
||||
if (_config.align_with_index())
|
||||
fmr.findEvent(F_BIT_INDEX);
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
|
||||
int revolution = 0;
|
||||
unsigned revTicks = 0;
|
||||
@@ -108,12 +107,14 @@ public:
|
||||
while (revolution < 5)
|
||||
{
|
||||
unsigned ticks;
|
||||
uint8_t bits = fmr.getNextEvent(ticks);
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
|
||||
ticksSinceLastPulse += ticks;
|
||||
totalTicks += ticks;
|
||||
revTicks += ticks;
|
||||
|
||||
if (fmr.eof() || (bits & F_BIT_INDEX))
|
||||
if (fmr.eof() || (event & F_BIT_INDEX))
|
||||
{
|
||||
auto* revheader = &trackheader.revolution[revolution];
|
||||
write_le32(revheader->offset, startOffset + sizeof(ScpTrack));
|
||||
@@ -125,7 +126,7 @@ public:
|
||||
startOffset = fluxdataWriter.pos;
|
||||
}
|
||||
|
||||
if (bits & F_BIT_PULSE)
|
||||
if (event & F_BIT_PULSE)
|
||||
{
|
||||
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
|
||||
while (t >= 0x10000)
|
||||
@@ -138,6 +139,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
_fileheader.revolutions = revolution - 1;
|
||||
write_le32(_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
|
||||
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
|
||||
trackdataWriter += fluxdata;
|
||||
|
||||
@@ -43,7 +43,8 @@ public:
|
||||
while (!fmr.eof())
|
||||
{
|
||||
unsigned ticks;
|
||||
uint8_t bits = fmr.getNextEvent(ticks);
|
||||
int event;
|
||||
fmr.getNextEvent(event, ticks);
|
||||
if (fmr.eof())
|
||||
break;
|
||||
|
||||
@@ -55,9 +56,9 @@ public:
|
||||
of << fmt::format("#{} ", (uint64_t)(timestamp * NS_PER_TICK));
|
||||
}
|
||||
|
||||
if (bits & F_BIT_PULSE)
|
||||
if (event & F_BIT_PULSE)
|
||||
of << "1p ";
|
||||
if (bits & F_BIT_INDEX)
|
||||
if (event & F_BIT_INDEX)
|
||||
of << "1i ";
|
||||
|
||||
lasttimestamp = timestamp;
|
||||
|
||||
@@ -139,77 +139,3 @@ std::unique_ptr<FluxSource> FluxSource::createCwfFluxSource(const CwfFluxSourceP
|
||||
{
|
||||
return std::unique_ptr<FluxSource>(new CwfFluxSource(config));
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "sql.h"
|
||||
#include "bytes.h"
|
||||
#include "protocol.h"
|
||||
|
||||
static std::ifstream inputFile;
|
||||
static sqlite3* outputDb;
|
||||
static CwfHeader header;
|
||||
static double clockRate;
|
||||
|
||||
static void syntax()
|
||||
{
|
||||
std::cout << "Syntax: fluxengine convert cwftoflux <cwffile> <fluxfile>\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void check_for_error()
|
||||
{
|
||||
if (inputFile.fail())
|
||||
Error() << fmt::format("I/O error: {}", strerror(errno));
|
||||
}
|
||||
|
||||
static void read_header()
|
||||
{
|
||||
}
|
||||
|
||||
static void read_track()
|
||||
{
|
||||
CwfTrack trackheader;
|
||||
inputFile.read((char*) &trackheader, sizeof(trackheader));
|
||||
check_for_error();
|
||||
|
||||
uint32_t length = Bytes(trackheader.length, 4).reader().read_le32() - sizeof(CwfTrack);
|
||||
unsigned track_number = trackheader.track * header.step;
|
||||
std::cout << fmt::format("{}.{}: {} input bytes; ", track_number, trackheader.side, length)
|
||||
<< std::flush;
|
||||
|
||||
std::vector<uint8_t> inputdata(length);
|
||||
inputFile.read((char*) &inputdata[0], length);
|
||||
check_for_error();
|
||||
|
||||
|
||||
std::cout << fmt::format(" {} ms in {} output bytes\n",
|
||||
fluxmap.duration() / 1e6, fluxmap.bytes());
|
||||
sqlWriteFlux(outputDb, track_number, trackheader.side, fluxmap);
|
||||
}
|
||||
|
||||
int mainConvertCwfToFlux(int argc, const char* argv[])
|
||||
{
|
||||
if (argc != 3)
|
||||
syntax();
|
||||
|
||||
inputFile.open(argv[1], std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << fmt::format("cannot open input file '{}'", argv[1]);
|
||||
|
||||
outputDb = sqlOpen(argv[2], SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
||||
sqlPrepareFlux(outputDb);
|
||||
sqlWriteIntProperty(outputDb, "version", FLUX_VERSION_CURRENT);
|
||||
sqlStmt(outputDb, "BEGIN;");
|
||||
|
||||
read_header();
|
||||
inputFile.seekg(sizeof(header), std::ios::beg);
|
||||
for (unsigned i=0; i<(header.cylinders*header.sides); i++)
|
||||
read_track();
|
||||
|
||||
sqlStmt(outputDb, "COMMIT;");
|
||||
sqlClose(outputDb);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
55
lib/fluxsource/fl2fluxsource.cc
Normal file
55
lib/fluxsource/fl2fluxsource.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "lib/fl2.pb.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include <fstream>
|
||||
|
||||
class Fl2FluxSource : public FluxSource
|
||||
{
|
||||
public:
|
||||
Fl2FluxSource(const Fl2FluxSourceProto& config):
|
||||
_config(config)
|
||||
{
|
||||
std::ifstream ifs(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
Error() << fmt::format("cannot open input file '{}': {}",
|
||||
_config.filename(), strerror(errno));
|
||||
|
||||
if (!_proto.ParseFromIstream(&ifs))
|
||||
Error() << "unable to read input file";
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Fluxmap> readFlux(int cylinder, int head)
|
||||
{
|
||||
for (const auto& track : _proto.track())
|
||||
{
|
||||
if ((track.cylinder() == cylinder) && (track.head() == head))
|
||||
return std::make_unique<Fluxmap>(track.flux());
|
||||
}
|
||||
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
}
|
||||
|
||||
void recalibrate() {}
|
||||
|
||||
private:
|
||||
void check_for_error(std::ifstream& ifs)
|
||||
{
|
||||
if (ifs.fail())
|
||||
Error() << fmt::format("FL2 read I/O error: {}", strerror(errno));
|
||||
}
|
||||
|
||||
private:
|
||||
const Fl2FluxSourceProto& _config;
|
||||
FluxFileProto _proto;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSource> FluxSource::createFl2FluxSource(const Fl2FluxSourceProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSource>(new Fl2FluxSource(config));
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
|
||||
case FluxSourceProto::kCwf:
|
||||
return createCwfFluxSource(config.cwf());
|
||||
|
||||
case FluxSourceProto::kFl2:
|
||||
return createFl2FluxSource(config.fl2());
|
||||
|
||||
default:
|
||||
Error() << "bad input disk configuration";
|
||||
return std::unique_ptr<FluxSource>();
|
||||
@@ -52,6 +55,7 @@ void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::stri
|
||||
{ std::regex("^(.*\\.flux)$"), [&](const auto& s) { proto->set_fluxfile(s); }},
|
||||
{ std::regex("^(.*\\.scp)$"), [&](const auto& s) { proto->mutable_scp()->set_filename(s); }},
|
||||
{ std::regex("^(.*\\.cwf)$"), [&](const auto& s) { proto->mutable_cwf()->set_filename(s); }},
|
||||
{ std::regex("^(.*\\.fl2)$"), [&](const auto& s) { proto->mutable_fl2()->set_filename(s); }},
|
||||
{ std::regex("^erase:$"), [&](const auto& s) { proto->mutable_erase(); }},
|
||||
{ std::regex("^kryoflux:(.*)$"), [&](const auto& s) { proto->mutable_kryoflux()->set_directory(s); }},
|
||||
{ std::regex("^testpattern:(.*)"), [&](const auto& s) { proto->mutable_test_pattern(); }},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
class CwfFluxSourceProto;
|
||||
class EraseFluxSourceProto;
|
||||
class Fl2FluxSourceProto;
|
||||
class FluxSourceProto;
|
||||
class FluxSpec;
|
||||
class Fluxmap;
|
||||
@@ -21,6 +22,7 @@ public:
|
||||
private:
|
||||
static std::unique_ptr<FluxSource> createCwfFluxSource(const CwfFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createEraseFluxSource(const EraseFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createFl2FluxSource(const Fl2FluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createHardwareFluxSource(const HardwareFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createKryofluxFluxSource(const KryofluxFluxSourceProto& config);
|
||||
static std::unique_ptr<FluxSource> createScpFluxSource(const ScpFluxSourceProto& config);
|
||||
|
||||
@@ -29,10 +29,15 @@ message ScpFluxSourceProto {
|
||||
}
|
||||
|
||||
message CwfFluxSourceProto {
|
||||
optional string filename = 1 [default = "flux.xwf",
|
||||
optional string filename = 1 [default = "flux.cwf",
|
||||
(help) = ".cwf file to read flux from"];
|
||||
}
|
||||
|
||||
message Fl2FluxSourceProto {
|
||||
optional string filename = 1 [default = "flux.fl2",
|
||||
(help) = ".fl2 file to read flux from"];
|
||||
}
|
||||
|
||||
message FluxSourceProto {
|
||||
oneof source {
|
||||
string fluxfile = 1 [default = "name of source flux file"];
|
||||
@@ -42,6 +47,7 @@ message FluxSourceProto {
|
||||
KryofluxFluxSourceProto kryoflux = 5;
|
||||
ScpFluxSourceProto scp = 6;
|
||||
CwfFluxSourceProto cwf = 7;
|
||||
Fl2FluxSourceProto fl2 = 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
#define mkdir(A, B) _mkdir(A)
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
static inline std::vector<T> vector_of(T item)
|
||||
{ return std::vector<T> { item }; }
|
||||
|
||||
typedef double nanoseconds_t;
|
||||
class Bytes;
|
||||
|
||||
@@ -45,5 +49,4 @@ private:
|
||||
std::stringstream _stream;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
Image readImage()
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
return 17;
|
||||
};
|
||||
|
||||
Image image;
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (int track = 0; track < 40; track++)
|
||||
{
|
||||
int numSectors = sectorsPerTrack(track);
|
||||
@@ -62,7 +62,7 @@ public:
|
||||
{
|
||||
for (int sectorId = 0; sectorId < numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = image.put(track, head, sectorId);
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
if ((offset < inputFileSize))
|
||||
{ //still data available sector OK
|
||||
br.seek(offset);
|
||||
@@ -88,7 +88,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
image.calculateSize();
|
||||
image->calculateSize();
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
176
lib/imagereader/d88imagereader.cc
Normal file
176
lib/imagereader/d88imagereader.cc
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "proto.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "imagereader/imagereaderimpl.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
// reader based on this partial documentation of the D88 format:
|
||||
// https://www.pc98.org/project/doc/d88.html
|
||||
|
||||
class D88ImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
D88ImageReader(const ImageReaderProto& config):
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
|
||||
Bytes header(0x24); // read first entry of track table as well
|
||||
inputFile.read((char*) header.begin(), header.size());
|
||||
|
||||
// the DIM header technically has a bit field for sectors present,
|
||||
// however it is currently ignored by this reader
|
||||
|
||||
std::string diskName = header.slice(0, 0x16);
|
||||
|
||||
if (diskName[0])
|
||||
std::cout << "D88: disk name: " << diskName << "\n";
|
||||
|
||||
ByteReader headerReader(header);
|
||||
|
||||
// media flag indicates media density, currently unused
|
||||
char mediaFlag = headerReader.seek(0x1b).read_8();
|
||||
|
||||
inputFile.seekg( 0, std::ios::end );
|
||||
int fileSize = inputFile.tellg();
|
||||
|
||||
int diskSize = headerReader.seek(0x1c).read_le32();
|
||||
|
||||
if (diskSize > fileSize)
|
||||
std::cout << "D88: found multiple disk images. Only using first\n";
|
||||
|
||||
int trackTableEnd = headerReader.seek(0x20).read_le32();
|
||||
int trackTableSize = trackTableEnd - 0x20;
|
||||
|
||||
Bytes trackTable(trackTableSize);
|
||||
inputFile.seekg(0x20);
|
||||
inputFile.read((char*) trackTable.begin(), trackTable.size());
|
||||
ByteReader trackTableReader(trackTable);
|
||||
|
||||
int diskSectorsPerTrack = -1;
|
||||
|
||||
if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET)
|
||||
std::cout << "D88: overriding configured format";
|
||||
|
||||
auto ibm = config.mutable_encoder()->mutable_ibm();
|
||||
config.mutable_cylinders()->set_end(0);
|
||||
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (int track = 0; track < trackTableSize / 4; track++)
|
||||
{
|
||||
int trackOffset = trackTableReader.seek(track * 4).read_le32();
|
||||
if (trackOffset == 0) continue;
|
||||
|
||||
int currentTrackOffset = trackOffset;
|
||||
int currentTrackCylinder = -1;
|
||||
int currentSectorsInTrack = 0xffff; // don't know # of sectors until we read the first one
|
||||
int trackSectorSize = -1;
|
||||
int trackMfm = -1;
|
||||
|
||||
auto trackdata = ibm->add_trackdata();
|
||||
trackdata->set_clock_rate_khz(500);
|
||||
trackdata->set_track_length_ms(167);
|
||||
auto sectors = trackdata->mutable_sectors();
|
||||
|
||||
for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack; sectorInTrack++){
|
||||
Bytes sectorHeader(0x10);
|
||||
inputFile.read((char*) sectorHeader.begin(), sectorHeader.size());
|
||||
ByteReader sectorHeaderReader(sectorHeader);
|
||||
int cylinder = 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();
|
||||
int sectorsInTrack = sectorHeaderReader.seek(4).read_le16();
|
||||
int fm = sectorHeaderReader.seek(6).read_8();
|
||||
int ddam = sectorHeaderReader.seek(7).read_8();
|
||||
int fddStatusCode = sectorHeaderReader.seek(8).read_8();
|
||||
int rpm = sectorHeaderReader.seek(13).read_8();
|
||||
// D88 provides much more sector information that is currently ignored
|
||||
if (ddam != 0)
|
||||
Error() << "D88: nonzero ddam currently unsupported";
|
||||
if (rpm != 0)
|
||||
Error() << "D88: 1.44MB 300rpm formats currently unsupported";
|
||||
if (fddStatusCode != 0)
|
||||
Error() << "D88: nonzero fdd status codes are currently unsupported";
|
||||
if (currentSectorsInTrack == 0xffff) {
|
||||
currentSectorsInTrack = sectorsInTrack;
|
||||
} else if (currentSectorsInTrack != sectorsInTrack) {
|
||||
Error() << "D88: mismatched number of sectors in track";
|
||||
}
|
||||
if (diskSectorsPerTrack < 0) {
|
||||
diskSectorsPerTrack = sectorsInTrack;
|
||||
} else if (diskSectorsPerTrack != sectorsInTrack) {
|
||||
Error() << "D88: varying numbers of sectors per track is currently unsupported";
|
||||
}
|
||||
if (currentTrackCylinder < 0) {
|
||||
currentTrackCylinder = cylinder;
|
||||
} else if (currentTrackCylinder != cylinder) {
|
||||
Error() << "D88: all sectors in a track must belong to the same cylinder";
|
||||
}
|
||||
if (trackSectorSize < 0) {
|
||||
trackSectorSize = sectorSize;
|
||||
// this is the first sector we've read, use it settings for per-track data
|
||||
trackdata->set_cylinder(cylinder);
|
||||
trackdata->set_head(head);
|
||||
trackdata->set_sector_size(sectorSize);
|
||||
trackdata->set_use_fm(fm);
|
||||
if (fm) {
|
||||
//trackdata->set_clock_rate_khz(250*300/360);
|
||||
trackdata->set_idam_byte(0xf57e);
|
||||
trackdata->set_dam_byte(0xf56f);
|
||||
}
|
||||
// create timings to approximately match N88-BASIC
|
||||
if (sectorSize <= 128) {
|
||||
trackdata->set_gap0(0x1b);
|
||||
trackdata->set_gap2(0x09);
|
||||
trackdata->set_gap3(0x1b);
|
||||
} else if (sectorSize <= 256) {
|
||||
trackdata->set_gap0(0x36);
|
||||
trackdata->set_gap3(0x36);
|
||||
}
|
||||
} else if (trackSectorSize != sectorSize) {
|
||||
Error() << "D88: multiple sector sizes per track are currently unsupported";
|
||||
}
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*) data.begin(), data.size());
|
||||
const auto& sector = image->put(cylinder, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = cylinder;
|
||||
sector->physicalCylinder = cylinder;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
std::cout << fmt::format("D88: read {} tracks, {} sides\n",
|
||||
geometry.numTracks, geometry.numSides);
|
||||
return image;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::createD88ImageReader(
|
||||
const ImageReaderProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageReader>(new D88ImageReader(config));
|
||||
}
|
||||
|
||||
150
lib/imagereader/dimimagereader.cc
Normal file
150
lib/imagereader/dimimagereader.cc
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "proto.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "imagereader/imagereaderimpl.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
// reader based on this partial documentation of the DIM format:
|
||||
// https://www.pc98.org/project/doc/dim.html
|
||||
|
||||
class DimImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
DimImageReader(const ImageReaderProto& config):
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
|
||||
Bytes header(256);
|
||||
inputFile.read((char*) header.begin(), header.size());
|
||||
if (header.slice(0xAB, 13) != Bytes("DIFC HEADER "))
|
||||
Error() << "DIM: could not find DIM header, is this a DIM file?";
|
||||
|
||||
// the DIM header technically has a bit field for sectors present,
|
||||
// however it is currently ignored by this reader
|
||||
|
||||
char mediaByte = header[0];
|
||||
int tracks;
|
||||
int sectorsPerTrack;
|
||||
int sectorSize;
|
||||
switch (mediaByte) {
|
||||
case 0:
|
||||
tracks = 77;
|
||||
sectorsPerTrack = 8;
|
||||
sectorSize = 1024;
|
||||
break;
|
||||
case 1:
|
||||
tracks = 80;
|
||||
sectorsPerTrack = 9;
|
||||
sectorSize = 1024;
|
||||
break;
|
||||
case 2:
|
||||
tracks = 80;
|
||||
sectorsPerTrack = 15;
|
||||
sectorSize = 512;
|
||||
break;
|
||||
case 3:
|
||||
tracks = 80;
|
||||
sectorsPerTrack = 18;
|
||||
sectorSize = 512;
|
||||
break;
|
||||
default:
|
||||
Error() << "DIM: unsupported media byte";
|
||||
break;
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
int trackCount = 0;
|
||||
for (int track = 0; track < tracks; track++)
|
||||
{
|
||||
if (inputFile.eof())
|
||||
break;
|
||||
int physicalCylinder = track;
|
||||
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
std::vector<unsigned> sectors;
|
||||
for (int i = 0; i < sectorsPerTrack; i++)
|
||||
sectors.push_back(i + 1);
|
||||
|
||||
for (int sectorId : sectors)
|
||||
{
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*) data.begin(), data.size());
|
||||
|
||||
const auto& sector = image->put(physicalCylinder, side, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->logicalSide = sector->physicalHead = side;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
}
|
||||
}
|
||||
|
||||
trackCount++;
|
||||
}
|
||||
|
||||
if (config.encoder().format_case() == EncoderProto::FormatCase::FORMAT_NOT_SET)
|
||||
{
|
||||
auto ibm = config.mutable_encoder()->mutable_ibm();
|
||||
auto trackdata = ibm->add_trackdata();
|
||||
trackdata->set_clock_rate_khz(500);
|
||||
auto sectors = trackdata->mutable_sectors();
|
||||
switch (mediaByte) {
|
||||
case 0x00:
|
||||
std::cout << "DIM: automatically setting format to 1.2MB (1024 byte sectors)\n";
|
||||
config.mutable_cylinders()->set_end(76);
|
||||
trackdata->set_track_length_ms(167);
|
||||
trackdata->set_sector_size(1024);
|
||||
for (int i = 0; i < 9; i++)
|
||||
sectors->add_sector(i);
|
||||
break;
|
||||
case 0x02:
|
||||
std::cout << "DIM: automatically setting format to 1.2MB (512 byte sectors)\n";
|
||||
trackdata->set_track_length_ms(167);
|
||||
trackdata->set_sector_size(512);
|
||||
for (int i = 0; i < 15; i++)
|
||||
sectors->add_sector(i);
|
||||
break;
|
||||
case 0x03:
|
||||
std::cout << "DIM: automatically setting format to 1.44MB\n";
|
||||
trackdata->set_track_length_ms(200);
|
||||
trackdata->set_sector_size(512);
|
||||
for (int i = 0; i < 18; i++)
|
||||
sectors->add_sector(i);
|
||||
break;
|
||||
default:
|
||||
Error() << fmt::format("DIM: unknown media byte 0x%02x, could not determine write profile automatically", mediaByte);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
std::cout << fmt::format("DIM: read {} tracks, {} sides, {} kB total\n",
|
||||
geometry.numTracks, geometry.numSides,
|
||||
((int)inputFile.tellg() - 256) / 1024);
|
||||
return image;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::createDimImageReader(
|
||||
const ImageReaderProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageReader>(new DimImageReader(config));
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
Image readImage()
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
@@ -89,7 +89,7 @@ public:
|
||||
uint32_t dataPtr = 0x54;
|
||||
uint32_t tagPtr = dataPtr + dataSize;
|
||||
|
||||
Image image;
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (int track = 0; track < numCylinders; track++)
|
||||
{
|
||||
int numSectors = sectorsPerTrack(track);
|
||||
@@ -105,7 +105,7 @@ public:
|
||||
Bytes tag = br.read(12);
|
||||
tagPtr += 12;
|
||||
|
||||
const auto& sector = image.put(track, head, sectorId);
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = track;
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
@@ -115,7 +115,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
image.setGeometry({
|
||||
image->setGeometry({
|
||||
.numTracks = numCylinders,
|
||||
.numSides = numHeads,
|
||||
.numSectors = 12,
|
||||
|
||||
94
lib/imagereader/fdiimagereader.cc
Normal file
94
lib/imagereader/fdiimagereader.cc
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "imagereader/imagereaderimpl.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
// reader based on this partial documentation of the FDI format:
|
||||
// https://www.pc98.org/project/doc/hdi.html
|
||||
|
||||
class FdiImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
FdiImageReader(const ImageReaderProto& config):
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
|
||||
Bytes header(32);
|
||||
inputFile.read((char*) header.begin(), header.size());
|
||||
ByteReader headerReader(header);
|
||||
if (headerReader.seek(0).read_le32() != 0)
|
||||
Error() << "FDI: could not find FDI header, is this a FDI file?";
|
||||
|
||||
// we currently don't use fddType but it could be used to automatically select
|
||||
// profile parameters in the future
|
||||
//
|
||||
int fddType = headerReader.seek(4).read_le32();
|
||||
int headerSize = headerReader.seek(0x08).read_le32();
|
||||
int sectorSize = headerReader.seek(0x10).read_le32();
|
||||
int sectorsPerTrack = headerReader.seek(0x14).read_le32();
|
||||
int sides = headerReader.seek(0x18).read_le32();
|
||||
int tracks = headerReader.seek(0x1c).read_le32();
|
||||
|
||||
inputFile.seekg(headerSize);
|
||||
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
int trackCount = 0;
|
||||
for (int track = 0; track < tracks; track++)
|
||||
{
|
||||
if (inputFile.eof())
|
||||
break;
|
||||
int physicalCylinder = track;
|
||||
|
||||
for (int side = 0; side < sides; side++)
|
||||
{
|
||||
std::vector<unsigned> sectors;
|
||||
for (int i = 0; i < sectorsPerTrack; i++)
|
||||
sectors.push_back(i + 1);
|
||||
|
||||
for (int sectorId : sectors)
|
||||
{
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*) data.begin(), data.size());
|
||||
|
||||
const auto& sector = image->put(physicalCylinder, side, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->logicalSide = sector->physicalHead = side;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
}
|
||||
}
|
||||
|
||||
trackCount++;
|
||||
}
|
||||
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
std::cout << fmt::format("FDI: read {} tracks, {} sides, {} kB total\n",
|
||||
geometry.numTracks, geometry.numSides,
|
||||
((int)inputFile.tellg() - headerSize) / 1024);
|
||||
return image;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::createFdiImageReader(
|
||||
const ImageReaderProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageReader>(new FdiImageReader(config));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,15 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
|
||||
{
|
||||
switch (config.format_case())
|
||||
{
|
||||
case ImageReaderProto::kDim:
|
||||
return ImageReader::createDimImageReader(config);
|
||||
|
||||
case ImageReaderProto::kD88:
|
||||
return ImageReader::createD88ImageReader(config);
|
||||
|
||||
case ImageReaderProto::kFdi:
|
||||
return ImageReader::createFdiImageReader(config);
|
||||
|
||||
case ImageReaderProto::kImd:
|
||||
return ImageReader::createIMDImageReader(config);
|
||||
|
||||
@@ -49,12 +58,16 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st
|
||||
{".jv3", [&]() { proto->mutable_jv3(); }},
|
||||
{".d64", [&]() { proto->mutable_d64(); }},
|
||||
{".d81", [&]() { proto->mutable_img(); }},
|
||||
{".d88", [&]() { proto->mutable_d88(); }},
|
||||
{".dim", [&]() { proto->mutable_dim(); }},
|
||||
{".diskcopy", [&]() { proto->mutable_diskcopy(); }},
|
||||
{".fdi", [&]() { proto->mutable_fdi(); }},
|
||||
{".imd", [&]() { proto->mutable_imd(); }},
|
||||
{".img", [&]() { proto->mutable_img(); }},
|
||||
{".st", [&]() { proto->mutable_img(); }},
|
||||
{".nsi", [&]() { proto->mutable_nsi(); }},
|
||||
{".td0", [&]() { proto->mutable_td0(); }},
|
||||
{".TD0", [&]() { proto->mutable_td0(); }},
|
||||
{".xdf", [&]() { proto->mutable_img(); }},
|
||||
};
|
||||
|
||||
for (const auto& it : formats)
|
||||
@@ -90,4 +103,3 @@ void getTrackFormat(const ImgInputOutputProto& config,
|
||||
trackdata.MergeFrom(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef IMAGEREADER_H
|
||||
#define IMAGEREADER_H
|
||||
|
||||
#include "image.h"
|
||||
|
||||
class ImageSpec;
|
||||
class ImageReaderProto;
|
||||
class Image;
|
||||
|
||||
class ImageReader
|
||||
{
|
||||
@@ -23,9 +24,12 @@ public:
|
||||
static std::unique_ptr<ImageReader> createIMDImageReader(const ImageReaderProto& config);
|
||||
static std::unique_ptr<ImageReader> createNsiImageReader(const ImageReaderProto& config);
|
||||
static std::unique_ptr<ImageReader> createTd0ImageReader(const ImageReaderProto& config);
|
||||
static std::unique_ptr<ImageReader> createDimImageReader(const ImageReaderProto& config);
|
||||
static std::unique_ptr<ImageReader> createFdiImageReader(const ImageReaderProto& config);
|
||||
static std::unique_ptr<ImageReader> createD88ImageReader(const ImageReaderProto& config);
|
||||
|
||||
public:
|
||||
virtual Image readImage() = 0;
|
||||
virtual std::unique_ptr<Image> readImage() = 0;
|
||||
|
||||
protected:
|
||||
const ImageReaderProto& _config;
|
||||
|
||||
@@ -38,6 +38,9 @@ message Jv3InputProto {}
|
||||
message D64InputProto {}
|
||||
message NsiInputProto {}
|
||||
message Td0InputProto {}
|
||||
message DimInputProto {}
|
||||
message FdiInputProto {}
|
||||
message D88InputProto {}
|
||||
|
||||
message ImageReaderProto {
|
||||
optional string filename = 1 [(help) = "filename of input sector image"];
|
||||
@@ -49,6 +52,9 @@ message ImageReaderProto {
|
||||
D64InputProto d64 = 6;
|
||||
NsiInputProto nsi = 7;
|
||||
Td0InputProto td0 = 8;
|
||||
DimInputProto dim = 9;
|
||||
FdiInputProto fdi = 10;
|
||||
D88InputProto d88 = 11;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "sector.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "image.h"
|
||||
#include "proto.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
@@ -11,68 +12,68 @@
|
||||
|
||||
static unsigned getModulationandSpeed(uint8_t flags, bool *mfm)
|
||||
{
|
||||
switch (flags)
|
||||
{
|
||||
case 0: /* 500 kbps FM */
|
||||
//clockRateKhz.setDefaultValue(250);
|
||||
*mfm = false;
|
||||
return 500;
|
||||
break;
|
||||
switch (flags)
|
||||
{
|
||||
case 0: /* 500 kbps FM */
|
||||
//clockRateKhz.setDefaultValue(250);
|
||||
*mfm = false;
|
||||
return 500;
|
||||
break;
|
||||
|
||||
case 1: /* 300 kbps FM */
|
||||
*mfm = false;
|
||||
return 300;
|
||||
|
||||
break;
|
||||
case 1: /* 300 kbps FM */
|
||||
*mfm = false;
|
||||
return 300;
|
||||
|
||||
break;
|
||||
|
||||
case 2: /* 250 kbps FM */
|
||||
*mfm = false;
|
||||
return 250;
|
||||
break;
|
||||
case 2: /* 250 kbps FM */
|
||||
*mfm = false;
|
||||
return 250;
|
||||
break;
|
||||
|
||||
case 3: /* 500 kbps MFM */
|
||||
*mfm = true;
|
||||
return 500;
|
||||
break;
|
||||
case 3: /* 500 kbps MFM */
|
||||
*mfm = true;
|
||||
return 500;
|
||||
break;
|
||||
|
||||
case 4: /* 300 kbps MFM */
|
||||
*mfm = true;
|
||||
return 300;
|
||||
break;
|
||||
case 4: /* 300 kbps MFM */
|
||||
*mfm = true;
|
||||
return 300;
|
||||
break;
|
||||
|
||||
case 5: /* 250 kbps MFM */
|
||||
*mfm = true;
|
||||
return 250;
|
||||
break;
|
||||
case 5: /* 250 kbps MFM */
|
||||
*mfm = true;
|
||||
return 250;
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << fmt::format("don't understand IMD disks with this modulation and speed {}", flags);
|
||||
throw 0;
|
||||
}
|
||||
default:
|
||||
Error() << fmt::format("don't understand IMD disks with this modulation and speed {}", flags);
|
||||
throw 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackHeader
|
||||
{
|
||||
uint8_t ModeValue;
|
||||
uint8_t track;
|
||||
uint8_t Head;
|
||||
uint8_t numSectors;
|
||||
uint8_t SectorSize;
|
||||
uint8_t ModeValue;
|
||||
uint8_t track;
|
||||
uint8_t Head;
|
||||
uint8_t numSectors;
|
||||
uint8_t SectorSize;
|
||||
};
|
||||
|
||||
static unsigned getSectorSize(uint8_t flags)
|
||||
{
|
||||
switch (flags)
|
||||
{
|
||||
case 0: return 128;
|
||||
case 1: return 256;
|
||||
case 2: return 512;
|
||||
case 3: return 1024;
|
||||
case 4: return 2048;
|
||||
case 5: return 4096;
|
||||
case 6: return 8192;
|
||||
}
|
||||
Error() << "not reachable";
|
||||
switch (flags)
|
||||
{
|
||||
case 0: return 128;
|
||||
case 1: return 256;
|
||||
case 2: return 512;
|
||||
case 3: return 1024;
|
||||
case 4: return 2048;
|
||||
case 5: return 4096;
|
||||
case 6: return 8192;
|
||||
}
|
||||
Error() << "not reachable";
|
||||
}
|
||||
|
||||
|
||||
@@ -85,204 +86,171 @@ static unsigned getSectorSize(uint8_t flags)
|
||||
class IMDImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
IMDImageReader(const ImageReaderProto& config):
|
||||
ImageReader(config)
|
||||
{}
|
||||
IMDImageReader(const ImageReaderProto& config):
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
Image readImage()
|
||||
/*
|
||||
IMAGE FILE FORMAT
|
||||
The overall layout of an ImageDisk .IMD image file is:
|
||||
IMD v.vv: dd/mm/yyyy hh:mm:ss
|
||||
Comment (ASCII only - unlimited size)
|
||||
1A byte - ASCII EOF character
|
||||
- For each track on the disk:
|
||||
1 byte Mode value see getModulationspeed for definition
|
||||
1 byte Cylinder
|
||||
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 head map (optional) definied in high byte of head (since head is 0 or 1)
|
||||
sector data records
|
||||
<End of file>
|
||||
*/
|
||||
{
|
||||
//Read File
|
||||
std::unique_ptr<Image> readImage()
|
||||
/*
|
||||
IMAGE FILE FORMAT
|
||||
The overall layout of an ImageDisk .IMD image file is:
|
||||
IMD v.vv: dd/mm/yyyy hh:mm:ss
|
||||
Comment (ASCII only - unlimited size)
|
||||
1A byte - ASCII EOF character
|
||||
- For each track on the disk:
|
||||
1 byte Mode value see getModulationspeed for definition
|
||||
1 byte Cylinder
|
||||
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 head map (optional) definied in high byte of head (since head is 0 or 1)
|
||||
sector data records
|
||||
<End of file>
|
||||
*/
|
||||
{
|
||||
//Read File
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
//define some variables
|
||||
bool mfm = false; //define coding just to show in comment for setting the right write parameters
|
||||
inputFile.seekg(0, inputFile.end);
|
||||
int inputFileSize = inputFile.tellg(); // determine filesize
|
||||
inputFile.seekg(0, inputFile.beg);
|
||||
Bytes data;
|
||||
data.writer() += inputFile;
|
||||
ByteReader br(data);
|
||||
Image image;
|
||||
TrackHeader header = {0, 0, 0, 0, 0};
|
||||
//define some variables
|
||||
bool mfm = false; //define coding just to show in comment for setting the right write parameters
|
||||
inputFile.seekg(0, inputFile.end);
|
||||
int inputFileSize = inputFile.tellg(); // determine filesize
|
||||
inputFile.seekg(0, inputFile.beg);
|
||||
Bytes data;
|
||||
data.writer() += inputFile;
|
||||
ByteReader br(data);
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
TrackHeader header = {0, 0, 0, 0, 0};
|
||||
|
||||
unsigned n = 0;
|
||||
unsigned headerPtr = 0;
|
||||
unsigned Modulation_Speed = 0;
|
||||
unsigned sectorSize = 0;
|
||||
std::string sector_skew;
|
||||
int b;
|
||||
unsigned char comment[8192]; //i choose a fixed value. dont know how to make dynamic arrays in C++. This should be enough
|
||||
// Read comment
|
||||
while ((b = br.read_8()) != EOF && b != 0x1A)
|
||||
{
|
||||
comment[n++] = (unsigned char)b;
|
||||
}
|
||||
headerPtr = n; //set pointer to after comment
|
||||
comment[n] = '\0'; // null-terminate the string
|
||||
//write comment to screen
|
||||
std::cout << "Comment in IMD image:\n"
|
||||
<< fmt::format("{}\n",
|
||||
comment);
|
||||
unsigned n = 0;
|
||||
unsigned headerPtr = 0;
|
||||
unsigned Modulation_Speed = 0;
|
||||
unsigned sectorSize = 0;
|
||||
std::string sector_skew;
|
||||
int b;
|
||||
unsigned char comment[8192]; //i choose a fixed value. dont know how to make dynamic arrays in C++. This should be enough
|
||||
// Read comment
|
||||
while ((b = br.read_8()) != EOF && b != 0x1A)
|
||||
{
|
||||
comment[n++] = (unsigned char)b;
|
||||
}
|
||||
headerPtr = n; //set pointer to after comment
|
||||
comment[n] = '\0'; // null-terminate the string
|
||||
//write comment to screen
|
||||
std::cout << "Comment in IMD image:\n"
|
||||
<< fmt::format("{}\n",
|
||||
comment);
|
||||
|
||||
//first read header
|
||||
for (;;)
|
||||
{
|
||||
if (headerPtr >= inputFileSize-1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
header.ModeValue = br.read_8();
|
||||
headerPtr++;
|
||||
Modulation_Speed = getModulationandSpeed(header.ModeValue, &mfm);
|
||||
header.track = br.read_8();
|
||||
headerPtr++;
|
||||
header.Head = br.read_8();
|
||||
headerPtr++;
|
||||
header.numSectors = br.read_8();
|
||||
headerPtr++;
|
||||
header.SectorSize = br.read_8();
|
||||
headerPtr++;
|
||||
sectorSize = getSectorSize(header.SectorSize);
|
||||
//first read header
|
||||
for (;;)
|
||||
{
|
||||
if (headerPtr >= inputFileSize-1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
header.ModeValue = br.read_8();
|
||||
headerPtr++;
|
||||
Modulation_Speed = getModulationandSpeed(header.ModeValue, &mfm);
|
||||
header.track = br.read_8();
|
||||
headerPtr++;
|
||||
header.Head = br.read_8();
|
||||
headerPtr++;
|
||||
header.numSectors = br.read_8();
|
||||
headerPtr++;
|
||||
header.SectorSize = br.read_8();
|
||||
headerPtr++;
|
||||
sectorSize = getSectorSize(header.SectorSize);
|
||||
|
||||
//Read optional cylinder map To Do
|
||||
//Read optional cylinder map To Do
|
||||
|
||||
//Read optional sector head map To Do
|
||||
//Read optional sector head map To Do
|
||||
|
||||
//read sector numbering map
|
||||
std::vector<unsigned> sector_map(header.numSectors);
|
||||
bool blnBaseOne = false;
|
||||
sector_skew.clear();
|
||||
for (b = 0; b < header.numSectors; b++)
|
||||
{
|
||||
sector_map[b] = br.read_8();
|
||||
sector_skew.push_back(sector_map[b] + '0');
|
||||
if (b == 0) //first sector see if base is 0 or 1 Fluxengine wants 0
|
||||
{
|
||||
if (sector_map[b]==1)
|
||||
{
|
||||
blnBaseOne = true;
|
||||
}
|
||||
}
|
||||
if (blnBaseOne==true)
|
||||
{
|
||||
sector_map[b] = (sector_map[b]-1);
|
||||
}
|
||||
headerPtr++;
|
||||
}
|
||||
//read the sectors
|
||||
for (int s = 0; s < header.numSectors; s++)
|
||||
{
|
||||
Bytes sectordata;
|
||||
const auto& sector = image.put(header.track, header.Head, sector_map[s]);
|
||||
//read the status of the sector
|
||||
unsigned int Status_Sector = br.read_8();
|
||||
headerPtr++;
|
||||
//read sector numbering map
|
||||
std::vector<unsigned> sector_map(header.numSectors);
|
||||
sector_skew.clear();
|
||||
for (int b = 0; b < header.numSectors; b++)
|
||||
{
|
||||
sector_map[b] = br.read_8();
|
||||
headerPtr++;
|
||||
}
|
||||
|
||||
switch (Status_Sector)
|
||||
{
|
||||
case 0: /* Sector data unavailable - could not be read */
|
||||
//read the sectors
|
||||
for (int s = 0; s < header.numSectors; s++)
|
||||
{
|
||||
Bytes sectordata;
|
||||
const auto& sector = image->put(header.track, header.Head, sector_map[s]);
|
||||
//read the status of the sector
|
||||
unsigned int Status_Sector = br.read_8();
|
||||
headerPtr++;
|
||||
|
||||
break;
|
||||
switch (Status_Sector)
|
||||
{
|
||||
case 0: /* Sector data unavailable - could not be read */
|
||||
break;
|
||||
|
||||
case 1: /* Normal data: (Sector Size) bytes follow */
|
||||
sectordata = br.read(sectorSize);
|
||||
headerPtr += sectorSize;
|
||||
sector->data.writer().append(sectordata);
|
||||
case 1: /* Normal data: (Sector Size) bytes follow */
|
||||
sectordata = br.read(sectorSize);
|
||||
headerPtr += sectorSize;
|
||||
sector->data.writer().append(sectordata);
|
||||
break;
|
||||
|
||||
break;
|
||||
case 2: /* Compressed: All bytes in sector have same value (xx) */
|
||||
sectordata = br.read(1);
|
||||
headerPtr++;
|
||||
sector->data.writer().append(sectordata);
|
||||
|
||||
case 2: /* Compressed: All bytes in sector have same value (xx) */
|
||||
sectordata = br.read(1);
|
||||
headerPtr++;
|
||||
sector->data.writer().append(sectordata);
|
||||
for (int k = 1; k < sectorSize; k++)
|
||||
{
|
||||
//fill data till sector is full
|
||||
sector->data.writer().append(sectordata);
|
||||
}
|
||||
break;
|
||||
|
||||
for (int k = 1; k < sectorSize; k++)
|
||||
{
|
||||
//fill data till sector is full
|
||||
sector->data.writer().append(sectordata);
|
||||
}
|
||||
case 3: /* Normal data with "Deleted-Data address mark" */
|
||||
case 4: /* Compressed with "Deleted-Data address mark"*/
|
||||
case 5: /* Normal data read with data error */
|
||||
case 6: /* Compressed read with data error" */
|
||||
case 7: /* Deleted data read with data error" */
|
||||
case 8: /* Compressed, Deleted read with data error" */
|
||||
default:
|
||||
Error() << fmt::format("don't understand IMD disks with sector status {}", Status_Sector);
|
||||
}
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = header.track;
|
||||
sector->logicalSide = sector->physicalHead = header.Head;
|
||||
sector->logicalSector = (sector_map[s]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
//Write format detected in IMD image to screen to help user set the right write parameters
|
||||
|
||||
case 3: /* Normal data with "Deleted-Data address mark" */
|
||||
image->setGeometry({
|
||||
.numTracks = header.track,
|
||||
.numSides = header.Head + 1U,
|
||||
.numSectors = header.numSectors,
|
||||
.sectorSize = sectorSize
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case 4: /* Compressed with "Deleted-Data address mark"*/
|
||||
|
||||
break;
|
||||
|
||||
case 5: /* Normal data read with data error */
|
||||
|
||||
break;
|
||||
|
||||
case 6: /* Compressed read with data error" */
|
||||
|
||||
break;
|
||||
|
||||
case 7: /* Deleted data read with data error" */
|
||||
|
||||
break;
|
||||
|
||||
case 8: /* Compressed, Deleted read with data error" */
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
Error() << fmt::format("don't understand IMD disks with sector status {}", Status_Sector);
|
||||
}
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = header.track;
|
||||
sector->logicalSide = sector->physicalHead = header.Head;
|
||||
sector->logicalSector = (sector_map[s]);
|
||||
}
|
||||
|
||||
}
|
||||
//Write format detected in IMD image to screen to help user set the right write parameters
|
||||
|
||||
image.setGeometry({
|
||||
.numTracks = header.track,
|
||||
.numSides = header.Head + 1U,
|
||||
.numSectors = header.numSectors,
|
||||
.sectorSize = sectorSize
|
||||
});
|
||||
|
||||
size_t headSize = header.numSectors * sectorSize;
|
||||
size_t headSize = header.numSectors * sectorSize;
|
||||
size_t trackSize = headSize * (header.Head + 1);
|
||||
|
||||
std::cout << "reading IMD image\n"
|
||||
<< fmt::format("{} tracks, {} heads; {}; {} kbps; {} sectoren; sectorsize {}; sectormap {}; {} kB total \n",
|
||||
header.track, header.Head + 1,
|
||||
mfm ? "MFM" : "FM",
|
||||
Modulation_Speed, header.numSectors, sectorSize, sector_skew, (header.track+1) * trackSize / 1024);
|
||||
std::cout << fmt::format("IMD: {} tracks, {} heads; {}; {} kbps; {} sectors; sectorsize {};\n"
|
||||
" sectormap {}; {} kB total\n",
|
||||
header.track, header.Head + 1,
|
||||
mfm ? "MFM" : "FM",
|
||||
Modulation_Speed, header.numSectors, sectorSize, sector_skew, (header.track+1) * trackSize / 1024);
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::createIMDImageReader(
|
||||
const ImageReaderProto& config)
|
||||
const ImageReaderProto& config)
|
||||
{
|
||||
return std::unique_ptr<ImageReader>(new IMDImageReader(config));
|
||||
}
|
||||
|
||||
|
||||
// vim: ts=4 sw=4 et
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
Image readImage()
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
if (!_config.img().tracks() || !_config.img().sides())
|
||||
Error() << "IMG: bad configuration; did you remember to set the tracks, sides and trackdata fields?";
|
||||
|
||||
Image image;
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
int trackCount = 0;
|
||||
for (int track = 0; track < _config.img().tracks(); track++)
|
||||
{
|
||||
@@ -44,7 +44,7 @@ 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(physicalCylinder, side, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = track;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
@@ -57,8 +57,8 @@ public:
|
||||
trackCount++;
|
||||
}
|
||||
|
||||
image.calculateSize();
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
std::cout << fmt::format("IMG: read {} tracks, {} sides, {} kB total\n",
|
||||
geometry.numTracks, geometry.numSides,
|
||||
inputFile.tellg() / 1024);
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
Image readImage()
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
inputFile.seekg( 0, std::ios::end);
|
||||
unsigned inputFileSize = inputFile.tellg();
|
||||
unsigned headerPtr = 0;
|
||||
Image image;
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (;;)
|
||||
{
|
||||
unsigned dataPtr = headerPtr + 2901*3 + 1;
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
inputFile.read((char*) data.begin(), sectorSize);
|
||||
|
||||
unsigned head = !!(header.flags & JV3_SIDE);
|
||||
const auto& sector = image.put(header.track, head, header.sector);
|
||||
const auto& sector = image->put(header.track, head, header.sector);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = header.track;
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
headerPtr = dataPtr;
|
||||
}
|
||||
|
||||
image.calculateSize();
|
||||
image->calculateSize();
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
Image readImage()
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
numCylinders * numHeads * trackSize / 1024)
|
||||
<< std::endl;
|
||||
|
||||
Image image;
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
unsigned sectorFileOffset;
|
||||
|
||||
for (unsigned head = 0; head < numHeads; head++)
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
Bytes data(sectorSize);
|
||||
inputFile.read((char*) data.begin(), sectorSize);
|
||||
|
||||
const auto& sector = image.put(track, head, sectorId);
|
||||
const auto& sector = image->put(track, head, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalCylinder = track;
|
||||
sector->logicalSide = sector->physicalHead = head;
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
image.setGeometry({
|
||||
image->setGeometry({
|
||||
.numTracks = numCylinders,
|
||||
.numSides = numHeads,
|
||||
.numSectors = numSectors,
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
ImageReader(config)
|
||||
{}
|
||||
|
||||
Image readImage()
|
||||
std::unique_ptr<Image> readImage()
|
||||
{
|
||||
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
version / 10, version % 10, comment);
|
||||
|
||||
unsigned totalSize = 0;
|
||||
Image image;
|
||||
std::unique_ptr<Image> image(new Image);
|
||||
for (;;)
|
||||
{
|
||||
/* Read track header */
|
||||
@@ -177,7 +177,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const auto& sector = image.put(logicalTrack, logicalSide, sectorId);
|
||||
const auto& sector = image->put(logicalTrack, logicalSide, sectorId);
|
||||
sector->status = Sector::OK;
|
||||
sector->physicalCylinder = physicalCylinder;
|
||||
sector->physicalHead = physicalHead;
|
||||
@@ -186,8 +186,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
image.calculateSize();
|
||||
const Geometry& geometry = image.getGeometry();
|
||||
image->calculateSize();
|
||||
const Geometry& geometry = image->getGeometry();
|
||||
std::cout << fmt::format("TD0: found {} tracks, {} sides, {} sectors, {} bytes per sector, {} kB total\n",
|
||||
geometry.numTracks, geometry.numSides, geometry.numSectors,
|
||||
geometry.sectorSize,
|
||||
|
||||
@@ -47,6 +47,7 @@ void ImageWriter::updateConfigForFilename(ImageWriterProto* proto, const std::st
|
||||
{".ldbs", [&]() { proto->mutable_ldbs(); }},
|
||||
{".st", [&]() { proto->mutable_img(); }},
|
||||
{".nsi", [&]() { proto->mutable_nsi(); }},
|
||||
{".xdf", [&]() { proto->mutable_img(); }},
|
||||
};
|
||||
|
||||
for (const auto& it : formats)
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "fmt/format.h"
|
||||
#include "proto.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/data.pb.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
@@ -27,11 +26,9 @@ static std::shared_ptr<Fluxmap> readFluxmap(FluxSource& fluxsource, unsigned cyl
|
||||
std::cout << fmt::format("{0:>3}.{1}: ", cylinder, head) << std::flush;
|
||||
std::shared_ptr<Fluxmap> fluxmap = fluxsource.readFlux(cylinder, head);
|
||||
std::cout << fmt::format(
|
||||
"{0} ms in {1} bytes\n",
|
||||
"{0:.0} ms in {1} bytes\n",
|
||||
fluxmap->duration()/1e6,
|
||||
fluxmap->bytes());
|
||||
if (outputFluxSink)
|
||||
outputFluxSink->writeFlux(cylinder, head, *fluxmap);
|
||||
return fluxmap;
|
||||
}
|
||||
|
||||
@@ -83,10 +80,13 @@ void readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder, ImageWrit
|
||||
auto track = std::make_unique<TrackFlux>();
|
||||
std::set<std::shared_ptr<Sector>> track_sectors;
|
||||
std::set<std::shared_ptr<Record>> track_records;
|
||||
Fluxmap totalFlux;
|
||||
|
||||
for (int retry = config.decoder().retries(); retry >= 0; retry--)
|
||||
{
|
||||
auto fluxmap = readFluxmap(fluxsource, cylinder, head);
|
||||
totalFlux.appendDesync().appendBytes(fluxmap->rawBytes());
|
||||
|
||||
{
|
||||
auto trackdata = decoder.decodeToSectors(fluxmap, cylinder, head);
|
||||
|
||||
@@ -105,6 +105,7 @@ void readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder, ImageWrit
|
||||
track_records.insert(trackdata->records.begin(), trackdata->records.end());
|
||||
track->trackDatas.push_back(std::move(trackdata));
|
||||
}
|
||||
|
||||
auto collected_sectors = collect_sectors(track_sectors);
|
||||
std::cout << fmt::format("{} distinct sectors; ", collected_sectors.size());
|
||||
|
||||
@@ -147,6 +148,9 @@ void readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder, ImageWrit
|
||||
std::cout << retry << " retries remaining" << std::endl;
|
||||
}
|
||||
|
||||
if (outputFluxSink)
|
||||
outputFluxSink->writeFlux(cylinder, head, totalFlux);
|
||||
|
||||
if (config.decoder().dump_records())
|
||||
{
|
||||
std::cout << "\nRaw (undecoded) records follow:\n\n";
|
||||
|
||||
@@ -108,6 +108,11 @@
|
||||
typedef int FileHandle;
|
||||
static FileHandle open_serial_port(const std::string& name)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
if (name.find("/dev/tty.") != std::string::npos)
|
||||
std::cerr << "Warning: you probably want to be using a /dev/cu.* device\n";
|
||||
#endif
|
||||
|
||||
int fd = open(name.c_str(), O_RDWR);
|
||||
if (fd == -1)
|
||||
Error() << fmt::format("cannot open GreaseWeazle serial port '{}': {}",
|
||||
@@ -515,7 +520,7 @@ public:
|
||||
{
|
||||
do_command({ CMD_SELECT, 3, (uint8_t)drive });
|
||||
do_command({ CMD_MOTOR, 4, (uint8_t)drive, 1 });
|
||||
do_command({ CMD_SET_PIN, 4, 2, (uint8_t)(high_density ? 0 : 1) });
|
||||
do_command({ CMD_SET_PIN, 4, 2, (uint8_t)(high_density ? 1 : 0) });
|
||||
}
|
||||
|
||||
void measureVoltages(struct voltages_frame* voltages)
|
||||
|
||||
@@ -7,11 +7,15 @@ bool beginsWith(const std::string& value, const std::string& ending)
|
||||
return std::equal(ending.begin(), ending.end(), value.begin());
|
||||
}
|
||||
|
||||
// Case-insensitive for endings within ASCII.
|
||||
bool endsWith(const std::string& value, const std::string& ending)
|
||||
{
|
||||
if (ending.size() > value.size())
|
||||
return false;
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
std::string lowercase(ending.size(), 0);
|
||||
std::transform(value.rbegin(), value.rbegin() + ending.size(), lowercase.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin()) ||
|
||||
std::equal(ending.rbegin(), ending.rend(), lowercase.begin());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ void writeTracksAndVerify(
|
||||
AbstractEncoder& encoder,
|
||||
FluxSource& fluxSource,
|
||||
AbstractDecoder& decoder,
|
||||
Image& image)
|
||||
const Image& image)
|
||||
{
|
||||
std::cout << "Writing to: " << fluxSink << std::endl;
|
||||
|
||||
@@ -129,10 +129,9 @@ void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
}
|
||||
}
|
||||
|
||||
void writeDiskCommand(ImageReader& imageReader, AbstractEncoder& encoder, FluxSink& fluxSink,
|
||||
void writeDiskCommand(const Image& image, AbstractEncoder& encoder, FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder, FluxSource* fluxSource)
|
||||
{
|
||||
Image image = imageReader.readImage();
|
||||
if (fluxSource && decoder)
|
||||
writeTracksAndVerify(fluxSink, encoder, *fluxSource, *decoder, image);
|
||||
else
|
||||
|
||||
43
lib/writer.h
43
lib/writer.h
@@ -1,21 +1,22 @@
|
||||
#ifndef WRITER_H
|
||||
#define WRITER_H
|
||||
|
||||
class Fluxmap;
|
||||
class AbstractDecoder;
|
||||
class AbstractEncoder;
|
||||
class ImageReader;
|
||||
class FluxSource;
|
||||
class FluxSink;
|
||||
|
||||
extern void writeTracks(FluxSink& fluxSink, const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer);
|
||||
|
||||
extern void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
unsigned& cursor, unsigned terminateAt,
|
||||
const std::vector<bool>& pattern);
|
||||
|
||||
extern void writeDiskCommand(ImageReader& imageReader, AbstractEncoder& encoder, FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder = nullptr, FluxSource* fluxSource = nullptr);
|
||||
extern void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink);
|
||||
|
||||
#endif
|
||||
#ifndef WRITER_H
|
||||
#define WRITER_H
|
||||
|
||||
class Fluxmap;
|
||||
class AbstractDecoder;
|
||||
class AbstractEncoder;
|
||||
class ImageReader;
|
||||
class FluxSource;
|
||||
class FluxSink;
|
||||
class Image;
|
||||
|
||||
extern void writeTracks(FluxSink& fluxSink, const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer);
|
||||
|
||||
extern void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
unsigned& cursor, unsigned terminateAt,
|
||||
const std::vector<bool>& pattern);
|
||||
|
||||
extern void writeDiskCommand(const Image& image, AbstractEncoder& encoder, FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder = nullptr, FluxSource* fluxSource = nullptr);
|
||||
extern void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink);
|
||||
|
||||
#endif
|
||||
|
||||
42
mkninja.sh
42
mkninja.sh
@@ -4,7 +4,7 @@ set -e
|
||||
cat <<EOF
|
||||
rule cxx
|
||||
command = $CXX $CFLAGS \$flags -I. -c -o \$out \$in -MMD -MF \$out.d
|
||||
description = CXX \$in
|
||||
description = CXX \$out
|
||||
depfile = \$out.d
|
||||
deps = gcc
|
||||
|
||||
@@ -36,8 +36,8 @@ rule test
|
||||
description = TEST \$in
|
||||
|
||||
rule encodedecode
|
||||
command = sh scripts/encodedecodetest.sh \$format \$configs > \$out
|
||||
description = ENCODEDECODE \$format
|
||||
command = sh scripts/encodedecodetest.sh \$format \$fluxx \$configs > \$out
|
||||
description = ENCODEDECODE \$fluxx \$format
|
||||
|
||||
rule strip
|
||||
command = cp -f \$in \$out && $STRIP \$out
|
||||
@@ -244,14 +244,12 @@ runtest() {
|
||||
buildlibrary lib$prog.a \
|
||||
-Idep/snowhouse/include \
|
||||
-d $OBJDIR/proto/libconfig.def \
|
||||
-d $OBJDIR/proto/libdata.def \
|
||||
"$@"
|
||||
|
||||
buildprogram $OBJDIR/$prog \
|
||||
lib$prog.a \
|
||||
libbackend.a \
|
||||
libconfig.a \
|
||||
libdata.a \
|
||||
libtestproto.a \
|
||||
libagg.a \
|
||||
libfmt.a
|
||||
@@ -264,9 +262,18 @@ encodedecodetest() {
|
||||
format=$1
|
||||
shift
|
||||
|
||||
echo "build $OBJDIR/$format.encodedecode.stamp : encodedecode | fluxengine$EXTENSION scripts/encodedecodetest.sh $*"
|
||||
echo "build $OBJDIR/$format.encodedecode.flux.stamp : encodedecode | fluxengine$EXTENSION scripts/encodedecodetest.sh $*"
|
||||
echo " format=$format"
|
||||
echo " configs=$*"
|
||||
echo " fluxx=flux"
|
||||
echo "build $OBJDIR/$format.encodedecode.scp.stamp : encodedecode | fluxengine$EXTENSION scripts/encodedecodetest.sh $*"
|
||||
echo " format=$format"
|
||||
echo " configs=$*"
|
||||
echo " fluxx=scp"
|
||||
echo "build $OBJDIR/$format.encodedecode.fl2.stamp : encodedecode | fluxengine$EXTENSION scripts/encodedecodetest.sh $*"
|
||||
echo " format=$format"
|
||||
echo " configs=$*"
|
||||
echo " fluxx=fl2"
|
||||
}
|
||||
|
||||
buildlibrary libagg.a \
|
||||
@@ -304,13 +311,13 @@ buildproto libconfig.a \
|
||||
lib/imagewriter/imagewriter.proto \
|
||||
lib/usb/usb.proto \
|
||||
|
||||
buildproto libdata.a \
|
||||
lib/data.proto
|
||||
buildproto libfl2.a \
|
||||
lib/fl2.proto
|
||||
|
||||
buildlibrary libbackend.a \
|
||||
-I$OBJDIR/proto \
|
||||
-d $OBJDIR/proto/libconfig.def \
|
||||
-d $OBJDIR/proto/libdata.def \
|
||||
-d $OBJDIR/proto/libfl2.def \
|
||||
arch/aeslanier/decoder.cc \
|
||||
arch/amiga/amiga.cc \
|
||||
arch/amiga/decoder.cc \
|
||||
@@ -334,18 +341,21 @@ buildlibrary libbackend.a \
|
||||
arch/tids990/decoder.cc \
|
||||
arch/tids990/encoder.cc \
|
||||
arch/victor9k/decoder.cc \
|
||||
arch/victor9k/encoder.cc \
|
||||
arch/zilogmcz/decoder.cc \
|
||||
lib/bitmap.cc \
|
||||
lib/bytes.cc \
|
||||
lib/crc.cc \
|
||||
lib/csvreader.cc \
|
||||
lib/decoders/decoders.cc \
|
||||
lib/decoders/fluxdecoder.cc \
|
||||
lib/decoders/fluxmapreader.cc \
|
||||
lib/decoders/fmmfm.cc \
|
||||
lib/encoders/encoders.cc \
|
||||
lib/flags.cc \
|
||||
lib/fluxmap.cc \
|
||||
lib/fluxsink/aufluxsink.cc \
|
||||
lib/fluxsink/fl2fluxsink.cc \
|
||||
lib/fluxsink/fluxsink.cc \
|
||||
lib/fluxsink/hardwarefluxsink.cc \
|
||||
lib/fluxsink/scpfluxsink.cc \
|
||||
@@ -353,6 +363,7 @@ buildlibrary libbackend.a \
|
||||
lib/fluxsink/vcdfluxsink.cc \
|
||||
lib/fluxsource/cwffluxsource.cc \
|
||||
lib/fluxsource/erasefluxsource.cc \
|
||||
lib/fluxsource/fl2fluxsource.cc \
|
||||
lib/fluxsource/fluxsource.cc \
|
||||
lib/fluxsource/hardwarefluxsource.cc \
|
||||
lib/fluxsource/kryoflux.cc \
|
||||
@@ -371,6 +382,9 @@ buildlibrary libbackend.a \
|
||||
lib/imagereader/jv3imagereader.cc \
|
||||
lib/imagereader/nsiimagereader.cc \
|
||||
lib/imagereader/td0imagereader.cc \
|
||||
lib/imagereader/dimimagereader.cc \
|
||||
lib/imagereader/fdiimagereader.cc \
|
||||
lib/imagereader/d88imagereader.cc \
|
||||
lib/imagewriter/d64imagewriter.cc \
|
||||
lib/imagewriter/diskcopyimagewriter.cc \
|
||||
lib/imagewriter/imagewriter.cc \
|
||||
@@ -416,6 +430,7 @@ FORMATS="\
|
||||
hplif770 \
|
||||
ibm \
|
||||
ibm1200_525 \
|
||||
ibm1232 \
|
||||
ibm1440 \
|
||||
ibm180_525 \
|
||||
ibm360_525 \
|
||||
@@ -429,7 +444,7 @@ FORMATS="\
|
||||
northstar350 \
|
||||
northstar87 \
|
||||
tids990 \
|
||||
victor9k \
|
||||
victor9k_ss \
|
||||
zilogmcz \
|
||||
"
|
||||
|
||||
@@ -443,7 +458,6 @@ buildmktable formats $OBJDIR/formats.cc $FORMATS
|
||||
buildlibrary libfrontend.a \
|
||||
-I$OBJDIR/proto \
|
||||
-d $OBJDIR/proto/libconfig.def \
|
||||
-d $OBJDIR/proto/libdata.def \
|
||||
$(for a in $FORMATS; do echo $OBJDIR/proto/src/formats/$a.cc; done) \
|
||||
$OBJDIR/formats.cc \
|
||||
src/fe-analysedriveresponse.cc \
|
||||
@@ -464,7 +478,7 @@ buildprogram fluxengine \
|
||||
libfrontend.a \
|
||||
libbackend.a \
|
||||
libconfig.a \
|
||||
libdata.a \
|
||||
libfl2.a \
|
||||
libfmt.a \
|
||||
libagg.a \
|
||||
|
||||
@@ -497,6 +511,7 @@ runtest bytes-test tests/bytes.cc
|
||||
runtest compression-test tests/compression.cc
|
||||
runtest csvreader-test tests/csvreader.cc
|
||||
runtest flags-test tests/flags.cc
|
||||
runtest fluxmapreader-test tests/fluxmapreader.cc
|
||||
runtest fluxpattern-test tests/fluxpattern.cc
|
||||
runtest fmmfm-test tests/fmmfm.cc
|
||||
runtest greaseweazle-test tests/greaseweazle.cc
|
||||
@@ -504,7 +519,6 @@ runtest kryoflux-test tests/kryoflux.cc
|
||||
runtest ldbs-test tests/ldbs.cc
|
||||
runtest proto-test -I$OBJDIR/proto \
|
||||
-d $OBJDIR/proto/libconfig.def \
|
||||
-d $OBJDIR/proto/libdata.def \
|
||||
-d $OBJDIR/proto/libtestproto.def \
|
||||
tests/proto.cc \
|
||||
$OBJDIR/proto/tests/testproto.cc
|
||||
@@ -521,6 +535,7 @@ encodedecodetest atarist820
|
||||
encodedecodetest brother120
|
||||
encodedecodetest brother240
|
||||
encodedecodetest ibm1200_525
|
||||
encodedecodetest ibm1232
|
||||
encodedecodetest ibm1440
|
||||
encodedecodetest ibm180_525
|
||||
encodedecodetest ibm360_525
|
||||
@@ -531,6 +546,7 @@ encodedecodetest commodore1581
|
||||
encodedecodetest commodore1541 scripts/commodore1541_test.textpb
|
||||
encodedecodetest mac400 scripts/mac400_test.textpb
|
||||
encodedecodetest mac800 scripts/mac800_test.textpb
|
||||
encodedecodetest victor9k_ss
|
||||
|
||||
# vim: sw=4 ts=4 et
|
||||
|
||||
|
||||
@@ -88,7 +88,9 @@ enum
|
||||
enum
|
||||
{
|
||||
F_BIT_PULSE = 0x80,
|
||||
F_BIT_INDEX = 0x40
|
||||
F_BIT_INDEX = 0x40,
|
||||
F_DESYNC = 0x00,
|
||||
F_EOF = 0x100 /* synthetic, only produced by library */
|
||||
};
|
||||
|
||||
struct frame_header
|
||||
|
||||
@@ -8,10 +8,11 @@ fi
|
||||
|
||||
tmp=/tmp/$$
|
||||
srcfile=$tmp.src.img
|
||||
fluxfile=$tmp.flux
|
||||
fluxfile=$tmp.$2
|
||||
destfile=$tmp.dest.img
|
||||
format=$1
|
||||
shift
|
||||
shift
|
||||
|
||||
trap "rm -f $srcfile $fluxfile $destfile" EXIT
|
||||
|
||||
|
||||
@@ -257,10 +257,11 @@ int mainAnalyseDriveResponse(int argc, const char* argv[])
|
||||
|
||||
FluxmapReader fmr(inFluxmap);
|
||||
fmr.seek((double)period*0.1); /* skip first 10% and last 10% as contains junk */
|
||||
fmr.findEvent(F_BIT_PULSE);
|
||||
fmr.skipToEvent(F_BIT_PULSE);
|
||||
while (fmr.tell().ns() < ((double)period*0.9))
|
||||
{
|
||||
unsigned ticks = fmr.findEvent(F_BIT_PULSE);
|
||||
unsigned ticks;
|
||||
fmr.findEvent(F_BIT_PULSE, ticks);
|
||||
if (ticks < numColumns)
|
||||
frequencies[row][ticks]++;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "reader.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "decoders/fluxdecoder.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "protocol.h"
|
||||
@@ -91,7 +92,8 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
|
||||
|
||||
while (!fr.eof())
|
||||
{
|
||||
unsigned interval = fr.findEvent(F_BIT_PULSE);
|
||||
unsigned interval;
|
||||
fr.findEvent(F_BIT_PULSE, interval);
|
||||
if (interval > 0xff)
|
||||
continue;
|
||||
buckets[interval]++;
|
||||
@@ -233,7 +235,9 @@ int mainInspect(int argc, const char* argv[])
|
||||
nanoseconds_t lasttransition = 0;
|
||||
while (!fmr.eof())
|
||||
{
|
||||
ticks += fmr.findEvent(F_BIT_PULSE);
|
||||
unsigned thisTicks;
|
||||
fmr.findEvent(F_BIT_PULSE, thisTicks);
|
||||
ticks += thisTicks;
|
||||
|
||||
nanoseconds_t transition = ticks*NS_PER_TICK;
|
||||
nanoseconds_t next;
|
||||
@@ -277,6 +281,7 @@ int mainInspect(int argc, const char* argv[])
|
||||
std::cout << fmt::format("\n\nAligned bitstream from {:.3f}ms follows:\n",
|
||||
fmr.tell().ns() / 1000000.0);
|
||||
|
||||
FluxDecoder decoder(&fmr, clockPeriod, config.decoder());
|
||||
while (!fmr.eof())
|
||||
{
|
||||
std::cout << fmt::format("{:06x} {: 10.3f} : ",
|
||||
@@ -285,7 +290,7 @@ int mainInspect(int argc, const char* argv[])
|
||||
{
|
||||
if (fmr.eof())
|
||||
break;
|
||||
bool b = fmr.readRawBit(clockPeriod);
|
||||
bool b = decoder.readBit();
|
||||
std::cout << (b ? 'X' : '-');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "usb/usb.h"
|
||||
#include "fluxsource/fluxsource.cc"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "proto.h"
|
||||
#include "protocol.h"
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ int mainWrite(int argc, const char* argv[])
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, formats);
|
||||
|
||||
std::unique_ptr<ImageReader> reader(ImageReader::create(config.image_reader()));
|
||||
std::unique_ptr<Image> image = reader->readImage();
|
||||
|
||||
std::unique_ptr<AbstractEncoder> encoder(AbstractEncoder::create(config.encoder()));
|
||||
std::unique_ptr<FluxSink> fluxSink(FluxSink::create(config.flux_sink()));
|
||||
|
||||
@@ -73,7 +75,7 @@ int mainWrite(int argc, const char* argv[])
|
||||
if (config.has_flux_source() && config.flux_source().has_drive())
|
||||
fluxSource = FluxSource::create(config.flux_source());
|
||||
|
||||
writeDiskCommand(*reader, *encoder, *fluxSink, decoder.get(), fluxSource.get());
|
||||
writeDiskCommand(*image, *encoder, *fluxSink, decoder.get(), fluxSource.get());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
comment: 'PC 3.5"/5.25" autodetect double sided format (ro)'
|
||||
comment: 'PC 3.5"/5.25" autodetect double sided format'
|
||||
|
||||
image_writer {
|
||||
filename: "ibm.img"
|
||||
|
||||
68
src/formats/ibm1232.textpb
Normal file
68
src/formats/ibm1232.textpb
Normal file
@@ -0,0 +1,68 @@
|
||||
comment: 'Japanese PC 1232kB 5.25"/3.5" 77-track 8-sector DSHD'
|
||||
|
||||
flux_sink {
|
||||
drive {
|
||||
high_density: true
|
||||
}
|
||||
}
|
||||
|
||||
flux_source {
|
||||
drive {
|
||||
high_density: true
|
||||
}
|
||||
}
|
||||
|
||||
image_reader {
|
||||
filename: "ibm1232.img"
|
||||
img {
|
||||
tracks: 77
|
||||
sides: 2
|
||||
trackdata {
|
||||
sector_size: 1024
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "ibm1232.img"
|
||||
img {}
|
||||
}
|
||||
|
||||
encoder {
|
||||
ibm {
|
||||
trackdata {
|
||||
sector_size: 1024
|
||||
track_length_ms: 167
|
||||
clock_rate_khz: 500
|
||||
sectors {
|
||||
sector: 1
|
||||
sector: 2
|
||||
sector: 3
|
||||
sector: 4
|
||||
sector: 5
|
||||
sector: 6
|
||||
sector: 7
|
||||
sector: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoder {
|
||||
ibm {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
start: 0
|
||||
end: 76
|
||||
}
|
||||
|
||||
heads {
|
||||
start: 0
|
||||
end: 1
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
comment: 'Victor 9000 / Sirius One 1224kB SSHD GCR variable sector (ro)'
|
||||
|
||||
image_writer {
|
||||
filename: "victor9k.img"
|
||||
img {}
|
||||
}
|
||||
|
||||
decoder {
|
||||
victor9k {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
heads {
|
||||
start: 0
|
||||
end: 0
|
||||
}
|
||||
|
||||
259
src/formats/victor9k_ss.textpb
Normal file
259
src/formats/victor9k_ss.textpb
Normal file
@@ -0,0 +1,259 @@
|
||||
comment: 'Victor 9000 / Sirius One 612kB SSHD GCR variable sector)'
|
||||
|
||||
image_reader {
|
||||
filename: "victor9k.img"
|
||||
img {
|
||||
tracks: 80
|
||||
sides: 1
|
||||
trackdata {
|
||||
sector_size: 512
|
||||
}
|
||||
trackdata {
|
||||
track: 0
|
||||
up_to_track: 3
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 19
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 4
|
||||
up_to_track: 15
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 18
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 16
|
||||
up_to_track: 26
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 17
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 27
|
||||
up_to_track: 37
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 16
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 38
|
||||
up_to_track: 48
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 15
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 49
|
||||
up_to_track: 59
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 14
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 60
|
||||
up_to_track: 70
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 13
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 71
|
||||
up_to_track: 79
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
image_writer {
|
||||
filename: "victor9k.img"
|
||||
img {
|
||||
tracks: 80
|
||||
sides: 1
|
||||
trackdata {
|
||||
sector_size: 512
|
||||
}
|
||||
trackdata {
|
||||
track: 0
|
||||
up_to_track: 3
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 19
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 4
|
||||
up_to_track: 15
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 18
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 16
|
||||
up_to_track: 26
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 17
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 27
|
||||
up_to_track: 37
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 16
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 38
|
||||
up_to_track: 48
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 15
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 49
|
||||
up_to_track: 59
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 14
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 60
|
||||
up_to_track: 70
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 13
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
track: 71
|
||||
up_to_track: 79
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoder {
|
||||
victor9k {
|
||||
trackdata {
|
||||
original_data_rate_khz: 500
|
||||
post_index_gap_us: 1000.0
|
||||
pre_header_sync_bits: 60
|
||||
post_header_gap_bits: 90
|
||||
pre_data_sync_bits: 50
|
||||
post_data_gap_bits: 300
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 0
|
||||
max_cylinder: 3
|
||||
original_period_ms: 237.9
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 19
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 4
|
||||
max_cylinder: 15
|
||||
original_period_ms: 224.5
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 18
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 16
|
||||
max_cylinder: 26
|
||||
original_period_ms: 212.2
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 17
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 27
|
||||
max_cylinder: 37
|
||||
original_period_ms: 199.9
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 16
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 38
|
||||
max_cylinder: 48
|
||||
original_period_ms: 187.6
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 15
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 49
|
||||
max_cylinder: 59
|
||||
original_period_ms: 175.3
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 14
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 60
|
||||
max_cylinder: 70
|
||||
original_period_ms: 163.0
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 13
|
||||
}
|
||||
}
|
||||
trackdata {
|
||||
head: 0
|
||||
min_cylinder: 71
|
||||
max_cylinder: 79
|
||||
original_period_ms: 149.6
|
||||
sector_range {
|
||||
start_sector: 1
|
||||
sector_count: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decoder {
|
||||
victor9k {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
start: 0
|
||||
end: 79
|
||||
}
|
||||
|
||||
heads {
|
||||
start: 0
|
||||
end: 0
|
||||
}
|
||||
|
||||
89
tests/fluxmapreader.cc
Normal file
89
tests/fluxmapreader.cc
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "protocol.h"
|
||||
#include "fmt/format.h"
|
||||
#include "tests.h"
|
||||
#include <sstream>
|
||||
|
||||
static Fluxmap fluxmap(Bytes {
|
||||
F_DESYNC,
|
||||
F_BIT_PULSE | 0x30,
|
||||
F_BIT_INDEX | 0x30,
|
||||
F_BIT_PULSE | F_BIT_INDEX | 0x30,
|
||||
0x30, F_BIT_PULSE | 0x30,
|
||||
F_DESYNC,
|
||||
F_BIT_PULSE | 0x30,
|
||||
0x30,
|
||||
F_DESYNC,
|
||||
0x30,
|
||||
F_BIT_PULSE | 0x30
|
||||
});
|
||||
|
||||
#define ASSERT_NEXT_EVENT(wantedEvent, wantedTicks) \
|
||||
do { \
|
||||
unsigned gotTicks; \
|
||||
int gotEvent; \
|
||||
fmr.getNextEvent(gotEvent, gotTicks); \
|
||||
assert((gotEvent == (wantedEvent)) && (gotTicks == (wantedTicks))); \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_READ_SPECIFIC_EVENT(wantedEvent, wantedTicks) \
|
||||
do { \
|
||||
unsigned gotTicks; \
|
||||
fmr.findEvent(wantedEvent, gotTicks); \
|
||||
assertThat(gotTicks).isEqualTo(wantedTicks); \
|
||||
} while(0)
|
||||
|
||||
void test_read_all_events()
|
||||
{
|
||||
FluxmapReader fmr(fluxmap);
|
||||
ASSERT_NEXT_EVENT(F_DESYNC, 0);
|
||||
ASSERT_NEXT_EVENT(F_BIT_PULSE, 0x30);
|
||||
ASSERT_NEXT_EVENT(F_BIT_INDEX, 0x30);
|
||||
ASSERT_NEXT_EVENT(F_BIT_PULSE | F_BIT_INDEX, 0x30);
|
||||
ASSERT_NEXT_EVENT(F_BIT_PULSE, 0x60);
|
||||
ASSERT_NEXT_EVENT(F_DESYNC, 0);
|
||||
ASSERT_NEXT_EVENT(F_BIT_PULSE, 0x30);
|
||||
ASSERT_NEXT_EVENT(F_DESYNC, 0x30);
|
||||
ASSERT_NEXT_EVENT(F_BIT_PULSE, 0x60);
|
||||
ASSERT_NEXT_EVENT(F_EOF, 0);
|
||||
ASSERT_NEXT_EVENT(F_EOF, 0);
|
||||
}
|
||||
|
||||
void test_read_pulses()
|
||||
{
|
||||
FluxmapReader fmr(fluxmap);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_PULSE, 0x30);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_PULSE, 0x60);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_PULSE, 0x60);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_PULSE, 0x30);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_PULSE, 0x90);
|
||||
}
|
||||
|
||||
void test_read_indices()
|
||||
{
|
||||
FluxmapReader fmr(fluxmap);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_INDEX, 0x60);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_INDEX, 0x30);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_BIT_INDEX, 6*0x30);
|
||||
}
|
||||
|
||||
void test_read_desyncs()
|
||||
{
|
||||
FluxmapReader fmr(fluxmap);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_DESYNC, 0);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_DESYNC, 0xf0);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_DESYNC, 0x60);
|
||||
ASSERT_READ_SPECIFIC_EVENT(F_DESYNC, 0x60);
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
test_read_all_events();
|
||||
test_read_pulses();
|
||||
test_read_indices();
|
||||
test_read_desyncs();
|
||||
return 0;
|
||||
}
|
||||
|
||||
38
tests/tests.h
Normal file
38
tests/tests.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef TESTS_H
|
||||
#define TESTS_H
|
||||
|
||||
class AssertionError
|
||||
{};
|
||||
|
||||
template <class T>
|
||||
class Subject
|
||||
{
|
||||
public:
|
||||
Subject(const std::string& filename, int lineno, T value):
|
||||
_filename(filename),
|
||||
_lineno(lineno),
|
||||
_value(value) {}
|
||||
|
||||
public:
|
||||
void isEqualTo(T wanted)
|
||||
{
|
||||
if (_value != wanted)
|
||||
fail(fmt::format("wanted {}, got {}", wanted, _value));
|
||||
}
|
||||
|
||||
private:
|
||||
void fail(const std::string& message)
|
||||
{
|
||||
Error() << "assertion failed: " << _filename << ":" << _lineno << ": " << message;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string _filename;
|
||||
int _lineno;
|
||||
T _value;
|
||||
};
|
||||
|
||||
#define assertThat(value) Subject(__FILE__, __LINE__, value)
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user