// // AppleDSK.cpp // Clock Signal // // Created by Thomas Harte on 27/04/2018. // Copyright 2018 Thomas Harte. All rights reserved. // #include "AppleDSK.hpp" #include "../../Track/PCMTrack.hpp" #include "../../Track/TrackSerialiser.hpp" #include "../../Encodings/AppleGCR/Encoder.hpp" #include "../../Encodings/AppleGCR/SegmentParser.hpp" using namespace Storage::Disk; namespace { const int number_of_tracks = 35; const int bytes_per_sector = 256; } AppleDSK::AppleDSK(const std::string &file_name) : file_(file_name) { if(file_.stats().st_size % (number_of_tracks*bytes_per_sector)) throw Error::InvalidFormat; sectors_per_track_ = static_cast(file_.stats().st_size / (number_of_tracks*bytes_per_sector)); if(sectors_per_track_ != 13 && sectors_per_track_ != 16) throw Error::InvalidFormat; // Check whether this is a Pro DOS disk by inspecting the filename. if(sectors_per_track_ == 16) { size_t string_index = file_name.size()-1; while(file_name[string_index] != '.') { if(file_name[string_index] == 'p') { is_prodos_ = true; break; } --string_index; } } } HeadPosition AppleDSK::get_maximum_head_position() { return HeadPosition(number_of_tracks); } bool AppleDSK::get_is_read_only() { return file_.get_is_known_read_only(); } long AppleDSK::file_offset(Track::Address address) { return address.position.as_int() * bytes_per_sector * sectors_per_track_; } std::shared_ptr AppleDSK::get_track_at_position(Track::Address address) { std::vector track_data; { std::lock_guard lock_guard(file_.get_file_access_mutex()); file_.seek(file_offset(address), SEEK_SET); track_data = file_.read(static_cast(bytes_per_sector * sectors_per_track_)); } Storage::Disk::PCMSegment segment; const uint8_t track = static_cast(address.position.as_int()); // In either case below, the code aims for exactly 50,000 bits per track. if(sectors_per_track_ == 16) { // Write the sectors. std::size_t sector_number_ = 0; for(uint8_t c = 0; c < 16; ++c) { segment += Encodings::AppleGCR::six_and_two_sync(10); segment += Encodings::AppleGCR::header(254, track, c); segment += Encodings::AppleGCR::six_and_two_sync(10); segment += Encodings::AppleGCR::six_and_two_data(&track_data[sector_number_ * 256]); // DOS and Pro DOS interleave sectors on disk, and they're represented in a disk // image in physical order rather than logical. So that skew needs to be applied here. sector_number_ += is_prodos_ ? 8 : 7; if(sector_number_ > 0xf) sector_number_ %= 15; } // Pad if necessary. if(segment.number_of_bits < 50000) { segment += Encodings::AppleGCR::six_and_two_sync((50000 - segment.number_of_bits) / 10); } } else { // TODO: 5 and 3, 13-sector format. If DSK actually supports it? } return std::make_shared(segment); } void AppleDSK::set_tracks(const std::map> &tracks) { std::map> tracks_by_address; for(const auto &pair: tracks) { // Decode the track. auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment( Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000))); // Rearrange sectors into Apple DOS or Pro-DOS order. std::vector track_contents(static_cast(bytes_per_sector * sectors_per_track_)); for(const auto §or_pair: sector_map) { size_t target_address = sector_pair.second.address.sector; if(target_address != 15) { target_address = (target_address * (is_prodos_ ? 8 : 7)) % 15; } memcpy(&track_contents[target_address*256], sector_pair.second.data.data(), bytes_per_sector); } // Store for later. tracks_by_address[pair.first] = std::move(track_contents); } std::lock_guard lock_guard(file_.get_file_access_mutex()); for(const auto &pair: tracks_by_address) { file_.seek(file_offset(pair.first), SEEK_SET); file_.write(pair.second); } }