1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-03-14 10:16:29 +00:00
Files
CLK/Storage/Disk/DiskImage/Formats/MOOF.cpp
2026-02-16 18:19:09 -05:00

237 lines
6.0 KiB
C++

//
// MOOF.cpp
// Clock Signal
//
// Created by Thomas Harte on 15/02/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#include "MOOF.hpp"
#include "Storage/Disk/Track/PCMTrack.hpp"
#include "Numeric/CRC.hpp"
using namespace Storage::Disk;
namespace {
constexpr uint32_t chunk(const char *str) {
return uint32_t(str[0] | (str[1] << 8) | (str[2] << 16) | (str[3] << 24));
}
class MOOFFluxTrack: public Track {
public:
MOOFFluxTrack(std::vector<uint8_t> &&data, const uint32_t bit_count) :
data_(std::move(data)), bit_count_(bit_count) {
seek_to(0.0f);
}
Event get_next_event() final {
Event event;
if(data_iterator_ == data_.end() || bits_passed_ == bit_count_) {
data_iterator_ = data_.begin();
bits_passed_ = 0;
return Event{
.type = Storage::Disk::Track::Event::IndexHole,
.length = Storage::Time()
};
}
Event result;
result.type = Storage::Disk::Track::Event::FluxTransition;
result.length.clock_rate = 8'000'000; // i.e. 125ns
result.length.length = 0;
while(true) {
const auto delta = *data_iterator_;
result.length.length += delta;
++data_iterator_;
if(delta != 0xff || data_iterator_ == data_.end()) {
break;
}
}
++bits_passed_;
return result;
}
float seek_to(const float time_since_index_hole) final {
bits_passed_ = 0;
data_iterator_ = data_.begin();
float time_observed = 0.0f;
while(time_observed < time_since_index_hole) {
auto prior = data_iterator_;
const auto next_time = get_next_event().length.get<float>();
if(time_observed + next_time > time_since_index_hole) {
--bits_passed_;
data_iterator_ = prior;
break;
}
time_observed += next_time;
}
return time_observed;
}
Track *clone() const final {
return new MOOFFluxTrack(data_, bit_count_);
}
private:
MOOFFluxTrack(const std::vector<uint8_t> &data, const uint32_t bit_count) :
data_(data), bit_count_(bit_count) {
seek_to(0.0f);
}
std::vector<uint8_t> data_;
uint32_t bit_count_ = 0;
std::vector<uint8_t>::iterator data_iterator_;
uint32_t bits_passed_ = 0;
};
}
MOOF::MOOF(const std::string &file_name) :
file_(file_name) {
static constexpr char signature[] = {
'M', 'O', 'O', 'F',
char(0xff), 0x0a, 0x0d, 0x0a
};
if(!file_.check_signature<SignatureType::Binary>(signature)) {
throw Error::InvalidFormat;
}
// Test the file's CRC32.
const auto crc = file_.get_le<uint32_t>();
post_crc_contents_ = file_.read(size_t(file_.stats().st_size - 12));
const uint32_t computed_crc = CRC::CRC32::crc_of(post_crc_contents_);
if(crc != computed_crc) {
throw Error::InvalidFormat;
}
// Retreat to the first byte after the CRC and parse all chunks.
file_.seek(12, Whence::SET);
bool has_tmap = false, has_flux = false;
std::fill(std::begin(track_map_), std::end(track_map_), 0xff);
std::fill(std::begin(flux_map_), std::end(flux_map_), 0xff);
while(true) {
const auto chunk_id = file_.get_le<uint32_t>();
const auto chunk_size = file_.get_le<uint32_t>();
const auto end_of_chunk = file_.tell() + long(chunk_size);
if(file_.eof()) break;
switch(chunk_id) {
case chunk("INFO"):
info_.version = file_.get();
info_.disk_type = Info::DiskType(file_.get());
info_.is_write_protected = file_.get();
break;
case chunk("TMAP"):
file_.read(track_map_, 160);
has_tmap = true;
break;
case chunk("FLUX"):
file_.read(flux_map_, 160);
has_flux = true;
break;
case chunk("TRKS"):
tracks_offset_ = file_.tell();
break;
default:
break;
}
file_.seek(end_of_chunk, Whence::SET);
}
// Structural issues.
if(tracks_offset_ == -1 || (!has_tmap && !has_flux)) {
throw Error::InvalidFormat;
}
// Versioning.
//
// TODO: determine what it means to be a Twiggy disk, in encoding terms.
const bool supports_disk_type =
info_.disk_type == Info::DiskType::GCR400kb ||
info_.disk_type == Info::DiskType::GCR800kb ||
info_.disk_type == Info::DiskType::MFM;
if(info_.version != 1 || !supports_disk_type) {
throw Error::InvalidFormat;
}
}
bool MOOF::is_read_only() const {
// Writing not-yet implemented.
return true; //info_.is_write_protected;
}
HeadPosition MOOF::maximum_head_position() const {
return HeadPosition(80);
}
int MOOF::head_count() const {
return info_.disk_type == Info::DiskType::GCR400kb ? 1 : 2;
}
std::unique_ptr<Track> MOOF::track_at_position(const Track::Address address) const {
const int table_position = address.position.as_int() * 2 + address.head;
if(table_position > 255) {
return nullptr;
}
const auto location = [&](const uint8_t offset) -> TrackLocation {
file_.seek(tracks_offset_ + 8 * long(offset), Whence::SET);
TrackLocation location;
location.starting_block = file_.get_le<uint16_t>();
location.block_count = file_.get_le<uint16_t>();
location.bit_count = file_.get_le<uint32_t>();
return location;
};
if(flux_map_[table_position] != 0xff) {
return flux(location(flux_map_[table_position]));
} else if(track_map_[table_position] != 0xff) {
return track(location(track_map_[table_position]));
}
return nullptr;
}
std::unique_ptr<Track> MOOF::flux(const TrackLocation location) const {
file_.seek(location.starting_block * 512, Whence::SET);
auto track_contents = file_.read((location.bit_count + 7) / 8);
return std::make_unique<MOOFFluxTrack>(std::move(track_contents), location.bit_count);
}
std::unique_ptr<Track> MOOF::track(const TrackLocation location) const {
file_.seek(location.starting_block * 512, Whence::SET);
const auto track_contents = file_.read((location.bit_count + 7) / 8);
return std::make_unique<PCMTrack>(PCMSegment(location.bit_count, track_contents));
}
bool MOOF::represents(const std::string &name) const {
return name == file_.name();
}
void MOOF::set_tracks([[maybe_unused]] const std::map<Track::Address, std::unique_ptr<Track>> &tracks) {
// for(const auto &[address, track]: tracks) {
// TODO:
//
// (1) can this be a PCM track, or does it need to be a flux track?
// (2) manipulate the many indirections of a MOOF file.
//
// To consider: would it be better to do MOOFs and WOZs with a higher-level
// data structure?
// }
}