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