mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-24 11:11:02 -07:00
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:
@@ -52,7 +52,7 @@ need to apply extra options to change the format if desired.
|
||||
|
||||
## Options
|
||||
|
||||
- :
|
||||
- $format:
|
||||
- `143`: 143kB 5.25" SSDD 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
|
||||
|
||||
@@ -410,7 +410,7 @@ static ReadGroupResult readGroup(const DiskLayout& diskLayout,
|
||||
|
||||
void writeTracks(const DiskLayout& diskLayout,
|
||||
|
||||
FluxSink& fluxSink,
|
||||
FluxSink& fluxSinkFactory,
|
||||
std::function<std::unique_ptr<const Fluxmap>(
|
||||
const std::shared_ptr<const LogicalTrackLayout>&)> producer,
|
||||
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"});
|
||||
|
||||
if (fluxSink.isHardware())
|
||||
if (fluxSinkFactory.isHardware())
|
||||
measureDiskRotation();
|
||||
int index = 0;
|
||||
for (auto& ch : logicalLocations)
|
||||
{
|
||||
log(OperationProgressLogMessage{
|
||||
index * 100 / (unsigned)logicalLocations.size()});
|
||||
index++;
|
||||
|
||||
testForEmergencyStop();
|
||||
|
||||
const auto& ltl = diskLayout.layoutByLogicalLocation.at(ch);
|
||||
int retriesRemaining = globalConfig()->decoder().retries();
|
||||
for (;;)
|
||||
auto fluxSink = fluxSinkFactory.create();
|
||||
int index = 0;
|
||||
for (auto& ch : logicalLocations)
|
||||
{
|
||||
for (int offset = 0; offset < ltl->groupSize;
|
||||
offset += diskLayout.headWidth)
|
||||
log(OperationProgressLogMessage{
|
||||
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;
|
||||
unsigned physicalHead = ltl->physicalHead;
|
||||
|
||||
log(BeginWriteOperationLogMessage{
|
||||
physicalCylinder, ltl->physicalHead});
|
||||
|
||||
if (offset == globalConfig()->drive().group_offset())
|
||||
for (int offset = 0; offset < ltl->groupSize;
|
||||
offset += diskLayout.headWidth)
|
||||
{
|
||||
auto fluxmap = producer(ltl);
|
||||
if (!fluxmap)
|
||||
goto erase;
|
||||
unsigned physicalCylinder = ltl->physicalCylinder + offset;
|
||||
unsigned physicalHead = ltl->physicalHead;
|
||||
|
||||
fluxSink.writeFlux(
|
||||
physicalCylinder, physicalHead, *fluxmap);
|
||||
log("writing {0} ms in {1} bytes",
|
||||
int(fluxmap->duration() / 1e6),
|
||||
fluxmap->bytes());
|
||||
}
|
||||
else
|
||||
{
|
||||
erase:
|
||||
/* Erase this track rather than writing. */
|
||||
log(BeginWriteOperationLogMessage{
|
||||
physicalCylinder, ltl->physicalHead});
|
||||
|
||||
Fluxmap blank;
|
||||
fluxSink.writeFlux(physicalCylinder, physicalHead, blank);
|
||||
log("erased");
|
||||
if (offset == globalConfig()->drive().group_offset())
|
||||
{
|
||||
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,
|
||||
DecodedDisk& decodedDisk)
|
||||
{
|
||||
std::unique_ptr<FluxSink> outputFluxSink;
|
||||
std::unique_ptr<FluxSink> outputFluxSinkFactory;
|
||||
if (globalConfig()->decoder().has_copy_flux_to())
|
||||
outputFluxSink =
|
||||
outputFluxSinkFactory =
|
||||
FluxSink::create(globalConfig()->decoder().copy_flux_to());
|
||||
|
||||
log(BeginOperationLogMessage{"Reading and decoding disk"});
|
||||
unsigned index = 0;
|
||||
for (auto& [logicalLocation, ltl] : diskLayout.layoutByLogicalLocation)
|
||||
{
|
||||
log(OperationProgressLogMessage{
|
||||
index * 100 / (unsigned)diskLayout.layoutByLogicalLocation.size()});
|
||||
index++;
|
||||
|
||||
testForEmergencyStop();
|
||||
|
||||
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)
|
||||
std::unique_ptr<FluxSink::Sink> outputFluxSink;
|
||||
if (outputFluxSinkFactory)
|
||||
outputFluxSink = outputFluxSinkFactory->create();
|
||||
unsigned index = 0;
|
||||
for (auto& [logicalLocation, ltl] : diskLayout.layoutByLogicalLocation)
|
||||
{
|
||||
for (const auto& data : trackFluxes)
|
||||
outputFluxSink->writeFlux(data->ptl->physicalCylinder,
|
||||
data->ptl->physicalHead,
|
||||
*data->fluxmap);
|
||||
}
|
||||
log(OperationProgressLogMessage{
|
||||
index * 100 /
|
||||
(unsigned)diskLayout.layoutByLogicalLocation.size()});
|
||||
index++;
|
||||
|
||||
if (globalConfig()->decoder().dump_records())
|
||||
{
|
||||
std::vector<std::shared_ptr<const Record>> sorted_records;
|
||||
testForEmergencyStop();
|
||||
|
||||
for (const auto& data : trackFluxes)
|
||||
sorted_records.insert(sorted_records.end(),
|
||||
data->records.begin(),
|
||||
data->records.end());
|
||||
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);
|
||||
|
||||
std::sort(sorted_records.begin(),
|
||||
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)
|
||||
if (outputFluxSink)
|
||||
{
|
||||
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;
|
||||
for (const auto& data : trackFluxes)
|
||||
outputFluxSink->addFlux(data->ptl->physicalCylinder,
|
||||
data->ptl->physicalHead,
|
||||
*data->fluxmap);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if (globalConfig()->decoder().dump_records())
|
||||
{
|
||||
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;
|
||||
std::vector<std::shared_ptr<const Record>> sorted_records;
|
||||
|
||||
for (const auto& data : trackFluxes)
|
||||
sorted_records.insert(sorted_records.end(),
|
||||
data->records.begin(),
|
||||
data->records.end());
|
||||
|
||||
std::sort(sorted_records.begin(),
|
||||
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",
|
||||
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)
|
||||
|
||||
@@ -17,227 +17,243 @@
|
||||
#include <sys/types.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:
|
||||
A2RFluxSink(const A2RFluxSinkProto& lconfig):
|
||||
_config(lconfig),
|
||||
_bytes{},
|
||||
_writer{_bytes.writer()}
|
||||
// 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(_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)};
|
||||
auto t = gmtime(&now);
|
||||
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
unsigned ticksSinceLastPulse = 0;
|
||||
// Write the flux data into its own Bytes
|
||||
Bytes trackBytes;
|
||||
auto trackWriter = trackBytes.writer();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
auto write_one_flux = [&](unsigned ticks)
|
||||
{
|
||||
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:
|
||||
const A2RFluxSinkProto& _config;
|
||||
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;
|
||||
};
|
||||
} // namespace
|
||||
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;
|
||||
}
|
||||
|
||||
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(
|
||||
const A2RFluxSinkProto& config)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "lib/core/globals.h"
|
||||
#include "lib/core/logger.h"
|
||||
#include "lib/config/flags.h"
|
||||
#include "lib/data/fluxmap.h"
|
||||
#include "lib/core/bytes.h"
|
||||
@@ -11,27 +12,29 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
class AuFluxSink : public FluxSink
|
||||
class AuSink : public FluxSink::Sink
|
||||
{
|
||||
public:
|
||||
AuFluxSink(const AuFluxSinkProto& config): _config(config) {}
|
||||
|
||||
~AuFluxSink()
|
||||
AuSink(const std::string& directory, bool indexMarkers):
|
||||
_directory(directory),
|
||||
_indexMarkers(indexMarkers)
|
||||
{
|
||||
std::cerr << "Warning: do not play these files, or you will break your "
|
||||
"speakers and/or ears!\n";
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
~AuSink()
|
||||
{
|
||||
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 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(
|
||||
fmt::format(
|
||||
"{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
|
||||
fmt::format("{}/c{:02d}.h{:01d}.au", _directory, track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
error("cannot open output file");
|
||||
@@ -73,7 +76,7 @@ public:
|
||||
|
||||
if (event & F_BIT_PULSE)
|
||||
data[timestamp * channels + 0] = 0x7f;
|
||||
if (_config.index_markers() && (event & F_BIT_INDEX))
|
||||
if (_indexMarkers && (event & F_BIT_INDEX))
|
||||
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
|
||||
{
|
||||
return fmt::format("au({})", _config.directory());
|
||||
|
||||
@@ -16,15 +16,10 @@
|
||||
#include <sys/types.h>
|
||||
#include <filesystem>
|
||||
|
||||
class Fl2FluxSink : public FluxSink
|
||||
class Fl2Sink : public FluxSink::Sink
|
||||
{
|
||||
public:
|
||||
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
|
||||
Fl2FluxSink(lconfig.filename())
|
||||
{
|
||||
}
|
||||
|
||||
Fl2FluxSink(const std::string& filename): _filename(filename)
|
||||
Fl2Sink(const std::string& filename): _filename(filename)
|
||||
{
|
||||
std::ofstream of(filename);
|
||||
if (!of.is_open())
|
||||
@@ -33,7 +28,7 @@ public:
|
||||
std::filesystem::remove(filename);
|
||||
}
|
||||
|
||||
~Fl2FluxSink()
|
||||
~Fl2Sink()
|
||||
{
|
||||
log("FL2: writing {}", _filename);
|
||||
|
||||
@@ -54,27 +49,46 @@ public:
|
||||
saveFl2File(_filename, proto);
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
void addFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
auto& vector = _data[std::make_pair(track, head)];
|
||||
vector.push_back(fluxmap.rawBytes());
|
||||
}
|
||||
|
||||
operator std::string() const override
|
||||
{
|
||||
return fmt::format("fl2({})", _filename);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _filename;
|
||||
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(
|
||||
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(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "lib/config/flags.h"
|
||||
#include "lib/data/locations.h"
|
||||
#include <ostream>
|
||||
#include <filesystem>
|
||||
|
||||
class Fluxmap;
|
||||
class FluxSinkProto;
|
||||
@@ -41,13 +42,26 @@ public:
|
||||
static std::unique_ptr<FluxSink> create(const FluxSinkProto& config);
|
||||
|
||||
public:
|
||||
/* Writes a fluxmap to a track and side. */
|
||||
|
||||
virtual void writeFlux(int track, int side, const Fluxmap& fluxmap) = 0;
|
||||
void writeFlux(const CylinderHead& location, const Fluxmap& fluxmap)
|
||||
class Sink
|
||||
{
|
||||
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. */
|
||||
|
||||
@@ -56,6 +70,14 @@ public:
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,15 +8,9 @@
|
||||
#include "lib/fluxsink/fluxsink.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
|
||||
class HardwareFluxSink : public FluxSink
|
||||
class HardwareSink : public FluxSink::Sink
|
||||
{
|
||||
public:
|
||||
HardwareFluxSink(const HardwareFluxSinkProto& conf): _config(conf) {}
|
||||
|
||||
~HardwareFluxSink() {}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int side, const Fluxmap& fluxmap) override
|
||||
void addFlux(int track, int side, const Fluxmap& fluxmap) override
|
||||
{
|
||||
auto& drive = globalConfig()->drive();
|
||||
usbSetDrive(drive.drive(), drive.high_density(), drive.index_mode());
|
||||
@@ -25,6 +19,15 @@ public:
|
||||
return usbWrite(
|
||||
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
|
||||
{
|
||||
@@ -33,15 +36,12 @@ public:
|
||||
|
||||
operator std::string() const override
|
||||
{
|
||||
return fmt::format("drive {}", globalConfig()->drive().drive());
|
||||
return "hardware {}";
|
||||
}
|
||||
|
||||
private:
|
||||
const HardwareFluxSinkProto& _config;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink(
|
||||
const HardwareFluxSinkProto& config)
|
||||
{
|
||||
return std::unique_ptr<FluxSink>(new HardwareFluxSink(config));
|
||||
return std::unique_ptr<FluxSink>();
|
||||
}
|
||||
|
||||
@@ -36,10 +36,13 @@ static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
|
||||
checksum += br.read_8();
|
||||
}
|
||||
|
||||
class ScpFluxSink : public FluxSink
|
||||
class ScpSink : public FluxSink::Sink
|
||||
{
|
||||
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.
|
||||
auto diskLayout = createDiskLayout();
|
||||
@@ -50,7 +53,7 @@ public:
|
||||
_fileheader.file_id[1] = 'C';
|
||||
_fileheader.file_id[2] = 'P';
|
||||
_fileheader.version = 0x18; /* Version 1.8 of the spec */
|
||||
_fileheader.type = _config.type_byte();
|
||||
_fileheader.type = _typeByte;
|
||||
_fileheader.start_track = strackno(minCylinder, minHead);
|
||||
_fileheader.end_track = strackno(maxCylinder, maxHead);
|
||||
_fileheader.flags = SCP_FLAG_INDEXED;
|
||||
@@ -72,7 +75,7 @@ public:
|
||||
_fileheader.end_track - _fileheader.start_track + 1);
|
||||
}
|
||||
|
||||
~ScpFluxSink()
|
||||
~ScpSink()
|
||||
{
|
||||
uint32_t checksum = 0;
|
||||
appendChecksum(checksum,
|
||||
@@ -82,7 +85,7 @@ public:
|
||||
write_le32(_fileheader.checksum, checksum);
|
||||
|
||||
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())
|
||||
error("cannot open output file");
|
||||
of.write((const char*)&_fileheader, sizeof(_fileheader));
|
||||
@@ -90,8 +93,7 @@ public:
|
||||
of.close();
|
||||
}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
void addFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
ByteWriter trackdataWriter(_trackdata);
|
||||
trackdataWriter.seekToEnd();
|
||||
@@ -99,7 +101,8 @@ public:
|
||||
|
||||
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,
|
||||
head);
|
||||
@@ -117,7 +120,7 @@ public:
|
||||
|
||||
int revolution =
|
||||
-1; // -1 indicates that we are before the first index pulse
|
||||
if (_config.align_with_index())
|
||||
if (_alignWithIndex)
|
||||
{
|
||||
fmr.skipToEvent(F_BIT_INDEX);
|
||||
revolution = 0;
|
||||
@@ -136,9 +139,9 @@ public:
|
||||
totalTicks += ticks;
|
||||
revTicks += ticks;
|
||||
|
||||
// if we haven't output any revolutions yet by the end of the track,
|
||||
// assume that the whole track is one rev
|
||||
// also discard any duplicate index pulses
|
||||
// if we haven't output any revolutions yet by the end of the
|
||||
// track, assume that the whole track is one rev also discard
|
||||
// any duplicate index pulses
|
||||
if (((fmr.eof() && revolution <= 0) ||
|
||||
((event & F_BIT_INDEX)) && revTicks > 0))
|
||||
{
|
||||
@@ -181,6 +184,32 @@ public:
|
||||
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
|
||||
{
|
||||
return fmt::format("scp({})", _config.filename());
|
||||
@@ -188,8 +217,6 @@ public:
|
||||
|
||||
private:
|
||||
const ScpFluxSinkProto& _config;
|
||||
ScpHeader _fileheader = {0};
|
||||
Bytes _trackdata;
|
||||
};
|
||||
|
||||
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(
|
||||
|
||||
@@ -11,18 +11,16 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
class VcdFluxSink : public FluxSink
|
||||
class VcdSink : public FluxSink::Sink
|
||||
{
|
||||
public:
|
||||
VcdFluxSink(const VcdFluxSinkProto& config): _config(config) {}
|
||||
VcdSink(const std::string& directory): _directory(directory) {}
|
||||
|
||||
public:
|
||||
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
void addFlux(int track, int head, const Fluxmap& fluxmap) override
|
||||
{
|
||||
mkdir(_config.directory().c_str(), 0744);
|
||||
mkdir(_directory.c_str(), 0744);
|
||||
std::ofstream of(
|
||||
fmt::format(
|
||||
"{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
|
||||
fmt::format("{}/c{:02d}.h{:01d}.vcd", _directory, track, head),
|
||||
std::ios::out | std::ios::binary);
|
||||
if (!of.is_open())
|
||||
error("cannot open output file");
|
||||
@@ -64,6 +62,26 @@ public:
|
||||
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
|
||||
{
|
||||
return fmt::format("vcd({})", _config.directory());
|
||||
|
||||
@@ -53,13 +53,14 @@ int mainConvert(int argc, const char* argv[])
|
||||
minHead,
|
||||
maxHead);
|
||||
|
||||
auto fluxSink = FluxSink::create(globalConfig());
|
||||
auto fluxSinkFactory = FluxSink::create(globalConfig());
|
||||
auto fluxSink = fluxSinkFactory->create();
|
||||
|
||||
for (const auto& physicalLocation : diskLayout->physicalLocations)
|
||||
{
|
||||
auto fi = fluxSource->readFlux(physicalLocation);
|
||||
while (fi->hasNext())
|
||||
fluxSink->writeFlux(physicalLocation, *fi->next());
|
||||
fluxSink->addFlux(physicalLocation, *fi->next());
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <hex/api/imhex_api/hex_editor.hpp>
|
||||
#include <hex/api/content_registry/settings.hpp>
|
||||
#include <hex/api/task_manager.hpp>
|
||||
#include <popups/popup_question.hpp>
|
||||
#include <toasts/toast_notification.hpp>
|
||||
#include "lib/core/globals.h"
|
||||
#include "lib/core/utils.h"
|
||||
@@ -580,6 +581,29 @@ void Datastore::beginWrite()
|
||||
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;
|
||||
writeDiskCommand(*diskLayout,
|
||||
*image,
|
||||
@@ -717,7 +741,9 @@ void Datastore::writeFluxFile(const std::fs::path& path)
|
||||
error("no loaded image");
|
||||
if (diskFlux->image->getGeometry().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());
|
||||
auto fluxSource = FluxSource::createMemoryFluxSource(*diskFlux);
|
||||
|
||||
@@ -101,9 +101,9 @@ void DiskProvider::writeRaw(u64 offset, const void* buffer, size_t size)
|
||||
|
||||
[[nodiscard]] u64 DiskProvider::getActualSize() const
|
||||
{
|
||||
const auto& diskFlux = Datastore::getDecodedDisk();
|
||||
if (diskFlux && diskFlux->image)
|
||||
return diskFlux->image->getGeometry().totalBytes;
|
||||
const auto& diskLayout = Datastore::getDiskLayout();
|
||||
if (diskLayout)
|
||||
return diskLayout->totalBytes;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,5 +52,7 @@
|
||||
"fluxengine.view.status.writeFlux": "Writing flux file to disk",
|
||||
"fluxengine.view.status.readImage": "Reading image file from 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?"
|
||||
}
|
||||
|
||||
@@ -271,7 +271,8 @@ int main(int argc, const char* argv[])
|
||||
int version = sqlGetVersion(db);
|
||||
|
||||
{
|
||||
auto fluxsink = FluxSink::createFl2FluxSink(outFilename);
|
||||
auto fluxSink = FluxSink::createFl2FluxSink(outFilename)->create();
|
||||
|
||||
for (const auto& locations : sqlFindFlux(db))
|
||||
{
|
||||
unsigned cylinder = locations.first;
|
||||
@@ -295,7 +296,7 @@ int main(int argc, const char* argv[])
|
||||
"bug)",
|
||||
version);
|
||||
}
|
||||
fluxsink->writeFlux(cylinder, head, fluxmap);
|
||||
fluxSink->addFlux(cylinder, head, fluxmap);
|
||||
std::cout << '.' << std::flush;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user