mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #27 from davidgiven/apple2
Add basic Apple 2 support.
This commit is contained in:
58
doc/apple2.md
Normal file
58
doc/apple2.md
Normal 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)
|
||||
@@ -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) | 🦄 | | |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user