diff --git a/README.md b/README.md
index b01c40e1..bfb4d3f5 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ physical disks with these formats and so know they work.
| [Commodore 64 1541](doc/disk-c64.md) | 🦖 | | and probably the other GCR formats |
| [Brother 120kB](doc/disk-brother.md) | 🦄 | | |
| [Brother 240kB](doc/disk-brother.md) | 🦄 | 🦄 | |
+| [Brother FB-100](doc/disk-fb100.md) | 🦖 | | Tandy Model 100, Husky Hunter, knitting machines |
| [Macintosh 800kB](doc/disk-macintosh.md) | 🦖 | | and probably the 400kB too |
| [TRS-80](doc/disk-trs80.md) | 🦖 | | a minor variation of the IBM scheme |
{: .datatable }
@@ -100,6 +101,7 @@ at least, check the CRC so what data's there is probably good.
| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 8-inch |
| [Zilog MCZ](doc/disk-zilogmcz.md) | 🦖 | | 8-inch _and_ hard sectors |
{: .datatable }
+
### Notes
- IBM PC disks are the lowest-common-denominator standard. A number of other
diff --git a/doc/disk-fb100.md b/doc/disk-fb100.md
new file mode 100644
index 00000000..4de5bd3a
--- /dev/null
+++ b/doc/disk-fb100.md
@@ -0,0 +1,32 @@
+Disk: Brother FB-100
+====================
+
+The Brother FB-100 is a serial-attached smart floppy drive used by a several
+different machines for mass storage, including the Tandy Model 100 and
+clones, the Husky Hunter 2, and (bizarrely) several knitting machines. It was
+usually rebadged, sometimes with a cheap paper label stuck over the Brother
+logo: the most common variant appears to be the Tandy Portable Disk Drive or
+TPDD:
+
+
+
+It's a bit of an oddball: the disk encoding is FM with a very custom record
+scheme: 40-track single-sided 3.5" disks storing 100kB or so each. Each track
+had only _two_ sectors, each 1280 bytes, but with an additional 17 bytes of
+ID data used for filesystem management.
+
+There was also apparently a TPDD-2 which could store twice as much data, but
+I don't have access to one of those disks.
+
+Reading discs
+-------------
+
+Just do:
+
+```
+.obj/fe-readfb100
+```
+
+You should end up with an `fb11.img` of the appropriate size. It's a simple
+array of 80 1297-byte sectors (17 bytes for the ID record plus 1280 bytes for
+the data).
\ No newline at end of file
diff --git a/lib/fb100/decoder.cc b/lib/fb100/decoder.cc
index d3dada56..d20fe9f7 100644
--- a/lib/fb100/decoder.cc
+++ b/lib/fb100/decoder.cc
@@ -30,6 +30,89 @@ static bool search(const RawBits& rawbits, size_t& cursor)
return false;
}
+/*
+ * Reverse engineered from a dump of the floppy drive's ROM. I have no idea how
+ * it works.
+ *
+ * LF8BA:
+ * clra
+ * staa X00B0
+ * staa X00B1
+ * ldx #$8000
+ * LF8C2: ldaa $00,x
+ * inx
+ * bsr LF8CF
+ * cpx #$8011
+ * bne LF8C2
+ * ldd X00B0
+ * rts
+ * LF8CF:
+ * eora X00B0
+ * staa X00CF
+ * asla
+ * asla
+ * asla
+ * asla
+ * eora X00CF
+ * staa X00CF
+ * rola
+ * rola
+ * rola
+ * tab
+ * anda #$F8
+ * eora X00B1
+ * staa X00B0
+ * rolb
+ * rolb
+ * andb #$0F
+ * eorb X00B0
+ * stab X00B0
+ * rolb
+ * eorb X00CF
+ * stab X00B1
+ * rts
+ */
+
+static void rol(uint8_t& b, bool& c)
+{
+ bool newc = b & 0x80;
+ b <<= 1;
+ b |= c;
+ c = newc;
+}
+
+static uint16_t checksum(const Bytes& bytes)
+{
+ uint8_t crclo = 0;
+ uint8_t crchi = 0;
+ for (uint8_t a : bytes)
+ {
+ a ^= crchi;
+ uint8_t t1 = a;
+ a <<= 4;
+ bool c = a & 0x10;
+ a ^= t1;
+ t1 = a;
+ rol(a, c);
+ rol(a, c);
+ rol(a, c);
+ uint8_t b = a;
+ a &= 0xf8;
+ a ^= crclo;
+ crchi = a;
+ rol(b, c);
+ rol(b, c);
+ b &= 0x0f;
+ b ^= crchi;
+ crchi = b;
+ rol(b, c);
+ b ^= t1;
+ crclo = b;
+ }
+
+ return (crchi << 8) | crclo;
+}
+
void Fb100Decoder::decodeToSectors(const RawBits& rawbits, unsigned,
RawRecordVector& rawrecords, SectorVector& sectors)
{
@@ -58,14 +141,19 @@ void Fb100Decoder::decodeToSectors(const RawBits& rawbits, unsigned,
br.seek(1);
const Bytes id = br.read(FB100_ID_SIZE);
uint16_t wantIdCrc = br.read_be16();
+ uint16_t gotIdCrc = checksum(id);
const Bytes payload = br.read(FB100_PAYLOAD_SIZE);
uint16_t wantPayloadCrc = br.read_be16();
+ uint16_t gotPayloadCrc = checksum(payload);
+
+ if (wantIdCrc != gotIdCrc)
+ continue;
uint8_t abssector = id[2];
uint8_t track = abssector >> 1;
uint8_t sectorid = abssector & 1;
- int status = Sector::BAD_CHECKSUM;
+ int status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
auto sector = std::unique_ptr(
new Sector(status, track, 0, sectorid, payload));
sectors.push_back(std::move(sector));