Add the Supercard Pro decoder.

This commit is contained in:
David Given
2019-08-28 23:32:09 +02:00
parent 46f1b0aef4
commit 4304d1eede
5 changed files with 185 additions and 10 deletions

View File

@@ -1,8 +1,8 @@
PACKAGES = zlib sqlite3 libusb-1.0
export CFLAGS = -O3 -g --std=c++14 \
export CFLAGS = -Os -g --std=c++14 \
-ffunction-sections -fdata-sections
export LDFLAGS = -O3
export LDFLAGS = -Os
ifeq ($(OS), Windows_NT)
export CXX = /mingw32/bin/g++

View File

@@ -221,6 +221,12 @@ directory.
file format in a non-backwards-compatible way; this tool will upgrade flux
files to the new format.
- `fluxengine convert`: converts various formats to various other formats.
You can use this to convert Catweasel or Supercard Pro flux files to
FluxEngine's native format, for flux files to various other formats useful
for debugging (including VCD which can be loaded into
[sigrok](http://sigrok.org)).
Commands which normally take `--source` or `--dest` get a sensible default if
left unspecified. `fluxengine read ibm` on its own will read drive 0 and
write an `ibm.img` file.
@@ -247,25 +253,24 @@ wrote to do useful things. These are built alongside FluxEngine.
- `brother120tool`: extracts files from a 120kB Brother filesystem image.
- `cwftoflux`: converts (one flavour of) CatWeasel flux file into a
FluxEngine flux file.
## The recommended workflow
So you've just received, say, a huge pile of old Brother word processor disks containing valuable historical data, and you want to read them.
So you've just received, say, a huge pile of old Brother word processor disks
containing valuable historical data, and you want to read them.
Typically I do this:
```
$ fluxengine read brother -s :d=0 -o brother.img --write-flux=brother.flux
$ fluxengine read brother -s :d=0 -o brother.img --write-flux=brother.flux --write-svg=brother.svg
```
This will read the disk in drive 0 and write out a filesystem image. It'll
also copy the flux to brother.flux. If I then need to tweak the settings, I
can rerun the decode without having to physically touch the disk like this:
also copy the flux to brother.flux and write out an SVG visualisation. If I
then need to tweak the settings, I can rerun the decode without having to
physically touch the disk like this:
```
$ fluxengine read brother -s brother.flux -o brother.img
$ fluxengine read brother -s brother.flux -o brother.img --write-svg=brother.svg
```
Apart from being drastically faster, this avoids touching the (potentially

View File

@@ -208,6 +208,7 @@ buildlibrary libfrontend.a \
src/fe-readvictor9k.cc \
src/fe-readzilogmcz.cc \
src/fe-rpm.cc \
src/fe-scptoflux.cc \
src/fe-seek.cc \
src/fe-testbulktransport.cc \
src/fe-upgradefluxfile.cc \

167
src/fe-scptoflux.cc Normal file
View File

@@ -0,0 +1,167 @@
#include "globals.h"
#include "fluxmap.h"
#include "sql.h"
#include "bytes.h"
#include "protocol.h"
#include "fmt/format.h"
#include <fstream>
struct ScpHeader
{
char file_id[3]; // file ID - 'SCP'
uint8_t version; // major/minor in nibbles
uint8_t type; // disk type - subclass/class in nibbles
uint8_t revolutions; // up to 5
uint8_t start_track; // 0..165
uint8_t end_track; // 0..165
uint8_t flags; // see below
uint8_t cell_width; // in bits, 0 meaning 16
uint8_t heads; // 0 = both, 1 = side 0 only, 2 = side 1 only
uint8_t resolution; // 25ns * (resolution+1)
uint8_t checksum[4]; // of data after this point
uint8_t track[165][4]; // track offsets, not necessarily 165
};
struct ScpTrack
{
char track_id[3]; // 'TRK'
uint8_t strack; // SCP track number
struct
{
uint8_t index[4]; // time for one revolution
uint8_t length[4]; // number of bitcells
uint8_t offset[4]; // offset to bitcell data, relative to track header
}
revolution[5];
};
static std::ifstream inputFile;
static sqlite3* outputDb;
static ScpHeader header;
static nanoseconds_t resolution;
static int startSide;
static int endSide;
static void syntax()
{
std::cout << "Syntax: fluxengine convert cwftoflux <cwffile> <fluxfile>\n";
exit(0);
}
static void check_for_error()
{
if (inputFile.fail())
Error() << fmt::format("I/O error: {}", strerror(errno));
}
static int trackno(int strack)
{
if (startSide == endSide)
return strack;
return strack >> 1;
}
static int headno(int strack)
{
if (startSide == endSide)
return startSide;
return strack & 1;
}
static void read_header()
{
inputFile.read((char*) &header, sizeof(header));
check_for_error();
if ((header.file_id[0] != 'S')
|| (header.file_id[1] != 'C')
|| (header.file_id[2] != 'P'))
Error() << "input not a SCP file";
resolution = 25 * (header.resolution + 1);
startSide = (header.heads == 2) ? 1 : 0;
endSide = (header.heads == 1) ? 0 : 1;
if ((header.cell_width != 0) && (header.cell_width != 16))
Error() << "currently only 16-bit cells are supported";
std::cout << fmt::format("tracks {}-{}, heads {}-{}\n",
trackno(header.start_track), trackno(header.end_track), startSide, endSide);
std::cout << fmt::format("sample resolution: {} ns\n", resolution);
}
static void read_track(int strack)
{
uint32_t offset = Bytes(header.track[strack], 4).reader().read_le32();
ScpTrack trackheader;
inputFile.seekg(offset, std::ios::beg);
inputFile.read((char*) &trackheader, sizeof(trackheader));
check_for_error();
if ((trackheader.track_id[0] != 'T')
|| (trackheader.track_id[1] != 'R')
|| (trackheader.track_id[2] != 'K'))
Error() << "corrupt SCP file";
std::cout << fmt::format("{}.{}: ", trackno(strack), headno(strack))
<< std::flush;
Fluxmap fluxmap;
nanoseconds_t pending = 0;
for (int revolution = 0; revolution < header.revolutions; revolution++)
{
if (revolution != 0)
fluxmap.appendIndex();
uint32_t datalength = Bytes(trackheader.revolution[revolution].length, 4).reader().read_le32();
uint32_t dataoffset = Bytes(trackheader.revolution[revolution].offset, 4).reader().read_le32();
Bytes data(datalength*2);
inputFile.seekg(dataoffset + offset, std::ios::beg);
inputFile.read((char*) data.begin(), data.size());
check_for_error();
ByteReader br(data);
for (int cell = 0; cell < datalength; cell++)
{
uint16_t interval = br.read_be16();
if (interval)
{
fluxmap.appendInterval((interval + pending) * resolution / NS_PER_TICK);
fluxmap.appendPulse();
pending = 0;
}
else
pending += interval;
}
}
std::cout << fmt::format(" {} ms in {} output bytes\n",
fluxmap.duration() / 1e6, fluxmap.bytes());
sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap);
}
int mainConvertScpToFlux(int argc, const char* argv[])
{
if (argc != 3)
syntax();
inputFile.open(argv[1], std::ios::in | std::ios::binary);
if (!inputFile.is_open())
Error() << fmt::format("cannot open input file '{}'", argv[1]);
outputDb = sqlOpen(argv[2], SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
sqlPrepareFlux(outputDb);
sqlWriteIntProperty(outputDb, "version", FLUX_VERSION_CURRENT);
sqlStmt(outputDb, "BEGIN;");
read_header();
inputFile.seekg(sizeof(header), std::ios::beg);
for (unsigned i=header.start_track; i<=header.end_track; i++)
read_track(i);
sqlStmt(outputDb, "COMMIT;");
sqlClose(outputDb);
return 0;
}

View File

@@ -6,6 +6,7 @@ extern command_cb mainErase;
extern command_cb mainConvertCwfToFlux;
extern command_cb mainConvertFluxToAu;
extern command_cb mainConvertFluxToVcd;
extern command_cb mainConvertScpToFlux;
extern command_cb mainInspect;
extern command_cb mainReadADFS;
extern command_cb mainReadAESLanier;
@@ -83,6 +84,7 @@ static std::vector<Command> writeables =
static std::vector<Command> convertables =
{
{ "cwftoflux", mainConvertCwfToFlux, "Converts CatWeasel stream files to flux.", },
{ "scptoflux", mainConvertScpToFlux, "Converts Supercard Pro stream files to flux.", },
{ "fluxtoau", mainConvertFluxToAu, "Converts (one track of a) flux file to an .au audio file.", },
{ "fluxtovcd", mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", },
};