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 
11 namespace openmsx {
12 
14 {
15  clear(size);
16 }
17 
18 void RawTrack::clear(unsigned size)
19 {
20  idam.clear();
21  data.assign(size, 0x4e);
22 }
23 
24 void RawTrack::addIdam(unsigned idx)
25 {
26  assert(idx < data.size());
27  assert(idam.empty() || (idx > idam.back()));
28  idam.push_back(idx);
29 }
30 
31 void RawTrack::write(int idx, byte 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 
49 std::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  byte addrCrc1 = read(idx++);
67  byte 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  byte 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  unsigned sectorSize = 128 << (sector.sizeCode & 7);
95  updateCrc(dataCrc, dataIdx, sectorSize);
96  byte dataCrc1 = read(dataIdx + sectorSize + 0);
97  byte 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 
110 std::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(i);
116  sector && (sector->dataIdx != -1)) {
117  result.push_back(*sector);
118  }
119  }
120  return result;
121 }
122 
123 static 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 
132 std::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(i)) {
137  return sector;
138  }
139  }
140  return {};
141 }
142 
143 std::optional<RawTrack::Sector> RawTrack::decodeSector(byte sectorNum) const
144 {
145  // only complete sectors (header + data block)
146  for (const auto& i : idam) {
147  if (auto sector = decodeSectorImpl(i);
148  sector &&
149  (sector->sector == sectorNum) &&
150  (sector->dataIdx != -1)) {
151  return sector;
152  }
153  }
154  return {};
155 }
156 
157 void RawTrack::readBlock(int idx, span<byte> destination) const
158 {
159  for (auto [i, d] : enumerate(destination)) {
160  d = read(idx + int(i));
161  }
162 }
164 {
165  for (auto [i, s] : enumerate(source)) {
166  write(idx + int(i), s);
167  }
168 }
169 
170 void 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(&data[start], size);
176  } else {
177  unsigned part = unsigned(data.size()) - start;
178  crc.update(&data[start], part);
179  crc.update(&data[ 0], size - part);
180  }
181 }
182 
183 word 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 (int i : idam) {
244  if ((read(i - 3) != 0xA1) ||
245  (read(i - 2) != 0xA1) ||
246  (read(i - 1) != 0xA1) ||
247  (read(i - 0) != 0xFE)) continue;
248  write(i - 3, 0x14);
249 
250  if (auto sector = decodeSectorImpl(i);
251  sector && (sector->dataIdx != -1)) {
252  int d = sector->dataIdx;
253  if ((read(d - 4) != 0xA1) ||
254  (read(d - 3) != 0xA1) ||
255  (read(d - 2) != 0xA1)) continue;
256  write(d - 4, 0x14);
257  }
258  }
259 }
260 
261 // version 1: initial version (fixed track length of 6250)
262 // version 2: variable track length
263 template<typename Archive>
264 void RawTrack::serialize(Archive& ar, unsigned version)
265 {
266  ar.serialize("idam", idam);
267  auto len = unsigned(data.size());
268  if (ar.versionAtLeast(version, 2)) {
269  ar.serialize("trackLength", len);
270  } else {
271  assert(Archive::IS_LOADER);
272  len = 6250;
273  }
274  if constexpr (Archive::IS_LOADER) {
275  data.resize(len);
276  }
277  ar.serialize_blob("data", data.data(), data.size());
278 }
280 
281 template<typename Archive>
282 void RawTrack::Sector::serialize(Archive& ar, unsigned /*version*/)
283 {
284  ar.serialize("addrIdx", addrIdx,
285  "dataIdx", dataIdx,
286  "track", track,
287  "head", head,
288  "sector", sector,
289  "sizeCode", sizeCode,
290  "deleted", deleted,
291  "addrCrcErr", addrCrcErr,
292  "dataCrcErr", dataCrcErr);
293 }
295 
296 } // namespace openmsx
Definition: one_of.hh:7
This class calculates CRC numbers for the polygon x^16 + x^12 + x^5 + 1.
Definition: CRC16.hh:17
constexpr void update(uint8_t value)
Update CRC with one byte.
Definition: CRC16.hh:43
constexpr uint16_t getValue() const
Get current CRC value.
Definition: CRC16.hh:87
std::vector< Sector > decodeAll() const
Get info on all sectors in this track.
Definition: RawTrack.cc:110
std::optional< Sector > decodeSector(byte sectorNum) const
Get a sector with a specific number.
Definition: RawTrack.cc:143
void addIdam(unsigned idx)
Definition: RawTrack.cc:24
byte read(int idx) const
Definition: RawTrack.hh:106
void write(int idx, byte val, bool setIdam=false)
Definition: RawTrack.cc:31
void serialize(Archive &ar, unsigned version)
Definition: RawTrack.cc:264
word calcCrc(int idx, int size) const
Convenience method to calculate CRC for part of this track.
Definition: RawTrack.cc:183
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
void readBlock(int idx, span< byte > destination) const
Like memcpy() but copy from/to circular buffer.
Definition: RawTrack.cc:157
void applyWd2793ReadTrackQuirk()
Definition: RawTrack.cc:190
int wrapIndex(int idx) const
Definition: RawTrack.hh:108
void updateCrc(CRC16 &crc, int idx, int size) const
Definition: RawTrack.cc:170
void clear(unsigned size)
Clear track data.
Definition: RawTrack.cc:18
void writeBlock(int idx, span< const byte > source)
Definition: RawTrack.cc:163
Definition: span.hh:126
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
mat4 rotate(float angle, const vec3 &axis)
Definition: gl_transform.hh:58
This file implemented 3 utility functions:
Definition: Autofire.cc:9
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
auto lower_bound(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
Definition: ranges.hh:85
size_t size(std::string_view utf8)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:998
void serialize(Archive &ar, unsigned version)
Definition: RawTrack.cc:282
constexpr auto xrange(T e)
Definition: xrange.hh:155
constexpr auto begin(const zstring_view &x)
Definition: zstring_view.hh:83
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:84