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