20static constexpr unsigned DOS1_MAX_CLUSTER_COUNT = 0x3FE;
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);
31static constexpr std::array<char, 11> SUNRISE_PARTITION_TABLE_HEADER = {
32 '\353',
'\376',
'\220',
'M',
'S',
'X',
'_',
'I',
'D',
'E',
' '
34static constexpr std::array<char, 11> NEXTOR_PARTITION_TABLE_HEADER = {
35 '\353',
'\376',
'\220',
'N',
'E',
'X',
'T',
'O',
'R',
'2',
'0'
37[[nodiscard]]
static std::optional<PartitionTableType> getPartitionTableType(
const SectorBuffer& buf)
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 &&
43 buf.ptNextor.part[0].start == 1) {
53 return getPartitionTableType(buf).has_value();
57static Partition& getPartitionNextorExtended(
59 unsigned remaining,
unsigned ebrOuterSector)
61 unsigned ebrSector = ebrOuterSector;
72 p.start = ebrSector + p.start;
78 if (link.start == 0) {
80 }
else if (link.sys_ind !=
one_of(0x05, 0x0F)) {
81 throw CommandException(
"Invalid extended boot record.");
83 ebrSector = ebrOuterSector + link.start;
86 throw CommandException(
"No partition number ",
partition);
90static Partition& getPartitionNextor(
91 const SectorAccessibleDisk& disk,
unsigned partition, SectorBuffer& buf)
94 for (
auto& p : buf.ptNextor.part) {
97 }
else if (p.sys_ind ==
one_of(0x05, 0x0F)) {
98 return getPartitionNextorExtended(disk,
partition, buf, remaining, p.start);
99 }
else if (remaining == 0) {
104 throw CommandException(
"No partition number ",
partition);
108static Partition& getPartitionSunrise(
unsigned partition, SectorBuffer& buf)
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(),
").");
117 auto& p = buf.ptSunrise.part[buf.ptSunrise.part.size() -
partition];
119 throw CommandException(
"No partition number ",
partition);
129 auto partitionTableType = getPartitionTableType(buf);
131 return getPartitionSunrise(
partition, buf);
133 return getPartitionNextor(disk,
partition, buf);
179 uint8_t nbReservedSectors = 1;
180 uint8_t nbHiddenSectors = 1;
184 uint16_t nbSides = 2;
186 uint16_t nbSectorsPerFat = 3;
187 uint8_t nbSectorsPerCluster = 2;
188 uint16_t nbDirEntry = 112;
189 uint8_t descriptor = 0xF9;
203 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 16,
size_t(4),
size_t(128)));
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);
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;
226 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 12,
size_t(1),
size_t(16)));
231 unsigned maxClusterCount = FAT12_MAX_CLUSTER_COUNT;
232 if (nbSectors <= 0x8000) {
233 maxClusterCount = DOS1_MAX_CLUSTER_COUNT - 1;
234 nbSectorsPerCluster *= 4;
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;
242 nbSectorsPerFat = narrow<uint16_t>((fatSize + SECTOR_SIZE - 1) / SECTOR_SIZE);
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;
258 nbSectorsPerCluster = narrow<uint8_t>(std::clamp(std::bit_ceil(nbSectors) >> 10,
size_t(2),
size_t(64)));
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;
265 nbSectorsPerFat = narrow<uint16_t>((fatSize + SECTOR_SIZE - 1) / SECTOR_SIZE);
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) {
278 nbSectorsPerFat = 12;
279 nbSectorsPerCluster = 16;
282 nbHiddenSectors = 16;
286 if (nbSectors > 65535) nbSectors = 65535;
287 }
else if (nbSectors > 16388) {
292 nbSectorsPerFat = 12;
293 nbSectorsPerCluster = 8;
296 }
else if (nbSectors > 8212) {
301 nbSectorsPerFat = 12;
302 nbSectorsPerCluster = 4;
305 }
else if (nbSectors > 4126) {
310 nbSectorsPerFat = 12;
311 nbSectorsPerCluster = 2;
314 }
else if (nbSectors > 2880) {
320 nbSectorsPerCluster = 2;
323 }
else if (nbSectors > 1440) {
329 nbSectorsPerCluster = 2;
332 }
else if (nbSectors > 720) {
338 nbSectorsPerCluster = 2;
348 nbSectorsPerCluster = 2;
354 assert(nbDirEntry % 16 == 0);
356 if (nbSectors < 0x10000) {
357 boot.
nrSectors = narrow<uint16_t>(nbSectors);
361 throw CommandException(
"Too many sectors for FAT12 ", nbSectors);
378 params.vol_id = vol_id;
381 if (nbSectors <= 0x80'0000) {
382 params.
nrSectors = narrow<unsigned>(nbSectors);
384 throw CommandException(
"Too many sectors for FAT16 ", nbSectors);
386 params.hiddenSectors = nbHiddenSectors;
387 params.vol_id = vol_id;
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;
454[[nodiscard]]
static constexpr CHS logicalToCHS(
unsigned logical)
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};
467static std::vector<unsigned> clampPartitionSizes(std::span<const unsigned> sizes,
468 size_t diskSize,
unsigned initialOffset,
unsigned perPartitionOffset)
470 std::vector<unsigned> clampedSizes;
471 size_t sizeRemaining = std::min(diskSize,
size_t(std::numeric_limits<unsigned>::max()));
473 if (sizeRemaining >= initialOffset) {
474 sizeRemaining -= initialOffset;
479 for (
auto size : sizes) {
480 if (sizeRemaining <= perPartitionOffset) {
483 sizeRemaining -= perPartitionOffset;
484 if (size <= perPartitionOffset) {
485 throw CommandException(
"Partition size too small: ", size);
487 size -= perPartitionOffset;
488 if (sizeRemaining > size) {
489 clampedSizes.push_back(size);
490 sizeRemaining -=
size;
492 clampedSizes.push_back(narrow<unsigned>(sizeRemaining));
500static std::vector<unsigned> partitionNextor(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
503 auto& pt = buf.ptNextor;
505 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 0, 1);
507 if (clampedSizes.empty()) {
509 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
511 disk.writeSector(0, buf);
515 unsigned ptSector = 0;
516 for (
auto [i, size] :
enumerate(clampedSizes)) {
519 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
524 auto& p = pt.part[0];
525 p.boot_ind = (i == 0) ? 0x80 : 0x00;
526 p.sys_ind =
size > 0x1'0000 ? 0x0E : 0x01;
531 if (i != clampedSizes.size() - 1) {
532 auto& link = pt.part[1];
535 link.start = ptSector + 1 +
size;
536 link.size =
sum(
view::drop(sizes, 1), [](
unsigned s) {
return 1 + s; });
538 link.start = ptSector + 1 +
size - (1 + clampedSizes[0]);
539 link.size = 1 + clampedSizes[i + 1];
543 disk.writeSector(ptSector, buf);
545 ptSector += 1 +
size;
551static std::vector<unsigned> partitionSunrise(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
554 auto& pt = buf.ptSunrise;
556 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 1, 0);
557 if (clampedSizes.size() > pt.part.size()) {
558 clampedSizes.resize(pt.part.size());
562 pt.header = SUNRISE_PARTITION_TABLE_HEADER;
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;
575 p.sector = startSector;
576 p.cyl = narrow_cast<uint8_t>(startCylinder);
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;
585 disk.writeSector(0, buf);
590static std::vector<unsigned> partitionBeer(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
593 auto& pt = buf.ptNextor;
595 std::vector<unsigned> clampedSizes = clampPartitionSizes(sizes, disk.getNbSectors(), 1, 0);
596 if (clampedSizes.size() > pt.part.size()) {
597 clampedSizes.resize(pt.part.size());
601 pt.header = NEXTOR_PARTITION_TABLE_HEADER;
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;
609 p.start = partitionOffset;
611 partitionOffset +=
size;
614 disk.writeSector(0, buf);
620 std::vector<unsigned> clampedSizes = [&] {
622 return partitionNextor(disk, sizes);
624 return partitionSunrise(disk, sizes);
626 return partitionBeer(disk, sizes);
632 for (
auto [i, size] :
enumerate(clampedSizes)) {
634 format(diskPartition, bootType, size);
637 return narrow<unsigned>(clampedSizes.size());
642 if (
const 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));
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;
670 return strCat((attrib & DIRECTORY ?
'd' :
'-'),
671 (attrib & READONLY ?
'r' :
'-'),
672 (attrib & HIDDEN ?
'h' :
'-'),
673 (attrib & VOLUME ?
'v' :
'-'),
674 (attrib & ARCHIVE ?
'a' :
'-'));