1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 16:31:31 +00:00

Liberalises segment parser not necessarily to require a standard epilogue.

It seems that real disks don't always have them; I guess the boot ROM doesn't require one.
This commit is contained in:
Thomas Harte 2020-07-15 22:27:04 -04:00
parent aed61f6251
commit 8da7806ee9

View File

@ -12,6 +12,8 @@
#include <array>
using namespace Storage::Encodings::AppleGCR;
namespace {
const uint8_t six_and_two_unmapping[] = {
@ -55,9 +57,183 @@ uint8_t unmap_five_and_three(uint8_t source) {
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;
}
}
// 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;
}
using namespace Storage::Encodings::AppleGCR;
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.
// 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)) 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 = &sector->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;
}
// 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> result;
@ -109,7 +285,7 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
// If this is the start of a data section, and at least
// one header has been witnessed, start a sector.
if(scanner[2] == data_prologue[2]) {
if(scanner[2] == data_prologue[2] || is_five_and_three) {
new_sector = std::make_unique<Sector>();
new_sector->data.reserve(710);
} else {
@ -120,23 +296,12 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
}
} else {
if(new_sector) {
// Check whether the value just read is a legal GCR byte, in six-and-two
// encoding (which is a strict superset of five-and-three).
// Check whether the value just read is a legal GCR byte, for this sector;
// if not, or if
const bool is_invalid = is_five_and_three ? (unmap_five_and_three(value) == 0xff) : (unmap_six_and_two(value) == 0xff);
if(is_invalid) {
// The second byte of the standard epilogue is illegal, so this still may
// be a valid sector. If the final byte was the first byte of an epilogue,
// 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);
if(is_invalid || new_sector->data.size() >= 704) {
// The second byte of the standard epilogue is 'illegal', as is the first byte of
// all prologues. So either a whole sector has been captured up to now, or it hasn't.
// Move the sector elsewhere for processing; there's definitely no way to proceed with
// the prospective sector if it doesn't parse.
@ -144,185 +309,19 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
new_sector.reset();
pointer = scanning_sentinel;
// Check for valid decoding options.
switch(sector->data.size()) {
default: // This is not a decodeable sector.
break;
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 = &sector->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;
}
// 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(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;
// Potentially this is a Macintosh sector.
auto macintosh_sector = decode_macintosh_sector(header, sector);
if(macintosh_sector) {
result.insert(std::make_pair(sector_location, std::move(*macintosh_sector)));
continue;
}
// Apple II then?
auto appleii_sector = decode_appleii_sector(header, sector, is_five_and_three);
if(appleii_sector) {
result.insert(std::make_pair(sector_location, std::move(*appleii_sector)));
}
} else {
new_sector->data.push_back(value);
}