From c36d7b4972e83346932d70fd5919bab5a70c5189 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 4 May 2018 23:11:12 -0400 Subject: [PATCH] Makes first attempt at 6 and 2 decoder. --- Analyser/Static/DiskII/StaticAnalyser.cpp | 14 +- .../Clock Signal.xcodeproj/project.pbxproj | 18 +- Storage/Disk/Encodings/AppleGCR/Encoder.cpp | 16 +- Storage/Disk/Encodings/AppleGCR/Sector.hpp | 47 +++++ .../Disk/Encodings/AppleGCR/SegmentParser.cpp | 166 ++++++++++++++++++ .../Disk/Encodings/AppleGCR/SegmentParser.hpp | 30 ++++ .../Disk/Encodings/AppleGCR/TrackParser.cpp | 9 - .../Disk/Encodings/AppleGCR/TrackParser.hpp | 14 -- Storage/Disk/Encodings/MFM/SegmentParser.hpp | 3 +- 9 files changed, 275 insertions(+), 42 deletions(-) create mode 100644 Storage/Disk/Encodings/AppleGCR/Sector.hpp create mode 100644 Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp create mode 100644 Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp delete mode 100644 Storage/Disk/Encodings/AppleGCR/TrackParser.cpp delete mode 100644 Storage/Disk/Encodings/AppleGCR/TrackParser.hpp diff --git a/Analyser/Static/DiskII/StaticAnalyser.cpp b/Analyser/Static/DiskII/StaticAnalyser.cpp index 4397aed9f..5cce37dfc 100644 --- a/Analyser/Static/DiskII/StaticAnalyser.cpp +++ b/Analyser/Static/DiskII/StaticAnalyser.cpp @@ -9,14 +9,24 @@ #include "StaticAnalyser.hpp" #include "../AppleII/Target.hpp" +#include "../../../Storage/Disk/Track/TrackSerialiser.hpp" +#include "../../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp" + Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { + // This analyser can comprehend disks only. + if(media.disks.empty()) return {}; + + // Grab track 0, sector 0. + auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, 0)); + auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment( + Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000))); + using Target = Analyser::Static::AppleII::Target; auto target = std::unique_ptr(new Target); target->machine = Machine::AppleII; target->media = media; - if(!target->media.disks.empty()) - target->disk_controller = Target::DiskController::SixteenSector; + target->disk_controller = Target::DiskController::SixteenSector; TargetList targets; targets.push_back(std::move(target)); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 37a28766d..d16ca4732 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -644,8 +644,8 @@ 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; }; 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; - 4BF437EE209D0F7E008CBD6B /* TrackParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* TrackParser.cpp */; }; - 4BF437EF209D0F7E008CBD6B /* TrackParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* TrackParser.cpp */; }; + 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; + 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; }; 4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; }; 4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; @@ -1422,8 +1422,9 @@ 4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = ""; }; 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = ""; }; 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = ""; }; - 4BF437EC209D0F7E008CBD6B /* TrackParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackParser.cpp; sourceTree = ""; }; - 4BF437ED209D0F7E008CBD6B /* TrackParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackParser.hpp; sourceTree = ""; }; + 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = ""; }; + 4BF437ED209D0F7E008CBD6B /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = ""; }; + 4BF437F0209D112F008CBD6B /* Sector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sector.hpp; sourceTree = ""; }; 4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = ""; }; 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = ""; }; 4BF8295F1D8F3C87001BAE39 /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRC.hpp; path = ../../NumberTheory/CRC.hpp; sourceTree = ""; }; @@ -3014,9 +3015,10 @@ isa = PBXGroup; children = ( 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */, + 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */, 4BD67DCF209BF27B00AB2146 /* Encoder.hpp */, - 4BF437EC209D0F7E008CBD6B /* TrackParser.cpp */, - 4BF437ED209D0F7E008CBD6B /* TrackParser.hpp */, + 4BF437F0209D112F008CBD6B /* Sector.hpp */, + 4BF437ED209D0F7E008CBD6B /* SegmentParser.hpp */, ); name = AppleGCR; path = Encodings/AppleGCR; @@ -3629,7 +3631,7 @@ 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, 4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, - 4BF437EF209D0F7E008CBD6B /* TrackParser.cpp in Sources */, + 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, 4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */, 4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */, 4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */, @@ -3872,7 +3874,7 @@ 4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */, 4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */, 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */, - 4BF437EE209D0F7E008CBD6B /* TrackParser.cpp in Sources */, + 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, 4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */, 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */, diff --git a/Storage/Disk/Encodings/AppleGCR/Encoder.cpp b/Storage/Disk/Encodings/AppleGCR/Encoder.cpp index 2651d69ad..69dfe48d2 100644 --- a/Storage/Disk/Encodings/AppleGCR/Encoder.cpp +++ b/Storage/Disk/Encodings/AppleGCR/Encoder.cpp @@ -139,24 +139,24 @@ Storage::Disk::PCMSegment AppleGCR::six_and_two_data(const uint8_t *source) { // and combined copies of the bottom two bits of the sector // contents; the 256 bytes afterwards are the remaining // six bits. - const uint8_t bit_shuffle[] = {0, 2, 1, 3}; + const uint8_t bit_reverse[] = {0, 2, 1, 3}; for(std::size_t c = 0; c < 84; ++c) { segment.data[3 + c] = static_cast( - bit_shuffle[source[c]&3] | - (bit_shuffle[source[c + 86]&3] << 2) | - (bit_shuffle[source[c + 172]&3] << 4) + bit_reverse[source[c]&3] | + (bit_reverse[source[c + 86]&3] << 2) | + (bit_reverse[source[c + 172]&3] << 4) ); } segment.data[87] = static_cast( - (bit_shuffle[source[84]&3] << 0) | - (bit_shuffle[source[170]&3] << 2) + (bit_reverse[source[84]&3] << 0) | + (bit_reverse[source[170]&3] << 2) ); segment.data[88] = static_cast( - (bit_shuffle[source[85]&3] << 0) | - (bit_shuffle[source[171]&3] << 2) + (bit_reverse[source[85]&3] << 0) | + (bit_reverse[source[171]&3] << 2) ); for(std::size_t c = 0; c < 256; ++c) { diff --git a/Storage/Disk/Encodings/AppleGCR/Sector.hpp b/Storage/Disk/Encodings/AppleGCR/Sector.hpp new file mode 100644 index 000000000..83611dd48 --- /dev/null +++ b/Storage/Disk/Encodings/AppleGCR/Sector.hpp @@ -0,0 +1,47 @@ +// +// Sector.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/05/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef Sector_h +#define Sector_h + +#include +#include + +namespace Storage { +namespace Encodings { +namespace AppleGCR { + +struct Sector { + /*! + Describes the location of a sector, implementing < to allow for use as a set key. + */ + struct Address { + uint_fast8_t volume = 0, track = 0, sector = 0; + + bool operator < (const Address &rhs) const { + return ((volume << 16) | (track << 8) | sector) < ((rhs.volume << 16) | (rhs.track << 8) | rhs.sector); + } + }; + + Address address; + std::vector data; + + bool has_data_checksum_error = false; + bool has_header_checksum_error = false; + + enum class Encoding { + FiveAndThree, SixAndTwo + }; + Encoding encoding = Encoding::SixAndTwo; +}; + +} +} +} + +#endif /* Sector_h */ diff --git a/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp new file mode 100644 index 000000000..8b43bbfe0 --- /dev/null +++ b/Storage/Disk/Encodings/AppleGCR/SegmentParser.cpp @@ -0,0 +1,166 @@ +// +// SegmentParser.cpp +// Clock Signal +// +// Created by Thomas Harte on 04/05/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "SegmentParser.hpp" + +#include "Encoder.hpp" + +#include + +namespace { + +const uint8_t six_and_two_unmapping[] = { + 0x00, 0x01, 0xff, 0xff, + 0x02, 0x03, 0xff, 0x04, 0x05, 0x06, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x07, 0x08, 0xff, 0xff, + 0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0xff, 0xff, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0xff, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1b, 0xff, 0x1c, 0x1d, 0x1e, 0xff, 0xff, + 0xff, 0x1f, 0xff, 0xff, 0x20, 0x21, 0xff, 0x22, + 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x29, 0x2a, 0x2b, 0xff, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0xff, 0xff, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xff, 0x39, + 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0xff, 0xff +}; + +} + +using namespace Storage::Encodings::AppleGCR; + +std::map Storage::Encodings::AppleGCR::sectors_from_segment(const Disk::PCMSegment &&segment) { + std::map result; + + uint_fast8_t shift_register = 0; + + const std::size_t scanning_sentinel = std::numeric_limits::max(); + std::unique_ptr new_sector; + std::size_t pointer = scanning_sentinel; + std::array header{{0, 0, 0, 0, 0, 0, 0, 0}}; + std::array scanner; + + for(unsigned int bit = 0; bit < segment.number_of_bits; ++bit) { + // Apple GCR parsing: bytes always have the top bit set. + shift_register = static_cast((shift_register << 1) | segment.bit(bit)); + if(!(shift_register&0x80)) continue; + + // Grab the byte. + const uint_fast8_t value = shift_register; + shift_register = 0; + + if(pointer == scanning_sentinel) { + scanner[0] = scanner[1]; + scanner[1] = scanner[2]; + scanner[2] = value; + + if( + scanner[0] == header_prologue[0] && + scanner[1] == header_prologue[1] && + ( + scanner[2] == header_prologue[2] || + scanner[2] == data_prologue[2] + )) { + pointer = 0; + + // 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]) { + new_sector.reset(new Sector); + new_sector->data.reserve(412); + } + } + } else { + if(new_sector) { + // If this is an epilogue, make sense of this whole sector; + // otherwise just keep the byte for later. + if(value == epilogue[1]) { + std::unique_ptr sector = std::move(new_sector); + new_sector.reset(); + pointer = scanning_sentinel; + + // Check for an expected size; the first byte of the epilogue has been stored, so + // these numbers are one bigger than usual. + if(sector->data.size() != 412 && sector->data.size() != 344) continue; + sector->data.resize(sector->data.size() - 1); + + // 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. + uint_fast8_t checksum = ((header[6] << 1) | 1) & header[7]; + if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) continue; + + if(sector->data.size() == 343) { + // Unmap the sector contents as 6 and 2 data. + bool out_of_bounds = false; + for(auto &c : sector->data) { + if(c < 0x96 || six_and_two_unmapping[c - 0x96] == 0xff) { + out_of_bounds = true; + break; + } + c = six_and_two_unmapping[c - 0x96]; + } + 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); + + // Undo the 6 and 2 mapping. + const uint8_t bit_reverse[] = {0, 2, 1, 3}; + #define unmap(byte, nibble, shift) \ + sector->data[86 + byte] = static_cast(\ + (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. + sector->data.erase(sector->data.begin(), sector->data.end() - 256); + } else { + new_sector->data.push_back(value); + } + } else { + // Just capture the header in place; it'll be decoded + // once a whole sector has been read. + header[pointer] = value; + ++pointer; + if(pointer == 8) { + pointer = scanning_sentinel; + } + } + } + } + + return result; +} diff --git a/Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp b/Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp new file mode 100644 index 000000000..ba9464d39 --- /dev/null +++ b/Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp @@ -0,0 +1,30 @@ +// +// SegmentParser.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/05/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef TrackParser_hpp +#define TrackParser_hpp + +#include "Sector.hpp" +#include "../../Track/PCMSegment.hpp" +#include + +namespace Storage { +namespace Encodings { +namespace AppleGCR { + +/*! + Scans @c segment for all included sectors, returning a set that maps from location within + the segment (counted in bits from the beginning) to sector. +*/ +std::map sectors_from_segment(const Disk::PCMSegment &&segment); + +} +} +} + +#endif /* TrackParser_hpp */ diff --git a/Storage/Disk/Encodings/AppleGCR/TrackParser.cpp b/Storage/Disk/Encodings/AppleGCR/TrackParser.cpp deleted file mode 100644 index 89e7a3a81..000000000 --- a/Storage/Disk/Encodings/AppleGCR/TrackParser.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// -// TrackParser.cpp -// Clock Signal -// -// Created by Thomas Harte on 04/05/2018. -// Copyright © 2018 Thomas Harte. All rights reserved. -// - -#include "TrackParser.hpp" diff --git a/Storage/Disk/Encodings/AppleGCR/TrackParser.hpp b/Storage/Disk/Encodings/AppleGCR/TrackParser.hpp deleted file mode 100644 index 1e869495f..000000000 --- a/Storage/Disk/Encodings/AppleGCR/TrackParser.hpp +++ /dev/null @@ -1,14 +0,0 @@ -// -// TrackParser.hpp -// Clock Signal -// -// Created by Thomas Harte on 04/05/2018. -// Copyright © 2018 Thomas Harte. All rights reserved. -// - -#ifndef TrackParser_hpp -#define TrackParser_hpp - -#include - -#endif /* TrackParser_hpp */ diff --git a/Storage/Disk/Encodings/MFM/SegmentParser.hpp b/Storage/Disk/Encodings/MFM/SegmentParser.hpp index a078f8bdb..c5047fcf4 100644 --- a/Storage/Disk/Encodings/MFM/SegmentParser.hpp +++ b/Storage/Disk/Encodings/MFM/SegmentParser.hpp @@ -19,7 +19,8 @@ namespace MFM { /*! Scans @c segment for all included sectors, returning a set that maps from location within - the segment (counted in bits from the beginning) to sector. + the segment (counted in bits from the beginning and pointing to the location the disk + had reached upon detection of the ID mark) to sector. */ std::map sectors_from_segment(const Disk::PCMSegment &&segment, bool is_double_density);