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