1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-03-11 04:42:20 +00:00
Files
CLK/Storage/Disk/DiskImage/Formats/D64.cpp
2025-11-19 13:14:13 -05:00

264 lines
7.6 KiB
C++

//
// D64.cpp
// Clock Signal
//
// Created by Thomas Harte on 01/08/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#include "D64.hpp"
#include <algorithm>
#include <cstring>
#include <set>
#include <sys/stat.h>
#include "Storage/Disk/Track/PCMTrack.hpp"
#include "Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "Storage/Disk/Track/TrackSerialiser.hpp"
#include "Numeric/SizedInt.hpp"
using namespace Storage::Disk;
D64::D64(const std::string &file_name) :
file_(file_name) {
// In D64, this is it for validation without imposing potential false-negative tests:
// check that the file size appears to be correct. Stone-age stuff.
if(file_.stats().st_size != 174848 && file_.stats().st_size != 196608)
throw Error::InvalidFormat;
number_of_tracks_ = (file_.stats().st_size == 174848) ? 35 : 40;
// Then, ostensibly, this is a valid file. Pick a disk ID as a
// function of the file_name, being the most stable thing available.
for(const auto &character: file_name) {
disk_id_ ^= character;
disk_id_ = uint16_t((disk_id_ << 2) ^ (disk_id_ >> 13));
}
}
HeadPosition D64::maximum_head_position() const {
return HeadPosition(number_of_tracks_);
}
bool D64::is_read_only() const {
return file_.is_known_read_only();
}
bool D64::represents(const std::string &name) const {
return name == file_.name();
}
D64::TrackExtent D64::track_extent(const Track::Address address) const {
static constexpr int tracks_in_zone[] = {17, 7, 6, 10};
static constexpr int sectors_by_zone[] = {21, 19, 18, 17};
int offset_to_track = 0;
int tracks_to_traverse = address.position.as_int();
int zone = 0;
for(int current_zone = 0; current_zone < 4; current_zone++) {
const int tracks = std::min(tracks_to_traverse, tracks_in_zone[current_zone]);
offset_to_track += tracks * sectors_by_zone[current_zone];
tracks_to_traverse -= tracks;
if(tracks == tracks_in_zone[current_zone]) {
++zone;
}
}
return TrackExtent {
.file_offset = offset_to_track * 256,
.zone = zone,
.number_of_sectors = sectors_by_zone[zone]
};
}
std::unique_ptr<Track> D64::track_at_position(const Track::Address address) const {
// Seek to start of data.
const auto extent = track_extent(address);
std::lock_guard lock_guard(file_.file_access_mutex());
file_.seek(extent.file_offset, Whence::SET);
// Build up a PCM sampling of the GCR version of this track.
// Format per sector:
//
// syncronisation: three $FFs directly in GCR
// value $08 to announce a header
// a checksum made of XORing the following four bytes
// sector number (1 byte)
// track number (1 byte)
// disk ID (2 bytes)
// five GCR bytes of value $55
// = [6 bytes -> 7.5 GCR bytes] + ... = 21 GCR bytes
//
// syncronisation: three $FFs directly in GCR
// value $07 to announce data
// 256 data bytes
// a checksum: the XOR of the previous 256 bytes
// two bytes of vaue $00
// = [260 bytes -> 325 GCR bytes] + 3 GCR bytes = 328 GCR bytes
//
// = 349 GCR bytes per sector
std::size_t track_bytes = 349 * size_t(extent.number_of_sectors);
std::vector<uint8_t> data(track_bytes);
for(int sector = 0; sector < extent.number_of_sectors; sector++) {
uint8_t *const sector_data = &data[size_t(sector) * 349];
sector_data[0] = sector_data[1] = sector_data[2] = 0xff;
const uint8_t sector_number = uint8_t(sector); // Sectors count from 0.
const uint8_t track_number = uint8_t(address.position.as_int() + 1); // Tracks count from 1.
uint8_t checksum = uint8_t(sector_number ^ track_number ^ disk_id_ ^ (disk_id_ >> 8));
const uint8_t header_start[4] = {
0x08, checksum, sector_number, track_number
};
Encodings::CommodoreGCR::encode_block(&sector_data[3], header_start);
const uint8_t header_end[4] = {
uint8_t(disk_id_ & 0xff), uint8_t(disk_id_ >> 8), 0, 0
};
Encodings::CommodoreGCR::encode_block(&sector_data[8], header_end);
// Pad out post-header parts.
static constexpr uint8_t zeros[4] = {0, 0, 0, 0};
Encodings::CommodoreGCR::encode_block(&sector_data[13], zeros);
sector_data[18] = 0x52;
sector_data[19] = 0x94;
sector_data[20] = 0xaf;
// Get the actual contents.
uint8_t source_data[256];
file_.read(source_data, sizeof(source_data));
// Compute the latest checksum.
checksum = 0;
for(int c = 0; c < 256; c++) {
checksum ^= source_data[c];
}
// Put in another sync.
sector_data[21] = sector_data[22] = sector_data[23] = 0xff;
// Now start writing in the actual data.
const uint8_t start_of_data[4] = {
0x07, source_data[0], source_data[1], source_data[2]
};
Encodings::CommodoreGCR::encode_block(&sector_data[24], start_of_data);
int source_data_offset = 3;
int target_data_offset = 29;
while((source_data_offset+4) < 256) {
Encodings::CommodoreGCR::encode_block(&sector_data[target_data_offset], &source_data[source_data_offset]);
target_data_offset += 5;
source_data_offset += 4;
}
const uint8_t end_of_data[4] = {
source_data[255], checksum, 0, 0
};
Encodings::CommodoreGCR::encode_block(&sector_data[target_data_offset], end_of_data);
}
return std::make_unique<PCMTrack>(PCMSegment(data));
}
void D64::set_tracks(const std::map<Track::Address, std::unique_ptr<Track>> &tracks) {
for(const auto &[address, track]: tracks) {
const auto extent = track_extent(address);
std::map<int, std::vector<uint8_t>> decoded;
// Get bit stream.
const auto serialisation =
Storage::Disk::track_serialisation(
*track,
Time(1, extent.number_of_sectors * 349 * 8) // This is relative to a normalised world where
// 1 unit of time = 1 track. So don't use
// length_of_a_bit_in_time_zone, which is relative to
// a wall clock.
);
// Decode sectors.
Numeric::SizedInt<10> shifter = 0;
int repeats = 2;
auto bit = serialisation.data.begin();
bool is_ended = false;
const auto shift = [&] {
shifter = uint16_t((shifter.get() << 1) | *bit);
++bit;
if(bit == serialisation.data.end()) {
bit = serialisation.data.begin();
--repeats;
is_ended |= !repeats;
}
};
const auto byte = [&] {
for(int c = 0; c < 9; c++) {
shift();
}
const auto result = Encodings::CommodoreGCR::decoding_from_dectet(shifter.get());
shift();
return uint8_t(result);
};
const auto block_type = [&] {
// Find synchronisation, then get first dectet after that.
while(!is_ended && shifter.get() != 0b11111'11111) {
shift();
}
while(!is_ended && shifter.get() == 0b11111'11111) {
shift();
}
// Type should be 8 for a header, 7 for some data.
return byte();
};
while(!is_ended && decoded.size() != size_t(extent.number_of_sectors)) {
// Find a header.
const auto header_start = block_type();
if(header_start != 0x8) {
continue;
}
const auto checksum = byte();
const auto sector_id = byte();
const auto track_id = byte();
const auto disk_id1 = byte();
const auto disk_id2 = byte();
if(checksum != (sector_id ^ track_id ^ disk_id1 ^ disk_id2)) {
continue;
}
if(sector_id >= extent.number_of_sectors) {
continue;
}
// Skip to data.
const auto data_start = block_type();
if(data_start != 0x7) {
continue;
}
// Copy into place if not yet present.
uint8_t data_checksum = 0;
std::vector<uint8_t> sector_contents(256);
for(size_t c = 0; c < 256; c++) {
const uint8_t next = byte();
data_checksum ^= next;
sector_contents[c] = next;
}
if(byte() != data_checksum) {
continue;
}
decoded.emplace(sector_id, std::move(sector_contents));
}
// Write.
std::lock_guard lock_guard(file_.file_access_mutex());
for(auto &[sector, contents]: decoded) {
file_.seek(extent.file_offset + sector * 256, Whence::SET);
file_.write(contents);
}
}
}