Merge pull request #27 from davidgiven/apple2

Add basic Apple 2 support.
This commit is contained in:
David Given
2019-03-02 14:06:02 +01:00
committed by GitHub
6 changed files with 93 additions and 103 deletions

58
doc/apple2.md Normal file
View File

@@ -0,0 +1,58 @@
Apple II disks
==============
Apple II disks are nominally fairly sensible 40-track, single-sided, 256
bytes-per-sector jobs. However, they come in two varieties: DOS 3.3 and
above, and pre-DOS 3.3. They use different GCR encoding systems, dubbed
6-and-2 and 5-and-3, and are mutually incompatible (although in some rare
cases you can mix 6-and-2 and 5-and-3 sectors on the same disk).
The difference is in the drive controller; the 6-and-2 controller is capable
of a more efficient encoding, and can fit 16 sectors on a track, storing
140kB on a disk. The 5-and-3 controller can only fit 13, with a mere 114kB.
Both formats use GCR (in different varieties) in a nice, simple grid of
sectors, unlike the Macintosh. Like the Macintosh, there's a crazy encoding
scheme applied to the data before it goes down on disk to speed up
checksumming.
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.
In addition, a lot of the behaviour of the drive was handled in software.
This means that Apple II disks can do all kinds of weird things, including
having spiral tracks! Copy protection for the Apple II was even madder than
on other systems.
FluxEngine can only read well-behaved, DOS 3.3 6-and-2 disks. It doesn't even
try to handle the weird stuff.
Sadly, DOS 3.3 also applies logical sector remapping on top of the physical
sector numbering on the disk, and this _varies_ depending on what the disk is
for. FluxEngine doesn't attempt to remap sectors, instead giving you an exact
copy of what's on the disk, so you may need to do some work before the images
are usable in emulators.
Reading discs
-------------
Just do:
```
.obj/fe-readapple2
```
You should end up with an `apple2.img` which is 143360 bytes long.
**Big warning!** The image may not work in an emulator, due to the
logical sector mapping issue described above.
Useful references
-----------------
- [Beneath Apple DOS](https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf)
- [MAME's ap2_dsk.cpp file](https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap2_dsk.cpp)

View File

@@ -85,6 +85,7 @@ Here's the table.
| [Acorn ADFS](acornadfs.html) | 🦖 | | single- and double- sided |
| [Acorn DFS](acorndfs.html) | 🦄 | | |
| [Ampro Little Board](ampro.html) | 🦖 | | |
| [Apple II DOS 3.3](apple2.html) | 🦖 | | doesn't do logical sector remapping |
| [Commodore Amiga](amiga.html) | 🦖 | | |
| [Commodore 64 1541](c64.html) | 🦖 | | and probably the other GCR formats |
| [Brother 120kB](brother.html) | 🦄 | | |

View File

@@ -53,8 +53,7 @@ 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),
- [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

View File

@@ -1,10 +1,11 @@
#ifndef APPLE2_H
#define APPLE2_H
#define MAC_SECTOR_RECORD 0xd5aa96
#define MAC_DATA_RECORD 0xd5aaad
#define APPLE2_SECTOR_RECORD 0xd5aa96
#define APPLE2_DATA_RECORD 0xd5aaad
#define MAC_SECTOR_LENGTH 524 /* yes, really */
#define APPLE2_SECTOR_LENGTH 256
#define APPLE2_ENCODED_SECTOR_LENGTH 342
class Sector;
class Fluxmap;

View File

@@ -23,99 +23,38 @@ static int decode_data_gcr(uint8_t gcr)
};
/* 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
* and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
*/
static std::vector<uint8_t> decode_crazy_data(const uint8_t* inp, int& status)
{
std::vector<uint8_t> output;
std::vector<uint8_t> output(APPLE2_SECTOR_LENGTH);
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 checksum = 0;
for (unsigned i = 0; i < APPLE2_ENCODED_SECTOR_LENGTH; i++)
{
uint8_t w4 = *inp++;
uint8_t w1 = *inp++;
uint8_t w2 = *inp++;
uint8_t w3 = (i != 174) ? *inp++ : 0;
checksum ^= decode_data_gcr(*inp++);
b1[i] = (w1 & 0x3F) | ((w4 << 2) & 0xC0);
b2[i] = (w2 & 0x3F) | ((w4 << 4) & 0xC0);
b3[i] = (w3 & 0x3F) | ((w4 << 6) & 0xC0);
if (i >= 86)
{
/* 6 bit */
output[i - 86] |= (checksum << 2);
}
else
{
/* 3 * 2 bit */
output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02);
output[i + 86] = ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02);
if ((i + 172) < APPLE2_SECTOR_LENGTH)
output[i + 172] = ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02);
}
}
/* 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;
checksum &= 0x3f;
uint8_t wantedchecksum = decode_data_gcr(*inp);
status = (checksum == wantedchecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
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);
}
uint8_t combine(uint16_t word)
{
return word & (word >> 7);
@@ -140,7 +79,7 @@ SectorVector Apple2Decoder::decodeToSectors(
uint32_t signature = read_be24(&rawbytes[0]);
switch (signature)
{
case MAC_SECTOR_RECORD:
case APPLE2_SECTOR_RECORD:
{
uint8_t volume = combine(read_be16(&rawbytes[3]));
nextTrack = combine(read_be16(&rawbytes[5]));
@@ -150,24 +89,16 @@ SectorVector Apple2Decoder::decodeToSectors(
break;
}
case MAC_DATA_RECORD:
case APPLE2_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);
}
std::vector<uint8_t> clippedbytes(rawbytes);
clippedbytes.resize(APPLE2_ENCODED_SECTOR_LENGTH + 5);
int status = Sector::BAD_CHECKSUM;
auto data = decode_crazy_data(inputbuffer, status);
auto data = decode_crazy_data(&clippedbytes[3], status);
auto sector = std::unique_ptr<Sector>(
new Sector(status, nextTrack, 0, nextSector, data));
@@ -183,7 +114,7 @@ SectorVector Apple2Decoder::decodeToSectors(
int Apple2Decoder::recordMatcher(uint64_t fifo) const
{
uint32_t masked = fifo & 0xffffff;
if ((masked == MAC_SECTOR_RECORD) || (masked == MAC_DATA_RECORD))
if ((masked == APPLE2_SECTOR_RECORD) || (masked == APPLE2_DATA_RECORD))
return 24;
return 0;
}

View File

@@ -14,11 +14,11 @@
static StringFlag outputFilename(
{ "--output", "-o" },
"The output image file to write to.",
"mac.img");
"apple2.img");
int main(int argc, const char* argv[])
{
setReaderDefaultSource(":t=0-79:s=0-1");
setReaderDefaultSource(":t=0-79:s=0");
setReaderRevolutions(2);
Flag::parseFlags(argc, argv);