From d065d6d98fb8e17f9b9a694f92a6973ee9a635cb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 15 Jul 2020 19:15:03 -0400 Subject: [PATCH 1/6] Adds read-only WOZ 2 support. --- Storage/Disk/DiskImage/Formats/WOZ.cpp | 71 ++++++++++++++++++++------ Storage/Disk/DiskImage/Formats/WOZ.hpp | 3 ++ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/WOZ.cpp b/Storage/Disk/DiskImage/Formats/WOZ.cpp index 78c50f1f2..bea9fd702 100644 --- a/Storage/Disk/DiskImage/Formats/WOZ.cpp +++ b/Storage/Disk/DiskImage/Formats/WOZ.cpp @@ -18,11 +18,21 @@ using namespace Storage::Disk; WOZ::WOZ(const std::string &file_name) : file_(file_name) { - const char signature[8] = { + constexpr const char signature1[8] = { 'W', 'O', 'Z', '1', char(0xff), 0x0a, 0x0d, 0x0a }; - if(!file_.check_signature(signature, 8)) throw Error::InvalidFormat; + constexpr const char signature2[8] = { + 'W', 'O', 'Z', '2', + char(0xff), 0x0a, 0x0d, 0x0a + }; + + const bool isWoz1 = file_.check_signature(signature1, 8); + file_.seek(0, SEEK_SET); + const bool isWoz2 = file_.check_signature(signature2, 8); + + if(!isWoz1 && !isWoz2) throw Error::InvalidFormat; + type_ = isWoz2 ? Type::WOZ2 : Type::WOZ1; // Get the file's CRC32. const uint32_t crc = file_.get32le(); @@ -52,13 +62,22 @@ WOZ::WOZ(const std::string &file_name) : switch(chunk_id) { case CK("INFO"): { const uint8_t version = file_.get8(); - if(version != 1) break; + if(version > 2) break; is_3_5_disk_ = file_.get8() == 2; is_read_only_ = file_.get8() == 1; - /* Ignored: - 1 byte: Synchronized; 1 = Cross track sync was used during imaging. - 1 byte: Cleaned; 1 = MC3470 fake bits have been removed. - 32 bytes: Cretor; a UTF-8 string. + /* + Ignored: + 1 byte: Synchronized; 1 = Cross track sync was used during imaging. + 1 byte: Cleaned; 1 = MC3470 fake bits have been removed. + 32 bytes: Creator; a UTF-8 string. + + And, if version 2, following the creator: + 1 byte number of disk sides + 1 byte boot sector format + 1 byte optimal bit timing + 2 bytes compatible hardware + 2 bytes minimum required RAM + 2 bytes largest track */ } break; @@ -99,11 +118,15 @@ long WOZ::file_offset(Track::Address address) { if(track_map_[table_position] == 0xff) return NoSuchTrack; // Seek to the real track. - return tracks_offset_ + track_map_[table_position] * 6656; + switch(type_) { + case Type::WOZ1: return tracks_offset_ + track_map_[table_position] * 6656; + default: + case Type::WOZ2: return tracks_offset_ + track_map_[table_position] * 8; + } } std::shared_ptr WOZ::get_track_at_position(Track::Address address) { - long offset = file_offset(address); + const long offset = file_offset(address); if(offset == NoSuchTrack) return nullptr; // Seek to the real track. @@ -113,18 +136,34 @@ std::shared_ptr WOZ::get_track_at_position(Track::Address address) { std::lock_guard lock_guard(file_.get_file_access_mutex()); file_.seek(offset, SEEK_SET); - // In WOZ a track is up to 6646 bytes of data, followed by a two-byte record of the - // number of bytes that actually had data in them, then a two-byte count of the number - // of bits that were used. Other information follows but is not intended for emulation. - track_contents = file_.read(6646); - file_.seek(2, SEEK_CUR); - number_of_bits = std::min(file_.get16le(), uint16_t(6646*8)); + switch(type_) { + case Type::WOZ1: + // In WOZ 1, a track is up to 6646 bytes of data, followed by a two-byte record of the + // number of bytes that actually had data in them, then a two-byte count of the number + // of bits that were used. Other information follows but is not intended for emulation. + track_contents = file_.read(6646); + file_.seek(2, SEEK_CUR); + number_of_bits = std::min(file_.get16le(), uint16_t(6646*8)); + break; + + case Type::WOZ2: { + // In WOZ 2 an extra level of indirection allows for variable track sizes. + const uint16_t starting_block = file_.get16le(); + file_.seek(2, SEEK_CUR); // Skip the block count; the amount of data to read is implied by the number of bits. + number_of_bits = file_.get32le(); + + file_.seek(starting_block * 512, SEEK_SET); + track_contents = file_.read((number_of_bits + 7) >> 3); + } break; + } } return std::make_shared(PCMSegment(number_of_bits, track_contents)); } void WOZ::set_tracks(const std::map> &tracks) { + if(type_ == Type::WOZ2) return; + for(const auto &pair: tracks) { // Decode the track and store, patching into the post_crc_contents_. auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000)); @@ -155,5 +194,5 @@ void WOZ::set_tracks(const std::map> &tra } bool WOZ::get_is_read_only() { - return file_.get_is_known_read_only(); + return file_.get_is_known_read_only() || is_read_only_ || type_ == Type::WOZ2; // WOZ 2 disks are currently read only. } diff --git a/Storage/Disk/DiskImage/Formats/WOZ.hpp b/Storage/Disk/DiskImage/Formats/WOZ.hpp index de1821d9b..c0531a94c 100644 --- a/Storage/Disk/DiskImage/Formats/WOZ.hpp +++ b/Storage/Disk/DiskImage/Formats/WOZ.hpp @@ -34,6 +34,9 @@ class WOZ: public DiskImage { private: Storage::FileHolder file_; + enum class Type { + WOZ1, WOZ2 + } type_ = Type::WOZ1; bool is_read_only_ = false; bool is_3_5_disk_ = false; uint8_t track_map_[160]; From aed61f6251ad04bda0b01fb58fa45feb460bd173 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 15 Jul 2020 19:34:05 -0400 Subject: [PATCH 2/6] Implements latest advocated MC3470 behaviour. --- Storage/Disk/Drive.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 473f0d198..ad3082a22 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -250,6 +250,15 @@ void Drive::run_for(const Cycles cycles) { // MARK: - Track timed event loop void Drive::get_next_event(float duration_already_passed) { + /* + Quick word on random-bit generation logic below; it seeks to obey the following logic: + if there is a gap of 15µs between recorded bits, start generating flux transitions + at random intervals thereafter, unless and until one is within 5µs of the next real transition. + + This behaviour is based on John Morris' observations of an MC3470, as described in his WOZ + file format documentation — https://applesaucefdc.com/woz/reference2/ + */ + if(!disk_) { current_event_.type = Track::Event::IndexHole; current_event_.length = 1.0f; @@ -268,17 +277,18 @@ void Drive::get_next_event(float duration_already_passed) { // If gain has now been turned up so as to generate noise, generate some noise. if(random_interval_ > 0.0f) { current_event_.type = Track::Event::FluxTransition; - current_event_.length = float(2 + (random_source_&1)) / 1000000.0f; + current_event_.length = float(2 + (random_source_&1)) / 1'000'000.0f; random_source_ = (random_source_ >> 1) | (random_source_ << 63); - if(random_interval_ < current_event_.length) { - current_event_.length = random_interval_; + // If this random transition is closer than 5µs to the next real bit, + // discard it. + if(random_interval_ < current_event_.length - 5.0f / 1'000'000.f) { random_interval_ = 0.0f; } else { random_interval_ -= current_event_.length; + set_next_event_time_interval(current_event_.length); + return; } - set_next_event_time_interval(current_event_.length); - return; } if(track_) { @@ -299,9 +309,9 @@ void Drive::get_next_event(float duration_already_passed) { // convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_ float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f); - // An interval greater than 15ms => adjust gain up the point where noise starts happening. - // Seed that up and leave a 15ms gap until it starts. - constexpr float safe_gain_period = 15.0f / 1000000.0f; + // An interval greater than 15µs => adjust gain up the point where noise starts happening. + // Seed that up and leave a 15µs gap until it starts. + constexpr float safe_gain_period = 15.0f / 1'000'000.0f; if(interval >= safe_gain_period) { random_interval_ = interval - safe_gain_period; interval = safe_gain_period; From 8da7806ee9cf2d6ce4016ad17efbccb52862b134 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 15 Jul 2020 22:27:04 -0400 Subject: [PATCH 3/6] Liberalises segment parser not necessarily to require a standard epilogue. It seems that real disks don't always have them; I guess the boot ROM doesn't require one. --- .../Disk/Encodings/AppleGCR/SegmentParser.cpp | 391 +++++++++--------- 1 file changed, 195 insertions(+), 196 deletions(-) diff --git a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp index 9a91c4155..12a99e76e 100644 --- a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp +++ b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp @@ -12,6 +12,8 @@ #include +using namespace Storage::Encodings::AppleGCR; + namespace { const uint8_t six_and_two_unmapping[] = { @@ -55,9 +57,183 @@ uint8_t unmap_five_and_three(uint8_t source) { return five_and_three_unmapping[source - 0xab]; } +std::unique_ptr decode_macintosh_sector(const std::array &header, const std::unique_ptr &original) { + // There must be at least 704 bytes to decode from. + if(original->data.size() < 704) return nullptr; + + // Attempt a six-and-two unmapping of the header. + std::array decoded_header; + for(size_t c = 0; c < decoded_header.size(); ++c) { + decoded_header[c] = unmap_six_and_two(header[c]); + if(decoded_header[c] == 0xff) { + return nullptr; + } + } + + // Allocate a sector. + auto sector = std::make_unique(); + sector->data.resize(704); + + // Test the checksum. + if(decoded_header[4] != (decoded_header[0] ^ decoded_header[1] ^ decoded_header[2] ^ decoded_header[3])) + sector->has_header_checksum_error = true; + + // Decode the header. + sector->address.track = uint8_t(decoded_header[0] | ((decoded_header[2]&0x1f) << 6)); + sector->address.sector = decoded_header[1]; + sector->address.format = decoded_header[3]; + sector->address.is_side_two = decoded_header[2] & 0x20; + + // Reverse the GCR encoding of the sector contents to get back to 6-bit data. + for(size_t index = 0; index < sector->data.size(); ++index) { + sector->data[index] = unmap_six_and_two(original->data[index]); + if(sector->data[index] == 0xff) { + return nullptr; + } + } + + // The first byte in the sector is a repeat of the sector number; test it + // for correctness. + if(sector->data[0] != sector->address.sector) { + return nullptr; + } + + // Cf. the corresponding section of Encoder.cpp for logic below. + int checksum[3] = {0, 0, 0}; + for(size_t c = 0; c < 175; ++c) { + // Calculate the rolling checcksum in order to decode the bytes. + checksum[0] = (checksum[0] << 1) | (checksum[0] >> 7); + + // All offsets are +1 below, to skip the initial sector number duplicate. + const uint8_t top_bits = sector->data[1 + c*4]; + + // Decode first byte. + sector->data[0 + c * 3] = uint8_t((sector->data[2 + c*4] + ((top_bits & 0x30) << 2)) ^ checksum[0]); + checksum[2] += sector->data[0 + c * 3] + (checksum[0] >> 8); + + // Decode second byte; + sector->data[1 + c * 3] = uint8_t((sector->data[3 + c*4] + ((top_bits & 0x0c) << 4)) ^ checksum[2]); + checksum[1] += sector->data[1 + c * 3] + (checksum[2] >> 8); + + // Decode third byte, if there is one. + if(c != 174) { + sector->data[2 + c * 3] = uint8_t((sector->data[4 + c*4] + ((top_bits & 0x03) << 6)) ^ checksum[1]); + checksum[0] += sector->data[2 + c * 3] + (checksum[1] >> 8); + } + + // Reset carries. + checksum[0] &= 0xff; + checksum[1] &= 0xff; + checksum[2] &= 0xff; + } + + // Test the checksum. + if( + checksum[0] != uint8_t(sector->data[703] + ((sector->data[700] & 0x03) << 6)) || + checksum[1] != uint8_t(sector->data[702] + ((sector->data[700] & 0x0c) << 4)) || + checksum[2] != uint8_t(sector->data[701] + ((sector->data[700] & 0x30) << 2)) + ) sector->has_data_checksum_error = true; + + // Report success. + sector->data.resize(524); + sector->encoding = Sector::Encoding::Macintosh; + return sector; } -using namespace Storage::Encodings::AppleGCR; +std::unique_ptr decode_appleii_sector(const std::array &header, const std::unique_ptr &original, bool is_five_and_three) { + // There must be at least 411 bytes to decode a five-and-three sector from; + // there must be only 343 if this is a six-and-two sector. + const size_t data_size = is_five_and_three ? 411 : 343; + if(original->data.size() < data_size) return nullptr; + + // Check for apparent four and four encoding. + uint_fast8_t header_mask = 0xff; + for(auto c : header) header_mask &= c; + header_mask &= 0xaa; + if(header_mask != 0xaa) return nullptr; + + // Allocate a sector and fill the header fields. + auto sector = std::make_unique(); + sector->data.resize(data_size); + + sector->address.volume = ((header[0] << 1) | 1) & header[1]; + sector->address.track = ((header[2] << 1) | 1) & header[3]; + sector->address.sector = ((header[4] << 1) | 1) & header[5]; + + // Check the header checksum. + // The 0x11 is reverse engineered from the game 'Alien Rain' and is present even on the boot sector, + // so probably isn't copy protection? + uint_fast8_t checksum = (((header[6] << 1) | 1) & header[7]) ^ (is_five_and_three ? 0x11 : 0x00); + if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) return nullptr; + + // Unmap the sector contents. + for(size_t index = 0; index < data_size; ++index) { + sector->data[index] = is_five_and_three ? unmap_five_and_three(original->data[index]) : unmap_six_and_two(original->data[index]); + if(sector->data[index] == 0xff) { + return nullptr; + } + } + + // Undo the XOR step on sector contents and check that checksum. + for(std::size_t c = 1; c < sector->data.size(); ++c) { + sector->data[c] ^= sector->data[c-1]; + } + if(sector->data.back()) return nullptr; + + // Having checked the checksum, remove it. + sector->data.resize(sector->data.size() - 1); + + if(is_five_and_three) { + // TODO: the below is almost certainly incorrect; Beneath Apple DOS partly documents + // the process, enough to give the basic outline below of how five source bytes are + // mapped to eight five-bit quantities, but isn't clear on the order those bytes will + // end up in on disk. + + std::vector buffer(256); + for(size_t c = 0; c < 0x33; ++c) { + const uint8_t *const base = §or->data[0x032 - c]; + + buffer[(c * 5) + 0] = uint8_t((base[0x000] << 3) | (base[0x100] >> 2)); + buffer[(c * 5) + 1] = uint8_t((base[0x033] << 3) | (base[0x133] >> 2)); + buffer[(c * 5) + 2] = uint8_t((base[0x066] << 3) | (base[0x166] >> 2)); + buffer[(c * 5) + 3] = uint8_t((base[0x099] << 3) | ((base[0x100] & 2) << 1) | (base[0x133] & 2) | ((base[0x166] & 2) >> 1)); + buffer[(c * 5) + 4] = uint8_t((base[0x0cc] << 3) | ((base[0x100] & 1) << 2) | ((base[0x133] & 1) << 1) | (base[0x166] & 1)); + } + buffer[255] = uint8_t((sector->data[0x0ff] << 3) | (sector->data[0x199] >> 2)); + + sector->data = std::move(buffer); + sector->encoding = Sector::Encoding::FiveAndThree; + } else { + // Undo the 6 and 2 mapping. + const uint8_t bit_reverse[] = {0, 2, 1, 3}; + #define unmap(byte, nibble, shift) \ + sector->data[86 + byte] = uint8_t(\ + (sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]); + + for(std::size_t c = 0; c < 84; ++c) { + unmap(c, c, 0); + unmap(c+86, c, 2); + unmap(c+172, c, 4); + } + + unmap(84, 84, 0); + unmap(170, 84, 2); + unmap(85, 85, 0); + unmap(171, 85, 2); + + #undef unmap + + // Throw away the collection of two-bit chunks from the start of the sector. + sector->data.erase(sector->data.begin(), sector->data.end() - 256); + + sector->encoding = Sector::Encoding::SixAndTwo; + } + + // Return successfully. + return sector; +} + +} std::map Storage::Encodings::AppleGCR::sectors_from_segment(const Disk::PCMSegment &segment) { std::map result; @@ -109,7 +285,7 @@ std::map Storage::Encodings::AppleGCR::sectors_from_segment // If this is the start of a data section, and at least // one header has been witnessed, start a sector. - if(scanner[2] == data_prologue[2]) { + if(scanner[2] == data_prologue[2] || is_five_and_three) { new_sector = std::make_unique(); new_sector->data.reserve(710); } else { @@ -120,23 +296,12 @@ std::map Storage::Encodings::AppleGCR::sectors_from_segment } } else { if(new_sector) { - // Check whether the value just read is a legal GCR byte, in six-and-two - // encoding (which is a strict superset of five-and-three). + // Check whether the value just read is a legal GCR byte, for this sector; + // if not, or if const bool is_invalid = is_five_and_three ? (unmap_five_and_three(value) == 0xff) : (unmap_six_and_two(value) == 0xff); - if(is_invalid) { - // The second byte of the standard epilogue is illegal, so this still may - // be a valid sector. If the final byte was the first byte of an epilogue, - // chop it off and see whether the sector is otherwise intelligible. - - if(new_sector->data.empty() || new_sector->data.back() != epilogue[0]) { - // No sector found; reset scanning procedure. - new_sector.reset(); - pointer = scanning_sentinel; - continue; - } - - // Chop off the last byte. - new_sector->data.resize(new_sector->data.size() - 1); + if(is_invalid || new_sector->data.size() >= 704) { + // The second byte of the standard epilogue is 'illegal', as is the first byte of + // all prologues. So either a whole sector has been captured up to now, or it hasn't. // Move the sector elsewhere for processing; there's definitely no way to proceed with // the prospective sector if it doesn't parse. @@ -144,185 +309,19 @@ std::map Storage::Encodings::AppleGCR::sectors_from_segment new_sector.reset(); pointer = scanning_sentinel; - // Check for valid decoding options. - switch(sector->data.size()) { - default: // This is not a decodeable sector. - break; - - case 411: // Potentially this is an Apple II five-and-three sector. - case 343: { // Potentially this is an Apple II six-and-two sector. - // Check for apparent four and four encoding. - uint_fast8_t header_mask = 0xff; - for(auto c : header) header_mask &= c; - header_mask &= 0xaa; - if(header_mask != 0xaa) continue; - - sector->address.volume = ((header[0] << 1) | 1) & header[1]; - sector->address.track = ((header[2] << 1) | 1) & header[3]; - sector->address.sector = ((header[4] << 1) | 1) & header[5]; - - // Check the header checksum. - // The 0x11 is reverse engineered from the game 'Alien Rain' and is present even on the boot sector, - // so probably isn't copy protection? - uint_fast8_t checksum = (((header[6] << 1) | 1) & header[7]) ^ (is_five_and_three ? 0x11 : 0x00); - if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) continue; - - // Unmap the sector contents. - bool out_of_bounds = false; - for(auto &c : sector->data) { - c = is_five_and_three ? unmap_five_and_three(c) : unmap_six_and_two(c); - if(c == 0xff) { - out_of_bounds = true; - break; - } - } - if(out_of_bounds) continue; - - // Undo the XOR step on sector contents and check that checksum. - for(std::size_t c = 1; c < sector->data.size(); ++c) { - sector->data[c] ^= sector->data[c-1]; - } - if(sector->data.back()) continue; - - // Having checked the checksum, remove it. - sector->data.resize(sector->data.size() - 1); - - if(is_five_and_three) { - // TODO: the above is almost certainly incorrect; Beneath Apple DOS partly documents - // the process, enough to give the basic outline below of how five source bytes are - // mapped to eight five-bit quantities, but isn't clear on the order those bytes will - // end up in on disk. - - std::vector buffer(256); - for(size_t c = 0; c < 0x33; ++c) { - const uint8_t *const base = §or->data[0x032 - c]; - - buffer[(c * 5) + 0] = uint8_t((base[0x000] << 3) | (base[0x100] >> 2)); - buffer[(c * 5) + 1] = uint8_t((base[0x033] << 3) | (base[0x133] >> 2)); - buffer[(c * 5) + 2] = uint8_t((base[0x066] << 3) | (base[0x166] >> 2)); - buffer[(c * 5) + 3] = uint8_t((base[0x099] << 3) | ((base[0x100] & 2) << 1) | (base[0x133] & 2) | ((base[0x166] & 2) >> 1)); - buffer[(c * 5) + 4] = uint8_t((base[0x0cc] << 3) | ((base[0x100] & 1) << 2) | ((base[0x133] & 1) << 1) | (base[0x166] & 1)); - } - buffer[255] = uint8_t((sector->data[0x0ff] << 3) | (sector->data[0x199] >> 2)); - - sector->data = std::move(buffer); - sector->encoding = Sector::Encoding::FiveAndThree; - } else { - // Undo the 6 and 2 mapping. - const uint8_t bit_reverse[] = {0, 2, 1, 3}; - #define unmap(byte, nibble, shift) \ - sector->data[86 + byte] = uint8_t(\ - (sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]); - - for(std::size_t c = 0; c < 84; ++c) { - unmap(c, c, 0); - unmap(c+86, c, 2); - unmap(c+172, c, 4); - } - - unmap(84, 84, 0); - unmap(170, 84, 2); - unmap(85, 85, 0); - unmap(171, 85, 2); - - #undef unmap - - // Throw away the collection of two-bit chunks from the start of the sector. - sector->data.erase(sector->data.begin(), sector->data.end() - 256); - - sector->encoding = Sector::Encoding::SixAndTwo; - } - // Add this sector to the map. - result.insert(std::make_pair(sector_location, std::move(*sector))); - } break; - - case 704: { // Potentially this is a Macintosh sector. - // Attempt a six-and-two unmapping of the header. - std::array decoded_header; - bool out_of_bounds = false; - for(size_t c = 0; c < decoded_header.size(); ++c) { - decoded_header[c] = unmap_six_and_two(header[c]); - if(decoded_header[c] == 0xff) { - out_of_bounds = true; - break; - } - } - if(out_of_bounds) { - continue; - } - - // Test the checksum. - if(decoded_header[4] != (decoded_header[0] ^ decoded_header[1] ^ decoded_header[2] ^ decoded_header[3])) - sector->has_header_checksum_error = true; - - // Decode the header. - sector->address.track = uint8_t(decoded_header[0] | ((decoded_header[2]&0x1f) << 6)); - sector->address.sector = decoded_header[1]; - sector->address.format = decoded_header[3]; - sector->address.is_side_two = decoded_header[2] & 0x20; - - // Reverse the GCR encoding of the sector contents to get back to 6-bit data. - for(auto &c: sector->data) { - c = unmap_six_and_two(c); - if(c == 0xff) { - out_of_bounds = true; - break; - } - } - if(out_of_bounds) { - continue; - } - - // The first byte in the sector is a repeat of the sector number; test it - // for correctness. - if(sector->data[0] != sector->address.sector) { - continue; - } - - // Cf. the corresponding section of Encoder.cpp for logic below. - int checksum[3] = {0, 0, 0}; - for(size_t c = 0; c < 175; ++c) { - // Calculate the rolling checcksum in order to decode the bytes. - checksum[0] = (checksum[0] << 1) | (checksum[0] >> 7); - - // All offsets are +1 below, to skip the initial sector number duplicate. - const uint8_t top_bits = sector->data[1 + c*4]; - - // Decode first byte. - sector->data[0 + c * 3] = uint8_t((sector->data[2 + c*4] + ((top_bits & 0x30) << 2)) ^ checksum[0]); - checksum[2] += sector->data[0 + c * 3] + (checksum[0] >> 8); - - // Decode second byte; - sector->data[1 + c * 3] = uint8_t((sector->data[3 + c*4] + ((top_bits & 0x0c) << 4)) ^ checksum[2]); - checksum[1] += sector->data[1 + c * 3] + (checksum[2] >> 8); - - // Decode third byte, if there is one. - if(c != 174) { - sector->data[2 + c * 3] = uint8_t((sector->data[4 + c*4] + ((top_bits & 0x03) << 6)) ^ checksum[1]); - checksum[0] += sector->data[2 + c * 3] + (checksum[1] >> 8); - } - - // Reset carries. - checksum[0] &= 0xff; - checksum[1] &= 0xff; - checksum[2] &= 0xff; - } - - // Test the checksum. - if( - checksum[0] != uint8_t(sector->data[703] + ((sector->data[700] & 0x03) << 6)) || - checksum[1] != uint8_t(sector->data[702] + ((sector->data[700] & 0x0c) << 4)) || - checksum[2] != uint8_t(sector->data[701] + ((sector->data[700] & 0x30) << 2)) - ) sector->has_data_checksum_error = true; - - // Chop to size, and that's that. - sector->data.resize(524); - - // Add this sector to the map. - sector->encoding = Sector::Encoding::Macintosh; - result.insert(std::make_pair(sector_location, std::move(*sector))); - } break; + // Potentially this is a Macintosh sector. + auto macintosh_sector = decode_macintosh_sector(header, sector); + if(macintosh_sector) { + result.insert(std::make_pair(sector_location, std::move(*macintosh_sector))); + continue; } + + // Apple II then? + auto appleii_sector = decode_appleii_sector(header, sector, is_five_and_three); + if(appleii_sector) { + result.insert(std::make_pair(sector_location, std::move(*appleii_sector))); + } + } else { new_sector->data.push_back(value); } From 9a952c889fdddb438b3cbb11153f1b0c7927fb99 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 15 Jul 2020 22:44:54 -0400 Subject: [PATCH 4/6] Fixes exit from random gain noise. --- Storage/Disk/Drive.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index ad3082a22..ae7c76c56 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -282,7 +282,7 @@ void Drive::get_next_event(float duration_already_passed) { // If this random transition is closer than 5µs to the next real bit, // discard it. - if(random_interval_ < current_event_.length - 5.0f / 1'000'000.f) { + if(random_interval_ - 5.0f / 1'000'000.f < current_event_.length) { random_interval_ = 0.0f; } else { random_interval_ -= current_event_.length; From 4fec7c82abdc3192d2b9dc7ef65d053efa96b04e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 16 Jul 2020 21:43:03 -0400 Subject: [PATCH 5/6] Very minor grammar improvement. --- Storage/Disk/DiskImage/Formats/WOZ.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Disk/DiskImage/Formats/WOZ.hpp b/Storage/Disk/DiskImage/Formats/WOZ.hpp index c0531a94c..f131f46c7 100644 --- a/Storage/Disk/DiskImage/Formats/WOZ.hpp +++ b/Storage/Disk/DiskImage/Formats/WOZ.hpp @@ -52,7 +52,7 @@ class WOZ: public DiskImage { the track does not exit. */ long file_offset(Track::Address address); - constexpr static long NoSuchTrack = 0; // This is definitely an offset a track can't lie at. + constexpr static long NoSuchTrack = 0; // This is an offset a track definitely can't lie at. }; } From c7ef258494ff582e871eeb2407e40acd6ec1b4f2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 16 Jul 2020 21:44:14 -0400 Subject: [PATCH 6/6] Ensures that five-and-three sectors pass static analysis. --- Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp index 12a99e76e..9531229d0 100644 --- a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp +++ b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp @@ -161,9 +161,7 @@ std::unique_ptr decode_appleii_sector(const std::array sector->address.sector = ((header[4] << 1) | 1) & header[5]; // Check the header checksum. - // The 0x11 is reverse engineered from the game 'Alien Rain' and is present even on the boot sector, - // so probably isn't copy protection? - uint_fast8_t checksum = (((header[6] << 1) | 1) & header[7]) ^ (is_five_and_three ? 0x11 : 0x00); + const uint_fast8_t checksum = ((header[6] << 1) | 1) & header[7]; if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) return nullptr; // Unmap the sector contents. @@ -205,7 +203,7 @@ std::unique_ptr decode_appleii_sector(const std::array sector->encoding = Sector::Encoding::FiveAndThree; } else { // Undo the 6 and 2 mapping. - const uint8_t bit_reverse[] = {0, 2, 1, 3}; + constexpr uint8_t bit_reverse[] = {0, 2, 1, 3}; #define unmap(byte, nibble, shift) \ sector->data[86 + byte] = uint8_t(\ (sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]); @@ -285,10 +283,10 @@ std::map Storage::Encodings::AppleGCR::sectors_from_segment // If this is the start of a data section, and at least // one header has been witnessed, start a sector. - if(scanner[2] == data_prologue[2] || is_five_and_three) { + if(scanner[2] == data_prologue[2]) { new_sector = std::make_unique(); new_sector->data.reserve(710); - } else { + } else { // i.e. the third symbol is from either of the header prologues. sector_location = size_t(bit % segment.data.size()); header_delay = 200; // Allow up to 200 bytes to find the body, if the // track split comes in between.