diff --git a/include/BitstreamDecoder.h b/include/BitstreamDecoder.h index c1fbfbf3..a2134a91 100644 --- a/include/BitstreamDecoder.h +++ b/include/BitstreamDecoder.h @@ -10,11 +10,13 @@ void scan_flux_mfm_fm (TrackData &trackdata, DataRate last_datarate); void scan_flux_amiga (TrackData &trackdata); void scan_flux_gcr (TrackData &trackdata); void scan_flux_ace (TrackData &trackdata); +void scan_flux_mx (TrackData &trackdata); void scan_bitstream (TrackData &trackdata); void scan_bitstream_mfm_fm (TrackData &trackdata); void scan_bitstream_amiga (TrackData &trackdata); void scan_bitstream_ace (TrackData &trackdata); void scan_bitstream_gcr (TrackData &trackdata); +void scan_bitstream_mx (TrackData &trackdata); #endif // BITSTREAM_DECODER_H diff --git a/include/Header.h b/include/Header.h index ba2bb8f4..b3b6756a 100644 --- a/include/Header.h +++ b/include/Header.h @@ -2,7 +2,7 @@ #define HEADER_H enum class DataRate : int { Unknown = 0, _250K = 250'000, _300K = 300'000, _500K = 500'000, _1M = 1'000'000 }; -enum class Encoding { Unknown, MFM, FM, Amiga, GCR, Ace }; +enum class Encoding { Unknown, MFM, FM, Amiga, GCR, Ace, MX }; std::string to_string (const DataRate &datarate); std::string to_string (const Encoding &encoding); diff --git a/include/SAMdisk.h b/include/SAMdisk.h index 01341c65..57d57752 100644 --- a/include/SAMdisk.h +++ b/include/SAMdisk.h @@ -267,8 +267,8 @@ typedef struct int offsets = -1, absoffsets = 0, nowrite = 0, nodiff = 0, noformat = 0, nodups = 0, rpm = 0, flip = 0; int base = -1, size = -1, gap3 = -1, interleave = -1, skew = -1, fill = -1, fm = -1, head0 = -1, head1 = -1; int maxcopies = 3, fmoverlap = 0, multiformat = 0, cylsfirst = -1; - int bdos = 0, atom = 0, hdf = 0, cpm = 0, resize = 0, nocfa = 0, noidentify = 0, ace = 0; - int force = 0, nosig = 0, check8k = -1, tty = 0, nodata = 0, noflux = 0, nowobble = 0; + int bdos = 0, atom = 0, hdf = 0, cpm = 0, resize = 0, nocfa = 0, noidentify = 0, ace = 0, mx = 0; + int force = 0, nosig = 0, check8k = -1, tty = 0, nodata = 0, noflux = 0, nowobble = 0, scale = 100; int time = 0, rate = -1, mt = -1; long sectors = -1; std::string label {}, boot {}; diff --git a/src/BitstreamDecoder.cpp b/src/BitstreamDecoder.cpp index ffd6c957..fe62989f 100644 --- a/src/BitstreamDecoder.cpp +++ b/src/BitstreamDecoder.cpp @@ -38,6 +38,12 @@ void scan_flux (TrackData &trackdata) return; } + if (opt.mx) + { + scan_flux_mx(trackdata); + return; + } + // Set the encoding scanning order, with the last successful encoding first (and its duplicate removed) std::vector encodings = { last_encoding, Encoding::MFM, Encoding::Amiga/*, Encoding::GCR*/ }; encodings.erase(std::next(std::find(encodings.rbegin(), encodings.rend(), last_encoding)).base()); @@ -107,6 +113,12 @@ void scan_bitstream (TrackData &trackdata) return; } + if (opt.mx) + { + scan_bitstream_mx(trackdata); + return; + } + // Set the encoding scanning order, with the last successful encoding first (and its duplicate removed) std::vector encodings = { last_encoding, Encoding::MFM, Encoding::Amiga/*, Encoding::GCR*/ }; encodings.erase(std::next(std::find(encodings.rbegin(), encodings.rend(), last_encoding)).base()); @@ -374,6 +386,106 @@ void scan_flux_ace (TrackData &trackdata) } +/* + * DVK MX format. DVK was a family of DEC LSI-11 compatible computers + * produced by Soviet Union in 1980's, MX.SYS is the RT-11 driver name + * for the controller. + * + * FM encoding. Track format is driver-dependent (except the sync word). + * Hardware always reads or writes entire track, hence no sector headers. + * + * 1. gap1 (8 words) + * 2. sync (1 word, 000363 octal, regular FM encoding) + * 3. zero-based track number (1 word) + * 4. 11 sectors: + * data (128 words) + * checksum (1 word) + * 5. extra (1..4 words) + * 6. gap4 (or unformatted) + * + * See also http://torlus.com/floppy/forum/viewtopic.php?f=19&t=1384 + */ + +void scan_bitstream_mx (TrackData &trackdata) +{ + Track track; + Data block; + uint64_t dword = 0; + uint16_t stored_cksum = 0, cksum = 0; + + auto &bitbuf = trackdata.bitstream(); + bitbuf.seek(0); + bitbuf.encoding = Encoding::FM; + track.tracklen = bitbuf.track_bitsize(); + bool sync = false; + + while (!bitbuf.wrapped()) + { + // Give up if no headers were found in the first revolution + if (!track.size() && bitbuf.tell() > track.tracklen) + break; + + // ignore sync sequences after first one + if (sync) + break; + + dword = (dword << 1) | bitbuf.read1(); + if (opt.debug) + util::cout << util::fmt (" s_b_mx %016lx c:h %d:%d\n", dword, trackdata.cylhead.cyl, trackdata.cylhead.head); + + switch (dword) + { + case 0x88888888aaaa88aa: // FM-encoded 0x00f3 (000363 octal) + sync = true; + if (opt.debug) util::cout << " s_b_mx found sync at " << bitbuf.tell() << "\n"; + break; + + default: + continue; + } + + // skip track number + bitbuf.read_byte(); + bitbuf.read_byte(); + + // read sectors + for (auto s = 0; s < 11; s++) { + Sector sector(DataRate::_250K, Encoding::MX, Header(trackdata.cylhead, s, SizeToCode(256))); + + block.clear(); + cksum = 0; + + for (auto i = 0; i < 128; i++) { + auto msb = bitbuf.read_byte(); + auto lsb = bitbuf.read_byte(); + cksum += (lsb | (msb << 8)); + block.push_back(lsb); + block.push_back(msb); + } + + stored_cksum = bitbuf.read_byte() << 8; + stored_cksum |= bitbuf.read_byte(); + + if (opt.debug) util::cout << util::fmt ("cksum s %d disk:calc %06o:%06o (%04x:%04x)\n", + s, stored_cksum, cksum, stored_cksum, cksum); + + sector.add(std::move(block), cksum != stored_cksum, 0); + track.add(std::move(sector)); + } + } + + trackdata.add(std::move(track)); +} + +void scan_flux_mx (TrackData &trackdata) +{ + FluxDecoder decoder(trackdata.flux(), ::bitcell_ns(DataRate::_250K), opt.scale); + BitBuffer bitbuf(DataRate::_250K, decoder); + + trackdata.add(std::move(bitbuf)); +} + + #define MFM_MASK 0x55555555UL static bool amiga_read_dwords (BitBuffer &bitbuf, uint32_t *pdw, size_t dwords, uint32_t &checksum) diff --git a/src/Header.cpp b/src/Header.cpp index bfe8bc1c..b90f361c 100644 --- a/src/Header.cpp +++ b/src/Header.cpp @@ -23,6 +23,7 @@ std::string to_string (const Encoding &encoding) case Encoding::GCR: return "GCR"; break; case Encoding::Amiga: return "Amiga"; break; case Encoding::Ace: return "Ace"; break; + case Encoding::MX: return "MX"; break; case Encoding::Unknown: break; } return "Unknown"; @@ -37,6 +38,7 @@ std::string short_name (const Encoding &encoding) case Encoding::GCR: return "gcr"; break; case Encoding::Amiga: return "ami"; break; case Encoding::Ace: return "ace"; break; + case Encoding::MX: return "mx"; break; case Encoding::Unknown: break; } return "unk"; diff --git a/src/SAMdisk.cpp b/src/SAMdisk.cpp index bad01ff4..f14ca5a6 100644 --- a/src/SAMdisk.cpp +++ b/src/SAMdisk.cpp @@ -83,7 +83,7 @@ extern "C" { #include "getopt_long.h" } -enum { OPT_RPM = 256, OPT_RATE, OPT_LOG, OPT_VERSION, OPT_HEAD0, OPT_HEAD1, OPT_GAPMASK, OPT_MAXCOPIES, OPT_MAXSPLICE, OPT_CHECK8K, OPT_BYTES, OPT_HDF, OPT_ORDER }; +enum { OPT_RPM = 256, OPT_RATE, OPT_LOG, OPT_VERSION, OPT_HEAD0, OPT_HEAD1, OPT_GAPMASK, OPT_MAXCOPIES, OPT_MAXSPLICE, OPT_CHECK8K, OPT_BYTES, OPT_HDF, OPT_ORDER, OPT_SCALE }; struct option long_options[] = { @@ -151,6 +151,7 @@ struct option long_options[] = { "byte-swap", no_argument, &opt.byteswap, 1 }, { "atom", no_argument, &opt.byteswap, 1 }, { "ace", no_argument, &opt.ace, 1 }, + { "mx", no_argument, &opt.mx, 1 }, { "quick", no_argument, &opt.quick, 1 }, { "repair", no_argument, &opt.repair, 1}, { "fix", no_argument, &opt.fix, 1 }, @@ -181,6 +182,7 @@ struct option long_options[] = { "hdf", required_argument, nullptr, OPT_HDF }, { "order", required_argument, nullptr, OPT_ORDER }, { "version", no_argument, nullptr, OPT_VERSION }, + { "scale", required_argument, nullptr, OPT_SCALE }, { 0, 0, 0, 0 } }; @@ -303,6 +305,7 @@ bool ParseCommandLine (int argc_, char *argv_[]) case OPT_RPM: if (!GetInt(optarg, opt.rpm) || (opt.rpm != 300 && opt.rpm != 360)) return BadValue("rpm"); break; case OPT_BYTES: if (!GetInt(optarg, opt.bytes) || !opt.bytes) return BadValue("bytes"); break; case OPT_HDF: if (!GetInt(optarg, opt.hdf) || (opt.hdf != 10 && opt.hdf != 11)) return BadValue("hdf"); break; + case OPT_SCALE: if (!GetInt(optarg, opt.scale)) return BadValue("scale"); break; case OPT_VERSION: Version();