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