mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Add a basic and largely untested TD0 reader.
This commit is contained in:
@@ -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
|
4.2](https://en.wikipedia.org/wiki/Disk_Copy) image file, commonly used by
|
||||||
Apple Macintosh emulators.
|
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>`
|
- `<filename.jv3>`
|
||||||
|
|
||||||
Read from a JV3 image file, commonly used by TRS-80 emulators. **Read
|
Read from a JV3 image file, commonly used by TRS-80 emulators. **Read
|
||||||
|
|||||||
@@ -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);
|
key_t key = std::make_tuple(track, side, sectorid);
|
||||||
std::shared_ptr<Sector> sector = std::make_shared<Sector>();
|
std::shared_ptr<Sector> sector = std::make_shared<Sector>();
|
||||||
|
sector->logicalTrack = track;
|
||||||
|
sector->logicalSide = side;
|
||||||
|
sector->logicalSector = sectorid;
|
||||||
return _sectors[key] = sector;
|
return _sectors[key] = sector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
|
|||||||
case ImageReaderProto::kNsi:
|
case ImageReaderProto::kNsi:
|
||||||
return ImageReader::createNsiImageReader(config);
|
return ImageReader::createNsiImageReader(config);
|
||||||
|
|
||||||
|
case ImageReaderProto::kTd0:
|
||||||
|
return ImageReader::createTd0ImageReader(config);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Error() << "bad input file config";
|
Error() << "bad input file config";
|
||||||
return std::unique_ptr<ImageReader>();
|
return std::unique_ptr<ImageReader>();
|
||||||
@@ -50,6 +53,7 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st
|
|||||||
{".img", [&]() { proto->mutable_img(); }},
|
{".img", [&]() { proto->mutable_img(); }},
|
||||||
{".st", [&]() { proto->mutable_img(); }},
|
{".st", [&]() { proto->mutable_img(); }},
|
||||||
{".nsi", [&]() { proto->mutable_nsi(); }},
|
{".nsi", [&]() { proto->mutable_nsi(); }},
|
||||||
|
{".td0", [&]() { proto->mutable_td0(); }},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& it : formats)
|
for (const auto& it : formats)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public:
|
|||||||
static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageReaderProto& config);
|
static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageReaderProto& config);
|
||||||
static std::unique_ptr<ImageReader> createIMDImageReader(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> createNsiImageReader(const ImageReaderProto& config);
|
||||||
|
static std::unique_ptr<ImageReader> createTd0ImageReader(const ImageReaderProto& config);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual Image readImage() = 0;
|
virtual Image readImage() = 0;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ message ImdInputProto {}
|
|||||||
message Jv3InputProto {}
|
message Jv3InputProto {}
|
||||||
message D64InputProto {}
|
message D64InputProto {}
|
||||||
message NsiInputProto {}
|
message NsiInputProto {}
|
||||||
|
message Td0InputProto {}
|
||||||
|
|
||||||
message ImageReaderProto {
|
message ImageReaderProto {
|
||||||
optional string filename = 1 [(help) = "filename of input sector image"];
|
optional string filename = 1 [(help) = "filename of input sector image"];
|
||||||
@@ -33,6 +34,7 @@ message ImageReaderProto {
|
|||||||
Jv3InputProto jv3 = 5;
|
Jv3InputProto jv3 = 5;
|
||||||
D64InputProto d64 = 6;
|
D64InputProto d64 = 6;
|
||||||
NsiInputProto nsi = 7;
|
NsiInputProto nsi = 7;
|
||||||
|
Td0InputProto td0 = 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
202
lib/imagereader/td0imagereader.cc
Normal file
202
lib/imagereader/td0imagereader.cc
Normal 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -355,6 +355,7 @@ buildlibrary libbackend.a \
|
|||||||
lib/imagereader/imgimagereader.cc \
|
lib/imagereader/imgimagereader.cc \
|
||||||
lib/imagereader/jv3imagereader.cc \
|
lib/imagereader/jv3imagereader.cc \
|
||||||
lib/imagereader/nsiimagereader.cc \
|
lib/imagereader/nsiimagereader.cc \
|
||||||
|
lib/imagereader/td0imagereader.cc \
|
||||||
lib/imagewriter/d64imagewriter.cc \
|
lib/imagewriter/d64imagewriter.cc \
|
||||||
lib/imagewriter/diskcopyimagewriter.cc \
|
lib/imagewriter/diskcopyimagewriter.cc \
|
||||||
lib/imagewriter/imagewriter.cc \
|
lib/imagewriter/imagewriter.cc \
|
||||||
|
|||||||
Reference in New Issue
Block a user