Skip to content

Commit a692b22

Browse files
authored
patterns: Add support for exFAT (#398)
1 parent 2ae0499 commit a692b22

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
6767
| DTED | | [`patterns/dted.hexpat`](patterns/dted.hexpat) | Digital Terrain Elevation Data (DTED) |
6868
| ELF | `application/x-executable` | [`patterns/elf.hexpat`](patterns/elf.hexpat) | ELF header in elf binaries |
6969
| EVTX | `application/x-ms-evtx` | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log |
70+
| EXFAT | | [`patterns/exfat.hexpat`](patterns/exfat.hexpat) | Extensible File Allocation Table (exFAT) |
7071
| EXT4 | | [`patterns/ext4.hexpat`](patterns/ext4.hexpat) | Ext4 filesystem |
7172
| FAS | | [`patterns/fas_oskasoftware.hexpat`](patterns/fas_oskasoftware.hexpat) [`patterns/fas_oskasoftware_old.hexpat`](patterns/fas_oskasoftware_old.hexpat) (Old versions of Oska DeskMate) | Oska Software DeskMates FAS (Frames and Sequences) file |
7273
| FBX | | [`patterns/fbx.hexpat`](patterns/fbx.hexpat) | Kaydara FBX Binary |

patterns/exfat.hexpat

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
#pragma magic [45 58 46 41 54 20 20 20] @ 0x03
2+
#pragma description Extensible File Allocation Table (exFAT)
3+
#pragma author Khoo Hao Yit
4+
5+
import std.mem;
6+
import std.string;
7+
import std.core;
8+
9+
struct BootSector {
10+
std::mem::Bytes<3> jumpBoot;
11+
char fileSystemName[8];
12+
std::mem::Bytes<53> mustBeZero;
13+
u64 partitionOffset;
14+
u64 volumeLength;
15+
u32 fatOffset;
16+
u32 fatLength;
17+
u32 clusterHeapOffset;
18+
u32 clusterCount;
19+
u32 firstClusterOfRootDirectory;
20+
u32 volumeSerialNumber;
21+
u16 fileSystemRevision;
22+
u16 volumeFlags;
23+
u8 bytesPerSectorShift;
24+
u8 sectorsPerClusterShift;
25+
u8 numberOfFats;
26+
u8 driveSelect;
27+
u8 percentInUse;
28+
std::mem::Bytes<7> reserved;
29+
std::mem::Bytes<390> bootCode;
30+
std::mem::Bytes<2> bootSignature;
31+
};
32+
33+
struct ExtendedBootSector {
34+
std::mem::Bytes<bytesPerSector - 4> bootCode;
35+
std::mem::Bytes<4> bootSignature;
36+
};
37+
38+
struct Parameter {
39+
std::mem::Bytes<16> guid;
40+
std::mem::Bytes<32> data;
41+
};
42+
43+
struct OemParameter {
44+
Parameter parameters[10];
45+
std::mem::Bytes<bytesPerSector - 480> reserved;
46+
};
47+
48+
u64 bytesPerSector;
49+
u64 startOfClusterHeap;
50+
u64 bytesPerCluster;
51+
u64 fatAddress;
52+
53+
struct BootRegion {
54+
BootSector bootSector;
55+
56+
bytesPerSector = 1 << bootSector.bytesPerSectorShift;
57+
bytesPerCluster = bytesPerSector << bootSector.sectorsPerClusterShift;
58+
59+
ExtendedBootSector extendedBootSectors[8];
60+
OemParameter oemParameter;
61+
std::mem::Bytes<bytesPerSector> reservedSector;
62+
std::mem::Bytes<bytesPerSector> bootChecksum;
63+
};
64+
65+
struct VolumeLabelEntry {
66+
u8 entryType;
67+
u8 characterCount;
68+
char volumeLabel[22];
69+
std::mem::Bytes<8> reserved;
70+
};
71+
72+
bitfield FileAttribute {
73+
readOnly : 1;
74+
hidden : 1;
75+
system : 1;
76+
reserved : 1;
77+
directory : 1;
78+
archive : 1;
79+
reserved1 : 10;
80+
};
81+
82+
auto currentClusterIndex = -1;
83+
auto currentClusterSize = -1;
84+
auto currentIsDirectory = -1;
85+
86+
struct FileEntry {
87+
u8 entryType;
88+
u8 secondayEntryCount;
89+
u16 checksum;
90+
FileAttribute attributes;
91+
std::mem::Bytes<2> reserved;
92+
u32 creationDatetime;
93+
u32 modificationDatetime;
94+
u32 accessDatetime;
95+
u8 creationTimeHundredths;
96+
u8 modificationTimeHundredths;
97+
u8 creationUtcOffset;
98+
u8 modificationUtcOffset;
99+
u8 accessUtcOffset;
100+
std::mem::Bytes<7> reserved1;
101+
102+
currentIsDirectory = attributes.directory;
103+
};
104+
105+
bitfield GeneralSecondaryFlags {
106+
allocationPossible : 1;
107+
noFatChain : 1;
108+
customDefined : 6;
109+
};
110+
111+
struct FileNameEntry {
112+
u8 entryType;
113+
u8 flags;
114+
char16 name[15];
115+
};
116+
117+
fn divideCeil(auto a, auto b) {
118+
auto result = a / b;
119+
auto remain = a % b;
120+
if (remain) {
121+
result += 1;
122+
}
123+
return result;
124+
};
125+
126+
struct ContinuousFatRange<auto ClusterIndex, auto ClusterSize> {
127+
u32 fatRange[ClusterSize] @ fatAddress + ClusterIndex * 4 [[sealed]];
128+
} [[name(std::format("ContinuousFatRange<{}, {}>", ClusterIndex, ClusterSize))]];
129+
130+
struct FatChain {
131+
auto clusterIndex = currentClusterIndex;
132+
auto clusterSize = currentClusterSize;
133+
currentClusterIndex = -1;
134+
currentClusterSize = -1;
135+
if (clusterIndex == -1) {
136+
std::error(std::format("Invalid cluster index: {}", clusterIndex));
137+
}
138+
if (clusterSize == -1) {
139+
auto startingFatRange = fatAddress + clusterIndex * 4;
140+
u32 clusters[while(
141+
$ == startingFatRange
142+
|| $ == fatAddress + std::mem::read_unsigned($ - 4, 4) * 4
143+
)] @ startingFatRange [[hidden]];
144+
clusterSize = std::core::member_count(clusters);
145+
}
146+
ContinuousFatRange<clusterIndex, clusterSize> data @ 0;
147+
try {
148+
auto nextClusterIndex = clusters[clusterSize - 1];
149+
if (nextClusterIndex != 0xffffffff && nextClusterIndex != 0) {
150+
currentClusterIndex = nextClusterIndex;
151+
FatChain next @ 0 [[inline]];
152+
}
153+
}
154+
} [[name(std::format("FatChain<{}>", clusterIndex))]];
155+
156+
struct ContinuousDataRange<auto ClusterIndex, auto ClusterSize> {
157+
std::mem::Bytes<ClusterSize * bytesPerCluster> dataRange @
158+
startOfClusterHeap + ClusterIndex * bytesPerCluster;
159+
} [[name(std::format("ContinuousDataRange<{}, {}>", ClusterIndex, ClusterSize))]];
160+
161+
struct DataChain {
162+
auto clusterIndex = currentClusterIndex;
163+
auto clusterSize = currentClusterSize;
164+
currentClusterIndex = -1;
165+
currentClusterSize = -1;
166+
if (clusterIndex == -1) {
167+
std::error(std::format("Invalid cluster index: {}", clusterIndex));
168+
}
169+
if (clusterSize == -1) {
170+
auto startingFatRange = fatAddress + clusterIndex * 4;
171+
u32 clusters[while(
172+
$ == startingFatRange
173+
|| $ == fatAddress + std::mem::read_unsigned($ - 4, 4) * 4
174+
)] @ startingFatRange [[hidden]];
175+
clusterSize = std::core::member_count(clusters);
176+
}
177+
ContinuousDataRange<clusterIndex, clusterSize> data @ 0;
178+
try {
179+
auto nextClusterIndex = clusters[clusterSize - 1];
180+
if (nextClusterIndex != 0xffffffff && nextClusterIndex != 0) {
181+
currentClusterIndex = nextClusterIndex;
182+
DataChain next @ 0 [[inline]];
183+
}
184+
}
185+
} [[name(std::format("DataChain<{}>", clusterIndex))]];
186+
187+
struct UpcaseTableEntry {
188+
u8 entryType;
189+
std::mem::Bytes<3> reserved;
190+
u32 tableChecksum;
191+
std::mem::Bytes<12> reserved1;
192+
u32 firstCluster;
193+
u64 dataLength;
194+
195+
ContinuousFatRange<firstCluster, 1> fatRange;
196+
ContinuousDataRange<firstCluster, 1> dataRange;
197+
};
198+
199+
struct AllocationBitmapEntry {
200+
u8 entryType;
201+
u8 bitmapFlags;
202+
std::mem::Bytes<18> reserved;
203+
u32 firstCluster;
204+
u64 dataLength;
205+
206+
ContinuousFatRange<firstCluster, 1> fatRange;
207+
ContinuousDataRange<firstCluster, 1> dataRange;
208+
};
209+
210+
using StreamExtensionEntry;
211+
struct DirectoryEntry {
212+
u8 entryType @ addressof(this) [[hidden]];
213+
match (entryType) {
214+
(0x83 | 0x03): VolumeLabelEntry;
215+
(0x81): AllocationBitmapEntry;
216+
(0x82): UpcaseTableEntry;
217+
(0x85 | 0x05): FileEntry;
218+
(0xc0 | 0x40): StreamExtensionEntry;
219+
(0xc1 | 0x41): FileNameEntry;
220+
(_): std::mem::Bytes<32> [[name(std::format("UnknownEntry @ {:#X}", addressof(this)))]];
221+
}
222+
} [[inline]];
223+
224+
struct ContinuousDirectoryEntry<auto ClusterIndex, auto ClusterSize> {
225+
DirectoryEntry entry[ClusterSize * bytesPerCluster / 32] @
226+
startOfClusterHeap + ClusterIndex * bytesPerCluster
227+
[[inline]];
228+
} [[name(std::format("ContinuousDirectoryEntry<{}, {}>", ClusterIndex, ClusterSize))]];
229+
230+
struct DirectoryEntryChain {
231+
auto clusterIndex = currentClusterIndex;
232+
auto clusterSize = currentClusterSize;
233+
currentClusterIndex = -1;
234+
currentClusterSize = -1;
235+
if (clusterIndex == -1) {
236+
std::error(std::format("Invalid cluster index: {}", clusterIndex));
237+
}
238+
if (clusterSize == -1) {
239+
auto startingFatRange = fatAddress + clusterIndex * 4;
240+
u32 clusters[while(
241+
$ == startingFatRange
242+
|| $ == fatAddress + std::mem::read_unsigned($ - 4, 4) * 4
243+
)] @ startingFatRange [[hidden]];
244+
clusterSize = std::core::member_count(clusters);
245+
}
246+
try {
247+
ContinuousDirectoryEntry<clusterIndex, clusterSize> data @ 0;
248+
} catch {
249+
std::warning(std::format("Error trying to parse ContinuousDirectoryEntry at {:#x} (Cluster index {} with cluster size of {})", startOfClusterHeap + clusterIndex * bytesPerCluster, clusterIndex, clusterSize));
250+
}
251+
try {
252+
auto nextClusterIndex = clusters[clusterSize - 1];
253+
if (nextClusterIndex != 0xffffffff && nextClusterIndex != 0) {
254+
currentClusterIndex = nextClusterIndex;
255+
DirectoryEntryChain next @ 0 [[inline]];
256+
}
257+
}
258+
currentIsDirectory = -1;
259+
} [[name(std::format("DirectoryEntryChain<{}>", clusterIndex))]];
260+
261+
struct StreamExtensionEntry {
262+
u8 entryType;
263+
GeneralSecondaryFlags secondaryFlags;
264+
std::mem::Bytes<1> reserved;
265+
u8 nameLength;
266+
u16 nameHash;
267+
std::mem::Bytes<2> reserved1;
268+
u64 validDateLength;
269+
std::mem::Bytes<4> reserved2;
270+
u32 firstCluster;
271+
u64 dataLength;
272+
273+
if (entryType & 0x80 && currentIsDirectory == 1) {
274+
currentClusterIndex = firstCluster;
275+
if (secondaryFlags.noFatChain) {
276+
currentClusterSize = divideCeil(dataLength, bytesPerCluster);
277+
}
278+
FatChain fatChain;
279+
currentClusterIndex = firstCluster;
280+
if (secondaryFlags.noFatChain) {
281+
currentClusterSize = divideCeil(dataLength, bytesPerCluster);
282+
}
283+
DirectoryEntryChain directoryChain;
284+
}
285+
else if (dataLength) {
286+
currentClusterIndex = firstCluster;
287+
if (secondaryFlags.noFatChain) {
288+
currentClusterSize = divideCeil(dataLength, bytesPerCluster);
289+
}
290+
FatChain fatChain;
291+
currentClusterIndex = firstCluster;
292+
if (secondaryFlags.noFatChain) {
293+
currentClusterSize = divideCeil(dataLength, bytesPerCluster);
294+
}
295+
DataChain data @ 0;
296+
}
297+
};
298+
299+
BootRegion bootRegion @ 0 [[name("BootRegion")]];
300+
BootRegion backupBootRegion @ 12 * bytesPerSector [[name("BackupBootRegion")]];
301+
302+
startOfClusterHeap =
303+
bootRegion.bootSector.clusterHeapOffset * bytesPerSector
304+
- 2 * bytesPerCluster;
305+
fatAddress =
306+
bootRegion.bootSector.fatOffset * bytesPerSector;
307+
308+
std::mem::Bytes<bootRegion.bootSector.fatLength * bytesPerSector> fat @ fatAddress [[hidden]];
309+
310+
currentClusterIndex = bootRegion.bootSector.firstClusterOfRootDirectory;
311+
FatChain rootDirectoryFatChain @ 0;
312+
currentClusterIndex = bootRegion.bootSector.firstClusterOfRootDirectory;
313+
DirectoryEntryChain rootDirectory @ 0;

0 commit comments

Comments
 (0)