Files
fluxengine/lib/fluxsink/scpfluxsink.cc
2025-10-14 22:32:42 +02:00

227 lines
7.0 KiB
C++

#include "lib/core/globals.h"
#include "lib/config/config.h"
#include "lib/config/flags.h"
#include "lib/data/fluxmap.h"
#include "lib/core/bytes.h"
#include "protocol.h"
#include "lib/fluxsink/fluxsink.h"
#include "lib/data/fluxmapreader.h"
#include "lib/fluxsink/fluxsink.pb.h"
#include "lib/config/proto.h"
#include "lib/data/fluxmap.h"
#include "lib/data/layout.h"
#include "lib/external/scp.h"
#include "lib/core/logger.h"
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
static int strackno(int track, int side)
{
return (track << 1) | side;
}
static void write_le32(uint8_t dest[4], uint32_t v)
{
dest[0] = v;
dest[1] = v >> 8;
dest[2] = v >> 16;
dest[3] = v >> 24;
}
static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
{
ByteReader br(bytes);
while (!br.eof())
checksum += br.read_8();
}
class ScpSink : public FluxSink
{
public:
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();
auto [minCylinder, maxCylinder, minHead, maxHead] =
diskLayout->getPhysicalBounds();
_fileheader.file_id[0] = 'S';
_fileheader.file_id[1] = 'C';
_fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _typeByte;
_fileheader.start_track = strackno(minCylinder, minHead);
_fileheader.end_track = strackno(maxCylinder, maxHead);
_fileheader.flags = SCP_FLAG_INDEXED;
if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
error("you can't write Apple II flux images to SCP files yet");
if (globalConfig()->drive().drive_type() != DRIVETYPE_40TRACK)
_fileheader.flags |= SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
if ((minHead == 0) && (maxHead == 0))
_fileheader.heads = 1;
else if ((minHead == 1) && (maxHead == 1))
_fileheader.heads = 2;
else
_fileheader.heads = 0;
log("SCP: writing {} tpi {} file containing {} tracks",
(_fileheader.flags & SCP_FLAG_96TPI) ? 96 : 48,
(minHead == maxHead) ? "single sided" : "double sided",
_fileheader.end_track - _fileheader.start_track + 1);
}
~ScpSink()
{
uint32_t checksum = 0;
appendChecksum(checksum,
Bytes((const uint8_t*)&_fileheader, sizeof(_fileheader))
.slice(0x10));
appendChecksum(checksum, _trackdata);
write_le32(_fileheader.checksum, checksum);
log("SCP: writing output file");
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));
_trackdata.writeTo(of);
of.close();
}
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
int strack = strackno(track, head);
if (strack >= std::size(_fileheader.track))
{
log("SCP: cannot write track {} head {}, there are not not "
"enough "
"Track Data Headers.",
track,
head);
return;
}
ScpTrack trackHeader = {0};
trackHeader.header.track_id[0] = 'T';
trackHeader.header.track_id[1] = 'R';
trackHeader.header.track_id[2] = 'K';
trackHeader.header.strack = strack;
FluxmapReader fmr(fluxmap);
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);
int revolution =
-1; // -1 indicates that we are before the first index pulse
if (_alignWithIndex)
{
fmr.skipToEvent(F_BIT_INDEX);
revolution = 0;
}
unsigned revTicks = 0;
unsigned totalTicks = 0;
unsigned ticksSinceLastPulse = 0;
uint32_t startOffset = 0;
while (revolution < 5)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
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 (((fmr.eof() && revolution <= 0) ||
((event & F_BIT_INDEX)) && revTicks > 0))
{
if (fmr.eof() && revolution == -1)
revolution = 0;
if (revolution >= 0)
{
auto* revheader = &trackHeader.revolution[revolution];
write_le32(
revheader->offset, startOffset + sizeof(ScpTrack));
write_le32(revheader->length,
(fluxdataWriter.pos - startOffset) / 2);
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
revheader++;
}
revolution++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
}
if (fmr.eof())
break;
if (event & F_BIT_PULSE)
{
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000)
{
fluxdataWriter.write_be16(0);
t -= 0x10000;
}
fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0;
}
}
_fileheader.revolutions = revolution;
write_le32(
_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackHeader, sizeof(trackHeader));
trackdataWriter += fluxdata;
}
private:
std::string _filename;
uint8_t _typeByte;
bool _alignWithIndex;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
class ScpFluxSinkFactory : public FluxSinkFactory
{
public:
ScpFluxSinkFactory(const ScpFluxSinkProto& lconfig): _config(lconfig) {}
std::unique_ptr<FluxSink> 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());
}
private:
const ScpFluxSinkProto& _config;
};
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createScpFluxSinkFactory(
const ScpFluxSinkProto& config)
{
return std::unique_ptr<FluxSinkFactory>(new ScpFluxSinkFactory(config));
}