mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
211 lines
6.8 KiB
C++
211 lines
6.8 KiB
C++
#include "lib/core/globals.h"
|
|
#include "lib/flags.h"
|
|
#include "lib/sector.h"
|
|
#include "lib/imagereader/imagereader.h"
|
|
#include "lib/image.h"
|
|
#include "lib/core/crc.h"
|
|
#include "lib/core/logger.h"
|
|
#include "lib/config.pb.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) {}
|
|
|
|
std::unique_ptr<Image> readImage() override
|
|
{
|
|
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());
|
|
}
|
|
|
|
log("TD0: TeleDisk {}.{}: {}", version / 10, version % 10, comment);
|
|
|
|
unsigned totalSize = 0;
|
|
std::unique_ptr<Image> image(new Image);
|
|
for (;;)
|
|
{
|
|
/* Read track header */
|
|
|
|
uint8_t sectorCount = br.read_8();
|
|
if (sectorCount == 0xff)
|
|
break;
|
|
|
|
uint8_t physicalTrack = br.read_8();
|
|
uint8_t physicalSide = 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->physicalTrack = physicalTrack;
|
|
sector->physicalSide = physicalSide;
|
|
sector->data = data.slice(0, sectorSize);
|
|
totalSize += sectorSize;
|
|
}
|
|
}
|
|
|
|
image->calculateSize();
|
|
const Geometry& geometry = image->getGeometry();
|
|
log("TD0: found {} tracks, {} sides, {} sectors, {} bytes per sector, "
|
|
"{} kB total",
|
|
geometry.numTracks,
|
|
geometry.numSides,
|
|
geometry.numSectors,
|
|
geometry.sectorSize,
|
|
totalSize / 1024);
|
|
return image;
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<ImageReader> ImageReader::createTd0ImageReader(
|
|
const ImageReaderProto& config)
|
|
{
|
|
return std::unique_ptr<ImageReader>(new Td0ImageReader(config));
|
|
}
|