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