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