From f92814b24b5429fd8b8cb16dbf84899873db7511 Mon Sep 17 00:00:00 2001 From: wybren1971 Date: Tue, 11 May 2021 19:54:50 +0200 Subject: [PATCH] Added option to write d64 images back to disk --- arch/c64/c64.h | 38 ++++ arch/c64/encoder.cc | 332 ++++++++++++++++++++++++++++++ lib/fluxmap.cc | 19 ++ lib/imagereader/d64imagereader.cc | 81 ++++++++ lib/imagereader/imagereader.cc | 2 + lib/imagereader/imagereader.h | 1 + mkninja.sh | 3 + src/fe-writec64.cc | 24 +++ src/fluxengine.cc | 2 + 9 files changed, 502 insertions(+) create mode 100644 arch/c64/encoder.cc create mode 100644 lib/imagereader/d64imagereader.cc create mode 100644 src/fe-writec64.cc diff --git a/arch/c64/c64.h b/arch/c64/c64.h index 15d246a9..fc603d2a 100644 --- a/arch/c64/c64.h +++ b/arch/c64/c64.h @@ -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 encode(int physicalTrack, int physicalSide, const SectorSet& allSectors); +}; + +extern FlagGroup Commodore64EncoderFlags; + + #endif diff --git a/arch/c64/encoder.cc b/arch/c64/encoder.cc new file mode 100644 index 00000000..8359ec9d --- /dev/null +++ b/arch/c64/encoder.cc @@ -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 +#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& bits, unsigned& cursor, const std::vector& src) +{ + for (bool bit : src) //Range-based for loop + { + if (cursor < bits.size()) + bits[cursor++] = bit; + } +} + +static void write_bits(std::vector& bits, unsigned& cursor, uint64_t data, int width) +{ + cursor += width; + for (int i=0; i>= 1; + } +} + +void bindump(std::ostream& stream, std::vector& 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 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 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& 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 Commodore64Encoder::encode( + int physicalTrack, int physicalSide, const SectorSet& allSectors) +{ + try + { + if ((physicalTrack < 0) || (physicalTrack >= C64_TRACKS_PER_DISK)) + return std::unique_ptr(); + + double clockRateUs = clockRateUsForTrack(physicalTrack) * clockCompensation; + + int bitsPerRevolution = 200000.0 / clockRateUs; + + std::vector bits(bitsPerRevolution); + unsigned cursor = 0; + + fillBitmapTo(bits, cursor, postIndexGapUs / clockRateUs, { true, false }); + lastBit = false; + + unsigned numSectors = sectorsForTrack(physicalTrack); + for (int sectorId=0; sectorId= bits.size()) + Error() << fmt::format("track data overrun by {} bits", cursor - bits.size()); + fillBitmapTo(bits, cursor, bits.size(), { true, false }); + + std::unique_ptr fluxmap(new Fluxmap); + fluxmap->appendBits(bits, clockRateUs*1e3); + return fluxmap; + } + catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + } +} + diff --git a/lib/fluxmap.cc b/lib/fluxmap.cc index 7a124952..74b3a3b4 100644 --- a/lib/fluxmap.cc +++ b/lib/fluxmap.cc @@ -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++) diff --git a/lib/imagereader/d64imagereader.cc b/lib/imagereader/d64imagereader.cc new file mode 100644 index 00000000..b84e27ea --- /dev/null +++ b/lib/imagereader/d64imagereader.cc @@ -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 +#include +#include + +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 = 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::createD64ImageReader( + const ImageSpec& spec) +{ + return std::unique_ptr(new D64ImageReader(spec)); +} + + diff --git a/lib/imagereader/imagereader.cc b/lib/imagereader/imagereader.cc index 718cfc49..9c756296 100644 --- a/lib/imagereader/imagereader.cc +++ b/lib/imagereader/imagereader.cc @@ -21,6 +21,8 @@ std::map ImageReader::formats = {".st", ImageReader::createImgImageReader}, {".imd", ImageReader::createIMDImageReader}, {".IMD", ImageReader::createIMDImageReader}, + {".d64", ImageReader::createD64ImageReader}, + {".D64", ImageReader::createD64ImageReader}, }; ImageReader::Constructor ImageReader::findConstructor(const ImageSpec& spec) diff --git a/lib/imagereader/imagereader.h b/lib/imagereader/imagereader.h index 138902c4..c909cf0d 100644 --- a/lib/imagereader/imagereader.h +++ b/lib/imagereader/imagereader.h @@ -27,6 +27,7 @@ private: static std::unique_ptr createImgImageReader(const ImageSpec& spec); static std::unique_ptr createJv3ImageReader(const ImageSpec& spec); static std::unique_ptr createIMDImageReader(const ImageSpec& spec); + static std::unique_ptr createD64ImageReader(const ImageSpec& spec); static Constructor findConstructor(const ImageSpec& spec); diff --git a/mkninja.sh b/mkninja.sh index 2e3cb37a..1aadfec0 100644 --- a/mkninja.sh +++ b/mkninja.sh @@ -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 \ diff --git a/src/fe-writec64.cc b/src/fe-writec64.cc new file mode 100644 index 00000000..090e3da6 --- /dev/null +++ b/src/fe-writec64.cc @@ -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 + +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; +} + + diff --git a/src/fluxengine.cc b/src/fluxengine.cc index 532c26a0..a475e58b 100644 --- a/src/fluxengine.cc +++ b/src/fluxengine.cc @@ -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 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.", },