diff --git a/arch/aeslanier/aeslanier.h b/arch/aeslanier/aeslanier.h index 76b7371e..c524a949 100644 --- a/arch/aeslanier/aeslanier.h +++ b/arch/aeslanier/aeslanier.h @@ -1,9 +1,19 @@ #ifndef AESLANIER_H #define AESLANIER_H +/* MFM: + * + * Raw bits: + * 5 5 5 5 5 1 2 2 + * 0101 0101 0101 0101 0101 0001 0010 0010 + * 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 + * 0 0 0 5 + * Decoded bits. + */ + #define AESLANIER_RECORD_SEPARATOR 0x55555122 #define AESLANIER_SECTOR_LENGTH 256 -#define AESLANIER_RECORD_SIZE (AESLANIER_SECTOR_LENGTH + 5) +#define AESLANIER_RECORD_SIZE (AESLANIER_SECTOR_LENGTH + 4) extern std::unique_ptr createAesLanierDecoder( const DecoderProto& config); diff --git a/arch/aeslanier/aeslanier.proto b/arch/aeslanier/aeslanier.proto index fe2df689..cae9d8d2 100644 --- a/arch/aeslanier/aeslanier.proto +++ b/arch/aeslanier/aeslanier.proto @@ -1,4 +1,4 @@ syntax = "proto2"; -message AesLanierDecoderProto {} +message AesLanier5DecoderProto {} diff --git a/arch/aeslanier5/aeslanier5.h b/arch/aeslanier5/aeslanier5.h new file mode 100644 index 00000000..af671054 --- /dev/null +++ b/arch/aeslanier5/aeslanier5.h @@ -0,0 +1,21 @@ +#ifndef AESLANIER5_H +#define AESLANIER5_H + +/* Format is FM: + * + * a a a a f b e f + * 1010 1010 1010 1010 1111 1011 1110 1111 + * 0 0 0 0 0 0 0 0 1 1 0 1 1 0 1 1 + * 0 0 d b + * + * However, note that this pattern is _not_ reversed... + */ + +#define AESLANIER5_RECORD_SEPARATOR 0xaaaaaaaaaaaafbefLL +#define AESLANIER5_SECTOR_LENGTH 151 +#define AESLANIER5_RECORD_SIZE (AESLANIER5_SECTOR_LENGTH + 3) + +extern std::unique_ptr createAesLanier5Decoder( + const DecoderProto& config); + +#endif diff --git a/arch/aeslanier5/aeslanier5.proto b/arch/aeslanier5/aeslanier5.proto new file mode 100644 index 00000000..fe2df689 --- /dev/null +++ b/arch/aeslanier5/aeslanier5.proto @@ -0,0 +1,4 @@ +syntax = "proto2"; + +message AesLanierDecoderProto {} + diff --git a/arch/aeslanier5/decoder.cc b/arch/aeslanier5/decoder.cc new file mode 100644 index 00000000..a631b35d --- /dev/null +++ b/arch/aeslanier5/decoder.cc @@ -0,0 +1,78 @@ +#include "lib/core/globals.h" +#include "lib/decoders/decoders.h" +#include "aeslanier5.h" +#include "lib/core/crc.h" +#include "lib/data/fluxmap.h" +#include "lib/data/fluxmapreader.h" +#include "lib/data/fluxpattern.h" +#include "lib/data/sector.h" +#include "lib/core/bytes.h" +#include "fmt/format.h" +#include + +static const FluxPattern SECTOR_PATTERN(32, AESLANIER5_RECORD_SEPARATOR); + +/* This is actually FM, rather than MFM, but it our MFM/FM decoder copes fine + * with it. */ + +class AesLanier5Decoder : public Decoder +{ +public: + AesLanier5Decoder(const DecoderProto& config): Decoder(config) {} + + nanoseconds_t advanceToNextRecord() override + { + return seekToPattern(SECTOR_PATTERN); + } + + void decodeSectorRecord() override + { + /* Skip ID mark (we know it's a AESLANIER5_RECORD_SEPARATOR). */ + + readRawBits(SECTOR_PATTERN.length()); + + const auto& rawbits = readRawBits(AESLANIER5_RECORD_SIZE * 16); + const auto& bytes = + decodeFmMfm(rawbits).slice(0, AESLANIER5_RECORD_SIZE); + const auto& reversed = bytes.reverseBits(); + + uint8_t encodedTrack = reversed[0]; + uint8_t encodedSector = reversed[1]; + + _sector->logicalTrack = encodedTrack >> 1; + _sector->logicalSide = 0; + _sector->logicalSector = encodedSector; + + /* Check header 'checksum' (which seems far too simple to mean much). */ + + { + uint8_t wanted = reversed[2]; + uint8_t got = reversed[0] + reversed[1]; + if (wanted != got) + return; + } + + /* Check data checksum, which also includes the header and is + * significantly better. */ + + _sector->data = reversed.slice(3, AESLANIER5_SECTOR_LENGTH); + uint8_t wanted, got; + ByteReader br(_sector->data); + if ((encodedSector == 0) || (encodedSector == 8)) + { + wanted = br.seek(17).read_8() + br.seek(150).read_8(); + got = sumBytes(_sector->data.slice(0, 17)) + sumBytes(_sector->data.slice(18, 132)); + } + else + { + wanted = br.seek(150).read_8(); + got = sumBytes(_sector->data.slice(0, AESLANIER5_SECTOR_LENGTH-1)); + } + _sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM; + } +}; + +std::unique_ptr createAesLanier5Decoder(const DecoderProto& config) +{ + return std::unique_ptr(new AesLanier5Decoder(config)); +} diff --git a/arch/arch.cc b/arch/arch.cc index 58bc7515..0903424a 100644 --- a/arch/arch.cc +++ b/arch/arch.cc @@ -4,6 +4,7 @@ #include "lib/config/config.h" #include "arch/agat/agat.h" #include "arch/aeslanier/aeslanier.h" +#include "arch/aeslanier5/aeslanier5.h" #include "arch/amiga/amiga.h" #include "arch/apple2/apple2.h" #include "arch/brother/brother.h" @@ -70,6 +71,7 @@ std::unique_ptr Arch::createDecoder(const DecoderProto& config) decoders = { {DecoderProto::kAgat, createAgatDecoder }, {DecoderProto::kAeslanier, createAesLanierDecoder }, + {DecoderProto::kAeslanier5, createAesLanier5Decoder }, {DecoderProto::kAmiga, createAmigaDecoder }, {DecoderProto::kApple2, createApple2Decoder }, {DecoderProto::kBrother, createBrotherDecoder }, diff --git a/arch/build.py b/arch/build.py index d3a53e61..f53e0272 100644 --- a/arch/build.py +++ b/arch/build.py @@ -5,6 +5,7 @@ name="proto", srcs=[ "./aeslanier/aeslanier.proto", + "./aeslanier5/aeslanier5.proto", "./agat/agat.proto", "./amiga/amiga.proto", "./apple2/apple2.proto", @@ -36,6 +37,7 @@ srcs=[ "./arch.cc", "./aeslanier/decoder.cc", + "./aeslanier5/decoder.cc", "./agat/agat.cc", "./agat/decoder.cc", "./agat/encoder.cc", @@ -83,6 +85,7 @@ "arch/f85/f85.h": "./f85/f85.h", "arch/mx/mx.h": "./mx/mx.h", "arch/aeslanier/aeslanier.h": "./aeslanier/aeslanier.h", + "arch/aeslanier5/aeslanier5.h": "./aeslanier5/aeslanier5.h", "arch/northstar/northstar.h": "./northstar/northstar.h", "arch/brother/data_gcr.h": "./brother/data_gcr.h", "arch/brother/brother.h": "./brother/brother.h", diff --git a/doc/disk-aeslanier.md b/doc/disk-aeslanier.md index 73073fc3..ecad2a11 100644 --- a/doc/disk-aeslanier.md +++ b/doc/disk-aeslanier.md @@ -31,13 +31,16 @@ based on what looks right. If anyone knows _anything_ about these disks, ## Options -(no options) + - Format variants: + - `8`: use the format found on 8" disks + - `5`: use the format found on 5.25" disks ## Examples To read: - - `fluxengine read aeslanier -s drive:0 -o aeslanier.img` + - `fluxengine read aeslanier --8 -s drive:0 -o aeslanier.img` + - `fluxengine read aeslanier --5 -s drive:0 -o aeslanier.img` ## References diff --git a/lib/data/fluxpattern.h b/lib/data/fluxpattern.h index 3ca2a992..8c52d1be 100644 --- a/lib/data/fluxpattern.h +++ b/lib/data/fluxpattern.h @@ -33,6 +33,11 @@ class FluxPattern : public FluxMatcher bool matches(const unsigned* intervals, FluxMatch& match) const override; + unsigned length() const + { + return _bits; + } + unsigned intervals() const override { return _intervals.size(); diff --git a/lib/decoders/decoders.proto b/lib/decoders/decoders.proto index df448ec5..8c64919e 100644 --- a/lib/decoders/decoders.proto +++ b/lib/decoders/decoders.proto @@ -2,6 +2,7 @@ syntax = "proto2"; import "arch/agat/agat.proto"; import "arch/aeslanier/aeslanier.proto"; +import "arch/aeslanier5/aeslanier5.proto"; import "arch/amiga/amiga.proto"; import "arch/apple2/apple2.proto"; import "arch/brother/brother.proto"; @@ -22,7 +23,7 @@ import "arch/zilogmcz/zilogmcz.proto"; import "lib/fluxsink/fluxsink.proto"; import "lib/config/common.proto"; -//NEXT: 33 +//NEXT: 34 message DecoderProto { optional double pulse_debounce_threshold = 1 [default = 0.30, (help) = "ignore pulses with intervals shorter than this, in fractions of a clock"]; @@ -37,6 +38,7 @@ message DecoderProto { oneof format { AesLanierDecoderProto aeslanier = 7; + AesLanier5DecoderProto aeslanier5 = 33; AgatDecoderProto agat = 28; AmigaDecoderProto amiga = 8; Apple2DecoderProto apple2 = 13; diff --git a/src/formats/aeslanier.textpb b/src/formats/aeslanier.textpb index 70c60e13..5a5686e4 100644 --- a/src/formats/aeslanier.textpb +++ b/src/formats/aeslanier.textpb @@ -14,6 +14,8 @@ of nearly £50,000 in 2018!). processing software off twin 5.25" drive units, but apparently other software was available. +**Note:** the following is wrong and needs to be updated. + The disk format is exceptionally weird. They used 77 track, 32 sector, single-sided _hard_ sectored disks, where there were multiple index holes, indicating to the hardware where the sectors start. The encoding scheme @@ -46,21 +48,56 @@ image_writer { type: IMAGETYPE_IMG } -decoder { - aeslanier {} -} +option_group { + comment: "$formats" + + option { + name: '8' + comment: 'use the format found on 8" disks' + set_by_default: true -layout { - format_type: FORMATTYPE_80TRACK - tracks: 77 - sides: 1 - layoutdata { - sector_size: 256 - physical { - start_sector: 0 - count: 32 + config { + decoder { + aeslanier {} + } + + layout { + format_type: FORMATTYPE_80TRACK + tracks: 77 + sides: 1 + layoutdata { + sector_size: 256 + physical { + start_sector: 0 + count: 32 + } + } + } } } -} + option { + name: '83' + comment: '83kB 5.25" 35-track 16-sector SSSD' + config { + decoder { + aeslanier5 {} + } + + layout { + format_type: FORMATTYPE_40TRACK + tracks: 35 + sides: 1 + layoutdata { + sector_size: 150 + physical { + start_sector: 0 + count: 16 + skew: 3 + } + } + } + } + } +} diff --git a/src/gui/fluxviewercontrol.cc b/src/gui/fluxviewercontrol.cc index 12546a6d..d9c1f820 100644 --- a/src/gui/fluxviewercontrol.cc +++ b/src/gui/fluxviewercontrol.cc @@ -246,12 +246,13 @@ void FluxViewerControl::OnPaint(wxPaintEvent&) dc.SetTextForeground(*wxBLACK); for (auto& sector : trackdata->sectors) { - nanoseconds_t sr = sector->dataEndTime; - if (!sr) - sr = sector->headerEndTime; + nanoseconds_t tl = sector->headerStartTime; + nanoseconds_t tr = sector->dataEndTime; + if (!tl && !sector->headerEndTime) + tl = sector->dataStartTime; - int sp = sector->headerStartTime / _nanosecondsPerPixel; - int sw = (sr - sector->headerStartTime) / _nanosecondsPerPixel; + int sp = tl / _nanosecondsPerPixel; + int sw = (tr - tl) / _nanosecondsPerPixel; wxRect rect = {x + sp, t1y - ch2, sw, ch}; bool hovered = rect.Contains(_mouseX, _mouseY);