Add a basic and largely untested TD0 reader.

This commit is contained in:
David Given
2021-07-23 23:21:30 +02:00
parent 3db7964509
commit 05552cc3e5
7 changed files with 219 additions and 0 deletions

View File

@@ -246,6 +246,12 @@ FluxEngine also supports a number of file system image formats. When using the
4.2](https://en.wikipedia.org/wiki/Disk_Copy) image file, commonly used by
Apple Macintosh emulators.
- `<filename.td0>`
Read a [Sydex Teledisk TD0
file](https://web.archive.org/web/20210420224336/http://dunfield.classiccmp.org/img47321/teledisk.htm)
image file. Note that only uncompressed images are supported (so far).
- `<filename.jv3>`
Read from a JV3 image file, commonly used by TRS-80 emulators. **Read

View File

@@ -30,6 +30,9 @@ const std::shared_ptr<Sector>& Image::put(unsigned track, unsigned side, unsigne
{
key_t key = std::make_tuple(track, side, sectorid);
std::shared_ptr<Sector> sector = std::make_shared<Sector>();
sector->logicalTrack = track;
sector->logicalSide = side;
sector->logicalSector = sectorid;
return _sectors[key] = sector;
}

View File

@@ -32,6 +32,9 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
case ImageReaderProto::kNsi:
return ImageReader::createNsiImageReader(config);
case ImageReaderProto::kTd0:
return ImageReader::createTd0ImageReader(config);
default:
Error() << "bad input file config";
return std::unique_ptr<ImageReader>();
@@ -50,6 +53,7 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st
{".img", [&]() { proto->mutable_img(); }},
{".st", [&]() { proto->mutable_img(); }},
{".nsi", [&]() { proto->mutable_nsi(); }},
{".td0", [&]() { proto->mutable_td0(); }},
};
for (const auto& it : formats)

View File

@@ -22,6 +22,7 @@ public:
static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageReaderProto& config);
static std::unique_ptr<ImageReader> createIMDImageReader(const ImageReaderProto& config);
static std::unique_ptr<ImageReader> createNsiImageReader(const ImageReaderProto& config);
static std::unique_ptr<ImageReader> createTd0ImageReader(const ImageReaderProto& config);
public:
virtual Image readImage() = 0;

View File

@@ -23,6 +23,7 @@ message ImdInputProto {}
message Jv3InputProto {}
message D64InputProto {}
message NsiInputProto {}
message Td0InputProto {}
message ImageReaderProto {
optional string filename = 1 [(help) = "filename of input sector image"];
@@ -33,6 +34,7 @@ message ImageReaderProto {
Jv3InputProto jv3 = 5;
D64InputProto d64 = 6;
NsiInputProto nsi = 7;
Td0InputProto td0 = 8;
}
}

View File

@@ -0,0 +1,202 @@
#include "globals.h"
#include "flags.h"
#include "sector.h"
#include "imagereader/imagereader.h"
#include "image.h"
#include "crc.h"
#include "fmt/format.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
#include <fstream>
/* The best description of the Teledisk format I've found is available here:
*
* https://web.archive.org/web/20210420230238/http://dunfield.classiccmp.org/img47321/td0notes.txt
*
* Header:
*
* Signature (2 bytes); TD for normal, td for compressed
* Sequence (1 byte)
* Checksequence (1 byte)
* Teledisk version (1 byte)
* Data rate (1 byte)
* Drive type (1 byte)
* Stepping (1 byte)
* DOS allocation flag (1 byte)
* Sides (1 byte)
* Cyclic Redundancy Check (2 bytes)
*/
enum
{
TD0_ENCODING_RAW = 0,
TD0_ENCODING_REPEATED = 1,
TD0_ENCODING_RLE = 2,
TD0_FLAG_DUPLICATE = 0x01,
TD0_FLAG_CRC_ERROR = 0x02,
TD0_FLAG_DELETED = 0x04,
TD0_FLAG_SKIPPED = 0x10,
TD0_FLAG_IDNODATA = 0x20,
TD0_FLAG_DATANOID = 0x40,
};
class Td0ImageReader : public ImageReader
{
public:
Td0ImageReader(const ImageReaderProto& config):
ImageReader(config)
{}
Image readImage()
{
std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
if (!inputFile.is_open())
Error() << "cannot open input file";
Bytes input;
input.writer() += inputFile;
ByteReader br(input);
uint16_t signature = br.read_be16();
br.skip(2); /* sequence and checksequence */
uint8_t version = br.read_8();
br.skip(2); /* data rate, drive type */
uint8_t stepping = br.read_8();
br.skip(1); /* sparse flag */
uint8_t sides = (br.read_8() == 1) ? 1 : 2;
uint16_t headerCrc = br.read_le16();
uint16_t gotCrc = crc16(0xa097, 0, input.slice(0, 10));
if (gotCrc != headerCrc)
Error() << "TD0: header checksum mismatch";
if (signature != 0x5444)
Error() << "TD0: unsupported file type (only uncompressed files are supported for now)";
std::string comment = "(no comment)";
if (stepping & 0x80)
{
/* Comment block */
br.skip(2); /* comment CRC */
uint16_t length = br.read_le16();
br.skip(6); /* timestamp */
comment = br.read(length);
std::replace(comment.begin(), comment.end(), '\0', '\n');
/* Strip trailing newlines */
auto nl = std::find_if(comment.rbegin(), comment.rend(),
[](unsigned char ch) { return !std::isspace(ch); });
comment.erase(nl.base(), comment.end());
}
std::cout << fmt::format("TD0: TeleDisk {:.1}: {}\n",
(double)version / 10.0, comment);
Image image;
for (;;)
{
/* Read track header */
uint8_t sectorCount = br.read_8();
if (sectorCount == 0xff)
break;
uint8_t physicalCylinder = br.read_8();
uint8_t physicalHead = br.read_8() & 1;
br.skip(1); /* crc */
for (int i = 0; i < sectorCount; i++)
{
/* Read sector */
uint8_t logicalTrack = br.read_8();
uint8_t logicalSide = br.read_8();
uint8_t sectorId = br.read_8();
uint8_t sectorSizeEncoded = br.read_8();
unsigned sectorSize = 128<<sectorSizeEncoded;
uint8_t flags = br.read_8();
br.skip(1); /* CRC */
uint16_t dataSize = br.read_le16();
Bytes encodedData = br.read(dataSize);
ByteReader bre(encodedData);
uint8_t encoding = bre.read_8();
Bytes data;
if (!(flags & (TD0_FLAG_SKIPPED|TD0_FLAG_IDNODATA)))
{
switch (encoding)
{
case TD0_ENCODING_RAW:
data = encodedData.slice(1);
break;
case TD0_ENCODING_REPEATED:
{
ByteWriter bw(data);
while (!bre.eof())
{
uint16_t pattern = bre.read_le16();
uint16_t count = bre.read_le16();
while (count--)
bw.write_le16(pattern);
}
break;
}
case TD0_ENCODING_RLE:
{
ByteWriter bw(data);
while (!bre.eof())
{
uint8_t length = bre.read_8()*2;
if (length == 0)
{
/* Literal block */
length = bre.read_8();
bw += bre.read(length);
}
else
{
/* Repeated block */
uint8_t count = bre.read_8();
Bytes b = bre.read(length);
while (count--)
bw += b;
}
}
break;
}
}
}
const auto& sector = image.put(logicalTrack, logicalSide, sectorId);
sector->status = Sector::OK;
sector->physicalCylinder = physicalCylinder;
sector->physicalHead = physicalHead;
sector->data = data.slice(0, sectorSize);
}
}
//image.calculateSize();
const Geometry& geometry = image.getGeometry();
std::cout << fmt::format("TD0: found {} tracks, {} sides, {} kB total\n",
geometry.numTracks, geometry.numSides,
input.size() / 1024);
return image;
}
};
std::unique_ptr<ImageReader> ImageReader::createTd0ImageReader(const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new Td0ImageReader(config));
}

View File

@@ -355,6 +355,7 @@ buildlibrary libbackend.a \
lib/imagereader/imgimagereader.cc \
lib/imagereader/jv3imagereader.cc \
lib/imagereader/nsiimagereader.cc \
lib/imagereader/td0imagereader.cc \
lib/imagewriter/d64imagewriter.cc \
lib/imagewriter/diskcopyimagewriter.cc \
lib/imagewriter/imagewriter.cc \