2018-04-28 03:18:45 +00:00
|
|
|
//
|
|
|
|
// AppleDSK.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 27/04/2018.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
2018-04-28 03:18:45 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "AppleDSK.hpp"
|
|
|
|
|
2018-04-28 19:18:48 +00:00
|
|
|
#include "../../Track/PCMTrack.hpp"
|
2018-05-20 02:30:52 +00:00
|
|
|
#include "../../Track/TrackSerialiser.hpp"
|
2018-05-04 02:40:45 +00:00
|
|
|
#include "../../Encodings/AppleGCR/Encoder.hpp"
|
2018-05-20 02:30:52 +00:00
|
|
|
#include "../../Encodings/AppleGCR/SegmentParser.hpp"
|
2018-04-28 19:18:48 +00:00
|
|
|
|
2018-05-24 02:31:35 +00:00
|
|
|
#include <cstring>
|
|
|
|
|
2018-04-28 03:18:45 +00:00
|
|
|
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) {
|
2018-05-12 22:05:33 +00:00
|
|
|
if(file_.stats().st_size % (number_of_tracks*bytes_per_sector)) throw Error::InvalidFormat;
|
2018-04-28 03:18:45 +00:00
|
|
|
|
|
|
|
sectors_per_track_ = static_cast<int>(file_.stats().st_size / (number_of_tracks*bytes_per_sector));
|
|
|
|
if(sectors_per_track_ != 13 && sectors_per_track_ != 16) throw Error::InvalidFormat;
|
2018-04-29 20:34:10 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
2018-04-28 03:18:45 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 03:17:36 +00:00
|
|
|
HeadPosition AppleDSK::get_maximum_head_position() {
|
|
|
|
return HeadPosition(number_of_tracks);
|
2018-04-28 03:18:45 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 02:30:52 +00:00
|
|
|
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_;
|
|
|
|
}
|
|
|
|
|
2018-05-20 02:59:59 +00:00
|
|
|
size_t AppleDSK::logical_sector_for_physical_sector(size_t physical) {
|
|
|
|
// DOS and Pro DOS interleave sectors on disk, and they're represented in a disk
|
|
|
|
// image in physical order rather than logical.
|
|
|
|
if(physical == 15) return 15;
|
|
|
|
return (physical * (is_prodos_ ? 8 : 7)) % 15;
|
|
|
|
}
|
|
|
|
|
2018-04-28 03:18:45 +00:00
|
|
|
std::shared_ptr<Track> AppleDSK::get_track_at_position(Track::Address address) {
|
2018-05-20 02:30:52 +00:00
|
|
|
std::vector<uint8_t> track_data;
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock_guard(file_.get_file_access_mutex());
|
|
|
|
file_.seek(file_offset(address), SEEK_SET);
|
|
|
|
track_data = file_.read(static_cast<size_t>(bytes_per_sector * sectors_per_track_));
|
|
|
|
}
|
2018-04-28 19:18:48 +00:00
|
|
|
|
2018-05-02 00:31:42 +00:00
|
|
|
Storage::Disk::PCMSegment segment;
|
2018-05-07 03:17:36 +00:00
|
|
|
const uint8_t track = static_cast<uint8_t>(address.position.as_int());
|
2018-04-28 19:18:48 +00:00
|
|
|
|
|
|
|
// In either case below, the code aims for exactly 50,000 bits per track.
|
|
|
|
if(sectors_per_track_ == 16) {
|
|
|
|
// Write the sectors.
|
2018-05-03 01:28:18 +00:00
|
|
|
for(uint8_t c = 0; c < 16; ++c) {
|
2018-05-02 00:31:42 +00:00
|
|
|
segment += Encodings::AppleGCR::six_and_two_sync(10);
|
2018-05-10 00:28:58 +00:00
|
|
|
segment += Encodings::AppleGCR::header(254, track, c);
|
2018-05-02 00:31:42 +00:00
|
|
|
segment += Encodings::AppleGCR::six_and_two_sync(10);
|
2018-05-20 02:59:59 +00:00
|
|
|
segment += Encodings::AppleGCR::six_and_two_data(&track_data[logical_sector_for_physical_sector(c) * 256]);
|
2018-04-28 19:18:48 +00:00
|
|
|
}
|
|
|
|
|
2018-04-28 19:47:50 +00:00
|
|
|
// Pad if necessary.
|
2018-05-02 00:31:42 +00:00
|
|
|
if(segment.number_of_bits < 50000) {
|
2018-05-14 02:29:36 +00:00
|
|
|
segment += Encodings::AppleGCR::six_and_two_sync((50000 - segment.number_of_bits) / 10);
|
2018-04-28 19:47:50 +00:00
|
|
|
}
|
2018-04-28 19:18:48 +00:00
|
|
|
} else {
|
2018-05-20 02:30:52 +00:00
|
|
|
// TODO: 5 and 3, 13-sector format. If DSK actually supports it?
|
2018-04-28 19:18:48 +00:00
|
|
|
}
|
2018-04-28 03:18:45 +00:00
|
|
|
|
2018-05-02 00:31:42 +00:00
|
|
|
return std::make_shared<PCMTrack>(segment);
|
2018-04-28 03:18:45 +00:00
|
|
|
}
|
2018-05-20 02:30:52 +00:00
|
|
|
|
|
|
|
void AppleDSK::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
|
|
|
|
std::map<Track::Address, std::vector<uint8_t>> 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<uint8_t> track_contents(static_cast<size_t>(bytes_per_sector * sectors_per_track_));
|
|
|
|
for(const auto §or_pair: sector_map) {
|
2018-05-20 02:59:59 +00:00
|
|
|
const size_t target_address = logical_sector_for_physical_sector(sector_pair.second.address.sector);
|
2018-05-20 02:30:52 +00:00
|
|
|
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<std::mutex> 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);
|
|
|
|
}
|
|
|
|
}
|