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