mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge from master.
This commit is contained in:
@@ -76,6 +76,8 @@ markers.
|
||||
- [Brother 120kB and 240kB word processor disks](brother.html); read and
|
||||
write
|
||||
|
||||
- [Macintosh 800kB (and probably 400kB too) disks](macintosh.html); read only
|
||||
|
||||
...aaaand that's it. If you want more, please [get in
|
||||
touch](https://github.com/davidgiven/fluxengine/issues/new); I need samples
|
||||
of floppy disks to scan and play with.
|
||||
|
||||
65
doc/macintosh.md
Normal file
65
doc/macintosh.md
Normal file
@@ -0,0 +1,65 @@
|
||||
Macintosh disks
|
||||
===============
|
||||
|
||||
Macintosh disks come in two varieties: the newer 1440kB ones, which are
|
||||
perfectly ordinary PC disks you should use `fe-readibm` to read, and the
|
||||
older 800kB disks (and 400kB for the single sides ones). They have 80 tracks
|
||||
and up to 12 sectors per track.
|
||||
|
||||
They are also completely insane.
|
||||
|
||||
It's not just the weird, custom GCR encoding. It's not just the utterly
|
||||
bizarre additional encoding/checksum built on top of that where [every byte
|
||||
is mutated according to the previous bytes in the
|
||||
sector](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/).
|
||||
It's not just the odd way in which disks think they have four sides, two on
|
||||
one side and two on the other, so that the track byte stores only the bottom
|
||||
6 bits of the track number. It's not just the way that Macintosh sectors are
|
||||
524 bytes long. No, it's the way the Macintosh drive changes speed depending
|
||||
on which track it's looking at, so that each track contains a different
|
||||
amount of data.
|
||||
|
||||
The reason for this is actually quite sensible: the tracks towards the centre
|
||||
of the disk are obviously moving more slowly, so you can't pack the bits in
|
||||
quite as closely (due to limitations in the magnetic media). You can use a
|
||||
higher bitrate at the edge of the disk than in the middle. Many platforms,
|
||||
for example the Commodore 64 1541 drive, changed bitrate this way.
|
||||
|
||||
But Macintosh disks used a constant bitrate and changed the speed that the
|
||||
disk spun instead to achieve the same effect...
|
||||
|
||||
_Anyway_: FluxEngine will read them fine on a conventional drive. Because
|
||||
it's clever.
|
||||
|
||||
Reading discs
|
||||
-------------
|
||||
|
||||
Just do:
|
||||
|
||||
```
|
||||
.obj/fe-readmac
|
||||
```
|
||||
|
||||
You should end up with an `mac.img` which is 1001888 bytes long (for a normal
|
||||
DD disk). If you want the single-sided variety, use `-s :s=0`.
|
||||
|
||||
**Big warning!** The image may not work in an emulator. Mac disk images are
|
||||
complicated due to the way the tracks are different sizes and the odd sector
|
||||
size. FluxEngine chooses to store them in a simple 524 x 12 x 2 x 80 layout,
|
||||
with holes where missing sectors should be. This was easiest. If anyone can
|
||||
suggest a better way, please [get in
|
||||
touch](https://github.com/davidgiven/fluxengine/issues/new).
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
|
||||
- [MAME's ap_dsk35.cpp file]
|
||||
(https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp),
|
||||
without which I'd never have managed to do this
|
||||
|
||||
- [Crazy Disk Encoding
|
||||
Schemes](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/), which made
|
||||
me realise just how nuts the format is
|
||||
|
||||
- [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an
|
||||
epicly detailed writeup of the Apple II disk format (which is closely related)
|
||||
@@ -11,7 +11,8 @@ class AmigaDecoder : public AbstractDecoder
|
||||
public:
|
||||
virtual ~AmigaDecoder() {}
|
||||
|
||||
SectorVector decodeToSectors(const RawRecordVector& rawRecords);
|
||||
SectorVector decodeToSectors(
|
||||
const RawRecordVector& rawRecords, unsigned physicalTrack);
|
||||
nanoseconds_t guessClock(Fluxmap& fluxmap) const;
|
||||
int recordMatcher(uint64_t fifo) const;
|
||||
};
|
||||
|
||||
@@ -63,7 +63,7 @@ static uint32_t checksum(std::vector<uint8_t>::const_iterator input, size_t len)
|
||||
return checksum & 0x55555555;
|
||||
}
|
||||
|
||||
SectorVector AmigaDecoder::decodeToSectors(const RawRecordVector& rawRecords)
|
||||
SectorVector AmigaDecoder::decodeToSectors(const RawRecordVector& rawRecords, unsigned)
|
||||
{
|
||||
std::vector<std::unique_ptr<Sector>> sectors;
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ class BrotherDecoder : public AbstractDecoder
|
||||
public:
|
||||
virtual ~BrotherDecoder() {}
|
||||
|
||||
SectorVector decodeToSectors(const RawRecordVector& rawRecords);
|
||||
SectorVector decodeToSectors(
|
||||
const RawRecordVector& rawRecords, unsigned physicalTrack);
|
||||
int recordMatcher(uint64_t fifo) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ static int decode_header_gcr(uint16_t word)
|
||||
return -1;
|
||||
};
|
||||
|
||||
SectorVector BrotherDecoder::decodeToSectors(const RawRecordVector& rawRecords)
|
||||
SectorVector BrotherDecoder::decodeToSectors(const RawRecordVector& rawRecords, unsigned)
|
||||
{
|
||||
std::vector<std::unique_ptr<Sector>> sectors;
|
||||
bool headerIsValid = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ void BitAccumulator::push(uint32_t bits, size_t size)
|
||||
{
|
||||
_fifo = (_fifo<<1) | (bits >> 31);
|
||||
_bitcount++;
|
||||
bits <<= 1;
|
||||
if (_bitcount == 8)
|
||||
{
|
||||
_data.push_back(_fifo);
|
||||
@@ -51,3 +52,11 @@ void BitAccumulator::push(uint32_t bits, size_t size)
|
||||
}
|
||||
}
|
||||
|
||||
void BitAccumulator::finish()
|
||||
{
|
||||
if (_bitcount != 0)
|
||||
{
|
||||
_data.push_back(_fifo);
|
||||
_bitcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,10 @@ public:
|
||||
void reset();
|
||||
void push(uint32_t bits, size_t size);
|
||||
size_t size() const { return _data.size(); }
|
||||
void finish();
|
||||
|
||||
operator const std::vector<uint8_t>& () const { return _data; }
|
||||
operator const std::vector<uint8_t>& ()
|
||||
{ finish(); return _data; }
|
||||
|
||||
private:
|
||||
uint8_t _fifo;
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
#include "protocol.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
static IntFlag clockDetectionNoiseFloor(
|
||||
{ "--clock-detection-noise-floor" },
|
||||
"Noise floor used for clock detection in flux.",
|
||||
200);
|
||||
|
||||
static DoubleFlag clockDecodeThreshold(
|
||||
{ "--clock-decode-threshold" },
|
||||
"Pulses below this fraction of a clock tick are considered spurious and ignored.",
|
||||
@@ -29,45 +24,78 @@ nanoseconds_t Fluxmap::guessClock() const
|
||||
for (uint8_t interval : _intervals)
|
||||
buckets[interval]++;
|
||||
|
||||
int peaklo = 1;
|
||||
while (peaklo < 256)
|
||||
uint32_t max = *std::max_element(std::begin(buckets), std::end(buckets));
|
||||
uint32_t min = *std::min_element(std::begin(buckets), std::end(buckets));
|
||||
uint32_t noise_floor = (min+max)/100;
|
||||
uint32_t signal_level = noise_floor * 5;
|
||||
|
||||
/* Find a point solidly within the first pulse. */
|
||||
|
||||
int pulseindex = 0;
|
||||
while (pulseindex < 256)
|
||||
{
|
||||
if (buckets[peaklo] > (uint32_t)(clockDetectionNoiseFloor*2))
|
||||
if (buckets[pulseindex] > signal_level)
|
||||
break;
|
||||
peaklo++;
|
||||
pulseindex++;
|
||||
}
|
||||
if (pulseindex == -1)
|
||||
return 0;
|
||||
|
||||
/* Find the upper and lower bounds of the pulse. */
|
||||
|
||||
int peaklo = pulseindex;
|
||||
while (peaklo > 0)
|
||||
{
|
||||
if (buckets[peaklo] < noise_floor)
|
||||
break;
|
||||
peaklo--;
|
||||
}
|
||||
|
||||
uint32_t peakmaxindex = peaklo;
|
||||
uint32_t peakmaxvalue = buckets[peakmaxindex];
|
||||
uint32_t peakhi = peaklo;
|
||||
while (peakhi < 256)
|
||||
int peakhi = pulseindex;
|
||||
while (peakhi < 255)
|
||||
{
|
||||
uint32_t v = buckets[peakhi];
|
||||
if (buckets[peakhi] < (uint32_t)clockDetectionNoiseFloor)
|
||||
if (buckets[peakhi] < noise_floor)
|
||||
break;
|
||||
if (v > peakmaxvalue)
|
||||
{
|
||||
peakmaxindex = peakhi;
|
||||
peakmaxvalue = v;
|
||||
}
|
||||
peakhi++;
|
||||
}
|
||||
|
||||
/* Find the total accumulated size of the pulse. */
|
||||
|
||||
uint32_t total_size = 0;
|
||||
for (int i = peaklo; i < peakhi; i++)
|
||||
total_size += buckets[i];
|
||||
|
||||
/* Now find the median. */
|
||||
|
||||
uint32_t count = 0;
|
||||
int median = peaklo;
|
||||
while (median < peakhi)
|
||||
{
|
||||
count += buckets[median];
|
||||
if (count > (total_size/2))
|
||||
break;
|
||||
median++;
|
||||
}
|
||||
|
||||
if (showClockHistogram)
|
||||
{
|
||||
std::cout << "Clock detection histogram:" << std::endl;
|
||||
for (int i=0; i<256; i++)
|
||||
{
|
||||
std::cout << fmt::format("{:.2f} {}\n", (double)i * US_PER_TICK, buckets[i]);
|
||||
}
|
||||
std::cout << fmt::format("{:.2f} {}", (double)i * US_PER_TICK, buckets[i]) << std::endl;
|
||||
|
||||
std::cout << fmt::format("Noise floor: {}", noise_floor) << std::endl;
|
||||
std::cout << fmt::format("Signal level: {}", signal_level) << std::endl;
|
||||
std::cout << fmt::format("Peak start: {} ({:.2f} us)", peaklo, peaklo*US_PER_TICK) << std::endl;
|
||||
std::cout << fmt::format("Peak end: {} ({:.2f} us)", peakhi, peakhi*US_PER_TICK) << std::endl;
|
||||
std::cout << fmt::format("Median: {} ({:.2f} us)", median, median*US_PER_TICK) << std::endl;
|
||||
}
|
||||
|
||||
/*
|
||||
* Okay, peakmaxindex should now be a good candidate for the (or a) clock.
|
||||
* Okay, the median should now be a good candidate for the (or a) clock.
|
||||
* How this maps onto the actual clock rate depends on the encoding.
|
||||
*/
|
||||
|
||||
return peakmaxindex * NS_PER_TICK;
|
||||
return median * NS_PER_TICK;
|
||||
}
|
||||
|
||||
/* Decodes a fluxmap into a nice aligned array of bits. */
|
||||
|
||||
@@ -18,7 +18,8 @@ public:
|
||||
virtual ~AbstractDecoder() {}
|
||||
|
||||
virtual nanoseconds_t guessClock(Fluxmap& fluxmap) const;
|
||||
virtual SectorVector decodeToSectors(const RawRecordVector& rawrecords) = 0;
|
||||
virtual SectorVector decodeToSectors(const RawRecordVector& rawrecords,
|
||||
unsigned physicalTrack) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
static_assert(std::is_trivially_copyable<IbmIdam>::value);
|
||||
|
||||
SectorVector AbstractIbmDecoder::decodeToSectors(const RawRecordVector& rawRecords)
|
||||
SectorVector AbstractIbmDecoder::decodeToSectors(const RawRecordVector& rawRecords, unsigned)
|
||||
{
|
||||
bool idamValid = false;
|
||||
IbmIdam idam;
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
{}
|
||||
virtual ~AbstractIbmDecoder() {}
|
||||
|
||||
SectorVector decodeToSectors(const RawRecordVector& rawRecords);
|
||||
SectorVector decodeToSectors(const RawRecordVector& rawRecords, unsigned physicalTrack);
|
||||
|
||||
protected:
|
||||
virtual int skipHeaderBytes() const = 0;
|
||||
|
||||
64
lib/macintosh/data_gcr.h
Normal file
64
lib/macintosh/data_gcr.h
Normal file
@@ -0,0 +1,64 @@
|
||||
GCR_ENTRY(0x96, 0x00)
|
||||
GCR_ENTRY(0x97, 0x01)
|
||||
GCR_ENTRY(0x9a, 0x02)
|
||||
GCR_ENTRY(0x9b, 0x03)
|
||||
GCR_ENTRY(0x9d, 0x04)
|
||||
GCR_ENTRY(0x9e, 0x05)
|
||||
GCR_ENTRY(0x9f, 0x06)
|
||||
GCR_ENTRY(0xa6, 0x07)
|
||||
GCR_ENTRY(0xa7, 0x08)
|
||||
GCR_ENTRY(0xab, 0x09)
|
||||
GCR_ENTRY(0xac, 0x0a)
|
||||
GCR_ENTRY(0xad, 0x0b)
|
||||
GCR_ENTRY(0xae, 0x0c)
|
||||
GCR_ENTRY(0xaf, 0x0d)
|
||||
GCR_ENTRY(0xb2, 0x0e)
|
||||
GCR_ENTRY(0xb3, 0x0f)
|
||||
GCR_ENTRY(0xb4, 0x10)
|
||||
GCR_ENTRY(0xb5, 0x11)
|
||||
GCR_ENTRY(0xb6, 0x12)
|
||||
GCR_ENTRY(0xb7, 0x13)
|
||||
GCR_ENTRY(0xb9, 0x14)
|
||||
GCR_ENTRY(0xba, 0x15)
|
||||
GCR_ENTRY(0xbb, 0x16)
|
||||
GCR_ENTRY(0xbc, 0x17)
|
||||
GCR_ENTRY(0xbd, 0x18)
|
||||
GCR_ENTRY(0xbe, 0x19)
|
||||
GCR_ENTRY(0xbf, 0x1a)
|
||||
GCR_ENTRY(0xcb, 0x1b)
|
||||
GCR_ENTRY(0xcd, 0x1c)
|
||||
GCR_ENTRY(0xce, 0x1d)
|
||||
GCR_ENTRY(0xcf, 0x1e)
|
||||
GCR_ENTRY(0xd3, 0x1f)
|
||||
GCR_ENTRY(0xd6, 0x20)
|
||||
GCR_ENTRY(0xd7, 0x21)
|
||||
GCR_ENTRY(0xd9, 0x22)
|
||||
GCR_ENTRY(0xda, 0x23)
|
||||
GCR_ENTRY(0xdb, 0x24)
|
||||
GCR_ENTRY(0xdc, 0x25)
|
||||
GCR_ENTRY(0xdd, 0x26)
|
||||
GCR_ENTRY(0xde, 0x27)
|
||||
GCR_ENTRY(0xdf, 0x28)
|
||||
GCR_ENTRY(0xe5, 0x29)
|
||||
GCR_ENTRY(0xe6, 0x2a)
|
||||
GCR_ENTRY(0xe7, 0x2b)
|
||||
GCR_ENTRY(0xe9, 0x2c)
|
||||
GCR_ENTRY(0xea, 0x2d)
|
||||
GCR_ENTRY(0xeb, 0x2e)
|
||||
GCR_ENTRY(0xec, 0x2f)
|
||||
GCR_ENTRY(0xed, 0x30)
|
||||
GCR_ENTRY(0xee, 0x31)
|
||||
GCR_ENTRY(0xef, 0x32)
|
||||
GCR_ENTRY(0xf2, 0x33)
|
||||
GCR_ENTRY(0xf3, 0x34)
|
||||
GCR_ENTRY(0xf4, 0x35)
|
||||
GCR_ENTRY(0xf5, 0x36)
|
||||
GCR_ENTRY(0xf6, 0x37)
|
||||
GCR_ENTRY(0xf7, 0x38)
|
||||
GCR_ENTRY(0xf9, 0x39)
|
||||
GCR_ENTRY(0xfa, 0x3a)
|
||||
GCR_ENTRY(0xfb, 0x3b)
|
||||
GCR_ENTRY(0xfc, 0x3c)
|
||||
GCR_ENTRY(0xfd, 0x3d)
|
||||
GCR_ENTRY(0xfe, 0x3e)
|
||||
GCR_ENTRY(0xff, 0x3f)
|
||||
189
lib/macintosh/decoder.cc
Normal file
189
lib/macintosh/decoder.cc
Normal file
@@ -0,0 +1,189 @@
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "protocol.h"
|
||||
#include "record.h"
|
||||
#include "decoders.h"
|
||||
#include "sector.h"
|
||||
#include "macintosh.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
static int decode_data_gcr(uint8_t gcr)
|
||||
{
|
||||
switch (gcr)
|
||||
{
|
||||
#define GCR_ENTRY(gcr, data) \
|
||||
case gcr: return data;
|
||||
#include "data_gcr.h"
|
||||
#undef GCR_ENTRY
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
|
||||
* and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
|
||||
*/
|
||||
static std::vector<uint8_t> decode_crazy_data(const uint8_t* inp, int& status)
|
||||
{
|
||||
std::vector<uint8_t> output;
|
||||
|
||||
static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3;
|
||||
|
||||
uint8_t b1[LOOKUP_LEN + 1];
|
||||
uint8_t b2[LOOKUP_LEN + 1];
|
||||
uint8_t b3[LOOKUP_LEN + 1];
|
||||
|
||||
for (int i=0; i<=LOOKUP_LEN; i++)
|
||||
{
|
||||
uint8_t w4 = *inp++;
|
||||
uint8_t w1 = *inp++;
|
||||
uint8_t w2 = *inp++;
|
||||
uint8_t w3 = (i != 174) ? *inp++ : 0;
|
||||
|
||||
b1[i] = (w1 & 0x3F) | ((w4 << 2) & 0xC0);
|
||||
b2[i] = (w2 & 0x3F) | ((w4 << 4) & 0xC0);
|
||||
b3[i] = (w3 & 0x3F) | ((w4 << 6) & 0xC0);
|
||||
}
|
||||
|
||||
/* Copy from the user's buffer to our buffer, while computing
|
||||
* the three-byte data checksum. */
|
||||
|
||||
uint32_t c1 = 0;
|
||||
uint32_t c2 = 0;
|
||||
uint32_t c3 = 0;
|
||||
unsigned count = 0;
|
||||
for (;;)
|
||||
{
|
||||
c1 = (c1 & 0xFF) << 1;
|
||||
if (c1 & 0x0100)
|
||||
c1++;
|
||||
|
||||
uint8_t val = b1[count] ^ c1;
|
||||
c3 += val;
|
||||
if (c1 & 0x0100)
|
||||
{
|
||||
c3++;
|
||||
c1 &= 0xFF;
|
||||
}
|
||||
output.push_back(val);
|
||||
|
||||
val = b2[count] ^ c3;
|
||||
c2 += val;
|
||||
if (c3 > 0xFF)
|
||||
{
|
||||
c2++;
|
||||
c3 &= 0xFF;
|
||||
}
|
||||
output.push_back(val);
|
||||
|
||||
if (output.size() == 524)
|
||||
break;
|
||||
|
||||
val = b3[count] ^ c2;
|
||||
c1 += val;
|
||||
if (c2 > 0xFF)
|
||||
{
|
||||
c1++;
|
||||
c2 &= 0xFF;
|
||||
}
|
||||
output.push_back(val);
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t c4 = ((c1 & 0xC0) >> 6) | ((c2 & 0xC0) >> 4) | ((c3 & 0xC0) >> 2);
|
||||
c1 &= 0x3f;
|
||||
c2 &= 0x3f;
|
||||
c3 &= 0x3f;
|
||||
c4 &= 0x3f;
|
||||
uint8_t g4 = *inp++;
|
||||
uint8_t g3 = *inp++;
|
||||
uint8_t g2 = *inp++;
|
||||
uint8_t g1 = *inp++;
|
||||
if ((g4 == c4) && (g3 == c3) && (g2 == c2) && (g1 == c1))
|
||||
status = Sector::OK;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
uint8_t decode_side(uint8_t side)
|
||||
{
|
||||
/* Mac disks, being weird, use the side byte to encode both the side (in
|
||||
* bit 5) and also whether we're above track 0x3f (in bit 6).
|
||||
*/
|
||||
|
||||
return !!(side & 0x40);
|
||||
}
|
||||
|
||||
SectorVector MacintoshDecoder::decodeToSectors(
|
||||
const RawRecordVector& rawRecords, unsigned physicalTrack)
|
||||
{
|
||||
std::vector<std::unique_ptr<Sector>> sectors;
|
||||
int nextSector;
|
||||
int nextSide;
|
||||
bool headerIsValid = false;
|
||||
|
||||
for (auto& rawrecord : rawRecords)
|
||||
{
|
||||
const std::vector<bool>& rawdata = rawrecord->data;
|
||||
const std::vector<uint8_t>& rawbytes = toBytes(rawdata);
|
||||
|
||||
if (rawbytes.size() < 8)
|
||||
continue;
|
||||
|
||||
uint32_t signature = read_be24(&rawbytes[0]);
|
||||
switch (signature)
|
||||
{
|
||||
case MAC_SECTOR_RECORD:
|
||||
{
|
||||
unsigned track = decode_data_gcr(rawbytes[3]);
|
||||
if (track != (physicalTrack & 0x3f))
|
||||
break;
|
||||
nextSector = decode_data_gcr(rawbytes[4]);
|
||||
nextSide = decode_data_gcr(rawbytes[5]);
|
||||
uint8_t formatByte = decode_data_gcr(rawbytes[6]);
|
||||
uint8_t wantedsum = decode_data_gcr(rawbytes[7]);
|
||||
|
||||
uint8_t gotsum = (track ^ nextSector ^ nextSide ^ formatByte) & 0x3f;
|
||||
headerIsValid = (wantedsum == gotsum);
|
||||
break;
|
||||
}
|
||||
|
||||
case MAC_DATA_RECORD:
|
||||
{
|
||||
if (!headerIsValid)
|
||||
break;
|
||||
headerIsValid = false;
|
||||
|
||||
uint8_t inputbuffer[MAC_SECTOR_LENGTH * 8/6 + 5] = {};
|
||||
for (unsigned i=0; i<sizeof(inputbuffer); i++)
|
||||
{
|
||||
auto p = rawbytes.begin() + 4 + i;
|
||||
if (p > rawbytes.end())
|
||||
break;
|
||||
|
||||
inputbuffer[i] = decode_data_gcr(*p);
|
||||
}
|
||||
|
||||
int status = Sector::BAD_CHECKSUM;
|
||||
auto data = decode_crazy_data(inputbuffer, status);
|
||||
|
||||
auto sector = std::unique_ptr<Sector>(
|
||||
new Sector(status, physicalTrack, decode_side(nextSide), nextSector, data));
|
||||
sectors.push_back(std::move(sector));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sectors;
|
||||
}
|
||||
|
||||
int MacintoshDecoder::recordMatcher(uint64_t fifo) const
|
||||
{
|
||||
uint32_t masked = fifo & 0xffffff;
|
||||
if ((masked == MAC_SECTOR_RECORD) || (masked == MAC_DATA_RECORD))
|
||||
return 24;
|
||||
return 0;
|
||||
}
|
||||
22
lib/macintosh/macintosh.h
Normal file
22
lib/macintosh/macintosh.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef MACINTOSH_H
|
||||
#define MACINTOSH_H
|
||||
|
||||
#define MAC_SECTOR_RECORD 0xd5aa96
|
||||
#define MAC_DATA_RECORD 0xd5aaad
|
||||
|
||||
#define MAC_SECTOR_LENGTH 524 /* yes, really */
|
||||
|
||||
class Sector;
|
||||
class Fluxmap;
|
||||
|
||||
class MacintoshDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
virtual ~MacintoshDecoder() {}
|
||||
|
||||
SectorVector decodeToSectors(const RawRecordVector& rawRecords, unsigned physicalTrack);
|
||||
int recordMatcher(uint64_t fifo) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -124,7 +124,7 @@ void readDiskCommand(AbstractDecoder& decoder, const std::string& outputFilename
|
||||
auto rawrecords = decoder.extractRecords(bitmap);
|
||||
std::cout << fmt::format("{} records", rawrecords.size()) << std::endl;
|
||||
|
||||
auto sectors = decoder.decodeToSectors(rawrecords);
|
||||
auto sectors = decoder.decodeToSectors(rawrecords, track->track);
|
||||
std::cout << " " << sectors.size() << " sectors; ";
|
||||
|
||||
for (auto& sector : sectors)
|
||||
|
||||
@@ -49,11 +49,12 @@ std::unique_ptr<Fluxmap> readStream(const std::string& path, unsigned track, uns
|
||||
case 0x0d: /* OOB block */
|
||||
{
|
||||
int blocktype = f.get();
|
||||
(void) blocktype;
|
||||
int blocklen = f.get() | (f.get()<<8);
|
||||
if (f.fail() || f.eof())
|
||||
goto finished;
|
||||
|
||||
f.seekg(here + blocklen, std::ios_base::beg);
|
||||
f.seekg(here + blocklen + 3, std::ios_base::beg);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -72,12 +73,13 @@ std::unique_ptr<Fluxmap> readStream(const std::string& path, unsigned track, uns
|
||||
else if (b == 0x09)
|
||||
{
|
||||
/* Nop2: skip one byte */
|
||||
f.seekg(1, std::ios_base::cur);
|
||||
f.get();
|
||||
}
|
||||
else if (b == 0x0a)
|
||||
{
|
||||
/* Nop3: skip two bytes */
|
||||
f.seekg(2, std::ios_base::cur);
|
||||
f.get();
|
||||
f.get();
|
||||
}
|
||||
else if (b == 0x0b)
|
||||
{ /* Ovl16: the next block is 0x10000 sclks longer than normal.
|
||||
|
||||
17
meson.build
17
meson.build
@@ -119,6 +119,15 @@ ibmdecoderlib = shared_library('ibmdecoderlib',
|
||||
)
|
||||
ibminc = include_directories('lib/ibm')
|
||||
|
||||
macdecoderlib = shared_library('macdecoderlib',
|
||||
[
|
||||
'lib/macintosh/decoder.cc',
|
||||
],
|
||||
include_directories: [feinc, fmtinc, decoderinc],
|
||||
link_with: [felib, fmtlib, decoderlib]
|
||||
)
|
||||
macinc = include_directories('lib/macintosh')
|
||||
|
||||
executable('fe-erase', ['src/fe-erase.cc'], include_directories: [feinc], link_with: [felib, writerlib])
|
||||
executable('fe-inspect', ['src/fe-inspect.cc'], include_directories: [feinc, fmtinc, decoderinc], link_with: [felib, readerlib, decoderlib, fmtlib])
|
||||
executable('fe-readadfs', ['src/fe-readadfs.cc'], include_directories: [feinc, fmtinc, decoderinc, ibminc], link_with: [felib, readerlib, decoderlib, ibmdecoderlib, fmtlib])
|
||||
@@ -126,6 +135,7 @@ executable('fe-readamiga', ['src/fe-readamiga.cc'], include_dire
|
||||
executable('fe-readbrother', ['src/fe-readbrother.cc'], include_directories: [feinc, fmtinc, decoderinc, brotherinc], link_with: [felib, readerlib, decoderlib, brotherdecoderlib, fmtlib])
|
||||
executable('fe-readdfs', ['src/fe-readdfs.cc'], include_directories: [feinc, fmtinc, decoderinc, ibminc], link_with: [felib, readerlib, decoderlib, ibmdecoderlib, fmtlib])
|
||||
executable('fe-readibm', ['src/fe-readibm.cc'], include_directories: [feinc, fmtinc, decoderinc, ibminc], link_with: [felib, readerlib, decoderlib, ibmdecoderlib, fmtlib])
|
||||
executable('fe-readmac', ['src/fe-readmac.cc'], include_directories: [feinc, fmtinc, decoderinc, macinc], link_with: [felib, readerlib, decoderlib, macdecoderlib, fmtlib])
|
||||
executable('fe-rpm', ['src/fe-rpm.cc'], include_directories: [feinc], link_with: [felib])
|
||||
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])
|
||||
@@ -135,6 +145,7 @@ executable('fe-writetestpattern', ['src/fe-writetestpattern.cc'], include_dire
|
||||
|
||||
executable('brother120tool', ['tools/brother120tool.cc'], include_directories: [feinc, fmtinc], link_with: [felib, fmtlib])
|
||||
|
||||
test('DataSpec', executable('dataspec-test', ['tests/dataspec.cc'], include_directories: [feinc], link_with: [felib]))
|
||||
test('Flags', executable('flags-test', ['tests/flags.cc'], include_directories: [feinc], link_with: [felib]))
|
||||
test('FmMfm', executable('fmmfm-test', ['tests/fmmfm.cc'], include_directories: [feinc, decoderinc], link_with: [felib, decoderlib]))
|
||||
test('DataSpec', executable('dataspec-test', ['tests/dataspec.cc'], include_directories: [feinc], link_with: [felib]))
|
||||
test('Flags', executable('flags-test', ['tests/flags.cc'], include_directories: [feinc], link_with: [felib]))
|
||||
test('FmMfm', executable('fmmfm-test', ['tests/fmmfm.cc'], include_directories: [feinc, decoderinc], link_with: [felib, decoderlib]))
|
||||
test('BitAccumulator', executable('bitaccumulator-test', ['tests/bitaccumulator.cc'], include_directories: [feinc], link_with: [felib]))
|
||||
|
||||
@@ -25,6 +25,11 @@ static IntFlag fluxmapResolutionFlag(
|
||||
"Resolution of flux visualisation (nanoseconds). 0 to autoscale",
|
||||
0);
|
||||
|
||||
static DoubleFlag seekFlag(
|
||||
{ "--seek", "-S" },
|
||||
"Seek this many milliseconds into the track before displaying it.",
|
||||
0.0);
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
Flag::parseFlags(argc, argv);
|
||||
@@ -54,13 +59,29 @@ int main(int argc, const char* argv[])
|
||||
if (resolution == 0)
|
||||
resolution = clockPeriod / 4;
|
||||
|
||||
nanoseconds_t now = 0;
|
||||
nanoseconds_t nextclock = clockPeriod;
|
||||
|
||||
nanoseconds_t now = 0;
|
||||
int cursor = 0;
|
||||
nanoseconds_t seekto = seekFlag*1000000.0;
|
||||
int ticks = 0;
|
||||
std::cout << fmt::format("{: 10.3f}:-", 0.0);
|
||||
for (int cursor=0; cursor<fluxmap->bytes(); cursor++)
|
||||
while (cursor < fluxmap->bytes())
|
||||
{
|
||||
int interval = (*fluxmap)[cursor];
|
||||
int interval = (*fluxmap)[cursor++];
|
||||
if (interval == 0)
|
||||
interval = 0x100;
|
||||
ticks += interval;
|
||||
|
||||
now = ticks * NS_PER_TICK;
|
||||
if (now >= seekto)
|
||||
break;
|
||||
}
|
||||
|
||||
std::cout << fmt::format("{: 10.3f}:-", ticks*US_PER_TICK);
|
||||
nanoseconds_t lasttransition = 0;
|
||||
while (cursor < fluxmap->bytes())
|
||||
{
|
||||
int interval = (*fluxmap)[cursor++];
|
||||
if (interval == 0)
|
||||
interval = 0x100;
|
||||
ticks += interval;
|
||||
@@ -82,7 +103,12 @@ int main(int argc, const char* argv[])
|
||||
now = next;
|
||||
}
|
||||
|
||||
std::cout << fmt::format("==== {: 10.3f}", (double)transition / 1000.0);
|
||||
nanoseconds_t length = transition - lasttransition;
|
||||
std::cout << fmt::format("==== {: 10.3f} +{:.3f} = {:.1f} clocks",
|
||||
(double)transition / 1000.0,
|
||||
(double)length / 1000.0,
|
||||
(double)length / clockPeriod);
|
||||
lasttransition = transition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,8 +118,13 @@ int main(int argc, const char* argv[])
|
||||
<< " follows:" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
for (bool bit : bitmap)
|
||||
std::cout << (bit ? 'X' : '-');
|
||||
size_t cursor = seekFlag*1000000.0 / clockPeriod;
|
||||
while (cursor < bitmap.size())
|
||||
{
|
||||
std::cout << (bitmap[cursor] ? 'X' : '-');
|
||||
cursor++;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
|
||||
29
src/fe-readmac.cc
Normal file
29
src/fe-readmac.cc
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "reader.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders.h"
|
||||
#include "macintosh.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "image.h"
|
||||
#include "record.h"
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
|
||||
static StringFlag outputFilename(
|
||||
{ "--output", "-o" },
|
||||
"The output image file to write to.",
|
||||
"mac.img");
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
setReaderDefaultSource(":t=0-79:s=0-1");
|
||||
setReaderRevolutions(2);
|
||||
Flag::parseFlags(argc, argv);
|
||||
|
||||
MacintoshDecoder decoder;
|
||||
readDiskCommand(decoder, outputFilename);
|
||||
|
||||
return 0;
|
||||
}
|
||||
15
tests/bitaccumulator.cc
Normal file
15
tests/bitaccumulator.cc
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "globals.h"
|
||||
#include "bytes.h"
|
||||
#include <assert.h>
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
BitAccumulator ba;
|
||||
|
||||
ba.reset();
|
||||
ba.push(0x1e, 5);
|
||||
assert((std::vector<uint8_t>)ba == std::vector<uint8_t>{ 0x1e });
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user