Skip to content

Commit

Permalink
Merge pull request #121 from davidgiven/amiga
Browse files Browse the repository at this point in the history
Add support for writing Amiga disks.
  • Loading branch information
davidgiven authored Dec 14, 2019
2 parents 21fe586 + d0ed5b3 commit ec327e2
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 54 deletions.
101 changes: 101 additions & 0 deletions arch/amiga/amiga.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include "globals.h"
#include "record.h"
#include "decoders/decoders.h"
#include "amiga.h"
#include "bytes.h"
#include "fmt/format.h"

uint32_t amigaChecksum(const Bytes& bytes)
{
ByteReader br(bytes);
uint32_t checksum = 0;

assert((bytes.size() & 3) == 0);
while (!br.eof())
checksum ^= br.read_be32();

return checksum & 0x55555555;
}

static uint8_t everyother(uint16_t x)
{
/* aabb ccdd eeff gghh */
x &= 0x6666; /* 0ab0 0cd0 0ef0 0gh0 */
x >>= 1; /* 00ab 00cd 00ef 00gh */
x |= x << 2; /* abab cdcd efef ghgh */
x &= 0x3c3c; /* 00ab cd00 00ef gh00 */
x >>= 2; /* 0000 abcd 0000 efgh */
x |= x >> 4; /* 0000 abcd abcd efgh */
return x;
}

Bytes amigaInterleave(const Bytes& input)
{
Bytes output;
ByteWriter bw(output);

/* Write all odd bits. (Numbering starts at 0...) */

{
ByteReader br(input);
while (!br.eof())
{
uint16_t x = br.read_be16();
x &= 0xaaaa; /* a0b0 c0d0 e0f0 g0h0 */
x |= x >> 1; /* aabb ccdd eeff gghh */
x = everyother(x); /* 0000 0000 abcd efgh */
bw.write_8(x);
}
}

/* Write all even bits. */

{
ByteReader br(input);
while (!br.eof())
{
uint16_t x = br.read_be16();
x &= 0x5555; /* 0a0b 0c0d 0e0f 0g0h */
x |= x << 1; /* aabb ccdd eeff gghh */
x = everyother(x); /* 0000 0000 abcd efgh */
bw.write_8(x);
}
}

return output;
}

Bytes amigaDeinterleave(const uint8_t*& input, size_t len)
{
assert(!(len & 1));
const uint8_t* odds = &input[0];
const uint8_t* evens = &input[len/2];
Bytes output;
ByteWriter bw(output);

for (size_t i=0; i<len/2; i++)
{
uint8_t o = *odds++;
uint8_t e = *evens++;

/* This is the 'Interleave bits with 64-bit multiply' technique from
* http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
*/
uint16_t result =
(((e * 0x0101010101010101ULL & 0x8040201008040201ULL)
* 0x0102040810204081ULL >> 49) & 0x5555) |
(((o * 0x0101010101010101ULL & 0x8040201008040201ULL)
* 0x0102040810204081ULL >> 48) & 0xAAAA);

bw.write_be16(result);
}

input += len;
return output;
}

Bytes amigaDeinterleave(const Bytes& input)
{
const uint8_t* ptr = input.cbegin();
return amigaDeinterleave(ptr, input.size());
}
21 changes: 21 additions & 0 deletions arch/amiga/amiga.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#ifndef AMIGA_H
#define AMIGA_H

#include "encoders/encoders.h"

#define AMIGA_SECTOR_RECORD 0xaaaa44894489LL

#define AMIGA_TRACKS_PER_DISK 80
#define AMIGA_SECTORS_PER_TRACK 11
#define AMIGA_RECORD_SIZE 0x21f

class Sector;
class Fluxmap;
class SectorSet;

class AmigaDecoder : public AbstractDecoder
{
Expand All @@ -17,4 +22,20 @@ class AmigaDecoder : public AbstractDecoder
void decodeSectorRecord();
};

class AmigaEncoder : public AbstractEncoder
{
public:
virtual ~AmigaEncoder() {}

public:
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
};

extern FlagGroup amigaEncoderFlags;

extern uint32_t amigaChecksum(const Bytes& bytes);
extern Bytes amigaInterleave(const Bytes& input);
extern Bytes amigaDeinterleave(const uint8_t*& input, size_t len);
extern Bytes amigaDeinterleave(const Bytes& input);

#endif
55 changes: 7 additions & 48 deletions arch/amiga/decoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,47 +21,6 @@

static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD);

static Bytes deinterleave(const uint8_t*& input, size_t len)
{
assert(!(len & 1));
const uint8_t* odds = &input[0];
const uint8_t* evens = &input[len/2];
Bytes output;
ByteWriter bw(output);

for (size_t i=0; i<len/2; i++)
{
uint8_t o = *odds++;
uint8_t e = *evens++;

/* This is the 'Interleave bits with 64-bit multiply' technique from
* http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
*/
uint16_t result =
(((e * 0x0101010101010101ULL & 0x8040201008040201ULL)
* 0x0102040810204081ULL >> 49) & 0x5555) |
(((o * 0x0101010101010101ULL & 0x8040201008040201ULL)
* 0x0102040810204081ULL >> 48) & 0xAAAA);

bw.write_be16(result);
}

input += len;
return output;
}

static uint32_t checksum(const Bytes& bytes)
{
ByteReader br(bytes);
uint32_t checksum = 0;

assert((bytes.size() & 3) == 0);
while (!br.eof())
checksum ^= br.read_be32();

return checksum & 0x55555555;
}

AbstractDecoder::RecordType AmigaDecoder::advanceToNextRecord()
{
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
Expand All @@ -78,22 +37,22 @@ void AmigaDecoder::decodeSectorRecord()

const uint8_t* ptr = bytes.begin() + 3;

Bytes header = deinterleave(ptr, 4);
Bytes recoveryinfo = deinterleave(ptr, 16);
Bytes header = amigaDeinterleave(ptr, 4);
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);

_sector->logicalTrack = header[1] >> 1;
_sector->logicalSide = header[1] & 1;
_sector->logicalSector = header[2];

uint32_t wantedheaderchecksum = deinterleave(ptr, 4).reader().read_be32();
uint32_t gotheaderchecksum = checksum(rawbytes.slice(6, 40));
uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(6, 40));
if (gotheaderchecksum != wantedheaderchecksum)
return;

uint32_t wanteddatachecksum = deinterleave(ptr, 4).reader().read_be32();
uint32_t gotdatachecksum = checksum(rawbytes.slice(62, 1024));
uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(62, 1024));

_sector->data.clear();
_sector->data.writer().append(deinterleave(ptr, 512)).append(recoveryinfo);
_sector->data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
126 changes: 126 additions & 0 deletions arch/amiga/encoder.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "globals.h"
#include "record.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "amiga.h"
#include "crc.h"
#include "sectorset.h"
#include "writer.h"

FlagGroup amigaEncoderFlags;

static DoubleFlag clockRateUs(
{ "--clock-rate" },
"Encoded data clock rate (microseconds).",
2.00);

static DoubleFlag postIndexGapMs(
{ "--post-index-gap" },
"Post-index gap before first sector header (milliseconds).",
0.5);

static int charToInt(char c)
{
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
}

static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{
for (bool bit : src)
{
if (cursor < bits.size())
bits[cursor++] = bit;
}
}

static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{
cursor += width;
for (int i=0; i<width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
}

static void write_interleaved_bytes(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{
assert(!(bytes.size() & 3));
Bytes interleaved = amigaInterleave(bytes);
encodeMfm(bits, cursor, interleaved);
}

static void write_interleaved_bytes(std::vector<bool>& bits, unsigned& cursor, uint32_t data)
{
Bytes b(4);
ByteWriter bw(b);
bw.write_be32(data);
write_interleaved_bytes(bits, cursor, b);
}

static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector)
{
if ((sector->data.size() != 512) && (sector->data.size() != 528))
Error() << "unsupported sector size --- you must pick 512 or 528";

write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6*8);

std::vector<bool> headerBits(20*16);
unsigned headerCursor = 0;

Bytes header =
{
0xff, /* Amiga 1.0 format byte */
(uint8_t) ((sector->logicalTrack<<1) | sector->logicalSide),
(uint8_t) sector->logicalSector,
(uint8_t) (AMIGA_SECTORS_PER_TRACK - sector->logicalSector)
};
write_interleaved_bytes(headerBits, headerCursor, header);
Bytes recoveryInfo(16);
if (sector->data.size() == 528)
recoveryInfo = sector->data.slice(512, 16);
write_interleaved_bytes(headerBits, headerCursor, recoveryInfo);

std::vector<bool> dataBits(512*16);
unsigned dataCursor = 0;
write_interleaved_bytes(dataBits, dataCursor, sector->data);

write_bits(bits, cursor, headerBits);
uint32_t headerChecksum = amigaChecksum(toBytes(headerBits));
write_interleaved_bytes(bits, cursor, headerChecksum);
uint32_t dataChecksum = amigaChecksum(toBytes(dataBits));
write_interleaved_bytes(bits, cursor, dataChecksum);
write_bits(bits, cursor, dataBits);
}

std::unique_ptr<Fluxmap> AmigaEncoder::encode(
int physicalTrack, int physicalSide, const SectorSet& allSectors)
{
if ((physicalTrack < 0) || (physicalTrack >= AMIGA_TRACKS_PER_DISK))
return std::unique_ptr<Fluxmap>();

int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;

fillBitmapTo(bits, cursor, postIndexGapMs * 1000 / clockRateUs, { true, false });

for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
{
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
write_sector(bits, cursor, sectorData);
}

if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), { true, false });

std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap;
}

2 changes: 1 addition & 1 deletion arch/brother/encoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ std::unique_ptr<Fluxmap> BrotherEncoder::encode(
write_sector_data(bits, cursor, sectorData->data);
}

if (cursor > bits.size())
if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), { true, false });

Expand Down
24 changes: 23 additions & 1 deletion doc/disk-amiga.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Bizarrely, the data in each sector is stored with all the odd bits first, and
then all the even bits. This is tied into the checksum algorithm, which is
distinctly subpar and not particularly good at detecting errors.

Reading discs
Reading disks
-------------

Just do:
Expand All @@ -34,6 +34,28 @@ You will end up with a 929280 byte long image which you probably _can't_ use
in an emulator; each sector will contain the 512 bytes of user payload
followed by the 16 bytes of metadata.

Writing disks
-------------

Just do:

```
fluxengine write amiga -i amiga.adf
```

This will rake a normal 901120 byte long ADF file and write it to a DD disk.
Note that writing to an HD disk will probably not work (this will depend on
your drive and disk and potential FluxEngine bugs I'm still working on ---
please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if
you have any insight here).

If you want to write the metadata as well, specify a 528 byte sector size for
the output image and supply a 929280 byte long file as described above.

```
fluxengine write amiga -i amiga.adf:b=528
```

Useful references
-----------------

Expand Down
Loading

0 comments on commit ec327e2

Please sign in to comment.