Add apple2 encoder

This is tested with encodedecodetest.sh but is not tested on HW yet.
It's likely that the sector order (interleave) doesn't match real systems.
This commit is contained in:
Jeff Epler
2022-02-28 11:37:02 -06:00
committed by Jeff Epler
parent 2b7c747209
commit 87cb4b6d18
8 changed files with 225 additions and 0 deletions

7
.gitignore vendored
View File

@@ -1,2 +1,9 @@
.obj
.project
/.ninja*
/brother120tool
/brother240tool
/fluxengine
/brother120tool-*
/brother240tool-*
/fluxengine-*

View File

@@ -1,6 +1,10 @@
#ifndef APPLE2_H
#define APPLE2_H
#include <memory.h>
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#define APPLE2_SECTOR_RECORD 0xd5aa96
#define APPLE2_DATA_RECORD 0xd5aaad
@@ -8,6 +12,7 @@
#define APPLE2_ENCODED_SECTOR_LENGTH 342
extern std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config);
extern std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config);
#endif

View File

@@ -2,3 +2,4 @@ syntax = "proto2";
message Apple2DecoderProto {}
message Apple2EncoderProto {}

187
arch/apple2/encoder.cc Normal file
View File

@@ -0,0 +1,187 @@
#include "globals.h"
#include "arch/apple2/apple2.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "sector.h"
#include "writer.h"
#include "image.h"
#include "fmt/format.h"
#include "lib/encoders/encoders.pb.h"
#include <ctype.h>
#include "bytes.h"
static int encode_data_gcr(uint8_t data) {
switch(data)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
class Apple2Encoder : public AbstractEncoder
{
public:
Apple2Encoder(const EncoderProto& config):
AbstractEncoder(config)
{}
public:
std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
constexpr auto numSectors = 16;
if (physicalSide == 0)
{
int logicalTrack = physicalTrack / 2;
unsigned numSectors = 16;
for (int sectorId=0; sectorId<numSectors; sectorId++)
{
const auto& sector = image.get(logicalTrack, 0, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
{
if (physicalSide != 0)
return std::unique_ptr<Fluxmap>();
int logicalTrack = physicalTrack / 2;
double clockRateUs = 4.;
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
for (const auto& sector : sectors) {
if(sector) {
writeSector(bits, cursor, *sector);
}
}
if (cursor >= bits.size())
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap;
}
private:
uint8_t volume_id = 254;
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
* and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
* as well as Understanding the Apple II (1983) Chapter 9
* https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater
*/
void writeSector(std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const
{
if ((sector.status == Sector::OK) or (sector.status == Sector::BAD_CHECKSUM))
{
auto write_bit = [&](bool val) {
if(cursor <= bits.size()) { bits[cursor] = val; }
cursor++;
};
auto write_bits = [&](uint32_t bits, int width) {
for(int i=width; i--;) {
write_bit(bits & (1u << i));
}
};
auto write_gcr44 = [&](uint8_t value) {
write_bits((value << 7) | value | 0xaaaa, 16);
};
auto write_gcr6 = [&](uint8_t value) {
write_bits(encode_data_gcr(value), 8);
};
auto write_ff40 = [&]() {
write_bits(0xff0, 12);
};
auto write_ff36 = [&]() {
write_bits(0xff << 2, 10);
};
auto write_ff32 = [&]() {
write_bits(0xff, 8);
};
// There is data to encode to disk.
if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector.data.size());
// Write address syncing leader : A sequence of "FF40"s followed by an "FF32", 5 to 40 of them
// "FF40" seems to indicate that the actual data written is "1111 1111 0000" i.e., 8 1s and a total of 40 microseconds
for(int i=0; i<4; i++) { write_ff40(); }
write_ff32();
// Write address field: APPLE2_SECTOR_RECORD + sector identifier + DE AA EB
write_bits(APPLE2_SECTOR_RECORD, 24);
write_gcr44(volume_id);
write_gcr44(sector.logicalTrack);
write_gcr44(sector.logicalSector);
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
write_bits(0xDEAAEB, 24);
// Write the "zip": a gap of 50 (we actually do 52, hopefully it's OK).
// In real HW this is actually turning OFF the write head for 50 cycles
write_bits(0, 13);
// Write data syncing leader: FF40 x4 + FF36 + APPLE2_DATA_RECORD + sector data + sum + DE AA EB (+ mystery bits cut off of the scan?)
for(int i=0; i<4; i++) write_ff40();
write_ff36();
write_bits(APPLE2_DATA_RECORD, 24);
// Convert the sector data to GCR, append the checksum, and write it out
constexpr auto TWOBIT_COUNT = 0x56; // Size of the 'twobit' area at the start of the GCR data
uint8_t checksum = 0;
for(int i=0; i<APPLE2_ENCODED_SECTOR_LENGTH; i++) {
int value;
if(i >= TWOBIT_COUNT) {
value = sector.data[i - TWOBIT_COUNT] >> 2;
} else {
uint8_t tmp = sector.data[i];
value = ((tmp & 1) << 1) | ((tmp & 2) >> 1);
tmp = sector.data[i + TWOBIT_COUNT];
value |= ((tmp & 1) << 3) | ((tmp & 2) << 1);
if(i + 2*TWOBIT_COUNT < APPLE2_SECTOR_LENGTH) {
tmp = sector.data[i + 2*TWOBIT_COUNT];
value |= ((tmp & 1) << 5) | ((tmp & 2) << 3);
}
}
checksum ^= value;
// assert(checksum & ~0x3f == 0);
write_gcr6(checksum);
checksum = value;
}
if(sector.status == Sector::BAD_CHECKSUM) checksum ^= 0x3f;
write_gcr6(checksum);
write_bits(0xDEAAEB, 24);
}
}
};
std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new Apple2Encoder(config));
}

View File

@@ -3,6 +3,7 @@
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/ibm/ibm.h"
@@ -20,6 +21,7 @@ std::unique_ptr<AbstractEncoder> AbstractEncoder::create(const EncoderProto& con
std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>> encoders =
{
{ EncoderProto::kAmiga, createAmigaEncoder },
{ EncoderProto::kApple2, createApple2Encoder },
{ EncoderProto::kBrother, createBrotherEncoder },
{ EncoderProto::kC64, createCommodore64Encoder },
{ EncoderProto::kIbm, createIbmEncoder },

View File

@@ -1,6 +1,7 @@
syntax = "proto2";
import "arch/amiga/amiga.proto";
import "arch/apple2/apple2.proto";
import "arch/brother/brother.proto";
import "arch/c64/c64.proto";
import "arch/ibm/ibm.proto";
@@ -22,5 +23,6 @@ message EncoderProto {
NorthstarEncoderProto northstar = 9;
MicropolisEncoderProto micropolis = 10;
Victor9kEncoderProto victor9k = 11;
Apple2EncoderProto apple2 = 12;
}
}

View File

@@ -402,6 +402,7 @@ buildlibrary libbackend.a \
arch/amiga/decoder.cc \
arch/amiga/encoder.cc \
arch/apple2/decoder.cc \
arch/apple2/encoder.cc \
arch/brother/decoder.cc \
arch/brother/encoder.cc \
arch/c64/decoder.cc \
@@ -646,6 +647,7 @@ runtest proto-test -I$OBJDIR/proto \
$OBJDIR/proto/tests/testproto.cc
encodedecodetest amiga
encodedecodetest apple2
encodedecodetest atarist360
encodedecodetest atarist370
encodedecodetest atarist400

View File

@@ -1,5 +1,20 @@
comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD (ro)'
image_reader {
filename: "apple2.img"
img {
tracks: 40
sides: 1
trackdata {
sector_size: 256
sector_range {
start_sector: 0
sector_count: 16
}
}
}
}
image_writer {
filename: "apple2.img"
img {}
@@ -9,6 +24,10 @@ decoder {
apple2 {}
}
encoder {
apple2 {}
}
cylinders {
start: 0
end: 79