Merge from master.

This commit is contained in:
David Given
2022-03-04 20:19:42 +01:00
64 changed files with 4015 additions and 844 deletions

View File

@@ -10,7 +10,7 @@ jobs:
with:
fetch-depth: 1
- name: apt
run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev ninja-build protobuf-compiler
run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev ninja-build protobuf-compiler libwxgtk3.0-gtk3-dev
- name: make
run: make
@@ -21,7 +21,7 @@ jobs:
with:
fetch-depth: 1
- name: brew
run: brew install sqlite pkg-config libusb ninja protobuf truncate
run: brew install sqlite pkg-config libusb ninja protobuf truncate wxwidgets
- name: make
run: make
@@ -46,6 +46,7 @@ jobs:
mingw-w64-i686-protobuf
vim
diffutils
mingw-w64-i686-wxWidgets
- uses: actions/checkout@v1
with:
fetch-depth: 1
@@ -55,7 +56,7 @@ jobs:
- name: zip
run: |
zip -9 fluxengine.zip fluxengine.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
zip -9 fluxengine.zip fluxengine.exe fluxengine-debug.exe fluxengine-gui.exe fluxengine-gui-debug.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
- name: Upload build artifacts
uses: actions/upload-artifact@v2

View File

@@ -27,6 +27,7 @@ jobs:
mingw-w64-i686-protobuf
vim
diffutils
mingw-w64-i686-wxWidgets
- uses: actions/checkout@v2
with:
fetch-depth: 1
@@ -35,7 +36,7 @@ jobs:
make
- name: zip
run: |
zip -9 fluxengine.zip fluxengine.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
- name: date
run: |
echo "RELEASE_DATE=$(date --rfc-3339=date)" >> ${GITHUB_ENV}

7
.gitignore vendored
View File

@@ -1,2 +1,9 @@
.obj
.project
/.ninja*
/brother120tool
/brother240tool
/fluxengine
/brother120tool-*
/brother240tool-*
/fluxengine-*

View File

@@ -29,9 +29,12 @@ export CXX = /mingw32/bin/g++
export AR = /mingw32/bin/ar rc
export RANLIB = /mingw32/bin/ranlib
export STRIP = /mingw32/bin/strip
export CFLAGS += -I/mingw32/include/libusb-1.0 -I/mingw32/include
export LDFLAGS +=
export LIBS += -L/mingw32/lib -static -lz -lsqlite3 \
export CFLAGS += -I/mingw32/include
export CXXFLAGS += $(shell wx-config --cxxflags --static=yes)
export GUILDFLAGS += -lmingw32
export LIBS += -L/mingw32/lib -static -lsqlite3 \
$(shell wx-config --libs --static=yes core base) -lz \
-lcomctl32 -loleaut32 -lspoolss -loleacc -lwinspool \
-lsetupapi -lwinusb -lole32 -lprotobuf -luuid
export EXTENSION = .exe
else
@@ -41,6 +44,10 @@ ifneq ($(packages-exist),yes)
$(warning These pkg-config packages are installed: $(shell pkg-config --list-all | sort | awk '{print $$1}'))
$(error You must have these pkg-config packages installed: $(PACKAGES))
endif
wx-exist = $(shell wx-config --cflags > /dev/null && echo yes)
ifneq ($(wx-exist),yes)
$(error You must have these wx-config installed)
endif
export PROTOC = protoc
export CC = gcc
@@ -48,9 +55,10 @@ export CXX = g++
export AR = ar rc
export RANLIB = ranlib
export STRIP = strip
export CFLAGS += $(shell pkg-config --cflags $(PACKAGES))
export CFLAGS += $(shell pkg-config --cflags $(PACKAGES)) $(shell wx-config --cxxflags)
export LDFLAGS +=
export LIBS += $(shell pkg-config --libs $(PACKAGES))
export GUILIBS += $(shell wx-config --libs core base)
export EXTENSION =
ifeq ($(shell uname),Darwin)

View File

@@ -97,10 +97,10 @@ people who've had it work).
| [Acorn ADFS](doc/disk-acornadfs.md) | 🦄 | 🦖* | single- and double- sided |
| [Acorn DFS](doc/disk-acorndfs.md) | 🦄 | 🦖* | |
| [Ampro Little Board](doc/disk-ampro.md) | 🦖 | 🦖* | |
| [Apple II DOS 3.3](doc/disk-apple2.md) | 🦄 | | doesn't do logical sector remapping |
| [Apple II DOS 3.3](doc/disk-apple2.md) | 🦄 | 🦖 | doesn't do logical sector remapping |
| [Amiga](doc/disk-amiga.md) | 🦄 | 🦄 | |
| [Commodore 64 1541/1581](doc/disk-c64.md) | 🦄 | 🦄 | and probably the other formats |
| [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦖 | |
| [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦄 | |
| [Brother 240kB](doc/disk-brother.md) | 🦄 | 🦄 | |
| [Brother FB-100](doc/disk-fb100.md) | 🦖 | | Tandy Model 100, Husky Hunter, knitting machines |
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |

View File

@@ -1,6 +1,10 @@
#ifndef APPLE2_H
#define APPLE2_H
#include <memory.h>
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#define APPLE2_SECTOR_RECORD 0xd5aa96
#define APPLE2_DATA_RECORD 0xd5aaad
@@ -8,6 +12,7 @@
#define APPLE2_ENCODED_SECTOR_LENGTH 342
extern std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config);
extern std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config);
#endif

View File

@@ -2,3 +2,4 @@ syntax = "proto2";
message Apple2DecoderProto {}
message Apple2EncoderProto {}

View File

@@ -90,6 +90,9 @@ public:
_sector->logicalTrack = combine(br.read_be16());
_sector->logicalSector = combine(br.read_be16());
uint8_t checksum = combine(br.read_be16());
// If the checksum is correct, upgrade the sector from MISSING
// to DATA_MISSING in anticipation of its data record
if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
@@ -101,11 +104,42 @@ public:
if (readRaw24() != APPLE2_DATA_RECORD)
return;
// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and
// the data itself. This has been seen on real world disks
// such as the Apple II Operating System Kit from Apple2Online.
// However, I haven't seen it described in any of the various
// references.
//
// This extra '0' bit would not affect the real disk interface,
// as it was a '1' reaching the top bit of a shift register
// that triggered a byte to be available, but it affects the
// way the data is read here.
//
// While the floppies tested only seemed to need this applied
// to the first byte of the data record, applying it
// consistently to all of them doesn't seem to hurt, and
// simplifies the code.
/* Read and decode data. */
unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
Bytes bytes = toBytes(readRawBits(recordLength*8)).slice(0, recordLength);
auto readApple8 = [&]() {
auto result = readRaw8();
while(result & 0x80 == 0) {
auto b = readRawBits(1);
result = (result << 1) | b[0];
}
return result;
};
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
uint8_t bytes[recordLength];
for(auto &byte : bytes) {
byte = readApple8();
}
// Upgrade the sector from MISSING to BAD_CHECKSUM.
// If decode_crazy_data succeeds, it upgrades the sector to
// OK.
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}

187
arch/apple2/encoder.cc Normal file
View File

@@ -0,0 +1,187 @@
#include "globals.h"
#include "arch/apple2/apple2.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "sector.h"
#include "writer.h"
#include "image.h"
#include "fmt/format.h"
#include "lib/encoders/encoders.pb.h"
#include <ctype.h>
#include "bytes.h"
static int encode_data_gcr(uint8_t data) {
switch(data)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
class Apple2Encoder : public AbstractEncoder
{
public:
Apple2Encoder(const EncoderProto& config):
AbstractEncoder(config)
{}
public:
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
constexpr auto numSectors = 16;
if (physicalSide == 0)
{
int logicalTrack = physicalTrack / 2;
unsigned numSectors = 16;
for (int sectorId=0; sectorId<numSectors; sectorId++)
{
const auto& sector = image.get(logicalTrack, 0, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
{
if (physicalSide != 0)
return std::unique_ptr<Fluxmap>();
int logicalTrack = physicalTrack / 2;
double clockRateUs = 4.;
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
for (const auto& sector : sectors) {
if(sector) {
writeSector(bits, cursor, *sector);
}
}
if (cursor >= bits.size())
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap;
}
private:
uint8_t volume_id = 254;
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
* and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
* as well as Understanding the Apple II (1983) Chapter 9
* https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater
*/
void writeSector(std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const
{
if ((sector.status == Sector::OK) or (sector.status == Sector::BAD_CHECKSUM))
{
auto write_bit = [&](bool val) {
if(cursor <= bits.size()) { bits[cursor] = val; }
cursor++;
};
auto write_bits = [&](uint32_t bits, int width) {
for(int i=width; i--;) {
write_bit(bits & (1u << i));
}
};
auto write_gcr44 = [&](uint8_t value) {
write_bits((value << 7) | value | 0xaaaa, 16);
};
auto write_gcr6 = [&](uint8_t value) {
write_bits(encode_data_gcr(value), 8);
};
auto write_ff40 = [&]() {
write_bits(0xff0, 12);
};
auto write_ff36 = [&]() {
write_bits(0xff << 2, 10);
};
auto write_ff32 = [&]() {
write_bits(0xff, 8);
};
// 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 field: APPLE2_SECTOR_RECORD + sector identifier + DE AA EB
write_bits(APPLE2_SECTOR_RECORD, 24);
write_gcr44(volume_id);
write_gcr44(sector.logicalTrack);
write_gcr44(sector.logicalSector);
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
write_bits(0xDEAAEB, 24);
// Write 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_bits(APPLE2_DATA_RECORD, 24);
// Convert the sector data to GCR, append the checksum, and write it out
constexpr auto TWOBIT_COUNT = 0x56; // Size of the 'twobit' area at the start of the GCR data
uint8_t checksum = 0;
for(int i=0; i<APPLE2_ENCODED_SECTOR_LENGTH; i++) {
int value;
if(i >= TWOBIT_COUNT) {
value = sector.data[i - TWOBIT_COUNT] >> 2;
} else {
uint8_t tmp = sector.data[i];
value = ((tmp & 1) << 1) | ((tmp & 2) >> 1);
tmp = sector.data[i + TWOBIT_COUNT];
value |= ((tmp & 1) << 3) | ((tmp & 2) << 1);
if(i + 2*TWOBIT_COUNT < APPLE2_SECTOR_LENGTH) {
tmp = sector.data[i + 2*TWOBIT_COUNT];
value |= ((tmp & 1) << 5) | ((tmp & 2) << 3);
}
}
checksum ^= value;
// assert(checksum & ~0x3f == 0);
write_gcr6(checksum);
checksum = value;
}
if(sector.status == Sector::BAD_CHECKSUM) checksum ^= 0x3f;
write_gcr6(checksum);
write_bits(0xDEAAEB, 24);
}
}
};
std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new Apple2Encoder(config));
}

View File

@@ -113,7 +113,6 @@ public:
{
std::vector<std::shared_ptr<const Sector>> sectors;
int logicalTrack;
if (physicalSide != 0)
return sectors;
physicalTrack -= _config.bias();
@@ -123,19 +122,17 @@ public:
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|| (physicalTrack & 1))
return sectors;
logicalTrack = physicalTrack/2;
break;
case BROTHER240:
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
return sectors;
logicalTrack = physicalTrack;
break;
}
for (int sectorId=0; sectorId<BROTHER_SECTORS_PER_TRACK; sectorId++)
{
const auto& sector = image.get(logicalTrack, 0, sectorId);
const auto& sector = image.get(physicalTrack, 0, sectorId);
if (sector)
sectors.push_back(sector);
}
@@ -146,7 +143,6 @@ public:
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
{
int logicalTrack;
if (physicalSide != 0)
return std::unique_ptr<Fluxmap>();
physicalTrack -= _config.bias();
@@ -156,13 +152,11 @@ public:
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|| (physicalTrack & 1))
return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack/2;
break;
case BROTHER240:
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack;
break;
}
@@ -179,10 +173,10 @@ public:
double dataMs = headerMs + _config.post_header_spacing_ms();
unsigned dataCursor = dataMs*1e3 / _config.clock_rate_us();
const auto& sectorData = image.get(logicalTrack, 0, sectorId);
const auto& sectorData = image.get(physicalTrack, 0, sectorId);
fillBitmapTo(bits, cursor, headerCursor, { true, false });
write_sector_header(bits, cursor, logicalTrack, sectorId);
write_sector_header(bits, cursor, sectorData->logicalTrack, sectorId);
fillBitmapTo(bits, cursor, dataCursor, { true, false });
write_sector_data(bits, cursor, sectorData->data);
}

View File

@@ -45,6 +45,18 @@ You should end up with an `apple2.img` which is 143360 bytes long.
**Big warning!** The image may not work in an emulator, due to the
logical sector mapping issue described above.
Writing discs
-------------
Just do:
```
fluxengine write apple2 -i apple2.img
```
**Big warning!** An image designed for an emulator may not work, due to the
logical sector mapping issue described above.
Useful references
-----------------

View File

@@ -18,6 +18,7 @@ metadata. Systems which use IBM scheme disks include but are not limited to:
- NEC PC-98 series
- Sharp X68000
- Fujitsu FM Towns
- VAX & PDP-11
- etc
FluxEngine supports reading these. However, some variants are more peculiar

View File

@@ -3,6 +3,7 @@
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/ibm/ibm.h"
@@ -20,6 +21,7 @@ std::unique_ptr<AbstractEncoder> AbstractEncoder::create(const EncoderProto& con
std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>> encoders =
{
{ EncoderProto::kAmiga, createAmigaEncoder },
{ EncoderProto::kApple2, createApple2Encoder },
{ EncoderProto::kBrother, createBrotherEncoder },
{ EncoderProto::kC64, createCommodore64Encoder },
{ EncoderProto::kIbm, createIbmEncoder },

View File

@@ -1,6 +1,7 @@
syntax = "proto2";
import "arch/amiga/amiga.proto";
import "arch/apple2/apple2.proto";
import "arch/brother/brother.proto";
import "arch/c64/c64.proto";
import "arch/ibm/ibm.proto";
@@ -22,5 +23,6 @@ message EncoderProto {
NorthstarEncoderProto northstar = 9;
MicropolisEncoderProto micropolis = 10;
Victor9kEncoderProto victor9k = 11;
Apple2EncoderProto apple2 = 12;
}
}

View File

@@ -182,32 +182,38 @@ void FlagGroup::parseFlagsWithConfigFiles(int argc, const char* argv[],
{
parseFlags(argc, argv,
[&](const auto& filename) {
const auto& it = configFiles.find(filename);
if (it != configFiles.end())
{
ConfigProto newConfig;
if (!newConfig.ParseFromString(it->second))
Error() << "couldn't load built-in config proto";
config.MergeFrom(newConfig);
}
else
{
std::ifstream f(filename, std::ios::out);
if (f.fail())
Error() << fmt::format("Cannot open '{}': {}", filename, strerror(errno));
std::ostringstream ss;
ss << f.rdbuf();
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
Error() << "couldn't load external config proto";
}
parseConfigFile(filename, configFiles);
return true;
}
);
}
void FlagGroup::parseConfigFile(
const std::string& filename,
const std::map<std::string, std::string>& configFiles)
{
const auto& it = configFiles.find(filename);
if (it != configFiles.end())
{
ConfigProto newConfig;
if (!newConfig.ParseFromString(it->second))
Error() << "couldn't load built-in config proto";
config.MergeFrom(newConfig);
}
else
{
std::ifstream f(filename, std::ios::out);
if (f.fail())
Error() << fmt::format("Cannot open '{}': {}", filename, strerror(errno));
std::ostringstream ss;
ss << f.rdbuf();
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
Error() << "couldn't load external config proto";
}
}
void FlagGroup::checkInitialised() const
{
if (!_initialised)

View File

@@ -15,11 +15,15 @@ public:
void parseFlags(int argc, const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&){ return false; });
std::vector<std::string> parseFlagsWithFilenames(int argc, const char* argv[],
std::vector<std::string> parseFlagsWithFilenames(
int argc, const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&){ return false; });
void parseFlagsWithConfigFiles(int argc, const char* argv[],
const std::map<std::string, std::string>& configFiles);
static void parseConfigFile(
const std::string& filename,
const std::map<std::string, std::string>& configFiles);
void addFlag(Flag* flag);
void checkInitialised() const;

View File

@@ -18,7 +18,7 @@ struct TrackDataFlux
unsigned physicalHead;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> sectors;
};
struct TrackFlux
@@ -32,7 +32,7 @@ struct TrackFlux
struct DiskFlux
{
std::vector<std::shared_ptr<const TrackFlux>> tracks;
std::unique_ptr<const Image> image;
std::shared_ptr<const Image> image;
};
#endif

View File

@@ -34,13 +34,13 @@ std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string& filename)
{
static const std::vector<std::pair<std::regex, std::function<void(const std::string&)>>> formats =
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSinkProto*)>>> formats =
{
{ std::regex("^(.*\\.flux)$"), [&](const auto& s) { proto->mutable_fl2()->set_filename(s); }},
{ std::regex("^(.*\\.scp)$"), [&](const auto& s) { proto->mutable_scp()->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)); }},
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
{ 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)); }},
};
for (const auto& it : formats)
@@ -48,7 +48,7 @@ void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string&
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1]);
it.second(match[1], proto);
return;
}
}

View File

@@ -48,15 +48,15 @@ std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::string& filename)
{
static const std::vector<std::pair<std::regex, std::function<void(const std::string&)>>> formats =
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSourceProto*)>>> formats =
{
{ std::regex("^(.*\\.flux)$"), [&](const auto& s) { proto->mutable_fl2()->set_filename(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("^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(); }},
{ std::regex("^drive:(.*)"), [&](const auto& s) { proto->mutable_drive()->set_drive(std::stoi(s)); }},
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
{ std::regex("^(.*\\.cwf)$"), [](auto& s, auto* proto) { proto->mutable_cwf()->set_filename(s); }},
{ 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)); }},
};
for (const auto& it : formats)
@@ -64,7 +64,7 @@ void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::stri
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1]);
it.second(match[1], proto);
return;
}
}

View File

@@ -31,13 +31,22 @@ extern double getCurrentTime();
extern void hexdump(std::ostream& stream, const Bytes& bytes);
extern void hexdumpForSrp16(std::ostream& stream, const Bytes& bytes);
struct ErrorException
{
const std::string message;
};
class Error
{
public:
[[ noreturn ]] ~Error()
Error()
{
_stream << "Error: ";
}
[[ noreturn ]] ~Error() noexcept(false)
{
std::cerr << "Error: " << _stream.str() << std::endl;
exit(1);
throw ErrorException { _stream.str() };
}
template <typename T>

View File

@@ -27,7 +27,7 @@ public:
public:
const_iterator(const wrapped_iterator_t& it): _it(it) {}
const Sector* operator* () { return _it->second.get(); }
std::shared_ptr<const Sector> operator* () { return _it->second; }
void operator++ () { _it++; }
bool operator== (const const_iterator& other) const { return _it == other._it; }
bool operator!= (const const_iterator& other) const { return _it != other._it; }

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "fmt/format.h"
#include "image.h"
#include "logger.h"
#include "proto.h"
#include <algorithm>
#include <iostream>
@@ -12,13 +13,12 @@
class D64ImageReader : public ImageReader
{
public:
D64ImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
D64ImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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";
@@ -26,24 +26,22 @@ public:
uint32_t begin = inputFile.tellg();
inputFile.seekg(0, inputFile.end);
uint32_t end = inputFile.tellg();
uint32_t inputFileSize = (end-begin);
uint32_t inputFileSize = (end - begin);
inputFile.seekg(0, inputFile.beg);
Bytes data;
data.writer() += inputFile;
ByteReader br(data);
Bytes data;
data.writer() += inputFile;
ByteReader br(data);
unsigned numCylinders = 39;
unsigned numHeads = 1;
unsigned numSectors = 0;
unsigned numCylinders = 39;
unsigned numHeads = 1;
unsigned numSectors = 0;
std::cout << "reading D64 image\n"
<< fmt::format("{} cylinders, {} heads\n",
numCylinders, numHeads);
Logger() << fmt::format("D64: reading image with {} cylinders, {} heads", numCylinders, numHeads);
uint32_t offset = 0;
auto sectorsPerTrack = [&](int track) -> int
{
auto sectorsPerTrack = [&](int track) -> int
{
if (track < 17)
return 21;
if (track < 24)
@@ -51,36 +49,37 @@ public:
if (track < 30)
return 18;
return 17;
};
};
std::unique_ptr<Image> image(new Image);
for (int track = 0; track < 40; track++)
{
int numSectors = sectorsPerTrack(track);
int physicalCylinder = track*2;
int numSectors = sectorsPerTrack(track);
int physicalCylinder = track * 2;
for (int head = 0; head < numHeads; head++)
{
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);
{ // still data available sector OK
br.seek(offset);
Bytes payload = br.read(256);
offset += 256;
sector->status = Sector::OK;
sector->logicalTrack = track;
sector->physicalCylinder = physicalCylinder;
sector->physicalCylinder = physicalCylinder;
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
sector->data.writer().append(payload);
}
else
{ //no more data in input file. Write sectors with status: DATA_MISSING
else
{ // no more data in input file. Write sectors with status:
// DATA_MISSING
sector->status = Sector::DATA_MISSING;
sector->logicalTrack = track;
sector->physicalCylinder = physicalCylinder;
sector->physicalCylinder = physicalCylinder;
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = sectorId;
}
@@ -88,14 +87,13 @@ public:
}
}
image->calculateSize();
image->calculateSize();
return image;
}
}
};
std::unique_ptr<ImageReader> ImageReader::createD64ImageReader(const ImageReaderProto& config)
std::unique_ptr<ImageReader> ImageReader::createD64ImageReader(
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new D64ImageReader(config));
}

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -16,18 +17,17 @@
class D88ImageReader : public ImageReader
{
public:
D88ImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
D88ImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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());
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
@@ -35,45 +35,55 @@ public:
std::string diskName = header.slice(0, 0x16);
if (diskName[0])
std::cout << "D88: disk name: " << diskName << "\n";
Logger() << fmt::format("D88: disk name: {}", diskName);
ByteReader headerReader(header);
char mediaFlag = headerReader.seek(0x1b).read_8();
inputFile.seekg( 0, std::ios::end );
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";
Logger() << "D88: found multiple disk images. Only using first";
int trackTableEnd = headerReader.seek(0x20).read_le32();
int trackTableSize = trackTableEnd - 0x20;
Bytes trackTable(trackTableSize);
inputFile.seekg(0x20);
inputFile.read((char*) trackTable.begin(), trackTable.size());
inputFile.read((char*)trackTable.begin(), trackTable.size());
ByteReader trackTableReader(trackTable);
if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET)
std::cout << "D88: overriding configured format\n";
if (config.encoder().format_case() !=
EncoderProto::FormatCase::FORMAT_NOT_SET)
Logger() << "D88: overriding configured format";
auto ibm = config.mutable_encoder()->mutable_ibm();
int physicalStep = 1;
int clockRate = 500;
if (mediaFlag == 0x20) {
std::cout << "D88: high density mode\n";
if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) {
config.mutable_flux_sink()->mutable_drive()->set_high_density(true);
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);
}
} else {
std::cout << "D88: single/double density mode\n";
}
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.flux_sink().dest_case() ==
FluxSinkProto::DestCase::kDrive)
{
config.mutable_flux_sink()->mutable_drive()->set_high_density(
false);
}
}
@@ -81,11 +91,13 @@ public:
for (int track = 0; track < trackTableSize / 4; track++)
{
int trackOffset = trackTableReader.seek(track * 4).read_le32();
if (trackOffset == 0) continue;
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 currentSectorsInTrack =
0xffff; // don't know # of sectors until we read the first one
int trackSectorSize = -1;
int trackMfm = -1;
@@ -94,9 +106,12 @@ public:
trackdata->set_track_length_ms(167);
auto sectors = trackdata->mutable_sectors();
for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack; sectorInTrack++){
for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack;
sectorInTrack++)
{
Bytes sectorHeader(0x10);
inputFile.read((char*) sectorHeader.begin(), sectorHeader.size());
inputFile.read(
(char*)sectorHeader.begin(), sectorHeader.size());
ByteReader sectorHeaderReader(sectorHeader);
int cylinder = sectorHeaderReader.seek(0).read_8();
int head = sectorHeaderReader.seek(1).read_8();
@@ -107,50 +122,70 @@ public:
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
// 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";
Error()
<< "D88: 1.44MB 300rpm formats currently unsupported";
if (fddStatusCode != 0)
Error() << "D88: nonzero fdd status codes are currently unsupported";
if (currentSectorsInTrack == 0xffff) {
Error() << "D88: nonzero fdd status codes are currently "
"unsupported";
if (currentSectorsInTrack == 0xffff)
{
currentSectorsInTrack = sectorsInTrack;
} else if (currentSectorsInTrack != sectorsInTrack) {
}
else if (currentSectorsInTrack != sectorsInTrack)
{
Error() << "D88: mismatched number of sectors in track";
}
if (currentTrackCylinder < 0) {
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) {
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
// this is the first sector we've read, use it settings for
// per-track data
trackdata->set_cylinder(cylinder * physicalStep);
trackdata->set_head(head);
trackdata->set_sector_size(sectorSize);
trackdata->set_use_fm(fm);
if (fm) {
if (fm)
{
trackdata->set_gap_fill_byte(0xffff);
trackdata->set_idam_byte(0xf57e);
trackdata->set_dam_byte(0xf56f);
}
// create timings to approximately match N88-BASIC
if (sectorSize <= 128) {
if (sectorSize <= 128)
{
trackdata->set_gap0(0x1b);
trackdata->set_gap2(0x09);
trackdata->set_gap3(0x1b);
} else if (sectorSize <= 256) {
}
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";
}
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 * physicalStep, head, sectorId);
inputFile.read((char*)data.begin(), data.size());
const auto& sector =
image->put(cylinder * physicalStep, head, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = cylinder;
sector->physicalCylinder = cylinder * physicalStep;
@@ -161,7 +196,8 @@ public:
sectors->add_sector(sectorId);
}
if (physicalStep == 2) {
if (physicalStep == 2)
{
auto trackdata = ibm->add_trackdata();
trackdata->set_clock_rate_khz(clockRate);
trackdata->set_track_length_ms(167);
@@ -170,31 +206,30 @@ public:
image->calculateSize();
const Geometry& geometry = image->getGeometry();
std::cout << fmt::format("D88: read {} tracks, {} sides\n",
geometry.numTracks, geometry.numSides);
Logger() << fmt::format("D88: read {} tracks, {} sides",
geometry.numTracks,
geometry.numSides);
if (!config.has_heads())
{
auto* heads = config.mutable_heads();
heads->set_start(0);
heads->set_end(geometry.numSides - 1);
}
if (!config.has_heads())
{
auto* heads = config.mutable_heads();
heads->set_start(0);
heads->set_end(geometry.numSides - 1);
}
if (!config.has_cylinders())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
}
if (!config.has_cylinders())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
}
return image;
}
};
std::unique_ptr<ImageReader> ImageReader::createD88ImageReader(
const ImageReaderProto& config)
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new D88ImageReader(config));
}

View File

@@ -3,6 +3,7 @@
#include "sector.h"
#include "imagereader/imagereader.h"
#include "image.h"
#include "logger.h"
#include "proto.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
@@ -16,18 +17,17 @@
class DimImageReader : public ImageReader
{
public:
DimImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
DimImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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());
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?";
@@ -38,7 +38,8 @@ public:
int tracks;
int sectorsPerTrack;
int sectorSize;
switch (mediaByte) {
switch (mediaByte)
{
case 0:
tracks = 77;
sectorsPerTrack = 8;
@@ -65,12 +66,12 @@ public:
}
std::unique_ptr<Image> image(new Image);
int trackCount = 0;
int trackCount = 0;
for (int track = 0; track < tracks; track++)
{
if (inputFile.eof())
break;
int physicalCylinder = track;
if (inputFile.eof())
break;
int physicalCylinder = track;
for (int side = 0; side < 2; side++)
{
@@ -81,30 +82,34 @@ public:
for (int sectorId : sectors)
{
Bytes data(sectorSize);
inputFile.read((char*) data.begin(), data.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;
sector->physicalCylinder = physicalCylinder;
sector->logicalSide = sector->physicalHead = side;
sector->logicalSector = sectorId;
sector->data = data;
}
}
trackCount++;
trackCount++;
}
if (config.encoder().format_case() == EncoderProto::FormatCase::FORMAT_NOT_SET)
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) {
switch (mediaByte)
{
case 0x00:
std::cout << "DIM: automatically setting format to 1.2MB (1024 byte sectors)\n";
Logger() << "DIM: automatically setting format to 1.2MB "
"(1024 byte sectors)";
config.mutable_cylinders()->set_end(76);
trackdata->set_track_length_ms(167);
trackdata->set_sector_size(1024);
@@ -112,55 +117,58 @@ public:
sectors->add_sector(i);
break;
case 0x02:
std::cout << "DIM: automatically setting format to 1.2MB (512 byte sectors)\n";
Logger() << "DIM: automatically setting format to 1.2MB "
"(512 byte sectors)";
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";
Logger() << "DIM: automatically setting format to 1.44MB";
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);
Error() << fmt::format(
"DIM: unknown media byte 0x%02x, could not determine "
"write profile automatically",
mediaByte);
break;
}
config.mutable_decoder()->mutable_ibm();
config.mutable_decoder()->mutable_ibm();
}
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);
image->calculateSize();
const Geometry& geometry = image->getGeometry();
Logger() << fmt::format("DIM: read {} tracks, {} sides, {} kB total",
geometry.numTracks,
geometry.numSides,
((int)inputFile.tellg() - 256) / 1024);
if (!config.has_heads())
{
auto* heads = config.mutable_heads();
heads->set_start(0);
heads->set_end(geometry.numSides - 1);
}
if (!config.has_heads())
{
auto* heads = config.mutable_heads();
heads->set_start(0);
heads->set_end(geometry.numSides - 1);
}
if (!config.has_cylinders())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
}
if (!config.has_cylinders())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
}
return image;
}
}
};
std::unique_ptr<ImageReader> ImageReader::createDimImageReader(
const ImageReaderProto& config)
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new DimImageReader(config));
}

View File

@@ -3,6 +3,7 @@
#include "sector.h"
#include "imagereader/imagereader.h"
#include "image.h"
#include "logger.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -12,98 +13,99 @@
class DiskCopyImageReader : public ImageReader
{
public:
DiskCopyImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
DiskCopyImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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 data;
data.writer() += inputFile;
ByteReader br(data);
Bytes data;
data.writer() += inputFile;
ByteReader br(data);
br.seek(1);
std::string label = br.read(data[0]);
br.seek(1);
std::string label = br.read(data[0]);
br.seek(0x40);
uint32_t dataSize = br.read_be32();
br.seek(0x40);
uint32_t dataSize = br.read_be32();
br.seek(0x50);
uint8_t encoding = br.read_8();
uint8_t formatByte = br.read_8();
br.seek(0x50);
uint8_t encoding = br.read_8();
uint8_t formatByte = br.read_8();
unsigned numCylinders = 80;
unsigned numHeads = 2;
unsigned numSectors = 0;
bool mfm = false;
unsigned numCylinders = 80;
unsigned numHeads = 2;
unsigned numSectors = 0;
bool mfm = false;
switch (encoding)
{
case 0: /* GCR CLV 400kB */
numHeads = 1;
break;
switch (encoding)
{
case 0: /* GCR CLV 400kB */
numHeads = 1;
break;
case 1: /* GCR CLV 800kB */
break;
case 1: /* GCR CLV 800kB */
break;
case 2: /* MFM CAV 720kB */
numSectors = 9;
mfm = true;
break;
case 2: /* MFM CAV 720kB */
numSectors = 9;
mfm = true;
break;
case 3: /* MFM CAV 1440kB */
numSectors = 18;
mfm = true;
break;
case 3: /* MFM CAV 1440kB */
numSectors = 18;
mfm = true;
break;
default:
Error() << fmt::format("don't understand DiskCopy disks of type {}", encoding);
}
default:
Error() << fmt::format(
"don't understand DiskCopy disks of type {}", encoding);
}
std::cout << "reading DiskCopy 4.2 image\n"
<< fmt::format("{} cylinders, {} heads; {}; {}\n",
numCylinders, numHeads,
mfm ? "MFM" : "GCR",
label);
Logger() << fmt::format(
"DC42: reading image with {} cylinders, {} heads; {}; {}",
numCylinders,
numHeads,
mfm ? "MFM" : "GCR",
label);
auto sectorsPerTrack = [&](int track) -> int
{
if (mfm)
return numSectors;
auto sectorsPerTrack = [&](int track) -> int
{
if (mfm)
return numSectors;
if (track < 16)
return 12;
if (track < 32)
return 11;
if (track < 48)
return 10;
if (track < 64)
return 9;
return 8;
};
if (track < 16)
return 12;
if (track < 32)
return 11;
if (track < 48)
return 10;
if (track < 64)
return 9;
return 8;
};
uint32_t dataPtr = 0x54;
uint32_t tagPtr = dataPtr + dataSize;
uint32_t dataPtr = 0x54;
uint32_t tagPtr = dataPtr + dataSize;
std::unique_ptr<Image> image(new Image);
for (int track = 0; track < numCylinders; track++)
{
int numSectors = sectorsPerTrack(track);
int numSectors = sectorsPerTrack(track);
for (int head = 0; head < numHeads; head++)
{
for (int sectorId = 0; sectorId < numSectors; sectorId++)
{
br.seek(dataPtr);
Bytes payload = br.read(512);
dataPtr += 512;
br.seek(dataPtr);
Bytes payload = br.read(512);
dataPtr += 512;
br.seek(tagPtr);
Bytes tag = br.read(12);
tagPtr += 12;
br.seek(tagPtr);
Bytes tag = br.read(12);
tagPtr += 12;
const auto& sector = image->put(track, head, sectorId);
sector->status = Sector::OK;
@@ -115,21 +117,17 @@ public:
}
}
image->setGeometry({
.numTracks = numCylinders,
.numSides = numHeads,
.numSectors = 12,
.sectorSize = 512 + 12,
.irregular = true
});
image->setGeometry({.numTracks = numCylinders,
.numSides = numHeads,
.numSectors = 12,
.sectorSize = 512 + 12,
.irregular = true});
return image;
}
}
};
std::unique_ptr<ImageReader> ImageReader::createDiskCopyImageReader(
const ImageReaderProto& config)
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new DiskCopyImageReader(config));
}

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -16,24 +17,23 @@
class FdiImageReader : public ImageReader
{
public:
FdiImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
FdiImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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());
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
// 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();
@@ -45,12 +45,12 @@ public:
inputFile.seekg(headerSize);
std::unique_ptr<Image> image(new Image);
int trackCount = 0;
int trackCount = 0;
for (int track = 0; track < tracks; track++)
{
if (inputFile.eof())
break;
int physicalCylinder = track;
if (inputFile.eof())
break;
int physicalCylinder = track;
for (int side = 0; side < sides; side++)
{
@@ -61,30 +61,34 @@ public:
for (int sectorId : sectors)
{
Bytes data(sectorSize);
inputFile.read((char*) data.begin(), data.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;
sector->physicalCylinder = physicalCylinder;
sector->logicalSide = sector->physicalHead = side;
sector->logicalSector = sectorId;
sector->data = data;
}
}
trackCount++;
trackCount++;
}
if (config.encoder().format_case() == EncoderProto::FormatCase::FORMAT_NOT_SET)
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 (fddType) {
switch (fddType)
{
case 0x90:
std::cout << "FDI: automatically setting format to 1.2MB (1024 byte sectors)\n";
Logger() << "FDI: automatically setting format to 1.2MB "
"(1024 byte sectors)";
config.mutable_cylinders()->set_end(76);
trackdata->set_track_length_ms(167);
trackdata->set_sector_size(1024);
@@ -92,46 +96,48 @@ public:
sectors->add_sector(i);
break;
case 0x30:
std::cout << "FDI: automatically setting format to 1.44MB\n";
Logger() << "FDI: automatically setting format to 1.44MB";
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("FDI: unknown fdd type 0x%02x, could not determine write profile automatically", fddType);
Error() << fmt::format(
"FDI: unknown fdd type 0x{:2x}, could not determine "
"write profile automatically",
fddType);
break;
}
}
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);
image->calculateSize();
const Geometry& geometry = image->getGeometry();
Logger() << fmt::format("FDI: read {} tracks, {} sides, {} kB total",
geometry.numTracks,
geometry.numSides,
((int)inputFile.tellg() - headerSize) / 1024);
if (!config.has_heads())
{
auto* heads = config.mutable_heads();
heads->set_start(0);
heads->set_end(geometry.numSides - 1);
}
if (!config.has_heads())
{
auto* heads = config.mutable_heads();
heads->set_start(0);
heads->set_end(geometry.numSides - 1);
}
if (!config.has_cylinders())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
}
if (!config.has_cylinders())
{
auto* cylinders = config.mutable_cylinders();
cylinders->set_start(0);
cylinders->set_end(geometry.numTracks - 1);
}
return image;
}
}
};
std::unique_ptr<ImageReader> ImageReader::createFdiImageReader(
const ImageReaderProto& config)
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new FdiImageReader(config));
}

View File

@@ -55,32 +55,32 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::string& filename)
{
static const std::map<std::string, std::function<void(void)>> formats =
static const std::map<std::string, std::function<void(ImageReaderProto*)>> formats =
{
{".adf", [&]() { proto->mutable_img(); }},
{".d64", [&]() { proto->mutable_d64(); }},
{".d81", [&]() { proto->mutable_img(); }},
{".d88", [&]() { proto->mutable_d88(); }},
{".dim", [&]() { proto->mutable_dim(); }},
{".diskcopy", [&]() { proto->mutable_diskcopy(); }},
{".dsk", [&]() { proto->mutable_img(); }},
{".fdi", [&]() { proto->mutable_fdi(); }},
{".imd", [&]() { proto->mutable_imd(); }},
{".img", [&]() { proto->mutable_img(); }},
{".jv3", [&]() { proto->mutable_jv3(); }},
{".nfd", [&]() { proto->mutable_nfd(); }},
{".nsi", [&]() { proto->mutable_nsi(); }},
{".st", [&]() { proto->mutable_img(); }},
{".td0", [&]() { proto->mutable_td0(); }},
{".vgi", [&]() { proto->mutable_img(); }},
{".xdf", [&]() { proto->mutable_img(); }},
{".adf", [](auto* proto) { proto->mutable_img(); }},
{".d64", [](auto* proto) { proto->mutable_d64(); }},
{".d81", [](auto* proto) { proto->mutable_img(); }},
{".d88", [](auto* proto) { proto->mutable_d88(); }},
{".dim", [](auto* proto) { proto->mutable_dim(); }},
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
{".dsk", [](auto* proto) { proto->mutable_img(); }},
{".fdi", [](auto* proto) { proto->mutable_fdi(); }},
{".imd", [](auto* proto) { proto->mutable_imd(); }},
{".img", [](auto* proto) { proto->mutable_img(); }},
{".jv3", [](auto* proto) { proto->mutable_jv3(); }},
{".nfd", [](auto* proto) { proto->mutable_nfd(); }},
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
{".st", [](auto* proto) { proto->mutable_img(); }},
{".td0", [](auto* proto) { proto->mutable_td0(); }},
{".vgi", [](auto* proto) { proto->mutable_img(); }},
{".xdf", [](auto* proto) { proto->mutable_img(); }},
};
for (const auto& it : formats)
{
if (endsWith(filename, it.first))
{
it.second();
it.second(proto);
proto->set_filename(filename);
return;
}

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -140,9 +141,7 @@ public:
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);
Logger() << fmt::format("IMD: comment: {}", comment);
//first read header
for (;;)
@@ -237,7 +236,7 @@ public:
size_t headSize = header.numSectors * sectorSize;
size_t trackSize = headSize * (header.Head + 1);
std::cout << fmt::format("IMD: {} tracks, {} heads; {}; {} kbps; {} sectors; sectorsize {};\n"
Logger() << fmt::format("IMD: {} tracks, {} heads; {}; {} kbps; {} sectors; sectorsize {};"
" sectormap {}; {} kB total\n",
header.track, header.Head + 1,
mfm ? "MFM" : "FM",

View File

@@ -3,6 +3,7 @@
#include "sector.h"
#include "imagereader/imagereader.h"
#include "image.h"
#include "logger.h"
#include "lib/config.pb.h"
#include "imginputoutpututils.h"
#include "fmt/format.h"
@@ -13,85 +14,91 @@
class ImgImageReader : public ImageReader
{
public:
ImgImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
ImgImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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";
if (!_config.img().tracks() || !_config.img().sides())
Error() << "IMG: bad configuration; did you remember to set the tracks, sides and trackdata fields?";
if (!_config.img().tracks() || !_config.img().sides())
Error() << "IMG: bad configuration; did you remember to set the "
"tracks, sides and trackdata fields?";
std::unique_ptr<Image> image(new Image);
for (const auto& p : getTrackOrdering(_config.img()))
{
int track = p.first;
int side = p.second;
for (const auto& p : getTrackOrdering(_config.img()))
{
int track = p.first;
int side = p.second;
if (inputFile.eof())
break;
int physicalCylinder = track * _config.img().physical_step() + _config.img().physical_offset();
if (inputFile.eof())
break;
int physicalCylinder = track * _config.img().physical_step() +
_config.img().physical_offset();
ImgInputOutputProto::TrackdataProto trackdata;
getTrackFormat(_config.img(), trackdata, track, side);
ImgInputOutputProto::TrackdataProto trackdata;
getTrackFormat(_config.img(), trackdata, track, side);
for (int sectorId : getSectors(trackdata))
{
Bytes data(trackdata.sector_size());
inputFile.read((char*) data.begin(), data.size());
for (int sectorId : getSectors(trackdata))
{
Bytes data(trackdata.sector_size());
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;
}
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;
}
}
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);
image->calculateSize();
const Geometry& geometry = image->getGeometry();
Logger() << fmt::format("IMG: read {} tracks, {} sides, {} kB total",
geometry.numTracks,
geometry.numSides,
inputFile.tellg() / 1024);
return image;
}
}
std::vector<unsigned> getSectors(const ImgInputOutputProto::TrackdataProto& trackdata)
{
std::vector<unsigned> sectors;
switch (trackdata.sectors_oneof_case())
{
case ImgInputOutputProto::TrackdataProto::SectorsOneofCase::kSectors:
{
for (int sectorId : trackdata.sectors().sector())
sectors.push_back(sectorId);
break;
}
std::vector<unsigned> getSectors(
const ImgInputOutputProto::TrackdataProto& trackdata)
{
std::vector<unsigned> sectors;
switch (trackdata.sectors_oneof_case())
{
case ImgInputOutputProto::TrackdataProto::SectorsOneofCase::
kSectors:
{
for (int sectorId : trackdata.sectors().sector())
sectors.push_back(sectorId);
break;
}
case ImgInputOutputProto::TrackdataProto::SectorsOneofCase::kSectorRange:
{
int sectorId = trackdata.sector_range().start_sector();
for (int i=0; i<trackdata.sector_range().sector_count(); i++)
sectors.push_back(sectorId + i);
break;
}
case ImgInputOutputProto::TrackdataProto::SectorsOneofCase::
kSectorRange:
{
int sectorId = trackdata.sector_range().start_sector();
for (int i = 0; i < trackdata.sector_range().sector_count();
i++)
sectors.push_back(sectorId + i);
break;
}
default:
Error() << "no list of sectors provided in track format";
}
return sectors;
}
default:
Error() << "no list of sectors provided in track format";
}
return sectors;
}
};
std::unique_ptr<ImageReader> ImageReader::createImgImageReader(
const ImageReaderProto& config)
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new ImgImageReader(config));
}

View File

@@ -3,15 +3,17 @@
#include "sector.h"
#include "imagereader/imagereader.h"
#include "image.h"
#include "logger.h"
#include "fmt/format.h"
#include "lib/config.pb.h"
#include <algorithm>
#include <iostream>
#include <fstream>
/* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, which may appear
* in any order, followed by the same again for more sectors. To find the second data block
* you need to know the size of the first data block, which requires parsing it.
/* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors,
* which may appear in any order, followed by the same again for more sectors.
* To find the second data block you need to know the size of the first data
* block, which requires parsing it.
*
* https://www.tim-mann.org/trs80/dskconfig.html
*
@@ -33,108 +35,117 @@
struct SectorHeader
{
uint8_t track;
uint8_t sector;
uint8_t flags;
uint8_t track;
uint8_t sector;
uint8_t flags;
};
#define JV3_DENSITY 0x80 /* 1=dden, 0=sden */
#define JV3_DAM 0x60 /* data address mark code; see below */
#define JV3_SIDE 0x10 /* 0=side 0, 1=side 1 */
#define JV3_ERROR 0x08 /* 0=ok, 1=CRC error */
#define JV3_NONIBM 0x04 /* 0=normal, 1=short */
#define JV3_SIZE 0x03 /* in used sectors: 0=256,1=128,2=1024,3=512
in free sectors: 0=512,1=1024,2=128,3=256 */
#define JV3_DENSITY 0x80 /* 1=dden, 0=sden */
#define JV3_DAM 0x60 /* data address mark code; see below */
#define JV3_SIDE 0x10 /* 0=side 0, 1=side 1 */
#define JV3_ERROR 0x08 /* 0=ok, 1=CRC error */
#define JV3_NONIBM 0x04 /* 0=normal, 1=short */
#define JV3_SIZE \
0x03 /* in used sectors: 0=256,1=128,2=1024,3=512 \
in free sectors: 0=512,1=1024,2=128,3=256 */
#define JV3_FREE 0xFF /* in track and sector fields of free sectors */
#define JV3_FREEF 0xFC /* in flags field, or'd with size code */
#define JV3_FREE 0xFF /* in track and sector fields of free sectors */
#define JV3_FREEF 0xFC /* in flags field, or'd with size code */
static unsigned getSectorSize(uint8_t flags)
{
if ((flags & JV3_FREEF) == JV3_FREEF)
{
switch (flags & JV3_SIZE)
{
case 0: return 512;
case 1: return 1024;
case 2: return 128;
case 3: return 256;
}
}
else
{
switch (flags & JV3_SIZE)
{
case 0: return 256;
case 1: return 128;
case 2: return 1024;
case 3: return 512;
}
}
Error() << "not reachable";
if ((flags & JV3_FREEF) == JV3_FREEF)
{
switch (flags & JV3_SIZE)
{
case 0:
return 512;
case 1:
return 1024;
case 2:
return 128;
case 3:
return 256;
}
}
else
{
switch (flags & JV3_SIZE)
{
case 0:
return 256;
case 1:
return 128;
case 2:
return 1024;
case 3:
return 512;
}
}
Error() << "not reachable";
}
class Jv3ImageReader : public ImageReader
{
public:
Jv3ImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
Jv3ImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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";
inputFile.seekg( 0, std::ios::end);
unsigned inputFileSize = inputFile.tellg();
unsigned headerPtr = 0;
inputFile.seekg(0, std::ios::end);
unsigned inputFileSize = inputFile.tellg();
unsigned headerPtr = 0;
std::unique_ptr<Image> image(new Image);
for (;;)
{
unsigned dataPtr = headerPtr + 2901*3 + 1;
if (dataPtr >= inputFileSize)
break;
for (;;)
{
unsigned dataPtr = headerPtr + 2901 * 3 + 1;
if (dataPtr >= inputFileSize)
break;
for (unsigned i=0; i<2901; i++)
{
SectorHeader header = {0, 0, 0xff};
inputFile.seekg(headerPtr);
inputFile.read((char*) &header, 3);
unsigned sectorSize = getSectorSize(header.flags);
if ((header.flags & JV3_FREEF) != JV3_FREEF)
{
Bytes data(sectorSize);
inputFile.seekg(dataPtr);
inputFile.read((char*) data.begin(), sectorSize);
for (unsigned i = 0; i < 2901; i++)
{
SectorHeader header = {0, 0, 0xff};
inputFile.seekg(headerPtr);
inputFile.read((char*)&header, 3);
unsigned sectorSize = getSectorSize(header.flags);
if ((header.flags & JV3_FREEF) != JV3_FREEF)
{
Bytes data(sectorSize);
inputFile.seekg(dataPtr);
inputFile.read((char*)data.begin(), sectorSize);
unsigned head = !!(header.flags & JV3_SIDE);
const auto& sector = image->put(header.track, head, header.sector);
unsigned head = !!(header.flags & JV3_SIDE);
const auto& sector =
image->put(header.track, head, header.sector);
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalCylinder = header.track;
sector->logicalTrack = sector->physicalCylinder =
header.track;
sector->logicalSide = sector->physicalHead = head;
sector->logicalSector = header.sector;
sector->data = data;
}
}
headerPtr += 3;
dataPtr += sectorSize;
}
headerPtr += 3;
dataPtr += sectorSize;
}
/* dataPtr is now pointing at the beginning of the next chunk. */
/* dataPtr is now pointing at the beginning of the next chunk. */
headerPtr = dataPtr;
}
headerPtr = dataPtr;
}
image->calculateSize();
image->calculateSize();
return image;
}
}
};
std::unique_ptr<ImageReader> ImageReader::createJv3ImageReader(const ImageReaderProto& config)
std::unique_ptr<ImageReader> ImageReader::createJv3ImageReader(
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new Jv3ImageReader(config));
}

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "proto.h"
#include "logger.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
@@ -16,44 +17,48 @@
class NFDImageReader : public ImageReader
{
public:
NFDImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
NFDImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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 fileId(14); // read first entry of track table as well
inputFile.read((char*) fileId.begin(), fileId.size());
inputFile.read((char*)fileId.begin(), fileId.size());
if (fileId == Bytes("T98FDDIMAGE.R1")) {
if (fileId == Bytes("T98FDDIMAGE.R1"))
{
Error() << "NFD: r1 images are not currently supported";
}
if (fileId != Bytes("T98FDDIMAGE.R0")) {
if (fileId != Bytes("T98FDDIMAGE.R0"))
{
Error() << "NFD: could not find NFD header";
}
Bytes header(0x10a10);
inputFile.seekg( 0, std::ios::beg );
inputFile.read((char*) header.begin(), header.size());
inputFile.seekg(0, std::ios::beg);
inputFile.read((char*)header.begin(), header.size());
ByteReader headerReader(header);
char heads = headerReader.seek(0x115).read_8();
if (heads != 2) {
if (heads != 2)
{
Error() << "NFD: unsupported number of heads";
}
if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET)
std::cout << "NFD: overriding configured format";
if (config.encoder().format_case() !=
EncoderProto::FormatCase::FORMAT_NOT_SET)
Logger() << "NFD: overriding configured format";
auto ibm = config.mutable_encoder()->mutable_ibm();
config.mutable_cylinders()->set_end(0);
std::cout << "NFD: HD 1.2MB mode\n";
if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) {
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);
}
@@ -68,7 +73,8 @@ public:
int currentTrackHead = -1;
int trackSectorSize = -1;
for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++){
for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++)
{
headerReader.seek(0x120 + track * 26 * 16 + sectorInTrack * 16);
int cylinder = headerReader.read_8();
int head = headerReader.read_8();
@@ -83,41 +89,58 @@ public:
if (ddam != 0)
Error() << "NFD: nonzero ddam currently unsupported";
if (status != 0)
Error() << "NFD: nonzero fdd status codes are currently unsupported";
if (currentTrackCylinder < 0) {
Error() << "NFD: nonzero fdd status codes are currently "
"unsupported";
if (currentTrackCylinder < 0)
{
currentTrackCylinder = cylinder;
currentTrackHead = head;
} else if (currentTrackCylinder != cylinder) {
Error() << "NFD: all sectors in a track must belong to the same cylinder";
} else if (currentTrackHead != head) {
Error() << "NFD: all sectors in a track must belong to the same head";
}
if (trackSectorSize < 0) {
else if (currentTrackCylinder != cylinder)
{
Error() << "NFD: all sectors in a track must belong to the "
"same cylinder";
}
else if (currentTrackHead != head)
{
Error() << "NFD: all sectors in a track must belong to the "
"same head";
}
if (trackSectorSize < 0)
{
trackSectorSize = sectorSize;
// this is the first sector we've read, use it settings for per-track data
// 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(!mfm);
if (!mfm) {
if (!mfm)
{
trackdata->set_gap_fill_byte(0xffff);
trackdata->set_idam_byte(0xf57e);
trackdata->set_dam_byte(0xf56f);
}
// create timings to approximately match N88-BASIC
if (sectorSize <= 128) {
if (sectorSize <= 128)
{
trackdata->set_gap0(0x1b);
trackdata->set_gap2(0x09);
trackdata->set_gap3(0x1b);
} else if (sectorSize <= 256) {
}
else if (sectorSize <= 256)
{
trackdata->set_gap0(0x36);
trackdata->set_gap3(0x36);
}
} else if (trackSectorSize != sectorSize) {
Error() << "NFD: multiple sector sizes per track are currently unsupported";
}
else if (trackSectorSize != sectorSize)
{
Error() << "NFD: multiple sector sizes per track are "
"currently unsupported";
}
Bytes data(sectorSize);
inputFile.read((char*) data.begin(), data.size());
inputFile.read((char*)data.begin(), data.size());
const auto& sector = image->put(cylinder, head, sectorId);
sector->status = Sector::OK;
sector->logicalTrack = cylinder;
@@ -134,16 +157,15 @@ public:
image->calculateSize();
const Geometry& geometry = image->getGeometry();
std::cout << fmt::format("NFD: read {} tracks, {} sides\n",
geometry.numTracks, geometry.numSides);
Logger() << fmt::format("NFD: read {} tracks, {} sides",
geometry.numTracks,
geometry.numSides);
return image;
}
};
std::unique_ptr<ImageReader> ImageReader::createNFDImageReader(
const ImageReaderProto& config)
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new NFDImageReader(config));
}

View File

@@ -6,6 +6,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "fmt/format.h"
#include "logger.h"
#include "lib/imagereader/imagereader.pb.h"
#include <algorithm>
#include <iostream>
@@ -14,55 +15,59 @@
class NsiImageReader : public ImageReader
{
public:
NsiImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
NsiImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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";
const auto begin = inputFile.tellg();
inputFile.seekg(0, std::ios::end);
const auto end = inputFile.tellg();
const auto fsize = (end - begin);
const auto begin = inputFile.tellg();
inputFile.seekg(0, std::ios::end);
const auto end = inputFile.tellg();
const auto fsize = (end - begin);
std::cout << "NSI: Autodetecting geometry based on file size: " << fsize << std::endl;
Logger() << fmt::format(
"NSI: Autodetecting geometry based on file size: {}", fsize);
unsigned numCylinders = 35;
unsigned numSectors = 10;
unsigned numHeads = 2;
unsigned sectorSize = 512;
unsigned numCylinders = 35;
unsigned numSectors = 10;
unsigned numHeads = 2;
unsigned sectorSize = 512;
switch (fsize) {
case 358400:
numHeads = 2;
sectorSize = 512;
break;
switch (fsize)
{
case 358400:
numHeads = 2;
sectorSize = 512;
break;
case 179200:
numHeads = 1;
sectorSize = 512;
break;
case 89600:
numHeads = 1;
sectorSize = 256;
break;
case 179200:
numHeads = 1;
sectorSize = 512;
break;
default:
Error() << "NSI: unknown file size";
}
case 89600:
numHeads = 1;
sectorSize = 256;
break;
default:
Error() << "NSI: unknown file size";
}
size_t trackSize = numSectors * sectorSize;
std::cout << fmt::format("reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total",
numCylinders, numHeads,
numSectors, sectorSize,
numCylinders * numHeads * trackSize / 1024)
<< std::endl;
Logger() << fmt::format(
"reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} "
"kB total",
numCylinders,
numHeads,
numSectors,
sectorSize,
numCylinders * numHeads * trackSize / 1024);
std::unique_ptr<Image> image(new Image);
unsigned sectorFileOffset;
@@ -73,19 +78,24 @@ public:
{
for (unsigned sectorId = 0; sectorId < numSectors; sectorId++)
{
if (head == 0) { /* Head 0 is from track 0-34 */
sectorFileOffset = track * trackSize + sectorId * sectorSize;
if (head == 0)
{ /* Head 0 is from track 0-34 */
sectorFileOffset =
track * trackSize + sectorId * sectorSize;
}
else { /* Head 1 is from track 70-35 */
sectorFileOffset = (trackSize * numCylinders) + /* Skip over side 0 */
else
{ /* Head 1 is from track 70-35 */
sectorFileOffset =
(trackSize * numCylinders) + /* Skip over side 0 */
((numCylinders - track - 1) * trackSize) +
(sectorId * sectorSize); /* Sector offset from beginning of track. */
(sectorId * sectorSize); /* Sector offset from
beginning of track. */
}
inputFile.seekg(sectorFileOffset, std::ios::beg);
Bytes data(sectorSize);
inputFile.read((char*) data.begin(), sectorSize);
inputFile.read((char*)data.begin(), sectorSize);
const auto& sector = image->put(track, head, sectorId);
sector->status = Sector::OK;
@@ -97,19 +107,16 @@ public:
}
}
image->setGeometry({
.numTracks = numCylinders,
.numSides = numHeads,
.numSectors = numSectors,
.sectorSize = sectorSize
});
image->setGeometry({.numTracks = numCylinders,
.numSides = numHeads,
.numSectors = numSectors,
.sectorSize = sectorSize});
return image;
}
}
};
std::unique_ptr<ImageReader> ImageReader::createNsiImageReader(
const ImageReaderProto& config)
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new NsiImageReader(config));
}

View File

@@ -4,6 +4,7 @@
#include "imagereader/imagereader.h"
#include "image.h"
#include "crc.h"
#include "logger.h"
#include "fmt/format.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
@@ -31,175 +32,182 @@
enum
{
TD0_ENCODING_RAW = 0,
TD0_ENCODING_REPEATED = 1,
TD0_ENCODING_RLE = 2,
TD0_ENCODING_RAW = 0,
TD0_ENCODING_REPEATED = 1,
TD0_ENCODING_RLE = 2,
TD0_FLAG_DUPLICATE = 0x01,
TD0_FLAG_CRC_ERROR = 0x02,
TD0_FLAG_DELETED = 0x04,
TD0_FLAG_SKIPPED = 0x10,
TD0_FLAG_IDNODATA = 0x20,
TD0_FLAG_DATANOID = 0x40,
TD0_FLAG_DUPLICATE = 0x01,
TD0_FLAG_CRC_ERROR = 0x02,
TD0_FLAG_DELETED = 0x04,
TD0_FLAG_SKIPPED = 0x10,
TD0_FLAG_IDNODATA = 0x20,
TD0_FLAG_DATANOID = 0x40,
};
class Td0ImageReader : public ImageReader
{
public:
Td0ImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
Td0ImageReader(const ImageReaderProto& config): ImageReader(config) {}
std::unique_ptr<Image> readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
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 input;
input.writer() += inputFile;
ByteReader br(input);
Bytes input;
input.writer() += inputFile;
ByteReader br(input);
uint16_t signature = br.read_be16();
br.skip(2); /* sequence and checksequence */
uint8_t version = br.read_8();
br.skip(2); /* data rate, drive type */
uint8_t stepping = br.read_8();
br.skip(1); /* sparse flag */
uint8_t sides = (br.read_8() == 1) ? 1 : 2;
uint16_t headerCrc = br.read_le16();
uint16_t signature = br.read_be16();
br.skip(2); /* sequence and checksequence */
uint8_t version = br.read_8();
br.skip(2); /* data rate, drive type */
uint8_t stepping = br.read_8();
br.skip(1); /* sparse flag */
uint8_t sides = (br.read_8() == 1) ? 1 : 2;
uint16_t headerCrc = br.read_le16();
uint16_t gotCrc = crc16(0xa097, 0, input.slice(0, 10));
if (gotCrc != headerCrc)
Error() << "TD0: header checksum mismatch";
if (signature != 0x5444)
Error() << "TD0: unsupported file type (only uncompressed files are supported for now)";
uint16_t gotCrc = crc16(0xa097, 0, input.slice(0, 10));
if (gotCrc != headerCrc)
Error() << "TD0: header checksum mismatch";
if (signature != 0x5444)
Error() << "TD0: unsupported file type (only uncompressed files "
"are supported for now)";
std::string comment = "(no comment)";
if (stepping & 0x80)
{
/* Comment block */
std::string comment = "(no comment)";
if (stepping & 0x80)
{
/* Comment block */
br.skip(2); /* comment CRC */
uint16_t length = br.read_le16();
br.skip(6); /* timestamp */
comment = br.read(length);
std::replace(comment.begin(), comment.end(), '\0', '\n');
br.skip(2); /* comment CRC */
uint16_t length = br.read_le16();
br.skip(6); /* timestamp */
comment = br.read(length);
std::replace(comment.begin(), comment.end(), '\0', '\n');
/* Strip trailing newlines */
/* Strip trailing newlines */
auto nl = std::find_if(comment.rbegin(), comment.rend(),
[](unsigned char ch) { return !std::isspace(ch); });
comment.erase(nl.base(), comment.end());
}
auto nl = std::find_if(comment.rbegin(),
comment.rend(),
[](unsigned char ch)
{
return !std::isspace(ch);
});
comment.erase(nl.base(), comment.end());
}
std::cout << fmt::format("TD0: TeleDisk {}.{}: {}\n",
version / 10, version % 10, comment);
Logger() << fmt::format(
"TD0: TeleDisk {}.{}: {}", version / 10, version % 10, comment);
unsigned totalSize = 0;
unsigned totalSize = 0;
std::unique_ptr<Image> image(new Image);
for (;;)
{
/* Read track header */
for (;;)
{
/* Read track header */
uint8_t sectorCount = br.read_8();
if (sectorCount == 0xff)
break;
uint8_t sectorCount = br.read_8();
if (sectorCount == 0xff)
break;
uint8_t physicalCylinder = br.read_8();
uint8_t physicalHead = br.read_8() & 1;
br.skip(1); /* crc */
uint8_t physicalCylinder = br.read_8();
uint8_t physicalHead = br.read_8() & 1;
br.skip(1); /* crc */
for (int i = 0; i < sectorCount; i++)
{
/* Read sector */
for (int i = 0; i < sectorCount; i++)
{
/* Read sector */
uint8_t logicalTrack = br.read_8();
uint8_t logicalSide = br.read_8();
uint8_t sectorId = br.read_8();
uint8_t sectorSizeEncoded = br.read_8();
unsigned sectorSize = 128<<sectorSizeEncoded;
uint8_t flags = br.read_8();
br.skip(1); /* CRC */
uint8_t logicalTrack = br.read_8();
uint8_t logicalSide = br.read_8();
uint8_t sectorId = br.read_8();
uint8_t sectorSizeEncoded = br.read_8();
unsigned sectorSize = 128 << sectorSizeEncoded;
uint8_t flags = br.read_8();
br.skip(1); /* CRC */
uint16_t dataSize = br.read_le16();
Bytes encodedData = br.read(dataSize);
ByteReader bre(encodedData);
uint8_t encoding = bre.read_8();
uint16_t dataSize = br.read_le16();
Bytes encodedData = br.read(dataSize);
ByteReader bre(encodedData);
uint8_t encoding = bre.read_8();
Bytes data;
if (!(flags & (TD0_FLAG_SKIPPED|TD0_FLAG_IDNODATA)))
{
switch (encoding)
{
case TD0_ENCODING_RAW:
data = encodedData.slice(1);
break;
Bytes data;
if (!(flags & (TD0_FLAG_SKIPPED | TD0_FLAG_IDNODATA)))
{
switch (encoding)
{
case TD0_ENCODING_RAW:
data = encodedData.slice(1);
break;
case TD0_ENCODING_REPEATED:
{
ByteWriter bw(data);
while (!bre.eof())
{
uint16_t pattern = bre.read_le16();
uint16_t count = bre.read_le16();
while (count--)
bw.write_le16(pattern);
}
break;
}
case TD0_ENCODING_REPEATED:
{
ByteWriter bw(data);
while (!bre.eof())
{
uint16_t pattern = bre.read_le16();
uint16_t count = bre.read_le16();
while (count--)
bw.write_le16(pattern);
}
break;
}
case TD0_ENCODING_RLE:
{
ByteWriter bw(data);
while (!bre.eof())
{
uint8_t length = bre.read_8()*2;
if (length == 0)
{
/* Literal block */
case TD0_ENCODING_RLE:
{
ByteWriter bw(data);
while (!bre.eof())
{
uint8_t length = bre.read_8() * 2;
if (length == 0)
{
/* Literal block */
length = bre.read_8();
bw += bre.read(length);
}
else
{
/* Repeated block */
length = bre.read_8();
bw += bre.read(length);
}
else
{
/* Repeated block */
uint8_t count = bre.read_8();
Bytes b = bre.read(length);
while (count--)
bw += b;
}
}
break;
}
}
}
uint8_t count = bre.read_8();
Bytes b = bre.read(length);
while (count--)
bw += b;
}
}
break;
}
}
}
const auto& sector = image->put(logicalTrack, logicalSide, sectorId);
sector->status = Sector::OK;
sector->physicalCylinder = physicalCylinder;
sector->physicalHead = physicalHead;
sector->data = data.slice(0, sectorSize);
totalSize += sectorSize;
}
}
const auto& sector =
image->put(logicalTrack, logicalSide, sectorId);
sector->status = Sector::OK;
sector->physicalCylinder = physicalCylinder;
sector->physicalHead = physicalHead;
sector->data = data.slice(0, sectorSize);
totalSize += sectorSize;
}
}
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,
totalSize / 1024);
image->calculateSize();
const Geometry& geometry = image->getGeometry();
Logger() << fmt::format(
"TD0: found {} tracks, {} sides, {} sectors, {} bytes per sector, "
"{} kB total",
geometry.numTracks,
geometry.numSides,
geometry.numSectors,
geometry.sectorSize,
totalSize / 1024);
return image;
}
}
};
std::unique_ptr<ImageReader> ImageReader::createTd0ImageReader(const ImageReaderProto& config)
std::unique_ptr<ImageReader> ImageReader::createTd0ImageReader(
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new Td0ImageReader(config));
}

View File

@@ -5,6 +5,7 @@
#include "fmt/format.h"
#include "image.h"
#include "ldbs.h"
#include "logger.h"
#include "lib/config.pb.h"
#include <algorithm>
#include <iostream>
@@ -30,7 +31,7 @@ public:
void writeImage(const Image& image)
{
std::cout << "writing D64 triangular image\n";
Logger() << "D64: writing triangular image";
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);
if (!outputFile.is_open())

View File

@@ -5,6 +5,7 @@
#include "fmt/format.h"
#include "ldbs.h"
#include "image.h"
#include "logger.h"
#include "lib/config.pb.h"
#include <algorithm>
#include <iostream>
@@ -52,8 +53,8 @@ public:
Error() << "this image is not compatible with the DiskCopy 4.2 format";
}
std::cout << "writing DiskCopy 4.2 image\n"
<< fmt::format("{} tracks, {} sides, {} sectors, {} bytes per sector; {}\n",
Logger() << "DC42: writing DiskCopy 4.2 image"
<< fmt::format("DC42: {} tracks, {} sides, {} sectors, {} bytes per sector; {}",
geometry.numTracks, geometry.numSides, geometry.numSectors, geometry.sectorSize,
mfm ? "MFM" : "GCR");

View File

@@ -40,27 +40,27 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config)
void ImageWriter::updateConfigForFilename(ImageWriterProto* proto, const std::string& filename)
{
static const std::map<std::string, std::function<void(void)>> formats =
static const std::map<std::string, std::function<void(ImageWriterProto*)>> formats =
{
{".adf", [&]() { proto->mutable_img(); }},
{".d64", [&]() { proto->mutable_d64(); }},
{".d81", [&]() { proto->mutable_img(); }},
{".diskcopy", [&]() { proto->mutable_diskcopy(); }},
{".dsk", [&]() { proto->mutable_img(); }},
{".img", [&]() { proto->mutable_img(); }},
{".ldbs", [&]() { proto->mutable_ldbs(); }},
{".nsi", [&]() { proto->mutable_nsi(); }},
{".raw", [&]() { proto->mutable_raw(); }},
{".st", [&]() { proto->mutable_img(); }},
{".vgi", [&]() { proto->mutable_img(); }},
{".xdf", [&]() { proto->mutable_img(); }},
{".adf", [](auto* proto) { proto->mutable_img(); }},
{".d64", [](auto* proto) { proto->mutable_d64(); }},
{".d81", [](auto* proto) { proto->mutable_img(); }},
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
{".dsk", [](auto* proto) { proto->mutable_img(); }},
{".img", [](auto* proto) { proto->mutable_img(); }},
{".ldbs", [](auto* proto) { proto->mutable_ldbs(); }},
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
{".raw", [](auto* proto) { proto->mutable_raw(); }},
{".st", [](auto* proto) { proto->mutable_img(); }},
{".vgi", [](auto* proto) { proto->mutable_img(); }},
{".xdf", [](auto* proto) { proto->mutable_img(); }},
};
for (const auto& it : formats)
{
if (endsWith(filename, it.first))
{
it.second();
it.second(proto);
proto->set_filename(filename);
return;
}

View File

@@ -6,6 +6,7 @@
#include "lib/config.pb.h"
#include "imginputoutpututils.h"
#include "fmt/format.h"
#include "logger.h"
#include <algorithm>
#include <iostream>
#include <fstream>
@@ -56,7 +57,7 @@ public:
}
}
std::cout << fmt::format("IMG: wrote {} tracks, {} sides, {} kB total\n",
Logger() << fmt::format("IMG: wrote {} tracks, {} sides, {} kB total",
tracks, sides,
outputFile.tellp() / 1024);
}

View File

@@ -5,6 +5,7 @@
#include "fmt/format.h"
#include "ldbs.h"
#include "image.h"
#include "logger.h"
#include "lib/config.pb.h"
#include <algorithm>
#include <iostream>
@@ -23,10 +24,9 @@ public:
const Geometry geometry = image.getGeometry();
std::cout << fmt::format("LDBS: writing {} tracks, {} sides, {} sectors, {} bytes per sector",
Logger() << fmt::format("LDBS: writing {} tracks, {} sides, {} sectors, {} bytes per sector",
geometry.numTracks, geometry.numSides, geometry.numSectors,
geometry.sectorSize)
<< std::endl;
geometry.sectorSize);
Bytes trackDirectory;
ByteWriter trackDirectoryWriter(trackDirectory);
@@ -39,14 +39,14 @@ public:
dataRate = (geometry.numSectors > 10) ? LDBSOutputProto::RATE_HD : LDBSOutputProto::RATE_DD;
if (geometry.sectorSize <= 256)
dataRate = LDBSOutputProto::RATE_SD;
std::cout << fmt::format("LDBS: guessing data rate as {}\n", LDBSOutputProto::DataRate_Name(dataRate));
Logger() << fmt::format("LDBS: guessing data rate as {}", LDBSOutputProto::DataRate_Name(dataRate));
}
LDBSOutputProto::RecordingMode recordingMode = _config.ldbs().recording_mode();
if (recordingMode == LDBSOutputProto::RECMODE_GUESS)
{
recordingMode = LDBSOutputProto::RECMODE_MFM;
std::cout << fmt::format("LDBS: guessing recording mode as {}\n", LDBSOutputProto::RecordingMode_Name(recordingMode));
Logger() << fmt::format("LDBS: guessing recording mode as {}", LDBSOutputProto::RecordingMode_Name(recordingMode));
}
for (int track = 0; track < geometry.numTracks; track++)

View File

@@ -5,6 +5,7 @@
#include "fmt/format.h"
#include "decoders/decoders.h"
#include "image.h"
#include "logger.h"
#include "arch/northstar/northstar.h"
#include "lib/imagewriter/imagewriter.pb.h"
#include <algorithm>
@@ -26,15 +27,14 @@ public:
size_t trackSize = geometry.numSectors * geometry.sectorSize;
if (geometry.numTracks * trackSize == 0) {
std::cout << "No sectors in output; skipping .nsi image file generation." << std::endl;
Logger() << "No sectors in output; skipping .nsi image file generation.";
return;
}
std::cout << fmt::format("Writing {} cylinders, {} sides, {} sectors, {} ({} bytes/sector), {} kB total",
Logger() << fmt::format("Writing {} cylinders, {} sides, {} sectors, {} ({} bytes/sector), {} kB total",
geometry.numTracks, geometry.numSides,
geometry.numSectors, geometry.sectorSize == 256 ? "SD" : "DD", geometry.sectorSize,
geometry.numTracks * geometry.numSides * geometry.numSectors * geometry.sectorSize / 1024)
<< std::endl;
geometry.numTracks * geometry.numSides * geometry.numSectors * geometry.sectorSize / 1024);
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);
if (!outputFile.is_open())
@@ -69,7 +69,7 @@ public:
char fill[256];
memset(fill, ' ', sizeof(fill));
if (mixedDensity == false) {
std::cout << "Warning: Disk contains mixed single/double-density sectors." << std::endl;
Logger() << "Warning: Disk contains mixed single/double-density sectors.";
}
mixedDensity = true;
sector->data.slice(0, 256).writeTo(outputFile);

View File

@@ -5,6 +5,7 @@
#include "fmt/format.h"
#include "decoders/decoders.h"
#include "image.h"
#include "logger.h"
#include "arch/northstar/northstar.h"
#include "lib/imagewriter/imagewriter.pb.h"
#include <algorithm>
@@ -25,11 +26,11 @@ public:
size_t trackSize = geometry.numSectors * geometry.sectorSize;
if (geometry.numTracks * trackSize == 0) {
std::cout << "RAW: no sectors in output; skipping image file generation." << std::endl;
Logger() << "RAW: no sectors in output; skipping image file generation.";
return;
}
std::cout << fmt::format("RAW: writing {} cylinders, {} sides\n",
Logger() << fmt::format("RAW: writing {} cylinders, {} sides",
geometry.numTracks, geometry.numSides);
std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary);

View File

@@ -31,11 +31,12 @@ std::string Logger::toString(const AnyLogMessage& message)
{
std::stringstream stream;
auto indent = [&]() {
if (!indented)
stream << " ";
indented = false;
};
auto indent = [&]()
{
if (!indented)
stream << " ";
indented = false;
};
std::visit(
overloaded{
@@ -58,20 +59,29 @@ std::string Logger::toString(const AnyLogMessage& message)
60e9 / m.rotationalPeriod);
},
/* Indicates that we're working on a given cylinder and head */
[&](const DiskContextLogMessage& m)
/* Indicates that we're starting a write operation. */
[&](const BeginWriteOperationLogMessage& m)
{
stream << fmt::format("{:2}.{}: ", m.cylinder, m.head);
indented = true;
},
/* A single read has happened */
[&](const SingleReadLogMessage& m)
/* Indicates that we're starting a read operation. */
[&](const BeginReadOperationLogMessage& m)
{
const auto& trackdataflux = m.trackDataFlux;
stream << fmt::format("{:2}.{}: ", m.cylinder, m.head);
indented = true;
},
/* We've just read a track (we might reread it if there are errors)
*/
[&](const TrackReadLogMessage& m)
{
const auto& track = *m.track;
const auto& trackdataflux = track.trackDatas.end()[-1];
indent();
stream << fmt::format("{} records, {} sectors",
stream << fmt::format("{} raw records, {} raw sectors",
trackdataflux->records.size(),
trackdataflux->sectors.size());
if (trackdataflux->sectors.size() > 0)
@@ -89,14 +99,9 @@ std::string Logger::toString(const AnyLogMessage& message)
stream << "sectors:";
std::vector<std::shared_ptr<const Sector>> sectors(
m.sectors.begin(), m.sectors.end());
std::sort(sectors.begin(),
sectors.end(),
[](const std::shared_ptr<const Sector>& s1,
const std::shared_ptr<const Sector>& s2)
{
return s1->logicalSector < s2->logicalSector;
});
track.sectors.begin(), track.sectors.end());
std::sort(
sectors.begin(), sectors.end(), sectorPointerSortPredicate);
for (const auto& sector : sectors)
stream << fmt::format(" {}{}",
@@ -104,11 +109,7 @@ std::string Logger::toString(const AnyLogMessage& message)
Sector::statusToChar(sector->status));
stream << '\n';
},
/* We've finished reading a track */
[&](const TrackReadLogMessage& m)
{
int size = 0;
std::set<std::pair<int, int>> track_ids;
for (const auto& sector : m.track->sectors)

View File

@@ -3,50 +3,70 @@
#include "fmt/format.h"
class DiskFlux;
class TrackDataFlux;
class TrackFlux;
class Sector;
struct BeginSpeedOperationLogMessage {};
struct ErrorLogMessage
{
std::string message;
};
struct BeginSpeedOperationLogMessage
{
};
struct EndSpeedOperationLogMessage
{
nanoseconds_t rotationalPeriod;
nanoseconds_t rotationalPeriod;
};
struct DiskContextLogMessage
struct TrackReadLogMessage
{
std::shared_ptr<const TrackFlux> track;
};
struct DiskReadLogMessage
{
std::shared_ptr<const DiskFlux> disk;
};
struct BeginReadOperationLogMessage
{
unsigned cylinder;
unsigned head;
};
struct SingleReadLogMessage
struct EndReadOperationLogMessage
{
std::shared_ptr<const TrackDataFlux> trackDataFlux;
std::set<std::shared_ptr<const Sector>> sectors;
};
struct TrackReadLogMessage
struct BeginWriteOperationLogMessage
{
std::shared_ptr<TrackFlux> track;
unsigned cylinder;
unsigned head;
};
struct BeginReadOperationLogMessage { };
struct EndReadOperationLogMessage { };
struct BeginWriteOperationLogMessage { };
struct EndWriteOperationLogMessage { };
struct EndWriteOperationLogMessage
{
};
class TrackFlux;
typedef std::variant<std::string,
SingleReadLogMessage,
TrackReadLogMessage,
DiskContextLogMessage,
BeginSpeedOperationLogMessage,
EndSpeedOperationLogMessage,
typedef std::variant<
std::string,
ErrorLogMessage,
TrackReadLogMessage,
DiskReadLogMessage,
BeginSpeedOperationLogMessage,
EndSpeedOperationLogMessage,
BeginReadOperationLogMessage,
EndReadOperationLogMessage,
BeginWriteOperationLogMessage,
EndWriteOperationLogMessage>
BeginWriteOperationLogMessage,
EndWriteOperationLogMessage>
AnyLogMessage;
class Logger

View File

@@ -15,24 +15,21 @@
#include "logger.h"
#include "fmt/format.h"
#include "proto.h"
#include "utils.h"
#include "lib/decoders/decoders.pb.h"
#include <iostream>
#include <fstream>
static std::unique_ptr<FluxSink> outputFluxSink;
static std::shared_ptr<Fluxmap> readFluxmap(
FluxSource& fluxsource, unsigned cylinder, unsigned head)
static std::shared_ptr<Fluxmap> readFluxmap(FluxSource& fluxsource, unsigned cylinder, unsigned head)
{
Logger() << DiskContextLogMessage{cylinder, head}
<< BeginReadOperationLogMessage();
std::shared_ptr<Fluxmap> fluxmap = fluxsource.readFlux(cylinder, head);
fluxmap->rescale(1.0 / config.flux_source().rescale());
Logger() << EndReadOperationLogMessage()
<< fmt::format("{0:.0} ms in {1} bytes",
fluxmap->duration() / 1e6,
fluxmap->bytes());
return fluxmap;
Logger() << BeginReadOperationLogMessage { cylinder, head };
std::shared_ptr<Fluxmap> fluxmap = fluxsource.readFlux(cylinder, head);
fluxmap->rescale(1.0/config.flux_source().rescale());
Logger() << EndReadOperationLogMessage()
<< fmt::format("{0:.0} ms in {1} bytes", fluxmap->duration()/1e6, fluxmap->bytes());
return fluxmap;
}
static bool conflictable(Sector::Status status)
@@ -88,19 +85,24 @@ static std::set<std::shared_ptr<const Sector>> collect_sectors(
return sector_set;
}
void readDiskCommand(
FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer)
std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder)
{
if (config.decoder().has_copy_flux_to())
outputFluxSink = FluxSink::create(config.decoder().copy_flux_to());
auto diskflux = std::make_unique<DiskFlux>();
auto diskflux = std::make_shared<DiskFlux>();
bool failures = false;
for (int cylinder : iterate(config.cylinders()))
{
for (int head : iterate(config.heads()))
{
testForEmergencyStop();
auto track = std::make_shared<TrackFlux>();
track->physicalCylinder = cylinder;
track->physicalHead = head;
diskflux->tracks.push_back(track);
std::set<std::shared_ptr<const Sector>> track_sectors;
std::set<std::shared_ptr<const Record>> track_records;
Fluxmap totalFlux;
@@ -141,7 +143,10 @@ void readDiskCommand(
hasBadSectors = true;
}
Logger() << SingleReadLogMessage{trackdataflux, result_sectors};
track->sectors = collect_sectors(result_sectors);
/* track can't be modified below this point. */
Logger() << TrackReadLogMessage { track };
if (hasBadSectors)
failures = false;
@@ -192,39 +197,43 @@ void readDiskCommand(
std::cout << std::endl;
}
}
for (const auto& sector : collect_sectors(track_sectors))
track->sectors.insert(sector);
Logger() << TrackReadLogMessage{track};
diskflux->tracks.push_back(track);
}
}
}
std::set<std::shared_ptr<const Sector>> all_sectors;
for (auto& track : diskflux->tracks)
for (auto& sector : track->sectors)
all_sectors.insert(sector);
all_sectors = collect_sectors(all_sectors);
diskflux->image.reset(new Image(all_sectors));
if (failures)
Logger() << "Warning: some sectors could not be decoded.";
std::set<std::shared_ptr<const Sector>> all_sectors;
for (auto& track : diskflux->tracks)
for (auto& sector : track->sectors)
all_sectors.insert(sector);
all_sectors = collect_sectors(all_sectors);
diskflux->image = std::make_shared<Image>(all_sectors);
/* diskflux can't be modified below this point. */
Logger() << DiskReadLogMessage { diskflux };
return diskflux;
}
void readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer)
{
auto diskflux = readDiskCommand(fluxsource, decoder);
writer.printMap(*diskflux->image);
if (config.decoder().has_write_csv_to())
writer.writeCsv(*diskflux->image, config.decoder().write_csv_to());
writer.writeImage(*diskflux->image);
if (failures)
std::cerr << "Warning: some sectors could not be decoded." << std::endl;
}
void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
{
for (int cylinder : iterate(config.cylinders()))
{
for (int head : iterate(config.heads()))
{
auto fluxmap = readFluxmap(fluxsource, cylinder, head);
fluxsink.writeFlux(cylinder, head, *fluxmap);
}
for (int cylinder : iterate(config.cylinders()))
{
for (int head : iterate(config.heads()))
{
testForEmergencyStop();
auto fluxmap = readFluxmap(fluxsource, cylinder, head);
fluxsink.writeFlux(cylinder, head, *fluxmap);
}
}
}

View File

@@ -2,6 +2,7 @@
#define READER_H
class AbstractDecoder;
class DiskFlux;
class FluxSink;
class FluxSource;
class Fluxmap;
@@ -10,6 +11,8 @@ class TrackDataFlux;
extern std::unique_ptr<TrackDataFlux> readAndDecodeTrack(
FluxSource& source, AbstractDecoder& decoder, unsigned cylinder, unsigned head);
extern std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder);
extern void readDiskCommand(FluxSource& source, AbstractDecoder& decoder, ImageWriter& writer);
extern void rawReadDiskCommand(FluxSource& source, FluxSink& sink);

View File

@@ -8,6 +8,7 @@
#include "bytes.h"
#include "proto.h"
#include "usbfinder.h"
#include "logger.h"
#include "greaseweazle.h"
#include "fmt/format.h"
@@ -17,7 +18,7 @@ USB::~USB() {}
static std::unique_ptr<CandidateDevice> selectDevice()
{
auto candidates = findUsbDevices({FLUXENGINE_ID, GREASEWEAZLE_ID});
auto candidates = findUsbDevices();
if (candidates.size() == 0)
Error() << "no devices found (is one plugged in? Do you have the "
"appropriate permissions?";
@@ -65,8 +66,8 @@ USB* get_usb_impl()
config.usb().greaseweazle().has_port())
{
const auto& conf = config.usb().greaseweazle();
std::cerr << fmt::format(
"Using GreaseWeazle on serial port {}\n", conf.port());
Logger() << fmt::format(
"Using GreaseWeazle on serial port {}", conf.port());
return createGreaseWeazleUsb(conf.port(), conf);
}
@@ -76,12 +77,12 @@ USB* get_usb_impl()
switch (candidate->id)
{
case FLUXENGINE_ID:
std::cerr << fmt::format(
"Using FluxEngine {}\n", candidate->serial);
Logger() << fmt::format(
"Using FluxEngine {}", candidate->serial);
return createFluxengineUsb(candidate->device);
case GREASEWEAZLE_ID:
std::cerr << fmt::format("Using GreaseWeazle {} on {}\n",
Logger() << fmt::format("Using GreaseWeazle {} on {}",
candidate->serial,
candidate->serialPort);
return createGreaseWeazleUsb(

View File

@@ -5,8 +5,11 @@
#include "fmt/format.h"
#include "usbfinder.h"
#include "greaseweazle.h"
#include "protocol.h"
#include "libusbp.hpp"
static const std::set<uint32_t> VALID_DEVICES = { GREASEWEAZLE_ID, FLUXENGINE_ID };
static const std::string get_serial_number(const libusbp::device& device)
{
try
@@ -21,8 +24,7 @@ static const std::string get_serial_number(const libusbp::device& device)
}
}
std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(
const std::set<uint32_t>& ids)
std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices()
{
std::vector<std::unique_ptr<CandidateDevice>> candidates;
for (const auto& it : libusbp::list_connected_devices())
@@ -31,7 +33,7 @@ std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(
candidate->device = it;
uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id();
if (ids.find(id) != ids.end())
if (VALID_DEVICES.find(id) != VALID_DEVICES.end())
{
candidate->id = id;
candidate->serial = get_serial_number(it);

View File

@@ -12,7 +12,7 @@ struct CandidateDevice
std::string serialPort;
};
extern std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(const std::set<uint32_t>& id);
extern std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices();
#endif

View File

@@ -1,4 +1,9 @@
#include "globals.h"
#include "utils.h"
bool emergencyStop = false;
static const char* WHITESPACE = " \t\n\r\f\v";
bool beginsWith(const std::string& value, const std::string& ending)
{
@@ -18,4 +23,25 @@ bool endsWith(const std::string& value, const std::string& ending)
std::equal(ending.rbegin(), ending.rend(), lowercase.begin());
}
void leftTrimWhitespace(std::string& value)
{
value.erase(0, value.find_first_not_of(WHITESPACE));
}
void rightTrimWhitespace(std::string& value)
{
value.erase(value.find_last_not_of(WHITESPACE) + 1);
}
void trimWhitespace(std::string& value)
{
leftTrimWhitespace(value);
rightTrimWhitespace(value);
}
void testForEmergencyStop()
{
if (emergencyStop)
throw EmergencyStopException();
}

View File

@@ -5,6 +5,16 @@
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);
/* If set, any running job will terminate as soon as possible (with an error).
*/
extern bool emergencyStop;
class EmergencyStopException {};
extern void testForEmergencyStop();
#endif

View File

@@ -13,6 +13,7 @@
#include "sector.h"
#include "image.h"
#include "logger.h"
#include "utils.h"
#include "lib/config.pb.h"
#include "proto.h"
@@ -23,9 +24,8 @@ void writeTracks(FluxSink& fluxSink,
{
for (unsigned head : iterate(config.heads()))
{
Logger() << DiskContextLogMessage { cylinder, head }
<< fmt::format("{0:>3}.{1}: writing", cylinder, head)
<< BeginWriteOperationLogMessage();
testForEmergencyStop();
Logger() << BeginWriteOperationLogMessage{cylinder, head};
std::unique_ptr<Fluxmap> fluxmap = producer(cylinder, head);
if (!fluxmap)
@@ -44,8 +44,8 @@ void writeTracks(FluxSink& fluxSink,
// fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2);
fluxSink.writeFlux(cylinder, head, *fluxmap);
Logger() << fmt::format("{0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
Logger() << EndWriteOperationLogMessage();
}
@@ -58,14 +58,13 @@ void writeTracksAndVerify(FluxSink& fluxSink,
AbstractDecoder& decoder,
const Image& image)
{
std::cout << "Writing to: " << fluxSink << std::endl;
Logger() << fmt::format("Writing to: {}", (std::string)fluxSink);
for (unsigned cylinder : iterate(config.cylinders()))
{
for (unsigned head : iterate(config.heads()))
{
Logger() << DiskContextLogMessage { cylinder, head }
<< fmt::format("{0:>3}.{1}", cylinder, head);
testForEmergencyStop();
auto sectors = encoder.collectSectors(cylinder, head, image);
std::unique_ptr<Fluxmap> fluxmap =
@@ -74,10 +73,11 @@ void writeTracksAndVerify(FluxSink& fluxSink,
{
/* Erase this track rather than writing. */
Logger() << BeginWriteOperationLogMessage() << "erasing";
Logger() << BeginWriteOperationLogMessage{cylinder, head};
fluxmap.reset(new Fluxmap());
fluxSink.writeFlux(cylinder, head, *fluxmap);
Logger() << EndWriteOperationLogMessage();
Logger() << EndWriteOperationLogMessage()
<< fmt::format("erased");
}
else
{
@@ -91,18 +91,18 @@ void writeTracksAndVerify(FluxSink& fluxSink,
* let's leave it disabled for now. */
// fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS,
// 2);
Logger() << BeginWriteOperationLogMessage() << "writing";
Logger() << BeginWriteOperationLogMessage{cylinder, head};
fluxSink.writeFlux(cylinder, head, *fluxmap);
Logger() << EndWriteOperationLogMessage()
<< fmt::format("{0} ms in {1} bytes",
<< fmt::format("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
Logger() << "verifying" << BeginReadOperationLogMessage();
Logger() << BeginReadOperationLogMessage{cylinder, head};
std::shared_ptr<Fluxmap> writtenFluxmap =
fluxSource.readFlux(cylinder, head);
Logger() << EndReadOperationLogMessage()
<< fmt::format("{0} ms in {1} bytes",
<< fmt::format("verifying {0} ms in {1} bytes",
int(writtenFluxmap->duration() / 1e6),
writtenFluxmap->bytes());
@@ -110,7 +110,7 @@ void writeTracksAndVerify(FluxSink& fluxSink,
decoder.decodeToSectors(writtenFluxmap, cylinder, head);
std::vector<std::shared_ptr<const Sector>> gotSectors(
trackdata->sectors.begin(), trackdata->sectors.end());
trackdata->sectors.begin(), trackdata->sectors.end());
gotSectors.erase(std::remove_if(gotSectors.begin(),
gotSectors.end(),
[](const auto& s)
@@ -173,6 +173,8 @@ void writeDiskCommand(const Image& image,
{
const auto& sectors =
encoder.collectSectors(physicalTrack, physicalSide, image);
if (sectors.empty())
return std::make_unique<Fluxmap>();
return encoder.encode(
physicalTrack, physicalSide, sectors, image);
});

View File

@@ -45,7 +45,7 @@ rule link
rule linkgui
command = $CXX $LDFLAGS $GUILDFLAGS -o \$out \$in \$flags $LIBS $GUILIBS
description = LINK-OBJC \$in
description = LINK-GUI \$in
rule test
command = \$in && touch \$out
@@ -402,6 +402,7 @@ buildlibrary libbackend.a \
arch/amiga/decoder.cc \
arch/amiga/encoder.cc \
arch/apple2/decoder.cc \
arch/apple2/encoder.cc \
arch/brother/decoder.cc \
arch/brother/encoder.cc \
arch/c64/decoder.cc \
@@ -528,6 +529,7 @@ FORMATS="\
northstar175 \
northstar350 \
northstar87 \
rx50 \
tids990 \
vgi \
victor9k_ss \
@@ -564,6 +566,15 @@ buildlibrary libfrontend.a \
src/fe-write.cc \
src/fluxengine.cc \
buildlibrary libgui.a \
-I$OBJDIR/proto \
-Idep/libusbp/include \
-d $OBJDIR/proto/libconfig.def \
src/gui/main.cc \
src/gui/layout.cpp \
src/gui/visualisation.cc \
src/gui/mainwindow.cc \
buildprogram fluxengine \
libfrontend.a \
libformats.a \
@@ -574,6 +585,16 @@ buildprogram fluxengine \
libfmt.a \
libagg.a \
buildprogram fluxengine-gui \
-rule linkgui \
libgui.a \
libformats.a \
libbackend.a \
libconfig.a \
libfl2.a \
libusbp.a \
libfmt.a \
buildlibrary libemu.a \
dep/emu/fnmatch.c
@@ -626,6 +647,7 @@ runtest proto-test -I$OBJDIR/proto \
$OBJDIR/proto/tests/testproto.cc
encodedecodetest amiga
encodedecodetest apple2
encodedecodetest atarist360
encodedecodetest atarist370
encodedecodetest atarist400
@@ -649,6 +671,7 @@ encodedecodetest ibm720_525
encodedecodetest mac400 scripts/mac400_test.textpb
encodedecodetest mac800 scripts/mac800_test.textpb
encodedecodetest n88basic
encodedecodetest rx50
encodedecodetest tids990
encodedecodetest victor9k_ss
encodedecodetest victor9k_ds

View File

@@ -141,7 +141,17 @@ int main(int argc, const char* argv[])
for (Command& c : commands)
{
if (command == c.name)
return c.main(argc-1, argv+1);
{
try
{
return c.main(argc-1, argv+1);
}
catch (const ErrorException& e)
{
std::cerr << e.message << '\n';
exit(1);
}
}
}
std::cerr << "fluxengine: unrecognised command (try --help)\n";

View File

@@ -1,4 +1,19 @@
comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD (ro)'
comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD'
image_reader {
filename: "apple2.img"
img {
tracks: 40
sides: 1
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
image_writer {
filename: "apple2.img"
@@ -9,6 +24,10 @@ decoder {
apple2 {}
}
encoder {
apple2 {}
}
cylinders {
start: 0
end: 79

View File

@@ -5,6 +5,7 @@ image_reader {
img {
tracks: 39
sides: 1
physical_step: 2
trackdata {
sector_size: 256
sector_range {
@@ -20,6 +21,7 @@ image_writer {
img {
tracks: 39
sides: 1
physical_step: 2
trackdata {
sector_size: 256
sector_range {
@@ -37,12 +39,13 @@ encoder {
}
decoder {
retries: 1
brother {}
}
cylinders {
start: 0
end: 39
end: 77
}
heads {

77
src/formats/rx50.textpb Normal file
View File

@@ -0,0 +1,77 @@
comment: 'Digital RX50 400kB 5.25" 80-track 10-sector SSQD'
flux_sink {
drive {
high_density: true
}
}
flux_source {
drive {
high_density: true
}
}
image_reader {
filename: "rx50.img"
img {
tracks: 80
sides: 1
trackdata {
sector_size: 512
sector_range {
start_sector: 1
sector_count: 10
}
}
}
}
image_writer {
filename: "rx50.img"
img {}
}
encoder {
ibm {
trackdata {
track_length_ms: 167
clock_rate_khz: 300
gap3: 30
sectors {
sector: 1
sector: 2
sector: 3
sector: 4
sector: 5
sector: 6
sector: 7
sector: 8
sector: 9
sector: 10
}
}
}
}
decoder {
ibm {
trackdata {
sector_range {
min_sector: 1
max_sector: 10
}
}
}
}
cylinders {
start: 0
end: 79
}
heads {
start: 0
end: 0
}

46
src/gui/gui.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef RENDEZVOUS_H
#define RENDEZVOUS_H
#include <wx/wx.h>
class ExecEvent;
class MainWindow;
extern void runOnUiThread(std::function<void()> callback);
extern void runOnWorkerThread(std::function<void()> callback);
template <typename R>
static inline R runOnUiThread(std::function<R()> callback)
{
R retvar;
runOnUiThread(
[&]() {
retvar = callback();
}
);
return retvar;
}
class FluxEngineApp : public wxApp, public wxThreadHelper
{
public:
virtual bool OnInit();
void RunOnWorkerThread(std::function<void()> callback);
private:
void OnExec(const ExecEvent& event);
public:
bool IsWorkerThreadRunning() const;
protected:
virtual wxThread::ExitCode Entry();
private:
std::function<void()> _callback;
MainWindow* _mainWindow;
};
wxDECLARE_APP(FluxEngineApp);
#endif

187
src/gui/layout.cpp Normal file
View File

@@ -0,0 +1,187 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "layout.h"
///////////////////////////////////////////////////////////////////////////
MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxSize( 450,500 ), wxDefaultSize );
this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
bSizer1 = new wxFlexGridSizer( 0, 2, 0, 0 );
bSizer1->AddGrowableCol( 1 );
bSizer1->AddGrowableRow( 0 );
bSizer1->SetFlexibleDirection( wxHORIZONTAL );
bSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
wxFlexGridSizer* fgSizer4;
fgSizer4 = new wxFlexGridSizer( 2, 1, 0, 0 );
fgSizer4->AddGrowableRow( 0 );
fgSizer4->SetFlexibleDirection( wxBOTH );
fgSizer4->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
visualiser = new VisualisationControl( this, wxID_ANY, wxDefaultPosition, wxSize( 200,480 ), wxBORDER_THEME );
visualiser->SetMinSize( wxSize( 200,480 ) );
fgSizer4->Add( visualiser, 1, wxALL|wxEXPAND, 5 );
stopButton = new wxButton( this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0 );
fgSizer4->Add( stopButton, 0, wxALIGN_CENTER|wxALL, 5 );
bSizer1->Add( fgSizer4, 1, wxEXPAND, 5 );
wxFlexGridSizer* fgSizer2;
fgSizer2 = new wxFlexGridSizer( 0, 1, 0, 0 );
fgSizer2->AddGrowableCol( 0 );
fgSizer2->AddGrowableRow( 1 );
fgSizer2->SetFlexibleDirection( wxVERTICAL );
fgSizer2->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL );
wxFlexGridSizer* fgSizer3;
fgSizer3 = new wxFlexGridSizer( 0, 2, 0, 0 );
fgSizer3->AddGrowableCol( 1 );
fgSizer3->SetFlexibleDirection( wxBOTH );
fgSizer3->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
m_staticText4 = new wxStaticText( this, wxID_ANY, wxT("Device:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText4->Wrap( -1 );
fgSizer3->Add( m_staticText4, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 );
deviceCombo = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_SORT );
fgSizer3->Add( deviceCombo, 0, wxALL|wxEXPAND, 5 );
m_staticText5 = new wxStaticText( this, wxID_ANY, wxT("Flux source/sink:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText5->Wrap( -1 );
fgSizer3->Add( m_staticText5, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 );
fluxSourceSinkCombo = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 );
fluxSourceSinkCombo->Append( wxT("drive:0") );
fluxSourceSinkCombo->Append( wxT("drive:1") );
fgSizer3->Add( fluxSourceSinkCombo, 0, wxALL|wxEXPAND, 5 );
m_staticText51 = new wxStaticText( this, wxID_ANY, wxT("Format:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText51->Wrap( -1 );
fgSizer3->Add( m_staticText51, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 );
wxArrayString formatChoiceChoices;
formatChoice = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, formatChoiceChoices, wxCB_SORT );
formatChoice->SetSelection( 0 );
fgSizer3->Add( formatChoice, 0, wxALL|wxEXPAND, 5 );
fgSizer3->Add( 0, 0, 1, wxEXPAND, 5 );
highDensityToggle = new wxCheckBox( this, wxID_ANY, wxT("High density disk"), wxDefaultPosition, wxDefaultSize, 0 );
fgSizer3->Add( highDensityToggle, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 );
fgSizer2->Add( fgSizer3, 1, wxEXPAND, 5 );
notebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
m_panel1 = new wxPanel( notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxFlexGridSizer* fgSizer5;
fgSizer5 = new wxFlexGridSizer( 0, 2, 0, 0 );
fgSizer5->AddGrowableCol( 0 );
fgSizer5->AddGrowableRow( 0 );
fgSizer5->SetFlexibleDirection( wxBOTH );
fgSizer5->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
additionalSettingsEntry = new wxTextCtrl( m_panel1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE );
fgSizer5->Add( additionalSettingsEntry, 0, wxALL|wxEXPAND, 5 );
m_panel1->SetSizer( fgSizer5 );
m_panel1->Layout();
fgSizer5->Fit( m_panel1 );
notebook->AddPage( m_panel1, wxT("Additional settings"), true );
m_panel2 = new wxPanel( notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxFlexGridSizer* fgSizer8;
fgSizer8 = new wxFlexGridSizer( 0, 2, 0, 0 );
fgSizer8->AddGrowableCol( 0 );
fgSizer8->AddGrowableRow( 0 );
fgSizer8->SetFlexibleDirection( wxBOTH );
fgSizer8->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
logEntry = new wxTextCtrl( m_panel2, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxTE_RICH );
fgSizer8->Add( logEntry, 0, wxALL|wxEXPAND, 5 );
m_panel2->SetSizer( fgSizer8 );
m_panel2->Layout();
fgSizer8->Fit( m_panel2 );
notebook->AddPage( m_panel2, wxT("Logs"), false );
m_panel3 = new wxPanel( notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxFlexGridSizer* fgSizer9;
fgSizer9 = new wxFlexGridSizer( 0, 2, 0, 0 );
fgSizer9->AddGrowableCol( 0 );
fgSizer9->AddGrowableRow( 0 );
fgSizer9->SetFlexibleDirection( wxBOTH );
fgSizer9->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
protoConfigEntry = new wxTextCtrl( m_panel3, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY );
fgSizer9->Add( protoConfigEntry, 0, wxALL|wxEXPAND, 5 );
m_panel3->SetSizer( fgSizer9 );
m_panel3->Layout();
fgSizer9->Fit( m_panel3 );
notebook->AddPage( m_panel3, wxT("Debug info"), false );
fgSizer2->Add( notebook, 1, wxEXPAND | wxALL, 5 );
wxGridSizer* m_sizer;
m_sizer = new wxGridSizer( 0, 2, 0, 0 );
readFluxButton = new wxButton( this, wxID_ANY, wxT("Read flux"), wxDefaultPosition, wxDefaultSize, 0 );
m_sizer->Add( readFluxButton, 0, wxALL|wxEXPAND, 5 );
readImageButton = new wxButton( this, wxID_ANY, wxT("Read image"), wxDefaultPosition, wxDefaultSize, 0 );
m_sizer->Add( readImageButton, 0, wxALL|wxEXPAND, 5 );
writeFluxButton = new wxButton( this, wxID_ANY, wxT("Write flux"), wxDefaultPosition, wxDefaultSize, 0 );
m_sizer->Add( writeFluxButton, 0, wxALL|wxEXPAND, 5 );
writeImageButton = new wxButton( this, wxID_ANY, wxT("Write image"), wxDefaultPosition, wxDefaultSize, 0 );
m_sizer->Add( writeImageButton, 0, wxALL|wxEXPAND, 5 );
fgSizer2->Add( m_sizer, 1, wxEXPAND|wxFIXED_MINSIZE, 5 );
bSizer1->Add( fgSizer2, 1, wxEXPAND, 5 );
this->SetSizer( bSizer1 );
this->Layout();
m_menubar1 = new wxMenuBar( 0 );
m_menu1 = new wxMenu();
wxMenuItem* m_menuItem2;
m_menuItem2 = new wxMenuItem( m_menu1, wxID_ABOUT, wxString( wxT("About") ) , wxEmptyString, wxITEM_NORMAL );
m_menu1->Append( m_menuItem2 );
wxMenuItem* m_menuItem1;
m_menuItem1 = new wxMenuItem( m_menu1, wxID_EXIT, wxString( wxT("E&xit") ) , wxEmptyString, wxITEM_NORMAL );
m_menu1->Append( m_menuItem1 );
m_menubar1->Append( m_menu1, wxT("&File") );
this->SetMenuBar( m_menubar1 );
// Connect Events
m_menu1->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnAbout ), this, m_menuItem2->GetId());
m_menu1->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnExit ), this, m_menuItem1->GetId());
}
MainWindowGen::~MainWindowGen()
{
// Disconnect Events
}

1537
src/gui/layout.fbp Normal file
View File

File diff suppressed because it is too large Load Diff

79
src/gui/layout.h Normal file
View File

@@ -0,0 +1,79 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#pragma once
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include "visualisation.h"
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/button.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/choice.h>
#include <wx/checkbox.h>
#include <wx/textctrl.h>
#include <wx/panel.h>
#include <wx/notebook.h>
#include <wx/menu.h>
#include <wx/frame.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class MainWindowGen
///////////////////////////////////////////////////////////////////////////////
class MainWindowGen : public wxFrame
{
private:
wxFlexGridSizer* bSizer1;
protected:
VisualisationControl* visualiser;
wxButton* stopButton;
wxStaticText* m_staticText4;
wxComboBox* deviceCombo;
wxStaticText* m_staticText5;
wxComboBox* fluxSourceSinkCombo;
wxStaticText* m_staticText51;
wxChoice* formatChoice;
wxCheckBox* highDensityToggle;
wxNotebook* notebook;
wxPanel* m_panel1;
wxTextCtrl* additionalSettingsEntry;
wxPanel* m_panel2;
wxTextCtrl* logEntry;
wxPanel* m_panel3;
wxTextCtrl* protoConfigEntry;
wxButton* readFluxButton;
wxButton* readImageButton;
wxButton* writeFluxButton;
wxButton* writeImageButton;
wxMenuBar* m_menubar1;
wxMenu* m_menu1;
// Virtual event handlers, override them in your derived class
virtual void OnAbout( wxCommandEvent& event ) { event.Skip(); }
virtual void OnExit( wxCommandEvent& event ) { event.Skip(); }
public:
MainWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("FluxEngine"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 587,595 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
~MainWindowGen();
};

117
src/gui/main.cc Normal file
View File

@@ -0,0 +1,117 @@
#include "globals.h"
#include "gui.h"
#include "mainwindow.h"
#include "utils.h"
class FluxEngineApp;
class ExecEvent;
static wxSemaphore execSemaphore(0);
wxDEFINE_EVENT(EXEC_EVENT_TYPE, ExecEvent);
class ExecEvent : public wxThreadEvent
{
public:
ExecEvent(wxEventType commandType = EXEC_EVENT_TYPE, int id = 0):
wxThreadEvent(commandType, id)
{
}
ExecEvent(const ExecEvent& event):
wxThreadEvent(event),
_callback(event._callback)
{
}
wxEvent* Clone() const
{
return new ExecEvent(*this);
}
void SetCallback(const std::function<void()> callback)
{
_callback = callback;
}
void RunCallback() const
{
_callback();
}
private:
std::function<void()> _callback;
};
bool FluxEngineApp::OnInit()
{
Bind(EXEC_EVENT_TYPE, &FluxEngineApp::OnExec, this);
_mainWindow = new MainWindow();
_mainWindow->Show(true);
return true;
}
wxThread::ExitCode FluxEngineApp::Entry()
{
try
{
if (_callback)
_callback();
}
catch (const ErrorException& e)
{
Logger() << ErrorLogMessage { e.message+'\n' };
}
catch (const EmergencyStopException& e)
{
Logger() << "Emergency stop!\n";
}
runOnUiThread(
[&] {
_callback = nullptr;
_mainWindow->UpdateState();
}
);
return 0;
}
void FluxEngineApp::RunOnWorkerThread(std::function<void()> callback)
{
if (_callback)
std::cerr << "Cannot start new worker task as one is already running\n";
_callback = callback;
if (GetThread())
GetThread()->Wait();
emergencyStop = false;
CreateThread(wxTHREAD_JOINABLE);
GetThread()->Run();
_mainWindow->UpdateState();
}
void runOnWorkerThread(std::function<void()> callback)
{
wxGetApp().RunOnWorkerThread(callback);
}
bool FluxEngineApp::IsWorkerThreadRunning() const
{
return !!_callback;
}
void FluxEngineApp::OnExec(const ExecEvent& event)
{
event.RunCallback();
execSemaphore.Post();
}
void runOnUiThread(std::function<void()> callback)
{
ExecEvent* event = new ExecEvent();
event->SetCallback(callback);
wxGetApp().QueueEvent(event);
execSemaphore.Wait();
}
wxIMPLEMENT_APP(FluxEngineApp);

355
src/gui/mainwindow.cc Normal file
View File

@@ -0,0 +1,355 @@
#include "globals.h"
#include "proto.h"
#include "gui.h"
#include "logger.h"
#include "reader.h"
#include "writer.h"
#include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h"
#include "imagereader/imagereader.h"
#include "imagewriter/imagewriter.h"
#include "encoders/encoders.h"
#include "decoders/decoders.h"
#include "lib/usb/usbfinder.h"
#include "fmt/format.h"
#include "utils.h"
#include "mainwindow.h"
#include <google/protobuf/text_format.h>
extern const std::map<std::string, std::string> formats;
MainWindow::MainWindow(): MainWindowGen(nullptr)
{
Logger::setLogger(
[&](std::shared_ptr<const AnyLogMessage> message)
{
runOnUiThread(
[message, this]()
{
OnLogMessage(message);
});
});
for (const auto& it : formats)
{
auto config = std::make_unique<ConfigProto>();
if (!config->ParseFromString(it.second))
continue;
if (config->is_extension())
continue;
formatChoice->Append(it.first);
_formats.push_back(std::move(config));
}
UpdateDevices();
if (deviceCombo->GetCount() > 0)
deviceCombo->SetValue(deviceCombo->GetString(0));
fluxSourceSinkCombo->SetValue(fluxSourceSinkCombo->GetString(0));
readFluxButton->Bind(wxEVT_BUTTON, &MainWindow::OnReadFluxButton, this);
readImageButton->Bind(wxEVT_BUTTON, &MainWindow::OnReadImageButton, this);
writeFluxButton->Bind(wxEVT_BUTTON, &MainWindow::OnWriteFluxButton, this);
writeImageButton->Bind(wxEVT_BUTTON, &MainWindow::OnWriteImageButton, this);
stopButton->Bind(wxEVT_BUTTON, &MainWindow::OnStopButton, this);
UpdateState();
}
void MainWindow::OnExit(wxCommandEvent& event)
{
Close(true);
}
void MainWindow::OnStopButton(wxCommandEvent&)
{
emergencyStop = true;
}
void MainWindow::OnReadFluxButton(wxCommandEvent&)
{
try
{
PrepareConfig();
FluxSource::updateConfigForFilename(config.mutable_flux_source(),
fluxSourceSinkCombo->GetValue().ToStdString());
visualiser->Clear();
_currentDisk = nullptr;
SetHighDensity();
ShowConfig();
runOnWorkerThread(
[this]()
{
auto fluxSource = FluxSource::create(config.flux_source());
auto decoder = AbstractDecoder::create(config.decoder());
auto diskflux = readDiskCommand(*fluxSource, *decoder);
runOnUiThread(
[&]()
{
visualiser->SetDiskData(diskflux);
});
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
}
}
void MainWindow::OnWriteFluxButton(wxCommandEvent&)
{
try
{
PrepareConfig();
FluxSink::updateConfigForFilename(config.mutable_flux_sink(),
fluxSourceSinkCombo->GetValue().ToStdString());
FluxSource::updateConfigForFilename(config.mutable_flux_source(),
fluxSourceSinkCombo->GetValue().ToStdString());
SetHighDensity();
ShowConfig();
auto image = _currentDisk->image;
runOnWorkerThread(
[image, this]()
{
auto encoder = AbstractEncoder::create(config.encoder());
auto fluxSink = FluxSink::create(config.flux_sink());
std::unique_ptr<AbstractDecoder> decoder;
std::unique_ptr<FluxSource> fluxSource;
if (config.has_decoder())
{
decoder = AbstractDecoder::create(config.decoder());
fluxSource = FluxSource::create(config.flux_source());
}
writeDiskCommand(*image, *encoder, *fluxSink, decoder.get(), fluxSource.get());
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
}
}
void MainWindow::OnReadImageButton(wxCommandEvent&)
{
try
{
PrepareConfig();
if (!config.has_image_reader())
Error() << "This format is read-only.";
auto filename = wxFileSelector(
"Choose a image file to read",
/* default_path= */ wxEmptyString,
/* default_filename= */ config.image_reader().filename(),
/* default_extension= */ wxEmptyString,
/* wildcard= */ wxEmptyString,
/* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (filename.empty())
return;
ImageReader::updateConfigForFilename(
config.mutable_image_reader(), filename.ToStdString());
visualiser->Clear();
_currentDisk = nullptr;
ShowConfig();
runOnWorkerThread(
[this]()
{
auto imageReader = ImageReader::create(config.image_reader());
std::unique_ptr<const Image> image = imageReader->readImage();
runOnUiThread(
[&]()
{
auto disk = std::make_shared<DiskFlux>();
disk = std::make_shared<DiskFlux>();
disk->image = std::move(image);
_currentDisk = disk;
visualiser->SetDiskData(_currentDisk);
});
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
}
}
void MainWindow::OnWriteImageButton(wxCommandEvent&)
{
try
{
PrepareConfig();
if (!config.has_image_writer())
Error() << "This format is write-only.";
auto filename = wxFileSelector(
"Choose a image file to write",
/* default_path= */ wxEmptyString,
/* default_filename= */ config.image_writer().filename(),
/* default_extension= */ wxEmptyString,
/* wildcard= */ wxEmptyString,
/* flags= */ wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (filename.empty())
return;
ImageWriter::updateConfigForFilename(
config.mutable_image_writer(), filename.ToStdString());
ShowConfig();
auto image = _currentDisk->image;
runOnWorkerThread(
[image, this]()
{
auto imageWriter = ImageWriter::create(config.image_writer());
imageWriter->writeImage(*image);
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
}
}
/* This sets the *global* config object. That's safe provided the worker thread
* isn't running, otherwise you'll get a race. */
void MainWindow::PrepareConfig()
{
assert(!wxGetApp().IsWorkerThreadRunning());
auto formatSelection = formatChoice->GetSelection();
if (formatSelection == wxNOT_FOUND)
Error() << "no format selected";
config = *_formats[formatChoice->GetSelection()];
auto serial = deviceCombo->GetValue().ToStdString();
if (!serial.empty() && (serial[0] == '/'))
setProtoByString(&config, "usb.greaseweazle.port", serial);
else
setProtoByString(&config, "usb.serial", serial);
ApplyCustomSettings();
logEntry->Clear();
}
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);
}
void MainWindow::ShowConfig()
{
std::string s;
google::protobuf::TextFormat::PrintToString(config, &s);
protoConfigEntry->Clear();
protoConfigEntry->AppendText(s);
}
void MainWindow::ApplyCustomSettings()
{
for (int i = 0; i < additionalSettingsEntry->GetNumberOfLines(); i++)
{
auto setting = additionalSettingsEntry->GetLineText(i).ToStdString();
trimWhitespace(setting);
if (setting.size() == 0)
continue;
auto equals = setting.find('=');
if (equals != std::string::npos)
{
auto key = setting.substr(0, equals);
auto value = setting.substr(equals + 1);
setProtoByString(&config, key, value);
}
else
FlagGroup::parseConfigFile(setting, formats);
}
}
void MainWindow::OnLogMessage(std::shared_ptr<const AnyLogMessage> message)
{
logEntry->AppendText(Logger::toString(*message));
notebook->SetSelection(1);
std::visit(
overloaded{
/* Fallback --- do nothing */
[&](const auto& m)
{
},
/* A fatal error. */
[&](const ErrorLogMessage& m)
{
wxMessageBox(m.message, "Error", wxOK | wxICON_ERROR);
},
/* Indicates that we're starting a write operation. */
[&](const BeginWriteOperationLogMessage& m)
{
visualiser->SetMode(m.cylinder, m.head, VISMODE_WRITING);
},
[&](const EndWriteOperationLogMessage& m)
{
visualiser->SetMode(0, 0, VISMODE_NOTHING);
},
/* Indicates that we're starting a read operation. */
[&](const BeginReadOperationLogMessage& m)
{
visualiser->SetMode(m.cylinder, m.head, VISMODE_READING);
},
[&](const EndReadOperationLogMessage& m)
{
visualiser->SetMode(0, 0, VISMODE_NOTHING);
},
[&](const TrackReadLogMessage& m)
{
visualiser->SetTrackData(m.track);
},
[&](const DiskReadLogMessage& m)
{
_currentDisk = m.disk;
},
},
*message);
}
void MainWindow::UpdateState()
{
bool running = wxGetApp().IsWorkerThreadRunning();
writeImageButton->Enable(!running && !!_currentDisk);
writeFluxButton->Enable(!running && !!_currentDisk);
stopButton->Enable(running);
readFluxButton->Enable(!running);
readImageButton->Enable(!running);
}
void MainWindow::UpdateDevices()
{
auto candidates = findUsbDevices();
deviceCombo->Clear();
_devices.clear();
for (auto& candidate : candidates)
{
deviceCombo->Append(candidate->serial);
_devices.push_back(std::move(candidate));
}
}

42
src/gui/mainwindow.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "layout.h"
#include "logger.h"
class CandidateDevice;
class ConfigProto;
class DiskFlux;
class MainWindow : public MainWindowGen
{
public:
MainWindow();
private:
void OnExit(wxCommandEvent& event);
void OnStopButton(wxCommandEvent&);
void OnReadFluxButton(wxCommandEvent&);
void OnReadImageButton(wxCommandEvent&);
void OnWriteFluxButton(wxCommandEvent&);
void OnWriteImageButton(wxCommandEvent&);
void OnLogMessage(std::shared_ptr<const AnyLogMessage> message);
public:
void UpdateState();
void UpdateDevices();
void PrepareConfig();
void ShowConfig();
void ApplyCustomSettings();
private:
void SetHighDensity();
private:
std::vector<std::unique_ptr<const ConfigProto>> _formats;
std::vector<std::unique_ptr<const CandidateDevice>> _devices;
std::shared_ptr<const DiskFlux> _currentDisk;
};
#endif

179
src/gui/visualisation.cc Normal file
View File

@@ -0,0 +1,179 @@
#include "globals.h"
#include "gui.h"
#include "visualisation.h"
#include "fluxmap.h"
#include "flux.h"
#include "sector.h"
#include "image.h"
#include "fmt/format.h"
#define BORDER 20
#define TICK 3
#define TRACKS 82
#define SECTORSIZE 5
#define DECLARE_COLOUR(name, red, green, blue) \
static const wxColour name##_COLOUR(red, green, blue); \
static const wxBrush name##_BRUSH(name##_COLOUR); \
static const wxPen name##_PEN(name##_COLOUR)
DECLARE_COLOUR(AXIS, 128, 128, 128);
DECLARE_COLOUR(GOOD_SECTOR, 0, 158, 115);
DECLARE_COLOUR(BAD_SECTOR, 213, 94, 0);
DECLARE_COLOUR(MISSING_SECTOR, 86, 180, 233);
DECLARE_COLOUR(READ_ARROW, 0, 128, 0);
DECLARE_COLOUR(WRITE_ARROW, 128, 0, 0);
VisualisationControl::VisualisationControl(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style):
wxWindow(parent, id, pos, size, style, "VisualisationControl")
{
SetDoubleBuffered(true);
}
wxBEGIN_EVENT_TABLE(VisualisationControl, wxPanel)
EVT_PAINT(VisualisationControl::OnPaint) wxEND_EVENT_TABLE()
void VisualisationControl::OnPaint(wxPaintEvent&)
{
auto size = GetSize();
int w = size.GetWidth();
int w2 = w / 2;
int h = size.GetHeight();
int centrey = h * 1.5;
int outerradius = centrey - BORDER;
int innerradius = centrey - h + BORDER;
int scalesize = TRACKS * SECTORSIZE;
int scaletop = h / 2 - scalesize / 2;
int scalebottom = scaletop + scalesize - 1;
wxPaintDC dc(this);
dc.SetBackground(*wxWHITE_BRUSH);
dc.Clear();
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxLIGHT_GREY_BRUSH);
dc.DrawCircle({w2, centrey}, outerradius);
dc.SetBrush(dc.GetBackground());
dc.DrawCircle({w2, centrey}, innerradius);
dc.SetPen(AXIS_PEN);
dc.DrawLine({w2, scaletop}, {w2, scalebottom});
if (_mode != VISMODE_NOTHING)
{
if (_mode == VISMODE_READING)
{
dc.SetPen(READ_ARROW_PEN);
dc.SetBrush(READ_ARROW_BRUSH);
}
else if (_mode == VISMODE_WRITING)
{
dc.SetPen(WRITE_ARROW_PEN);
dc.SetBrush(WRITE_ARROW_BRUSH);
}
int factor = (_head == 0) ? -1 : 1;
int y = scaletop + _cylinder * SECTORSIZE;
wxPoint points[] = {
{ w2 + factor*TICK, y-1 },
{ w2 + factor*TICK, y+SECTORSIZE-1 },
{ w2 + factor*TICK*2, y+SECTORSIZE/2 }
};
dc.DrawPolygon(3, points);
}
for (int track = 0; track <= TRACKS; track++)
{
int y = scaletop + track * SECTORSIZE;
dc.SetBrush(AXIS_BRUSH);
dc.SetPen(AXIS_PEN);
dc.DrawLine({w2 - TICK, y-1}, {w2 + TICK, y-1});
auto drawSectors = [&](int head)
{
key_t key = {track, head};
std::vector<std::shared_ptr<const Sector>> sectors;
for (auto it = _sectors.lower_bound(key);
it != _sectors.upper_bound(key);
it++)
sectors.push_back(it->second);
std::sort(
sectors.begin(), sectors.end(), sectorPointerSortPredicate);
int x = 1;
for (const auto& sector : sectors)
{
if (sector->status == Sector::OK)
{
dc.SetBrush(GOOD_SECTOR_BRUSH);
dc.SetPen(GOOD_SECTOR_PEN);
}
else if (sector->status == Sector::MISSING)
{
dc.SetBrush(MISSING_SECTOR_BRUSH);
dc.SetPen(MISSING_SECTOR_PEN);
}
else
{
dc.SetBrush(BAD_SECTOR_BRUSH);
dc.SetPen(BAD_SECTOR_PEN);
}
if (head == 0)
dc.DrawRectangle(
{w2 - x * SECTORSIZE - (SECTORSIZE - 1), y},
{SECTORSIZE - 1, SECTORSIZE - 1});
else
dc.DrawRectangle({w2 + x * SECTORSIZE + 1, y},
{SECTORSIZE - 1, SECTORSIZE - 1});
x++;
}
};
drawSectors(0);
drawSectors(1);
}
}
void VisualisationControl::SetMode(int cylinder, int head, int mode)
{
_cylinder = cylinder;
_head = head;
_mode = mode;
Refresh();
}
void VisualisationControl::Clear()
{
_sectors.clear();
Refresh();
}
void VisualisationControl::SetTrackData(std::shared_ptr<const TrackFlux> track)
{
key_t key = {track->physicalCylinder, track->physicalHead};
_sectors.erase(key);
for (auto& sector : track->sectors)
_sectors.insert({key, sector});
Refresh();
}
void VisualisationControl::SetDiskData(std::shared_ptr<const DiskFlux> disk)
{
_sectors.clear();
for (const auto& sector : *(disk->image))
{
key_t key = {sector->physicalCylinder, sector->physicalHead};
_sectors.insert({key, sector});
}
Refresh();
}

46
src/gui/visualisation.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef VISUALISATION_H
#define VISUALISATION_H
#include <memory>
#include <map>
#include <wx/control.h>
class Sector;
class DiskFlux;
class TrackFlux;
enum {
VISMODE_NOTHING,
VISMODE_READING,
VISMODE_WRITING
};
class VisualisationControl : public wxWindow
{
public:
VisualisationControl(wxWindow* parent,
wxWindowID winid,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0);
public:
void Clear();
void SetMode(int head, int cylinder, int mode);
void SetTrackData(std::shared_ptr<const TrackFlux> track);
void SetDiskData(std::shared_ptr<const DiskFlux> disk);
private:
void OnPaint(wxPaintEvent & evt);
private:
typedef std::pair<unsigned, unsigned> key_t;
int _head;
int _cylinder;
int _mode = VISMODE_NOTHING;
std::multimap<key_t, std::shared_ptr<const Sector>> _sectors;
wxDECLARE_EVENT_TABLE();
};
#endif