From a692b22ecc2e9d5d85f55de338276879297dc543 Mon Sep 17 00:00:00 2001 From: Khoo Hao Yit Date: Sat, 17 May 2025 19:13:35 +0800 Subject: [PATCH] patterns: Add support for exFAT (#398) --- README.md | 1 + patterns/exfat.hexpat | 313 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 patterns/exfat.hexpat diff --git a/README.md b/README.md index a550eed..b52e591 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | DTED | | [`patterns/dted.hexpat`](patterns/dted.hexpat) | Digital Terrain Elevation Data (DTED) | | ELF | `application/x-executable` | [`patterns/elf.hexpat`](patterns/elf.hexpat) | ELF header in elf binaries | | EVTX | `application/x-ms-evtx` | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log | +| EXFAT | | [`patterns/exfat.hexpat`](patterns/exfat.hexpat) | Extensible File Allocation Table (exFAT) | | EXT4 | | [`patterns/ext4.hexpat`](patterns/ext4.hexpat) | Ext4 filesystem | | 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 | | FBX | | [`patterns/fbx.hexpat`](patterns/fbx.hexpat) | Kaydara FBX Binary | diff --git a/patterns/exfat.hexpat b/patterns/exfat.hexpat new file mode 100644 index 0000000..8e3da00 --- /dev/null +++ b/patterns/exfat.hexpat @@ -0,0 +1,313 @@ +#pragma magic [45 58 46 41 54 20 20 20] @ 0x03 +#pragma description Extensible File Allocation Table (exFAT) +#pragma author Khoo Hao Yit + +import std.mem; +import std.string; +import std.core; + +struct BootSector { + std::mem::Bytes<3> jumpBoot; + char fileSystemName[8]; + std::mem::Bytes<53> mustBeZero; + u64 partitionOffset; + u64 volumeLength; + u32 fatOffset; + u32 fatLength; + u32 clusterHeapOffset; + u32 clusterCount; + u32 firstClusterOfRootDirectory; + u32 volumeSerialNumber; + u16 fileSystemRevision; + u16 volumeFlags; + u8 bytesPerSectorShift; + u8 sectorsPerClusterShift; + u8 numberOfFats; + u8 driveSelect; + u8 percentInUse; + std::mem::Bytes<7> reserved; + std::mem::Bytes<390> bootCode; + std::mem::Bytes<2> bootSignature; +}; + +struct ExtendedBootSector { + std::mem::Bytes bootCode; + std::mem::Bytes<4> bootSignature; +}; + +struct Parameter { + std::mem::Bytes<16> guid; + std::mem::Bytes<32> data; +}; + +struct OemParameter { + Parameter parameters[10]; + std::mem::Bytes reserved; +}; + +u64 bytesPerSector; +u64 startOfClusterHeap; +u64 bytesPerCluster; +u64 fatAddress; + +struct BootRegion { + BootSector bootSector; + + bytesPerSector = 1 << bootSector.bytesPerSectorShift; + bytesPerCluster = bytesPerSector << bootSector.sectorsPerClusterShift; + + ExtendedBootSector extendedBootSectors[8]; + OemParameter oemParameter; + std::mem::Bytes reservedSector; + std::mem::Bytes bootChecksum; +}; + +struct VolumeLabelEntry { + u8 entryType; + u8 characterCount; + char volumeLabel[22]; + std::mem::Bytes<8> reserved; +}; + +bitfield FileAttribute { + readOnly : 1; + hidden : 1; + system : 1; + reserved : 1; + directory : 1; + archive : 1; + reserved1 : 10; +}; + +auto currentClusterIndex = -1; +auto currentClusterSize = -1; +auto currentIsDirectory = -1; + +struct FileEntry { + u8 entryType; + u8 secondayEntryCount; + u16 checksum; + FileAttribute attributes; + std::mem::Bytes<2> reserved; + u32 creationDatetime; + u32 modificationDatetime; + u32 accessDatetime; + u8 creationTimeHundredths; + u8 modificationTimeHundredths; + u8 creationUtcOffset; + u8 modificationUtcOffset; + u8 accessUtcOffset; + std::mem::Bytes<7> reserved1; + + currentIsDirectory = attributes.directory; +}; + +bitfield GeneralSecondaryFlags { + allocationPossible : 1; + noFatChain : 1; + customDefined : 6; +}; + +struct FileNameEntry { + u8 entryType; + u8 flags; + char16 name[15]; +}; + +fn divideCeil(auto a, auto b) { + auto result = a / b; + auto remain = a % b; + if (remain) { + result += 1; + } + return result; +}; + +struct ContinuousFatRange { + u32 fatRange[ClusterSize] @ fatAddress + ClusterIndex * 4 [[sealed]]; +} [[name(std::format("ContinuousFatRange<{}, {}>", ClusterIndex, ClusterSize))]]; + +struct FatChain { + auto clusterIndex = currentClusterIndex; + auto clusterSize = currentClusterSize; + currentClusterIndex = -1; + currentClusterSize = -1; + if (clusterIndex == -1) { + std::error(std::format("Invalid cluster index: {}", clusterIndex)); + } + if (clusterSize == -1) { + auto startingFatRange = fatAddress + clusterIndex * 4; + u32 clusters[while( + $ == startingFatRange + || $ == fatAddress + std::mem::read_unsigned($ - 4, 4) * 4 + )] @ startingFatRange [[hidden]]; + clusterSize = std::core::member_count(clusters); + } + ContinuousFatRange data @ 0; + try { + auto nextClusterIndex = clusters[clusterSize - 1]; + if (nextClusterIndex != 0xffffffff && nextClusterIndex != 0) { + currentClusterIndex = nextClusterIndex; + FatChain next @ 0 [[inline]]; + } + } +} [[name(std::format("FatChain<{}>", clusterIndex))]]; + +struct ContinuousDataRange { + std::mem::Bytes dataRange @ + startOfClusterHeap + ClusterIndex * bytesPerCluster; +} [[name(std::format("ContinuousDataRange<{}, {}>", ClusterIndex, ClusterSize))]]; + +struct DataChain { + auto clusterIndex = currentClusterIndex; + auto clusterSize = currentClusterSize; + currentClusterIndex = -1; + currentClusterSize = -1; + if (clusterIndex == -1) { + std::error(std::format("Invalid cluster index: {}", clusterIndex)); + } + if (clusterSize == -1) { + auto startingFatRange = fatAddress + clusterIndex * 4; + u32 clusters[while( + $ == startingFatRange + || $ == fatAddress + std::mem::read_unsigned($ - 4, 4) * 4 + )] @ startingFatRange [[hidden]]; + clusterSize = std::core::member_count(clusters); + } + ContinuousDataRange data @ 0; + try { + auto nextClusterIndex = clusters[clusterSize - 1]; + if (nextClusterIndex != 0xffffffff && nextClusterIndex != 0) { + currentClusterIndex = nextClusterIndex; + DataChain next @ 0 [[inline]]; + } + } +} [[name(std::format("DataChain<{}>", clusterIndex))]]; + +struct UpcaseTableEntry { + u8 entryType; + std::mem::Bytes<3> reserved; + u32 tableChecksum; + std::mem::Bytes<12> reserved1; + u32 firstCluster; + u64 dataLength; + + ContinuousFatRange fatRange; + ContinuousDataRange dataRange; +}; + +struct AllocationBitmapEntry { + u8 entryType; + u8 bitmapFlags; + std::mem::Bytes<18> reserved; + u32 firstCluster; + u64 dataLength; + + ContinuousFatRange fatRange; + ContinuousDataRange dataRange; +}; + +using StreamExtensionEntry; +struct DirectoryEntry { + u8 entryType @ addressof(this) [[hidden]]; + match (entryType) { + (0x83 | 0x03): VolumeLabelEntry; + (0x81): AllocationBitmapEntry; + (0x82): UpcaseTableEntry; + (0x85 | 0x05): FileEntry; + (0xc0 | 0x40): StreamExtensionEntry; + (0xc1 | 0x41): FileNameEntry; + (_): std::mem::Bytes<32> [[name(std::format("UnknownEntry @ {:#X}", addressof(this)))]]; + } +} [[inline]]; + +struct ContinuousDirectoryEntry { + DirectoryEntry entry[ClusterSize * bytesPerCluster / 32] @ + startOfClusterHeap + ClusterIndex * bytesPerCluster + [[inline]]; +} [[name(std::format("ContinuousDirectoryEntry<{}, {}>", ClusterIndex, ClusterSize))]]; + +struct DirectoryEntryChain { + auto clusterIndex = currentClusterIndex; + auto clusterSize = currentClusterSize; + currentClusterIndex = -1; + currentClusterSize = -1; + if (clusterIndex == -1) { + std::error(std::format("Invalid cluster index: {}", clusterIndex)); + } + if (clusterSize == -1) { + auto startingFatRange = fatAddress + clusterIndex * 4; + u32 clusters[while( + $ == startingFatRange + || $ == fatAddress + std::mem::read_unsigned($ - 4, 4) * 4 + )] @ startingFatRange [[hidden]]; + clusterSize = std::core::member_count(clusters); + } + try { + ContinuousDirectoryEntry data @ 0; + } catch { + std::warning(std::format("Error trying to parse ContinuousDirectoryEntry at {:#x} (Cluster index {} with cluster size of {})", startOfClusterHeap + clusterIndex * bytesPerCluster, clusterIndex, clusterSize)); + } + try { + auto nextClusterIndex = clusters[clusterSize - 1]; + if (nextClusterIndex != 0xffffffff && nextClusterIndex != 0) { + currentClusterIndex = nextClusterIndex; + DirectoryEntryChain next @ 0 [[inline]]; + } + } + currentIsDirectory = -1; +} [[name(std::format("DirectoryEntryChain<{}>", clusterIndex))]]; + +struct StreamExtensionEntry { + u8 entryType; + GeneralSecondaryFlags secondaryFlags; + std::mem::Bytes<1> reserved; + u8 nameLength; + u16 nameHash; + std::mem::Bytes<2> reserved1; + u64 validDateLength; + std::mem::Bytes<4> reserved2; + u32 firstCluster; + u64 dataLength; + + if (entryType & 0x80 && currentIsDirectory == 1) { + currentClusterIndex = firstCluster; + if (secondaryFlags.noFatChain) { + currentClusterSize = divideCeil(dataLength, bytesPerCluster); + } + FatChain fatChain; + currentClusterIndex = firstCluster; + if (secondaryFlags.noFatChain) { + currentClusterSize = divideCeil(dataLength, bytesPerCluster); + } + DirectoryEntryChain directoryChain; + } + else if (dataLength) { + currentClusterIndex = firstCluster; + if (secondaryFlags.noFatChain) { + currentClusterSize = divideCeil(dataLength, bytesPerCluster); + } + FatChain fatChain; + currentClusterIndex = firstCluster; + if (secondaryFlags.noFatChain) { + currentClusterSize = divideCeil(dataLength, bytesPerCluster); + } + DataChain data @ 0; + } +}; + +BootRegion bootRegion @ 0 [[name("BootRegion")]]; +BootRegion backupBootRegion @ 12 * bytesPerSector [[name("BackupBootRegion")]]; + +startOfClusterHeap = + bootRegion.bootSector.clusterHeapOffset * bytesPerSector + - 2 * bytesPerCluster; +fatAddress = + bootRegion.bootSector.fatOffset * bytesPerSector; + +std::mem::Bytes fat @ fatAddress [[hidden]]; + +currentClusterIndex = bootRegion.bootSector.firstClusterOfRootDirectory; +FatChain rootDirectoryFatChain @ 0; +currentClusterIndex = bootRegion.bootSector.firstClusterOfRootDirectory; +DirectoryEntryChain rootDirectory @ 0;