From d041b538bb2bc222de147ca9efb4f3121f48ff4c Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 23 Apr 2025 21:17:10 +0200 Subject: [PATCH 1/2] Add boilerplate for the aeslanier5 format. --- arch/aeslanier/aeslanier.proto | 2 +- arch/aeslanier5/aeslanier5.h | 11 ++++++ arch/aeslanier5/aeslanier5.proto | 4 ++ arch/aeslanier5/decoder.cc | 65 ++++++++++++++++++++++++++++++++ arch/arch.cc | 2 + arch/build.py | 3 ++ doc/disk-aeslanier.md | 7 +++- lib/decoders/decoders.proto | 4 +- src/formats/aeslanier.textpb | 61 +++++++++++++++++++++++------- 9 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 arch/aeslanier5/aeslanier5.h create mode 100644 arch/aeslanier5/aeslanier5.proto create mode 100644 arch/aeslanier5/decoder.cc diff --git a/arch/aeslanier/aeslanier.proto b/arch/aeslanier/aeslanier.proto index fe2df6898..cae9d8d21 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 000000000..cf948036e --- /dev/null +++ b/arch/aeslanier5/aeslanier5.h @@ -0,0 +1,11 @@ +#ifndef AESLANIER5_H +#define AESLANIER5_H + +#define AESLANIER5_RECORD_SEPARATOR 0x55555122 +#define AESLANIER5_SECTOR_LENGTH 256 +#define AESLANIER5_RECORD_SIZE (AESLANIER5_SECTOR_LENGTH + 5) + +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 000000000..fe2df6898 --- /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 000000000..43e98e51e --- /dev/null +++ b/arch/aeslanier5/decoder.cc @@ -0,0 +1,65 @@ +#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 M2FM, 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(16); + + const auto& rawbits = readRawBits(AESLANIER5_RECORD_SIZE * 16); + const auto& bytes = + decodeFmMfm(rawbits).slice(0, AESLANIER5_RECORD_SIZE); + const auto& reversed = bytes.reverseBits(); + + _sector->logicalTrack = reversed[1]; + _sector->logicalSide = 0; + _sector->logicalSector = reversed[2]; + + /* Check header 'checksum' (which seems far too simple to mean much). */ + + { + uint8_t wanted = reversed[3]; + uint8_t got = reversed[1] + reversed[2]; + if (wanted != got) + return; + } + + /* Check data checksum, which also includes the header and is + * significantly better. */ + + _sector->data = reversed.slice(1, AESLANIER5_SECTOR_LENGTH); + uint16_t wanted = reversed.reader().seek(0x101).read_le16(); + uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data); + _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 58bc75155..0903424a2 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 d3a53e617..f53e02724 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 73073fc34..1635031a8 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" format`: use the format found on 8" disks + - `5.25" format`: use the format found on 5.25" disks ## Examples To read: - - `fluxengine read aeslanier -s drive:0 -o aeslanier.img` + - `fluxengine read aeslanier --8" format -s drive:0 -o aeslanier.img` + - `fluxengine read aeslanier --5.25" format -s drive:0 -o aeslanier.img` ## References diff --git a/lib/decoders/decoders.proto b/lib/decoders/decoders.proto index df448ec56..8c64919e3 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 70c60e133..4e34c4b9c 100644 --- a/src/formats/aeslanier.textpb +++ b/src/formats/aeslanier.textpb @@ -46,21 +46,56 @@ image_writer { type: IMAGETYPE_IMG } -decoder { - aeslanier {} -} +option_group { + comment: "$formats" + + option { + name: '8" format' + 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: '5.25" format' + comment: 'use the format found on 5.25" disks' + config { + decoder { + aeslanier5 {} + } + + layout { + format_type: FORMATTYPE_40TRACK + tracks: 35 + sides: 1 + layoutdata { + sector_size: 150 + physical { + start_sector: 0 + count: 16 + skew: 3 + } + } + } + } + } +} From 98a28225d641f631e3dc21cf9fb74532107d17c2 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 23 Apr 2025 23:25:09 +0200 Subject: [PATCH 2/2] Add support for the 83kB 5.25" Lanier format. --- arch/aeslanier/aeslanier.h | 12 +++++++++++- arch/aeslanier5/aeslanier5.h | 16 +++++++++++++--- arch/aeslanier5/decoder.cc | 31 ++++++++++++++++++++++--------- doc/disk-aeslanier.md | 8 ++++---- lib/data/fluxpattern.h | 5 +++++ src/formats/aeslanier.textpb | 8 +++++--- src/gui/fluxviewercontrol.cc | 11 ++++++----- 7 files changed, 66 insertions(+), 25 deletions(-) diff --git a/arch/aeslanier/aeslanier.h b/arch/aeslanier/aeslanier.h index 76b7371e6..c524a9493 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/aeslanier5/aeslanier5.h b/arch/aeslanier5/aeslanier5.h index cf948036e..af671054a 100644 --- a/arch/aeslanier5/aeslanier5.h +++ b/arch/aeslanier5/aeslanier5.h @@ -1,9 +1,19 @@ #ifndef AESLANIER5_H #define AESLANIER5_H -#define AESLANIER5_RECORD_SEPARATOR 0x55555122 -#define AESLANIER5_SECTOR_LENGTH 256 -#define AESLANIER5_RECORD_SIZE (AESLANIER5_SECTOR_LENGTH + 5) +/* 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); diff --git a/arch/aeslanier5/decoder.cc b/arch/aeslanier5/decoder.cc index 43e98e51e..a631b35d6 100644 --- a/arch/aeslanier5/decoder.cc +++ b/arch/aeslanier5/decoder.cc @@ -12,7 +12,7 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER5_RECORD_SEPARATOR); -/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine +/* This is actually FM, rather than MFM, but it our MFM/FM decoder copes fine * with it. */ class AesLanier5Decoder : public Decoder @@ -29,22 +29,25 @@ class AesLanier5Decoder : public Decoder { /* Skip ID mark (we know it's a AESLANIER5_RECORD_SEPARATOR). */ - readRawBits(16); + 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(); - _sector->logicalTrack = reversed[1]; + uint8_t encodedTrack = reversed[0]; + uint8_t encodedSector = reversed[1]; + + _sector->logicalTrack = encodedTrack >> 1; _sector->logicalSide = 0; - _sector->logicalSector = reversed[2]; + _sector->logicalSector = encodedSector; /* Check header 'checksum' (which seems far too simple to mean much). */ { - uint8_t wanted = reversed[3]; - uint8_t got = reversed[1] + reversed[2]; + uint8_t wanted = reversed[2]; + uint8_t got = reversed[0] + reversed[1]; if (wanted != got) return; } @@ -52,9 +55,19 @@ class AesLanier5Decoder : public Decoder /* Check data checksum, which also includes the header and is * significantly better. */ - _sector->data = reversed.slice(1, AESLANIER5_SECTOR_LENGTH); - uint16_t wanted = reversed.reader().seek(0x101).read_le16(); - uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data); + _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; } }; diff --git a/doc/disk-aeslanier.md b/doc/disk-aeslanier.md index 1635031a8..ecad2a119 100644 --- a/doc/disk-aeslanier.md +++ b/doc/disk-aeslanier.md @@ -32,15 +32,15 @@ based on what looks right. If anyone knows _anything_ about these disks, ## Options - Format variants: - - `8" format`: use the format found on 8" disks - - `5.25" format`: use the format found on 5.25" disks + - `8`: use the format found on 8" disks + - `5`: use the format found on 5.25" disks ## Examples To read: - - `fluxengine read aeslanier --8" format -s drive:0 -o aeslanier.img` - - `fluxengine read aeslanier --5.25" format -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 3ca2a9927..8c52d1be1 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/src/formats/aeslanier.textpb b/src/formats/aeslanier.textpb index 4e34c4b9c..5a5686e41 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 @@ -50,7 +52,7 @@ option_group { comment: "$formats" option { - name: '8" format' + name: '8' comment: 'use the format found on 8" disks' set_by_default: true @@ -75,8 +77,8 @@ option_group { } option { - name: '5.25" format' - comment: 'use the format found on 5.25" disks' + name: '83' + comment: '83kB 5.25" 35-track 16-sector SSSD' config { decoder { diff --git a/src/gui/fluxviewercontrol.cc b/src/gui/fluxviewercontrol.cc index 12546a6d9..d9c1f8203 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);