OXYGENHUB
◆ main openmsx / src / fdc / DMKDiskImage.cc
6.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "DMKDiskImage.hh"
2
3
#include "DiskExceptions.hh"
4
#include "File.hh"
5
#include "FilePool.hh"
6
#include "RawTrack.hh"
7
8
#include "narrow.hh"
9
#include "one_of.hh"
10
#include "xrange.hh"
11
12
#include <algorithm>
13
#include <array>
14
#include <cassert>
15
16
namespace openmsx {
17
18
struct DmkHeader
19
{
20
	uint8_t writeProtected;
21
	uint8_t numTracks;
22
	std::array<uint8_t, 2> trackLen;
23
	uint8_t flags;
24
	std::array<uint8_t, 7> reserved;
25
	std::array<uint8_t, 4> format;
26
};
27
static_assert(sizeof(DmkHeader) == 16);
28
29
static constexpr uint8_t FLAG_SINGLE_SIDED = 0x10;
30
static constexpr unsigned IDAM_FLAGS_MASK = 0xC000;
31
static constexpr unsigned FLAG_MFM_SECTOR = 0x8000;
32
33
34
[[nodiscard]] static constexpr bool isValidDmkHeader(const DmkHeader& header)
35
{
36
	if (header.writeProtected != one_of(0x00, 0xff)) {
37
		return false;
38
	}
39
	unsigned trackLen = header.trackLen[0] + 256 * header.trackLen[1];
40
	if (trackLen >= 0x4000) return false; // too large track length
41
	if (trackLen <= 128)    return false; // too small
42
	if (header.flags & ~0xd0) return false; // unknown flag set
43
	return std::ranges::all_of(header.reserved, [](auto& r) { return r == 0; }) &&
44
	       std::ranges::all_of(header.format,   [](auto& f) { return f == 0; });
45
}
46
47
DMKDiskImage::DMKDiskImage(Filename filename, std::shared_ptr<File> file_)
48
	: Disk(DiskName(std::move(filename)))
49
	, file(std::move(file_))
50
{
51
	DmkHeader header;
52
	file->seek(0);
53
	file->read(std::span{&header, 1});
54
	if (!isValidDmkHeader(header)) {
55
		throw MSXException("Not a DMK image");
56
	}
57
58
	numTracks = header.numTracks;
59
	dmkTrackLen = header.trackLen[0] + 256 * header.trackLen[1] - 128;
60
	singleSided = (header.flags & FLAG_SINGLE_SIDED) != 0;
61
	writeProtected = header.writeProtected == 0xff;
62
63
	// TODO should we print a warning when dmkTrackLen is too far from the
64
	//      ideal value RawTrack::SIZE? This might indicate the disk image
65
	//      was not a 3.5" DD disk image and data will be lost on either
66
	//      read or write.
67
}
68
69
void DMKDiskImage::seekTrack(uint8_t track, uint8_t side)
70
{
71
	size_t t = singleSided ? track : (2 * track + side);
72
	file->seek(sizeof(DmkHeader) + t * (dmkTrackLen + 128));
73
}
74
75
void DMKDiskImage::readTrack(uint8_t track, uint8_t side, RawTrack& output)
76
{
77
	assert(side < 2);
78
	output.clear(dmkTrackLen);
79
	if ((singleSided && side) || (track >= numTracks)) {
80
		// no such side/track, only clear output
81
		return;
82
	}
83
84
	seekTrack(track, side);
85
86
	// Read idam data (still needs to be converted).
87
	std::array<uint8_t, 2 * 64> idamBuf;
88
	file->read(idamBuf);
89
90
	// Read raw track data.
91
	file->read(output.getRawBuffer());
92
93
	// Convert idam data into an easier to work with internal format.
94
	int lastIdam = -1;
95
	for (auto i : xrange(64)) {
96
		unsigned idx = idamBuf[2 * i + 0] + 256 * idamBuf[2 * i + 1];
97
		if (idx == 0) break; // end of table reached
98
99
		if ((idx & IDAM_FLAGS_MASK) != FLAG_MFM_SECTOR) {
100
			// single density (FM) sector not yet supported, ignore
101
			continue;
102
		}
103
		idx &= ~IDAM_FLAGS_MASK; // clear flags
104
105
		if (idx < 128) {
106
			// Invalid IDAM offset, ignore
107
			continue;
108
		}
109
		idx -= 128;
110
		if (idx >= dmkTrackLen) {
111
			// Invalid IDAM offset, ignore
112
			continue;
113
		}
114
115
		if (int(idx) <= lastIdam) {
116
			// Invalid IDAM offset:
117
			//   must be strictly bigger than previous, ignore
118
			continue;
119
		}
120
121
		output.addIdam(idx);
122
		lastIdam = narrow<int>(idx);
123
	}
124
}
125
126
void DMKDiskImage::writeTrackImpl(uint8_t track, uint8_t side, const RawTrack& input)
127
{
128
	assert(side < 2);
129
	if (singleSided && (side != 0)) {
130
		// second side on a single-side image, ignore write.
131
		// TODO possible enhancement:  convert to double sided
132
		return;
133
	}
134
	if (numTracks <= track) [[unlikely]] {
135
		extendImageToTrack(track);
136
	}
137
	doWriteTrack(track, side, input);
138
}
139
140
void DMKDiskImage::doWriteTrack(uint8_t track, uint8_t side, const RawTrack& input)
141
{
142
	seekTrack(track, side);
143
144
	// Write idam table.
145
	std::array<uint8_t, 2 * 64> idamOut = {}; // zero-initialize
146
	for (const auto& idamIn = input.getIdamBuffer();
147
	     auto i : xrange(std::min(64, int(idamIn.size())))) {
148
		auto t = (idamIn[i] + 128) | FLAG_MFM_SECTOR;
149
		idamOut[2 * i + 0] = narrow<uint8_t>(t & 0xff);
150
		idamOut[2 * i + 1] = narrow<uint8_t>(t >> 8);
151
	}
152
	file->write(idamOut);
153
154
	// Write raw track data.
155
	assert(input.getLength() == dmkTrackLen);
156
	file->write(input.getRawBuffer());
157
}
158
159
void DMKDiskImage::extendImageToTrack(uint8_t track)
160
{
161
	// extend image with empty tracks
162
	RawTrack emptyTrack(dmkTrackLen);
163
	uint8_t numSides = singleSided ? 1 : 2;
164
	while (numTracks <= track) {
165
		for (auto side : xrange(numSides)) {
166
			doWriteTrack(numTracks, side, emptyTrack);
167
		}
168
		++numTracks;
169
	}
170
171
	// update header
172
	file->seek(1); // position in header where numTracks is stored
173
	std::array<uint8_t, 1> numTracksBuf = {numTracks};
174
	file->write(numTracksBuf);
175
}
176
177
178
void DMKDiskImage::readSectorImpl(size_t logicalSector, SectorBuffer& buf)
179
{
180
	auto [track, side, sector] = logToPhys(logicalSector);
181
	RawTrack rawTrack;
182
	readTrack(track, side, rawTrack);
183
184
	if (auto sectorInfo = rawTrack.decodeSector(sector)) {
185
		// TODO should we check sector size == 512?
186
		//      crc errors? correct track/head?
187
		rawTrack.readBlock(sectorInfo->dataIdx, buf.raw);
188
	} else {
189
		throw NoSuchSectorException("Sector not found");
190
	}
191
}
192
193
void DMKDiskImage::writeSectorImpl(size_t logicalSector, const SectorBuffer& buf)
194
{
195
	auto [track, side, sector] = logToPhys(logicalSector);
196
	RawTrack rawTrack;
197
	readTrack(track, side, rawTrack);
198
199
	if (auto sectorInfo = rawTrack.decodeSector(sector)) {
200
		// TODO do checks? see readSectorImpl()
201
		rawTrack.writeBlock(sectorInfo->dataIdx, buf.raw);
202
	} else {
203
		throw NoSuchSectorException("Sector not found");
204
	}
205
206
	writeTrack(track, side, rawTrack);
207
}
208
209
size_t DMKDiskImage::getNbSectorsImpl()
210
{
211
	size_t t = singleSided ? numTracks : (2 * numTracks);
212
	return t * getSectorsPerTrack();
213
}
214
215
bool DMKDiskImage::isWriteProtectedImpl() const
216
{
217
	return writeProtected || file->isReadOnly();
218
}
219
220
Sha1Sum DMKDiskImage::getSha1SumImpl(FilePool& filePool)
221
{
222
	return filePool.getSha1Sum(*file, getName().getResolved());
223
}
224
225
void DMKDiskImage::detectGeometryFallback()
226
{
227
	// The implementation in Disk::detectGeometryFallback() uses
228
	// getNbSectors(), but for DMK images that doesn't work before we know
229
	// the geometry.
230
231
	// detectGeometryFallback() is for example used when the boot sector
232
	// could not be read. For a DMK image this can happen when that sector
233
	// has CRC errors or is missing or deleted.
234
235
	setSectorsPerTrack(9);
236
	setNbSides(singleSided ? 1 : 2);
237
}
238
239
} // namespace openmsx