openMSX
RawTrack.cc
Go to the documentation of this file.
1#include "RawTrack.hh"
2#include "CRC16.hh"
3#include "enumerate.hh"
4#include "one_of.hh"
5#include "ranges.hh"
6#include "serialize.hh"
7#include "serialize_stl.hh"
8#include "xrange.hh"
9#include <cassert>
10
11namespace openmsx {
12
13RawTrack::RawTrack(unsigned size)
14{
15 clear(size);
16}
17
18void RawTrack::clear(unsigned size)
19{
20 idam.clear();
21 data.assign(size, 0x4e);
22}
23
24void RawTrack::addIdam(unsigned idx)
25{
26 assert(idx < data.size());
27 assert(idam.empty() || (idx > idam.back()));
28 idam.push_back(idx);
29}
30
31void RawTrack::write(int idx, uint8_t val, bool setIdam)
32{
33 unsigned i2 = wrapIndex(idx);
34 auto it = ranges::lower_bound(idam, i2);
35 if (setIdam) {
36 // add idam (if not already present)
37 if ((it == end(idam)) || (*it != i2)) {
38 idam.insert(it, i2);
39 }
40 } else {
41 // remove idam (if present)
42 if ((it != end(idam)) && (*it == i2)) {
43 idam.erase(it);
44 }
45 }
46 data[i2] = val;
47}
48
49std::optional<RawTrack::Sector> RawTrack::decodeSectorImpl(int idx) const
50{
51 // read (and check) address mark
52 // assume addr mark starts with three A1 bytes (should be
53 // located right before the current 'idx' position)
54 if (read(idx) != 0xFE) return {};
55 ++idx;
56
57 Sector sector;
58 sector.addrIdx = idx;
59 CRC16 addrCrc;
60 addrCrc.init({0xA1, 0xA1, 0xA1, 0xFE});
61 updateCrc(addrCrc, sector.addrIdx, 4);
62 sector.track = read(idx++);
63 sector.head = read(idx++);
64 sector.sector = read(idx++);
65 sector.sizeCode = read(idx++);
66 uint8_t addrCrc1 = read(idx++);
67 uint8_t addrCrc2 = read(idx++);
68 sector.addrCrcErr = (256 * addrCrc1 + addrCrc2) != addrCrc.getValue();
69
70 sector.dataIdx = -1; // (for now) no data block found
71 sector.deleted = false; // dummy
72 sector.dataCrcErr = true; // dummy
73
74 if (!sector.addrCrcErr) {
75 // Locate data mark, should starts within 43 bytes from current
76 // position (that's what the WD2793 does).
77 for (auto i : xrange(43)) {
78 int idx2 = idx + i;
79 int j = 0;
80 for (; j < 3; ++j) {
81 if (read(idx2 + j) != 0xA1) break;
82 }
83 if (j != 3) continue; // didn't find 3 x 0xA1
84
85 uint8_t type = read(idx2 + 3);
86 if (type != one_of(0xfb, 0xf8)) continue;
87
88 CRC16 dataCrc;
89 dataCrc.init({0xA1, 0xA1, 0xA1});
90 dataCrc.update(type);
91
92 // OK, found start of data, calculate CRC.
93 int dataIdx = idx2 + 4;
94 int sectorSize = 128 << (sector.sizeCode & 7);
95 updateCrc(dataCrc, dataIdx, sectorSize);
96 uint8_t dataCrc1 = read(dataIdx + sectorSize + 0);
97 uint8_t dataCrc2 = read(dataIdx + sectorSize + 1);
98 bool dataCrcErr = (256 * dataCrc1 + dataCrc2) != dataCrc.getValue();
99
100 // store result
101 sector.dataIdx = dataIdx;
102 sector.deleted = type == 0xf8;
103 sector.dataCrcErr = dataCrcErr;
104 break;
105 }
106 }
107 return sector;
108}
109
110std::vector<RawTrack::Sector> RawTrack::decodeAll() const
111{
112 // only complete sectors (header + data block)
113 std::vector<Sector> result;
114 for (const auto& i : idam) {
115 if (auto sector = decodeSectorImpl(narrow<int>(i));
116 sector && (sector->dataIdx != -1)) {
117 result.push_back(*sector);
118 }
119 }
120 return result;
121}
122
123static std::vector<unsigned> rotateIdam(std::vector<unsigned> idam, unsigned startIdx)
124{
125 // find first element that is equal or bigger
126 auto it = ranges::lower_bound(idam, startIdx);
127 // rotate range so that we start at that element
128 std::rotate(begin(idam), it, end(idam));
129 return idam;
130}
131
132std::optional<RawTrack::Sector> RawTrack::decodeNextSector(unsigned startIdx) const
133{
134 // get first valid sector-header
135 for (auto& i : rotateIdam(idam, startIdx)) {
136 if (auto sector = decodeSectorImpl(narrow<int>(i))) {
137 return sector;
138 }
139 }
140 return {};
141}
142
143std::optional<RawTrack::Sector> RawTrack::decodeSector(uint8_t sectorNum) const
144{
145 // only complete sectors (header + data block)
146 for (const auto& i : idam) {
147 if (auto sector = decodeSectorImpl(narrow<int>(i));
148 sector &&
149 (sector->sector == sectorNum) &&
150 (sector->dataIdx != -1)) {
151 return sector;
152 }
153 }
154 return {};
155}
156
157void RawTrack::readBlock(int idx, std::span<uint8_t> destination) const
158{
159 for (auto [i, d] : enumerate(destination)) {
160 d = read(idx + int(i));
161 }
162}
163void RawTrack::writeBlock(int idx, std::span<const uint8_t> source)
164{
165 for (auto [i, s] : enumerate(source)) {
166 write(idx + int(i), s);
167 }
168}
169
170void RawTrack::updateCrc(CRC16& crc, int idx, int size) const
171{
172 unsigned start = wrapIndex(idx);
173 unsigned end = start + size;
174 if (end <= data.size()) {
175 crc.update(subspan(data, start, size));
176 } else {
177 unsigned part = unsigned(data.size()) - start;
178 crc.update(subspan(data, start, part));
179 crc.update(subspan(data, 0, size - part));
180 }
181}
182
183uint16_t RawTrack::calcCrc(int idx, int size) const
184{
185 CRC16 crc;
186 updateCrc(crc, idx, size);
187 return crc.getValue();
188}
189
191{
192 // Tests on a real WD2793 have shown that the first 'A1' byte in a
193 // sequence of 3 'special' A1 bytes gets systematically read as a
194 // different value by the read-track command.
195 //
196 // Very roughly in my tests I got:
197 // - about 1/2 of the times the value 0x14 is returned
198 // - about 1/3 of the times the value 0xC2 is returned
199 // - about 1/6 of the times a different value is returned (I've seen
200 // 00, 02, F8, FC)
201 // When re-reading the same track again, the values 14 and C2 remain,
202 // but often the values 00 or 02 change to 14, and F8 or FC change to
203 // C2. I've never seen the 'real' value 'A1'. Note again that this
204 // transformation only applies to the 1st of the three A1 bytes, the
205 // 2nd and 3rd are correctly read as A1.
206 //
207 // I've no good explanation for why this is happening, except for the
208 // following observation which might be the start of a partial
209 // explanation, or it might just be a coincidence:
210 //
211 // The 'special' value A1 is encoded as: 0x4489, or in binary
212 // 01 00 01 00 10 00 10 01
213 // If you only look at the even bits (the data bits) you get the value
214 // A1 back. The odd bits (the clock bits) are generated by the MFM
215 // encoding rules and ensure that there are never 2 consecutive 1-bits
216 // and never more than 3 consecutive 0-bits. Though this is a 'special'
217 // sequence and the clock bit of the 6th bit (counting from left to
218 // right starting at 1) is 0, it should be 1 according to the MFM
219 // rules. The normal encoding for A1 is 0x44A9. An FDC uses this
220 // special bit-pattern to synchronize the bitstream on, e.g. to know
221 // where the byte-boundaries are located in the stream.
222 //
223 // Now if you shift the pattern 0x4489 1 bit to the left, you get
224 // 0x8912. If you then decode the data bits you get the value 0x14. And
225 // this _might_ be a partial explanation for why read-track often
226 // returns 0x14 instead of 0xA1.
227 //
228 // The implementation below always changes the first 0xA1 byte to 0x14.
229 // It does not also return the value 0xC2, because I've no clue yet
230 // when which value should be returned.
231 //
232 // The motivation for this (crude) implementation is the following:
233 // There's a cracked disk version of Nemesis that comes with a
234 // copy-protection. The disk has a track which contains sector-headers
235 // with CRC errors. The software verifies the protection via the
236 // read-track command. In the resulting data block it scans for the
237 // first A1 byte and then sums the next 8 bytes. Initially openMSX
238 // returned A1 'too early', and then the sum of the 8 following bytes
239 // doesn't match. But if the first A1 is turned into something
240 // (anything) else, so the sum starts one byte later, it does work
241 // fine.
242
243 for (unsigned ii : idam) {
244 auto i = narrow<int>(ii);
245 if ((read(i - 3) != 0xA1) ||
246 (read(i - 2) != 0xA1) ||
247 (read(i - 1) != 0xA1) ||
248 (read(i - 0) != 0xFE)) continue;
249 write(i - 3, 0x14);
250
251 if (auto sector = decodeSectorImpl(i);
252 sector && (sector->dataIdx != -1)) {
253 int d = sector->dataIdx;
254 if ((read(d - 4) != 0xA1) ||
255 (read(d - 3) != 0xA1) ||
256 (read(d - 2) != 0xA1)) continue;
257 write(d - 4, 0x14);
258 }
259 }
260}
261
262// version 1: initial version (fixed track length of 6250)
263// version 2: variable track length
264template<typename Archive>
265void RawTrack::serialize(Archive& ar, unsigned version)
266{
267 ar.serialize("idam", idam);
268 auto len = unsigned(data.size());
269 if (ar.versionAtLeast(version, 2)) {
270 ar.serialize("trackLength", len);
271 } else {
272 assert(Archive::IS_LOADER);
273 len = 6250;
274 }
275 if constexpr (Archive::IS_LOADER) {
276 data.resize(len);
277 }
278 ar.serialize_blob("data", data);
279}
281
282template<typename Archive>
283void RawTrack::Sector::serialize(Archive& ar, unsigned /*version*/)
284{
285 ar.serialize("addrIdx", addrIdx,
286 "dataIdx", dataIdx,
287 "track", track,
288 "head", head,
289 "sector", sector,
290 "sizeCode", sizeCode,
291 "deleted", deleted,
292 "addrCrcErr", addrCrcErr,
293 "dataCrcErr", dataCrcErr);
294}
296
297} // namespace openmsx
This class calculates CRC numbers for the polygon x^16 + x^12 + x^5 + 1.
Definition CRC16.hh:19
constexpr void update(uint8_t value)
Update CRC with one byte.
Definition CRC16.hh:45
constexpr uint16_t getValue() const
Get current CRC value.
Definition CRC16.hh:89
std::vector< Sector > decodeAll() const
Get info on all sectors in this track.
Definition RawTrack.cc:110
void write(int idx, uint8_t val, bool setIdam=false)
Definition RawTrack.cc:31
void addIdam(unsigned idx)
Definition RawTrack.cc:24
uint8_t read(int idx) const
Definition RawTrack.hh:107
void serialize(Archive &ar, unsigned version)
Definition RawTrack.cc:265
std::optional< Sector > decodeNextSector(unsigned startIdx) const
Get the next sector (starting from a certain index).
Definition RawTrack.cc:132
RawTrack(unsigned size=STANDARD_SIZE)
Definition RawTrack.cc:13
std::optional< Sector > decodeSector(uint8_t sectorNum) const
Get a sector with a specific number.
Definition RawTrack.cc:143
void applyWd2793ReadTrackQuirk()
Definition RawTrack.cc:190
int wrapIndex(int idx) const
Definition RawTrack.hh:109
void updateCrc(CRC16 &crc, int idx, int size) const
Definition RawTrack.cc:170
void writeBlock(int idx, std::span< const uint8_t > source)
Definition RawTrack.cc:163
void clear(unsigned size)
Clear track data.
Definition RawTrack.cc:18
void readBlock(int idx, std::span< uint8_t > destination) const
Like memcpy() but copy from/to circular buffer.
Definition RawTrack.cc:157
uint16_t calcCrc(int idx, int size) const
Convenience method to calculate CRC for part of this track.
Definition RawTrack.cc:183
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition enumerate.hh:28
This file implemented 3 utility functions:
Definition Autofire.cc:9
auto lower_bound(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
Definition ranges.hh:115
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:471
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
void serialize(Archive &ar, unsigned version)
Definition RawTrack.cc:283
constexpr auto xrange(T e)
Definition xrange.hh:132
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)