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