mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
7
.github/workflows/ccpp.yml
vendored
7
.github/workflows/ccpp.yml
vendored
@@ -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
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -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}
|
||||
|
||||
12
Makefile
12
Makefile
@@ -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
|
||||
@@ -48,9 +51,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)
|
||||
|
||||
48
lib/flags.cc
48
lib/flags.cc
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
58
lib/logger.h
58
lib/logger.h
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
26
lib/utils.cc
26
lib/utils.cc
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
10
lib/utils.h
10
lib/utils.h
@@ -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
|
||||
|
||||
|
||||
@@ -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,7 +73,7 @@ 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();
|
||||
@@ -91,18 +90,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 +109,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)
|
||||
|
||||
21
mkninja.sh
21
mkninja.sh
@@ -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
|
||||
@@ -564,6 +564,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 +583,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
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
46
src/gui/gui.h
Normal file
46
src/gui/gui.h
Normal 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
187
src/gui/layout.cpp
Normal 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
1537
src/gui/layout.fbp
Normal file
File diff suppressed because it is too large
Load Diff
79
src/gui/layout.h
Normal file
79
src/gui/layout.h
Normal 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
117
src/gui/main.cc
Normal 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
355
src/gui/mainwindow.cc
Normal 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
42
src/gui/mainwindow.h
Normal 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
179
src/gui/visualisation.cc
Normal 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
46
src/gui/visualisation.h
Normal 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
|
||||
Reference in New Issue
Block a user