From b4befd57a975cfea8595995484a4f663791bf8f4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 9 Jan 2020 21:03:01 -0500 Subject: [PATCH] Advances to being able to cope with STXs with no special features whatsoever. Well, other than perhaps a broken data CRC. Fuzzy bits, timing differences and the stuff between sectors are all currently absent. --- Storage/Disk/DiskImage/Formats/STX.cpp | 281 +++++++++++++++---------- 1 file changed, 172 insertions(+), 109 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/STX.cpp b/Storage/Disk/DiskImage/Formats/STX.cpp index 5e4662694..09473a41a 100644 --- a/Storage/Disk/DiskImage/Formats/STX.cpp +++ b/Storage/Disk/DiskImage/Formats/STX.cpp @@ -24,6 +24,79 @@ namespace { class TrackConstructor { public: + constexpr static uint16_t NoFirstOffset = std::numeric_limits::max(); + + struct Sector { + // Records explicitly present in the sector table. + uint32_t data_offset = 0; + size_t bit_position = 0; + uint16_t data_duration = 0; + uint8_t address[6] = {0, 0, 0, 0, 0, 0}; + uint8_t status = 0; + + // Other facts that will either be supplied by the STX or which + // will be empty. + std::vector fuzzy_mask; + std::vector contents; + std::vector timing; + + // Accessors. + uint32_t data_size() { + return uint32_t(128 << address[3]); + } +// std::vector get_track_header_image() { +// +// } + }; + + + TrackConstructor(const std::vector &track_data, const std::vector §ors, size_t track_size, uint16_t first_sync) : + track_data_(track_data), sectors_(sectors), track_size_(track_size), first_sync_(first_sync) { + } + + std::shared_ptr get_track() { + std::unique_ptr encoder; + std::unique_ptr segment; + + // To reconcile the list of sectors with the WD get track-style track image, + // use sector bodies as definitive and refer to the track image for in-fill. + for(const auto §or: sectors_) { + // HACK: assume nothing between sectors. Crazy time! + + if(!encoder) { + segment.reset(new PCMSegment); + encoder = Storage::Encodings::MFM::GetMFMEncoder(segment->data); + } + + // Add sector header. + encoder->add_ID_address_mark(); + for(int c = 0; c < 6; ++c) + encoder->add_byte(sector.address[c]); + + // Add a gap. + for(int c = 0; c < 12; ++c) + encoder->add_byte(0x4e); + + // Add sector body. + encoder->add_data_address_mark(); + for(const auto byte: sector.contents) { + encoder->add_byte(byte); + } + encoder->add_crc(sector.status & 0x8); // Get the CRC wrong if required. (TODO: take from track image, if possible?) + + // Add a gap. + for(int c = 0; c < 42; ++c) + encoder->add_byte(0x4e); + } + + return std::make_shared(*segment); + } + + private: + const std::vector &track_data_; + const std::vector §ors_; + const size_t track_size_; + const uint16_t first_sync_; }; @@ -107,131 +180,127 @@ std::shared_ptr<::Storage::Disk::Track> STX::get_track_at_position(::Storage::Di } // Grab sector records, if provided. - struct Sector { - // Records explicitly present in the sector table. - uint32_t data_offset = 0; - size_t bit_position = 0; - uint16_t data_duration = 0; - uint8_t address[6] = {0, 0, 0, 0, 0, 0}; - uint8_t status = 0; + std::vector sectors; + std::vector track_data; + uint16_t first_sync = TrackConstructor::NoFirstOffset; - // Other facts that will either be supplied by the STX or which - // will be empty. - std::vector fuzzy_mask; - std::vector contents; - - // Information accumulated locally during processing. - bool address_has_crc = true; - size_t track_offset_of_header = 0; - size_t track_offset_of_data = 0; - - // Accessors. - uint32_t data_size() { - return uint32_t(128 << address[3]); - } - }; - std::vector sectors; - if(flags & 1) { - // Read sector records first. - for(uint16_t c = 0; c < sector_count; ++c) { - sectors.emplace_back(); - sectors.back().data_offset = file_.get32le(); - sectors.back().bit_position = file_.get16le(); - sectors.back().data_duration = file_.get16le(); - file_.read(sectors.back().address, 6); - sectors.back().status = file_.get8(); - file_.seek(1, SEEK_CUR); - } - - // Now read fuzzy masks, if available. - if(fuzzy_size) { - uint32_t fuzzy_bytes_read = 0; - for(auto §or: sectors) { - // Check for the fuzzy bit mask; if it's not set then - // there's nothing for this sector. - if(!(sector.status & 0x80)) continue; - - // Make sure there are enough bytes left. - const uint32_t expected_bytes = sector.data_size(); - if(fuzzy_bytes_read + expected_bytes > fuzzy_size) break; - - // Okay, there are, so read them. - sector.fuzzy_mask = file_.read(expected_bytes); - fuzzy_bytes_read += expected_bytes; - } - - // It should be true that the number of fuzzy masks caused - // exactly the correct number of fuzzy bytes to be read. - // But, just in case, check and possibly skip some. - file_.seek(long(fuzzy_size) - fuzzy_bytes_read, SEEK_CUR); - } - } else { - // No sector records, so there should be no fuzzy records. - // Skip the supplied size, just in case. - file_.seek(fuzzy_size, SEEK_CUR); + // Sector records come first. + for(uint16_t c = 0; c < sector_count; ++c) { + sectors.emplace_back(); + sectors.back().data_offset = file_.get32le(); + sectors.back().bit_position = file_.get16le(); + sectors.back().data_duration = file_.get16le(); + file_.read(sectors.back().address, 6); + sectors.back().status = file_.get8(); + file_.seek(1, SEEK_CUR); } - // From here: there's either a track image or there isn't. - // - // If there is then it may or may not contain the sector bodies. - // The sectors themselves will be the guide — if they have - // offsets within the track image then that's that; if it's - // outside then that implies extra sector contents. - // - // If there isn't a track image at all then either the sectors - // were explicit or they're completely implicit, like an ST file. + // If fuzzy masks are specified, attach them to their corresponding sectors. + if(fuzzy_size) { + uint32_t fuzzy_bytes_read = 0; + for(auto §or: sectors) { + // Check for the fuzzy bit mask; if it's not set then + // there's nothing for this sector. + if(!(sector.status & 0x80)) continue; + + // Make sure there are enough bytes left. + const uint32_t expected_bytes = sector.data_size(); + if(fuzzy_bytes_read + expected_bytes > fuzzy_size) break; + + // Okay, there are, so read them. + sector.fuzzy_mask = file_.read(expected_bytes); + fuzzy_bytes_read += expected_bytes; + } + + // It should be true that the number of fuzzy masks caused + // exactly the correct number of fuzzy bytes to be read. + // But, just in case, check and possibly skip some. + file_.seek(long(fuzzy_size) - fuzzy_bytes_read, SEEK_CUR); + } + + // There may or may not be a track image. Grab it if so. // Grab the read-track-esque track contents, if available. - std::vector track_data; long sector_start = file_.tell(); if(flags & 0x40) { + // Bit 6 => there is a track to read; + // bit if(flags & 0x80) { - const uint16_t first_sync = file_.get16le(); + first_sync = file_.get16le(); const uint16_t image_size = file_.get16le(); track_data = file_.read(image_size); - - // TODO: and encode... ignoring sector contents? - (void)first_sync; } else { const uint16_t image_size = file_.get16le(); track_data = file_.read(image_size); } } - // Grab all sector contents. - if(sectors.empty()) { - // No explicit sectors were given, so create the implied sort. - for(int c = 0; c < sector_count; ++c) { - sectors.emplace_back(); - sectors.back().address[0] = uint8_t(address.position.as_int()); // Track. - sectors.back().address[1] = uint8_t(address.head); // Head. - sectors.back().address[2] = uint8_t(c + 1); // Sector. - sectors.back().address[3] = uint8_t(c + 1); // Size. - sectors.back().address_has_crc = false; - - sectors.back().contents = file_.read(512); - sectors.back().bit_position = size_t(c); // For the sake of ordering only. + // Grab sector contents. + long end_of_data = file_.tell(); + for(auto §or: sectors) { + // If the FDC record-not-found flag is set, there's no sector body to find. + // Otherwise there's a sector body in the file somewhere. + if(!(sector.status & 0x10)) { + file_.seek(sector.data_offset + sector_start, SEEK_SET); + sector.contents = file_.read(sector.data_size()); + end_of_data = std::max(end_of_data, file_.tell()); } - } else { - long end_of_data = file_.tell(); - for(auto §or: sectors) { - if(!(sector.status & 0x10)) { - file_.seek(sector.data_offset + sector_start, SEEK_SET); - sector.contents = file_.read(sector.data_size()); - end_of_data = std::max(end_of_data, file_.tell()); + } + file_.seek(end_of_data, SEEK_SET); + + // Grab timing info if available. + file_.seek(4, SEEK_CUR); // Skip the timing descriptor, as it includes no new information. + for(auto §or: sectors) { + // Skip any sector with no intra-sector bit width variation. + if(!(sector.status&1)) continue; + + const auto timing_record_size = sector.data_size() >> 4; // Use one entry per 16 bytes. + sector.timing.resize(timing_record_size); + + if(!is_new_format_) { + // Generate timing records for Macrodos/Speedlock. + // Timing is specified in quarters. Which might or might not be + // quantities of 128 bytes, who knows? + for(size_t c = 0; c < timing_record_size; ++c) { + if(c < (timing_record_size >> 2)) { + sector.timing[c] = 127; + } else if(c < ((timing_record_size*2) >> 2)) { + sector.timing[c] = 133; + } else if(c < ((timing_record_size*3) >> 2)) { + sector.timing[c] = 121; + } else { + sector.timing[c] = 127; + } } + + continue; + } + + // This is going to be a new-format record. + for(size_t c = 0; c < timing_record_size; ++c) { + sector.timing[c] = file_.get16be(); // These values are big endian, unlike the rest of the file. } - file_.seek(end_of_data, SEEK_SET); } - // Check for timing info. - if(is_new_format_) { - // Do something, do something, else, else. - } + // Sort the sectors by starting position. It's perfectly possible that they're always + // sorted in STX but, again, the reverse-engineered documentation doesn't make the + // promise, so that's that. + std::sort(sectors.begin(), sectors.end(), + [] (TrackConstructor::Sector &lhs, TrackConstructor::Sector &rhs) { + return lhs.bit_position < rhs.bit_position; + }); + /* - Having reached here: + Having reached here, the actual stuff of parsing the file structure should be done. + So hand off to the TrackConstructor. + */ + + TrackConstructor constructor(track_data, sectors, track_length, first_sync); + return constructor.get_track(); + + /* * if track_data is not empty, it is what you'd see from a read track command; * the vector of sectors will contain sectors to be written; contents will be populated, and each individually may or may not have a fuzzy_mask and/or timing. @@ -239,12 +308,7 @@ std::shared_ptr<::Storage::Disk::Track> STX::get_track_at_position(::Storage::Di Also note track_length, which is the perceived length of the track, rounded to whole bytes. */ - // Sort the sectors by starting position. It's perfectly possible that they're always - // sorted in STX but, again, the reverse-engineered documentation doesn't make the - // promise, so that's that. - std::sort(sectors.begin(), sectors.end(), [] (Sector &lhs, Sector &rhs) { return lhs.bit_position < rhs.bit_position; }); - - if(track_data.empty()) { +/* if(track_data.empty()) { } else { // Locate things that might be ID or data address marks; as a side effect of the way @@ -407,7 +471,6 @@ std::shared_ptr<::Storage::Disk::Track> STX::get_track_at_position(::Storage::Di } return std::make_shared(*segment); - } + }*/ - return nullptr; }