You can now decode Brother disks to images. No CRC checking, though.

This commit is contained in:
David Given
2018-10-21 19:10:33 +02:00
parent 3d8f06ea89
commit d7b2505720
9 changed files with 353 additions and 11 deletions

View File

@@ -0,0 +1,235 @@
#include "globals.h"
#include "sql.h"
#include "fluxmap.h"
#include "decoders.h"
#include <ctype.h>
static std::vector<uint8_t> outputbuffer;
/*
* Brother disks have this very very non-IBM system where sector header records
* and data records use two different kinds of GCR: sector headers are 8-in-16
* (but the encodable values range from 0 to 77ish only) and data headers are
* 5-in-8. In addition, there's a non-encoded 10-bit ID word at the beginning
* of each record, as well as a string of 53 1s introducing them. That does at
* least make them easy to find.
*
* Disk formats vary from machine to machine, but mine uses 78 tracks. Track 0
* is erased but not formatted. Track alignment is extremely dubious and
* Brother track 0 shows up on my machine at track 2.
*/
static int decode_data_gcr(uint8_t gcr)
{
switch (gcr)
{
case 0x55: return 0; // 00000
case 0x57: return 1; // 00001
case 0x5b: return 2; // 00010
case 0x5d: return 3; // 00011
case 0x5f: return 4; // 00100
case 0x6b: return 5; // 00101
case 0x6d: return 6; // 00110
case 0x6f: return 7; // 00111
case 0x75: return 8; // 01000
case 0x77: return 9; // 01001
case 0x7b: return 10; // 01010
case 0x7d: return 11; // 01011
case 0x7f: return 12; // 01100
case 0xab: return 13; // 01101
case 0xad: return 14; // 01110
case 0xaf: return 15; // 01111
case 0xb5: return 16; // 10000
case 0xb7: return 17; // 10001
case 0xbb: return 18; // 10010
case 0xbd: return 19; // 10011
case 0xbf: return 20; // 10100
case 0xd5: return 21; // 10101
case 0xd7: return 22; // 10110
case 0xdb: return 23; // 10111
case 0xdd: return 24; // 11000
case 0xdf: return 25; // 11001
case 0xeb: return 26; // 11010
case 0xed: return 27; // 11011
case 0xef: return 28; // 11100
case 0xf5: return 29; // 11101
case 0xf7: return 30; // 11110
case 0xfb: return 31; // 11111
}
return -1;
};
static int decode_sector_gcr(uint16_t word)
{
switch (word)
{
case 0xDFB5: return 0;
case 0x5B6F: return 1;
case 0x7DF7: return 2;
case 0xBFD5: return 3;
case 0xF57F: return 4;
case 0x6D5D: return 5;
case 0xAFEB: return 6;
case 0xDDB7: return 7;
case 0x5775: return 8;
case 0x7BFB: return 9;
case 0xBDD7: return 10;
case 0xEFAB: return 11;
case 0x6B5F: return 12;
case 0xADED: return 13;
case 0xDBBB: return 14;
case 0x5577: return 15;
case 0x77DB: return 16;
case 0xBBAD: return 17;
case 0xED6B: return 18;
case 0x5FEF: return 19;
case 0xABBD: return 20;
case 0xD77B: return 21;
case 0xFB57: return 22;
case 0x75DD: return 23;
case 0xB7AF: return 24;
case 0xEB6D: return 25;
case 0x5DF5: return 26;
case 0x7FBF: return 27;
case 0xD57D: return 28;
case 0xF75B: return 29;
case 0x6FDF: return 30;
case 0xB5B5: return 31;
case 0xDF6F: return 32;
case 0x5BF7: return 33;
case 0x7DD5: return 34;
case 0xBF7F: return 35;
case 0xF55D: return 36;
case 0x6DEB: return 37;
case 0xAFB7: return 38;
case 0xDD75: return 39;
case 0x57FB: return 40;
case 0x7BD7: return 41;
case 0xBDAB: return 42;
case 0xEF5F: return 43;
case 0x6BED: return 44;
case 0xADBB: return 45;
case 0xDB77: return 46;
case 0xBB55: return 47;
case 0xEDDB: return 48;
case 0x5FAD: return 49;
case 0xAB6B: return 50;
case 0xD7EF: return 51;
case 0xFBBD: return 52;
case 0x757B: return 53;
case 0xB757: return 54;
case 0xEBDD: return 55;
case 0x5DAF: return 56;
case 0x7F6D: return 57;
case 0xD5F5: return 58;
case 0xF7BF: return 59;
case 0x6F7D: return 60;
case 0xB55B: return 61;
case 0xDFDF: return 62;
case 0x5BB5: return 63;
case 0x7D6F: return 64;
case 0xBFF7: return 65;
case 0xF5D5: return 66;
case 0x6D7F: return 67;
case 0xAF5D: return 68;
case 0xDDEB: return 69;
case 0x57B7: return 70;
case 0x7B75: return 71;
case 0xBDFB: return 72;
case 0xEFD7: return 73;
case 0x6BAB: return 74;
case 0xAD5F: return 75;
case 0xDBED: return 76;
case 0x55BB: return 77;
}
return -1;
};
std::vector<std::vector<uint8_t>> decodeBitsToRecordsBrother(const std::vector<bool>& bits)
{
std::vector<std::vector<uint8_t>> records;
enum
{
SEEKING,
READINGSECTOR,
READINGDATA
};
size_t cursor = 0;
uint32_t inputfifo = 0;
int inputcount = 0;
uint8_t outputfifo = 0;
int outputcount = 0;
int state = SEEKING;
while (cursor < bits.size())
{
bool bit = bits[cursor++];
inputfifo = (inputfifo << 1) | bit;
inputcount++;
if (inputfifo == BROTHER_SECTOR_RECORD)
{
if (state != SEEKING)
records.push_back(outputbuffer);
outputbuffer.resize(1);
outputbuffer[0] = BROTHER_SECTOR_RECORD & 0xff;
state = READINGSECTOR;
inputcount = 0;
}
else if (inputfifo == BROTHER_DATA_RECORD)
{
if (state != SEEKING)
records.push_back(outputbuffer);
outputbuffer.resize(1);
outputbuffer[0] = BROTHER_DATA_RECORD & 0xff;
state = READINGDATA;
outputcount = 0;
inputcount = 0;
}
else
{
switch (state)
{
case READINGSECTOR:
{
if (inputcount != 16)
break;
int data = decode_sector_gcr(inputfifo & 0xffff);
inputcount = 0;
outputbuffer.push_back(data);
break;
}
case READINGDATA:
{
if (inputcount != 8)
break;
int data = decode_data_gcr(inputfifo & 0xff);
inputcount = 0;
for (int i=0; i<5; i++)
{
outputfifo = (outputfifo << 1) | !!(data & 0x10);
data <<= 1;
outputcount++;
if (outputcount == 8)
{
outputbuffer.push_back(outputfifo);
outputcount = 0;
}
}
break;
}
}
}
}
if (state != SEEKING)
records.push_back(outputbuffer);
return records;
}

View File

@@ -0,0 +1,48 @@
#include "globals.h"
#include "decoders.h"
#include "image.h"
#include <string.h>
std::vector<std::unique_ptr<Sector>> parseRecordsToSectorsBrother(const std::vector<std::vector<uint8_t>>& records)
{
int nextTrack = 0;
int nextSector = 0;
bool hasHeader = false;
std::vector<std::unique_ptr<Sector>> sectors;
for (auto& record : records)
{
switch (record[0])
{
case BROTHER_SECTOR_RECORD & 0xff:
if (record.size() < 3)
goto garbage;
nextTrack = record[1];
nextSector = record[2];
hasHeader = true;
/* TODO: check CRC! */
break;
case BROTHER_DATA_RECORD & 0xff:
{
if (record.size() < (BROTHER_DATA_RECORD_PAYLOAD+1))
goto garbage;
/* TODO: check CRC! */
std::vector<uint8_t> sectordata(BROTHER_DATA_RECORD_PAYLOAD);
memcpy(&sectordata[0], &record[1], BROTHER_DATA_RECORD_PAYLOAD);
auto sector = std::unique_ptr<Sector>(new Sector(nextTrack, 0, nextSector, sectordata));
sectors.push_back(std::move(sector));
hasHeader = false;
break;
}
default:
garbage:
break;
}
}
return sectors;
}

View File

@@ -3,12 +3,12 @@
/* IBM format (i.e. ordinary PC floppies). */
#define IBM_IAM 0xFC /* start-of-track record */
#define IBM_IAM 0xFC /* start-of-track record */
#define IBM_IAM_LEN 4
#define IBM_IDAM 0xFE /* sector header */
#define IBM_IDAM 0xFE /* sector header */
#define IBM_IDAM_LEN 10
#define IBM_DAM1 0xF8 /* sector data (type 1) */
#define IBM_DAM2 0xFB /* sector data (type 2) */
#define IBM_DAM1 0xF8 /* sector data (type 1) */
#define IBM_DAM2 0xFB /* sector data (type 2) */
#define IBM_DAM_LEN 6 /* plus user data */
/* Length of a DAM record is determined by the previous sector header. */
@@ -23,13 +23,21 @@ struct IbmIdam
uint16_t crcBE;
};
/* Brother word processor format (or at least, one of them) */
#define BROTHER_SECTOR_RECORD 0xFFFFFD57
#define BROTHER_DATA_RECORD 0xFFFFFDDB
#define BROTHER_DATA_RECORD_PAYLOAD 256
class Sector;
class Fluxmap;
extern std::vector<bool> decodeFluxmapToBits(const Fluxmap& fluxmap, nanoseconds_t clock_period);
extern std::vector<std::vector<uint8_t>> decodeBitsToRecordsMfm(const std::vector<bool>& bitmap);
extern std::vector<std::vector<uint8_t>> decodeBitsToRecordsBrother(const std::vector<bool>& bitmap);
extern std::vector<std::unique_ptr<Sector>> decodeIbmRecordsToSectors(const std::vector<std::vector<uint8_t>>& records);
extern std::vector<std::unique_ptr<Sector>> parseRecordsToSectorsIbm(const std::vector<std::vector<uint8_t>>& records);
extern std::vector<std::unique_ptr<Sector>> parseRecordsToSectorsBrother(const std::vector<std::vector<uint8_t>>& records);
#endif

View File

@@ -5,7 +5,7 @@
static_assert(std::is_trivially_copyable<IbmIdam>::value);
std::vector<std::unique_ptr<Sector>> decodeIbmRecordsToSectors(const std::vector<std::vector<uint8_t>>& records)
std::vector<std::unique_ptr<Sector>> parseRecordsToSectorsIbm(const std::vector<std::vector<uint8_t>>& records)
{
bool idamValid = false;
IbmIdam idam;

View File

@@ -25,8 +25,9 @@ void writeSectorsToFile(const std::vector<std::unique_ptr<Sector>>& sectors, con
size_t trackSize = sideSize * sideCount;
std::cout << fmt::format("{} tracks, {} sides, {} sectors, {} bytes per sector, {} kB total",
trackCount, sideCount, sectorCount, sectorSize,
trackCount * sideCount * sectorCount * sectorSize / 1024);
trackCount, sideCount, sectorCount, sectorSize,
trackCount * sideCount * sectorCount * sectorSize / 1024)
<< std::endl;
std::ofstream outputFile(filename, std::ios::out | std::ios::binary);
if (!outputFile.is_open())

View File

@@ -51,8 +51,10 @@ readerlib = shared_library('readerlib', ['lib/reader.cc'], include_directori
decoderlib = shared_library('decoderlib',
[
'lib/decoders/decoders.cc',
'lib/decoders/mfm.cc',
'lib/decoders/ibm.cc'
'lib/decoders/mfmdecoder.cc',
'lib/decoders/ibmparser.cc',
'lib/decoders/brotherdecoder.cc',
'lib/decoders/brotherparser.cc',
],
include_directories: [feinc],
link_with: [felib]
@@ -63,5 +65,6 @@ executable('fe-rpm', ['src/fe-rpm.cc'], include_dire
executable('fe-seek', ['src/fe-seek.cc'], include_directories: [feinc], link_with: [felib])
executable('fe-testbulktransport', ['src/fe-testbulktransport.cc'], include_directories: [feinc], link_with: [felib])
executable('fe-readibm', ['src/fe-readibm.cc'], include_directories: [feinc, fmtinc, decoderinc], link_with: [felib, readerlib, decoderlib, fmtlib])
executable('fe-readbrother', ['src/fe-readbrother.cc'], include_directories: [feinc, fmtinc, decoderinc], link_with: [felib, readerlib, decoderlib, fmtlib])
executable('fe-inspect', ['src/fe-inspect.cc'], include_directories: [feinc, fmtinc, decoderinc], link_with: [felib, readerlib, decoderlib, fmtlib])

47
src/fe-readbrother.cc Normal file
View File

@@ -0,0 +1,47 @@
#include "globals.h"
#include "flags.h"
#include "reader.h"
#include "fluxmap.h"
#include "decoders.h"
#include "image.h"
#include <fmt/format.h>
static StringFlag outputFilename(
{ "--output", "-o" },
"The output image file to write to.",
"brother.img");
int main(int argc, const char* argv[])
{
Flag::parseFlags(argc, argv);
std::vector<std::unique_ptr<Sector>> allSectors;
for (auto& track : readTracks())
{
Fluxmap& fluxmap = track->read();
nanoseconds_t clockPeriod = fluxmap.guessClock();
std::cout << fmt::format(" {:.1f} us clock; ", (double)clockPeriod/1000.0) << std::flush;
auto bitmap = decodeFluxmapToBits(fluxmap, clockPeriod);
std::cout << fmt::format("{} bytes encoded; ", bitmap.size()/8) << std::flush;
auto records = decodeBitsToRecordsBrother(bitmap);
std::cout << records.size() << " records." << std::endl;
auto sectors = parseRecordsToSectorsBrother(records);
std::cout << " " << sectors.size() << " sectors; ";
int size = 0;
for (auto& sector : sectors)
{
size += sector->data().size();
allSectors.push_back(std::move(sector));
}
std::cout << size << " bytes decoded." << std::endl;
}
writeSectorsToFile(allSectors, outputFilename);
return 0;
}

View File

@@ -30,7 +30,7 @@ int main(int argc, const char* argv[])
auto records = decodeBitsToRecordsMfm(bitmap);
std::cout << records.size() << " records." << std::endl;
auto sectors = decodeIbmRecordsToSectors(records);
auto sectors = parseRecordsToSectorsIbm(records);
std::cout << " " << sectors.size() << " sectors; ";
int size = 0;