Added option to write d64 images back to disk

This commit is contained in:
wybren1971
2021-05-11 19:54:50 +02:00
parent 6527ceb913
commit f92814b24b
9 changed files with 502 additions and 0 deletions

View File

@@ -1,10 +1,36 @@
#ifndef C64_H
#define C64_H
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#define C64_SECTOR_RECORD 0xffd49
#define C64_DATA_RECORD 0xffd57
#define C64_SECTOR_LENGTH 256
#define C64_FORMAT_ID_BYTE1 0x00
#define C64_FORMAT_ID_BYTE2 0x00
/* Source: http://www.unusedino.de/ec64/technical/formats/g64.html
1. Header sync FF FF FF FF FF (40 'on' bits, not GCR)
2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes)
3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read)
4. Data sync FF FF FF FF FF (40 'on' bits, not GCR)
5. Data block 55...4A (325 GCR bytes)
6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read)
1. Header sync (SYNC for the next sector)
*/
#define C64_HEADER_DATA_SYNC 0xFF
#define C64_HEADER_BLOCK_ID 0x08
#define C64_DATA_BLOCK_ID 0x07
#define C64_ENCODED_HEADER_LENGTH 10
#define C64_HEADER_GAP 0x55
#define C64_DATA_BLOCK_LENGTH 325
#define C64_INTER_SECTOR_GAP 0x55
#define C64_PADDING 0x0F
#define C64_TRACKS_PER_DISK 40
class Sector;
class Fluxmap;
@@ -18,4 +44,16 @@ public:
void decodeDataRecord();
};
class Commodore64Encoder : public AbstractEncoder
{
public:
virtual ~Commodore64Encoder() {}
public:
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
};
extern FlagGroup Commodore64EncoderFlags;
#endif

332
arch/c64/encoder.cc Normal file
View File

@@ -0,0 +1,332 @@
#include "globals.h"
#include "record.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "c64.h"
#include "crc.h"
#include "sectorset.h"
#include "writer.h"
#include "fmt/format.h"
#include <ctype.h>
#include "bytes.h"
FlagGroup Commodore64EncoderFlags;
static DoubleFlag postIndexGapUs(
{ "--post-index-gap-us" },
"Post-index gap before first sector header (microseconds).",
0);
static DoubleFlag clockCompensation(
{ "--clock-compensation-factor" },
"Scale the output clock by this much.",
1.0);
static bool lastBit;
static double clockRateUsForTrack(unsigned track)
{ //what are the clockrateUsfortrack for a c64 disk... ????
/*
Track # Sectors/Track Speed Zone bits/rotation
1 17 21 3 61,538.4
18 24 19 2 57,142.8
25 30 18 1 53,333.4
31 35 17 0 50,000.0
*/
if (track < 17)
return 3.25; //200000.0/61538.4
if (track < 24)
return 3.50; //200000.0/57142.8
if (track < 30)
return 3.750; //200000.0/53,333.4
return 4.00; //200000.0/50.000.0
}
static unsigned sectorsForTrack(unsigned track)
/*
Track Sectors/track # Sectors Storage in Bytes
----- ------------- --------- ----------------
1-17 21 357 7820
18-24 19 133 7170
25-30 18 108 6300
31-40(*) 17 85 6020
---
683 (for a 35 track image)
*/
{
if (track < 17)
return 21;
if (track < 24)
return 19;
if (track < 30)
return 18;
return 17;
}
static int encode_data_gcr(uint8_t data)
{
switch (data)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
};
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{
for (bool bit : src) //Range-based for loop
{
if (cursor < bits.size())
bits[cursor++] = bit;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{
cursor += width;
for (int i=0; i<width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
}
void bindump(std::ostream& stream, std::vector<bool>& buffer)
{
size_t pos = 0;
while ((pos < buffer.size()) and (pos <520))
{
stream << fmt::format("{:5d} : ", pos);
for (int i=0; i<40; i++)
{
if ((pos+i) < buffer.size())
stream << fmt::format("{:01b}", (buffer[pos+i]));
else
stream << "-- ";
if ((((pos + i + 1) % 8) == 0) and i != 0)
stream << " ";
}
stream << std::endl;
pos += 40;
}
}
static std::vector<bool> encode_data(uint8_t input)
/*
Four 8-bit data bytes are converted to four 10-bit GCR bytes at a time by the 1541 DOS.
RAM is only an 8-bit storage device though. This hardware limitation prevents a 10-bit
GCR byte from being stored in a single memory location. Four 10-bit GCR bytes total
40 bits - a number evenly divisible by our overriding 8-bit constraint. Commodore sub-
divides the 40 GCR bits into five 8-bit bytes to solve this dilemma. This explains why
four 8-bit data bytes are converted to GCR form at a time. The following step by step
example demonstrates how this bit manipulation is performed by the DOS.
STEP 1. Four 8-bit Data Bytes
$08 $10 $00 $12
STEP 2. Hexadecimal to Binary Conversion
1. Binary Equivalents
$08 $10 $00 $12
00001000 00010000 00000000 00010010
STEP 3. Binary to GCR Conversion
1. Four 8-bit Data Bytes
00001000 00010000 00000000 00010010
2. High and Low Nybbles
0000 1000 0001 0000 0000 0000 0001 0010
3. High and Low Nybble GCR Equivalents
01010 01001 01011 01010 01010 01010 01011 10010
4. Four 10-bit GCR Bytes
0101001001 0101101010 0101001010 0101110010
STEP 4. 10-bit GCR to 8-bit GCR Conversion
1. Concatenate Four 10-bit GCR Bytes
0101001001010110101001010010100101110010
2. Five 8-bit Subdivisions
01010010 01010110 10100101 00101001 01110010
STEP 5. Binary to Hexadecimal Conversion
1. Hexadecimal Equivalents
01010010 01010110 10100101 00101001 01110010
$52 $56 $A5 $29 $72
STEP 6. Four 8-bit Data Bytes are Recorded as Five 8-bit GCR Bytes
$08 $10 $00 $12
are recorded as
$52 $56 $A5 $29 $72
*/
{
std::vector<bool> output(10, false);
uint8_t hi =0;
uint8_t lo =0;
uint8_t lo_GCR =0;
uint8_t hi_GCR =0;
//Convert the byte in high and low nibble
lo = input >> 4; //get the lo nibble shift the bits 4 to the right
hi = input & 15; //get the hi nibble bij masking the lo bits (00001111)
lo_GCR = encode_data_gcr(lo); //example value: 0000 GCR = 01010
hi_GCR = encode_data_gcr(hi); //example value: 1000 GCR = 01001
//output = [0,1,2,3,4,5,6,7,8,9]
//value = [0,1,0,1,0,0,1,0,0,1]
// 01010 01001
int b = 4;
for (int i = 0; i < 10; i++)
{
if (i < 5) //01234
{ //i = 0 op
output[4-i] = (lo_GCR & 1); //01010
//01010 -> & 00001 -> 00000 output[4] = 0
//00101 -> & 00001 -> 00001 output[3] = 1
//00010 -> & 00001 -> 00000 output[2] = 0
//00001 -> & 00001 -> 00001 output[1] = 1
//00000 -> & 00001 -> 00000 output[0] = 0
lo_GCR >>= 1;
} else
{
output[i+b] = (hi_GCR & 1); //01001
//01001 -> & 00001 -> 00001 output[9] = 1
//00100 -> & 00001 -> 00000 output[8] = 0
//00010 -> & 00001 -> 00000 output[7] = 0
//00001 -> & 00001 -> 00001 output[6] = 1
//00000 -> & 00001 -> 00000 output[5] = 0
hi_GCR >>= 1;
b = b-2;
}
}
return output;
}
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector)
{
try
{
/* Source: http://www.unusedino.de/ec64/technical/formats/g64.html
1. Header sync FF FF FF FF FF (40 'on' bits, not GCR)
2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes)
3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read)
4. Data sync FF FF FF FF FF (40 'on' bits, not GCR)
5. Data block 55...4A (325 GCR bytes)
6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read)
1. Header sync (SYNC for the next sector)
*/
if ((sector->data.size() != C64_SECTOR_LENGTH))
Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector->data.size());
//1. Write header Sync (not GCR)
for (int i=0; i<6; i++)
write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
//2. Write Header info 10 GCR bytes
/*
The 10 byte header info (#2) is GCR encoded and must be decoded to it's
normal 8 bytes to be understood. Once decoded, its breakdown is as follows:
Byte $00 - header block ID ($08)
01 - header block checksum 16 (EOR of $02-$05)
02 - Sector
03 - Track
04 - Format ID byte #2
05 - Format ID byte #1
06-07 - $0F ("off" bytes)
*/
uint8_t encodedTrack = ((sector->logicalTrack) + 1); // C64 track numbering starts with 1. Fluxengine with 0.
uint8_t encodedSector = sector->logicalSector;
uint8_t formatByte1 = C64_FORMAT_ID_BYTE1;
uint8_t formatByte2 = C64_FORMAT_ID_BYTE2;
uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ formatByte1 ^ formatByte2);
write_bits(bits, cursor, encode_data(C64_HEADER_BLOCK_ID));
write_bits(bits, cursor, encode_data(headerChecksum));
write_bits(bits, cursor, encode_data(encodedSector));
write_bits(bits, cursor, encode_data(encodedTrack));
write_bits(bits, cursor, encode_data(formatByte1));
write_bits(bits, cursor, encode_data(formatByte2));
write_bits(bits, cursor, encode_data(C64_PADDING));
write_bits(bits, cursor, encode_data(C64_PADDING));
//3. Write header GAP not GCR
for (int i=0; i<9; i++)
write_bits(bits, cursor, C64_HEADER_GAP, 1*8); /* header gap */
//4. Write Data sync not GCR
for (int i=0; i<6; i++)
write_bits(bits, cursor, C64_HEADER_DATA_SYNC, 1*8); /* sync */
//5. Write data block 325 GCR bytes
/*
The 325 byte data block (#5) is GCR encoded and must be decoded to its
normal 260 bytes to be understood. The data block is made up of the following:
Byte $00 - data block ID ($07)
01-100 - 256 bytes data
101 - data block checksum (EOR of $01-100)
102-103 - $00 ("off" bytes, to make the sector size a multiple of 5)
*/
write_bits(bits, cursor, encode_data(C64_DATA_BLOCK_ID));
uint8_t dataChecksum = xorBytes(sector->data);
ByteReader br(sector->data);
int i = 0;
for (i = 0; i < C64_SECTOR_LENGTH; i++)
{
uint8_t val = br.read_8();
write_bits(bits, cursor, encode_data(val));
}
write_bits(bits, cursor, encode_data(dataChecksum));
write_bits(bits, cursor, encode_data(C64_PADDING));
write_bits(bits, cursor, encode_data(C64_PADDING));
//6. Write inter-sector gap 9 - 12 bytes nor gcr
for (int i=0; i<9; i++)
write_bits(bits, cursor, C64_INTER_SECTOR_GAP, 1*8); /* sync */
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
}
}
std::unique_ptr<Fluxmap> Commodore64Encoder::encode(
int physicalTrack, int physicalSide, const SectorSet& allSectors)
{
try
{
if ((physicalTrack < 0) || (physicalTrack >= C64_TRACKS_PER_DISK))
return std::unique_ptr<Fluxmap>();
double clockRateUs = clockRateUsForTrack(physicalTrack) * clockCompensation;
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
fillBitmapTo(bits, cursor, postIndexGapUs / clockRateUs, { true, false });
lastBit = false;
unsigned numSectors = sectorsForTrack(physicalTrack);
for (int sectorId=0; sectorId<numSectors; sectorId++)
{
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
write_sector(bits, cursor, sectorData);
}
if (cursor >= bits.size())
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap;
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
}
}

View File

@@ -58,6 +58,25 @@ Fluxmap& Fluxmap::appendIndex()
void Fluxmap::precompensate(int threshold_ticks, int amount_ticks)
{
/* PRECOMP 432PRECOMPENSATION DELAY https://www.mouser.com/datasheet/2/268/37c78-468028.pdf
111 0.00 ns-DISABLED
001 41.67 ns
010 83.34 ns
011 125.00 ns
100 166.67 ns
101 208.33 ns
110 250.00 ns
000 Default (See Table 12)
Table 12 - Default Precompensation Delays
DATA RATEPRECOMPENSATION DELAYS
2 Mbps 20.8 ns
1 Mbps 41.67 ns
500 Kbps 125 ns
300 Kbps 125 ns
250 Kbps 125 ns
*/
uint8_t junk = 0xff;
for (unsigned i=0; i<_bytes.size(); i++)

View File

@@ -0,0 +1,81 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
#include <fstream>
class D64ImageReader : public ImageReader
{
public:
D64ImageReader(const ImageSpec& spec):
ImageReader(spec)
{}
SectorSet readImage()
{
std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary);
if (!inputFile.is_open())
Error() << "cannot open input file";
Bytes data;
data.writer() += inputFile;
ByteReader br(data);
unsigned numCylinders = 39;
unsigned numHeads = 1;
unsigned numSectors = 0;
std::cout << "reading D64 image\n"
<< fmt::format("{} cylinders, {} heads\n",
numCylinders, numHeads);
uint32_t offset = 0;
auto sectorsPerTrack = [&](int track) -> int
{
if (track < 17)
return 21;
if (track < 24)
return 19;
if (track < 30)
return 18;
return 17;
};
SectorSet sectors;
for (int track = 0; track < 40; track++)
{
int numSectors = sectorsPerTrack(track);
for (int head = 0; head < numHeads; head++)
{
for (int sectorId = 0; sectorId < numSectors; sectorId++)
{
br.seek(offset);
Bytes payload = br.read(256);
offset += 256;
std::unique_ptr<Sector>& sector = sectors.get(track, head, sectorId);
sector.reset(new Sector);
sector->status = Sector::OK;
sector->logicalTrack = sector->physicalTrack = track;
sector->logicalSide = sector->physicalSide = head;
sector->logicalSector = sectorId;
sector->data.writer().append(payload);
}
}
}
return sectors;
}
};
std::unique_ptr<ImageReader> ImageReader::createD64ImageReader(
const ImageSpec& spec)
{
return std::unique_ptr<ImageReader>(new D64ImageReader(spec));
}

View File

@@ -21,6 +21,8 @@ std::map<std::string, ImageReader::Constructor> ImageReader::formats =
{".st", ImageReader::createImgImageReader},
{".imd", ImageReader::createIMDImageReader},
{".IMD", ImageReader::createIMDImageReader},
{".d64", ImageReader::createD64ImageReader},
{".D64", ImageReader::createD64ImageReader},
};
ImageReader::Constructor ImageReader::findConstructor(const ImageSpec& spec)

View File

@@ -27,6 +27,7 @@ private:
static std::unique_ptr<ImageReader> createImgImageReader(const ImageSpec& spec);
static std::unique_ptr<ImageReader> createJv3ImageReader(const ImageSpec& spec);
static std::unique_ptr<ImageReader> createIMDImageReader(const ImageSpec& spec);
static std::unique_ptr<ImageReader> createD64ImageReader(const ImageSpec& spec);
static Constructor findConstructor(const ImageSpec& spec);

View File

@@ -162,6 +162,7 @@ buildlibrary libbackend.a \
lib/imagereader/imgimagereader.cc \
lib/imagereader/jv3imagereader.cc \
lib/imagereader/imdimagereader.cc \
lib/imagereader/d64imagereader.cc \
lib/imagewriter/d64imagewriter.cc \
lib/imagewriter/diskcopyimagewriter.cc \
lib/imagewriter/imagewriter.cc \
@@ -176,6 +177,7 @@ buildlibrary libbackend.a \
arch/brother/decoder.cc \
arch/brother/encoder.cc \
arch/c64/decoder.cc \
arch/c64/encoder.cc \
arch/f85/decoder.cc \
arch/fb100/decoder.cc \
arch/ibm/decoder.cc \
@@ -256,6 +258,7 @@ buildlibrary libfrontend.a \
src/fe-upgradefluxfile.cc \
src/fe-writeamiga.cc \
src/fe-writebrother.cc \
src/fe-writec64.cc \
src/fe-writeibm.cc \
src/fe-writemac.cc \
src/fe-writetids990.cc \

24
src/fe-writec64.cc Normal file
View File

@@ -0,0 +1,24 @@
#include "globals.h"
#include "flags.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "c64/c64.h"
#include "writer.h"
#include "fmt/format.h"
#include <fstream>
static FlagGroup flags { &writerFlags, &Commodore64EncoderFlags };
int mainWriteC64(int argc, const char* argv[])
{
setWriterDefaultInput(":c=40:h=1:s=21:b=256");
setWriterDefaultDest(":d=0:t=0-39:s=0");
flags.parseFlags(argc, argv);
Commodore64Encoder encoder;
writeDiskCommand(encoder);
return 0;
}

View File

@@ -39,6 +39,7 @@ extern command_cb mainWriteAmiga;
extern command_cb mainWriteBrother;
extern command_cb mainWriteIbm;
extern command_cb mainWriteMac;
extern command_cb mainWriteC64;
extern command_cb mainWriteTiDs990;
extern command_cb mainWriteFlux;
extern command_cb mainWriteTestPattern;
@@ -98,6 +99,7 @@ static std::vector<Command> writeables =
{
{ "amiga", mainWriteAmiga, "Writes Amiga disks.", },
{ "brother", mainWriteBrother, "Writes 120kB and 240kB Brother word processor disks.", },
{ "c64", mainWriteC64, "Writes Commodore 64 disks.", },
{ "ibm", mainWriteIbm, "Writes the ubiquitous IBM format disks.", },
{ "mac", mainWriteMac, "Writes Apple Macintosh disks.", },
{ "tids990", mainWriteTiDs990, "Writes Texas Instruments DS990 disks.", },