Skip to content

Commit 5f695e1

Browse files
authored
Merge pull request #1855 from TomHarte/IOErrors
Improve CoCo CAS handling.
2 parents 4e1b044 + 4064dd4 commit 5f695e1

3 files changed

Lines changed: 137 additions & 49 deletions

File tree

Storage/FileHolder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ FileHolder::FileHolder(const std::string &file_name, const FileMode ideal_mode)
3232
case FileMode::ReadWrite:
3333
file_ = std::fopen(file_name.c_str(), "rb+");
3434
if(file_) break;
35-
is_read_only_ = true;
3635
[[fallthrough]];
3736

3837
case FileMode::Read:
38+
is_read_only_ = true;
3939
file_ = std::fopen(file_name.c_str(), "rb");
4040
break;
4141

Storage/Tape/Formats/CoCoCAS.cpp

Lines changed: 121 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,73 +14,152 @@ using namespace Storage::Tape;
1414
[CoCo-style] CAS files are a raw dump of source bytes as per Microsoft's 6809 BASIC and
1515
the Tandy (or Dragon) encoding.
1616
17-
It is therefore very similar to the Thomson K7 file format.
17+
It is therefore very similar to the Thomson K7 file format, but weird:
1818
19+
Contents are not necessarily byte-aligned with original content; within a file block
20+
it's an arbitrarily-aligned bitstream.
21+
22+
... but, most files abbreviate the sync periods. So you've still to apply
23+
ROM-style formatting.
1924
*/
25+
CoCoCAS::CoCoCAS(const std::string &file_name) {
26+
struct Shifter {
27+
Shifter(const std::string &file_name) : file_(file_name, FileMode::Read) {}
28+
29+
uint32_t value() const {
30+
return uint32_t(shifter_);
31+
}
32+
33+
void advance(size_t length) {
34+
while(length--) shift();
35+
}
36+
37+
void shift() {
38+
if(!depth_ && file_.eof()) {
39+
return;
40+
}
41+
42+
shifter_ >>= 1;
43+
--depth_;
44+
++shifted_;
45+
while(depth_ <= 56 && !file_.eof()) {
46+
shifter_ |= uint64_t(file_.get()) << depth_;
47+
depth_ += 8;
48+
}
49+
}
50+
51+
size_t offset() const {
52+
return shifted_;
53+
}
54+
55+
void set_offset(const size_t offset) {
56+
file_.seek(0, Whence::SET);
57+
shifted_ = 0;
58+
advance(offset);
59+
}
60+
61+
bool eof() const {
62+
return file_.eof() && !depth_;
63+
}
64+
65+
private:
66+
Storage::FileHolder file_;
67+
68+
uint64_t shifter_ = 0;
69+
int depth_ = 0;
70+
size_t shifted_ = 0;
71+
};
72+
73+
Shifter shifter(file_name);
74+
while(!shifter.eof()) {
75+
// Find next sync byte.
76+
while(!shifter.eof() && (shifter.value() & 0xff) != 0x3c) {
77+
shifter.shift();
78+
}
79+
const auto offset = shifter.offset();
80+
if(shifter.eof()) break;
81+
82+
auto &block = blocks_.emplace_back();
83+
shifter.advance(8);
84+
const auto type = uint8_t(shifter.value());
85+
86+
shifter.advance(8);
87+
const auto length = uint8_t(shifter.value());
2088

21-
CoCoCAS::CoCoCAS(const std::string &file_name) : file_name_(file_name) {
22-
// TODO: reject unless at least one normative CoCo-esque block is within the image.
23-
// CAS is not an unambiguous extension.
89+
block.data.reserve(length + 3);
90+
block.data.push_back(0x3c);
91+
block.data.push_back(type);
92+
block.data.push_back(length);
93+
94+
for(int c = 0; c <= length; c++) {
95+
shifter.advance(8);
96+
block.data.push_back(uint8_t(shifter.value()));
97+
}
98+
99+
const auto checksum = uint8_t(std::accumulate(block.data.begin() + 1, block.data.end() - 1, 0));
100+
if(checksum != block.data.back()) {
101+
blocks_.pop_back();
102+
shifter.set_offset(offset + 1);
103+
}
104+
}
105+
106+
if(blocks_.empty()) {
107+
throw ErrorBadFormat;
108+
}
24109
}
25110

26111
std::unique_ptr<FormatSerialiser> CoCoCAS::format_serialiser() const {
27-
return std::make_unique<Serialiser>(file_name_);
112+
return std::make_unique<Serialiser>(blocks_);
28113
}
29114

30-
CoCoCAS::Serialiser::Serialiser(const std::string &name) : file_(name, FileMode::Read) {
115+
CoCoCAS::Serialiser::Serialiser(const std::vector<Block> &blocks) : blocks_(blocks) {
31116
reset();
32117
}
33118

119+
void CoCoCAS::Serialiser::reset() {
120+
block_ = blocks_.begin();
121+
state_ = State::LeadIn;
122+
state_length_ = 0;
123+
}
124+
34125
void CoCoCAS::Serialiser::push_next_pulses() {
126+
const auto post_bit = [&](const bool bit) {
127+
// Generate a single wave of either 1200Hz (for a 0) or 2400Hz tone (for a 1).
128+
const Time length(
129+
1,
130+
bit ? 4800 : 2400
131+
);
132+
emplace_back(Pulse::Low, length);
133+
emplace_back(Pulse::High, length);
134+
};
135+
35136
const auto serialise = [&](uint8_t next) {
36137
for(int c = 0; c < 8; c++) {
37-
// Generate a single wave of either 1200Hz (for a 0) or 2400Hz tone (for a 1).
38-
const Time length(
39-
1,
40-
next & 0x01 ? 4800 : 2400
41-
);
138+
post_bit(next & 1);
42139
next >>= 1;
43-
44-
emplace_back(Pulse::Low, length);
45-
emplace_back(Pulse::High, length);
46140
}
47141
};
48142

49143
switch(state_) {
50-
case State::PreLeadInPause:
51-
emplace_back(Pulse::Zero, Time(1, 2));
52-
state_ = State::LeadIn;
53-
break;
54-
55-
case State::LeadIn: {
56-
const uint8_t next = file_.get();
57-
serialise(next);
58-
59-
if(next == 0x3c) {
144+
case State::LeadIn:
145+
serialise(0x55);
146+
++state_length_;
147+
if(state_length_ == 150 && block_ != blocks_.end()) {
60148
state_ = State::Body;
61-
state_length_ = -1;
149+
state_length_ = 0;
62150
}
63-
}
64151
break;
65152

66-
case State::Body: {
67-
const uint8_t next = file_.get();
68-
serialise(next);
69-
70-
--state_length_;
71-
if(state_length_ == -3) {
72-
state_length_ = next + 1;
73-
} else if(!state_length_) {
74-
state_ = State::PreLeadInPause;
153+
case State::Body:
154+
serialise(block_->data[state_length_]);
155+
++state_length_;
156+
if(state_length_ == block_->data.size()) {
157+
state_ = State::LeadIn;
158+
state_length_ = 0;
159+
++block_;
75160
}
76-
}
77161
break;
78162

163+
default: __builtin_unreachable();
79164
}
80165
}
81-
82-
void CoCoCAS::Serialiser::reset() {
83-
// Add 1s of blank before the tape begins.
84-
state_ = State::PreLeadInPause;
85-
file_.seek(0, Whence::SET);
86-
}

Storage/Tape/Formats/CoCoCAS.hpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,33 @@ class CoCoCAS: public Tape {
2020
public:
2121
CoCoCAS(const std::string &file_name);
2222

23+
enum {
24+
ErrorBadFormat
25+
};
26+
2327
private:
24-
std::string file_name_;
28+
struct Block {
29+
std::vector<uint8_t> data;
30+
};
31+
std::vector<Block> blocks_;
32+
2533
std::unique_ptr<FormatSerialiser> format_serialiser() const override;
2634

2735
struct Serialiser: public PulseQueuedSerialiser {
28-
Serialiser(const std::string &);
36+
Serialiser(const std::vector<Block> &);
2937

3038
private:
3139
void push_next_pulses() override;
3240
void reset() override;
3341

42+
const std::vector<Block> &blocks_;
43+
std::vector<Block>::const_iterator block_;
44+
3445
enum class State {
35-
PreLeadInPause,
3646
LeadIn,
3747
Body,
38-
} state_ = State::PreLeadInPause;
39-
int state_length_ = 0;
40-
Storage::FileHolder file_;
48+
} state_ = State::LeadIn;
49+
size_t state_length_ = 0;
4150
};
4251
};
4352

0 commit comments

Comments
 (0)