openMSX
DiskImageUtils.cc
Go to the documentation of this file.
1#include "DiskImageUtils.hh"
2#include "DiskPartition.hh"
3#include "CommandException.hh"
4#include "BootBlocks.hh"
5#include "endian.hh"
6#include "enumerate.hh"
7#include "one_of.hh"
8#include "random.hh"
9#include "ranges.hh"
10#include "strCat.hh"
11#include "view.hh"
12#include "xrange.hh"
13#include <algorithm>
14#include <bit>
15#include <cassert>
16#include <ctime>
17
19
20static constexpr unsigned DOS1_MAX_CLUSTER_COUNT = 0x3FE; // Max 3 sectors per FAT
21static constexpr unsigned FAT12_MAX_CLUSTER_COUNT = 0xFF4;
22static constexpr unsigned FAT16_MAX_CLUSTER_COUNT = 0xFFF4;
23static constexpr unsigned SECTOR_SIZE = sizeof(SectorBuffer);
24static constexpr unsigned DIR_ENTRIES_PER_SECTOR = SECTOR_SIZE / sizeof(MSXDirEntry);
25
28 NEXTOR
29};
30
31static constexpr std::array<char, 11> SUNRISE_PARTITION_TABLE_HEADER = {
32 '\353', '\376', '\220', 'M', 'S', 'X', '_', 'I', 'D', 'E', ' '
33};
34static constexpr std::array<char, 11> NEXTOR_PARTITION_TABLE_HEADER = {
35 '\353', '\376', '\220', 'N', 'E', 'X', 'T', 'O', 'R', '2', '0'
36};
37[[nodiscard]] static std::optional<PartitionTableType> getPartitionTableType(const SectorBuffer& buf)
38{
39 if (buf.ptSunrise.header == SUNRISE_PARTITION_TABLE_HEADER) {
41 } else if (buf.ptNextor.header == NEXTOR_PARTITION_TABLE_HEADER &&
42 buf.ptNextor.part[0].sys_ind != 0 && // Extra checks to distinguish MBR from VBR
43 buf.ptNextor.part[0].start == 1) { // since they have the same OEM signature.
45 }
46 return {};
47}
48
50{
51 SectorBuffer buf;
52 disk.readSector(0, buf);
53 return getPartitionTableType(buf).has_value();
54}
55
56// Get partition from Nextor extended boot record (standard EBR) chain.
57static Partition& getPartitionNextorExtended(
58 const SectorAccessibleDisk& disk, unsigned partition, SectorBuffer& buf,
59 unsigned remaining, unsigned ebrOuterSector)
60{
61 unsigned ebrSector = ebrOuterSector;
62 while (true) {
63 disk.readSector(ebrSector, buf);
64
65 if (remaining == 0) {
66 // EBR partition entry. Start is relative to *this* EBR sector.
67 auto& p = buf.ptNextor.part[0];
68 if (p.start == 0) {
69 break;
70 }
71 // Adjust to absolute address before returning.
72 p.start = ebrSector + p.start;
73 return p;
74 }
75
76 // EBR link entry. Start is relative to *outermost* EBR sector.
77 auto& link = buf.ptNextor.part[1];
78 if (link.start == 0) {
79 break;
80 } else if (link.sys_ind != one_of(0x05, 0x0F)) {
81 throw CommandException("Invalid extended boot record.");
82 }
83 ebrSector = ebrOuterSector + link.start;
84 remaining--;
85 }
86 throw CommandException("No partition number ", partition);
87}
88
89// Get partition from Nextor master boot record (standard MBR).
90static Partition& getPartitionNextor(
91 const SectorAccessibleDisk& disk, unsigned partition, SectorBuffer& buf)
92{
93 unsigned remaining = partition - 1;
94 for (auto& p : buf.ptNextor.part) {
95 if (p.start == 0) {
96 break;
97 } else if (p.sys_ind == one_of(0x05, 0x0F)) {
98 return getPartitionNextorExtended(disk, partition, buf, remaining, p.start);
99 } else if (remaining == 0) {
100 return p;
101 }
102 remaining--;
103 }
104 throw CommandException("No partition number ", partition);
105}
106
107// Get partition from Sunrise IDE master boot record.
108static Partition& getPartitionSunrise(unsigned partition, SectorBuffer& buf)
109{
110 // check number in range
111 if (partition < 1 || partition > buf.ptSunrise.part.size()) {
112 throw CommandException(
113 "Invalid partition number specified (must be 1-",
114 buf.ptSunrise.part.size(), ").");
115 }
116 // check valid partition number
117 auto& p = buf.ptSunrise.part[buf.ptSunrise.part.size() - partition];
118 if (p.start == 0) {
119 throw CommandException("No partition number ", partition);
120 }
121 return p;
122}
123
125{
126 // check drive has a partition table
127 // check valid partition number and return the entry
128 disk.readSector(0, buf);
129 auto partitionTableType = getPartitionTableType(buf);
130 if (partitionTableType == PartitionTableType::SUNRISE_IDE) {
131 return getPartitionSunrise(partition, buf);
132 } else if (partitionTableType == PartitionTableType::NEXTOR) {
133 return getPartitionNextor(disk, partition, buf);
134 } else {
135 throw CommandException("No (or invalid) partition table.");
136 }
137}
138
140{
141 SectorBuffer buf;
142 Partition& p = getPartition(disk, partition, buf);
143
144 // check partition type
145 if (p.sys_ind != one_of(0x01, 0x04, 0x06, 0x0E)) {
146 throw CommandException("Only FAT12 and FAT16 partitions are supported.");
147 }
148}
149
150
151// Create a correct boot sector depending on the required size of the filesystem
154 unsigned fatCount;
155 unsigned fatStart;
156 unsigned rootDirStart;
157 unsigned dataStart;
158 uint8_t descriptor;
159 bool fat16;
160};
161static SetBootSectorResult setBootSector(
162 MSXBootSector& boot, MSXBootSectorType bootType, size_t nbSectors)
163{
164 // start from the default boot block ..
165 if (bootType == MSXBootSectorType::DOS1) {
167 } else if (bootType == MSXBootSectorType::DOS2) {
169 } else if (bootType == MSXBootSectorType::NEXTOR && nbSectors > 0x10000) {
171 } else if (bootType == MSXBootSectorType::NEXTOR) {
173 } else {
175 }
176
177 // .. and fill-in image-size dependent parameters ..
178 // these are the same for most formats
179 uint8_t nbReservedSectors = 1;
180 uint8_t nbHiddenSectors = 1;
181 uint32_t vol_id = random_32bit() & 0x7F7F7F7F; // why are bits masked?;
182
183 // all these are set below (but initialize here to avoid warning)
184 uint16_t nbSides = 2;
185 uint8_t nbFats = 2;
186 uint16_t nbSectorsPerFat = 3;
187 uint8_t nbSectorsPerCluster = 2;
188 uint16_t nbDirEntry = 112;
189 uint8_t descriptor = 0xF9;
190 bool fat16 = false;
191
192 // now set correct info according to size of image (in sectors!)
193 if (bootType == MSXBootSectorType::NEXTOR && nbSectors > 0x10000) {
194 // using the same layout as Nextor 2.1.1’s FDISK
195 nbSides = 0;
196 nbFats = 2;
197 nbDirEntry = 512;
198 descriptor = 0xF0;
199 nbHiddenSectors = 0;
200 fat16 = true;
201
202 // <= 128 MB: 4, <= 256 MB: 8, ..., <= 4 GB: 128
203 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 16, size_t(4), size_t(128)));
204
205 // Calculate fat size based on cluster count estimate
206 unsigned fatStart = nbReservedSectors + nbDirEntry / DIR_ENTRIES_PER_SECTOR;
207 unsigned estSectorCount = narrow<unsigned>(nbSectors - fatStart);
208 unsigned estClusterCount = std::min(estSectorCount / nbSectorsPerCluster, FAT16_MAX_CLUSTER_COUNT);
209 unsigned fatSize = 2 * (estClusterCount + 2);
210 nbSectorsPerFat = narrow<uint16_t>((fatSize + SECTOR_SIZE - 1) / SECTOR_SIZE); // round up
211
212 // Adjust sectors count down to match cluster count
213 unsigned dataStart = fatStart + nbFats * nbSectorsPerFat;
214 unsigned dataSectorCount = narrow<unsigned>(nbSectors - dataStart);
215 unsigned clusterCount = std::min(dataSectorCount / nbSectorsPerCluster, FAT16_MAX_CLUSTER_COUNT);
216 nbSectors = dataStart + clusterCount * nbSectorsPerCluster;
217 } else if (bootType == MSXBootSectorType::NEXTOR) {
218 // using the same layout as Nextor 2.1.1’s FDISK
219 nbSides = 0;
220 nbFats = 2;
221 nbDirEntry = 112;
222 descriptor = 0xF0;
223 nbHiddenSectors = 0;
224
225 // <= 2 MB: 1, <= 4 MB: 2, ..., <= 32 MB: 16
226 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 12, size_t(1), size_t(16)));
227
228 // Partitions <= 16 MB are defined to have at most 3 sectors per FAT,
229 // so that they can boot DOS 1. This limits the cluster count to 1022.
230 // And for some unknown reason, Nextor limits it to one less than that.
231 unsigned maxClusterCount = FAT12_MAX_CLUSTER_COUNT;
232 if (nbSectors <= 0x8000) {
233 maxClusterCount = DOS1_MAX_CLUSTER_COUNT - 1;
234 nbSectorsPerCluster *= 4; // <= 2 MB: 4, <= 4 MB: 8, ..., <= 16 MB: 32
235 }
236
237 // Calculate fat size based on cluster count estimate
238 unsigned fatStart = nbReservedSectors + nbDirEntry / DIR_ENTRIES_PER_SECTOR;
239 unsigned estSectorCount = narrow<unsigned>(nbSectors - fatStart);
240 unsigned estClusterCount = std::min(estSectorCount / nbSectorsPerCluster, maxClusterCount);
241 unsigned fatSize = (3 * (estClusterCount + 2) + 1) / 2; // round up
242 nbSectorsPerFat = narrow<uint16_t>((fatSize + SECTOR_SIZE - 1) / SECTOR_SIZE); // round up
243
244 // Adjust sectors count down to match cluster count
245 unsigned dataStart = fatStart + nbFats * nbSectorsPerFat;
246 unsigned dataSectorCount = narrow<unsigned>(nbSectors - dataStart);
247 unsigned clusterCount = std::min(dataSectorCount / nbSectorsPerCluster, maxClusterCount);
248 nbSectors = dataStart + clusterCount * nbSectorsPerCluster;
249 } else if (bootType == MSXBootSectorType::DOS1 && nbSectors > 1440) {
250 // DOS1 supports up to 3 sectors per FAT, limiting the cluster count to 1022.
251 nbSides = 0;
252 nbFats = 2;
253 nbDirEntry = 112;
254 descriptor = 0xF0;
255 nbHiddenSectors = 0;
256
257 // <= 1 MB: 2, <= 2 MB: 4, ..., <= 32 MB: 64
258 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 10, size_t(2), size_t(64)));
259
260 // Calculate fat size based on cluster count estimate
261 unsigned fatStart = nbReservedSectors + nbDirEntry / DIR_ENTRIES_PER_SECTOR;
262 unsigned estSectorCount = narrow<unsigned>(nbSectors - fatStart);
263 unsigned estClusterCount = std::min(estSectorCount / nbSectorsPerCluster, DOS1_MAX_CLUSTER_COUNT);
264 unsigned fatSize = (3 * (estClusterCount + 2) + 1) / 2; // round up
265 nbSectorsPerFat = narrow<uint16_t>((fatSize + SECTOR_SIZE - 1) / SECTOR_SIZE); // round up
266
267 // Adjust sectors count down to match cluster count
268 unsigned dataStart = fatStart + nbFats * nbSectorsPerFat;
269 unsigned dataSectorCount = narrow<unsigned>(nbSectors - dataStart);
270 unsigned clusterCount = std::min(dataSectorCount / nbSectorsPerCluster, DOS1_MAX_CLUSTER_COUNT);
271 nbSectors = dataStart + clusterCount * nbSectorsPerCluster;
272 } else if (nbSectors > 32732) {
273 // using the same layout as used by Jon in IDEFDISK v 3.1
274 // 32732 < nbSectors
275 // note: this format is only valid for nbSectors <= 65536
276 nbSides = 32; // copied from a partition from an IDE HD
277 nbFats = 2;
278 nbSectorsPerFat = 12; // copied from a partition from an IDE HD
279 nbSectorsPerCluster = 16;
280 nbDirEntry = 256;
281 descriptor = 0xF0;
282 nbHiddenSectors = 16; // override default from above
283 // for a 32MB disk or greater the sectors would be >= 65536
284 // since MSX use 16 bits for this, in case of sectors = 65536
285 // the truncated word will be 0 -> formatted as 320 Kb disk!
286 if (nbSectors > 65535) nbSectors = 65535; // this is the max size for fat12 :-)
287 } else if (nbSectors > 16388) {
288 // using the same layout as used by Jon in IDEFDISK v 3.1
289 // 16388 < nbSectors <= 32732
290 nbSides = 2; // unknown yet
291 nbFats = 2;
292 nbSectorsPerFat = 12;
293 nbSectorsPerCluster = 8;
294 nbDirEntry = 256;
295 descriptor = 0XF0;
296 } else if (nbSectors > 8212) {
297 // using the same layout as used by Jon in IDEFDISK v 3.1
298 // 8212 < nbSectors <= 16388
299 nbSides = 2; // unknown yet
300 nbFats = 2;
301 nbSectorsPerFat = 12;
302 nbSectorsPerCluster = 4;
303 nbDirEntry = 256;
304 descriptor = 0xF0;
305 } else if (nbSectors > 4126) {
306 // using the same layout as used by Jon in IDEFDISK v 3.1
307 // 4126 < nbSectors <= 8212
308 nbSides = 2; // unknown yet
309 nbFats = 2;
310 nbSectorsPerFat = 12;
311 nbSectorsPerCluster = 2;
312 nbDirEntry = 256;
313 descriptor = 0xF0;
314 } else if (nbSectors > 2880) {
315 // using the same layout as used by Jon in IDEFDISK v 3.1
316 // 2880 < nbSectors <= 4126
317 nbSides = 2; // unknown yet
318 nbFats = 2;
319 nbSectorsPerFat = 6;
320 nbSectorsPerCluster = 2;
321 nbDirEntry = 224;
322 descriptor = 0xF0;
323 } else if (nbSectors > 1440) {
324 // using the same layout as used by Jon in IDEFDISK v 3.1
325 // 1440 < nbSectors <= 2880
326 nbSides = 2; // unknown yet
327 nbFats = 2;
328 nbSectorsPerFat = 5;
329 nbSectorsPerCluster = 2;
330 nbDirEntry = 112;
331 descriptor = 0xF0;
332 } else if (nbSectors > 720) {
333 // normal double sided disk
334 // 720 < nbSectors <= 1440
335 nbSides = 2;
336 nbFats = 2;
337 nbSectorsPerFat = 3;
338 nbSectorsPerCluster = 2;
339 nbDirEntry = 112;
340 descriptor = 0xF9;
341 nbSectors = 1440; // force nbSectors to 1440, why?
342 } else {
343 // normal single sided disk
344 // nbSectors <= 720
345 nbSides = 1;
346 nbFats = 2;
347 nbSectorsPerFat = 2;
348 nbSectorsPerCluster = 2;
349 nbDirEntry = 112;
350 descriptor = 0xF8;
351 nbSectors = 720; // force nbSectors to 720, why?
352 }
353
354 assert(nbDirEntry % 16 == 0); // Non multiples of 16 not supported.
355
356 if (nbSectors < 0x10000) {
357 boot.nrSectors = narrow<uint16_t>(nbSectors);
358 } else if (bootType == MSXBootSectorType::NEXTOR && fat16) {
359 boot.nrSectors = 0;
360 } else {
361 throw CommandException("Too many sectors for FAT12 ", nbSectors);
362 }
363
364 boot.nrSides = nbSides;
365 boot.spCluster = nbSectorsPerCluster;
366 boot.nrFats = nbFats;
367 boot.sectorsFat = nbSectorsPerFat;
368 boot.dirEntries = nbDirEntry;
369 boot.descriptor = descriptor;
370 boot.resvSectors = nbReservedSectors;
371
372 if (bootType == MSXBootSectorType::DOS1) {
373 auto& params = boot.params.dos1;
374 params.hiddenSectors = nbHiddenSectors;
375 } else if (bootType == MSXBootSectorType::DOS2 || (bootType == MSXBootSectorType::NEXTOR && !fat16)) {
376 auto& params = boot.params.dos2;
377 params.hiddenSectors = nbHiddenSectors;
378 params.vol_id = vol_id;
379 } else if (bootType == MSXBootSectorType::NEXTOR && fat16) {
380 auto& params = boot.params.extended;
381 if (nbSectors <= 0x80'0000) {
382 params.nrSectors = narrow<unsigned>(nbSectors);
383 } else {
384 throw CommandException("Too many sectors for FAT16 ", nbSectors);
385 }
386 params.hiddenSectors = nbHiddenSectors;
387 params.vol_id = vol_id;
388 } else {
390 }
391
392 SetBootSectorResult result;
393 result.sectorsPerFat = nbSectorsPerFat;
394 result.fatCount = nbFats;
395 result.fatStart = nbReservedSectors;
396 result.rootDirStart = result.fatStart + nbFats * nbSectorsPerFat;
397 result.dataStart = result.rootDirStart + nbDirEntry / 16;
398 result.descriptor = descriptor;
399 result.fat16 = fat16;
400 return result;
401}
402
404{
405 format(disk, bootType, disk.getNbSectors());
406}
407
408void format(SectorAccessibleDisk& disk, MSXBootSectorType bootType, size_t nbSectors)
409{
410 // first create a boot sector for given partition size
411 nbSectors = std::min(nbSectors, disk.getNbSectors());
412 SectorBuffer buf;
413 SetBootSectorResult result = setBootSector(buf.bootSector, bootType, nbSectors);
414 disk.writeSector(0, buf);
415
416 // write empty FAT sectors (except for first sector, see below)
417 ranges::fill(buf.raw, 0);
418 for (auto fat : xrange(result.fatCount)) {
419 for (auto i : xrange(1u, result.sectorsPerFat)) {
420 disk.writeSector(i + result.fatStart + fat * result.sectorsPerFat, buf);
421 }
422 }
423
424 // write empty directory sectors
425 for (auto i : xrange(result.rootDirStart, result.dataStart)) {
426 disk.writeSector(i, buf);
427 }
428
429 // first FAT sector is special:
430 // - first byte contains the media descriptor
431 // - first two clusters must be marked as EOF
432 buf.raw[0] = result.descriptor;
433 buf.raw[1] = 0xFF;
434 buf.raw[2] = 0xFF;
435 if (result.fat16) {
436 buf.raw[3] = 0xFF;
437 }
438 for (auto fat : xrange(result.fatCount)) {
439 disk.writeSector(result.fatStart + fat * result.sectorsPerFat, buf);
440 }
441
442 // write 'empty' data sectors
443 ranges::fill(buf.raw, 0xE5);
444 for (auto i : xrange(result.dataStart, nbSectors)) {
445 disk.writeSector(i, buf);
446 }
447}
448
449struct CHS {
450 unsigned cylinder;
451 uint8_t head; // 0-15
452 uint8_t sector; // 1-32
453};
454[[nodiscard]] static constexpr CHS logicalToCHS(unsigned logical)
455{
456 // This is made to fit the openMSX hard disk configuration:
457 // 32 sectors/track 16 heads
458 unsigned tmp = logical + 1;
459 uint8_t sector = tmp % 32;
460 if (sector == 0) sector = 32;
461 tmp = (tmp - sector) / 32;
462 uint8_t head = tmp % 16;
463 unsigned cylinder = tmp / 16;
464 return {cylinder, head, sector};
465}
466
467static std::vector<unsigned> clampPartitionSizes(std::span<const unsigned> sizes,
468 size_t diskSize, unsigned initialOffset, unsigned perPartitionOffset)
469{
470 std::vector<unsigned> clampedSizes;
471 size_t sizeRemaining = std::min(diskSize, size_t(std::numeric_limits<unsigned>::max()));
472
473 if (sizeRemaining >= initialOffset) {
474 sizeRemaining -= initialOffset;
475 } else {
476 return clampedSizes;
477 }
478
479 for (auto size : sizes) {
480 if (sizeRemaining <= perPartitionOffset) {
481 break;
482 }
483 sizeRemaining -= perPartitionOffset;
484 if (size <= perPartitionOffset) {
485 throw CommandException("Partition size too small: ", size);
486 }
487 size -= perPartitionOffset;
488 if (sizeRemaining > size) {
489 clampedSizes.push_back(size);
490 sizeRemaining -= size;
491 } else {
492 clampedSizes.push_back(narrow<unsigned>(sizeRemaining));
493 break;
494 }
495 }
496 return clampedSizes;
497}
498
499// Partition with standard master / extended boot record (MBR / EBR) for Nextor.
500static std::vector<unsigned> partitionNextor(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
501{
502 SectorBuffer buf;
503 auto& pt = buf.ptNextor;
504
505 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 0, 1);
506
507 if (clampedSizes.empty()) {
508 ranges::fill(buf.raw, 0);
509 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
510 pt.end = 0xAA55;
511 disk.writeSector(0, buf);
512 return clampedSizes;
513 }
514
515 unsigned ptSector = 0;
516 for (auto [i, size] : enumerate(clampedSizes)) {
517 ranges::fill(buf.raw, 0);
518 if (i == 0) {
519 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
520 }
521 pt.end = 0xAA55;
522
523 // Add partition entry
524 auto& p = pt.part[0];
525 p.boot_ind = (i == 0) ? 0x80 : 0x00; // boot flag
526 p.sys_ind = size > 0x1'0000 ? 0x0E : 0x01; // FAT16B (LBA), or FAT12
527 p.start = 1;
528 p.size = size;
529
530 // Add link if not the last partition
531 if (i != clampedSizes.size() - 1) {
532 auto& link = pt.part[1];
533 link.sys_ind = 0x05; // EBR
534 if (i == 0) {
535 link.start = ptSector + 1 + size;
536 link.size = sum(view::drop(sizes, 1), [](unsigned s) { return 1 + s; });
537 } else {
538 link.start = ptSector + 1 + size - (1 + clampedSizes[0]);
539 link.size = 1 + clampedSizes[i + 1];
540 }
541 }
542
543 disk.writeSector(ptSector, buf);
544
545 ptSector += 1 + size;
546 }
547 return clampedSizes;
548}
549
550// Partition with Sunrise IDE master boot record.
551static std::vector<unsigned> partitionSunrise(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
552{
553 SectorBuffer buf;
554 auto& pt = buf.ptSunrise;
555
556 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 1, 0);
557 if (clampedSizes.size() > pt.part.size()) {
558 clampedSizes.resize(pt.part.size());
559 }
560
561 ranges::fill(buf.raw, 0);
562 pt.header = SUNRISE_PARTITION_TABLE_HEADER;
563 pt.end = 0xAA55;
564
565 unsigned partitionOffset = 1;
566 for (auto [i, size] : enumerate(clampedSizes)) {
567 unsigned partitionNbSectors = size;
568 auto& p = pt.part[30 - i];
569 auto [startCylinder, startHead, startSector] =
570 logicalToCHS(partitionOffset);
571 auto [endCylinder, endHead, endSector] =
572 logicalToCHS(partitionOffset + partitionNbSectors - 1);
573 p.boot_ind = (i == 0) ? 0x80 : 0x00; // boot flag
574 p.head = startHead;
575 p.sector = startSector;
576 p.cyl = narrow_cast<uint8_t>(startCylinder); // wraps for size larger than 64MB
577 p.sys_ind = 0x01; // FAT12
578 p.end_head = endHead;
579 p.end_sector = endSector;
580 p.end_cyl = narrow_cast<uint8_t>(endCylinder);
581 p.start = partitionOffset;
582 p.size = partitionNbSectors;
583 partitionOffset += partitionNbSectors;
584 }
585 disk.writeSector(0, buf);
586 return clampedSizes;
587}
588
589// Partition with standard master boot record (MBR) for Beer IDE 1.9.
590static std::vector<unsigned> partitionBeer(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
591{
592 SectorBuffer buf;
593 auto& pt = buf.ptNextor;
594
595 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 1, 0);
596 if (clampedSizes.size() > pt.part.size()) {
597 clampedSizes.resize(pt.part.size());
598 }
599
600 ranges::fill(buf.raw, 0);
601 pt.header = NEXTOR_PARTITION_TABLE_HEADER; // TODO: Find out BEER IDE signature
602 pt.end = 0xAA55;
603
604 unsigned partitionOffset = 1;
605 for (auto [i, size] : enumerate(clampedSizes)) {
606 auto& p = pt.part[i];
607 p.boot_ind = (i == 0) ? 0x80 : 0x00; // boot flag
608 p.sys_ind = 0x01; // FAT12
609 p.start = partitionOffset;
610 p.size = size;
611 partitionOffset += size;
612 }
613
614 disk.writeSector(0, buf);
615 return clampedSizes;
616}
617
618unsigned partition(SectorAccessibleDisk& disk, std::span<const unsigned> sizes, MSXBootSectorType bootType)
619{
620 std::vector<unsigned> clampedSizes = [&] {
621 if (bootType == MSXBootSectorType::NEXTOR) {
622 return partitionNextor(disk, sizes);
623 } else if (bootType == MSXBootSectorType::DOS2) {
624 return partitionSunrise(disk, sizes);
625 } else if (bootType == MSXBootSectorType::DOS1) {
626 return partitionBeer(disk, sizes);
627 } else {
629 }
630 }();
631
632 for (auto [i, size] : enumerate(clampedSizes)) {
633 DiskPartition diskPartition(disk, narrow<unsigned>(i + 1));
634 format(diskPartition, bootType, size);
635 }
636
637 return narrow<unsigned>(clampedSizes.size());
638}
639
640FatTimeDate toTimeDate(time_t totalSeconds)
641{
642 if (tm* mtim = localtime(&totalSeconds)) {
643 auto time = narrow<uint16_t>(
644 (std::min(mtim->tm_sec, 59) >> 1) + (mtim->tm_min << 5) +
645 (mtim->tm_hour << 11));
646 auto date = narrow<uint16_t>(
647 mtim->tm_mday + ((mtim->tm_mon + 1) << 5) +
648 (std::clamp(mtim->tm_year + 1900 - 1980, 0, 119) << 9));
649 return {time, date};
650 }
651 return {0, 0};
652}
653
654time_t fromTimeDate(FatTimeDate timeDate)
655{
656 struct tm tm{};
657 tm.tm_sec = std::clamp(((timeDate.time >> 0) & 31) * 2, 0, 60);
658 tm.tm_min = std::clamp(((timeDate.time >> 5) & 63), 0, 59);
659 tm.tm_hour = std::clamp(((timeDate.time >> 11) & 31), 0, 23);
660 tm.tm_mday = std::clamp((timeDate.date >> 0) & 31, 1, 31);
661 tm.tm_mon = std::clamp(((timeDate.date >> 5) & 15) - 1, 0, 11);
662 tm.tm_year = (timeDate.date >> 9) + 1980 - 1900;
663 tm.tm_isdst = -1;
664 return mktime(&tm);
665}
666
667std::string formatAttrib(uint8_t attrib)
668{
669 return strCat((attrib & MSXDirEntry::Attrib::DIRECTORY ? 'd' : '-'),
670 (attrib & MSXDirEntry::Attrib::READONLY ? 'r' : '-'),
671 (attrib & MSXDirEntry::Attrib::HIDDEN ? 'h' : '-'),
672 (attrib & MSXDirEntry::Attrib::VOLUME ? 'v' : '-'), // TODO check if this is the output of files,l
673 (attrib & MSXDirEntry::Attrib::ARCHIVE ? 'a' : '-')); // TODO check if this is the output of files,l
674}
675
676} // namespace openmsx::DiskImageUtils
static const SectorBuffer nextorBootBlockFAT12
Definition BootBlocks.hh:18
static const SectorBuffer dos2BootBlock
Definition BootBlocks.hh:15
static const SectorBuffer dos1BootBlock
Definition BootBlocks.hh:12
static const SectorBuffer nextorBootBlockFAT16
Definition BootBlocks.hh:21
void readSector(size_t sector, SectorBuffer &buf) const
void writeSector(size_t sector, const SectorBuffer &buf)
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition enumerate.hh:28
bool hasPartitionTable(const SectorAccessibleDisk &disk)
Check whether the given disk is partitioned.
void checkSupportedPartition(const SectorAccessibleDisk &disk, unsigned partition)
Check whether partition is of type FAT12 or FAT16.
std::string formatAttrib(uint8_t attrib)
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
Partition & getPartition(const SectorAccessibleDisk &disk, unsigned partition, SectorBuffer &buf)
Gets the requested partition.
time_t fromTimeDate(FatTimeDate timeDate)
unsigned partition(SectorAccessibleDisk &disk, std::span< const unsigned > sizes, MSXBootSectorType bootType)
Write a partition table to the given disk and format each partition.
FatTimeDate toTimeDate(time_t totalSeconds)
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
size_t size(std::string_view utf8)
constexpr auto drop(Range &&range, size_t n)
Definition view.hh:502
uint32_t random_32bit()
Return a random 32-bit value.
Definition random.hh:67
auto sum(InputRange &&range, Proj proj={})
Definition stl.hh:245
std::string strCat()
Definition strCat.hh:703
struct openmsx::MSXBootSector::@1::@2 dos1
struct openmsx::MSXBootSector::@1::@4 extended
struct openmsx::MSXBootSector::@1::@3 dos2
Endian::UA_L16 dirEntries
union openmsx::MSXBootSector::@1 params
std::array< Partition, 4 > part
std::array< uint8_t, 512 > raw
PartitionTableNextor ptNextor
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132