mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,2 +1,9 @@
|
||||
.obj
|
||||
.project
|
||||
/.ninja*
|
||||
/brother120tool
|
||||
/brother240tool
|
||||
/fluxengine
|
||||
/brother120tool-*
|
||||
/brother240tool-*
|
||||
/fluxengine-*
|
||||
|
||||
@@ -97,7 +97,7 @@ people who've had it work).
|
||||
| [Acorn ADFS](doc/disk-acornadfs.md) | 🦄 | 🦖* | single- and double- sided |
|
||||
| [Acorn DFS](doc/disk-acorndfs.md) | 🦄 | 🦖* | |
|
||||
| [Ampro Little Board](doc/disk-ampro.md) | 🦖 | 🦖* | |
|
||||
| [Apple II DOS 3.3](doc/disk-apple2.md) | 🦄 | | doesn't do logical sector remapping |
|
||||
| [Apple II DOS 3.3](doc/disk-apple2.md) | 🦄 | 🦖 | doesn't do logical sector remapping |
|
||||
| [Amiga](doc/disk-amiga.md) | 🦄 | 🦄 | |
|
||||
| [Commodore 64 1541/1581](doc/disk-c64.md) | 🦄 | 🦄 | and probably the other formats |
|
||||
| [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦖 | |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@ syntax = "proto2";
|
||||
|
||||
message Apple2DecoderProto {}
|
||||
|
||||
message Apple2EncoderProto {}
|
||||
|
||||
187
arch/apple2/encoder.cc
Normal file
187
arch/apple2/encoder.cc
Normal 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,18 @@ 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.
|
||||
|
||||
Writing discs
|
||||
-------------
|
||||
|
||||
Just do:
|
||||
```
|
||||
fluxengine write apple2 -i apple2.img
|
||||
```
|
||||
|
||||
**Big warning!** An image designed for an emulator may not work, due to the
|
||||
logical sector mapping issue described above.
|
||||
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD (ro)'
|
||||
comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD'
|
||||
|
||||
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"
|
||||
@@ -9,6 +24,10 @@ decoder {
|
||||
apple2 {}
|
||||
}
|
||||
|
||||
encoder {
|
||||
apple2 {}
|
||||
}
|
||||
|
||||
cylinders {
|
||||
start: 0
|
||||
end: 79
|
||||
|
||||
Reference in New Issue
Block a user