Merge from master.

This commit is contained in:
David Given
2022-03-16 16:01:24 +01:00
71 changed files with 1055 additions and 493 deletions

View File

@@ -123,15 +123,16 @@ public:
/* Read and decode data. */
auto readApple8 = [&]() {
auto result = readRaw8();
while(result & 0x80 == 0) {
auto result = 0;
while((result & 0x80) == 0) {
auto b = readRawBits(1);
if(b.empty()) break;
result = (result << 1) | b[0];
}
return result;
};
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH+2;
uint8_t bytes[recordLength];
for(auto &byte : bytes) {
byte = readApple8();

View File

@@ -57,6 +57,7 @@ public:
int logicalTrack = physicalTrack / 2;
double clockRateUs = 4.;
// apple2 drives spin at 300rpm. 300/minute in us = 200000.
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
@@ -110,26 +111,28 @@ private:
write_bits(encode_data_gcr(value), 8);
};
auto write_ff40 = [&]() {
write_bits(0xff0, 12);
};
auto write_ff36 = [&]() {
write_bits(0xff << 2, 10);
};
auto write_ff32 = [&]() {
write_bits(0xff, 8);
// The special "FF40" sequence is used to synchronize the receiving
// shift register. It's written as "1111 1111 00"; FF indicates the
// 8 consecutive 1-bits, while "40" indicates the total number of
// microseconds.
auto write_ff40 = [&](int n=1) {
for(;n--;) {
write_bits(0xff << 2, 10);
}
};
// There is data to encode to disk.
if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector.data.size());
// Write address syncing leader : A sequence of "FF40"s followed by an "FF32", 5 to 40 of them
// "FF40" seems to indicate that the actual data written is "1111 1111 0000" i.e., 8 1s and a total of 40 microseconds
for(int i=0; i<4; i++) { write_ff40(); }
write_ff32();
// Write address syncing leader : A sequence of "FF40"s; 5 of them
// are said to suffice to synchronize the decoder.
// "FF40" indicates that the actual data written is "1111
// 1111 00" i.e., 8 1s and a total of 40 microseconds
//
// In standard formatting, the first logical sector apparently gets
// extra padding.
write_ff40(sector.logicalSector == 0 ? 32 : 8);
// Write address field: APPLE2_SECTOR_RECORD + sector identifier + DE AA EB
write_bits(APPLE2_SECTOR_RECORD, 24);
@@ -139,13 +142,8 @@ private:
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
write_bits(0xDEAAEB, 24);
// Write the "zip": a gap of 50 (we actually do 52, hopefully it's OK).
// In real HW this is actually turning OFF the write head for 50 cycles
write_bits(0, 13);
// Write data syncing leader: FF40 x4 + FF36 + APPLE2_DATA_RECORD + sector data + sum + DE AA EB (+ mystery bits cut off of the scan?)
for(int i=0; i<4; i++) write_ff40();
write_ff36();
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector data + sum + DE AA EB (+ mystery bits cut off of the scan?)
write_ff40(8);
write_bits(APPLE2_DATA_RECORD, 24);
// Convert the sector data to GCR, append the checksum, and write it out

View File

@@ -78,13 +78,22 @@ static int encode_data_gcr(uint8_t data)
return -1;
}
static void write_byte(std::vector<bool>& bits, unsigned& cursor, uint8_t b)
{
write_bits(bits, cursor, encode_data_gcr(b>>4), 5);
write_bits(bits, cursor, encode_data_gcr(b), 5);
}
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);
}
write_byte(bits, cursor, b);
}
static void write_gap(std::vector<bool>& bits, unsigned& cursor, int length)
{
for (int i = 0; i < length/10; i++)
write_byte(bits, cursor, '0');
}
static void write_sector(std::vector<bool>& bits, unsigned& cursor,
@@ -102,8 +111,8 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor,
(uint8_t)(encodedTrack + encodedSector),
});
write_gap(bits, cursor, trackdata.post_header_gap_bits());
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);
@@ -112,8 +121,7 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor,
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());
write_gap(bits, cursor, trackdata.post_data_gap_bits());
}
class Victor9kEncoder : public AbstractEncoder

View File

@@ -8,11 +8,13 @@ class DecoderProto;
/* ... 1101 0101 0111
* ^^ ^^^^ ^^^^ ten bit IO byte */
#define VICTOR9K_SECTOR_RECORD 0xfffffd57
#define VICTOR9K_SECTOR_RECORD 0xfffffd57
#define VICTOR9K_HEADER_ID 0x7
/* ... 1101 0100 1001
* ^^ ^^^^ ^^^^ ten bit IO byte */
#define VICTOR9K_DATA_RECORD 0xfffffd49
#define VICTOR9K_DATA_ID 0x8
#define VICTOR9K_SECTOR_LENGTH 512

View File

@@ -187,12 +187,12 @@ install some support packages.
- For Linux (this is Ubuntu, but this should apply to Debian too):
`ninja-build`, `libusb-1.0-0-dev`, `libsqlite3-dev`, `zlib1g-dev`,
`libudev-dev`.
`libudev-dev`, `protobuf-compiler`, `libwxgtk3.0-gtk3-dev`.
- For OSX with Homebrew: `ninja`, `libusb`, `pkg-config`, `sqlite`,
`protobuf`.
`protobuf`, `truncate`, `wxwidgets`.
- For Windows with MSYS2: `make`, `ninja`, `mingw-w64-i686-libusb`,
`mingw-w64-i686-protobuf`, `mingw-w64-i686-sqlite3`, `mingw-w64-i686-zlib`,
`mingw-w64-i686-gcc`.
`mingw-w64-i686-gcc`, `vim`, `diffutils`, `mingw-w64-i686-wxWidgets`.
These lists are not necessarily exhaustive --- please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new) if I've missed

View File

@@ -111,13 +111,13 @@ with an obvious directory and allocation table. I have reversed engineered
a very simple tool for extracting files from it. To show the directory, do:
```
.obj/brother120tool image.img
brother120tool image.img
```
To extract a file, do:
```
.obj/brother120tool image.img filename
brother120tool image.img filename
```
Wildcards are supported, so use `'*'` for the filename (remember to quote it)
@@ -131,6 +131,18 @@ format](https://mathesoft.eu/brother-wp-1-dokumente/) and has produced a
RTF](https://mathesoft.eu/sdm_downloads/wp2rtf/). This will only work on WP-1
files.
To create a disk image (note: this creates a _new_ disk image, overwriting the
previous image), do:
```
brother120tool --create image.img filename1 filename2...
```
Any files whose names begin with an asterisk (`*`) will be marked as hidden. If
the file is named `*boot`, then a boot sector will be created which will load
and run the file at 0x7000 if the machine is started with CODE+Q pressed. So
far this has only been confirmed to work on a WP-1.
Any questions? please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new).

View File

@@ -317,7 +317,7 @@ disks, and have different magnetic properties. 3.5" drives can usually
autodetect what kind of medium is inserted into the drive based on the hole in
the disk casing, but 5.25" drives can't. As a result, you need to explicitly
tell FluxEngine on the command line whether you're using a high density disk or
not with the `--flux_source/sink.drive.high_density` configuration setting.
not with the `--drive.high_density` configuration setting.
**If you don't do this, your disks may not read correctly and will _certainly_
fail to write correctly.**
@@ -334,14 +334,14 @@ here.](http://www.retrotechnology.com/herbs_stuff/guzis.html)
These flags apply to many operations and are useful for modifying the overall
behaviour.
- `--flux_source.drive.revolutions=X`
- `--drive.revolutions=X`
When reading, spin the disk X times. X
can be a floating point number. The default is usually 1.2. Some formats
default to 1. Increasing the number will sample more data, and can be
useful on dubious disks to try and get a better read.
- `--flux_source.drive.sync_with_index=true|false`
- `--drive.sync_with_index=true|false`
Wait for an index pulse
before starting to read the disk. (Ignored for write operations.) By
@@ -349,7 +349,7 @@ behaviour.
disk problems it's helpful to have all your data start at the same place
each time.
- `--flux_source.drive.index_source=X`, `--flux_sink.drive.index_source=X`
- `--drive.index_source=X`
Set the source of index pulses when reading or writing respectively. This
is for use with drives which don't produce index pulse data. `X` can be

View File

@@ -160,6 +160,24 @@ Bytes Bytes::slice(unsigned start) const
return slice(start, len);
}
std::vector<Bytes> Bytes::split(uint8_t separator) const
{
std::vector<Bytes> vector;
int lastEnd = 0;
for (int i=0; i<size(); i++)
{
if ((*this)[i] == separator)
{
vector.push_back(this->slice(lastEnd, i-lastEnd));
lastEnd = i + 1;
}
}
vector.push_back(this->slice(lastEnd));
return vector;
}
std::vector<bool> Bytes::toBits() const
{
std::vector<bool> bits;

View File

@@ -51,6 +51,8 @@ public:
Bytes& clear()
{ resize(0); return *this; }
std::vector<Bytes> split(uint8_t separator) const;
Bytes slice(unsigned start, unsigned len) const;
Bytes slice(unsigned start) const;
Bytes swab() const;

View File

@@ -7,10 +7,11 @@ import "lib/imagewriter/imagewriter.proto";
import "lib/fluxsource/fluxsource.proto";
import "lib/fluxsink/fluxsink.proto";
import "lib/usb/usb.proto";
import "lib/drive.proto";
import "lib/mapper.proto";
import "lib/common.proto";
// NEXT_TAG: 15
// NEXT_TAG: 16
message ConfigProto {
optional string comment = 8;
optional bool is_extension = 13;
@@ -20,6 +21,7 @@ message ConfigProto {
optional FluxSourceProto flux_source = 10;
optional FluxSinkProto flux_sink = 11;
optional DriveProto drive = 15;
optional EncoderProto encoder = 3;
optional DecoderProto decoder = 4;

14
lib/drive.proto Normal file
View File

@@ -0,0 +1,14 @@
syntax = "proto2";
import "lib/common.proto";
message DriveProto {
optional int32 drive = 1 [default = 0, (help) = "which drive to write to (0 or 1)"];
optional IndexMode index_mode = 2 [default = INDEXMODE_DRIVE, (help) = "index pulse source"];
optional int32 hard_sector_count = 3 [default = 0, (help) = "number of hard sectors on disk"];
optional bool high_density = 4 [default = true, (help) = "set if this is a high density disk"];
optional bool sync_with_index = 5 [default = false, (help) = "start reading at index mark"];
optional double revolutions = 6 [default = 1.2, (help) = "number of revolutions to read"];
}

View File

@@ -6,12 +6,13 @@ enum FluxMagic {
enum FluxFileVersion {
VERSION_1 = 1;
VERSION_2 = 2;
}
message TrackFluxProto {
optional int32 cylinder = 1;
optional int32 head = 2;
optional bytes flux = 3;
repeated bytes flux = 3;
}
message FluxFileProto {

View File

@@ -4,8 +4,8 @@
Fluxmap& Fluxmap::appendBytes(const Bytes& bytes)
{
if (bytes.size() == 0)
return *this;
if (bytes.size() == 0)
return *this;
return appendBytes(&bytes[0], bytes.size());
}
@@ -18,7 +18,7 @@ Fluxmap& Fluxmap::appendBytes(const uint8_t* ptr, size_t len)
while (len--)
{
uint8_t byte = *ptr++;
_ticks += byte & 0x3f;
_ticks += byte & 0x3f;
bw.write_8(byte);
}
@@ -28,9 +28,9 @@ Fluxmap& Fluxmap::appendBytes(const uint8_t* ptr, size_t len)
uint8_t& Fluxmap::findLastByte()
{
if (_bytes.empty())
appendByte(0x00);
return *(_bytes.end() - 1);
if (_bytes.empty())
appendByte(0x00);
return *(_bytes.end() - 1);
}
Fluxmap& Fluxmap::appendInterval(uint32_t ticks)
@@ -46,19 +46,19 @@ Fluxmap& Fluxmap::appendInterval(uint32_t ticks)
Fluxmap& Fluxmap::appendPulse()
{
findLastByte() |= 0x80;
findLastByte() |= 0x80;
return *this;
}
Fluxmap& Fluxmap::appendIndex()
{
findLastByte() |= 0x40;
findLastByte() |= 0x40;
return *this;
}
Fluxmap& Fluxmap::appendDesync()
{
appendByte(F_DESYNC);
appendByte(F_DESYNC);
return *this;
}
@@ -66,65 +66,66 @@ void Fluxmap::precompensate(int threshold_ticks, int amount_ticks)
{
uint8_t junk = 0xff;
for (unsigned i=0; i<_bytes.size(); i++)
for (unsigned i = 0; i < _bytes.size(); i++)
{
uint8_t& prev = (i == 0) ? junk : _bytes[i-1];
uint8_t prevticks = prev & 0x3f;
uint8_t& prev = (i == 0) ? junk : _bytes[i - 1];
uint8_t prevticks = prev & 0x3f;
uint8_t currticks = _bytes[i] & 0x3f;
if (currticks < (3*threshold_ticks))
if (currticks < (3 * threshold_ticks))
{
if ((prevticks <= threshold_ticks) && (currticks > threshold_ticks))
{
/* 01001; move the previous bit backwards. */
if (prevticks >= (1+amount_ticks))
if (prevticks >= (1 + amount_ticks))
prev -= amount_ticks;
if (currticks <= (0x7f-amount_ticks))
if (currticks <= (0x7f - amount_ticks))
currticks += amount_ticks;
}
else if ((prevticks > threshold_ticks) && (currticks <= threshold_ticks))
else if ((prevticks > threshold_ticks) &&
(currticks <= threshold_ticks))
{
/* 00101; move the current bit forwards. */
if (prevticks <= (0x7f-amount_ticks))
if (prevticks <= (0x7f - amount_ticks))
prev += amount_ticks;
if (currticks >= (1+amount_ticks))
if (currticks >= (1 + amount_ticks))
currticks -= amount_ticks;
}
}
}
}
std::vector<Fluxmap> Fluxmap::split() {
std::vector<Fluxmap> maps;
Fluxmap map;
for (unsigned i=0; i<_bytes.size(); i++) {
if (_bytes[i] == F_DESYNC) {
if (i > 0)
maps.push_back(map);
map = Fluxmap();
} else {
map.appendByte(_bytes[i]);
}
std::vector<std::unique_ptr<const Fluxmap>> Fluxmap::split() const
{
std::vector<std::unique_ptr<const Fluxmap>> maps;
auto bytesVector = rawBytes().split(F_DESYNC);
for (auto bytes : bytesVector)
{
if (bytes.size() != 0)
maps.push_back(std::move(std::make_unique<Fluxmap>(bytes)));
}
maps.push_back(map);
return maps;
}
void Fluxmap::rescale(double scale) {
if (scale != 1.0) {
auto bytesOrig = _bytes;
_bytes = Bytes();
_duration = 0;
_ticks = 0;
int lastEvent = 0;
for (unsigned i=0; i<bytesOrig.size(); i++)
std::unique_ptr<const Fluxmap> Fluxmap::rescale(double scale) const
{
if (scale == 1.0)
return std::make_unique<Fluxmap>(rawBytes());
auto newFluxmap = std::make_unique<Fluxmap>();
int lastEvent = 0;
for (uint8_t b : _bytes)
{
lastEvent += b & 0x3f;
if (b & 0xc0)
{
lastEvent += bytesOrig[i] & 0x3f;
if (bytesOrig[i] & 0xc0) {
appendInterval(lastEvent * scale + 0.5);
findLastByte() |= bytesOrig[i] & 0xc0;
lastEvent = 0;
}
newFluxmap->appendInterval(lastEvent * scale + 0.5);
newFluxmap->findLastByte() |= b & 0xc0;
lastEvent = 0;
}
}
return newFluxmap;
}

View File

@@ -64,8 +64,8 @@ public:
Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock);
void precompensate(int threshold_ticks, int amount_ticks);
std::vector<Fluxmap> split();
void rescale(double scale);
std::vector<std::unique_ptr<const Fluxmap>> split() const;
std::unique_ptr<const Fluxmap> rescale(double scale) const;
private:
uint8_t& findLastByte();

View File

@@ -26,7 +26,7 @@ public:
}
public:
void writeFlux(int cylinder, int head, Fluxmap& fluxmap)
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
unsigned totalTicks = fluxmap.ticks() + 2;
unsigned channels = _config.index_markers() ? 2 : 1;

View File

@@ -33,13 +33,14 @@ public:
{
FluxFileProto proto;
proto.set_magic(FluxMagic::MAGIC);
proto.set_version(FluxFileVersion::VERSION_1);
proto.set_version(FluxFileVersion::VERSION_2);
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);
for (const auto& fluxBytes : e.second)
track->add_flux(fluxBytes);
}
if (!proto.SerializeToOstream(&_of))
@@ -50,9 +51,10 @@ public:
}
public:
void writeFlux(int cylinder, int head, Fluxmap& fluxmap)
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
_data[std::make_pair(cylinder, head)] = fluxmap.rawBytes();
auto& vector = _data[std::make_pair(cylinder, head)];
vector.push_back(fluxmap.rawBytes());
}
operator std::string () const
@@ -63,7 +65,7 @@ public:
private:
std::string _filename;
std::ofstream _of;
std::map<std::pair<unsigned, unsigned>, Bytes> _data;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
};
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const Fl2FluxSinkProto& config)

View File

@@ -40,7 +40,7 @@ void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string&
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
{ std::regex("^vcd:(.*)$"), [](auto& s, auto* proto) { proto->mutable_vcd()->set_directory(s); }},
{ std::regex("^au:(.*)$"), [](auto& s, auto* proto) { proto->mutable_au()->set_directory(s); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive()->set_drive(std::stoi(s)); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
};
for (const auto& it : formats)

View File

@@ -29,7 +29,7 @@ public:
static void updateConfigForFilename(FluxSinkProto* proto, const std::string& filename);
public:
virtual void writeFlux(int track, int side, Fluxmap& fluxmap) = 0;
virtual void writeFlux(int track, int side, const Fluxmap& fluxmap) = 0;
virtual operator std::string () const = 0;
};

View File

@@ -2,12 +2,7 @@ syntax = "proto2";
import "lib/common.proto";
message HardwareFluxSinkProto {
optional IndexMode index_mode = 1 [default = INDEXMODE_DRIVE, (help) = "index pulse source"];
optional int32 hard_sector_count = 2 [(help) = "number of hard sectors on the disk"];
optional bool high_density = 3 [default = true, (help) = "set if this is a high density disk"];
optional int32 drive = 4 [default = 0, (help) = "which drive to write to (0 or 1)"];
}
message HardwareFluxSinkProto {}
message AuFluxSinkProto {
optional string directory = 1 [default = "aufiles", (help) = "directory to write .au files to"];

View File

@@ -2,6 +2,7 @@
#include "flags.h"
#include "fluxmap.h"
#include "logger.h"
#include "proto.h"
#include "usb/usb.h"
#include "fluxsink/fluxsink.h"
#include "lib/fluxsink/fluxsink.pb.h"
@@ -10,19 +11,19 @@
class HardwareFluxSink : public FluxSink
{
public:
HardwareFluxSink(const HardwareFluxSinkProto& config):
_config(config)
HardwareFluxSink(const HardwareFluxSinkProto& conf):
_config(conf)
{
if (config.has_hard_sector_count())
if (config.drive().has_hard_sector_count())
{
nanoseconds_t oneRevolution;
int retries = 5;
usbSetDrive(_config.drive(), _config.high_density(), _config.index_mode());
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
Logger() << BeginSpeedOperationLogMessage();
do {
oneRevolution = usbGetRotationalPeriod(_config.hard_sector_count());
_hardSectorThreshold = oneRevolution * 3 / (4 * _config.hard_sector_count());
oneRevolution = usbGetRotationalPeriod(config.drive().hard_sector_count());
_hardSectorThreshold = oneRevolution * 3 / (4 * config.drive().hard_sector_count());
retries--;
} while ((oneRevolution == 0) && (retries > 0));
@@ -41,9 +42,9 @@ public:
}
public:
void writeFlux(int track, int side, Fluxmap& fluxmap)
void writeFlux(int track, int side, const Fluxmap& fluxmap) override
{
usbSetDrive(_config.drive(), _config.high_density(), _config.index_mode());
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
#if 0
if (fluxSourceSinkFortyTrack)
{
@@ -60,7 +61,7 @@ public:
operator std::string () const
{
return fmt::format("drive {}", _config.drive());
return fmt::format("drive {}", config.drive().drive());
}
private:

View File

@@ -8,6 +8,7 @@
#include "lib/fluxsink/fluxsink.pb.h"
#include "proto.h"
#include "fmt/format.h"
#include "fluxmap.h"
#include "scp.h"
#include <fstream>
#include <sys/stat.h>
@@ -79,7 +80,7 @@ public:
}
public:
void writeFlux(int cylinder, int head, Fluxmap& fluxmap)
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
@@ -97,9 +98,7 @@ public:
trackheader.header.track_id[2] = 'K';
trackheader.header.strack = strack;
auto lastFluxmap = fluxmap.split().back();
FluxmapReader fmr(lastFluxmap);
FluxmapReader fmr(fluxmap);
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);

View File

@@ -20,7 +20,7 @@ public:
{}
public:
void writeFlux(int cylinder, int head, Fluxmap& fluxmap)
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
mkdir(_config.directory().c_str(), 0744);
std::ofstream of(

View File

@@ -30,7 +30,7 @@ struct CwfTrack
uint8_t length[4]; // little-endian
};
class CwfFluxSource : public FluxSource
class CwfFluxSource : public TrivialFluxSource
{
public:
CwfFluxSource(const CwfFluxSourceProto& config):
@@ -79,11 +79,11 @@ public:
}
public:
std::unique_ptr<Fluxmap> readFlux(int track, int side)
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side)
{
const auto& p = _trackOffsets.find(std::make_pair(track, side));
if (p == _trackOffsets.end())
return std::unique_ptr<Fluxmap>();
return std::make_unique<const Fluxmap>();
off_t pos = p->second.first;;
size_t length = p->second.second;

View File

@@ -4,16 +4,16 @@
#include "lib/fluxsource/fluxsource.pb.h"
#include "fmt/format.h"
class EraseFluxSource : public FluxSource
class EraseFluxSource : public TrivialFluxSource
{
public:
EraseFluxSource(const EraseFluxSourceProto& config) {}
~EraseFluxSource() {}
public:
std::unique_ptr<Fluxmap> readFlux(int track, int side)
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
return std::unique_ptr<Fluxmap>();
return std::unique_ptr<const Fluxmap>();
}
void recalibrate() {}
@@ -21,7 +21,7 @@ public:
std::unique_ptr<FluxSource> FluxSource::createEraseFluxSource(const EraseFluxSourceProto& config)
{
return std::unique_ptr<FluxSource>(new EraseFluxSource(config));
return std::make_unique<EraseFluxSource>(config);
}

View File

@@ -5,8 +5,46 @@
#include "fluxsource/fluxsource.h"
#include "proto.h"
#include "fmt/format.h"
#include "fluxmap.h"
#include <fstream>
class Fl2FluxSourceIterator : public FluxSourceIterator
{
public:
Fl2FluxSourceIterator(const TrackFluxProto& proto):
_proto(proto)
{}
bool hasNext() const override
{
return _count < _proto.flux_size();
}
std::unique_ptr<const Fluxmap> next() override
{
auto bytes = _proto.flux(_count);
_count++;
return std::make_unique<Fluxmap>(bytes);
}
private:
const TrackFluxProto& _proto;
int _count = 0;
};
class EmptyFluxSourceIterator : public FluxSourceIterator
{
bool hasNext() const override
{
return false;
}
std::unique_ptr<const Fluxmap> next() override
{
Error() << "no flux to read";
}
};
class Fl2FluxSource : public FluxSource
{
public:
@@ -20,18 +58,19 @@ public:
if (!_proto.ParseFromIstream(&ifs))
Error() << "unable to read input file";
upgradeFluxFile();
}
public:
std::unique_ptr<Fluxmap> readFlux(int cylinder, int head)
std::unique_ptr<FluxSourceIterator> readFlux(int cylinder, int head) override
{
for (const auto& track : _proto.track())
{
if ((track.cylinder() == cylinder) && (track.head() == head))
return std::make_unique<Fluxmap>(track.flux());
return std::make_unique<Fl2FluxSourceIterator>(track);
}
return std::make_unique<Fluxmap>();
return std::make_unique<EmptyFluxSourceIterator>();
}
void recalibrate() {}
@@ -43,6 +82,31 @@ private:
Error() << fmt::format("FL2 read I/O error: {}", strerror(errno));
}
void upgradeFluxFile()
{
if (_proto.version() == FluxFileVersion::VERSION_1)
{
/* Change a flux datastream with multiple segments separated by F_DESYNC into multiple
* flux segments. */
for (auto& track : *_proto.mutable_track())
{
if (track.flux_size() != 0)
{
Fluxmap oldFlux(track.flux(0));
track.clear_flux();
for (const auto& flux : oldFlux.split())
track.add_flux(flux->rawBytes());
}
}
_proto.set_version(FluxFileVersion::VERSION_2);
}
if (_proto.version() > FluxFileVersion::VERSION_2)
Error() << fmt::format("this is a version {} flux file, but this build of the client can only handle up to version {} --- please upgrade", _proto.version(), FluxFileVersion::VERSION_2);
}
private:
const Fl2FluxSourceProto& _config;
FluxFileProto _proto;

View File

@@ -1,6 +1,7 @@
#include "globals.h"
#include "flags.h"
#include "fluxsource/fluxsource.h"
#include "fluxmap.h"
#include "lib/config.pb.h"
#include "proto.h"
#include "utils.h"
@@ -56,7 +57,7 @@ void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::stri
{ std::regex("^erase:$"), [](auto& s, auto* proto) { proto->mutable_erase(); }},
{ std::regex("^kryoflux:(.*)$"), [](auto& s, auto* proto) { proto->mutable_kryoflux()->set_directory(s); }},
{ std::regex("^testpattern:(.*)"), [](auto& s, auto* proto) { proto->mutable_test_pattern(); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive()->set_drive(std::stoi(s)); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
};
for (const auto& it : formats)
@@ -72,5 +73,36 @@ void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::stri
Error() << fmt::format("unrecognised flux filename '{}'", filename);
}
class TrivialFluxSourceIterator : public FluxSourceIterator
{
public:
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int cylinder, int head):
_fluxSource(fluxSource),
_cylinder(cylinder),
_head(head)
{}
bool hasNext() const override
{
return !!_fluxSource;
}
std::unique_ptr<const Fluxmap> next() override
{
auto fluxmap = _fluxSource->readSingleFlux(_cylinder, _head);
_fluxSource = nullptr;
return fluxmap;
}
private:
TrivialFluxSource* _fluxSource;
int _cylinder;
int _head;
};
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int cylinder, int head)
{
return std::make_unique<TrivialFluxSourceIterator>(this, cylinder, head);
}

View File

@@ -14,6 +14,15 @@ class KryofluxFluxSourceProto;
class ScpFluxSourceProto;
class TestPatternFluxSourceProto;
class FluxSourceIterator
{
public:
virtual ~FluxSourceIterator() {}
virtual bool hasNext() const = 0;
virtual std::unique_ptr<const Fluxmap> next() = 0;
};
class FluxSource
{
public:
@@ -33,9 +42,16 @@ public:
static void updateConfigForFilename(FluxSourceProto* proto, const std::string& filename);
public:
virtual std::unique_ptr<Fluxmap> readFlux(int track, int side) = 0;
virtual std::unique_ptr<FluxSourceIterator> readFlux(int track, int side) = 0;
virtual void recalibrate() {}
virtual bool retryable() { return false; }
virtual bool isHardware() { return false; }
};
class TrivialFluxSource : public FluxSource
{
public:
std::unique_ptr<FluxSourceIterator> readFlux(int track, int side);
virtual std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) = 0;
};
#endif

View File

@@ -2,15 +2,7 @@ syntax = "proto2";
import "lib/common.proto";
message HardwareFluxSourceProto {
optional int32 drive = 1 [default = 0, (help) = "which drive to read from"];
optional double revolutions = 2 [default = 1.2, (help) = "number of revolutions to read"];
optional bool sync_with_index = 3 [default = false, (help) = "start reading at index mark"];
optional IndexMode index_mode = 4 [default = INDEXMODE_DRIVE, (help) = "index pulse source"];
optional int32 hard_sector_count = 5 [default = 0, (help) = "number of hard sectors on disk"];
optional bool high_density = 6 [default = true, (help) = "set if this is a high density disk"];
}
message HardwareFluxSourceProto {}
message TestPatternFluxSourceProto {
optional double interval_us = 1 [default = 4.0, (help) = "interval between pulses"];

View File

@@ -2,6 +2,7 @@
#include "flags.h"
#include "fluxmap.h"
#include "logger.h"
#include "proto.h"
#include "usb/usb.h"
#include "fluxsource/fluxsource.h"
#include "lib/fluxsource/fluxsource.pb.h"
@@ -9,54 +10,84 @@
class HardwareFluxSource : public FluxSource
{
private:
class HardwareFluxSourceIterator : public FluxSourceIterator
{
public:
HardwareFluxSourceIterator(
const HardwareFluxSource& fluxsource, int cylinder, int head):
_fluxsource(fluxsource),
_cylinder(cylinder),
_head(head)
{
}
bool hasNext() const
{
return true;
}
std::unique_ptr<const Fluxmap> next()
{
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
usbSeek(_cylinder);
Bytes data = usbRead(_head,
config.drive().sync_with_index(),
config.drive().revolutions() * _fluxsource._oneRevolution,
_fluxsource._hardSectorThreshold);
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBytes(data);
return fluxmap;
}
private:
const HardwareFluxSource& _fluxsource;
int _cylinder;
int _head;
};
public:
HardwareFluxSource(const HardwareFluxSourceProto& config):
_config(config)
HardwareFluxSource(const HardwareFluxSourceProto& conf): _config(conf)
{
int retries = 5;
usbSetDrive(_config.drive(), _config.high_density(), _config.index_mode());
Logger() << BeginSpeedOperationLogMessage();
do {
_oneRevolution = usbGetRotationalPeriod(_config.hard_sector_count());
if (_config.hard_sector_count() != 0)
_hardSectorThreshold = _oneRevolution * 3 / (4 * _config.hard_sector_count());
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
Logger() << BeginSpeedOperationLogMessage();
do
{
_oneRevolution =
usbGetRotationalPeriod(config.drive().hard_sector_count());
if (config.drive().hard_sector_count() != 0)
_hardSectorThreshold =
_oneRevolution * 3 / (4 * config.drive().hard_sector_count());
else
_hardSectorThreshold = 0;
retries--;
} while ((_oneRevolution == 0) && (retries > 0));
if (_oneRevolution == 0) {
Error() << "Failed\nIs a disk in the drive?";
}
if (_oneRevolution == 0)
Error() << "Failed\nIs a disk in the drive?";
Logger() << EndSpeedOperationLogMessage { _oneRevolution };
Logger() << EndSpeedOperationLogMessage{_oneRevolution};
}
~HardwareFluxSource()
{
}
~HardwareFluxSource() {}
public:
std::unique_ptr<Fluxmap> readFlux(int track, int side)
std::unique_ptr<FluxSourceIterator> readFlux(int cylinder, int head) override
{
usbSetDrive(_config.drive(), _config.high_density(), _config.index_mode());
usbSeek(track);
Bytes data = usbRead(
side, _config.sync_with_index(), _config.revolutions() * _oneRevolution, _hardSectorThreshold);
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBytes(data);
return fluxmap;
return std::make_unique<HardwareFluxSourceIterator>(
*this, cylinder, head);
}
void recalibrate()
void recalibrate() override
{
usbRecalibrate();
}
bool retryable()
bool isHardware() override
{
return true;
}
@@ -67,10 +98,8 @@ private:
nanoseconds_t _hardSectorThreshold;
};
std::unique_ptr<FluxSource> FluxSource::createHardwareFluxSource(const HardwareFluxSourceProto& config)
std::unique_ptr<FluxSource> FluxSource::createHardwareFluxSource(
const HardwareFluxSourceProto& config)
{
return std::unique_ptr<FluxSource>(new HardwareFluxSource(config));
}

View File

@@ -4,7 +4,7 @@
#include "lib/fluxsource/fluxsource.pb.h"
#include "fluxsource/fluxsource.h"
class KryofluxFluxSource : public FluxSource
class KryofluxFluxSource : public TrivialFluxSource
{
public:
KryofluxFluxSource(const KryofluxFluxSourceProto& config):
@@ -12,7 +12,7 @@ public:
{}
public:
std::unique_ptr<Fluxmap> readFlux(int track, int side)
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
return readStream(_path, track, side);
}
@@ -25,5 +25,5 @@ private:
std::unique_ptr<FluxSource> FluxSource::createKryofluxFluxSource(const KryofluxFluxSourceProto& config)
{
return std::unique_ptr<FluxSource>(new KryofluxFluxSource(config));
return std::make_unique<KryofluxFluxSource>(config);
}

View File

@@ -24,7 +24,7 @@ static int strackno(int track, int side)
return (track << 1) | side;
}
class ScpFluxSource : public FluxSource
class ScpFluxSource : public TrivialFluxSource
{
public:
ScpFluxSource(const ScpFluxSourceProto& config):
@@ -55,14 +55,14 @@ public:
}
public:
std::unique_ptr<Fluxmap> readFlux(int track, int side)
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
int strack = strackno(track, side);
if (strack >= ARRAY_SIZE(_header.track))
return std::unique_ptr<Fluxmap>();
return std::make_unique<Fluxmap>();
uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32();
if (offset == 0)
return std::unique_ptr<Fluxmap>();
return std::make_unique<Fluxmap>();
ScpTrackHeader trackheader;
_if.seekg(offset, std::ios::beg);
@@ -83,7 +83,7 @@ public:
revs[revolution] = trackrev;
}
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
auto fluxmap = std::make_unique<Fluxmap>();
nanoseconds_t pending = 0;
unsigned inputBytes = 0;
for (int revolution = 0; revolution < _header.revolutions; revolution++)

View File

@@ -4,7 +4,7 @@
#include "lib/fluxsource/fluxsource.pb.h"
#include "fmt/format.h"
class TestPatternFluxSource : public FluxSource
class TestPatternFluxSource : public TrivialFluxSource
{
public:
TestPatternFluxSource(const TestPatternFluxSourceProto& config):
@@ -14,9 +14,9 @@ public:
~TestPatternFluxSource() {}
public:
std::unique_ptr<Fluxmap> readFlux(int track, int side)
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
auto fluxmap = std::make_unique<Fluxmap>();
while (fluxmap->duration() < (_config.sequence_length_us()*1000000.0))
{
@@ -35,7 +35,7 @@ private:
std::unique_ptr<FluxSource> FluxSource::createTestPatternFluxSource(const TestPatternFluxSourceProto& config)
{
return std::unique_ptr<FluxSource>(new TestPatternFluxSource(config));
return std::make_unique<TestPatternFluxSource>(config);
}

View File

@@ -34,6 +34,8 @@ extern void hexdumpForSrp16(std::ostream& stream, const Bytes& bytes);
struct ErrorException
{
const std::string message;
void print() const;
};
class Error

View File

@@ -67,24 +67,16 @@ public:
if (mediaFlag == 0x20)
{
Logger() << "D88: high density mode";
if (config.flux_sink().dest_case() ==
FluxSinkProto::DestCase::kDrive)
{
config.mutable_flux_sink()->mutable_drive()->set_high_density(
true);
}
if (!config.drive().has_drive())
config.mutable_drive()->set_high_density(true);
}
else
{
Logger() << "D88: single/double density mode";
physicalStep = 2;
clockRate = 300;
if (config.flux_sink().dest_case() ==
FluxSinkProto::DestCase::kDrive)
{
config.mutable_flux_sink()->mutable_drive()->set_high_density(
false);
}
if (!config.drive().has_drive())
config.mutable_drive()->set_high_density(false);
}
std::unique_ptr<Image> image(new Image);

View File

@@ -57,10 +57,8 @@ public:
auto ibm = config.mutable_encoder()->mutable_ibm();
config.mutable_cylinders()->set_end(0);
Logger() << "NFD: HD 1.2MB mode";
if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive)
{
config.mutable_flux_sink()->mutable_drive()->set_high_density(true);
}
if (!config.drive().has_drive())
config.mutable_drive()->set_high_density(true);
std::unique_ptr<Image> image(new Image);
for (int track = 0; track < 163; track++)

View File

@@ -6,8 +6,8 @@
ConfigProto config = []() {
ConfigProto config;
config.mutable_flux_source()->mutable_drive()->set_drive(0);
config.mutable_flux_sink()->mutable_drive()->set_drive(0);
config.mutable_drive()->set_drive(0);
config.mutable_drive()->set_drive(0);
return config;
}();

View File

@@ -23,11 +23,10 @@
static std::unique_ptr<FluxSink> outputFluxSink;
static std::shared_ptr<Fluxmap> readFluxmap(FluxSource& fluxsource, unsigned cylinder, unsigned head)
static std::shared_ptr<const Fluxmap> readFluxmap(FluxSourceIterator& fluxsourceIterator, unsigned cylinder, unsigned head)
{
Logger() << BeginReadOperationLogMessage { cylinder, head };
std::shared_ptr<Fluxmap> fluxmap = fluxsource.readFlux(cylinder, head);
fluxmap->rescale(1.0/config.flux_source().rescale());
auto fluxmap = fluxsourceIterator.next()->rescale(1.0/config.flux_source().rescale());
Logger() << EndReadOperationLogMessage()
<< fmt::format("{0:.0} ms in {1} bytes", fluxmap->duration()/1e6, fluxmap->bytes());
return fluxmap;
@@ -108,9 +107,11 @@ std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, Abstract
std::set<std::shared_ptr<const Record>> track_records;
Fluxmap totalFlux;
for (int retry = config.decoder().retries(); retry >= 0; retry--)
auto fluxsourceIterator = fluxsource.readFlux(cylinder, head);
int retry = 0;
while (fluxsourceIterator->hasNext())
{
auto fluxmap = readFluxmap(fluxsource, cylinder, head);
auto fluxmap = readFluxmap(*fluxsourceIterator, cylinder, head);
totalFlux.appendDesync().appendBytes(fluxmap->rawBytes());
auto trackdataflux =
@@ -155,13 +156,17 @@ std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, Abstract
if (!hasBadSectors)
break;
if (!fluxsource.retryable())
if (!fluxsourceIterator->hasNext())
break;
if (retry == 0)
Logger() << fmt::format("giving up");
else
Logger()
<< fmt::format("retrying; {} retries remaining", retry);
if (fluxsource.isHardware())
{
retry++;
if (retry == 0)
Logger() << fmt::format("giving up");
else
Logger()
<< fmt::format("retrying; {} retries remaining", retry);
}
}
if (outputFluxSink)
@@ -236,7 +241,8 @@ void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
for (int head : iterate(config.heads()))
{
testForEmergencyStop();
auto fluxmap = readFluxmap(fluxsource, cylinder, head);
auto fluxsourceIterator = fluxsource.readFlux(cylinder, head);
auto fluxmap = readFluxmap(*fluxsourceIterator, cylinder, head);
fluxsink.writeFlux(cylinder, head, *fluxmap);
}
}

View File

@@ -4,6 +4,12 @@
bool emergencyStop = false;
static const char* WHITESPACE = " \t\n\r\f\v";
static const char* SEPARATORS = "/\\";
void ErrorException::print() const
{
std::cerr << message << '\n';
}
bool beginsWith(const std::string& value, const std::string& ending)
{
@@ -17,31 +23,52 @@ bool endsWith(const std::string& value, const std::string& ending)
{
if (ending.size() > value.size())
return false;
std::string lowercase(ending.size(), 0);
std::transform(value.rbegin(), value.rbegin() + ending.size(), lowercase.begin(), [](unsigned char c){ return std::tolower(c); });
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());
std::equal(ending.rbegin(), ending.rend(), lowercase.begin());
}
void leftTrimWhitespace(std::string& value)
std::string leftTrimWhitespace(std::string value)
{
value.erase(0, value.find_first_not_of(WHITESPACE));
value.erase(0, value.find_first_not_of(WHITESPACE));
return value;
}
void rightTrimWhitespace(std::string& value)
std::string rightTrimWhitespace(std::string value)
{
value.erase(value.find_last_not_of(WHITESPACE) + 1);
value.erase(value.find_last_not_of(WHITESPACE) + 1);
return value;
}
void trimWhitespace(std::string& value)
std::string trimWhitespace(const std::string& value)
{
leftTrimWhitespace(value);
rightTrimWhitespace(value);
return leftTrimWhitespace(rightTrimWhitespace(value));
}
std::string getLeafname(const std::string& value)
{
constexpr char sep = '/';
size_t i = value.find_last_of(SEPARATORS);
if (i != std::string::npos)
{
return value.substr(i + 1, value.length() - i);
}
return value;
}
void testForEmergencyStop()
{
if (emergencyStop)
throw EmergencyStopException();
if (emergencyStop)
throw EmergencyStopException();
}

View File

@@ -5,9 +5,10 @@
extern bool beginsWith(const std::string& value, const std::string& beginning);
extern bool endsWith(const std::string& value, const std::string& ending);
extern void leftTrimWhitespace(std::string& value);
extern void rightTrimWhitespace(std::string& value);
extern void trimWhitespace(std::string& value);
extern std::string leftTrimWhitespace(std::string value);
extern std::string rightTrimWhitespace(std::string value);
extern std::string trimWhitespace(const std::string& value);
extern std::string getLeafname(const std::string& value);
/* If set, any running job will terminate as soon as possible (with an error).
*/

View File

@@ -19,7 +19,7 @@
#include "proto.h"
void writeTracks(FluxSink& fluxSink,
const std::function<std::unique_ptr<Fluxmap>(int track, int side)> producer)
const std::function<std::unique_ptr<const Fluxmap>(int track, int side)> producer)
{
for (unsigned cylinder : iterate(config.cylinders()))
{
@@ -28,7 +28,7 @@ void writeTracks(FluxSink& fluxSink,
testForEmergencyStop();
Logger() << BeginWriteOperationLogMessage{cylinder, head};
std::unique_ptr<Fluxmap> fluxmap = producer(cylinder, head);
auto fluxmap = producer(cylinder, head);
if (!fluxmap)
{
/* Erase this track rather than writing. */
@@ -39,11 +39,11 @@ void writeTracks(FluxSink& fluxSink,
}
else
{
fluxmap->rescale(config.flux_sink().rescale());
auto scaled = fluxmap->rescale(config.flux_sink().rescale());
/* Precompensation actually seems to make things worse, so let's
* leave it disabled for now. */
// fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2);
fluxSink.writeFlux(cylinder, head, *fluxmap);
fluxSink.writeFlux(cylinder, head, *scaled);
Logger() << fmt::format("{0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
@@ -100,8 +100,7 @@ void writeTracksAndVerify(FluxSink& fluxSink,
fluxmap->bytes());
Logger() << BeginReadOperationLogMessage{cylinder, head};
std::shared_ptr<Fluxmap> writtenFluxmap =
fluxSource.readFlux(cylinder, head);
std::shared_ptr<const Fluxmap> writtenFluxmap = fluxSource.readFlux(cylinder, head)->next();
Logger() << EndReadOperationLogMessage()
<< fmt::format("verifying {0} ms in {1} bytes",
int(writtenFluxmap->duration() / 1e6),
@@ -188,8 +187,8 @@ void writeDiskCommand(
void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink)
{
writeTracks(fluxSink,
[&](int track, int side) -> std::unique_ptr<Fluxmap>
[&](int track, int side) -> std::unique_ptr<const Fluxmap>
{
return fluxSource.readFlux(track, side);
return fluxSource.readFlux(track, side)->next();
});
}

View File

@@ -10,7 +10,7 @@ class FluxSink;
class Image;
extern void writeTracks(FluxSink& fluxSink,
const std::function<std::unique_ptr<Fluxmap>(int track, int side)>
const std::function<std::unique_ptr<const Fluxmap>(int track, int side)>
producer);
extern void fillBitmapTo(std::vector<bool>& bitmap,

View File

@@ -382,6 +382,7 @@ buildproto libconfig.a \
lib/common.proto \
lib/config.proto \
lib/decoders/decoders.proto \
lib/drive.proto \
lib/encoders/encoders.proto \
lib/fluxsink/fluxsink.proto \
lib/fluxsource/fluxsource.proto \
@@ -644,6 +645,7 @@ runtest fmmfm-test tests/fmmfm.cc
runtest greaseweazle-test tests/greaseweazle.cc
runtest kryoflux-test tests/kryoflux.cc
runtest ldbs-test tests/ldbs.cc
runtest utils-test tests/utils.cc
runtest proto-test -I$OBJDIR/proto \
-d $OBJDIR/proto/libconfig.def \
-d $OBJDIR/proto/libtestproto.def \

View File

@@ -89,7 +89,7 @@ enum
{
F_BIT_PULSE = 0x80,
F_BIT_INDEX = 0x40,
F_DESYNC = 0x00,
F_DESYNC = 0x00, /* obsolete */
F_EOF = 0x100 /* synthetic, only produced by library */
};

View File

@@ -204,14 +204,15 @@ static void draw_x_graticules(Agg2D& painter, double x1, double y1, double x2, d
int mainAnalyseDriveResponse(int argc, const char* argv[])
{
config.mutable_flux_source()->mutable_drive();
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (!config.flux_sink().has_drive())
Error() << "this only makes sense with a real disk drive";
usbSetDrive(config.flux_sink().drive().drive(),
config.flux_sink().drive().high_density(),
config.flux_sink().drive().index_mode());
usbSetDrive(config.drive().drive(),
config.drive().high_density(),
config.drive().index_mode());
usbSeek(destCylinder);
std::cout << "Measuring rotational speed...\n";

View File

@@ -201,10 +201,11 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
int mainInspect(int argc, const char* argv[])
{
config.mutable_flux_source()->mutable_drive();
flags.parseFlagsWithConfigFiles(argc, argv, {});
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));
const auto fluxmap = fluxSource->readFlux(cylinderFlag, headFlag);
const auto fluxmap = fluxSource->readFlux(cylinderFlag, headFlag)->next();
std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n",
fluxmap->bytes(),

View File

@@ -61,6 +61,7 @@ int mainRawRead(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawread", formats);
config.mutable_flux_source()->mutable_drive();
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.flux_sink().has_drive())

View File

@@ -65,6 +65,7 @@ int mainRawWrite(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawwrite", formats);
config.mutable_flux_sink()->mutable_drive();
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.flux_source().has_drive())

View File

@@ -67,6 +67,7 @@ int mainRead(int argc, const char* argv[])
{
if (argc == 1)
showProfiles("read", formats);
config.mutable_flux_source()->mutable_drive();
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.decoder().copy_flux_to().has_drive())

View File

@@ -23,8 +23,8 @@ int mainRpm(int argc, const char* argv[])
if (!config.flux_source().has_drive())
Error() << "this only makes sense with a real disk drive";
usbSetDrive(config.flux_source().drive().drive(), false, config.flux_source().drive().index_mode());
nanoseconds_t period = usbGetRotationalPeriod(config.flux_source().drive().hard_sector_count());
usbSetDrive(config.drive().drive(), false, config.drive().index_mode());
nanoseconds_t period = usbGetRotationalPeriod(config.drive().hard_sector_count());
if (period != 0)
std::cout << "Rotational period is " << period/1000000 << " ms (" << 60e9/period << " rpm)" << std::endl;
else

View File

@@ -30,7 +30,7 @@ int mainSeek(int argc, const char* argv[])
if (!config.flux_source().has_drive())
Error() << "this only makes sense with a real disk drive";
usbSetDrive(config.flux_source().drive().drive(), false, config.flux_source().drive().index_mode());
usbSetDrive(config.drive().drive(), false, config.drive().index_mode());
usbSeek(cylinder);
return 0;
}

View File

@@ -67,6 +67,8 @@ int mainWrite(int argc, const char* argv[])
{
if (argc == 1)
showProfiles("write", formats);
config.mutable_flux_sink()->mutable_drive();
config.mutable_flux_source()->mutable_drive();
flags.parseFlagsWithConfigFiles(argc, argv, formats);
std::unique_ptr<ImageReader> reader(ImageReader::create(config.image_reader()));

View File

@@ -148,7 +148,7 @@ int main(int argc, const char* argv[])
}
catch (const ErrorException& e)
{
std::cerr << e.message << '\n';
e.print();
exit(1);
}
}

View File

@@ -1,15 +1,7 @@
comment: 'PC 1200kB 5.25" 80-track 15-sector DSHD'
flux_sink {
drive {
high_density: true
}
}
flux_source {
drive {
high_density: true
}
drive {
high_density: true
}
image_reader {

View File

@@ -1,15 +1,7 @@
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
}
drive {
high_density: true
}
image_reader {

View File

@@ -1,15 +1,7 @@
comment: 'Micropolis MetaFloppy Mod I 143kB 5.25" SSDD hard-sectored'
flux_source {
drive {
hard_sector_count: 16
}
}
flux_sink {
drive {
hard_sector_count: 16
}
drive {
hard_sector_count: 16
}
image_reader {

View File

@@ -1,15 +1,7 @@
comment: 'Micropolis MetaFloppy Mod I 287kB 5.25" DSDD hard-sectored'
flux_source {
drive {
hard_sector_count: 16
}
}
flux_sink {
drive {
hard_sector_count: 16
}
drive {
hard_sector_count: 16
}
image_reader {

View File

@@ -1,15 +1,7 @@
comment: 'Micropolis MetaFloppy Mod II 315kB 5.25" SSDD hard-sectored'
flux_source {
drive {
hard_sector_count: 16
}
}
flux_sink {
drive {
hard_sector_count: 16
}
drive {
hard_sector_count: 16
}
image_reader {

View File

@@ -1,15 +1,7 @@
comment: 'Micropolis MetaFloppy Mod II 630kB 5.25" DSDD hard-sectored'
flux_source {
drive {
hard_sector_count: 16
}
}
flux_sink {
drive {
hard_sector_count: 16
}
drive {
hard_sector_count: 16
}
image_reader {

View File

@@ -1,15 +1,7 @@
comment: 'N88-BASIC 5.25"/3.5" 77-track 26-sector DSHD'
flux_sink {
drive {
high_density: true
}
}
flux_source {
drive {
high_density: true
}
drive {
high_density: true
}
image_reader {

View File

@@ -10,17 +10,9 @@ image_writer {
nsi {}
}
flux_source {
drive {
hard_sector_count: 10
sync_with_index: 1
}
}
flux_sink {
drive {
hard_sector_count: 10
}
drive {
hard_sector_count: 10
sync_with_index: true
}
encoder {

View File

@@ -10,17 +10,9 @@ image_writer {
nsi {}
}
flux_source {
drive {
hard_sector_count: 10
sync_with_index: 1
}
}
flux_sink {
drive {
hard_sector_count: 10
}
drive {
hard_sector_count: 10
sync_with_index: true
}
encoder {

View File

@@ -10,17 +10,9 @@ image_writer {
nsi {}
}
flux_source {
drive {
hard_sector_count: 10
sync_with_index: 1
}
}
flux_sink {
drive {
hard_sector_count: 10
}
drive {
hard_sector_count: 10
sync_with_index: true
}
encoder {

View File

@@ -1,15 +1,7 @@
comment: 'Digital RX50 400kB 5.25" 80-track 10-sector SSQD'
flux_sink {
drive {
high_density: true
}
}
flux_source {
drive {
high_density: true
}
drive {
high_density: true
}
image_reader {

View File

@@ -287,12 +287,12 @@ image_writer {
encoder {
victor9k {
trackdata {
original_data_rate_khz: 500
post_index_gap_us: 1000.0
pre_header_sync_bits: 120
post_header_gap_bits: 48
original_data_rate_khz: 468
post_index_gap_us: 500.0
pre_header_sync_bits: 150
post_header_gap_bits: 60
pre_data_sync_bits: 40
post_data_gap_bits: 200
post_data_gap_bits: 300
}
trackdata {
head: 0

View File

@@ -143,11 +143,11 @@ image_writer {
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
original_data_rate_khz: 468
post_index_gap_us: 500.0
pre_header_sync_bits: 150
post_header_gap_bits: 60
pre_data_sync_bits: 40
post_data_gap_bits: 300
}
trackdata {

View File

@@ -242,10 +242,7 @@ void MainWindow::PrepareConfig()
void MainWindow::SetHighDensity()
{
bool hd = highDensityToggle->GetValue();
if (config.flux_source().has_drive())
config.mutable_flux_source()->mutable_drive()->set_high_density(hd);
if (config.flux_sink().has_drive())
config.mutable_flux_sink()->mutable_drive()->set_high_density(hd);
config.mutable_drive()->set_high_density(hd);
}
void MainWindow::ShowConfig()
@@ -261,7 +258,7 @@ void MainWindow::ApplyCustomSettings()
for (int i = 0; i < additionalSettingsEntry->GetNumberOfLines(); i++)
{
auto setting = additionalSettingsEntry->GetLineText(i).ToStdString();
trimWhitespace(setting);
setting = trimWhitespace(setting);
if (setting.size() == 0)
continue;

View File

@@ -1,5 +1,8 @@
#include "globals.h"
#include "bytes.h"
#include "snowhouse/snowhouse.h"
using namespace snowhouse;
static void check_oob(Bytes& b, unsigned pos)
{
@@ -115,6 +118,49 @@ static void test_slice()
assert((bs == Bytes{ 0, 0 }));
}
static void test_split()
{
AssertThat(
(Bytes{ }).split(0),
Equals(std::vector<Bytes> {
Bytes{}
}));
AssertThat(
(Bytes{ 0 }).split(0),
Equals(std::vector<Bytes> {
Bytes{},
Bytes{}
}));
AssertThat(
(Bytes{ 1 }).split(0),
Equals(std::vector<Bytes> {
Bytes{ 1 }
}));
AssertThat(
(Bytes{ 1, 0 }).split(0),
Equals(std::vector<Bytes> {
Bytes{ 1 },
Bytes{ }
}));
AssertThat(
(Bytes{ 0, 1 }).split(0),
Equals(std::vector<Bytes> {
Bytes{ },
Bytes{ 1 }
}));
AssertThat(
(Bytes{ 1, 0, 1 }).split(0),
Equals(std::vector<Bytes> {
Bytes{ 1 },
Bytes{ 1 }
}));
}
static void test_tobits()
{
Bytes b = {1, 2};
@@ -139,6 +185,7 @@ int main(int argc, const char* argv[])
test_reads();
test_writes();
test_slice();
test_split();
test_tobits();
test_tostring();
return 0;

47
tests/utils.cc Normal file
View File

@@ -0,0 +1,47 @@
#include "globals.h"
#include "utils.h"
#include "snowhouse/snowhouse.h"
using namespace snowhouse;
static void testLeftTrim()
{
AssertThat(leftTrimWhitespace("string"), Equals("string"));
AssertThat(leftTrimWhitespace(" string"), Equals("string"));
AssertThat(leftTrimWhitespace(" string "), Equals("string "));
AssertThat(leftTrimWhitespace("string "), Equals("string "));
}
static void testRightTrim()
{
AssertThat(rightTrimWhitespace("string"), Equals("string"));
AssertThat(rightTrimWhitespace(" string"), Equals(" string"));
AssertThat(rightTrimWhitespace(" string "), Equals(" string"));
AssertThat(rightTrimWhitespace("string "), Equals("string"));
}
static void testTrim()
{
AssertThat(trimWhitespace("string"), Equals("string"));
AssertThat(trimWhitespace(" string"), Equals("string"));
AssertThat(trimWhitespace(" string "), Equals("string"));
AssertThat(trimWhitespace("string "), Equals("string"));
}
static void testLeafname()
{
AssertThat(getLeafname(""), Equals(""));
AssertThat(getLeafname("filename"), Equals("filename"));
AssertThat(getLeafname("path/filename"), Equals("filename"));
AssertThat(getLeafname("/path/path/filename"), Equals("filename"));
}
int main(void)
{
testLeftTrim();
testRightTrim();
testTrim();
testLeafname();
return 0;
}

View File

@@ -1,10 +1,21 @@
#include "globals.h"
#include "bytes.h"
#include "fmt/format.h"
#include "utils.h"
#include <fstream>
#include "fnmatch.h"
/* Theoretical maximum number of sectors. */
static const int SECTOR_COUNT = 640;
/* Number of sectors on a 120kB disk. */
static constexpr int SECTOR_COUNT = 468;
/* Start sector for data (after the directory */
static constexpr int DATA_START_SECTOR = 14;
/* Size of a sector */
static constexpr int SECTOR_SIZE = 256;
/* Number of dirents in a directory. */
static constexpr int DIRECTORY_SIZE = 128;
struct Dirent
{
@@ -14,46 +25,220 @@ struct Dirent
int sectorCount;
};
static std::ifstream inputFile;
static std::fstream file;
static std::map<std::string, std::unique_ptr<Dirent>> directory;
static std::map<uint16_t, uint16_t> allocationTable;
static std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v")
{
s.erase(s.find_last_not_of(t) + 1);
return s;
}
void syntax()
{
std::cout << "Syntax: brother120tool <image> [<filenames...>]\n"
"If you specify a filename, it's extracted into the current directory.\n"
"Wildcards are allowed. If you don't, the directory is listed instead.\n";
" brother120tool --create <image> <filenames...>\n"
"If you specify a filename, it's extracted into the current "
"directory.\n"
"Wildcards are allowed. If you don't, the directory is listed "
"instead.\n";
exit(0);
}
void readDirectory()
{
for (int i=0; i<0x80; i++)
for (int i = 0; i < DIRECTORY_SIZE; i++)
{
inputFile.seekg(i*16, std::ifstream::beg);
file.seekg(i * 16, std::ifstream::beg);
uint8_t buffer[16];
inputFile.read((char*) buffer, sizeof(buffer));
Bytes buffer(16);
file.read((char*)&buffer[0], buffer.size());
if (buffer[0] == 0xf0)
continue;
std::string filename((const char*)buffer, 8);
filename = rtrim(filename);
ByteReader br(buffer);
std::string filename = br.read(8);
filename = filename.substr(0, filename.find(" "));
std::unique_ptr<Dirent> dirent(new Dirent);
dirent->filename = filename;
dirent->type = buffer[8];
dirent->startSector = buffer[9]*256 + buffer[10];
dirent->sectorCount = buffer[11];
dirent->type = br.read_8();
dirent->startSector = br.read_be16();
dirent->sectorCount = br.read_8();
directory[filename] = std::move(dirent);
}
}
void writeDirectory()
{
Bytes buffer(2048);
ByteWriter bw(buffer);
int count = 0;
for (const auto& it : directory)
{
const auto& dirent = it.second;
if (count == DIRECTORY_SIZE)
Error() << "too many files on disk";
bw.append(dirent->filename);
for (int i = dirent->filename.size(); i < 8; i++)
bw.write_8(' ');
bw.write_8(dirent->type);
bw.write_be16(dirent->startSector);
bw.write_8(dirent->sectorCount);
bw.write_be32(0); /* unknown */
count++;
}
static const Bytes padding(15);
while (count < DIRECTORY_SIZE)
{
bw.write_8(0xf0);
bw.append(padding);
count++;
}
file.seekp(0, std::ifstream::beg);
buffer.writeTo(file);
}
void writeBootSector(const Dirent& dirent, uint16_t checksum)
{
uint8_t sslo = dirent.startSector & 0xff;
uint8_t sshi = dirent.startSector >> 8;
uint8_t scnt = dirent.sectorCount;
uint8_t end = 0x70 + scnt;
uint8_t cklo = checksum & 0xff;
uint8_t ckhi = checksum >> 8;
uint8_t machineCode[] =
{
/* 6000 */ 0x55, /* magic number? */
/* 6001 */ 0xf3, /* di */
/* 6002 */ 0x31, 0x00, 0x00, /* ld sp, $0000 */
/* 6005 */ 0x3e, 0x00, /* ld a, $00 */
/* 6007 */ 0xed, 0x39, 0x34, /* out0 ($34), a */
/* 600a */ 0x3e, 0x0f, /* ld a, $0f */
/* 600c */ 0xed, 0x39, 0x0a, /* out0 ($0a), a */
/* 600f */ 0x3e, 0x00, /* ld a,$00 */
/* 6011 */ 0xed, 0x39, 0xd8, /* out0 ($d8),a */
/* 6014 */ 0x3e, 0xe7, /* ld a,$e7 */
/* 6016 */ 0xed, 0x39, 0x3a, /* out0 ($3a),a */
/* 6019 */ 0x3e, 0x16, /* ld a,$16 */
/* 601b */ 0xed, 0x39, 0x38, /* out0 ($38),a */
/* 601e */ 0x3e, 0x20, /* ld a,$20 */
/* 6020 */ 0xed, 0x39, 0x39, /* out0 ($39),a */
/* 6023 */ 0x01, sslo, sshi, /* ld bc, start sector */
/* 6026 */ 0x21, 0x00, 0xf8, /* ld hl,$f800 */
/* 6029 */ 0x1e, scnt, /* ld e, sector count */
/* 602b */ 0x16, 0x70, /* ld d,$70 */
/* 602d */ 0xc5, /* push bc */
/* 602e */ 0xd5, /* push de */
/* 602f */ 0xe5, /* push hl */
/* 6030 */ 0x3e, 0x06, /* ld a,$06 */
/* 6032 */ 0xef, /* rst $28 */
/* 6033 */ 0xda, 0xd3, 0x60, /* jp c,$60d3 */
/* 6036 */ 0xe1, /* pop hl */
/* 6037 */ 0xd1, /* pop de */
/* 6038 */ 0xc1, /* pop bc */
/* 6039 */ 0x3e, 0x00, /* ld a,$00 */
/* 603b */ 0xed, 0x39, 0x20, /* out0 ($20),a */
/* 603e */ 0x3e, 0x58, /* ld a,$58 */
/* 6040 */ 0xed, 0x39, 0x21, /* out0 ($21),a */
/* 6043 */ 0x3e, 0x02, /* ld a,$02 */
/* 6045 */ 0xed, 0x39, 0x22, /* out0 ($22),a */
/* 6048 */ 0x3e, 0x00, /* ld a,$00 */
/* 604a */ 0xed, 0x39, 0x23, /* out0 ($23),a */
/* 604d */ 0x7a, /* ld a,d */
/* 604e */ 0xed, 0x39, 0x24, /* out0 ($24),a */
/* 6051 */ 0x3e, 0x02, /* ld a,$02 */
/* 6053 */ 0xed, 0x39, 0x25, /* out0 ($25),a */
/* 6056 */ 0x3e, 0x00, /* ld a,$00 */
/* 6058 */ 0xed, 0x39, 0x26, /* out0 ($26),a */
/* 605b */ 0x3e, 0x01, /* ld a,$01 */
/* 605d */ 0xed, 0x39, 0x27, /* out0 ($27),a */
/* 6060 */ 0x3e, 0x02, /* ld a,$02 */
/* 6062 */ 0xed, 0x39, 0x31, /* out0 ($31),a */
/* 6065 */ 0x3e, 0x40, /* ld a,$40 */
/* 6067 */ 0xed, 0x39, 0x30, /* out0 ($30),a */
/* 606a */ 0x03, /* inc bc */
/* 606b */ 0x14, /* inc d */
/* 606c */ 0x1d, /* dec e */
/* 606d */ 0x7b, /* ld a,e */
/* 606e */ 0xfe, 0x00, /* cp $00 */
/* 6070 */ 0x20, 0xbb, /* jr nz,$602d */
/* 6072 */ 0x3e, 0x02, /* ld a,$02 */
/* 6074 */ 0xef, /* rst $28 */
/* 6075 */ 0x3e, 0x20, /* ld a,$20 */
/* 6077 */ 0xed, 0x39, 0x38, /* out0 ($38),a */
/* 607a */ 0x3e, 0x0c, /* ld a,$0c */
/* 607c */ 0xed, 0x39, 0x39, /* out0 ($39),a */
/* 607f */ 0x3e, 0x64, /* ld a,$64 */
/* 6081 */ 0xed, 0x39, 0x3a, /* out0 ($3a),a */
/* 6084 */ 0x3e, 0x0f, /* ld a,$0f */
/* 6086 */ 0xed, 0x39, 0x0a, /* out0 ($0a),a */
/* checksum routine */
/* 6089 */ 0x01, 0x00, 0x70, /* ld bc,$7000 */
/* 608c */ 0x11, 0x00, 0x00, /* ld de,$0000 */
/* 608f */ 0x21, 0x00, 0x00, /* ld hl,$0000 */
/* 6092 */ 0x0a, /* ld a,(bc) */
/* 6093 */ 0x5f, /* ld e,a */
/* 6094 */ 0x19, /* add hl,de */
/* 6095 */ 0x03, /* inc bc */
/* 6096 */ 0x79, /* ld a,c */
/* 6097 */ 0xfe, 0x00, /* cp $00 */
/* 6099 */ 0x20, 0xf7, /* jr nz,$6092 */
/* 609b */ 0x78, /* ld a,b */
/* 609c */ 0xfe, end, /* cp end page */
/* 609e */ 0x20, 0xf2, /* jr nz,$6092 */
/* 60a0 */ 0x11, 0xf3, 0x60, /* ld de,$60f3 */
/* 60a3 */ 0x1a, /* ld a,(de) */
/* 60a4 */ 0xbd, /* cp l */
/* 60a5 */ 0x20, 0x2f, /* jr nz,$60d6 */
/* 60a7 */ 0x13, /* inc de */
/* 60a8 */ 0x1a, /* ld a,(de) */
/* 60a9 */ 0xbc, /* cp h */
/* 60aa */ 0x20, 0x2a, /* jr nz,$60d6 */
/* reset and execute */
/* 60ac */ 0x3e, 0xff, /* ld a,$ff */
/* 60ae */ 0xed, 0x39, 0x88, /* out0 ($88),a */
/* 60b1 */ 0x01, 0xff, 0x0f, /* ld bc,$0fff */
/* 60b4 */ 0x21, 0x00, 0x40, /* ld hl,$4000 */
/* 60b7 */ 0x11, 0x01, 0x40, /* ld de,$4001 */
/* 60ba */ 0x36, 0x00, /* ld (hl),$00 */
/* 60bc */ 0xed, 0xb0, /* ldir */
/* 60be */ 0x01, 0xff, 0x0f, /* ld bc,$0fff */
/* 60c1 */ 0x21, 0x00, 0x50, /* ld hl,$5000 */
/* 60c4 */ 0x11, 0x01, 0x50, /* ld de,$5001 */
/* 60c7 */ 0x36, 0x20, /* ld (hl),$20 */
/* 60c9 */ 0xed, 0xb0, /* ldir */
/* 60cb */ 0x3e, 0xfe, /* ld a,$fe */
/* 60cd */ 0xed, 0x39, 0x88, /* out0 ($88),a */
/* 60d0 */ 0xc3, 0x00, 0x70, /* jp $7000 */
/* 60d3 */ 0xe1, /* pop hl */
/* 60d4 */ 0xd1, /* pop de */
/* 60d5 */ 0xc1, /* pop bc */
/* 60d6 */ 0x01, 0x00, 0x00, /* ld bc,$0000 */
/* 60d9 */ 0x0b, /* dec bc */
/* 60da */ 0x3e, 0xfe, /* ld a,$fe */
/* 60dc */ 0xed, 0x39, 0x90, /* out0 ($90),a */
/* 60df */ 0x78, /* ld a,b */
/* 60e0 */ 0xb1, /* or c */
/* 60e1 */ 0x20, 0xf6, /* jr nz,$60d9 */
/* 60e3 */ 0x31, 0x00, 0x00, /* ld sp,$0000 */
/* 60e6 */ 0x3e, 0xff, /* ld a,$ff */
/* 60e8 */ 0xed, 0x39, 0x90, /* out0 ($90),a */
/* 60eb */ 0x3e, 0x0f, /* ld a,$0f */
/* 60ed */ 0xed, 0x39, 0x0a, /* out0 ($0a),a */
/* 60f0 */ 0xc3, 0x00, 0x00, /* jp $0000 */
/* 60f3 */ cklo, ckhi, /* checksum */
};
file.seekp(0xc00, std::ifstream::beg);
file.write((char*) machineCode, sizeof(machineCode));
}
static bool isValidFile(const Dirent& dirent)
{
return (dirent.filename[0] & 0x80) == 0;
@@ -61,17 +246,47 @@ static bool isValidFile(const Dirent& dirent)
void readAllocationTable()
{
for (int sector=14; sector!=SECTOR_COUNT; sector++)
for (int sector = 1; sector != SECTOR_COUNT; sector++)
{
inputFile.seekg((sector-1)*2 + 0x800, std::ifstream::beg);
uint8_t buffer[2];
inputFile.read((char*) buffer, sizeof(buffer));
file.seekg((sector - 1) * 2 + 0x800, std::ifstream::beg);
Bytes buffer(2);
file.read((char*)&buffer[0], buffer.size());
uint16_t nextSector = (buffer[0]*256) + buffer[1];
uint16_t nextSector = buffer.reader().read_be16();
allocationTable[sector] = nextSector;
}
}
void writeAllocationTable()
{
Bytes buffer(SECTOR_COUNT * 2);
ByteWriter bw(buffer);
for (int sector = 1; sector < (DATA_START_SECTOR - 2); sector++)
bw.write_le16(sector + 1);
bw.write_le16(0xffff);
bw.write_le16(0xffff);
for (int sector = DATA_START_SECTOR; sector != SECTOR_COUNT; sector++)
bw.write_be16(allocationTable[sector]);
file.seekp(0x800, std::ifstream::beg);
buffer.writeTo(file);
}
uint16_t allocateSector()
{
for (int sector = DATA_START_SECTOR; sector != SECTOR_COUNT; sector++)
{
if (allocationTable[sector] == 0)
{
allocationTable[sector] = 0xffff;
return sector;
}
}
Error() << "unable to allocate sector --- disk full";
return 0;
}
void checkConsistency()
{
/* Verify that we more-or-less understand the format by fscking the disk. */
@@ -82,22 +297,26 @@ void checkConsistency()
const Dirent& dirent = *i.second;
if (!isValidFile(dirent))
continue;
int count = 0;
uint16_t sector = dirent.startSector;
while ((sector != 0xffff) && (sector != 0))
{
if (bitmap[sector])
std::cout << fmt::format("warning: sector {} appears to be multiply used\n", sector);
std::cout << fmt::format(
"warning: sector {} appears to be multiply used\n", sector);
bitmap[sector] = true;
sector = allocationTable[sector];
count++;
}
if (count != dirent.sectorCount)
std::cout <<
fmt::format("Warning: file '{}' claims to be {} sectors long but its chain is {}\n",
dirent.filename, dirent.sectorCount, count);
std::cout << fmt::format(
"Warning: file '{}' claims to be {} sectors long but its chain "
"is {}\n",
dirent.filename,
dirent.sectorCount,
count);
}
}
@@ -107,26 +326,86 @@ void listDirectory()
{
const Dirent& dirent = *i.second;
std::cout << fmt::format("{:9} {:6.2f}kB type {}: ",
dirent.filename,
(double)dirent.sectorCount / 4.0,
dirent.type);
dirent.filename,
(double)dirent.sectorCount / 4.0,
dirent.type);
if (isValidFile(dirent))
std::cout << fmt::format("{} sectors starting at sector {}",
dirent.sectorCount, dirent.startSector);
dirent.sectorCount,
dirent.startSector);
else
std::cout << "DELETED";
std::cout << "DELETED";
std::cout << std::endl;
}
}
void insertFile(const std::string& filename)
{
auto leafname = getLeafname(filename);
if (leafname.size() > 8)
Error() << "filename too long";
std::cout << fmt::format("Inserting '{}'\n", leafname);
std::ifstream inputFile(filename, std::ios::in | std::ios::binary);
if (!inputFile)
Error() << fmt::format(
"unable to open input file: {}", strerror(errno));
if (directory.find(leafname) != directory.end())
Error() << fmt::format("duplicate filename: {}", leafname);
auto dirent = std::make_unique<Dirent>();
dirent->filename = leafname;
dirent->type = (leafname.find('*') != std::string::npos);
dirent->startSector = 0xffff;
dirent->sectorCount = 0;
uint16_t lastSector = 0xffff;
uint16_t checksum = 0;
while (!inputFile.eof())
{
uint8_t buffer[SECTOR_SIZE];
inputFile.read((char*) buffer, sizeof(buffer));
for (int i=0; i<inputFile.gcount(); i++)
checksum += buffer[i];
if (inputFile.gcount() == 0)
break;
if (inputFile.bad())
Error() << fmt::format("I/O error on read: {}", strerror(errno));
uint16_t thisSector = allocateSector();
if (lastSector == 0xffff)
dirent->startSector = thisSector;
else
allocationTable[lastSector] = thisSector;
dirent->sectorCount++;
file.seekp((thisSector - 1) * 0x100, std::ifstream::beg);
file.write((char*) buffer, sizeof(buffer));
if (file.bad())
Error() << fmt::format("I/O error on write: {}", strerror(errno));
lastSector = thisSector;
}
if (leafname == "*boot")
{
std::cout << fmt::format(
"Writing boot sector with checksum 0x{:04x}\n", checksum);
writeBootSector(*dirent, checksum);
}
directory[leafname] = std::move(dirent);
}
void extractFile(const std::string& pattern)
{
for (const auto& i : directory)
{
const Dirent& dirent = *i.second;
if (dirent.type != 0)
if ((dirent.type == 0xf0) || (dirent.type == 0xe5))
continue;
if (fnmatch(pattern.c_str(), dirent.filename.c_str(), 0))
@@ -137,31 +416,56 @@ void extractFile(const std::string& pattern)
std::ofstream outputFile(dirent.filename,
std::ios::out | std::ios::binary | std::ios::trunc);
if (!outputFile)
Error() << fmt::format("unable to open output file: {}", strerror(errno));
Error() << fmt::format(
"unable to open output file: {}", strerror(errno));
uint16_t sector = dirent.startSector;
while ((sector != 0) && (sector != 0xffff))
{
uint8_t buffer[256];
inputFile.seekg((sector-1) * 0x100, std::ifstream::beg);
if (!inputFile.read((char*) buffer, sizeof(buffer)))
Error() << fmt::format("I/O error on read: {}", strerror(errno));
if (!outputFile.write((const char*) buffer, sizeof(buffer)))
Error() << fmt::format("I/O error on write: {}", strerror(errno));
file.seekg((sector - 1) * 0x100, std::ifstream::beg);
if (!file.read((char*)buffer, sizeof(buffer)))
Error() << fmt::format(
"I/O error on read: {}", strerror(errno));
if (!outputFile.write((const char*)buffer, sizeof(buffer)))
Error() << fmt::format(
"I/O error on write: {}", strerror(errno));
sector = allocationTable[sector];
}
}
}
int main(int argc, const char* argv[])
static void doCreate(int argc, const char* argv[])
{
if (argc < 3)
syntax();
file.open(argv[1], std::ios::out | std::ios::binary | std::ios::trunc);
if (!file.is_open())
Error() << fmt::format("cannot open output file '{}'", argv[1]);
file.seekp(SECTOR_COUNT * SECTOR_SIZE - 1, std::ifstream::beg);
file.put(0);
for (int i = 2; i < argc; i++)
insertFile(argv[i]);
writeDirectory();
writeAllocationTable();
checkConsistency();
file.close();
}
static void doExtract(int argc, const char* argv[])
{
if (argc < 2)
syntax();
inputFile.open(argv[1], std::ios::in | std::ios::binary);
if (!inputFile.is_open())
Error() << fmt::format("cannot open input file '{}'", argv[1]);
file.open(argv[1], std::ios::in | std::ios::binary);
if (!file.is_open())
Error() << fmt::format("cannot open input file '{}'", argv[1]);
readDirectory();
readAllocationTable();
@@ -171,10 +475,26 @@ int main(int argc, const char* argv[])
listDirectory();
else
{
for (int i=2; i<argc; i++)
for (int i = 2; i < argc; i++)
extractFile(argv[i]);
}
inputFile.close();
file.close();
}
int main(int argc, const char* argv[])
{
try
{
if ((argc > 1) && (strcmp(argv[1], "--create") == 0))
doCreate(argc - 1, argv + 1);
else
doExtract(argc, argv);
}
catch (const ErrorException& e)
{
e.print();
exit(1);
}
return 0;
}

View File

@@ -26,31 +26,39 @@ void putbyte(uint32_t offset, uint8_t value)
int main(int argc, const char* argv[])
{
if (argc < 2)
syntax();
inputFile.open(argv[1], std::ios::in | std::ios::out | std::ios::binary);
if (!inputFile.is_open())
Error() << fmt::format("cannot open input file '{}'", argv[1]);
try
{
if (argc < 2)
syntax();
inputFile.open(argv[1], std::ios::in | std::ios::out | std::ios::binary);
if (!inputFile.is_open())
Error() << fmt::format("cannot open input file '{}'", argv[1]);
uint8_t b1 = getbyte(0x015);
uint8_t b2 = getbyte(0x100);
if ((b1 == 0x58) && (b2 == 0x58))
{
std::cerr << "Flipping from Brother to DOS.\n";
putbyte(0x015, 0xf0);
putbyte(0x100, 0xf0);
}
else if ((b1 == 0xf0) && (b2 == 0xf0))
{
std::cerr << "Flipping from DOS to Brother.\n";
putbyte(0x015, 0x58);
putbyte(0x100, 0x58);
}
else
Error() << "Unknown image format.";
uint8_t b1 = getbyte(0x015);
uint8_t b2 = getbyte(0x100);
if ((b1 == 0x58) && (b2 == 0x58))
{
std::cerr << "Flipping from Brother to DOS.\n";
putbyte(0x015, 0xf0);
putbyte(0x100, 0xf0);
}
else if ((b1 == 0xf0) && (b2 == 0xf0))
{
std::cerr << "Flipping from DOS to Brother.\n";
putbyte(0x015, 0x58);
putbyte(0x100, 0x58);
}
else
Error() << "Unknown image format.";
inputFile.close();
return 0;
inputFile.close();
return 0;
}
catch (const ErrorException& e)
{
e.print();
exit(1);
}
}

View File

@@ -255,59 +255,67 @@ static void translateFluxVersion2(Fluxmap& fluxmap, const Bytes& bytes)
int main(int argc, const char* argv[])
{
if ((argc != 2) || (strcmp(argv[1], "--help") == 0))
syntax();
try
{
if ((argc != 2) || (strcmp(argv[1], "--help") == 0))
syntax();
std::string filename = argv[1];
if (!isSqlite(filename))
{
std::cout << "File is up to date.\n";
exit(0);
}
std::string filename = argv[1];
if (!isSqlite(filename))
{
std::cout << "File is up to date.\n";
exit(0);
}
std::string outFilename = filename + ".out.flux";
auto db = sqlOpen(filename, SQLITE_OPEN_READONLY);
int version = sqlGetVersion(db);
std::string outFilename = filename + ".out.flux";
auto db = sqlOpen(filename, SQLITE_OPEN_READONLY);
int version = sqlGetVersion(db);
{
auto fluxsink = FluxSink::createFl2FluxSink(outFilename);
for (const auto& locations : sqlFindFlux(db))
{
unsigned cylinder = locations.first;
unsigned head = locations.second;
Bytes bytes = sqlReadFluxBytes(db, cylinder, head);
Fluxmap fluxmap;
switch (version)
{
case FLUX_VERSION_2:
translateFluxVersion2(fluxmap, bytes);
break;
{
auto fluxsink = FluxSink::createFl2FluxSink(outFilename);
for (const auto& locations : sqlFindFlux(db))
{
unsigned cylinder = locations.first;
unsigned head = locations.second;
Bytes bytes = sqlReadFluxBytes(db, cylinder, head);
Fluxmap fluxmap;
switch (version)
{
case FLUX_VERSION_2:
translateFluxVersion2(fluxmap, bytes);
break;
case FLUX_VERSION_3:
fluxmap.appendBytes(bytes);
break;
case FLUX_VERSION_3:
fluxmap.appendBytes(bytes);
break;
default:
Error() << fmt::format(
"you cannot upgrade version {} files (please file a "
"bug)",
version);
}
fluxsink->writeFlux(cylinder, head, fluxmap);
std::cout << '.' << std::flush;
}
default:
Error() << fmt::format(
"you cannot upgrade version {} files (please file a "
"bug)",
version);
}
fluxsink->writeFlux(cylinder, head, fluxmap);
std::cout << '.' << std::flush;
}
std::cout << "Writing output file...\n";
}
std::cout << "Writing output file...\n";
}
sqlite3_close(db);
sqlite3_close(db);
if (remove(filename.c_str()) != 0)
Error() << fmt::format(
"couldn't remove input file: {}", strerror(errno));
if (remove(filename.c_str()) != 0)
Error() << fmt::format(
"couldn't remove input file: {}", strerror(errno));
if (rename(outFilename.c_str(), filename.c_str()) != 0)
Error() << fmt::format(
"couldn't replace input file: {}", strerror(errno));
return 0;
if (rename(outFilename.c_str(), filename.c_str()) != 0)
Error() << fmt::format(
"couldn't replace input file: {}", strerror(errno));
return 0;
}
catch (const ErrorException& e)
{
e.print();
exit(1);
}
}