23static constexpr unsigned DOS1_MAX_CLUSTER_COUNT = 0x3FE;
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);
34static constexpr std::array<char, 11> SUNRISE_PARTITION_TABLE_HEADER = {
35 '\353',
'\376',
'\220',
'M',
'S',
'X',
'_',
'I',
'D',
'E',
' '
37static constexpr std::array<char, 11> NEXTOR_PARTITION_TABLE_HEADER = {
38 '\353',
'\376',
'\220',
'N',
'E',
'X',
'T',
'O',
'R',
'2',
'0'
40[[nodiscard]]
static std::optional<PartitionTableType> getPartitionTableType(
const SectorBuffer& buf)
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 &&
46 buf.ptNextor.part[0].start == 1) {
56 return getPartitionTableType(buf).has_value();
60static Partition& getPartitionNextorExtended(
62 unsigned remaining,
unsigned ebrOuterSector)
64 unsigned ebrSector = ebrOuterSector;
75 p.start = ebrSector + p.start;
81 if (link.start == 0) {
83 }
else if (link.sys_ind !=
one_of(0x05, 0x0F)) {
84 throw CommandException(
"Invalid extended boot record.");
86 ebrSector = ebrOuterSector + link.start;
89 throw CommandException(
"No partition number ",
partition);
93static Partition& getPartitionNextor(
94 const SectorAccessibleDisk& disk,
unsigned partition, SectorBuffer& buf)
97 for (
auto& p : buf.ptNextor.part) {
100 }
else if (p.sys_ind ==
one_of(0x05, 0x0F)) {
101 return getPartitionNextorExtended(disk,
partition, buf, remaining, p.start);
102 }
else if (remaining == 0) {
107 throw CommandException(
"No partition number ",
partition);
111static Partition& getPartitionSunrise(
unsigned partition, SectorBuffer& buf)
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(),
").");
120 auto& p = buf.ptSunrise.part[buf.ptSunrise.part.size() -
partition];
122 throw CommandException(
"No partition number ",
partition);
132 auto partitionTableType = getPartitionTableType(buf);
134 return getPartitionSunrise(
partition, buf);
136 return getPartitionNextor(disk,
partition, buf);
182 uint8_t nbReservedSectors = 1;
183 uint8_t nbHiddenSectors = 1;
187 uint16_t nbSides = 2;
189 uint16_t nbSectorsPerFat = 3;
190 uint8_t nbSectorsPerCluster = 2;
191 uint16_t nbDirEntry = 112;
192 uint8_t descriptor = 0xF9;
206 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 16,
size_t(4),
size_t(128)));
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);
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;
229 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 12,
size_t(1),
size_t(16)));
234 unsigned maxClusterCount = FAT12_MAX_CLUSTER_COUNT;
235 if (nbSectors <= 0x8000) {
236 maxClusterCount = DOS1_MAX_CLUSTER_COUNT - 1;
237 nbSectorsPerCluster *= 4;
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;
245 nbSectorsPerFat = narrow<uint16_t>((fatSize + SECTOR_SIZE - 1) / SECTOR_SIZE);
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;
261 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 10,
size_t(2),
size_t(64)));
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;
268 nbSectorsPerFat = narrow<uint16_t>((fatSize + SECTOR_SIZE - 1) / SECTOR_SIZE);
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) {
281 nbSectorsPerFat = 12;
282 nbSectorsPerCluster = 16;
285 nbHiddenSectors = 16;
289 if (nbSectors > 65535) nbSectors = 65535;
290 }
else if (nbSectors > 16388) {
295 nbSectorsPerFat = 12;
296 nbSectorsPerCluster = 8;
299 }
else if (nbSectors > 8212) {
304 nbSectorsPerFat = 12;
305 nbSectorsPerCluster = 4;
308 }
else if (nbSectors > 4126) {
313 nbSectorsPerFat = 12;
314 nbSectorsPerCluster = 2;
317 }
else if (nbSectors > 2880) {
323 nbSectorsPerCluster = 2;
326 }
else if (nbSectors > 1440) {
332 nbSectorsPerCluster = 2;
335 }
else if (nbSectors > 720) {
341 nbSectorsPerCluster = 2;
351 nbSectorsPerCluster = 2;
357 assert(nbDirEntry % 16 == 0);
359 if (nbSectors < 0x10000) {
360 boot.
nrSectors = narrow<uint16_t>(nbSectors);
364 throw CommandException(
"Too many sectors for FAT12 ", nbSectors);
381 params.vol_id = vol_id;
384 if (nbSectors <= 0x80'0000) {
385 params.
nrSectors = narrow<unsigned>(nbSectors);
387 throw CommandException(
"Too many sectors for FAT16 ", nbSectors);
389 params.hiddenSectors = nbHiddenSectors;
390 params.vol_id = vol_id;
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;
457[[nodiscard]]
static constexpr CHS logicalToCHS(
unsigned logical)
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};
470static std::vector<unsigned> clampPartitionSizes(std::span<const unsigned> sizes,
471 size_t diskSize,
unsigned initialOffset,
unsigned perPartitionOffset)
473 std::vector<unsigned> clampedSizes;
474 size_t sizeRemaining = std::min(diskSize,
size_t(std::numeric_limits<unsigned>::max()));
476 if (sizeRemaining >= initialOffset) {
477 sizeRemaining -= initialOffset;
482 for (
auto size : sizes) {
483 if (sizeRemaining <= perPartitionOffset) {
486 sizeRemaining -= perPartitionOffset;
487 if (size <= perPartitionOffset) {
488 throw CommandException(
"Partition size too small: ", size);
490 size -= perPartitionOffset;
491 if (sizeRemaining > size) {
492 clampedSizes.push_back(size);
493 sizeRemaining -=
size;
495 clampedSizes.push_back(narrow<unsigned>(sizeRemaining));
503static std::vector<unsigned> partitionNextor(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
506 auto& pt = buf.ptNextor;
508 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 0, 1);
510 if (clampedSizes.empty()) {
512 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
514 disk.writeSector(0, buf);
518 unsigned ptSector = 0;
519 for (
auto [i, size] :
enumerate(clampedSizes)) {
522 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
527 auto& p = pt.part[0];
528 p.boot_ind = (i == 0) ? 0x80 : 0x00;
529 p.sys_ind =
size > 0x1'0000 ? 0x0E : 0x01;
534 if (i != clampedSizes.size() - 1) {
535 auto& link = pt.part[1];
538 link.start = ptSector + 1 +
size;
539 link.size =
sum(std::views::drop(sizes, 1), [](
unsigned s) {
return 1 + s; });
541 link.start = ptSector + 1 +
size - (1 + clampedSizes[0]);
542 link.size = 1 + clampedSizes[i + 1];
546 disk.writeSector(ptSector, buf);
548 ptSector += 1 +
size;
554static std::vector<unsigned> partitionSunrise(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
557 auto& pt = buf.ptSunrise;
559 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 1, 0);
560 if (clampedSizes.size() > pt.part.size()) {
561 clampedSizes.resize(pt.part.size());
565 pt.header = SUNRISE_PARTITION_TABLE_HEADER;
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;
578 p.sector = startSector;
579 p.cyl = narrow_cast<uint8_t>(startCylinder);
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;
588 disk.writeSector(0, buf);
593static std::vector<unsigned> partitionBeer(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
596 auto& pt = buf.ptNextor;
598 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 1, 0);
599 if (clampedSizes.size() > pt.part.size()) {
600 clampedSizes.resize(pt.part.size());
604 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
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;
612 p.start = partitionOffset;
614 partitionOffset +=
size;
617 disk.writeSector(0, buf);
623 std::vector<unsigned> clampedSizes = [&] {
625 return partitionNextor(disk, sizes);
627 return partitionSunrise(disk, sizes);
629 return partitionBeer(disk, sizes);
635 for (
auto [i, size] :
enumerate(clampedSizes)) {
637 format(diskPartition, bootType, size);
640 return narrow<unsigned>(clampedSizes.size());
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));
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;
673 return strCat((attrib & DIRECTORY ?
'd' :
'-'),
674 (attrib & READONLY ?
'r' :
'-'),
675 (attrib & HIDDEN ?
'h' :
'-'),
676 (attrib & VOLUME ?
'v' :
'-'),
677 (attrib & ARCHIVE ?
'a' :
'-'));