mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 23:52:26 +00:00
commit
9de43dac95
@ -18,11 +18,21 @@ using namespace Storage::Disk;
|
|||||||
WOZ::WOZ(const std::string &file_name) :
|
WOZ::WOZ(const std::string &file_name) :
|
||||||
file_(file_name) {
|
file_(file_name) {
|
||||||
|
|
||||||
const char signature[8] = {
|
constexpr const char signature1[8] = {
|
||||||
'W', 'O', 'Z', '1',
|
'W', 'O', 'Z', '1',
|
||||||
char(0xff), 0x0a, 0x0d, 0x0a
|
char(0xff), 0x0a, 0x0d, 0x0a
|
||||||
};
|
};
|
||||||
if(!file_.check_signature(signature, 8)) throw Error::InvalidFormat;
|
constexpr const char signature2[8] = {
|
||||||
|
'W', 'O', 'Z', '2',
|
||||||
|
char(0xff), 0x0a, 0x0d, 0x0a
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool isWoz1 = file_.check_signature(signature1, 8);
|
||||||
|
file_.seek(0, SEEK_SET);
|
||||||
|
const bool isWoz2 = file_.check_signature(signature2, 8);
|
||||||
|
|
||||||
|
if(!isWoz1 && !isWoz2) throw Error::InvalidFormat;
|
||||||
|
type_ = isWoz2 ? Type::WOZ2 : Type::WOZ1;
|
||||||
|
|
||||||
// Get the file's CRC32.
|
// Get the file's CRC32.
|
||||||
const uint32_t crc = file_.get32le();
|
const uint32_t crc = file_.get32le();
|
||||||
@ -52,13 +62,22 @@ WOZ::WOZ(const std::string &file_name) :
|
|||||||
switch(chunk_id) {
|
switch(chunk_id) {
|
||||||
case CK("INFO"): {
|
case CK("INFO"): {
|
||||||
const uint8_t version = file_.get8();
|
const uint8_t version = file_.get8();
|
||||||
if(version != 1) break;
|
if(version > 2) break;
|
||||||
is_3_5_disk_ = file_.get8() == 2;
|
is_3_5_disk_ = file_.get8() == 2;
|
||||||
is_read_only_ = file_.get8() == 1;
|
is_read_only_ = file_.get8() == 1;
|
||||||
/* Ignored:
|
/*
|
||||||
|
Ignored:
|
||||||
1 byte: Synchronized; 1 = Cross track sync was used during imaging.
|
1 byte: Synchronized; 1 = Cross track sync was used during imaging.
|
||||||
1 byte: Cleaned; 1 = MC3470 fake bits have been removed.
|
1 byte: Cleaned; 1 = MC3470 fake bits have been removed.
|
||||||
32 bytes: Cretor; a UTF-8 string.
|
32 bytes: Creator; a UTF-8 string.
|
||||||
|
|
||||||
|
And, if version 2, following the creator:
|
||||||
|
1 byte number of disk sides
|
||||||
|
1 byte boot sector format
|
||||||
|
1 byte optimal bit timing
|
||||||
|
2 bytes compatible hardware
|
||||||
|
2 bytes minimum required RAM
|
||||||
|
2 bytes largest track
|
||||||
*/
|
*/
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
@ -99,11 +118,15 @@ long WOZ::file_offset(Track::Address address) {
|
|||||||
if(track_map_[table_position] == 0xff) return NoSuchTrack;
|
if(track_map_[table_position] == 0xff) return NoSuchTrack;
|
||||||
|
|
||||||
// Seek to the real track.
|
// Seek to the real track.
|
||||||
return tracks_offset_ + track_map_[table_position] * 6656;
|
switch(type_) {
|
||||||
|
case Type::WOZ1: return tracks_offset_ + track_map_[table_position] * 6656;
|
||||||
|
default:
|
||||||
|
case Type::WOZ2: return tracks_offset_ + track_map_[table_position] * 8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
|
std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
|
||||||
long offset = file_offset(address);
|
const long offset = file_offset(address);
|
||||||
if(offset == NoSuchTrack) return nullptr;
|
if(offset == NoSuchTrack) return nullptr;
|
||||||
|
|
||||||
// Seek to the real track.
|
// Seek to the real track.
|
||||||
@ -113,18 +136,34 @@ std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
|
|||||||
std::lock_guard lock_guard(file_.get_file_access_mutex());
|
std::lock_guard lock_guard(file_.get_file_access_mutex());
|
||||||
file_.seek(offset, SEEK_SET);
|
file_.seek(offset, SEEK_SET);
|
||||||
|
|
||||||
// In WOZ a track is up to 6646 bytes of data, followed by a two-byte record of the
|
switch(type_) {
|
||||||
|
case Type::WOZ1:
|
||||||
|
// In WOZ 1, a track is up to 6646 bytes of data, followed by a two-byte record of the
|
||||||
// number of bytes that actually had data in them, then a two-byte count of the number
|
// number of bytes that actually had data in them, then a two-byte count of the number
|
||||||
// of bits that were used. Other information follows but is not intended for emulation.
|
// of bits that were used. Other information follows but is not intended for emulation.
|
||||||
track_contents = file_.read(6646);
|
track_contents = file_.read(6646);
|
||||||
file_.seek(2, SEEK_CUR);
|
file_.seek(2, SEEK_CUR);
|
||||||
number_of_bits = std::min(file_.get16le(), uint16_t(6646*8));
|
number_of_bits = std::min(file_.get16le(), uint16_t(6646*8));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::WOZ2: {
|
||||||
|
// In WOZ 2 an extra level of indirection allows for variable track sizes.
|
||||||
|
const uint16_t starting_block = file_.get16le();
|
||||||
|
file_.seek(2, SEEK_CUR); // Skip the block count; the amount of data to read is implied by the number of bits.
|
||||||
|
number_of_bits = file_.get32le();
|
||||||
|
|
||||||
|
file_.seek(starting_block * 512, SEEK_SET);
|
||||||
|
track_contents = file_.read((number_of_bits + 7) >> 3);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_shared<PCMTrack>(PCMSegment(number_of_bits, track_contents));
|
return std::make_shared<PCMTrack>(PCMSegment(number_of_bits, track_contents));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
|
void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
|
||||||
|
if(type_ == Type::WOZ2) return;
|
||||||
|
|
||||||
for(const auto &pair: tracks) {
|
for(const auto &pair: tracks) {
|
||||||
// Decode the track and store, patching into the post_crc_contents_.
|
// Decode the track and store, patching into the post_crc_contents_.
|
||||||
auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000));
|
auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000));
|
||||||
@ -155,5 +194,5 @@ void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tra
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WOZ::get_is_read_only() {
|
bool WOZ::get_is_read_only() {
|
||||||
return file_.get_is_known_read_only();
|
return file_.get_is_known_read_only() || is_read_only_ || type_ == Type::WOZ2; // WOZ 2 disks are currently read only.
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,9 @@ class WOZ: public DiskImage {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Storage::FileHolder file_;
|
Storage::FileHolder file_;
|
||||||
|
enum class Type {
|
||||||
|
WOZ1, WOZ2
|
||||||
|
} type_ = Type::WOZ1;
|
||||||
bool is_read_only_ = false;
|
bool is_read_only_ = false;
|
||||||
bool is_3_5_disk_ = false;
|
bool is_3_5_disk_ = false;
|
||||||
uint8_t track_map_[160];
|
uint8_t track_map_[160];
|
||||||
@ -49,7 +52,7 @@ class WOZ: public DiskImage {
|
|||||||
the track does not exit.
|
the track does not exit.
|
||||||
*/
|
*/
|
||||||
long file_offset(Track::Address address);
|
long file_offset(Track::Address address);
|
||||||
constexpr static long NoSuchTrack = 0; // This is definitely an offset a track can't lie at.
|
constexpr static long NoSuchTrack = 0; // This is an offset a track definitely can't lie at.
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -250,6 +250,15 @@ void Drive::run_for(const Cycles cycles) {
|
|||||||
// MARK: - Track timed event loop
|
// MARK: - Track timed event loop
|
||||||
|
|
||||||
void Drive::get_next_event(float duration_already_passed) {
|
void Drive::get_next_event(float duration_already_passed) {
|
||||||
|
/*
|
||||||
|
Quick word on random-bit generation logic below; it seeks to obey the following logic:
|
||||||
|
if there is a gap of 15µs between recorded bits, start generating flux transitions
|
||||||
|
at random intervals thereafter, unless and until one is within 5µs of the next real transition.
|
||||||
|
|
||||||
|
This behaviour is based on John Morris' observations of an MC3470, as described in his WOZ
|
||||||
|
file format documentation — https://applesaucefdc.com/woz/reference2/
|
||||||
|
*/
|
||||||
|
|
||||||
if(!disk_) {
|
if(!disk_) {
|
||||||
current_event_.type = Track::Event::IndexHole;
|
current_event_.type = Track::Event::IndexHole;
|
||||||
current_event_.length = 1.0f;
|
current_event_.length = 1.0f;
|
||||||
@ -268,18 +277,19 @@ void Drive::get_next_event(float duration_already_passed) {
|
|||||||
// If gain has now been turned up so as to generate noise, generate some noise.
|
// If gain has now been turned up so as to generate noise, generate some noise.
|
||||||
if(random_interval_ > 0.0f) {
|
if(random_interval_ > 0.0f) {
|
||||||
current_event_.type = Track::Event::FluxTransition;
|
current_event_.type = Track::Event::FluxTransition;
|
||||||
current_event_.length = float(2 + (random_source_&1)) / 1000000.0f;
|
current_event_.length = float(2 + (random_source_&1)) / 1'000'000.0f;
|
||||||
random_source_ = (random_source_ >> 1) | (random_source_ << 63);
|
random_source_ = (random_source_ >> 1) | (random_source_ << 63);
|
||||||
|
|
||||||
if(random_interval_ < current_event_.length) {
|
// If this random transition is closer than 5µs to the next real bit,
|
||||||
current_event_.length = random_interval_;
|
// discard it.
|
||||||
|
if(random_interval_ - 5.0f / 1'000'000.f < current_event_.length) {
|
||||||
random_interval_ = 0.0f;
|
random_interval_ = 0.0f;
|
||||||
} else {
|
} else {
|
||||||
random_interval_ -= current_event_.length;
|
random_interval_ -= current_event_.length;
|
||||||
}
|
|
||||||
set_next_event_time_interval(current_event_.length);
|
set_next_event_time_interval(current_event_.length);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(track_) {
|
if(track_) {
|
||||||
const auto track_event = track_->get_next_event();
|
const auto track_event = track_->get_next_event();
|
||||||
@ -299,9 +309,9 @@ void Drive::get_next_event(float duration_already_passed) {
|
|||||||
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
|
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
|
||||||
float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f);
|
float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f);
|
||||||
|
|
||||||
// An interval greater than 15ms => adjust gain up the point where noise starts happening.
|
// An interval greater than 15µs => adjust gain up the point where noise starts happening.
|
||||||
// Seed that up and leave a 15ms gap until it starts.
|
// Seed that up and leave a 15µs gap until it starts.
|
||||||
constexpr float safe_gain_period = 15.0f / 1000000.0f;
|
constexpr float safe_gain_period = 15.0f / 1'000'000.0f;
|
||||||
if(interval >= safe_gain_period) {
|
if(interval >= safe_gain_period) {
|
||||||
random_interval_ = interval - safe_gain_period;
|
random_interval_ = interval - safe_gain_period;
|
||||||
interval = safe_gain_period;
|
interval = safe_gain_period;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
|
using namespace Storage::Encodings::AppleGCR;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const uint8_t six_and_two_unmapping[] = {
|
const uint8_t six_and_two_unmapping[] = {
|
||||||
@ -55,9 +57,181 @@ uint8_t unmap_five_and_three(uint8_t source) {
|
|||||||
return five_and_three_unmapping[source - 0xab];
|
return five_and_three_unmapping[source - 0xab];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Sector> decode_macintosh_sector(const std::array<uint_fast8_t, 8> &header, const std::unique_ptr<Sector> &original) {
|
||||||
|
// There must be at least 704 bytes to decode from.
|
||||||
|
if(original->data.size() < 704) return nullptr;
|
||||||
|
|
||||||
|
// Attempt a six-and-two unmapping of the header.
|
||||||
|
std::array<uint_fast8_t, 5> decoded_header;
|
||||||
|
for(size_t c = 0; c < decoded_header.size(); ++c) {
|
||||||
|
decoded_header[c] = unmap_six_and_two(header[c]);
|
||||||
|
if(decoded_header[c] == 0xff) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace Storage::Encodings::AppleGCR;
|
// Allocate a sector.
|
||||||
|
auto sector = std::make_unique<Sector>();
|
||||||
|
sector->data.resize(704);
|
||||||
|
|
||||||
|
// Test the checksum.
|
||||||
|
if(decoded_header[4] != (decoded_header[0] ^ decoded_header[1] ^ decoded_header[2] ^ decoded_header[3]))
|
||||||
|
sector->has_header_checksum_error = true;
|
||||||
|
|
||||||
|
// Decode the header.
|
||||||
|
sector->address.track = uint8_t(decoded_header[0] | ((decoded_header[2]&0x1f) << 6));
|
||||||
|
sector->address.sector = decoded_header[1];
|
||||||
|
sector->address.format = decoded_header[3];
|
||||||
|
sector->address.is_side_two = decoded_header[2] & 0x20;
|
||||||
|
|
||||||
|
// Reverse the GCR encoding of the sector contents to get back to 6-bit data.
|
||||||
|
for(size_t index = 0; index < sector->data.size(); ++index) {
|
||||||
|
sector->data[index] = unmap_six_and_two(original->data[index]);
|
||||||
|
if(sector->data[index] == 0xff) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first byte in the sector is a repeat of the sector number; test it
|
||||||
|
// for correctness.
|
||||||
|
if(sector->data[0] != sector->address.sector) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cf. the corresponding section of Encoder.cpp for logic below.
|
||||||
|
int checksum[3] = {0, 0, 0};
|
||||||
|
for(size_t c = 0; c < 175; ++c) {
|
||||||
|
// Calculate the rolling checcksum in order to decode the bytes.
|
||||||
|
checksum[0] = (checksum[0] << 1) | (checksum[0] >> 7);
|
||||||
|
|
||||||
|
// All offsets are +1 below, to skip the initial sector number duplicate.
|
||||||
|
const uint8_t top_bits = sector->data[1 + c*4];
|
||||||
|
|
||||||
|
// Decode first byte.
|
||||||
|
sector->data[0 + c * 3] = uint8_t((sector->data[2 + c*4] + ((top_bits & 0x30) << 2)) ^ checksum[0]);
|
||||||
|
checksum[2] += sector->data[0 + c * 3] + (checksum[0] >> 8);
|
||||||
|
|
||||||
|
// Decode second byte;
|
||||||
|
sector->data[1 + c * 3] = uint8_t((sector->data[3 + c*4] + ((top_bits & 0x0c) << 4)) ^ checksum[2]);
|
||||||
|
checksum[1] += sector->data[1 + c * 3] + (checksum[2] >> 8);
|
||||||
|
|
||||||
|
// Decode third byte, if there is one.
|
||||||
|
if(c != 174) {
|
||||||
|
sector->data[2 + c * 3] = uint8_t((sector->data[4 + c*4] + ((top_bits & 0x03) << 6)) ^ checksum[1]);
|
||||||
|
checksum[0] += sector->data[2 + c * 3] + (checksum[1] >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset carries.
|
||||||
|
checksum[0] &= 0xff;
|
||||||
|
checksum[1] &= 0xff;
|
||||||
|
checksum[2] &= 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the checksum.
|
||||||
|
if(
|
||||||
|
checksum[0] != uint8_t(sector->data[703] + ((sector->data[700] & 0x03) << 6)) ||
|
||||||
|
checksum[1] != uint8_t(sector->data[702] + ((sector->data[700] & 0x0c) << 4)) ||
|
||||||
|
checksum[2] != uint8_t(sector->data[701] + ((sector->data[700] & 0x30) << 2))
|
||||||
|
) sector->has_data_checksum_error = true;
|
||||||
|
|
||||||
|
// Report success.
|
||||||
|
sector->data.resize(524);
|
||||||
|
sector->encoding = Sector::Encoding::Macintosh;
|
||||||
|
return sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Sector> decode_appleii_sector(const std::array<uint_fast8_t, 8> &header, const std::unique_ptr<Sector> &original, bool is_five_and_three) {
|
||||||
|
// There must be at least 411 bytes to decode a five-and-three sector from;
|
||||||
|
// there must be only 343 if this is a six-and-two sector.
|
||||||
|
const size_t data_size = is_five_and_three ? 411 : 343;
|
||||||
|
if(original->data.size() < data_size) return nullptr;
|
||||||
|
|
||||||
|
// Check for apparent four and four encoding.
|
||||||
|
uint_fast8_t header_mask = 0xff;
|
||||||
|
for(auto c : header) header_mask &= c;
|
||||||
|
header_mask &= 0xaa;
|
||||||
|
if(header_mask != 0xaa) return nullptr;
|
||||||
|
|
||||||
|
// Allocate a sector and fill the header fields.
|
||||||
|
auto sector = std::make_unique<Sector>();
|
||||||
|
sector->data.resize(data_size);
|
||||||
|
|
||||||
|
sector->address.volume = ((header[0] << 1) | 1) & header[1];
|
||||||
|
sector->address.track = ((header[2] << 1) | 1) & header[3];
|
||||||
|
sector->address.sector = ((header[4] << 1) | 1) & header[5];
|
||||||
|
|
||||||
|
// Check the header checksum.
|
||||||
|
const uint_fast8_t checksum = ((header[6] << 1) | 1) & header[7];
|
||||||
|
if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) return nullptr;
|
||||||
|
|
||||||
|
// Unmap the sector contents.
|
||||||
|
for(size_t index = 0; index < data_size; ++index) {
|
||||||
|
sector->data[index] = is_five_and_three ? unmap_five_and_three(original->data[index]) : unmap_six_and_two(original->data[index]);
|
||||||
|
if(sector->data[index] == 0xff) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo the XOR step on sector contents and check that checksum.
|
||||||
|
for(std::size_t c = 1; c < sector->data.size(); ++c) {
|
||||||
|
sector->data[c] ^= sector->data[c-1];
|
||||||
|
}
|
||||||
|
if(sector->data.back()) return nullptr;
|
||||||
|
|
||||||
|
// Having checked the checksum, remove it.
|
||||||
|
sector->data.resize(sector->data.size() - 1);
|
||||||
|
|
||||||
|
if(is_five_and_three) {
|
||||||
|
// TODO: the below is almost certainly incorrect; Beneath Apple DOS partly documents
|
||||||
|
// the process, enough to give the basic outline below of how five source bytes are
|
||||||
|
// mapped to eight five-bit quantities, but isn't clear on the order those bytes will
|
||||||
|
// end up in on disk.
|
||||||
|
|
||||||
|
std::vector<uint8_t> buffer(256);
|
||||||
|
for(size_t c = 0; c < 0x33; ++c) {
|
||||||
|
const uint8_t *const base = §or->data[0x032 - c];
|
||||||
|
|
||||||
|
buffer[(c * 5) + 0] = uint8_t((base[0x000] << 3) | (base[0x100] >> 2));
|
||||||
|
buffer[(c * 5) + 1] = uint8_t((base[0x033] << 3) | (base[0x133] >> 2));
|
||||||
|
buffer[(c * 5) + 2] = uint8_t((base[0x066] << 3) | (base[0x166] >> 2));
|
||||||
|
buffer[(c * 5) + 3] = uint8_t((base[0x099] << 3) | ((base[0x100] & 2) << 1) | (base[0x133] & 2) | ((base[0x166] & 2) >> 1));
|
||||||
|
buffer[(c * 5) + 4] = uint8_t((base[0x0cc] << 3) | ((base[0x100] & 1) << 2) | ((base[0x133] & 1) << 1) | (base[0x166] & 1));
|
||||||
|
}
|
||||||
|
buffer[255] = uint8_t((sector->data[0x0ff] << 3) | (sector->data[0x199] >> 2));
|
||||||
|
|
||||||
|
sector->data = std::move(buffer);
|
||||||
|
sector->encoding = Sector::Encoding::FiveAndThree;
|
||||||
|
} else {
|
||||||
|
// Undo the 6 and 2 mapping.
|
||||||
|
constexpr uint8_t bit_reverse[] = {0, 2, 1, 3};
|
||||||
|
#define unmap(byte, nibble, shift) \
|
||||||
|
sector->data[86 + byte] = uint8_t(\
|
||||||
|
(sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]);
|
||||||
|
|
||||||
|
for(std::size_t c = 0; c < 84; ++c) {
|
||||||
|
unmap(c, c, 0);
|
||||||
|
unmap(c+86, c, 2);
|
||||||
|
unmap(c+172, c, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
unmap(84, 84, 0);
|
||||||
|
unmap(170, 84, 2);
|
||||||
|
unmap(85, 85, 0);
|
||||||
|
unmap(171, 85, 2);
|
||||||
|
|
||||||
|
#undef unmap
|
||||||
|
|
||||||
|
// Throw away the collection of two-bit chunks from the start of the sector.
|
||||||
|
sector->data.erase(sector->data.begin(), sector->data.end() - 256);
|
||||||
|
|
||||||
|
sector->encoding = Sector::Encoding::SixAndTwo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return successfully.
|
||||||
|
return sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment(const Disk::PCMSegment &segment) {
|
std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment(const Disk::PCMSegment &segment) {
|
||||||
std::map<std::size_t, Sector> result;
|
std::map<std::size_t, Sector> result;
|
||||||
@ -112,7 +286,7 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
|
|||||||
if(scanner[2] == data_prologue[2]) {
|
if(scanner[2] == data_prologue[2]) {
|
||||||
new_sector = std::make_unique<Sector>();
|
new_sector = std::make_unique<Sector>();
|
||||||
new_sector->data.reserve(710);
|
new_sector->data.reserve(710);
|
||||||
} else {
|
} else { // i.e. the third symbol is from either of the header prologues.
|
||||||
sector_location = size_t(bit % segment.data.size());
|
sector_location = size_t(bit % segment.data.size());
|
||||||
header_delay = 200; // Allow up to 200 bytes to find the body, if the
|
header_delay = 200; // Allow up to 200 bytes to find the body, if the
|
||||||
// track split comes in between.
|
// track split comes in between.
|
||||||
@ -120,23 +294,12 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(new_sector) {
|
if(new_sector) {
|
||||||
// Check whether the value just read is a legal GCR byte, in six-and-two
|
// Check whether the value just read is a legal GCR byte, for this sector;
|
||||||
// encoding (which is a strict superset of five-and-three).
|
// if not, or if
|
||||||
const bool is_invalid = is_five_and_three ? (unmap_five_and_three(value) == 0xff) : (unmap_six_and_two(value) == 0xff);
|
const bool is_invalid = is_five_and_three ? (unmap_five_and_three(value) == 0xff) : (unmap_six_and_two(value) == 0xff);
|
||||||
if(is_invalid) {
|
if(is_invalid || new_sector->data.size() >= 704) {
|
||||||
// The second byte of the standard epilogue is illegal, so this still may
|
// The second byte of the standard epilogue is 'illegal', as is the first byte of
|
||||||
// be a valid sector. If the final byte was the first byte of an epilogue,
|
// all prologues. So either a whole sector has been captured up to now, or it hasn't.
|
||||||
// chop it off and see whether the sector is otherwise intelligible.
|
|
||||||
|
|
||||||
if(new_sector->data.empty() || new_sector->data.back() != epilogue[0]) {
|
|
||||||
// No sector found; reset scanning procedure.
|
|
||||||
new_sector.reset();
|
|
||||||
pointer = scanning_sentinel;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chop off the last byte.
|
|
||||||
new_sector->data.resize(new_sector->data.size() - 1);
|
|
||||||
|
|
||||||
// Move the sector elsewhere for processing; there's definitely no way to proceed with
|
// Move the sector elsewhere for processing; there's definitely no way to proceed with
|
||||||
// the prospective sector if it doesn't parse.
|
// the prospective sector if it doesn't parse.
|
||||||
@ -144,185 +307,19 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
|
|||||||
new_sector.reset();
|
new_sector.reset();
|
||||||
pointer = scanning_sentinel;
|
pointer = scanning_sentinel;
|
||||||
|
|
||||||
// Check for valid decoding options.
|
// Potentially this is a Macintosh sector.
|
||||||
switch(sector->data.size()) {
|
auto macintosh_sector = decode_macintosh_sector(header, sector);
|
||||||
default: // This is not a decodeable sector.
|
if(macintosh_sector) {
|
||||||
break;
|
result.insert(std::make_pair(sector_location, std::move(*macintosh_sector)));
|
||||||
|
|
||||||
case 411: // Potentially this is an Apple II five-and-three sector.
|
|
||||||
case 343: { // Potentially this is an Apple II six-and-two sector.
|
|
||||||
// Check for apparent four and four encoding.
|
|
||||||
uint_fast8_t header_mask = 0xff;
|
|
||||||
for(auto c : header) header_mask &= c;
|
|
||||||
header_mask &= 0xaa;
|
|
||||||
if(header_mask != 0xaa) continue;
|
|
||||||
|
|
||||||
sector->address.volume = ((header[0] << 1) | 1) & header[1];
|
|
||||||
sector->address.track = ((header[2] << 1) | 1) & header[3];
|
|
||||||
sector->address.sector = ((header[4] << 1) | 1) & header[5];
|
|
||||||
|
|
||||||
// Check the header checksum.
|
|
||||||
// The 0x11 is reverse engineered from the game 'Alien Rain' and is present even on the boot sector,
|
|
||||||
// so probably isn't copy protection?
|
|
||||||
uint_fast8_t checksum = (((header[6] << 1) | 1) & header[7]) ^ (is_five_and_three ? 0x11 : 0x00);
|
|
||||||
if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) continue;
|
|
||||||
|
|
||||||
// Unmap the sector contents.
|
|
||||||
bool out_of_bounds = false;
|
|
||||||
for(auto &c : sector->data) {
|
|
||||||
c = is_five_and_three ? unmap_five_and_three(c) : unmap_six_and_two(c);
|
|
||||||
if(c == 0xff) {
|
|
||||||
out_of_bounds = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(out_of_bounds) continue;
|
|
||||||
|
|
||||||
// Undo the XOR step on sector contents and check that checksum.
|
|
||||||
for(std::size_t c = 1; c < sector->data.size(); ++c) {
|
|
||||||
sector->data[c] ^= sector->data[c-1];
|
|
||||||
}
|
|
||||||
if(sector->data.back()) continue;
|
|
||||||
|
|
||||||
// Having checked the checksum, remove it.
|
|
||||||
sector->data.resize(sector->data.size() - 1);
|
|
||||||
|
|
||||||
if(is_five_and_three) {
|
|
||||||
// TODO: the above is almost certainly incorrect; Beneath Apple DOS partly documents
|
|
||||||
// the process, enough to give the basic outline below of how five source bytes are
|
|
||||||
// mapped to eight five-bit quantities, but isn't clear on the order those bytes will
|
|
||||||
// end up in on disk.
|
|
||||||
|
|
||||||
std::vector<uint8_t> buffer(256);
|
|
||||||
for(size_t c = 0; c < 0x33; ++c) {
|
|
||||||
const uint8_t *const base = §or->data[0x032 - c];
|
|
||||||
|
|
||||||
buffer[(c * 5) + 0] = uint8_t((base[0x000] << 3) | (base[0x100] >> 2));
|
|
||||||
buffer[(c * 5) + 1] = uint8_t((base[0x033] << 3) | (base[0x133] >> 2));
|
|
||||||
buffer[(c * 5) + 2] = uint8_t((base[0x066] << 3) | (base[0x166] >> 2));
|
|
||||||
buffer[(c * 5) + 3] = uint8_t((base[0x099] << 3) | ((base[0x100] & 2) << 1) | (base[0x133] & 2) | ((base[0x166] & 2) >> 1));
|
|
||||||
buffer[(c * 5) + 4] = uint8_t((base[0x0cc] << 3) | ((base[0x100] & 1) << 2) | ((base[0x133] & 1) << 1) | (base[0x166] & 1));
|
|
||||||
}
|
|
||||||
buffer[255] = uint8_t((sector->data[0x0ff] << 3) | (sector->data[0x199] >> 2));
|
|
||||||
|
|
||||||
sector->data = std::move(buffer);
|
|
||||||
sector->encoding = Sector::Encoding::FiveAndThree;
|
|
||||||
} else {
|
|
||||||
// Undo the 6 and 2 mapping.
|
|
||||||
const uint8_t bit_reverse[] = {0, 2, 1, 3};
|
|
||||||
#define unmap(byte, nibble, shift) \
|
|
||||||
sector->data[86 + byte] = uint8_t(\
|
|
||||||
(sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]);
|
|
||||||
|
|
||||||
for(std::size_t c = 0; c < 84; ++c) {
|
|
||||||
unmap(c, c, 0);
|
|
||||||
unmap(c+86, c, 2);
|
|
||||||
unmap(c+172, c, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
unmap(84, 84, 0);
|
|
||||||
unmap(170, 84, 2);
|
|
||||||
unmap(85, 85, 0);
|
|
||||||
unmap(171, 85, 2);
|
|
||||||
|
|
||||||
#undef unmap
|
|
||||||
|
|
||||||
// Throw away the collection of two-bit chunks from the start of the sector.
|
|
||||||
sector->data.erase(sector->data.begin(), sector->data.end() - 256);
|
|
||||||
|
|
||||||
sector->encoding = Sector::Encoding::SixAndTwo;
|
|
||||||
}
|
|
||||||
// Add this sector to the map.
|
|
||||||
result.insert(std::make_pair(sector_location, std::move(*sector)));
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case 704: { // Potentially this is a Macintosh sector.
|
|
||||||
// Attempt a six-and-two unmapping of the header.
|
|
||||||
std::array<uint_fast8_t, 5> decoded_header;
|
|
||||||
bool out_of_bounds = false;
|
|
||||||
for(size_t c = 0; c < decoded_header.size(); ++c) {
|
|
||||||
decoded_header[c] = unmap_six_and_two(header[c]);
|
|
||||||
if(decoded_header[c] == 0xff) {
|
|
||||||
out_of_bounds = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(out_of_bounds) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the checksum.
|
// Apple II then?
|
||||||
if(decoded_header[4] != (decoded_header[0] ^ decoded_header[1] ^ decoded_header[2] ^ decoded_header[3]))
|
auto appleii_sector = decode_appleii_sector(header, sector, is_five_and_three);
|
||||||
sector->has_header_checksum_error = true;
|
if(appleii_sector) {
|
||||||
|
result.insert(std::make_pair(sector_location, std::move(*appleii_sector)));
|
||||||
// Decode the header.
|
|
||||||
sector->address.track = uint8_t(decoded_header[0] | ((decoded_header[2]&0x1f) << 6));
|
|
||||||
sector->address.sector = decoded_header[1];
|
|
||||||
sector->address.format = decoded_header[3];
|
|
||||||
sector->address.is_side_two = decoded_header[2] & 0x20;
|
|
||||||
|
|
||||||
// Reverse the GCR encoding of the sector contents to get back to 6-bit data.
|
|
||||||
for(auto &c: sector->data) {
|
|
||||||
c = unmap_six_and_two(c);
|
|
||||||
if(c == 0xff) {
|
|
||||||
out_of_bounds = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(out_of_bounds) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first byte in the sector is a repeat of the sector number; test it
|
|
||||||
// for correctness.
|
|
||||||
if(sector->data[0] != sector->address.sector) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cf. the corresponding section of Encoder.cpp for logic below.
|
|
||||||
int checksum[3] = {0, 0, 0};
|
|
||||||
for(size_t c = 0; c < 175; ++c) {
|
|
||||||
// Calculate the rolling checcksum in order to decode the bytes.
|
|
||||||
checksum[0] = (checksum[0] << 1) | (checksum[0] >> 7);
|
|
||||||
|
|
||||||
// All offsets are +1 below, to skip the initial sector number duplicate.
|
|
||||||
const uint8_t top_bits = sector->data[1 + c*4];
|
|
||||||
|
|
||||||
// Decode first byte.
|
|
||||||
sector->data[0 + c * 3] = uint8_t((sector->data[2 + c*4] + ((top_bits & 0x30) << 2)) ^ checksum[0]);
|
|
||||||
checksum[2] += sector->data[0 + c * 3] + (checksum[0] >> 8);
|
|
||||||
|
|
||||||
// Decode second byte;
|
|
||||||
sector->data[1 + c * 3] = uint8_t((sector->data[3 + c*4] + ((top_bits & 0x0c) << 4)) ^ checksum[2]);
|
|
||||||
checksum[1] += sector->data[1 + c * 3] + (checksum[2] >> 8);
|
|
||||||
|
|
||||||
// Decode third byte, if there is one.
|
|
||||||
if(c != 174) {
|
|
||||||
sector->data[2 + c * 3] = uint8_t((sector->data[4 + c*4] + ((top_bits & 0x03) << 6)) ^ checksum[1]);
|
|
||||||
checksum[0] += sector->data[2 + c * 3] + (checksum[1] >> 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset carries.
|
|
||||||
checksum[0] &= 0xff;
|
|
||||||
checksum[1] &= 0xff;
|
|
||||||
checksum[2] &= 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the checksum.
|
|
||||||
if(
|
|
||||||
checksum[0] != uint8_t(sector->data[703] + ((sector->data[700] & 0x03) << 6)) ||
|
|
||||||
checksum[1] != uint8_t(sector->data[702] + ((sector->data[700] & 0x0c) << 4)) ||
|
|
||||||
checksum[2] != uint8_t(sector->data[701] + ((sector->data[700] & 0x30) << 2))
|
|
||||||
) sector->has_data_checksum_error = true;
|
|
||||||
|
|
||||||
// Chop to size, and that's that.
|
|
||||||
sector->data.resize(524);
|
|
||||||
|
|
||||||
// Add this sector to the map.
|
|
||||||
sector->encoding = Sector::Encoding::Macintosh;
|
|
||||||
result.insert(std::make_pair(sector_location, std::move(*sector)));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
new_sector->data.push_back(value);
|
new_sector->data.push_back(value);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user