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