Looks like we're going to have to rework the reader/writer/source/sink

interfaces, so do fluxsink. This lets us test for overwriting a flux
file on writing in the GUI. HG: Enter commit message.
This commit is contained in:
David Given
2025-10-14 21:54:59 +02:00
parent 2de8b52e56
commit 2d6cb22e3a
14 changed files with 578 additions and 417 deletions

View File

@@ -52,7 +52,7 @@ need to apply extra options to change the format if desired.
## Options ## Options
- : - $format:
- `143`: 143kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod I - `143`: 143kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod I
- `287`: 287kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod I - `287`: 287kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod I
- `315`: 315kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod II - `315`: 315kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod II

View File

@@ -410,7 +410,7 @@ static ReadGroupResult readGroup(const DiskLayout& diskLayout,
void writeTracks(const DiskLayout& diskLayout, void writeTracks(const DiskLayout& diskLayout,
FluxSink& fluxSink, FluxSink& fluxSinkFactory,
std::function<std::unique_ptr<const Fluxmap>( std::function<std::unique_ptr<const Fluxmap>(
const std::shared_ptr<const LogicalTrackLayout>&)> producer, const std::shared_ptr<const LogicalTrackLayout>&)> producer,
std::function<bool(const std::shared_ptr<const LogicalTrackLayout>&)> std::function<bool(const std::shared_ptr<const LogicalTrackLayout>&)>
@@ -419,63 +419,67 @@ void writeTracks(const DiskLayout& diskLayout,
{ {
log(BeginOperationLogMessage{"Encoding and writing to disk"}); log(BeginOperationLogMessage{"Encoding and writing to disk"});
if (fluxSink.isHardware()) if (fluxSinkFactory.isHardware())
measureDiskRotation(); measureDiskRotation();
int index = 0;
for (auto& ch : logicalLocations)
{ {
log(OperationProgressLogMessage{ auto fluxSink = fluxSinkFactory.create();
index * 100 / (unsigned)logicalLocations.size()}); int index = 0;
index++; for (auto& ch : logicalLocations)
testForEmergencyStop();
const auto& ltl = diskLayout.layoutByLogicalLocation.at(ch);
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
{ {
for (int offset = 0; offset < ltl->groupSize; log(OperationProgressLogMessage{
offset += diskLayout.headWidth) index * 100 / (unsigned)logicalLocations.size()});
index++;
testForEmergencyStop();
const auto& ltl = diskLayout.layoutByLogicalLocation.at(ch);
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
{ {
unsigned physicalCylinder = ltl->physicalCylinder + offset; for (int offset = 0; offset < ltl->groupSize;
unsigned physicalHead = ltl->physicalHead; offset += diskLayout.headWidth)
log(BeginWriteOperationLogMessage{
physicalCylinder, ltl->physicalHead});
if (offset == globalConfig()->drive().group_offset())
{ {
auto fluxmap = producer(ltl); unsigned physicalCylinder = ltl->physicalCylinder + offset;
if (!fluxmap) unsigned physicalHead = ltl->physicalHead;
goto erase;
fluxSink.writeFlux( log(BeginWriteOperationLogMessage{
physicalCylinder, physicalHead, *fluxmap); physicalCylinder, ltl->physicalHead});
log("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
else
{
erase:
/* Erase this track rather than writing. */
Fluxmap blank; if (offset == globalConfig()->drive().group_offset())
fluxSink.writeFlux(physicalCylinder, physicalHead, blank); {
log("erased"); auto fluxmap = producer(ltl);
if (!fluxmap)
goto erase;
fluxSink->addFlux(
physicalCylinder, physicalHead, *fluxmap);
log("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
else
{
erase:
/* Erase this track rather than writing. */
Fluxmap blank;
fluxSink->addFlux(
physicalCylinder, physicalHead, blank);
log("erased");
}
log(EndWriteOperationLogMessage());
} }
log(EndWriteOperationLogMessage()); if (verifier(ltl))
break;
if (retriesRemaining == 0)
error("fatal error on write");
log("retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
} }
if (verifier(ltl))
break;
if (retriesRemaining == 0)
error("fatal error on write");
log("retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
} }
} }
@@ -672,105 +676,111 @@ void readDiskCommand(const DiskLayout& diskLayout,
Decoder& decoder, Decoder& decoder,
DecodedDisk& decodedDisk) DecodedDisk& decodedDisk)
{ {
std::unique_ptr<FluxSink> outputFluxSink; std::unique_ptr<FluxSink> outputFluxSinkFactory;
if (globalConfig()->decoder().has_copy_flux_to()) if (globalConfig()->decoder().has_copy_flux_to())
outputFluxSink = outputFluxSinkFactory =
FluxSink::create(globalConfig()->decoder().copy_flux_to()); FluxSink::create(globalConfig()->decoder().copy_flux_to());
log(BeginOperationLogMessage{"Reading and decoding disk"}); log(BeginOperationLogMessage{"Reading and decoding disk"});
unsigned index = 0;
for (auto& [logicalLocation, ltl] : diskLayout.layoutByLogicalLocation)
{ {
log(OperationProgressLogMessage{ std::unique_ptr<FluxSink::Sink> outputFluxSink;
index * 100 / (unsigned)diskLayout.layoutByLogicalLocation.size()}); if (outputFluxSinkFactory)
index++; outputFluxSink = outputFluxSinkFactory->create();
unsigned index = 0;
testForEmergencyStop(); for (auto& [logicalLocation, ltl] : diskLayout.layoutByLogicalLocation)
auto [trackFluxes, trackSectors] =
readAndDecodeTrack(diskLayout, fluxSource, decoder, ltl);
for (const auto& flux : trackFluxes)
decodedDisk.tracksByPhysicalLocation.emplace(
CylinderHead{
flux->ptl->physicalCylinder, flux->ptl->physicalHead},
flux);
for (const auto& sector : trackSectors)
decodedDisk.sectorsByPhysicalLocation.emplace(
sector->physicalLocation.value(), sector);
if (outputFluxSink)
{ {
for (const auto& data : trackFluxes) log(OperationProgressLogMessage{
outputFluxSink->writeFlux(data->ptl->physicalCylinder, index * 100 /
data->ptl->physicalHead, (unsigned)diskLayout.layoutByLogicalLocation.size()});
*data->fluxmap); index++;
}
if (globalConfig()->decoder().dump_records()) testForEmergencyStop();
{
std::vector<std::shared_ptr<const Record>> sorted_records;
for (const auto& data : trackFluxes) auto [trackFluxes, trackSectors] =
sorted_records.insert(sorted_records.end(), readAndDecodeTrack(diskLayout, fluxSource, decoder, ltl);
data->records.begin(), for (const auto& flux : trackFluxes)
data->records.end()); decodedDisk.tracksByPhysicalLocation.emplace(
CylinderHead{
flux->ptl->physicalCylinder, flux->ptl->physicalHead},
flux);
for (const auto& sector : trackSectors)
decodedDisk.sectorsByPhysicalLocation.emplace(
sector->physicalLocation.value(), sector);
std::sort(sorted_records.begin(), if (outputFluxSink)
sorted_records.end(),
[](const auto& o1, const auto& o2)
{
return o1->startTime < o2->startTime;
});
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
{ {
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n", for (const auto& data : trackFluxes)
record->startTime / 1000.0, outputFluxSink->addFlux(data->ptl->physicalCylinder,
record->clock / 1000.0); data->ptl->physicalHead,
hexdump(std::cout, record->rawData); *data->fluxmap);
std::cout << std::endl;
} }
}
if (globalConfig()->decoder().dump_sectors()) if (globalConfig()->decoder().dump_records())
{
auto sectors = collectSectors(trackSectors, false);
std::ranges::sort(sectors,
[](const auto& o1, const auto& o2)
{
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sectors)
{ {
std::cout << fmt::format( std::vector<std::shared_ptr<const Record>> sorted_records;
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n", for (const auto& data : trackFluxes)
sector->logicalCylinder, sorted_records.insert(sorted_records.end(),
sector->logicalHead, data->records.begin(),
sector->logicalSector, data->records.end());
sector->headerStartTime / 1000.0,
sector->clock / 1000.0, std::sort(sorted_records.begin(),
Sector::statusToString(sector->status)); sorted_records.end(),
hexdump(std::cout, sector->data); [](const auto& o1, const auto& o2)
std::cout << std::endl; {
return o1->startTime < o2->startTime;
});
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record->startTime / 1000.0,
record->clock / 1000.0);
hexdump(std::cout, record->rawData);
std::cout << std::endl;
}
} }
if (globalConfig()->decoder().dump_sectors())
{
auto sectors = collectSectors(trackSectors, false);
std::ranges::sort(sectors,
[](const auto& o1, const auto& o2)
{
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sectors)
{
std::cout << fmt::format(
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n",
sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector,
sector->headerStartTime / 1000.0,
sector->clock / 1000.0,
Sector::statusToString(sector->status));
hexdump(std::cout, sector->data);
std::cout << std::endl;
}
}
/* track can't be modified below this point. */
log(TrackReadLogMessage{trackFluxes, trackSectors});
std::vector<std::shared_ptr<const Sector>> all_sectors;
for (auto& [ch, sector] : decodedDisk.sectorsByPhysicalLocation)
all_sectors.push_back(sector);
all_sectors = collectSectors(all_sectors);
decodedDisk.image = std::make_shared<Image>(all_sectors);
/* Log a _copy_ of the decodedDisk structure so that the logger
* doesn't see the decodedDisk get mutated in subsequent reads. */
log(DiskReadLogMessage{std::make_shared<DecodedDisk>(decodedDisk)});
} }
/* track can't be modified below this point. */
log(TrackReadLogMessage{trackFluxes, trackSectors});
std::vector<std::shared_ptr<const Sector>> all_sectors;
for (auto& [ch, sector] : decodedDisk.sectorsByPhysicalLocation)
all_sectors.push_back(sector);
all_sectors = collectSectors(all_sectors);
decodedDisk.image = std::make_shared<Image>(all_sectors);
/* Log a _copy_ of the decodedDisk structure so that the logger doesn't
* see the decodedDisk get mutated in subsequent reads. */
log(DiskReadLogMessage{std::make_shared<DecodedDisk>(decodedDisk)});
} }
if (!decodedDisk.image) if (!decodedDisk.image)

View File

@@ -17,227 +17,243 @@
#include <sys/types.h> #include <sys/types.h>
#include "fmt/chrono.h" #include "fmt/chrono.h"
namespace static uint32_t ticks_to_a2r(uint32_t ticks)
{ {
uint32_t ticks_to_a2r(uint32_t ticks) return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
}
class A2RSink : public FluxSink::Sink
{
public:
A2RSink(const std::string& filename):
_filename(filename),
_bytes{},
_writer(_bytes.writer())
{ {
return ticks * NS_PER_TICK / A2R_NS_PER_TICK; time_t now{std::time(nullptr)};
auto t = gmtime(&now);
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
} }
class A2RFluxSink : public FluxSink ~A2RSink()
{ {
public: // FIXME: should use a passed-in DiskLayout object.
A2RFluxSink(const A2RFluxSinkProto& lconfig): auto diskLayout = createDiskLayout();
_config(lconfig), auto [minCylinder, maxCylinder, minHead, maxHead] =
_bytes{}, diskLayout->getPhysicalBounds();
_writer{_bytes.writer()}
_minCylinder = minCylinder;
_maxCylinder = maxCylinder;
_minHead = minHead;
_maxHead = maxHead;
log("A2R: writing A2R {} file containing {} tracks...",
(_minHead == _maxHead) ? "single sided" : "double sided",
_maxCylinder - _minCylinder + 1);
writeHeader();
writeInfo();
writeStream();
writeMeta();
std::ofstream of(_filename, std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
_bytes.writeTo(of);
of.close();
}
private:
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
{
_writer.write_le32(chunk_id);
_writer.write_le32(data.size());
_writer += data;
}
void writeHeader()
{
static const uint8_t a2r2_fileheader[] = {
'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
}
void writeInfo()
{
Bytes info;
auto writer = info.writer();
writer.write_8(A2R_INFO_CHUNK_VERSION);
auto version_str_padded = fmt::format("{: <32}", "FluxEngine");
assert(version_str_padded.size() == 32);
writer.append(version_str_padded);
writer.write_8(
(globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
? A2R_DISK_525
: A2R_DISK_35);
writer.write_8(1); // write protected
writer.write_8(1); // synchronized
writeChunkAndData(A2R_CHUNK_INFO, info);
}
void writeMeta()
{
Bytes meta;
auto writer = meta.writer();
for (auto& i : _metadata)
{ {
time_t now{std::time(nullptr)}; writer.append(i.first);
auto t = gmtime(&now); writer.write_8('\t');
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t); writer.append(i.second);
writer.write_8('\n');
}
writeChunkAndData(A2R_CHUNK_META, meta);
}
void writeStream()
{
// A STRM always ends with a 255, even though this could ALSO
// indicate the first byte of a multi-byte sequence
_strmWriter.write_8(255);
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
}
public:
void addFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
if (!fluxmap.bytes())
{
return;
} }
~A2RFluxSink() // Writing from an image (as opposed to from a floppy) will
// contain exactly one revolution and no index events.
auto is_image = [](auto& fluxmap)
{ {
// FIXME: should use a passed-in DiskLayout object.
auto diskLayout = createDiskLayout();
auto [minCylinder, maxCylinder, minHead, maxHead] =
diskLayout->getPhysicalBounds();
_minCylinder = minCylinder;
_maxCylinder = maxCylinder;
_minHead = minHead;
_maxHead = maxHead;
log("A2R: writing A2R {} file containing {} tracks...",
(_minHead == _maxHead) ? "single sided" : "double sided",
_maxCylinder - _minCylinder + 1);
writeHeader();
writeInfo();
writeStream();
writeMeta();
std::ofstream of(
_config.filename(), std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
_bytes.writeTo(of);
of.close();
}
private:
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
{
_writer.write_le32(chunk_id);
_writer.write_le32(data.size());
_writer += data;
}
void writeHeader()
{
static const uint8_t a2r2_fileheader[] = {
'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
}
void writeInfo()
{
Bytes info;
auto writer = info.writer();
writer.write_8(A2R_INFO_CHUNK_VERSION);
auto version_str_padded = fmt::format("{: <32}", "FluxEngine");
assert(version_str_padded.size() == 32);
writer.append(version_str_padded);
writer.write_8(
(globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
? A2R_DISK_525
: A2R_DISK_35);
writer.write_8(1); // write protected
writer.write_8(1); // synchronized
writeChunkAndData(A2R_CHUNK_INFO, info);
}
void writeMeta()
{
Bytes meta;
auto writer = meta.writer();
for (auto& i : _metadata)
{
writer.append(i.first);
writer.write_8('\t');
writer.append(i.second);
writer.write_8('\n');
}
writeChunkAndData(A2R_CHUNK_META, meta);
}
void writeStream()
{
// A STRM always ends with a 255, even though this could ALSO
// indicate the first byte of a multi-byte sequence
_strmWriter.write_8(255);
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
}
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
if (!fluxmap.bytes())
{
return;
}
// Writing from an image (as opposed to from a floppy) will contain
// exactly one revolution and no index events.
auto is_image = [](auto& fluxmap)
{
FluxmapReader fmr(fluxmap);
fmr.skipToEvent(F_BIT_INDEX);
// but maybe there is no index, if we're writing from an image
// to an a2r
return fmr.eof();
};
// Write the flux data into its own Bytes
Bytes trackBytes;
auto trackWriter = trackBytes.writer();
auto write_one_flux = [&](unsigned ticks)
{
auto value = ticks_to_a2r(ticks);
while (value > 254)
{
trackWriter.write_8(255);
value -= 255;
}
trackWriter.write_8(value);
};
int revolution = 0;
uint32_t loopPoint = 0;
uint32_t totalTicks = 0;
FluxmapReader fmr(fluxmap); FluxmapReader fmr(fluxmap);
fmr.skipToEvent(F_BIT_INDEX);
// but maybe there is no index, if we're writing from an
// image to an a2r
return fmr.eof();
};
auto write_flux = [&](unsigned maxTicks = ~0u) // Write the flux data into its own Bytes
{ Bytes trackBytes;
unsigned ticksSinceLastPulse = 0; auto trackWriter = trackBytes.writer();
while (!fmr.eof() && totalTicks < maxTicks) auto write_one_flux = [&](unsigned ticks)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
if (event & F_BIT_PULSE)
{
write_one_flux(ticksSinceLastPulse);
ticksSinceLastPulse = 0;
}
if (event & F_BIT_INDEX && revolution == 0)
{
loopPoint = totalTicks;
revolution += 1;
}
}
};
if (is_image(fluxmap))
{
// A timing stream with no index represents exactly one
// revolution with no index. However, a2r nominally contains 450
// degress of rotation, 250ms at 300rpm.
write_flux();
loopPoint = totalTicks;
fmr.rewind();
revolution += 1;
write_flux(totalTicks * 5 / 4);
}
else
{
// We have an index, so this is a real read from a floppy and
// should be "one revolution plus a bit"
fmr.skipToEvent(F_BIT_INDEX);
write_flux();
}
uint32_t chunk_size = 10 + trackBytes.size();
if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
_strmWriter.write_8(cylinder);
else
_strmWriter.write_8((cylinder << 1) | head);
_strmWriter.write_8(A2R_TIMING);
_strmWriter.write_le32(trackBytes.size());
_strmWriter.write_le32(ticks_to_a2r(loopPoint));
_strmWriter += trackBytes;
}
operator std::string() const override
{ {
return fmt::format("a2r({})", _config.filename()); auto value = ticks_to_a2r(ticks);
while (value > 254)
{
trackWriter.write_8(255);
value -= 255;
}
trackWriter.write_8(value);
};
int revolution = 0;
uint32_t loopPoint = 0;
uint32_t totalTicks = 0;
FluxmapReader fmr(fluxmap);
auto write_flux = [&](unsigned maxTicks = ~0u)
{
unsigned ticksSinceLastPulse = 0;
while (!fmr.eof() && totalTicks < maxTicks)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
if (event & F_BIT_PULSE)
{
write_one_flux(ticksSinceLastPulse);
ticksSinceLastPulse = 0;
}
if (event & F_BIT_INDEX && revolution == 0)
{
loopPoint = totalTicks;
revolution += 1;
}
}
};
if (is_image(fluxmap))
{
// A timing stream with no index represents exactly one
// revolution with no index. However, a2r nominally contains
// 450 degress of rotation, 250ms at 300rpm.
write_flux();
loopPoint = totalTicks;
fmr.rewind();
revolution += 1;
write_flux(totalTicks * 5 / 4);
}
else
{
// We have an index, so this is a real read from a floppy
// and should be "one revolution plus a bit"
fmr.skipToEvent(F_BIT_INDEX);
write_flux();
} }
private: uint32_t chunk_size = 10 + trackBytes.size();
const A2RFluxSinkProto& _config;
Bytes _bytes; if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
ByteWriter _writer; _strmWriter.write_8(cylinder);
Bytes _strmBytes; else
ByteWriter _strmWriter{_strmBytes.writer()}; _strmWriter.write_8((cylinder << 1) | head);
std::map<std::string, std::string> _metadata;
int _minHead; _strmWriter.write_8(A2R_TIMING);
int _maxHead; _strmWriter.write_le32(trackBytes.size());
int _minCylinder; _strmWriter.write_le32(ticks_to_a2r(loopPoint));
int _maxCylinder; _strmWriter += trackBytes;
}; }
} // namespace
private:
std::string _filename;
Bytes _bytes;
ByteWriter _writer;
Bytes _strmBytes;
ByteWriter _strmWriter{_strmBytes.writer()};
std::map<std::string, std::string> _metadata;
int _minHead;
int _maxHead;
int _minCylinder;
int _maxCylinder;
};
class A2RFluxSink : public FluxSink
{
public:
A2RFluxSink(const A2RFluxSinkProto& lconfig): _config(lconfig) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<A2RSink>(_config.filename());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.filename());
}
operator std::string() const override
{
return fmt::format("a2r({})", _config.filename());
}
private:
const A2RFluxSinkProto& _config;
};
std::unique_ptr<FluxSink> FluxSink::createA2RFluxSink( std::unique_ptr<FluxSink> FluxSink::createA2RFluxSink(
const A2RFluxSinkProto& config) const A2RFluxSinkProto& config)

View File

@@ -1,4 +1,5 @@
#include "lib/core/globals.h" #include "lib/core/globals.h"
#include "lib/core/logger.h"
#include "lib/config/flags.h" #include "lib/config/flags.h"
#include "lib/data/fluxmap.h" #include "lib/data/fluxmap.h"
#include "lib/core/bytes.h" #include "lib/core/bytes.h"
@@ -11,27 +12,29 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
class AuFluxSink : public FluxSink class AuSink : public FluxSink::Sink
{ {
public: public:
AuFluxSink(const AuFluxSinkProto& config): _config(config) {} AuSink(const std::string& directory, bool indexMarkers):
_directory(directory),
~AuFluxSink() _indexMarkers(indexMarkers)
{ {
std::cerr << "Warning: do not play these files, or you will break your "
"speakers and/or ears!\n";
} }
public: ~AuSink()
void writeFlux(int track, int head, const Fluxmap& fluxmap) override {
log("Warning: do not play these files, or you will break your "
"speakers and/or ears!");
}
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{ {
unsigned totalTicks = fluxmap.ticks() + 2; unsigned totalTicks = fluxmap.ticks() + 2;
unsigned channels = _config.index_markers() ? 2 : 1; unsigned channels = _indexMarkers ? 2 : 1;
mkdir(_config.directory().c_str(), 0744); mkdir(_directory.c_str(), 0744);
std::ofstream of( std::ofstream of(
fmt::format( fmt::format("{}/c{:02d}.h{:01d}.au", _directory, track, head),
"{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
std::ios::out | std::ios::binary); std::ios::out | std::ios::binary);
if (!of.is_open()) if (!of.is_open())
error("cannot open output file"); error("cannot open output file");
@@ -73,7 +76,7 @@ public:
if (event & F_BIT_PULSE) if (event & F_BIT_PULSE)
data[timestamp * channels + 0] = 0x7f; data[timestamp * channels + 0] = 0x7f;
if (_config.index_markers() && (event & F_BIT_INDEX)) if (_indexMarkers && (event & F_BIT_INDEX))
data[timestamp * channels + 1] = 0x7f; data[timestamp * channels + 1] = 0x7f;
} }
@@ -81,6 +84,27 @@ public:
} }
} }
private:
std::string _directory;
bool _indexMarkers;
};
class AuFluxSink : public FluxSink
{
public:
AuFluxSink(const AuFluxSinkProto& config): _config(config) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<AuSink>(
_config.directory(), _config.index_markers());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.directory());
}
operator std::string() const override operator std::string() const override
{ {
return fmt::format("au({})", _config.directory()); return fmt::format("au({})", _config.directory());

View File

@@ -16,15 +16,10 @@
#include <sys/types.h> #include <sys/types.h>
#include <filesystem> #include <filesystem>
class Fl2FluxSink : public FluxSink class Fl2Sink : public FluxSink::Sink
{ {
public: public:
Fl2FluxSink(const Fl2FluxSinkProto& lconfig): Fl2Sink(const std::string& filename): _filename(filename)
Fl2FluxSink(lconfig.filename())
{
}
Fl2FluxSink(const std::string& filename): _filename(filename)
{ {
std::ofstream of(filename); std::ofstream of(filename);
if (!of.is_open()) if (!of.is_open())
@@ -33,7 +28,7 @@ public:
std::filesystem::remove(filename); std::filesystem::remove(filename);
} }
~Fl2FluxSink() ~Fl2Sink()
{ {
log("FL2: writing {}", _filename); log("FL2: writing {}", _filename);
@@ -54,27 +49,46 @@ public:
saveFl2File(_filename, proto); saveFl2File(_filename, proto);
} }
public: void addFlux(int track, int head, const Fluxmap& fluxmap) override
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{ {
auto& vector = _data[std::make_pair(track, head)]; auto& vector = _data[std::make_pair(track, head)];
vector.push_back(fluxmap.rawBytes()); vector.push_back(fluxmap.rawBytes());
} }
operator std::string() const override
{
return fmt::format("fl2({})", _filename);
}
private: private:
std::string _filename; std::string _filename;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data; std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
}; };
class Fl2FluxSink : public FluxSink
{
public:
Fl2FluxSink(const std::string& filename): _filename(filename) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<Fl2Sink>(_filename);
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_filename);
}
public:
operator std::string() const override
{
return fmt::format("fl2({})", _filename);
}
private:
const std::string _filename;
};
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink( std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
const Fl2FluxSinkProto& config) const Fl2FluxSinkProto& config)
{ {
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config)); return std::unique_ptr<FluxSink>(new Fl2FluxSink(config.filename()));
} }
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink( std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(

View File

@@ -4,6 +4,7 @@
#include "lib/config/flags.h" #include "lib/config/flags.h"
#include "lib/data/locations.h" #include "lib/data/locations.h"
#include <ostream> #include <ostream>
#include <filesystem>
class Fluxmap; class Fluxmap;
class FluxSinkProto; class FluxSinkProto;
@@ -41,13 +42,26 @@ public:
static std::unique_ptr<FluxSink> create(const FluxSinkProto& config); static std::unique_ptr<FluxSink> create(const FluxSinkProto& config);
public: public:
/* Writes a fluxmap to a track and side. */ class Sink
virtual void writeFlux(int track, int side, const Fluxmap& fluxmap) = 0;
void writeFlux(const CylinderHead& location, const Fluxmap& fluxmap)
{ {
writeFlux(location.cylinder, location.head, fluxmap); public:
} Sink() {}
virtual ~Sink() {}
public:
/* Writes a fluxmap to a track and side. */
virtual void addFlux(int track, int side, const Fluxmap& fluxmap) = 0;
void addFlux(const CylinderHead& location, const Fluxmap& fluxmap)
{
addFlux(location.cylinder, location.head, fluxmap);
}
};
public:
/* Creates a writer object. */
virtual std::unique_ptr<Sink> create() = 0;
/* Returns whether this is writing to real hardware or not. */ /* Returns whether this is writing to real hardware or not. */
@@ -56,6 +70,14 @@ public:
return false; return false;
} }
/* Returns the path (filename or directory) being written to, if there is
* one. */
virtual std::optional<std::filesystem::path> getPath() const
{
return {};
}
virtual operator std::string() const = 0; virtual operator std::string() const = 0;
}; };

View File

@@ -8,15 +8,9 @@
#include "lib/fluxsink/fluxsink.h" #include "lib/fluxsink/fluxsink.h"
#include "lib/fluxsink/fluxsink.pb.h" #include "lib/fluxsink/fluxsink.pb.h"
class HardwareFluxSink : public FluxSink class HardwareSink : public FluxSink::Sink
{ {
public: void addFlux(int track, int side, const Fluxmap& fluxmap) override
HardwareFluxSink(const HardwareFluxSinkProto& conf): _config(conf) {}
~HardwareFluxSink() {}
public:
void writeFlux(int track, int side, const Fluxmap& fluxmap) override
{ {
auto& drive = globalConfig()->drive(); auto& drive = globalConfig()->drive();
usbSetDrive(drive.drive(), drive.high_density(), drive.index_mode()); usbSetDrive(drive.drive(), drive.high_density(), drive.index_mode());
@@ -25,6 +19,15 @@ public:
return usbWrite( return usbWrite(
side, fluxmap.rawBytes(), drive.hard_sector_threshold_ns()); side, fluxmap.rawBytes(), drive.hard_sector_threshold_ns());
} }
};
class HardwareFluxSink : public FluxSink
{
public:
std::unique_ptr<Sink> create() override
{
return std::make_unique<HardwareSink>();
}
bool isHardware() const override bool isHardware() const override
{ {
@@ -33,15 +36,12 @@ public:
operator std::string() const override operator std::string() const override
{ {
return fmt::format("drive {}", globalConfig()->drive().drive()); return "hardware {}";
} }
private:
const HardwareFluxSinkProto& _config;
}; };
std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink( std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink(
const HardwareFluxSinkProto& config) const HardwareFluxSinkProto& config)
{ {
return std::unique_ptr<FluxSink>(new HardwareFluxSink(config)); return std::unique_ptr<FluxSink>();
} }

View File

@@ -36,10 +36,13 @@ static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
checksum += br.read_8(); checksum += br.read_8();
} }
class ScpFluxSink : public FluxSink class ScpSink : public FluxSink::Sink
{ {
public: public:
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig) ScpSink(const std::string& filename, uint8_t typeByte, bool alignWithIndex):
_filename(filename),
_typeByte(typeByte),
_alignWithIndex(alignWithIndex)
{ {
// FIXME: should use a passed-in DiskLayout object. // FIXME: should use a passed-in DiskLayout object.
auto diskLayout = createDiskLayout(); auto diskLayout = createDiskLayout();
@@ -50,7 +53,7 @@ public:
_fileheader.file_id[1] = 'C'; _fileheader.file_id[1] = 'C';
_fileheader.file_id[2] = 'P'; _fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */ _fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _config.type_byte(); _fileheader.type = _typeByte;
_fileheader.start_track = strackno(minCylinder, minHead); _fileheader.start_track = strackno(minCylinder, minHead);
_fileheader.end_track = strackno(maxCylinder, maxHead); _fileheader.end_track = strackno(maxCylinder, maxHead);
_fileheader.flags = SCP_FLAG_INDEXED; _fileheader.flags = SCP_FLAG_INDEXED;
@@ -72,7 +75,7 @@ public:
_fileheader.end_track - _fileheader.start_track + 1); _fileheader.end_track - _fileheader.start_track + 1);
} }
~ScpFluxSink() ~ScpSink()
{ {
uint32_t checksum = 0; uint32_t checksum = 0;
appendChecksum(checksum, appendChecksum(checksum,
@@ -82,7 +85,7 @@ public:
write_le32(_fileheader.checksum, checksum); write_le32(_fileheader.checksum, checksum);
log("SCP: writing output file"); log("SCP: writing output file");
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary); std::ofstream of(_filename, std::ios::out | std::ios::binary);
if (!of.is_open()) if (!of.is_open())
error("cannot open output file"); error("cannot open output file");
of.write((const char*)&_fileheader, sizeof(_fileheader)); of.write((const char*)&_fileheader, sizeof(_fileheader));
@@ -90,8 +93,7 @@ public:
of.close(); of.close();
} }
public: void addFlux(int track, int head, const Fluxmap& fluxmap) override
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{ {
ByteWriter trackdataWriter(_trackdata); ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd(); trackdataWriter.seekToEnd();
@@ -99,7 +101,8 @@ public:
if (strack >= std::size(_fileheader.track)) if (strack >= std::size(_fileheader.track))
{ {
log("SCP: cannot write track {} head {}, there are not not enough " log("SCP: cannot write track {} head {}, there are not not "
"enough "
"Track Data Headers.", "Track Data Headers.",
track, track,
head); head);
@@ -117,7 +120,7 @@ public:
int revolution = int revolution =
-1; // -1 indicates that we are before the first index pulse -1; // -1 indicates that we are before the first index pulse
if (_config.align_with_index()) if (_alignWithIndex)
{ {
fmr.skipToEvent(F_BIT_INDEX); fmr.skipToEvent(F_BIT_INDEX);
revolution = 0; revolution = 0;
@@ -136,9 +139,9 @@ public:
totalTicks += ticks; totalTicks += ticks;
revTicks += ticks; revTicks += ticks;
// if we haven't output any revolutions yet by the end of the track, // if we haven't output any revolutions yet by the end of the
// assume that the whole track is one rev // track, assume that the whole track is one rev also discard
// also discard any duplicate index pulses // any duplicate index pulses
if (((fmr.eof() && revolution <= 0) || if (((fmr.eof() && revolution <= 0) ||
((event & F_BIT_INDEX)) && revTicks > 0)) ((event & F_BIT_INDEX)) && revTicks > 0))
{ {
@@ -181,6 +184,32 @@ public:
trackdataWriter += fluxdata; trackdataWriter += fluxdata;
} }
private:
std::string _filename;
uint8_t _typeByte;
bool _alignWithIndex;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
class ScpFluxSink : public FluxSink
{
public:
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<ScpSink>(_config.filename(),
_config.type_byte(),
_config.align_with_index());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.filename());
}
public:
operator std::string() const override operator std::string() const override
{ {
return fmt::format("scp({})", _config.filename()); return fmt::format("scp({})", _config.filename());
@@ -188,8 +217,6 @@ public:
private: private:
const ScpFluxSinkProto& _config; const ScpFluxSinkProto& _config;
ScpHeader _fileheader = {0};
Bytes _trackdata;
}; };
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink( std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(

View File

@@ -11,18 +11,16 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
class VcdFluxSink : public FluxSink class VcdSink : public FluxSink::Sink
{ {
public: public:
VcdFluxSink(const VcdFluxSinkProto& config): _config(config) {} VcdSink(const std::string& directory): _directory(directory) {}
public: void addFlux(int track, int head, const Fluxmap& fluxmap) override
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{ {
mkdir(_config.directory().c_str(), 0744); mkdir(_directory.c_str(), 0744);
std::ofstream of( std::ofstream of(
fmt::format( fmt::format("{}/c{:02d}.h{:01d}.vcd", _directory, track, head),
"{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
std::ios::out | std::ios::binary); std::ios::out | std::ios::binary);
if (!of.is_open()) if (!of.is_open())
error("cannot open output file"); error("cannot open output file");
@@ -64,6 +62,26 @@ public:
of << "\n"; of << "\n";
} }
private:
const std::string _directory;
};
class VcdFluxSink : public FluxSink
{
public:
VcdFluxSink(const VcdFluxSinkProto& config): _config(config) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<VcdSink>(_config.directory());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.directory());
}
public:
operator std::string() const override operator std::string() const override
{ {
return fmt::format("vcd({})", _config.directory()); return fmt::format("vcd({})", _config.directory());

View File

@@ -53,13 +53,14 @@ int mainConvert(int argc, const char* argv[])
minHead, minHead,
maxHead); maxHead);
auto fluxSink = FluxSink::create(globalConfig()); auto fluxSinkFactory = FluxSink::create(globalConfig());
auto fluxSink = fluxSinkFactory->create();
for (const auto& physicalLocation : diskLayout->physicalLocations) for (const auto& physicalLocation : diskLayout->physicalLocations)
{ {
auto fi = fluxSource->readFlux(physicalLocation); auto fi = fluxSource->readFlux(physicalLocation);
while (fi->hasNext()) while (fi->hasNext())
fluxSink->writeFlux(physicalLocation, *fi->next()); fluxSink->addFlux(physicalLocation, *fi->next());
} }
return 0; return 0;

View File

@@ -3,6 +3,7 @@
#include <hex/api/imhex_api/hex_editor.hpp> #include <hex/api/imhex_api/hex_editor.hpp>
#include <hex/api/content_registry/settings.hpp> #include <hex/api/content_registry/settings.hpp>
#include <hex/api/task_manager.hpp> #include <hex/api/task_manager.hpp>
#include <popups/popup_question.hpp>
#include <toasts/toast_notification.hpp> #include <toasts/toast_notification.hpp>
#include "lib/core/globals.h" #include "lib/core/globals.h"
#include "lib/core/utils.h" #include "lib/core/utils.h"
@@ -580,6 +581,29 @@ void Datastore::beginWrite()
globalConfig().getVerificationFluxSourceProto()); globalConfig().getVerificationFluxSourceProto());
} }
auto path = fluxSink->getPath();
if (path.has_value() && std::filesystem::exists(*path))
{
{
bool result;
wtRunSynchronouslyOnUiThread((
std::function<void()>)[&] {
hex::ui::PopupQuestion::open(
"fluxengine.messages.writingFluxToFile"_lang,
[&]
{
result = true;
},
[&]
{
result = false;
});
});
if (!result)
throw EmergencyStopException();
}
}
auto image = diskFlux->image; auto image = diskFlux->image;
writeDiskCommand(*diskLayout, writeDiskCommand(*diskLayout,
*image, *image,
@@ -717,7 +741,9 @@ void Datastore::writeFluxFile(const std::fs::path& path)
error("no loaded image"); error("no loaded image");
if (diskFlux->image->getGeometry().totalBytes != if (diskFlux->image->getGeometry().totalBytes !=
diskLayout->totalBytes) diskLayout->totalBytes)
error("loaded image is not the right size for this format"); error(
"loaded image is not the right size for this "
"format");
globalConfig().setFluxSink(path.string()); globalConfig().setFluxSink(path.string());
auto fluxSource = FluxSource::createMemoryFluxSource(*diskFlux); auto fluxSource = FluxSource::createMemoryFluxSource(*diskFlux);

View File

@@ -101,9 +101,9 @@ void DiskProvider::writeRaw(u64 offset, const void* buffer, size_t size)
[[nodiscard]] u64 DiskProvider::getActualSize() const [[nodiscard]] u64 DiskProvider::getActualSize() const
{ {
const auto& diskFlux = Datastore::getDecodedDisk(); const auto& diskLayout = Datastore::getDiskLayout();
if (diskFlux && diskFlux->image) if (diskLayout)
return diskFlux->image->getGeometry().totalBytes; return diskLayout->totalBytes;
return 0; return 0;
} }

View File

@@ -52,5 +52,7 @@
"fluxengine.view.status.writeFlux": "Writing flux file to disk", "fluxengine.view.status.writeFlux": "Writing flux file to disk",
"fluxengine.view.status.readImage": "Reading image file from disk", "fluxengine.view.status.readImage": "Reading image file from disk",
"fluxengine.view.status.writeImage": "Writing image file to disk", "fluxengine.view.status.writeImage": "Writing image file to disk",
"fluxengine.view.status.blankFilesystem": "Creating blank filesystem" "fluxengine.view.status.blankFilesystem": "Creating blank filesystem",
"fluxengine.messages.writingFluxToFile": "The current configuration is to write to a flux file rather than to hardware. Is this what you intended?"
} }

View File

@@ -271,7 +271,8 @@ int main(int argc, const char* argv[])
int version = sqlGetVersion(db); int version = sqlGetVersion(db);
{ {
auto fluxsink = FluxSink::createFl2FluxSink(outFilename); auto fluxSink = FluxSink::createFl2FluxSink(outFilename)->create();
for (const auto& locations : sqlFindFlux(db)) for (const auto& locations : sqlFindFlux(db))
{ {
unsigned cylinder = locations.first; unsigned cylinder = locations.first;
@@ -295,7 +296,7 @@ int main(int argc, const char* argv[])
"bug)", "bug)",
version); version);
} }
fluxsink->writeFlux(cylinder, head, fluxmap); fluxSink->addFlux(cylinder, head, fluxmap);
std::cout << '.' << std::flush; std::cout << '.' << std::flush;
} }