Compare commits

...

2 Commits

Author SHA1 Message Date
David Given
98a28225d6 Add support for the 83kB 5.25" Lanier format. 2025-04-23 23:25:09 +02:00
David Given
d041b538bb Add boilerplate for the aeslanier5 format. 2025-04-23 21:17:10 +02:00
12 changed files with 190 additions and 24 deletions

View File

@@ -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<Decoder> createAesLanierDecoder(
const DecoderProto& config);

View File

@@ -1,4 +1,4 @@
syntax = "proto2";
message AesLanierDecoderProto {}
message AesLanier5DecoderProto {}

View File

@@ -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<Decoder> createAesLanier5Decoder(
const DecoderProto& config);
#endif

View File

@@ -0,0 +1,4 @@
syntax = "proto2";
message AesLanierDecoderProto {}

View File

@@ -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 <string.h>
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<Decoder> createAesLanier5Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new AesLanier5Decoder(config));
}

View File

@@ -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<Decoder> Arch::createDecoder(const DecoderProto& config)
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAeslanier5, createAesLanier5Decoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },

View File

@@ -5,6 +5,7 @@ proto(
name="proto",
srcs=[
"./aeslanier/aeslanier.proto",
"./aeslanier5/aeslanier5.proto",
"./agat/agat.proto",
"./amiga/amiga.proto",
"./apple2/apple2.proto",
@@ -36,6 +37,7 @@ cxxlibrary(
srcs=[
"./arch.cc",
"./aeslanier/decoder.cc",
"./aeslanier5/decoder.cc",
"./agat/agat.cc",
"./agat/decoder.cc",
"./agat/encoder.cc",
@@ -83,6 +85,7 @@ cxxlibrary(
"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",

View File

@@ -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

View File

@@ -33,6 +33,11 @@ public:
bool matches(const unsigned* intervals, FluxMatch& match) const override;
unsigned length() const
{
return _bits;
}
unsigned intervals() const override
{
return _intervals.size();

View File

@@ -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;

View File

@@ -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"
layout {
format_type: FORMATTYPE_80TRACK
tracks: 77
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 32
option {
name: '8'
comment: 'use the format found on 8" disks'
set_by_default: true
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
}
}
}
}
}
}

View File

@@ -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);