From 5a9f3cfc1e3a44c02a4469187101546d6af0e20d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Jul 2019 17:37:07 -0400 Subject: [PATCH] Completes Mac GCR decoding and its associated test. --- .../Mac/Clock SignalTests/MacGCRTests.mm | 30 ++++++++-- Storage/Disk/Encodings/AppleGCR/Sector.hpp | 4 +- .../Disk/Encodings/AppleGCR/SegmentParser.cpp | 58 ++++++++++++++++++- 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/MacGCRTests.mm b/OSBindings/Mac/Clock SignalTests/MacGCRTests.mm index d977237d8..16c61f431 100644 --- a/OSBindings/Mac/Clock SignalTests/MacGCRTests.mm +++ b/OSBindings/Mac/Clock SignalTests/MacGCRTests.mm @@ -163,17 +163,19 @@ uint8_t sector_plus_tags[524]; // Provide tags plus a sector body that are just the sector number ad infinitum. - memset(sector_plus_tags, sector_id, sizeof(sector_plus_tags)); + for(size_t c = 0; c < sizeof(sector_plus_tags); ++c) { + sector_plus_tags[c] = uint8_t(sector_id + (c * 3)); + } // NB: sync lengths below are identical to those for // the Apple II, as I have no idea whatsoever what they // should be. segment += Storage::Encodings::AppleGCR::Macintosh::header( - format, - track_id, + format ^ c, + track_id - c, sector_id, - is_side_two + is_side_two ^ (c & 1) ); segment += Storage::Encodings::AppleGCR::six_and_two_sync(7); segment += Storage::Encodings::AppleGCR::Macintosh::data(sector_id, sector_plus_tags); @@ -183,8 +185,26 @@ // Parse the prepared track to look for sectors. const auto decoded_sectors = Storage::Encodings::AppleGCR::sectors_from_segment(segment); - // Assert that all sectors fed in were found and correctly decoded. + // Assert that the proper number of sectors was found. XCTAssertEqual(decoded_sectors.size(), 8); + + // Assert that the sector descriptions and contents are correct. + int sector = 0; + for(const auto &pair: decoded_sectors) { + XCTAssertFalse(pair.second.has_header_checksum_error); + XCTAssertFalse(pair.second.has_data_checksum_error); + + XCTAssertEqual(pair.second.address.is_side_two, is_side_two ^ (sector & 1)); + XCTAssertEqual(pair.second.address.format, format ^ sector); + XCTAssertEqual(pair.second.address.track, track_id - sector); + XCTAssertEqual(pair.second.address.sector, sector); + + for(size_t c = 0; c < sizeof(pair.second.data.size()); ++c) { + XCTAssertEqual(pair.second.data[c], uint8_t(sector + (c * 3))); + } + + ++sector; + } } @end diff --git a/Storage/Disk/Encodings/AppleGCR/Sector.hpp b/Storage/Disk/Encodings/AppleGCR/Sector.hpp index a1a17e7dd..3515a4474 100644 --- a/Storage/Disk/Encodings/AppleGCR/Sector.hpp +++ b/Storage/Disk/Encodings/AppleGCR/Sector.hpp @@ -24,8 +24,8 @@ struct Sector { struct { /// For Apple II-type sectors, provides the volume number. uint_fast8_t volume = 0; - /// For Macintosh-type sectors, provides the type from the sector header. - uint_fast8_t type = 0; + /// For Macintosh-type sectors, provides the format from the sector header. + uint_fast8_t format = 0; }; uint_fast8_t track = 0; uint_fast8_t sector = 0; diff --git a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp index dce175201..1bd7e7947 100644 --- a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp +++ b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp @@ -196,15 +196,67 @@ std::map Storage::Encodings::AppleGCR::sectors_from_segment if(out_of_bounds) continue; // Test the checksum. - if(decoded_header[4] != (decoded_header[0] ^ decoded_header[1] ^ decoded_header[2] ^ decoded_header[3])) continue; + 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.type = decoded_header[3]; + sector->address.format = decoded_header[3]; sector->address.is_side_two = decoded_header[2] & 0x20; - // TODO: sector contents, naturally. + // 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;