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 "random.hh"
8#include "xrange.hh"
9#include <cassert>
10#include <ctime>
11
13
14static constexpr std::array<char, 11> PARTITION_TABLE_HEADER = {
15 '\353', '\376', '\220', 'M', 'S', 'X', '_', 'I', 'D', 'E', ' '
16};
17[[nodiscard]] static bool isPartitionTableSector(const PartitionTable& pt)
18{
19 return pt.header == PARTITION_TABLE_HEADER;
20}
21
23{
24 SectorBuffer buf;
25 disk.readSector(0, buf);
26 return isPartitionTableSector(buf.pt);
27}
28
29
30static Partition& checkImpl(
31 SectorAccessibleDisk& disk, unsigned partition, SectorBuffer& buf)
32{
33 // check number in range
34 if (partition < 1 || partition > 31) {
35 throw CommandException(
36 "Invalid partition number specified (must be 1-31).");
37 }
38 // check drive has a partition table
39 disk.readSector(0, buf);
40 if (!isPartitionTableSector(buf.pt)) {
41 throw CommandException(
42 "No (or invalid) partition table.");
43 }
44 // check valid partition number
45 auto& p = buf.pt.part[31 - partition];
46 if (p.start == 0) {
47 throw CommandException("No partition number ", partition);
48 }
49 return p;
50}
51
53{
54 SectorBuffer buf;
55 checkImpl(disk, partition, buf);
56}
57
59{
60 SectorBuffer buf;
61 Partition& p = checkImpl(disk, partition, buf);
62
63 // check partition type
64 if (p.sys_ind != 0x01) {
65 throw CommandException("Only FAT12 partitions are supported.");
66 }
67}
68
69
70// Create a correct boot sector depending on the required size of the filesystem
73 uint8_t descriptor;
74};
75static SetBootSectorResult setBootSector(
76 MSXBootSector& boot, size_t nbSectors, bool dos1)
77{
78 // start from the default boot block ..
79 const auto& defaultBootBlock = dos1 ? BootBlocks::dos1BootBlock : BootBlocks::dos2BootBlock;
80 boot = defaultBootBlock.bootSector;
81
82 // .. and fill-in image-size dependent parameters ..
83 // these are the same for most formats
84 uint8_t nbReservedSectors = 1;
85 uint8_t nbHiddenSectors = 1;
86
87 // all these are set below (but initialize here to avoid warning)
88 uint16_t nbSides = 2;
89 uint8_t nbFats = 2;
90 uint8_t nbSectorsPerFat = 3;
91 uint8_t nbSectorsPerCluster = 2;
92 uint16_t nbDirEntry = 112;
93 uint8_t descriptor = 0xF9;
94
95 // now set correct info according to size of image (in sectors!)
96 // and using the same layout as used by Jon in IDEFDISK v 3.1
97 if (nbSectors > 32732) {
98 // 32732 < nbSectors
99 // note: this format is only valid for nbSectors <= 65536
100 nbSides = 32; // copied from a partition from an IDE HD
101 nbFats = 2;
102 nbSectorsPerFat = 12; // copied from a partition from an IDE HD
103 nbSectorsPerCluster = 16;
104 nbDirEntry = 256;
105 descriptor = 0xF0;
106 nbHiddenSectors = 16; // override default from above
107 } else if (nbSectors > 16388) {
108 // 16388 < nbSectors <= 32732
109 nbSides = 2; // unknown yet
110 nbFats = 2;
111 nbSectorsPerFat = 12;
112 nbSectorsPerCluster = 8;
113 nbDirEntry = 256;
114 descriptor = 0XF0;
115 } else if (nbSectors > 8212) {
116 // 8212 < nbSectors <= 16388
117 nbSides = 2; // unknown yet
118 nbFats = 2;
119 nbSectorsPerFat = 12;
120 nbSectorsPerCluster = 4;
121 nbDirEntry = 256;
122 descriptor = 0xF0;
123 } else if (nbSectors > 4126) {
124 // 4126 < nbSectors <= 8212
125 nbSides = 2; // unknown yet
126 nbFats = 2;
127 nbSectorsPerFat = 12;
128 nbSectorsPerCluster = 2;
129 nbDirEntry = 256;
130 descriptor = 0xF0;
131 } else if (nbSectors > 2880) {
132 // 2880 < nbSectors <= 4126
133 nbSides = 2; // unknown yet
134 nbFats = 2;
135 nbSectorsPerFat = 6;
136 nbSectorsPerCluster = 2;
137 nbDirEntry = 224;
138 descriptor = 0xF0;
139 } else if (nbSectors > 1440) {
140 // 1440 < nbSectors <= 2880
141 nbSides = 2; // unknown yet
142 nbFats = 2;
143 nbSectorsPerFat = 5;
144 nbSectorsPerCluster = 2;
145 nbDirEntry = 112;
146 descriptor = 0xF0;
147 } else if (nbSectors > 720) {
148 // normal double sided disk
149 // 720 < nbSectors <= 1440
150 nbSides = 2;
151 nbFats = 2;
152 nbSectorsPerFat = 3;
153 nbSectorsPerCluster = 2;
154 nbDirEntry = 112;
155 descriptor = 0xF9;
156 nbSectors = 1440; // force nbSectors to 1440, why?
157 } else {
158 // normal single sided disk
159 // nbSectors <= 720
160 nbSides = 1;
161 nbFats = 2;
162 nbSectorsPerFat = 2;
163 nbSectorsPerCluster = 2;
164 nbDirEntry = 112;
165 descriptor = 0xF8;
166 nbSectors = 720; // force nbSectors to 720, why?
167 }
168
169 boot.nrSectors = uint16_t(nbSectors); // TODO check for overflow
170 boot.nrSides = nbSides;
171 boot.spCluster = nbSectorsPerCluster;
172 boot.nrFats = nbFats;
173 boot.sectorsFat = nbSectorsPerFat;
174 boot.dirEntries = nbDirEntry;
175 boot.descriptor = descriptor;
176 boot.resvSectors = nbReservedSectors;
177 boot.hiddenSectors = nbHiddenSectors;
178
179 if (!dos1) {
180 // set random volume id
181 boot.vol_id = random_32bit() & 0x7F7F7F7F; // why are bits masked?
182 }
183 unsigned nbRootDirSectors = nbDirEntry / 16;
184 unsigned rootDirStart = 1 + nbFats * nbSectorsPerFat;
185 unsigned firstDataSector = rootDirStart + nbRootDirSectors;
186 return {firstDataSector, descriptor};
187}
188
189void format(SectorAccessibleDisk& disk, bool dos1)
190{
191 // first create a boot sector for given partition size
192 size_t nbSectors = disk.getNbSectors();
193 SectorBuffer buf;
194 auto [firstDataSector, descriptor] = setBootSector(buf.bootSector, nbSectors, dos1);
195 disk.writeSector(0, buf);
196
197 // write empty FAT and directory sectors
198 ranges::fill(buf.raw, 0);
199 for (auto i : xrange(2u, firstDataSector)) {
200 disk.writeSector(i, buf);
201 }
202 // first FAT sector is special:
203 // - first byte contains the media descriptor
204 // - first two clusters must be marked as EOF
205 buf.raw[0] = descriptor;
206 buf.raw[1] = 0xFF;
207 buf.raw[2] = 0xFF;
208 disk.writeSector(1, buf);
209
210 // write 'empty' data sectors
211 ranges::fill(buf.raw, 0xE5);
212 for (auto i : xrange(firstDataSector, nbSectors)) {
213 disk.writeSector(i, buf);
214 }
215}
216
217struct CHS {
218 unsigned cylinder;
219 unsigned head;
220 unsigned sector;
221};
222[[nodiscard]] static constexpr CHS logicalToCHS(unsigned logical)
223{
224 // This is made to fit the openMSX hard disk configuration:
225 // 32 sectors/track 16 heads
226 unsigned tmp = logical + 1;
227 unsigned sector = tmp % 32;
228 if (sector == 0) sector = 32;
229 tmp = (tmp - sector) / 32;
230 unsigned head = tmp % 16;
231 unsigned cylinder = tmp / 16;
232 return {cylinder, head, sector};
233}
234
235void partition(SectorAccessibleDisk& disk, std::span<const unsigned> sizes)
236{
237 assert(sizes.size() <= 31);
238
239 SectorBuffer buf;
240 ranges::fill(buf.raw, 0);
241 buf.pt.header = PARTITION_TABLE_HEADER;
242 buf.pt.end = 0xAA55;
243
244 unsigned partitionOffset = 1;
245 for (auto [i, size] : enumerate(sizes)) {
246 unsigned partitionNbSectors = size;
247 auto& p = buf.pt.part[30 - i];
248 auto [startCylinder, startHead, startSector] =
249 logicalToCHS(partitionOffset);
250 auto [endCylinder, endHead, endSector] =
251 logicalToCHS(partitionOffset + partitionNbSectors - 1);
252 p.boot_ind = (i == 0) ? 0x80 : 0x00; // boot flag
253 p.head = startHead;
254 p.sector = startSector;
255 p.cyl = startCylinder;
256 p.sys_ind = 0x01; // FAT12
257 p.end_head = endHead;
258 p.end_sector = endSector;
259 p.end_cyl = endCylinder;
260 p.start = partitionOffset;
261 p.size = size;
262 DiskPartition diskPartition(disk, partitionOffset, partitionNbSectors);
263 format(diskPartition);
264 partitionOffset += partitionNbSectors;
265 }
266 disk.writeSector(0, buf);
267}
268
269} // namespace openmsx::DiskImageUtils
static const SectorBuffer dos2BootBlock
Definition: BootBlocks.hh:15
static const SectorBuffer dos1BootBlock
Definition: BootBlocks.hh:12
void readSector(size_t sector, SectorBuffer &buf)
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(SectorAccessibleDisk &disk)
Check whether the given disk is partitioned.
void partition(SectorAccessibleDisk &disk, std::span< const unsigned > sizes)
Write a partition table to the given disk and format each partition.
void format(SectorAccessibleDisk &disk, bool dos1)
Format the given disk (= a single partition).
void checkValidPartition(SectorAccessibleDisk &disk, unsigned partition)
Checks whether the disk is partitioned the specified partition exists throws a CommandException if on...
void checkFAT12Partition(SectorAccessibleDisk &disk, unsigned partition)
Like above, but also check whether partition is of type FAT12.
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
size_t size(std::string_view utf8)
uint32_t random_32bit()
Return a random 32-bit value.
Definition: random.hh:67
Endian::UA_L16 dirEntries
Endian::UA_L16 nrSectors
std::array< Partition, 31 > part
std::array< char, 11 > header
std::array< uint8_t, 512 > raw
MSXBootSector bootSector
constexpr auto xrange(T e)
Definition: xrange.hh:133